Skip to content

Conversation

@alchemistklk
Copy link
Contributor

@alchemistklk alchemistklk commented Oct 19, 2025

…tail structure

  • Introduced ToolCallResult model to manage tool call data, including status, input, output, and timestamps.
  • Updated ActionDetail and ActionStep to include toolCalls, allowing for better tracking of tool interactions within actions.
  • Enhanced the ToolCallService to handle tool call persistence, grouping, and status updates.
  • Improved the rendering of tool call results in the UI, ensuring compliance with coding standards and performance optimizations.
  • Added necessary updates to OpenAPI schema and TypeScript types for better integration and type safety.

Summary

Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

Tip

Close issue syntax: Fixes #<issue number> or Resolves #<issue number>, see documentation for more details.

Impact Areas

Please check the areas this PR affects:

  • Multi-threaded Dialogues
  • AI-Powered Capabilities (Web Search, Knowledge Base Search, Question Recommendations)
  • Context Memory & References
  • Knowledge Base Integration & RAG
  • Quotes & Citations
  • AI Document Editing & WYSIWYG
  • Free-form Canvas Interface
  • Other

Screenshots/Videos

Before After
... ...

Checklist

Important

Please review the checklist below before submitting your pull request.

  • This change requires a documentation update, included: Refly Documentation
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran dev/reformat(backend) and cd web && npx lint-staged(frontend) to appease the lint gods

Summary by CodeRabbit

  • New Features

    • Tool invocations now include status (executing/completed/failed), duration, error/result details, and per-invocation versioning for retries.
  • UI

    • Multi-state status icons, duration display, merged duplicate tool-use entries, and enriched tool-call metadata attributes for rendering.
  • Streaming

    • Tool-call events streamed and handled separately for real-time updates.
  • API / Schema

    • ToolCallResult surfaced across public schemas and included in action/step responses and events.
  • Localization

    • Added "parameters" and "result" translations for Markdown components.

…tail structure

- Introduced ToolCallResult model to manage tool call data, including status, input, output, and timestamps.
- Updated ActionDetail and ActionStep to include toolCalls, allowing for better tracking of tool interactions within actions.
- Enhanced the ToolCallService to handle tool call persistence, grouping, and status updates.
- Improved the rendering of tool call results in the UI, ensuring compliance with coding standards and performance optimizations.
- Added necessary updates to OpenAPI schema and TypeScript types for better integration and type safety.
@coderabbitai
Copy link

coderabbitai bot commented Oct 19, 2025

Walkthrough

Adds end-to-end tool-call tracking: Prisma schema fields/index for ToolCallResult; new ToolCallModule/Service (XML/SSE/persist/fetch/group/derive-status); integrates tool calls into SkillInvoker and Action flows; frontend parsing, rendering, SSE, types, OpenAPI, canvas node versioning, and i18n updates.

Changes

Cohort / File(s) Summary
Prisma Data Model
apps/api/prisma/schema.prisma
Extend ToolCallResult with resultId, version (default 0), status (default "executing"), optional stepName, and add composite index @@index([resultId, version]).
ToolCall Module & Service
apps/api/src/modules/tool-call/tool-call.module.ts, apps/api/src/modules/tool-call/tool-call.service.ts
New ToolCallModule and ToolCallService: XML generation, SSE emit, persist/upsert/fetch tool-call results, grouping/attaching by step, derive/update action status, helpers/types (ToolCallStatus, ToolEventPayload).
Action DTOs & Service Integration
apps/api/src/modules/action/action.dto.ts, apps/api/src/modules/action/action.module.ts, apps/api/src/modules/action/action.service.ts
Add ActionStepDetail (steps may include toolCalls), mapper toolCallResultPO2DTO; wire ToolCallModule into ActionModule; inject ToolCallService into ActionService and enrich getActionResult to fetch/group/attach tool calls and derive status.
Skill Invoker Integration
apps/api/src/modules/skill/skill-invoker.service.ts, apps/api/src/modules/skill/skill.module.ts
Inject ToolCallService into SkillInvokerService; emit/persist tool-call stream/events on tool start/stream/end/error; add ToolCallModule to SkillModule imports.
Frontend: Node Versioning & Canvas Hooks
packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx, packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx, packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts, packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts
Propagate/increment version on rerun/retry; pass version in action payloads; add SSE handlers for tool_call_start/stream, parse/merge tool-use XML into step.toolCalls, local latest-steps caching and node-update optimizations.
Frontend: Markdown Tool-Call Plugin & Renderer
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts, packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts, packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx, packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts
Parse tool_use chunks (callId, status, timestamps, error, input/output), exports parseToolCallFromChunk and mergeToolCallById; dedupe/merge duplicate nodes in rehype post-pass; add ToolCallStatus enum, status icons, attributes, duration/error display, and rendering adjustments.
SSE Post Utility
packages/ai-workspace-common/src/utils/sse-post.ts
Extend ssePost signature with onToolCallStart and onToolCallStream callbacks; route tool_call_start/stream events separately from batched flow.
OpenAPI & Types
packages/openapi-schema/schema.yml, packages/openapi-schema/src/schemas.gen.ts, packages/openapi-schema/src/types.gen.ts, packages/ai-workspace-common/src/requests/types.gen.ts
Add ToolCallResult schema/type; add toolCalls to ActionStep/ActionResult; add toolCallResult to SkillEvent; extend skill event types with tool_call variants; export tool-call status unions.
Utilities & Merge Logic
packages/utils/src/step.ts
Merge toolCalls by callId when merging steps to deduplicate/merge results.
i18n
packages/i18n/src/en-US/ui.ts, packages/i18n/src/zh-Hans/ui.ts
Add Markdown translation keys parameters and result.
IDE Launch Config
.vscode/launch.json
Replace debug launch configuration with "Launch API (debug)" and an "Attach to API" configuration.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Frontend
    participant SkillInvoker
    participant ToolCallService
    participant DB[(Database)]
    participant SSE

    Frontend->>SkillInvoker: invoke action (resultId, version)
    SkillInvoker->>ToolCallService: generateToolUseXML(startTs)
    ToolCallService-->>SkillInvoker: xmlChunk
    SkillInvoker->>ToolCallService: emitToolUseStream(res,{xmlContent,toolCallId,step})
    ToolCallService->>SSE: writeSSEResponse(event: tool_call_stream, xml)
    SSE-->>Client: stream tool_use chunk

    alt tool completes
        SkillInvoker->>ToolCallService: persistToolCallResult(..., status=COMPLETED)
    else tool errors
        SkillInvoker->>ToolCallService: persistToolCallResult(..., status=FAILED, error)
    end
    ToolCallService->>DB: upsert ToolCallResult
    ToolCallService->>DB: deriveAndUpdateActionStatus(resultId, version)
Loading
sequenceDiagram
    participant Client
    participant ActionService
    participant ToolCallService
    participant DB[(Database)]

    Client->>ActionService: getActionResult(resultId, version)
    ActionService->>DB: fetch ActionResult + steps
    ActionService->>ToolCallService: fetchToolCalls(resultId, version)
    ToolCallService->>DB: query ToolCallResult[]
    DB-->>ToolCallService: toolCalls
    ToolCallService->>ToolCallService: groupToolCallsByStep(steps, toolCalls)
    ToolCallService-->>ActionService: attachToolCallsToSteps(...)
    ActionService-->>Client: ActionDetail with steps[].toolCalls and derived status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • mrcfps
  • anthhub

Poem

🐰
I hop through streams and stitch XML thread,
Tool calls hum while versions tread.
I merge the chunks and label each call,
Statuses tracked — I celebrate them all.
Hooray — the rabbit logs it all with a nibble and a sprawl! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description is substantially incomplete and leaves template placeholders unfilled. While the author provided useful bullet points summarizing the changes, the critical "Summary" section still contains template placeholder text ("Please include a summary of the change...") rather than actual content. Required information is missing or inadequate: no issue reference is provided (no "Fixes #" or "Resolves #" syntax), motivation and context are absent beyond the bullet list, dependencies are not explicitly stated, impact areas are entirely unchecked despite clear relevance to the codebase, and the Screenshots/Videos section shows only placeholder content. Additionally, the documentation checkbox is unchecked while the author claims documentation was updated. Replace the template placeholder text in the Summary section with a concrete summary of the changes. Add an issue reference using "Fixes #" or "Resolves #" syntax if applicable. Provide explicit motivation and context explaining why tool-call management and action detail enhancement were needed. Explicitly state any dependencies (or confirm none if that's the case). Check the appropriate Impact Areas checkboxes (likely "Free-form Canvas Interface" and "AI-Powered Capabilities" based on the changes). Address the documentation checkbox inconsistency and provide screenshots if relevant UI changes were made.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "feat(tool-call): implement tool call management and enhance action detail structure" accurately and specifically describes the primary changes in the changeset. It follows conventional commit format and clearly indicates both the feature area (tool-call) and the main objectives: implementing tool call management and enhancing the action detail structure. The title is concise, avoids vague terminology, and aligns well with the substantial changes across the ToolCallService, ActionDetail/ActionStep structures, and UI rendering layers evident in the raw summary.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/tool_call_status

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx (2)

233-244: Retry payload ignores the latest selected toolsets.

handleRetry sends selectedToolsets from node metadata, not user-updated state. Use the state variable to avoid silently dropping user choices.

Apply:

-        selectedToolsets: nodeSelectedToolsets,
+        selectedToolsets,

Also add selectedToolsets to the dependency array.

-  }, [resultId, title, canvasId, invokeAction, resetFailedState, setNodeData, node.id, node.data]);
+  }, [resultId, title, canvasId, invokeAction, resetFailedState, setNodeData, node.id, node.data, selectedToolsets]);

236-244: Retry should use the current query, not the title.

Users may edit the query; sending title loses intent.

Apply:

-        query: title,
+        query: currentQuery ?? title,

And include currentQuery in dependencies:

-  }, [resultId, title, canvasId, invokeAction, resetFailedState, setNodeData, node.id, node.data]);
+  }, [resultId, title, currentQuery, canvasId, invokeAction, resetFailedState, setNodeData, node.id, node.data, selectedToolsets]);
apps/api/src/modules/skill/skill-invoker.service.ts (3)

169-171: Potential null dereference on userPo.uiLocale

userPo can be null; direct access risks a crash.

Apply:

-        uiLocale: userPo.uiLocale,
+        uiLocale: userPo?.uiLocale,

As per coding guidelines: use optional chaining when accessing object properties.


274-275: Avoid logging raw user input (PII/compliance risk)

Logging full input may leak PII in logs. Log high‑level metadata only.

Suggested:

-    this.logger.log(
-      `invoke skill with input: ${JSON.stringify(input)}, resultId: ${resultId}, version: ${version}`,
-    );
+    this.logger.log(
+      `invoke skill: resultId=${resultId}, version=${version}, queryLength=${String(input?.query?.length ?? 0)}`
+    );

Consider adding a redact helper for structured logs. Based on coding guidelines.


836-836: Guard SSE end event when res is undefined

Avoid calling writeSSEResponse with undefined res.

Apply:

-      writeSSEResponse(res, { event: 'end', resultId, version });
+      if (res) {
+        writeSSEResponse(res, { event: 'end', resultId, version });
+      }
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx (1)

61-82: Missing prop typing: data-tool-toolset-key is used but not declared.

This breaks TS type-checking.

 interface ToolCallProps {
   'data-tool-name'?: string;
+  'data-tool-toolset-key'?: string;
   'data-tool-call-id'?: string;
   'data-tool-call-status'?: string;
   'data-tool-created-at'?: string;
   'data-tool-updated-at'?: string;
   'data-tool-arguments'?: string;
   'data-tool-result'?: string;
   'data-tool-type'?: 'use' | 'result';
   ...
   'data-tool-error'?: string;
   id?: string;
   mode?: MarkdownMode;
 }
packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx (1)

806-815: Invalid Tailwind class z-1 confirmed; however, suggested replacement z-10 may be semantically incorrect.

The review comment is correct that z-1 is not a valid Tailwind class—it is not defined in the project's Tailwind configuration. However, the suggested replacement to z-10 appears semantically wrong for a base node container. Standard Tailwind only provides z-0, z-10, z-20, z-30, z-40, and z-50. For a primary container element beneath overlays (which use z-10), z-0 would be the appropriate Tailwind value.

This issue also exists across multiple canvas node files (start.tsx, website.tsx, video.tsx, skill.tsx, resource.tsx, code-artifact.tsx, audio.tsx, etc.), not just skill-response.tsx, suggesting a systemic pattern rather than an isolated mistake.

🧹 Nitpick comments (21)
packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx (1)

223-231: Guard against rapid retries causing duplicate versions/requests.

Double-clicks can compute the same nextVersion and fire multiple invokes.

Options:

  • Disable the retry button while isStreaming or during a local “retrying” flag.
  • Derive nextVersion from the latest store result/version inside the callback before invoking.

Example minimal guard:

+  const [retrying, setRetrying] = useState(false);
   const handleRetry = useCallback(() => {
+    if (retrying || isStreaming) { return; }
+    setRetrying(true);
     // Reset failed state before retrying
     resetFailedState(resultId);
     // Update node status immediately to show "waiting" state
     const nextVersion = (node.data?.metadata?.version || 0) + 1;
     setNodeData(node.id, {
       ...node.data,
       metadata: {
         ...node.data?.metadata,
         status: 'waiting',
         version: nextVersion,
       },
     });
     invokeAction(
       {
         resultId,
-        query: title,
+        query: currentQuery ?? title,
         selectedSkill: {
           name: actionMeta?.name || 'commonQnA',
         },
         contextItems,
-        selectedToolsets: nodeSelectedToolsets,
+        selectedToolsets,
         version: nextVersion,
       },
       {
         entityId: canvasId,
         entityType: 'canvas',
       },
     );
+    setRetrying(false);
   }, [/* include: retrying, isStreaming, currentQuery, selectedToolsets, ... */]);

Also applies to: 243-249

apps/api/src/modules/tool-call/tool-call.module.ts (1)

5-10: Module scaffold is minimal and correct.

Provides and exports ToolCallService; good separation. Consider adding @global() if widely used to reduce import churn (optional).

packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts (1)

35-46: Type safety and Visitor usage.

Everything is typed as any; tighten types and visitor signatures to match unist/hast.

Apply:

-import { visit } from 'unist-util-visit';
+import { visit } from 'unist-util-visit';
+import type { Root } from 'hast';
+import type { Parent } from 'unist';

-function rehypePlugin() {
-  return (tree: any) => {
+function rehypePlugin() {
+  return (tree: Root) => {
     visit(tree, (node, index, parent) => {
       // ...
     });
 
     // Post-pass: merge duplicated tool nodes by callId, keeping the first occurrence
-    const toolNodes: Array<{ node: any; index: number; parent: any }> = [];
-    visit(tree, (node: any, index: number | null, parent: any) => {
+    const toolNodes: Array<{ node: any; index: number; parent: Parent }> = [];
+    visit(tree, (node: any, index: number | null, parent: Parent | null) => {
       // ...
     });
 
-    const firstByCallId = new Map<string, { node: any; index: number; parent: any }>();
-    const toRemove: Array<{ index: number; parent: any }> = [];
+    const firstByCallId = new Map<string, { node: any; index: number; parent: Parent }>();
+    const toRemove: Array<{ index: number; parent: Parent }> = [];
 
     // Remove duplicates...
-    for (const { index, parent } of sortedRemovals) {
-      if (Array.isArray(parent?.children)) {
-        parent.children.splice(index, 1);
-      }
-    }
+    for (const { index, parent } of sortedRemovals) {
+      if (Array.isArray((parent as Parent).children)) {
+        (parent as Parent).children.splice(index, 1);
+      }
+    }
   };
}

Optionally declare a Plugin<[], Root> type for the export.
Based on learnings

Also applies to: 48-50, 74-81

packages/utils/src/step.ts (1)

1-1: Consider using explicit type import syntax.

The coding guidelines specify to use explicit import type syntax for type-only imports and group them separately. If ToolCallResult is only used for type annotations, consider:

As per coding guidelines.

-import { ActionStep, Artifact, ActionResult, ToolCallResult } from '@refly/openapi-schema';
+import type { ActionStep, Artifact, ActionResult, ToolCallResult } from '@refly/openapi-schema';

Note: Only apply this if ToolCallResult is used purely for type annotations and not at runtime.

packages/openapi-schema/src/types.gen.ts (1)

3246-3249: Consider improving auto-generated type alias naming.

The type alias status2 (tool call status) and status3 (test result status at line 5424) use generic numeric suffixes that don't convey their semantic meaning. This can cause confusion, especially since status2 appears in other generated files with different values (e.g., 'success' | 'failed' | 'unknown' in packages/ai-workspace-common/src/requests/types.gen.ts).

Since this is an auto-generated file, consider updating the OpenAPI schema or code generator to produce more descriptive type alias names, such as:

  • ToolCallStatus instead of status2 (line 3249)
  • ProviderTestStatus instead of status3 (line 5424)

This would improve type clarity and reduce the risk of using the wrong status type.

packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (1)

154-164: Type the toolCalls merge to OpenAPI type; avoid any

Replace any-casts with ToolCallResult and a narrow mapper to keep types sound.

Example:

+import type { ToolCallResult } from '@refly/openapi-schema';
 ...
-    const merged = mergeToolCallById(updatedStep.toolCalls as any, parsed as any);
-    updatedStep.toolCalls = merged as any;
+    const merged = mergeToolCallById(
+      (updatedStep.toolCalls as ToolCallResult[] | undefined) as any,
+      parsed as any
+    ) as unknown as ToolCallResult[];
+    updatedStep.toolCalls = merged;

Optionally add a mapper from ParsedToolCall → ToolCallResult to avoid casts entirely.

apps/api/prisma/schema.prisma (1)

1558-1558: Index likely needs stepName for step-level grouping/filtering.

If queries group tool calls by step in a result/version, add a composite index including stepName for efficient lookups.

-  @@index([resultId, version])
+  @@index([resultId, version])
+  @@index([resultId, version, stepName])
packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx (3)

146-149: Avoid any for props; type modelInfo.

Use the generated OpenAPI type and import it as a type to keep TS soundness.

-    modelInfo: any;
+    modelInfo: import('@refly/openapi-schema').ModelInfo | undefined;

As per coding guidelines.


303-305: Remove console logging in production path.

Console noise in effects hampers observability; use a debug logger or guard by env.

-        console.log(
-          `[Pilot Step Sync] Updating node ${id} status from ${data?.metadata?.status} to ${currentPilotStep.status}`,
-        );
+        // optionally: debug('pilotStepSync', { nodeId: id, from: data?.metadata?.status, to: currentPilotStep.status });

838-852: Use brand palette for state colors.

Replace raw text-green-500 with the project Success color (#12B76A) utility/token to match branding.

-                  <IconLoading className="h-3 w-3 animate-spin text-green-500" />
+                  <IconLoading className="h-3 w-3 animate-spin text-[#12B76A]" />

As per coding guidelines.

packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (3)

8-9: Regex from variable is static here; simplify and avoid false ReDoS warning.

Since TOOL_USE_TAG is constant, inline the literal to keep it simple and slightly faster.

-const TOOL_USE_REGEX = new RegExp(`<${TOOL_USE_TAG}[^>]*>([\\s\\S]*?)</${TOOL_USE_TAG}>`, 'i');
+const TOOL_USE_REGEX = /<tool_use[^>]*>([\s\S]*?)<\/tool_use>/i;

If you plan to change TOOL_USE_TAG dynamically in future, keep current form and document safe inputs.


44-60: Unbounded caches can grow over long sessions.

extractionCache never evicts. Scope caches per-process call or cap with a tiny LRU.

-const extractionCache = new Map<string, string>();
+const extractionCache = new Map<string, string>();
+const MAX_CACHE = 200;
 ...
   // Cache the result for future use
   extractionCache.set(cacheKey, result);
+  if (extractionCache.size > MAX_CACHE) {
+    const firstKey = extractionCache.keys().next().value;
+    extractionCache.delete(firstKey);
+  }

Apply similar bounds to jsonParseCache.


330-348: Only first tool_use block is processed; consider multi-block chunks.

If a chunk contains multiple tool_use blocks, downstream will miss subsequent ones.

Parse iteratively with a global regex or loop advancing lastIndex to return all matches.

packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx (2)

154-163: Avoid inline style objects; use Tailwind utilities with conditional classes.

Prevents re-renders and follows style guide.

-          <ToolOutlined
-            className="text-gray-500 dark:text-gray-400"
-            style={{
-              fontSize: '16px',
-              marginRight: '12px',
-              transition: 'transform 0.2s ease-in-out',
-              transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)',
-            }}
-          />
+          <ToolOutlined
+            className={[
+              'text-gray-500 dark:text-gray-400',
+              'mr-3 transition-transform duration-200 ease-in-out',
+              isCollapsed ? '-rotate-90' : 'rotate-0',
+              'text-[16px]',
+            ].join(' ')}
+          />

As per coding guidelines.


169-188: Status UI looks good; ensure colors align with brand tokens.

Consider using brand Success color (#12B76A) for completed and Error color (#F04438) for failed in class names.

apps/api/src/modules/tool-call/tool-call.service.ts (6)

1-6: Add explicit return type for fetchToolCalls and import the DB model type

Public APIs should declare explicit return types. Use the generated ToolCallResult type.

Apply:

 import { Injectable, Logger } from '@nestjs/common';
 import type { ActionStepMeta } from '@refly/openapi-schema';
 import type { Response } from 'express';
 import { ActionResult, ActionStatus } from '../../generated/client';
+import type { ToolCallResult as DbToolCallResult } from '../../generated/client';
-  async fetchToolCalls(resultId: string, version: number) {
+  async fetchToolCalls(resultId: string, version: number): Promise<DbToolCallResult[]> {
     return this.prisma.toolCallResult.findMany({

Also applies to: 147-156


158-179: Avoid any[] and make grouping generic and type‑safe

Use a generic for tool calls to retain types and remove any[].

Apply:

-  groupToolCallsByStep(
-    steps: Array<{ name: string }>,
-    toolCalls: Array<{ stepName?: string | null }>,
-  ): Map<string, any[]> {
+  groupToolCallsByStep<TCall extends { stepName?: string | null }>(
+    steps: Array<{ name: string }>,
+    toolCalls: TCall[],
+  ): Map<string, TCall[]> {
     const existingStepNames = new Set(steps.map((step) => step.name));
     const fallbackStepName = steps.length > 0 ? steps[steps.length - 1].name : undefined;
 
-    return toolCalls.reduce<Map<string, any[]>>((acc, call) => {
+    return toolCalls.reduce<Map<string, TCall[]>>((acc, call) => {

181-189: Propagate concrete tool call type to steps instead of any[]

Keep strong typing when attaching tool calls to steps.

Apply:

-  attachToolCallsToSteps<T extends { name: string }>(
-    steps: T[],
-    toolCallsByStep: Map<string, any[]>,
-  ): Array<T & { toolCalls?: any[] }> {
+  attachToolCallsToSteps<TStep extends { name: string }, TCall>(
+    steps: TStep[],
+    toolCallsByStep: Map<string, TCall[]>,
+  ): Array<TStep & { toolCalls?: TCall[] }> {
     return steps.map((step) => ({
       ...step,
       toolCalls: toolCallsByStep.get(step.name),
     }));
   }

191-217: Use ToolCallStatus enum in derivation and declare explicit return type

Avoid magic strings; return type should be explicit.

Apply:

-  async deriveAndUpdateActionStatus(result: ActionResult, toolCalls: Array<{ status: string }>) {
+  async deriveAndUpdateActionStatus(
+    result: ActionResult,
+    toolCalls: Array<{ status: ToolCallStatus }>,
+  ): Promise<ActionStatus> {
     let actionStatus = result.status;
 
     if (toolCalls.length > 0) {
-      const hasFailed = toolCalls.some((tc) => tc.status === 'failed');
-      const hasExecuting = toolCalls.some((tc) => tc.status === 'executing');
-      const allCompleted = toolCalls.every((tc) => tc.status === 'completed');
+      const hasFailed = toolCalls.some((tc) => tc.status === ToolCallStatus.FAILED);
+      const hasExecuting = toolCalls.some((tc) => tc.status === ToolCallStatus.EXECUTING);
+      const allCompleted = toolCalls.every((tc) => tc.status === ToolCallStatus.COMPLETED);

25-26: Remove unused startTimestamps map

Dead code; also risks accidental retention growth if later used without cleanup.

Apply:

-  private readonly startTimestamps = new Map<string, number>();

117-131: Consider accepting unknown for input/output/error and normalize internally

Callers shouldn’t need to pre‑stringify. Accept unknown and stringify once in persistToolCall for consistency.

Apply:

-    input: string | undefined,
-    output: string | undefined,
+    input: unknown | undefined,
+    output: unknown | undefined,
-      input?: string;
-      output?: string;
-      error?: string;
+      input?: unknown;
+      output?: unknown;
+      error?: unknown;

Also applies to: 221-234

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e6b70a and 16edb70.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • apps/api/prisma/schema.prisma (2 hunks)
  • apps/api/src/modules/action/action.dto.ts (1 hunks)
  • apps/api/src/modules/action/action.module.ts (1 hunks)
  • apps/api/src/modules/action/action.service.ts (3 hunks)
  • apps/api/src/modules/skill/skill-invoker.service.ts (4 hunks)
  • apps/api/src/modules/skill/skill.module.ts (2 hunks)
  • apps/api/src/modules/tool-call/tool-call.module.ts (1 hunks)
  • apps/api/src/modules/tool-call/tool-call.service.ts (1 hunks)
  • packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx (3 hunks)
  • packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx (3 hunks)
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts (2 hunks)
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx (5 hunks)
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (4 hunks)
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts (1 hunks)
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (3 hunks)
  • packages/i18n/src/en-US/ui.ts (1 hunks)
  • packages/i18n/src/zh-Hans/ui.ts (1 hunks)
  • packages/openapi-schema/schema.yml (3 hunks)
  • packages/openapi-schema/src/schemas.gen.ts (3 hunks)
  • packages/openapi-schema/src/types.gen.ts (4 hunks)
  • packages/utils/src/step.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Always use optional chaining (?.) when accessing object properties.
Always use nullish coalescing (??) or default values for potentially undefined values.
Always check array existence before using array methods.
Always validate object properties before destructuring.
Always use single quotes for string literals in JavaScript/TypeScript code.

Use JSDoc style comments for functions and classes in JavaScript/TypeScript

**/*.{js,jsx,ts,tsx}: Use single quotes for string literals
Always use optional chaining (?.) when accessing object properties
Always use nullish coalescing (??) or default values for potentially undefined values
Always check array existence before using array methods
Validate object properties before destructuring
Use ES6+ features like arrow functions, destructuring, and spread operators
Use async/await instead of raw promises for asynchronous code

**/*.{js,jsx,ts,tsx}: Maximum line length of 100 characters
Use 2 spaces for indentation, no tabs
Use semicolons at the end of statements
Include spaces around operators (e.g., a + b instead of a+b)
Always use curly braces for control statements
Place opening braces on the same line as their statement
No trailing whitespace at the end of lines

**/*.{js,jsx,ts,tsx}: Each file should contain only one main component
File names should match the component name
Keep component files focused with minimal dependencies
Group import statements in the following order: 1. React/framework libraries 2. Third-party libraries 3. Internal modules 4. Relative path imports (parent directories first, then child directories) 5. Type imports 6. Style imports
Sort imports alphabetically within each group
Leave a blank line between import groups
Components should be organized in the following order: 1. Import statements 2. Type definitions 3. Constant declarations 4. Component function 5. Hook calls 6. Event handlers 7. Helper render functions 8. JSX return statement
Extract complex logic in...

Files:

  • packages/i18n/src/en-US/ui.ts
  • apps/api/src/modules/tool-call/tool-call.module.ts
  • apps/api/src/modules/skill/skill.module.ts
  • packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx
  • packages/i18n/src/zh-Hans/ui.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • apps/api/src/modules/action/action.module.ts
  • packages/openapi-schema/src/types.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/action/action.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts
  • packages/utils/src/step.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/03-typescript-guidelines.mdc)

**/*.{ts,tsx}: Avoid using any type whenever possible - it defeats the purpose of TypeScript
When tempted to use any, use unknown type instead and add proper type guards
Always define explicit return types for functions, especially for public APIs
Prefer extending existing types over creating entirely new ones
Use TypeScript's utility types to derive new types: Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Record<K, T>
Use union types and intersection types to combine existing types
Always import types explicitly using the import type syntax
Group type imports separately from value imports
Minimize creating local type aliases for imported types

Explicitly type props interfaces/types

Files:

  • packages/i18n/src/en-US/ui.ts
  • apps/api/src/modules/tool-call/tool-call.module.ts
  • apps/api/src/modules/skill/skill.module.ts
  • packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx
  • packages/i18n/src/zh-Hans/ui.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • apps/api/src/modules/action/action.module.ts
  • packages/openapi-schema/src/types.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/action/action.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts
  • packages/utils/src/step.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

Follow NestJS patterns for backend code

Files:

  • packages/i18n/src/en-US/ui.ts
  • apps/api/src/modules/tool-call/tool-call.module.ts
  • apps/api/src/modules/skill/skill.module.ts
  • packages/i18n/src/zh-Hans/ui.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • apps/api/src/modules/action/action.module.ts
  • packages/openapi-schema/src/types.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/action/action.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts
  • packages/utils/src/step.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/openapi-schema/src/schemas.gen.ts
**/*.{css,scss,sass,less,styl,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/09-design-system.mdc)

**/*.{css,scss,sass,less,styl,js,jsx,ts,tsx}: Use Primary color (#155EEF) as the main brand color for buttons, links, and accents
Use Error color (#F04438) for error states and destructive actions
Use Success color (#12B76A) for success states and confirmations
Use Warning color (#F79009) for warnings and important notifications
Use Info color (#0BA5EC) for informational elements

Files:

  • packages/i18n/src/en-US/ui.ts
  • apps/api/src/modules/tool-call/tool-call.module.ts
  • apps/api/src/modules/skill/skill.module.ts
  • packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx
  • packages/i18n/src/zh-Hans/ui.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • apps/api/src/modules/action/action.module.ts
  • packages/openapi-schema/src/types.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/action/action.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts
  • packages/utils/src/step.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx
packages/i18n/src/{en-US,zh-Hans}/**

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

packages/i18n/src/{en-US,zh-Hans}/**: Add new translation keys to both language files: packages/i18n/src/en-US and packages/i18n/src/zh-Hans
Maintain consistency in naming conventions for translation keys
Use descriptive keys that reflect the content in translation files
Group related translation keys together
Use namespaces for different sections of the application in translation keys
Follow a hierarchical structure for nested components in translation keys
Support dynamic content with placeholders in translation strings

Files:

  • packages/i18n/src/en-US/ui.ts
  • packages/i18n/src/zh-Hans/ui.ts
**/*.{css,scss,less,styl,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{css,scss,less,styl,js,jsx,ts,tsx}: Use the primary blue (#155EEF) for main UI elements, CTAs, and active states
Use red (#F04438) only for errors, warnings, and destructive actions
Use green (#12B76A) for success states and confirmations
Use orange (#F79009) for warning states and important notifications
Use blue (#0BA5EC) for informational elements
Maintain a minimum contrast ratio of 4.5:1 for text

Files:

  • packages/i18n/src/en-US/ui.ts
  • apps/api/src/modules/tool-call/tool-call.module.ts
  • apps/api/src/modules/skill/skill.module.ts
  • packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx
  • packages/i18n/src/zh-Hans/ui.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • apps/api/src/modules/action/action.module.ts
  • packages/openapi-schema/src/types.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/action/action.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts
  • packages/utils/src/step.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{jsx,tsx}: Always use tailwind css to style the component.
Always wrap pure components with React.memo to prevent unnecessary re-renders.
Always use useMemo for expensive computations or complex object creation.
Always use useCallback for function props to maintain referential equality.
Always specify proper dependency arrays in useEffect to prevent infinite loops.
Always avoid inline object/array creation in render to prevent unnecessary re-renders.
Always use proper key props when rendering lists.
Always split nested components with closures into separate components to avoid performance issues and improve code maintainability.

**/*.{jsx,tsx}: Always wrap pure components with React.memo to prevent unnecessary re-renders
Always use useMemo for expensive computations or complex object creation
Always use useCallback for function props to maintain referential equality
Always specify proper dependency arrays in useEffect to prevent infinite loops
Always avoid inline object/array creation in render to prevent unnecessary re-renders
Always use proper key props when rendering lists (avoid using index when possible)
Always split nested components with closures into separate components
Use lazy loading for components that are not immediately needed
Debounce handlers for events that might fire rapidly (resize, scroll, input)
Implement fallback UI for components that might fail
Use error boundaries to catch and handle runtime errors
Use Tailwind CSS for styling components
Group related utility classes together
Prefer Tailwind utilities over custom CSS when possible

**/*.{jsx,tsx}: Place each attribute on a new line when a component has multiple attributes in JSX
Use self-closing tags for elements without children in JSX
Keep JSX expressions simple, extract complex logic to variables
Put closing brackets for multi-line JSX on a new line

Use React best practices for frontend code

Files:

  • packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx
**/index.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/05-code-organization.mdc)

Use index files to export multiple components from a directory

Files:

  • packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

**/*.tsx: Use the translation wrapper component when displaying user-facing text in components
Ensure all user-facing text in components is translatable

Files:

  • packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-21T02:58:52.235Z
Learnt from: CR
PR: refly-ai/refly#0
File: .cursor/rules/09-i18n-guidelines.mdc:0-0
Timestamp: 2025-07-21T02:58:52.235Z
Learning: Applies to packages/i18n/src/{en-US,zh-Hans}/** : Add new translation keys to both language files: packages/i18n/src/en-US and packages/i18n/src/zh-Hans

Applied to files:

  • packages/i18n/src/en-US/ui.ts
  • packages/i18n/src/zh-Hans/ui.ts
🧬 Code graph analysis (11)
apps/api/src/modules/tool-call/tool-call.module.ts (2)
apps/api/src/modules/action/action.module.ts (1)
  • Module (9-15)
apps/api/src/modules/skill/skill.module.ts (1)
  • Module (35-81)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (1)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts (1)
  • TOOL_USE_TAG_RENDER (5-5)
apps/api/src/modules/action/action.module.ts (2)
apps/api/src/modules/skill/skill.module.ts (1)
  • Module (35-81)
apps/api/src/modules/tool-call/tool-call.module.ts (1)
  • Module (5-10)
packages/openapi-schema/src/types.gen.ts (3)
packages/agent-tools/src/base.ts (1)
  • ToolCallResult (4-25)
packages/ai-workspace-common/src/requests/types.gen.ts (1)
  • status2 (5348-5348)
packages/request/src/requests/types.gen.ts (1)
  • status2 (5348-5348)
packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (2)
packages/openapi-schema/src/types.gen.ts (1)
  • ActionStep (1634-1669)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (2)
  • parseToolCallFromChunk (439-497)
  • mergeToolCallById (502-514)
apps/api/src/modules/skill/skill-invoker.service.ts (1)
apps/api/src/modules/tool-call/tool-call.service.ts (1)
  • persistToolCall (219-284)
apps/api/src/modules/action/action.dto.ts (3)
packages/openapi-schema/src/types.gen.ts (2)
  • ActionStep (1634-1669)
  • ToolCallResult (3187-3244)
packages/utils/src/typesafe.ts (1)
  • pick (3-9)
packages/ai-workspace-common/src/utils/parse.ts (1)
  • safeParseJSON (1-10)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts (1)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (1)
  • TOOL_USE_TAG_RENDER (5-5)
packages/utils/src/step.ts (1)
packages/openapi-schema/src/types.gen.ts (1)
  • ToolCallResult (3187-3244)
apps/api/src/modules/tool-call/tool-call.service.ts (4)
apps/api/src/modules/action/action.service.ts (1)
  • Injectable (13-303)
apps/api/src/modules/skill/skill-invoker.service.ts (1)
  • Injectable (58-1034)
packages/openapi-schema/src/types.gen.ts (5)
  • type (748-748)
  • ActionStepMeta (1602-1607)
  • status (297-297)
  • ActionResult (1726-1837)
  • ActionStatus (1547-1547)
apps/api/src/utils/response.ts (1)
  • writeSSEResponse (11-15)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx (1)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts (1)
  • parseToolCallStatus (8-17)
🪛 ast-grep (0.39.6)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts

[warning] 7-7: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(<${TOOL_USE_TAG}[^>]*>([\\s\\S]*?)</${TOOL_USE_TAG}>, 'i')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (22)
packages/ai-workspace-common/src/components/canvas/node-preview/skill-response/index.tsx (1)

186-207: Status text effect misses actionMeta in deps.

If the skill changes without a status/steps change, text may desync. Add actionMeta?.name.

-  }, [result?.status, steps, t]);
+  }, [result?.status, steps, t, actionMeta?.name]);
apps/api/src/modules/skill/skill.module.ts (1)

33-50: Wiring ToolCallModule into SkillModule looks correct.

Exports/imports pattern aligns with Nest DI; no issues spotted.

apps/api/src/modules/action/action.module.ts (1)

9-13: ActionModule now depends on ToolCallModule — OK.

Dependency graph remains sane alongside forwardRef(SkillModule).

If ActionService injects ToolCallService, ensure ToolCallModule is not also provided elsewhere to avoid duplicate providers in tests.

packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts (2)

1-6: LGTM! Clean enum definition.

The ToolCallStatus enum is well-structured with clear status values that align with the backend schema. String literals correctly use single quotes as per coding guidelines.


14-15: Consider semantic correctness of default behavior.

The function defaults to EXECUTING for any undefined or unrecognized input. While this may be intentional for progressive loading scenarios, consider whether undefined or null input should explicitly default to EXECUTING or potentially throw an error for invalid values.

Please verify that defaulting to EXECUTING for all unknown/undefined values aligns with the intended UI behavior, especially when tool call data hasn't been loaded yet.

packages/utils/src/step.ts (2)

86-102: Good defensive array handling and deduplication logic.

The tool call merging logic correctly:

  • Uses Array.isArray() checks before operations
  • Applies nullish coalescing (??) for defaults
  • Deduplicates by callId using a Map
  • Handles missing callId gracefully
  • Uses optional chaining (?.) appropriately

One minor observation: Line 98 uses ?? {} as a fallback for a non-existent entry. While this works, the empty object might not fully satisfy ToolCallResult type constraints, though TypeScript's structural typing likely accepts it with the spread.


104-116: LGTM! Clean integration of toolCalls field.

The toolCalls field is properly integrated into the merged ActionStep object alongside other metadata fields. The field will be undefined when no tool calls exist, which is consistent with the optional nature of toolCalls in the ActionStep type.

packages/openapi-schema/src/types.gen.ts (3)

3181-3185: LGTM! SkillEvent extension for tool call tracking.

The toolCallResult field is properly added to SkillEvent as an optional field with appropriate JSDoc documentation. This enables streaming of tool call status during skill execution.


3187-3244: Well-structured ToolCallResult type definition.

The type definition is comprehensive with appropriate field types and documentation. The flexible input/output types (object | string) accommodate various tool response formats.

Note: There's a naming collision with ToolCallResult in packages/agent-tools/src/base.ts, which has a different structure (status: 'success' | 'error' vs status: 'executing' | 'completed' | 'failed'). While both serve different purposes (internal tool execution vs public API representation), this could cause confusion. Consider namespacing or renaming one of them in future refactors if they're used in overlapping contexts.


1665-1668: LGTM! ActionStep extended with toolCalls tracking.

The toolCalls field is appropriately added as an optional array of ToolCallResult, enabling steps to track multiple tool invocations. This aligns with the PR objective to track tool interactions within actions.

apps/api/src/modules/skill/skill-invoker.service.ts (1)

282-289: Double enqueueing syncRequestUsage

You add 'syncRequestUsage' at start and again in finally; likely double counts.

Decide on one place (start or end) and remove the duplicate, or make the two intents distinct (e.g., "request_started" vs "request_finished") to avoid overcounting.

Also applies to: 854-864

packages/openapi-schema/src/schemas.gen.ts (4)

2179-2186: ActionStep.toolCalls addition looks good

The step now exposes tool calls; aligns with backend usage.


2350-2356: ActionResult.toolCalls surface approved

Action-level aggregation of tool calls is useful for summaries.


4414-4420: SkillEvent.toolCallResult field addition LGTM

Enables streaming single-call updates.


4421-4490: ToolCallResult schema shape is sensible; confirm enum mapping

Status enum uses lowercase strings. Ensure backend enum-to-JSON mapping matches ('executing' | 'completed' | 'failed').

packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (1)

209-211: Tool call sync on stream: good integration

Parsing tool_use XML per chunk and merging by callId is appropriate.

apps/api/src/modules/action/action.service.ts (2)

63-69: Read handler mutates state (deriveAndUpdateActionStatus)

getActionResult triggers a DB write via deriveAndUpdateActionStatus; unexpected side effect in a GET.

Consider moving status reconciliation to the write/stream path or running it asynchronously (queue) to keep GET idempotent and low-latency.


26-27: ToolCallService injection: LGTM

Wiring looks correct; improves step enrichment.

packages/ai-workspace-common/src/components/canvas/nodes/skill-response.tsx (1)

412-432: Version incrementation looks correct; ensure server expects version in payload.

Client updates metadata and passes version: nextVersion to invokeAction. Verify backend uses this to write ActionResult/ToolCallResult versions.

apps/api/src/modules/action/action.dto.ts (1)

27-36: DTO for steps: good; verify toolCalls ordering and dedup policy.

If multiple tool calls share callId, ensure upstream uses merge semantics and stable sort (by createdAt).

packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx (1)

110-115: Result content prioritization is fine; ensure i18n for error text elsewhere.

No change required; note that hasResult computes from error/result consistently.

apps/api/src/modules/tool-call/tool-call.service.ts (1)

249-283: Reconsider the key constraint strategy; callId is already globally unique

The original concern about collisions is not valid: callId is already defined as globally unique in the schema (@unique), so the current upsert key is safe and will reliably find the correct record without overwriting unrelated records.

However, two valid concerns remain:

  1. The update block mutates resultId and version (identity fields), which is semantically problematic even though callId uniqueness prevents collisions.
  2. Error is unconditionally set (error: data.error ?? undefined), clearing prior errors when undefined is provided.

Since callId is globally unique, you may keep the current where clause as-is. Focus instead on preventing identity field mutations and conditional error updates:

      update: {
-        resultId: ids.resultId,
-        version: ids.version,
-        uid: data.uid ?? '',
-        toolsetId: data.toolsetId ?? '',
-        toolName: data.toolName ?? '',
-        stepName: data.stepName ?? undefined,
+        // do not update identity fields (resultId, version)
+        // only update optional fields when explicitly provided
+        ...(data.uid !== undefined ? { uid: data.uid } : {}),
+        ...(data.toolsetId !== undefined ? { toolsetId: data.toolsetId } : {}),
+        ...(data.toolName !== undefined ? { toolName: data.toolName } : {}),
+        ...(data.stepName !== undefined ? { stepName: data.stepName } : {}),
         // only overwrite when a new value is provided
         ...(data.input !== undefined ? { input: toJSONStr(data.input) } : {}),
         ...(data.output !== undefined ? { output: toJSONStr(data.output) } : {}),
         status: data.status ?? ToolCallStatus.EXECUTING,
-        error: data.error ?? undefined,
+        ...(data.error !== undefined ? { error: data.error } : {}),
         updatedAt: new Date(data.updatedAt),
         deletedAt: toDateOrNull(data.deletedAt),
       },

Likely an incorrect or invalid review comment.

Comment on lines +1533 to 1534
callId String @unique @map("call_id")
/// UID
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Global unique callId can collide across results/versions; prefer composite unique.

run_id may repeat across separate results or retries. Make uniqueness scoped per result/version to avoid constraint violations.

Apply:

-  callId    String  @unique @map("call_id")
+  callId    String  @map("call_id")
+  @@unique([resultId, version, callId])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
callId String @unique @map("call_id")
/// UID
callId String @map("call_id")
@@unique([resultId, version, callId])
/// UID
🤖 Prompt for AI Agents
In apps/api/prisma/schema.prisma around lines 1533-1534, remove the global
@unique on callId and instead enforce uniqueness scoped to the result/version by
adding a composite unique constraint at the model level (e.g., @@unique([callId,
result_id]) or @@unique([callId, version]) depending on which column represents
the result/version) so run_id/retries won't collide; also delete the @unique
attribute from the callId field and ensure the chosen secondary column name
matches the model's field.

Comment on lines +1547 to 1549
/// Status: enum
status String @default("executing") @map("status")
/// Error message
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Status is a free-form String; enforce enum for integrity.

Define a Prisma enum ('executing' | 'completed' | 'failed') to prevent invalid states and align with OpenAPI.

+enum ToolCallStatus {
+  executing
+  completed
+  failed
+}
 ...
-  status    String    @default("executing") @map("status")
+  status    ToolCallStatus @default(executing) @map("status")

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/api/prisma/schema.prisma around lines 1547 to 1549, the status field is
currently a free-form String; change it to use a Prisma enum to enforce allowed
values. Add a Prisma enum declaration (executing, completed, failed) in the
schema, update the model's status field type from String to that enum, keep the
@default("executing") but switch to the enum default (executing) and retain
@map("status") so the DB column name stays the same; after schema change run
prisma migrate to apply the enum to the database.

Comment on lines +38 to 51
export function toolCallResultPO2DTO(toolCall: ToolCallResultModel): ToolCallResult {
return {
callId: toolCall.callId,
uid: toolCall.uid,
toolsetId: toolCall.toolsetId,
toolName: toolCall.toolName,
input: safeParseJSON(toolCall.input || '{}'),
output: safeParseJSON(toolCall.output || '{}'),
error: toolCall.error || '',
status: toolCall.status as 'executing' | 'completed' | 'failed',
createdAt: toolCall.createdAt.getTime(),
updatedAt: toolCall.updatedAt.getTime(),
deletedAt: toolCall.deletedAt?.getTime(),
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Map stepName and preserve raw input/output strings when JSON parse fails.

Currently stepName is dropped; safeParseJSON returns undefined on invalid JSON, losing original content.

 export function toolCallResultPO2DTO(toolCall: ToolCallResultModel): ToolCallResult {
   return {
     callId: toolCall.callId,
     uid: toolCall.uid,
     toolsetId: toolCall.toolsetId,
     toolName: toolCall.toolName,
-    input: safeParseJSON(toolCall.input || '{}'),
-    output: safeParseJSON(toolCall.output || '{}'),
+    input: safeParseJSON(toolCall.input, () => toolCall.input) ?? toolCall.input ?? undefined,
+    output: safeParseJSON(toolCall.output, () => toolCall.output) ?? toolCall.output ?? undefined,
     error: toolCall.error || '',
     status: toolCall.status as 'executing' | 'completed' | 'failed',
+    stepName: (toolCall as any).stepName, // or add to generated model type if available
     createdAt: toolCall.createdAt.getTime(),
     updatedAt: toolCall.updatedAt.getTime(),
     deletedAt: toolCall.deletedAt?.getTime(),
   };
 }

Based on openapi schema.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/api/src/modules/action/action.dto.ts around lines 38 to 51, the DTO
mapping drops stepName and loses raw input/output when safeParseJSON returns
undefined; update the mapper to include toolCall.stepName -> stepName and change
input/output handling to try JSON.parse and on failure keep the original string
(e.g., parsedInput = tryParse(toolCall.input) ?? toolCall.input ?? '{}'), so the
DTO preserves the raw string if parsing fails while still returning parsed
objects when valid.

Comment on lines +3 to +6
import { DirectConnection } from '@hocuspocus/server';
import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/dist/messages';
import { AIMessage, HumanMessage } from '@langchain/core/messages';
import { InjectQueue } from '@nestjs/bullmq';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use public LangChain entrypoints, not internal /dist paths

Importing from '@langchain/core/dist/messages' is brittle and may break on upgrades.

Apply:

-import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/dist/messages';
+import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/messages';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { DirectConnection } from '@hocuspocus/server';
import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/dist/messages';
import { AIMessage, HumanMessage } from '@langchain/core/messages';
import { InjectQueue } from '@nestjs/bullmq';
import { DirectConnection } from '@hocuspocus/server';
import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/messages';
import { AIMessage, HumanMessage } from '@langchain/core/messages';
import { InjectQueue } from '@nestjs/bullmq';
🤖 Prompt for AI Agents
In apps/api/src/modules/skill/skill-invoker.service.ts around lines 3-6, the
file imports message types from the internal path
'@langchain/core/dist/messages' which is brittle; replace that import with the
package's public entrypoint (import the needed types — AIMessageChunk,
BaseMessage, MessageContentComplex and any message classes — from the documented
public export of LangChain, e.g. the top-level package export such as
'@langchain/core' or the library's documented schema entrypoint), and
consolidate any message imports so no code imports from '/dist' paths.

Comment on lines +236 to +244
const toJSONStr = (value: unknown): string => {
if (value === undefined || value === null) return undefined;
try {
return typeof value === 'string' ? value : JSON.stringify(value);
} catch {
return '';
}
};

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix compile-time bug: toJSONStr returns undefined despite string return type

toJSONStr declares a string return but returns undefined for nullish input. This will fail type-checking and can propagate empty writes unexpectedly.

Apply:

-    const toJSONStr = (value: unknown): string => {
-      if (value === undefined || value === null) return undefined;
+    const toJSONStr = (value: unknown): string => {
+      if (value === undefined || value === null) return '';
       try {
         return typeof value === 'string' ? value : JSON.stringify(value);
       } catch {
         return '';
       }
     };
🤖 Prompt for AI Agents
In apps/api/src/modules/tool-call/tool-call.service.ts around lines 236-244,
toJSONStr is declared to return string but returns undefined for null/undefined
inputs; change the function signature to return string | undefined (or
alternatively return an empty string) and keep the nullish branch returning
undefined, then update any callers to handle the possible undefined (e.g.,
coalesce to '' or skip writing) so types align and no compile-time error occurs.

Comment on lines +55 to +71
const existing = firstByCallId.get(callId);
if (!existing) {
firstByCallId.set(callId, entry);
} else {
// Merge properties from later node into the first one, preferring non-empty values
const targetProps = existing.node.properties ?? {};
const sourceProps = entry.node.properties ?? {};
for (const key of Object.keys(sourceProps)) {
const val = sourceProps[key];
if (val !== undefined && val !== null && String(val).trim() !== '') {
targetProps[key] = val;
}
}
existing.node.properties = targetProps;
// Mark the later duplicate for removal
toRemove.push({ index: entry.index, parent: entry.parent });
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

“Non-empty” check will treat {} and [] as non-empty (stringified), overwriting useful props.

String(val).trim() is truthy for objects/arrays, causing unintended overwrites.

Use a semantic isMeaningful guard:

-          const val = sourceProps[key];
-          if (val !== undefined && val !== null && String(val).trim() !== '') {
-            targetProps[key] = val;
-          }
+          const val = sourceProps[key];
+          const isMeaningful =
+            (typeof val === 'string' && val.trim().length > 0) ||
+            (Array.isArray(val) && val.length > 0) ||
+            (val && typeof val === 'object' && Object.keys(val).length > 0) ||
+            typeof val === 'number' ||
+            typeof val === 'boolean';
+          if (isMeaningful) {
+            targetProps[key] = val;
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const existing = firstByCallId.get(callId);
if (!existing) {
firstByCallId.set(callId, entry);
} else {
// Merge properties from later node into the first one, preferring non-empty values
const targetProps = existing.node.properties ?? {};
const sourceProps = entry.node.properties ?? {};
for (const key of Object.keys(sourceProps)) {
const val = sourceProps[key];
if (val !== undefined && val !== null && String(val).trim() !== '') {
targetProps[key] = val;
}
}
existing.node.properties = targetProps;
// Mark the later duplicate for removal
toRemove.push({ index: entry.index, parent: entry.parent });
}
const existing = firstByCallId.get(callId);
if (!existing) {
firstByCallId.set(callId, entry);
} else {
// Merge properties from later node into the first one, preferring non-empty values
const targetProps = existing.node.properties ?? {};
const sourceProps = entry.node.properties ?? {};
for (const key of Object.keys(sourceProps)) {
const val = sourceProps[key];
const isMeaningful =
(typeof val === 'string' && val.trim().length > 0) ||
(Array.isArray(val) && val.length > 0) ||
(val && typeof val === 'object' && Object.keys(val).length > 0) ||
typeof val === 'number' ||
typeof val === 'boolean';
if (isMeaningful) {
targetProps[key] = val;
}
}
existing.node.properties = targetProps;
// Mark the later duplicate for removal
toRemove.push({ index: entry.index, parent: entry.parent });
}

Comment on lines +457 to +461
const status = (attrs['data-tool-call-status'] ?? ToolCallStatus.FAILED.toString()) as
| 'executing'
| 'completed'
| 'failed';

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Status normalization is inconsistent; defaulting to ToolCallStatus.FAILED.toString() risks casing mismatch.

OpenAPI/UI expect lowercase ('executing'|'completed'|'failed'). Default should be computed from presence of result/error, not enum.toString().

-  // the status is always failed if the result is not present
-  const status = (attrs['data-tool-call-status'] ?? ToolCallStatus.FAILED.toString()) as
-    | 'executing'
-    | 'completed'
-    | 'failed';
+  const rawStatus = attrs['data-tool-call-status']?.toLowerCase?.();
+  const hasResult = !!attrs['data-tool-result'];
+  const hasError = !!attrs['data-tool-error'];
+  const status: 'executing' | 'completed' | 'failed' =
+    rawStatus === 'completed' ? 'completed' :
+    rawStatus === 'failed' ? 'failed' :
+    rawStatus === 'executing' ? 'executing' :
+    hasResult ? 'completed' :
+    hasError ? 'failed' : 'executing';
🤖 Prompt for AI Agents
In
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
around lines 457-461, the status default uses ToolCallStatus.FAILED.toString()
which can produce a casing mismatch and isn’t derived from the node data;
instead determine the default status from the presence of
attrs['data-tool-call-result'] (=> 'completed') or attrs['data-tool-call-error']
(=> 'failed'), otherwise 'executing', then normalize the final value to a
lowercase string ('executing'|'completed'|'failed') before casting/using it so
it matches the OpenAPI/UI expectations.

Comment on lines 479 to 494
const argsStr = attrs['data-tool-arguments'];
const resultStr = attrs['data-tool-result'];

const parsed: ParsedToolCall = {
callId,
status,
createdAt,
updatedAt,
toolsetId: attrs['data-tool-toolset-key'] ?? undefined,
toolName: attrs['data-tool-name'] ?? undefined,
stepName,
input: safeParse(argsStr) as Record<string, unknown> | undefined,
output: safeParse(resultStr),
error: status === 'failed' ? String(resultStr ?? '') : undefined,
uid: undefined,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: error is taken from resultStr; should use parsed <error> content.

This mislabels successful results as errors and hides real error messages.

-  const resultStr = attrs['data-tool-result'];
+  const resultStr = attrs['data-tool-result'];
+  const errorStr = attrs['data-tool-error'];
 ...
   const parsed: ParsedToolCall = {
     callId,
     status,
     createdAt,
     updatedAt,
     toolsetId: attrs['data-tool-toolset-key'] ?? undefined,
     toolName: attrs['data-tool-name'] ?? undefined,
     stepName,
     input: safeParse(argsStr) as Record<string, unknown> | undefined,
     output: safeParse(resultStr),
-    error: status === 'failed' ? String(resultStr ?? '') : undefined,
+    error: status === 'failed' ? String(errorStr ?? '') : undefined,
     uid: undefined,
   };
🤖 Prompt for AI Agents
In
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
around lines 479 to 494, the code sets error from resultStr which mislabels
normal results as errors; change the error assignment to read the actual error
payload (e.g. attrs['data-tool-error'] or the parsed <error> content) instead of
resultStr, parse it with safeParse and coerce to string only when status ===
'failed' so parsed.error contains the real error message or undefined otherwise.

Comment on lines +8 to +17
export const parseToolCallStatus = (val?: string): ToolCallStatus => {
switch (val) {
case ToolCallStatus.COMPLETED:
return ToolCallStatus.COMPLETED;
case ToolCallStatus.FAILED:
return ToolCallStatus.FAILED;
default:
return ToolCallStatus.EXECUTING;
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add JSDoc comments for the function.

The function is missing JSDoc documentation which is required per coding guidelines for JavaScript/TypeScript functions.

As per coding guidelines.

Apply this diff to add JSDoc:

+/**
+ * Parses a string value to a ToolCallStatus enum value.
+ * @param val - Optional status string to parse
+ * @returns Parsed ToolCallStatus, defaults to EXECUTING for unknown values
+ */
 export const parseToolCallStatus = (val?: string): ToolCallStatus => {
   switch (val) {
     case ToolCallStatus.COMPLETED:
       return ToolCallStatus.COMPLETED;
     case ToolCallStatus.FAILED:
       return ToolCallStatus.FAILED;
     default:
       return ToolCallStatus.EXECUTING;
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const parseToolCallStatus = (val?: string): ToolCallStatus => {
switch (val) {
case ToolCallStatus.COMPLETED:
return ToolCallStatus.COMPLETED;
case ToolCallStatus.FAILED:
return ToolCallStatus.FAILED;
default:
return ToolCallStatus.EXECUTING;
}
};
/**
* Parses a string value to a ToolCallStatus enum value.
* @param val - Optional status string to parse
* @returns Parsed ToolCallStatus, defaults to EXECUTING for unknown values
*/
export const parseToolCallStatus = (val?: string): ToolCallStatus => {
switch (val) {
case ToolCallStatus.COMPLETED:
return ToolCallStatus.COMPLETED;
case ToolCallStatus.FAILED:
return ToolCallStatus.FAILED;
default:
return ToolCallStatus.EXECUTING;
}
};
🤖 Prompt for AI Agents
In
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts
around lines 8 to 17, the parseToolCallStatus function lacks the required JSDoc
block; add a concise JSDoc comment immediately above the function that describes
its purpose, documents the parameter (val?: string) and its possible values,
specifies the return type (ToolCallStatus), and notes the default behavior when
val is unrecognized (returns ToolCallStatus.EXECUTING), following project JSDoc
style.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
apps/api/src/modules/skill/skill-invoker.service.ts (3)

4-6: Use public LangChain entrypoints and type-only imports

Importing from '@langchain/core/dist/messages' is brittle. Switch to the public entrypoint and mark type-only imports. Also keep value imports (AIMessage, HumanMessage) as runtime imports.

Apply:

-import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/dist/messages';
-import { AIMessage, HumanMessage } from '@langchain/core/messages';
+import type { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/messages';
+import { AIMessage, HumanMessage } from '@langchain/core/messages';

Based on learnings.


690-692: Re-enable stream suppression during tool execution

Prevents tool output from duplicating in the chat stream.

-            // if (event?.metadata?.langgraph_node === 'tools') {
-            //   break;
-            // }
+            if (event?.metadata?.langgraph_node === 'tools') {
+              break;
+            }

583-614: Guard against empty toolCallId before persisting

Avoid upserting with empty callId by validating and fixing it once.

             const persistToolCall = async (
               status: ToolCallStatus,
               data: {
                 input: string | undefined;
                 output: string | undefined;
                 errorMessage?: string;
               },
             ) => {
+              if (!toolCallId) {
+                this.logger.warn('Missing toolCallId; generating fallback');
+                toolCallId = randomUUID();
+              }
               const toolsetId = String(event.metadata?.toolsetKey ?? '');
               const toolName = String(event.metadata?.name ?? '');
🧹 Nitpick comments (3)
apps/api/src/modules/skill/skill-invoker.service.ts (3)

312-321: Type config reads and add default for streamIdleTimeout

Ensure numeric type and sane default.

-    const streamIdleTimeout = this.config.get('skill.streamIdleTimeout');
+    const streamIdleTimeout = this.config.get<number>('skill.streamIdleTimeout', 45000);
@@
-    if (!streamIdleTimeout || streamIdleTimeout <= 0) {
+    if (streamIdleTimeout <= 0) {

As per coding guidelines.


13-18: Use type-only imports for schema types

These are types at compile time; prefer import type.

-import {
+import type {
   ActionResult,
   ActionStep,
   Artifact,
   CreditBilling,
   SkillEvent,
   TokenUsageItem,
   User,
   ProviderItem,
 } from '@refly/openapi-schema';

As per coding guidelines.


109-115: Remove any with a narrow type guard for vision capability

Avoid (providerItem?.config as any). Use a guard.

type ProviderConfig = { capabilities?: { vision?: boolean } };
const hasVision = (cfg: unknown): cfg is ProviderConfig =>
  !!cfg && typeof cfg === 'object' && 'capabilities' in (cfg as any);

const canSee = hasVision(providerItem?.config) && !!providerItem.config.capabilities?.vision;
if (result.input?.images?.length > 0 && canSee) {
  // ...
}

As per coding guidelines.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16edb70 and 89d9056.

📒 Files selected for processing (3)
  • apps/api/src/modules/skill/skill-invoker.service.ts (4 hunks)
  • packages/i18n/src/en-US/ui.ts (1 hunks)
  • packages/i18n/src/zh-Hans/ui.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/i18n/src/en-US/ui.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Always use optional chaining (?.) when accessing object properties.
Always use nullish coalescing (??) or default values for potentially undefined values.
Always check array existence before using array methods.
Always validate object properties before destructuring.
Always use single quotes for string literals in JavaScript/TypeScript code.

Use JSDoc style comments for functions and classes in JavaScript/TypeScript

**/*.{js,jsx,ts,tsx}: Use single quotes for string literals
Always use optional chaining (?.) when accessing object properties
Always use nullish coalescing (??) or default values for potentially undefined values
Always check array existence before using array methods
Validate object properties before destructuring
Use ES6+ features like arrow functions, destructuring, and spread operators
Use async/await instead of raw promises for asynchronous code

**/*.{js,jsx,ts,tsx}: Maximum line length of 100 characters
Use 2 spaces for indentation, no tabs
Use semicolons at the end of statements
Include spaces around operators (e.g., a + b instead of a+b)
Always use curly braces for control statements
Place opening braces on the same line as their statement
No trailing whitespace at the end of lines

**/*.{js,jsx,ts,tsx}: Each file should contain only one main component
File names should match the component name
Keep component files focused with minimal dependencies
Group import statements in the following order: 1. React/framework libraries 2. Third-party libraries 3. Internal modules 4. Relative path imports (parent directories first, then child directories) 5. Type imports 6. Style imports
Sort imports alphabetically within each group
Leave a blank line between import groups
Components should be organized in the following order: 1. Import statements 2. Type definitions 3. Constant declarations 4. Component function 5. Hook calls 6. Event handlers 7. Helper render functions 8. JSX return statement
Extract complex logic in...

Files:

  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/i18n/src/zh-Hans/ui.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/03-typescript-guidelines.mdc)

**/*.{ts,tsx}: Avoid using any type whenever possible - it defeats the purpose of TypeScript
When tempted to use any, use unknown type instead and add proper type guards
Always define explicit return types for functions, especially for public APIs
Prefer extending existing types over creating entirely new ones
Use TypeScript's utility types to derive new types: Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Record<K, T>
Use union types and intersection types to combine existing types
Always import types explicitly using the import type syntax
Group type imports separately from value imports
Minimize creating local type aliases for imported types

Explicitly type props interfaces/types

Files:

  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/i18n/src/zh-Hans/ui.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

Follow NestJS patterns for backend code

Files:

  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/i18n/src/zh-Hans/ui.ts
**/*.{css,scss,sass,less,styl,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/09-design-system.mdc)

**/*.{css,scss,sass,less,styl,js,jsx,ts,tsx}: Use Primary color (#155EEF) as the main brand color for buttons, links, and accents
Use Error color (#F04438) for error states and destructive actions
Use Success color (#12B76A) for success states and confirmations
Use Warning color (#F79009) for warnings and important notifications
Use Info color (#0BA5EC) for informational elements

Files:

  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/i18n/src/zh-Hans/ui.ts
**/*.{css,scss,less,styl,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{css,scss,less,styl,js,jsx,ts,tsx}: Use the primary blue (#155EEF) for main UI elements, CTAs, and active states
Use red (#F04438) only for errors, warnings, and destructive actions
Use green (#12B76A) for success states and confirmations
Use orange (#F79009) for warning states and important notifications
Use blue (#0BA5EC) for informational elements
Maintain a minimum contrast ratio of 4.5:1 for text

Files:

  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/i18n/src/zh-Hans/ui.ts
packages/i18n/src/{en-US,zh-Hans}/**

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

packages/i18n/src/{en-US,zh-Hans}/**: Add new translation keys to both language files: packages/i18n/src/en-US and packages/i18n/src/zh-Hans
Maintain consistency in naming conventions for translation keys
Use descriptive keys that reflect the content in translation files
Group related translation keys together
Use namespaces for different sections of the application in translation keys
Follow a hierarchical structure for nested components in translation keys
Support dynamic content with placeholders in translation strings

Files:

  • packages/i18n/src/zh-Hans/ui.ts
🧠 Learnings (1)
📚 Learning: 2025-07-21T02:58:52.235Z
Learnt from: CR
PR: refly-ai/refly#0
File: .cursor/rules/09-i18n-guidelines.mdc:0-0
Timestamp: 2025-07-21T02:58:52.235Z
Learning: Applies to packages/i18n/src/{en-US,zh-Hans}/** : Add new translation keys to both language files: packages/i18n/src/en-US and packages/i18n/src/zh-Hans

Applied to files:

  • packages/i18n/src/zh-Hans/ui.ts
🧬 Code graph analysis (1)
apps/api/src/modules/skill/skill-invoker.service.ts (1)
apps/api/src/modules/tool-call/tool-call.service.ts (1)
  • persistToolCall (219-284)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build / Build
🔇 Additional comments (4)
packages/i18n/src/zh-Hans/ui.ts (1)

2947-2948: LGTM! Both language files properly updated.

The Chinese translations for parameters and result are accurate and properly formatted. The corresponding English translations are confirmed to exist at lines 3000-3001 in packages/i18n/src/en-US/ui.ts with matching structure and key names. Both files follow the required coding guidelines for maintaining parallel translation keys.

apps/api/src/modules/skill/skill-invoker.service.ts (3)

31-31: Add UUID fallback for tool call IDs

You’ll need a safe fallback ID when run_id is missing. Import randomUUID once.

[raise_recommended_refactor]

 import { EventEmitter } from 'node:events';
+import { randomUUID } from 'node:crypto';

694-708: Confirm extractChunkContent handles undefined chunk

chunk can be undefined; ensure helper is defensive to avoid runtime errors.

If not, guard:

-            const { content, reasoningContent } = extractChunkContent(chunk);
+            const { content, reasoningContent } = chunk ? extractChunkContent(chunk) : { content: '', reasoningContent: '' };

286-295: Incorrect review comment — syncRequestUsage is idempotent and does not double-count

The consumer implementation shows syncRequestUsage recalculates the total request count from the actionResult table and updates (not increments) the meter with that total. This design is fully idempotent—multiple job invocations produce identical results and do not compound usage counts. The double enqueue is redundant but does not cause the double-counting concern raised.

Likely an incorrect or invalid review comment.

Comment on lines 4 to +6
import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/dist/messages';
import { AIMessage, HumanMessage } from '@langchain/core/messages';
import { InjectQueue } from '@nestjs/bullmq';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify MessageContentComplex availability from public export

If MessageContentComplex isn’t publicly exported, switch to the correct public type (e.g., MessageContent).


🌐 Web query:

Is `MessageContentComplex` exported from '@langchain/core/messages' in LangChain JS v0.3.x? If not, what is the correct public type for complex (text + image_url) message content?

💡 Result:

Short answer: No — MessageContentComplex is an internal/type-only alias and is not exported from the public entrypoint @langchain/core/messages in v0.3.x. Use the public MessageContent type (which accepts a string or an array of complex content blocks, i.e. text + image_url blocks) for multimodal (text + image_url) message content. [1][2]

Sources:

  • MessageContentComplex / MessageContent type declarations (langchain-core v0.3 API docs). [1]
  • Module index for @langchain/core/messages showing the public exports (types are not re-exported from the entrypoint). [2]

Use public MessageContent type instead of internal MessageContentComplex

MessageContentComplex is an internal/type-only alias and is not exported from the public entrypoint @langchain/core/messages in v0.3.x. Replace the import with the public MessageContent type (which accepts a string or an array of complex content blocks, i.e. text + image_url blocks):

import { AIMessageChunk, BaseMessage, MessageContent } from '@langchain/core/messages';
import { AIMessage, HumanMessage } from '@langchain/core/messages';
import { InjectQueue } from '@nestjs/bullmq';

Also consolidate the LangChain imports into a single statement from the public entrypoint (not /dist/messages).

🤖 Prompt for AI Agents
In apps/api/src/modules/skill/skill-invoker.service.ts around lines 4 to 6, the
code imports MessageContentComplex from the internal path
'@langchain/core/dist/messages' — replace that with the public MessageContent
type and consolidate LangChain imports to the public entrypoint; specifically,
import AIMessageChunk, BaseMessage, and MessageContent from
'@langchain/core/messages' (and keep AIMessage, HumanMessage from the same
single import) instead of using the /dist path and the internal alias.

Comment on lines 535 to 538
// tool callId, now we use first time returned run_id as tool call id
let toolCallId = '';
const startTs = Date.now();
for await (const event of skill.streamEvents(input, {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Per-call timestamps and safe toolCallId fallback

startTs is captured once and reused; on concurrent/serial tools this skews createdAt and XML. Also run_id can be empty. Track per-call toolStartTs and generate a fallback ID.

-      // tool callId, now we use first time returned run_id as tool call id
-      let toolCallId = '';
-      const startTs = Date.now();
+      // Per-call tool identifiers and timestamps
+      let toolCallId = '';
+      let toolStartTs = 0;

@@
-            const buildToolUseXML = (includeResult: boolean, errorMsg: string, updatedTs: number) =>
+            const buildToolUseXML = (includeResult: boolean, errorMsg: string, updatedTs: number) =>
               this.toolCallService.generateToolUseXML({
                 toolCallId,
                 includeResult,
                 errorMsg,
                 metadata: {
                   name: event.metadata?.name,
                   type: event.metadata?.type as string | undefined,
                   toolsetKey: event.metadata?.toolsetKey,
                   toolsetName: event.metadata?.toolsetName,
                 },
                 input: event.data?.input,
                 output: event.data?.output,
-                startTs: startTs,
+                startTs: toolStartTs,
                 updatedTs: updatedTs,
               });
@@
-              const createdAt = startTs;
+              const createdAt = toolStartTs || Date.now();
               const updatedAt = Date.now();
@@
-            if (event.event === 'on_tool_start') {
-              toolCallId = String(event.run_id ?? '');
+            if (event.event === 'on_tool_start') {
+              toolCallId = String(event.run_id ?? '') || randomUUID();
+              toolStartTs = Date.now();
               // Persist tool call result
               await persistToolCall(ToolCallStatus.EXECUTING, {
                 input: event.data?.input,
                 output: '',
               });

Also applies to: 566-581, 596-611, 615-635

🤖 Prompt for AI Agents
In apps/api/src/modules/skill/skill-invoker.service.ts around lines 535-538 (and
similarly for 566-581, 596-611, 615-635): startTs is captured once outside the
per-tool-call loop which skews createdAt/XML when multiple tool calls occur, and
run_id may be empty so toolCallId can be blank; move the timestamp capture
inside the per-event/per-call handling (e.g., toolStartTs = Date.now() at the
start of each tool call), use that per-call timestamp for createdAt/XML, and
derive toolCallId from the first non-empty run_id for that call or generate a
safe fallback ID (timestamp+counter or UUID) when run_id is empty; apply the
same per-call timestamp and fallback-id logic to the other listed ranges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/modules/skill/skill-invoker.service.ts (1)

535-624: Track tool-call timestamps per invocation

startTs is captured once outside the event loop, so every tool call (even ones that begin later) is persisted with the same createdAt and XML <createdAt> value. This makes timelines inaccurate and can invert durations when multiple tools fire. Please capture the timestamp when each tool actually starts—e.g., store Date.now() keyed by toolCallId on on_tool_start, reuse it for on_tool_end/on_tool_error, and clear it after releasing the ID.

-      // tool callId, now we use first time returned run_id as tool call id
-      const startTs = Date.now();
+      const toolStartTs = new Map<string, number>();
@@
-            const buildToolUseXML = (includeResult: boolean, errorMsg: string, updatedTs: number) =>
+            const buildToolUseXML = (includeResult: boolean, errorMsg: string, updatedTs: number) => {
+              const startTs = toolStartTs.get(toolCallId) ?? updatedTs;
               this.toolCallService.generateToolUseXML({
                 toolCallId,
                 includeResult,
                 errorMsg,
                 metadata: {
                   name: toolName,
                   type: event.metadata?.type as string | undefined,
                   toolsetKey: toolsetId,
                   toolsetName: event.metadata?.toolsetName,
                 },
                 input: event.data?.input,
                 output: event.data?.output,
-                startTs: startTs,
+                startTs,
                 updatedTs: updatedTs,
               });
+            };
@@
             if (event.event === 'on_tool_start') {
+              toolStartTs.set(toolCallId, Date.now());
               await persistToolCall(ToolCallStatus.EXECUTING, {
                 input: event.data?.input,
                 output: '',
               });
@@
             if (event.event === 'on_tool_error') {
@@
               this.toolCallService.releaseToolCallId({
                 resultId,
                 version,
                 toolName,
                 toolsetId,
                 runId,
               });
+              toolStartTs.delete(toolCallId);
               break;
             }
             if (event.event === 'on_tool_end') {
@@
               this.toolCallService.releaseToolCallId({
                 resultId,
                 version,
                 toolName,
                 toolsetId,
                 runId,
               });
+              toolStartTs.delete(toolCallId);
               break;
             }
♻️ Duplicate comments (2)
apps/api/src/modules/skill/skill-invoker.service.ts (1)

4-6: Use public LangChain exports

Importing from @langchain/core/dist/messages pulls internal files that LangChain does not guarantee to keep stable. Please switch to the documented public entry point (@langchain/core/messages) and use the exported MessageContent type instead of the internal MessageContentComplex.

-import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/dist/messages';
-import { AIMessage, HumanMessage } from '@langchain/core/messages';
+import {
+  AIMessage,
+  AIMessageChunk,
+  BaseMessage,
+  HumanMessage,
+  MessageContent,
+} from '@langchain/core/messages';
apps/api/src/modules/tool-call/tool-call.service.ts (1)

93-125: Escape tool-call payload before emitting XML

generateToolUseXML writes raw input/output/error directly into XML. Any <, &, quotes, or literal HTML coming from a tool will break the markup and exposes the renderer to XSS. Please add an XML escaping helper (and stringify non-strings safely) before interpolating values.

-    const lines: string[] = [];
+    const toStringSafe = (value: unknown): string => {
+      try {
+        return typeof value === 'string' ? value : JSON.stringify(value);
+      } catch {
+        return '';
+      }
+    };
+    const escapeXml = (raw: string): string =>
+      raw
+        .replace(/&/g, '&amp;')
+        .replace(/</g, '&lt;')
+        .replace(/>/g, '&gt;')
+        .replace(/"/g, '&quot;')
+        .replace(/'/g, '&apos;');
+
+    const lines: string[] = [];
@@
-    lines.push('<arguments>');
-    lines.push(input ? (typeof input === 'string' ? input : JSON.stringify(input)) : '');
-    lines.push('</arguments>');
+    lines.push('<arguments>');
+    lines.push(escapeXml(toStringSafe(input)));
+    lines.push('</arguments>');
@@
-      lines.push(
-        `<result>${output ? (typeof output === 'string' ? output : JSON.stringify(output)) : ''}</result>`,
-      );
-      lines.push(
-        `<error>${errorMsg ? (typeof errorMsg === 'string' ? errorMsg : JSON.stringify(errorMsg)) : ''}</error>`,
-      );
+      lines.push(`<result>${escapeXml(toStringSafe(output))}</result>`);
+      lines.push(`<error>${escapeXml(toStringSafe(errorMsg))}</error>`);
@@
-      lines.push(
-        `<result>${output ? (typeof output === 'string' ? output : JSON.stringify(output)) : ''}</result>`,
-      );
+      lines.push(`<result>${escapeXml(toStringSafe(output))}</result>`);
🧹 Nitpick comments (1)
packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (1)

223-225: Consider simplifying the tool_call event routing.

The onToolCall handler is a thin wrapper that simply forwards to onSkillStream. Since onSkillStream already calls updateToolCallsFromXml to parse tool_use XML from content, having a separate onToolCall callback adds indirection without clear benefit.

Consider either:

  1. Remove the separate onToolCall handler and let tool_call events go through the standard onSkillStream path
  2. If tool_call events require distinct processing, implement that logic here instead of just forwarding

Based on the current implementation, option 1 seems more appropriate unless there's a future plan to handle tool_call events differently.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89d9056 and 09ebf73.

📒 Files selected for processing (8)
  • apps/api/src/modules/skill/skill-invoker.service.ts (4 hunks)
  • apps/api/src/modules/tool-call/tool-call.service.ts (1 hunks)
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (5 hunks)
  • packages/ai-workspace-common/src/utils/sse-post.ts (3 hunks)
  • packages/openapi-schema/schema.yml (4 hunks)
  • packages/openapi-schema/src/schemas.gen.ts (4 hunks)
  • packages/openapi-schema/src/types.gen.ts (5 hunks)
  • packages/skill-template/src/base.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Always use optional chaining (?.) when accessing object properties.
Always use nullish coalescing (??) or default values for potentially undefined values.
Always check array existence before using array methods.
Always validate object properties before destructuring.
Always use single quotes for string literals in JavaScript/TypeScript code.

Use JSDoc style comments for functions and classes in JavaScript/TypeScript

**/*.{js,jsx,ts,tsx}: Use single quotes for string literals
Always use optional chaining (?.) when accessing object properties
Always use nullish coalescing (??) or default values for potentially undefined values
Always check array existence before using array methods
Validate object properties before destructuring
Use ES6+ features like arrow functions, destructuring, and spread operators
Use async/await instead of raw promises for asynchronous code

**/*.{js,jsx,ts,tsx}: Maximum line length of 100 characters
Use 2 spaces for indentation, no tabs
Use semicolons at the end of statements
Include spaces around operators (e.g., a + b instead of a+b)
Always use curly braces for control statements
Place opening braces on the same line as their statement
No trailing whitespace at the end of lines

**/*.{js,jsx,ts,tsx}: Each file should contain only one main component
File names should match the component name
Keep component files focused with minimal dependencies
Group import statements in the following order: 1. React/framework libraries 2. Third-party libraries 3. Internal modules 4. Relative path imports (parent directories first, then child directories) 5. Type imports 6. Style imports
Sort imports alphabetically within each group
Leave a blank line between import groups
Components should be organized in the following order: 1. Import statements 2. Type definitions 3. Constant declarations 4. Component function 5. Hook calls 6. Event handlers 7. Helper render functions 8. JSX return statement
Extract complex logic in...

Files:

  • packages/skill-template/src/base.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/openapi-schema/src/types.gen.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/03-typescript-guidelines.mdc)

**/*.{ts,tsx}: Avoid using any type whenever possible - it defeats the purpose of TypeScript
When tempted to use any, use unknown type instead and add proper type guards
Always define explicit return types for functions, especially for public APIs
Prefer extending existing types over creating entirely new ones
Use TypeScript's utility types to derive new types: Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Record<K, T>
Use union types and intersection types to combine existing types
Always import types explicitly using the import type syntax
Group type imports separately from value imports
Minimize creating local type aliases for imported types

Explicitly type props interfaces/types

Files:

  • packages/skill-template/src/base.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/openapi-schema/src/types.gen.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

Follow NestJS patterns for backend code

Files:

  • packages/skill-template/src/base.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/openapi-schema/src/types.gen.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{css,scss,sass,less,styl,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/09-design-system.mdc)

**/*.{css,scss,sass,less,styl,js,jsx,ts,tsx}: Use Primary color (#155EEF) as the main brand color for buttons, links, and accents
Use Error color (#F04438) for error states and destructive actions
Use Success color (#12B76A) for success states and confirmations
Use Warning color (#F79009) for warnings and important notifications
Use Info color (#0BA5EC) for informational elements

Files:

  • packages/skill-template/src/base.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/openapi-schema/src/types.gen.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{css,scss,less,styl,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{css,scss,less,styl,js,jsx,ts,tsx}: Use the primary blue (#155EEF) for main UI elements, CTAs, and active states
Use red (#F04438) only for errors, warnings, and destructive actions
Use green (#12B76A) for success states and confirmations
Use orange (#F79009) for warning states and important notifications
Use blue (#0BA5EC) for informational elements
Maintain a minimum contrast ratio of 4.5:1 for text

Files:

  • packages/skill-template/src/base.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/openapi-schema/src/types.gen.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
🧬 Code graph analysis (6)
packages/skill-template/src/base.ts (3)
packages/openapi-schema/src/types.gen.ts (1)
  • SkillEvent (3123-3186)
packages/ai-workspace-common/src/requests/types.gen.ts (1)
  • SkillEvent (3114-3173)
packages/request/src/requests/types.gen.ts (1)
  • SkillEvent (3114-3173)
packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (2)
packages/openapi-schema/src/types.gen.ts (3)
  • ActionStep (1634-1669)
  • ActionStepMeta (1602-1607)
  • SkillEvent (3123-3186)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (2)
  • parseToolCallFromChunk (439-497)
  • mergeToolCallById (502-514)
packages/ai-workspace-common/src/utils/sse-post.ts (1)
packages/openapi-schema/src/types.gen.ts (1)
  • SkillEvent (3123-3186)
apps/api/src/modules/skill/skill-invoker.service.ts (1)
apps/api/src/modules/tool-call/tool-call.service.ts (1)
  • persistToolCall (269-334)
packages/openapi-schema/src/types.gen.ts (3)
packages/agent-tools/src/base.ts (1)
  • ToolCallResult (4-25)
packages/ai-workspace-common/src/requests/types.gen.ts (1)
  • status2 (5348-5348)
packages/request/src/requests/types.gen.ts (1)
  • status2 (5348-5348)
apps/api/src/modules/tool-call/tool-call.service.ts (3)
apps/api/src/modules/skill/skill-invoker.service.ts (1)
  • Injectable (59-1066)
apps/api/src/modules/action/action.service.ts (1)
  • Injectable (13-303)
packages/openapi-schema/src/types.gen.ts (5)
  • type (748-748)
  • ActionStepMeta (1602-1607)
  • status (297-297)
  • ActionResult (1726-1837)
  • ActionStatus (1547-1547)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build / Build
🔇 Additional comments (4)
packages/skill-template/src/base.ts (1)

267-267: LGTM!

The tool_call event addition to SkillEventMap is consistent with other event definitions and properly typed.

packages/openapi-schema/src/types.gen.ts (3)

1665-1668: LGTM!

The toolCalls field additions to ActionStep and ActionResult are properly typed and documented. The optional nature is appropriate since not all action steps will involve tool calls.

Also applies to: 1801-1804


3120-3120: LGTM!

The tool_call event type addition to SkillEventType and the corresponding toolCallResult field in SkillEvent follow the established pattern for event-specific data fields.

Also applies to: 3182-3185


3188-3250: Verify consistency with other ToolCallResult definitions.

The ToolCallResult type is well-defined with comprehensive fields for tracking tool call lifecycle. However, note that packages/agent-tools/src/base.ts also defines a ToolCallResult interface with a different structure (status: 'success' | 'error' instead of 'executing' | 'completed' | 'failed').

Ensure this naming doesn't cause confusion and that the two types serve distinct purposes (API/persistence vs. internal tool execution).

Comment on lines 264 to 267
if (skillEvent.event === 'tool_call') {
onToolCall?.(skillEvent);
continue;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clarify tool_call event handling and add safeguard.

The current implementation immediately invokes onToolCall and bypasses batching with continue. This means:

  1. If onToolCall is undefined: tool_call events are silently dropped (the optional chaining on line 265 returns undefined, then continue skips adding to batch)
  2. No batching/throttling: Unlike other events, tool_call events are processed immediately

Consider whether this is intentional. If tool_call events need immediate processing, document why. If not, remove the continue to let them flow through normal batching:

  if (skillEvent.event === 'tool_call') {
    onToolCall?.(skillEvent);
-   continue;
  }

Or, if immediate processing is required, provide a fallback:

  if (skillEvent.event === 'tool_call') {
-   onToolCall?.(skillEvent);
-   continue;
+   if (onToolCall) {
+     onToolCall(skillEvent);
+     continue;
+   }
+   // Fallback: add to batch if no handler provided
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (skillEvent.event === 'tool_call') {
onToolCall?.(skillEvent);
continue;
}
if (skillEvent.event === 'tool_call') {
onToolCall?.(skillEvent);
}
Suggested change
if (skillEvent.event === 'tool_call') {
onToolCall?.(skillEvent);
continue;
}
if (skillEvent.event === 'tool_call') {
if (onToolCall) {
onToolCall(skillEvent);
continue;
}
// Fallback: add to batch if no handler provided
}
🤖 Prompt for AI Agents
In packages/ai-workspace-common/src/utils/sse-post.ts around lines 264 to 267,
tool_call events are currently handled by calling onToolCall?.(skillEvent) and
then continue, which both drops the event when onToolCall is undefined and
bypasses normal batching. Decide the intended behavior and implement one of two
fixes: (A) If tool_call must be processed immediately, explicitly call
onToolCall when present but always also push the event into the regular batch
queue (remove the continue) so it isn’t lost when the handler is undefined, and
add a short comment explaining immediate processing; or (B) If tool_call should
follow normal batching/throttling, remove the onToolCall invocation and the
continue so the event flows into the batch; in either case add a brief inline
comment documenting the choice and add a small test or runtime assertion to
ensure events are not silently dropped.

Comment on lines +6825 to +6849
# input can be structured object or raw string; keep it flexible
oneOf:
- type: object
- type: string
description: Tool input arguments
output:
# output can be structured object or raw string
oneOf:
- type: object
- type: string
description: Tool output result
error:
type: string
description: Error message if tool execution failed
status:
type: string
description: Tool call status
enum:
- executing
- completed
- failed
createdAt:
type: number
description: Tool call start timestamp (milliseconds)
updatedAt:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Allow arbitrary JSON payloads for tool call input/output.

The schema constrains input/output to object|string, but our tool runner already emits arrays, booleans, numbers, and nulls (e.g., web-search returns a result list, boolean toggles, numeric counters). Generated clients based on this spec will treat those perfectly valid responses as type violations. Please model these fields as “any JSON value” so client SDKs stay in sync with what the backend actually serializes.

Apply this diff to loosen the payload typing:

         input:
-          # input can be structured object or raw string; keep it flexible
-          oneOf:
-            - type: object
-            - type: string
-          description: Tool input arguments
+          $ref: '#/components/schemas/ToolCallPayload'
           description: Tool input arguments
         output:
-          # output can be structured object or raw string
-          oneOf:
-            - type: object
-            - type: string
-          description: Tool output result
+          $ref: '#/components/schemas/ToolCallPayload'
+          description: Tool output result

And add this helper schema once under components.schemas:

+    ToolCallPayload:
+      description: Arbitrary JSON payload captured from the tool invocation.
+      nullable: true
+      oneOf:
+        - type: object
+          additionalProperties: true
+        - type: array
+          items: {}
+        - type: string
+        - type: number
+        - type: integer
+        - type: boolean

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +4478 to +4488
type: 'number',
description: 'Tool call start timestamp (milliseconds)',
},
updatedAt: {
type: 'number',
description: 'Tool call last update timestamp (milliseconds)',
},
deletedAt: {
type: 'number',
description: 'Tool call deletion timestamp (milliseconds)',
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix ToolCallResult timestamp schema mismatch

createdAt, updatedAt, and deletedAt are stored as DateTime in Prisma and are serialized to ISO strings when the API returns tool call records. Declaring them as number (milliseconds) makes the OpenAPI contract lie about the wire format and will break generated clients. Please change these fields to type: 'string' with format: 'date-time' (and make deletedAt nullable) so the schema matches the actual payload.

-    createdAt: {
-      type: 'number',
-      description: 'Tool call start timestamp (milliseconds)',
-    },
-    updatedAt: {
-      type: 'number',
-      description: 'Tool call last update timestamp (milliseconds)',
-    },
-    deletedAt: {
-      type: 'number',
-      description: 'Tool call deletion timestamp (milliseconds)',
-    },
+    createdAt: {
+      type: 'string',
+      format: 'date-time',
+      description: 'Tool call start time',
+    },
+    updatedAt: {
+      type: 'string',
+      format: 'date-time',
+      description: 'Tool call last update time',
+    },
+    deletedAt: {
+      oneOf: [
+        { type: 'string', format: 'date-time' },
+        { type: 'null' },
+      ],
+      description: 'Tool call deletion time (null when active)',
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type: 'number',
description: 'Tool call start timestamp (milliseconds)',
},
updatedAt: {
type: 'number',
description: 'Tool call last update timestamp (milliseconds)',
},
deletedAt: {
type: 'number',
description: 'Tool call deletion timestamp (milliseconds)',
},
createdAt: {
type: 'string',
format: 'date-time',
description: 'Tool call start time',
},
updatedAt: {
type: 'string',
format: 'date-time',
description: 'Tool call last update time',
},
deletedAt: {
oneOf: [
{ type: 'string', format: 'date-time' },
{ type: 'null' },
],
description: 'Tool call deletion time (null when active)',
},
🤖 Prompt for AI Agents
In packages/openapi-schema/src/schemas.gen.ts around lines 4478 to 4488, the
ToolCallResult timestamp fields are declared as numbers but the API serializes
them as ISO datetime strings; change createdAt and updatedAt to type: 'string'
with format: 'date-time', and change deletedAt to type: 'string', format:
'date-time' and make it nullable (or allow null via nullable: true or type:
['string','null'] depending on schema style) so the OpenAPI schema matches the
actual JSON payload.

- Updated tool call management to include consolidated tool call history by step, improving tracking and reducing duplication.
- Introduced new event types for tool call start and stream, enhancing event handling capabilities.
- Refactored SkillInvokerService and ToolCallService to utilize the new ToolCallResult model for better data management.
- Improved UI rendering for tool call results, ensuring compliance with coding standards and performance optimizations.
- Updated OpenAPI schema and TypeScript types to reflect new event structures and tool call handling improvements.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/ai-workspace-common/src/utils/sse-post.ts (1)

142-147: Start event fires repeatedly; set the guard flag.

onSkillStart is gated by isSkillFirstMessage but the flag is never set to false, causing repeated starts. Set it to false after the first start.

           if (isSkillFirstMessage) {
-            onSkillStart(lastStartEvent);
+            onSkillStart(lastStartEvent);
+            isSkillFirstMessage = false;
           }
packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts (2)

15-26: Token-usage cache key ignores values; can return stale totals.

Keying on step names only will miss updates when usage changes but names don’t. Include usage in the key (e.g., hash of steps.map(s => s.tokenUsage)).

-  return (steps: ActionStep[]) => {
-    const cacheKey = JSON.stringify(steps.map((s) => s?.name));
+  return (steps: ActionStep[]) => {
+    const cacheKey = JSON.stringify(
+      steps.map((s) => ({ name: s?.name, usage: s?.tokenUsage ?? [] }))
+    );

84-95: Array comparison by length only can skip real updates.

isNodeDataEqual treats arrays equal if lengths match, missing content changes (e.g., same artifact count but different items). Compare a stable fingerprint instead (ids or JSON of minimal fields).

-    else if (Array.isArray(newMetadata[key])) {
-      const oldArray = oldMetadata[key] || [];
-      const newArray = newMetadata[key] || [];
-      if (oldArray.length !== newArray.length) {
-        return false;
-      }
-    }
+    else if (Array.isArray(newMetadata[key])) {
+      const oldArray = oldMetadata[key] || [];
+      const newArray = newMetadata[key] || [];
+      if (oldArray.length !== newArray.length) return false;
+      const oldSig = JSON.stringify(oldArray);
+      const newSig = JSON.stringify(newArray);
+      if (oldSig !== newSig) return false;
+    }
apps/api/src/modules/skill/skill-invoker.service.ts (1)

544-635: Track per-call timestamps instead of a single startTs

startTs is captured once before the event loop, so every tool call is persisted and emitted with the same createdAt. When multiple tools run in one action, the auditing data becomes meaningless and completion XML shows identical start times. Please store a per-tool timestamp (e.g., Map<toolCallId, number>) on on_tool_start, reuse it in the XML builder/persistence, and fall back to Date.now() only if the ID was never seen.

-      const startTs = Date.now();
+      const toolStartTimes = new Map<string, number>();
...
-            const buildToolUseXML = (includeResult: boolean, errorMsg: string, updatedTs: number) =>
+            const buildToolUseXML = (
+              includeResult: boolean,
+              errorMsg: string,
+              updatedTs: number,
+              startTs: number,
+            ) =>
               this.toolCallService.generateToolUseXML({
                 toolCallId,
                 includeResult,
                 errorMsg,
                 metadata: {
                   name: toolName,
                   type: event.metadata?.type as string | undefined,
                   toolsetKey: toolsetId,
                   toolsetName: event.metadata?.toolsetName,
                 },
                 input: event.data?.input,
                 output: event.data?.output,
-                startTs: startTs,
+                startTs,
                 updatedTs: updatedTs,
               });
...
             if (event.event === 'on_tool_start') {
               if (!toolCallIds.has(toolCallId)) {
                 toolCallIds.add(toolCallId);
+                const startedAt = Date.now();
+                toolStartTimes.set(toolCallId, startedAt);
                 await persistToolCall(ToolCallStatus.EXECUTING, {
                   input: event.data?.input,
                   output: '',
                 });
                 // Send XML for executing state
-                const xmlContent = buildToolUseXML(false, '', Date.now());
+                const xmlContent = buildToolUseXML(false, '', Date.now(), startedAt);
...
             if (event.event === 'on_tool_error') {
               const errorMsg = String((event.data as any)?.error ?? 'Tool execution failed');
               await persistToolCall(ToolCallStatus.FAILED, {
                 input: undefined,
                 output: event.data?.output,
                 errorMessage: errorMsg,
               });
               // Send XML for failed state
-              const xmlContent = buildToolUseXML(false, errorMsg, Date.now());
+              const startTs = toolStartTimes.get(toolCallId) ?? Date.now();
+              const xmlContent = buildToolUseXML(false, errorMsg, Date.now(), startTs);
...
             if (event.event === 'on_tool_end') {
               await persistToolCall(ToolCallStatus.COMPLETED, {
                 input: undefined,
                 output: event.data?.output,
               });
               // Extract tool_call_chunks from AIMessageChunk
               if (event.metadata.langgraph_node === 'tools' && event.data?.output) {
...
-                const xmlContent = buildToolUseXML(true, '', Date.now());
+                const startTs = toolStartTimes.get(toolCallId) ?? Date.now();
+                const xmlContent = buildToolUseXML(true, '', Date.now(), startTs);
               }
               this.toolCallService.releaseToolCallId({
                 resultId,
                 version,
                 toolName,
                 toolsetId,
                 runId,
               });
               toolCallIds.delete(toolCallId);
+              toolStartTimes.delete(toolCallId);
♻️ Duplicate comments (8)
packages/ai-workspace-common/src/utils/sse-post.ts (1)

272-275: Don’t drop tool_call_start when no handler is provided.

Current logic onToolCallStart?.(...); continue; silently drops the event if the handler is undefined. Fall back to batching so UIs still receive it.

-                if (skillEvent.event === 'tool_call_start') {
-                  onToolCallStart?.(skillEvent);
-                  continue;
-                }
+                if (skillEvent.event === 'tool_call_start') {
+                  if (onToolCallStart) {
+                    onToolCallStart(skillEvent);
+                    continue;
+                  }
+                  // No handler: allow it to flow through the batch.
+                }
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (2)

439-444: Normalize status; derive sane default.

Defaulting via ToolCallStatus.FAILED.toString() risks casing mismatch and wrong default. Infer from presence of result/error and normalize to lowercase.

-  // the status is always failed if the result is not present
-  const status = (attrs['data-tool-call-status'] ?? ToolCallStatus.FAILED.toString()) as
-    | 'executing'
-    | 'completed'
-    | 'failed';
+  const rawStatus = attrs['data-tool-call-status']?.toLowerCase?.();
+  const hasResult = !!attrs['data-tool-result'];
+  const hasErrorAttr = !!attrs['data-tool-error'];
+  const status: 'executing' | 'completed' | 'failed' =
+    rawStatus === 'completed' ? 'completed' :
+    rawStatus === 'failed' ? 'failed' :
+    rawStatus === 'executing' ? 'executing' :
+    hasResult ? 'completed' :
+    hasErrorAttr ? 'failed' : 'executing';

465-477: Bug: error is taken from result; use <error> content.

Set error from data-tool-error, not data-tool-result, and parse it consistently.

-    output: safeParse(resultStr) as Record<string, unknown> | undefined,
-    error: status === 'failed' ? String(resultStr ?? '') : undefined,
+    output: safeParse(resultStr) as Record<string, unknown> | undefined,
+    error: status === 'failed' ? String(attrs['data-tool-error'] ?? '') : undefined,
packages/openapi-schema/schema.yml (1)

6825-6836: Allow any JSON shape for tool-call input/output.

The ToolCallService persists whatever JSON the tool returns (arrays, booleans, numbers, null, nested mixes). Constraining input/output to object|string means generated clients will reject perfectly valid responses we already emit. Please loosen these fields to “any JSON value,” e.g. by factoring them through a helper schema that covers object/array/primitive/null.

         input:
-          # input can be structured object or raw string; keep it flexible
-          oneOf:
-            - type: object
-            - type: string
+          $ref: '#/components/schemas/ToolCallPayload'
           description: Tool input arguments
         output:
-          # output can be structured object or raw string
-          oneOf:
-            - type: object
-            - type: string
-          description: Tool output result
+          $ref: '#/components/schemas/ToolCallPayload'
+          description: Tool output result
         error:
@@
+    ToolCallPayload:
+      description: Arbitrary JSON payload captured from the tool invocation.
+      nullable: true
+      oneOf:
+        - type: object
+          additionalProperties: true
+        - type: array
+          items: {}
+        - type: string
+        - type: number
+        - type: integer
+        - type: boolean
packages/openapi-schema/src/schemas.gen.ts (1)

4479-4489: Fix ToolCallResult timestamp types to match API (ISO date-time, not numbers).

createdAt/updatedAt/deletedAt are serialized as ISO strings; declaring number breaks clients. Make them string date-time (deletedAt nullable).

Apply this diff:

-    createdAt: {
-      type: 'number',
-      description: 'Tool call start timestamp (milliseconds)',
-    },
-    updatedAt: {
-      type: 'number',
-      description: 'Tool call last update timestamp (milliseconds)',
-    },
-    deletedAt: {
-      type: 'number',
-      description: 'Tool call deletion timestamp (milliseconds)',
-    },
+    createdAt: {
+      type: 'string',
+      format: 'date-time',
+      description: 'Tool call start time',
+    },
+    updatedAt: {
+      type: 'string',
+      format: 'date-time',
+      description: 'Tool call last update time',
+    },
+    deletedAt: {
+      oneOf: [
+        { type: 'string', format: 'date-time' },
+        { type: 'null' },
+      ],
+      description: 'Tool call deletion time (null when active)',
+    },

Run to confirm the type in generated types matches:

#!/bin/bash
rg -nC2 'export type ToolCallResult' packages/openapi-schema/src/types.gen.ts
rg -nC2 'createdAt' packages/openapi-schema/src/types.gen.ts
apps/api/src/modules/tool-call/tool-call.service.ts (2)

318-325: Fix return type mismatch in toJSONStr

toJSONStr is declared to return a string but returns undefined for nullish values. That fails TypeScript strict checking and can propagate unexpected undefined into Prisma writes. Return an empty string (or widen the signature) so callers always receive a string.

-    const toJSONStr = (value: unknown): string => {
-      if (value === undefined || value === null) return undefined;
+    const toJSONStr = (value: unknown): string => {
+      if (value === undefined || value === null) return '';

94-124: Escape XML payload before streaming

generateToolUseXML injects raw input, output, and errorMsg directly into XML. If those strings contain <, >, &, or quotes, the markup breaks and we risk XSS in downstream consumers rendering the XML. Please JSON-stringify as needed and escape XML entities before interpolation.

-    const lines: string[] = [];
+    const toStr = (value: unknown): string => {
+      try {
+        return typeof value === 'string' ? value : JSON.stringify(value);
+      } catch {
+        return '';
+      }
+    };
+    const escapeXml = (raw: string): string =>
+      raw
+        .replace(/&/g, '&amp;')
+        .replace(/</g, '&lt;')
+        .replace(/>/g, '&gt;')
+        .replace(/"/g, '&quot;')
+        .replace(/'/g, '&apos;');
+
+    const lines: string[] = [];
     lines.push('<tool_use>');
     lines.push(`<callId>${toolCallId}</callId>`);
     lines.push(`<name>${name ?? ''}</name>`);
     lines.push(`<type>${type ?? ''}</type>`);
     lines.push(`<toolsetKey>${toolsetKey}</toolsetKey>`);
     lines.push(`<toolsetName>${toolsetName ?? ''}</toolsetName>`);
     lines.push('<arguments>');
-    lines.push(input ? (typeof input === 'string' ? input : JSON.stringify(input)) : '');
+    lines.push(escapeXml(toStr(input)));
     lines.push('</arguments>');
     lines.push(`<createdAt>${startTs}</createdAt>`);
 
     if (errorMsg) {
-      lines.push(
-        `<result>${output ? (typeof output === 'string' ? output : JSON.stringify(output)) : ''}</result>`,
-      );
-      lines.push(
-        `<error>${errorMsg ? (typeof errorMsg === 'string' ? errorMsg : JSON.stringify(errorMsg)) : ''}</error>`,
-      );
+      lines.push(`<result>${escapeXml(toStr(output))}</result>`);
+      lines.push(`<error>${escapeXml(toStr(errorMsg))}</error>`);
       lines.push(`<status>${ToolCallStatus.FAILED}</status>`);
       lines.push(`<updatedAt>${updatedTs}</updatedAt>`);
     } else if (includeResult) {
-      lines.push(
-        `<result>${output ? (typeof output === 'string' ? output : JSON.stringify(output)) : ''}</result>`,
-      );
+      lines.push(`<result>${escapeXml(toStr(output))}</result>`);
       lines.push(`<status>${ToolCallStatus.COMPLETED}</status>`);
       lines.push(`<updatedAt>${updatedTs}</updatedAt>`);
apps/api/src/modules/skill/skill-invoker.service.ts (1)

4-6: Avoid internal LangChain /dist import and use public MessageContent type

Pulling MessageContentComplex from @langchain/core/dist/messages is an internal path that breaks whenever LangChain reshuffles its build output, and the public docs point to MessageContent from @langchain/core/messages for multimodal payloads. Please switch the import to the public entrypoint and type the content with MessageContent instead. (readmex.com)

Here’s an example fix:

-import { AIMessageChunk, BaseMessage, MessageContentComplex } from '@langchain/core/dist/messages';
-import { AIMessage, HumanMessage } from '@langchain/core/messages';
+import { AIMessage, AIMessageChunk, BaseMessage, HumanMessage, MessageContent } from '@langchain/core/messages';
...
-    let messageContent: string | MessageContentComplex[] = query;
+    let messageContent: MessageContent = query;
🧹 Nitpick comments (16)
.vscode/launch.json (2)

15-17: Restrict inspector to localhost.

NODE_OPTIONS: "--inspect=0.0.0.0:9229" exposes the debugger on all interfaces. Prefer --inspect=127.0.0.1:9229 to avoid unintended remote access.

-        "NODE_OPTIONS": "--inspect=0.0.0.0:9229"
+        "NODE_OPTIONS": "--inspect=127.0.0.1:9229"

10-20: Optional: exclude node_modules from stepping.

Add "**/node_modules/**" to skipFiles for a cleaner debug experience.

-      "skipFiles": ["<node_internals>/**"]
+      "skipFiles": ["<node_internals>/**", "**/node_modules/**"]
packages/ai-workspace-common/src/utils/sse-post.ts (2)

174-180: Handle both tool_call_stream and tool_call_end (naming divergence).

Types add tool_call_end, code handles tool_call_stream. Support both to stay forward/backward compatible until unified.

-        if (eventsByType.tool_call_stream?.length) {
-          for (const event of eventsByType.tool_call_stream) {
-            onToolCallStream?.(event);
-          }
-        }
+        const toolCallStreams = [
+          ...(eventsByType.tool_call_stream ?? []),
+          ...(eventsByType.tool_call_end ?? []),
+        ];
+        if (toolCallStreams.length) {
+          for (const event of toolCallStreams) {
+            onToolCallStream?.(event);
+          }
+        }

66-81: onStart prop is unused.

Remove it or invoke it (e.g., right before reading or when first start arrives) to avoid dead API.

-  onStart: () => void;

Or call it once before starting the read loop.

packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts (1)

215-217: Avoid double JSON serialize/parse for cloning.

JSON.parse(JSON.stringify(...)) is expensive and loses types. The updates object is created locally; pass it directly or shallow-clone if needed.

-          const nodeUpdates = JSON.parse(
-            JSON.stringify(buildNodeUpdates(resultId, merged as Partial<ActionResult>)),
-          );
+          const nodeUpdates = buildNodeUpdates(resultId, merged as Partial<ActionResult>);

and similarly below.

Also applies to: 324-326

packages/openapi-schema/src/schemas.gen.ts (1)

4448-4466: Loosen object payloads for tool input/output (optional).

For object variants under input/output, specify additionalProperties: true to avoid “empty object” inference in strict generators.

     input: {
       oneOf: [
         {
-          type: 'object',
+          type: 'object',
+          additionalProperties: true,
         },
         {
           type: 'string',
         },
       ],
       description: 'Tool input arguments',
     },
     output: {
       oneOf: [
         {
-          type: 'object',
+          type: 'object',
+          additionalProperties: true,
         },
         {
           type: 'string',
         },
       ],
       description: 'Tool output result',
     },

Also applies to: 4450-4462

packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (4)

215-241: Consider parsing tool XML in onSkillStream as well.

Tool-use tags can appear in regular model stream; parsing here improves resilience.

     const updatedStep: ActionStep = findOrCreateStep(result.steps ?? [], step);
     updatedStep.content = (updatedStep.content ?? '') + (content ?? '');
     if (!updatedStep.reasoningContent) {
       updatedStep.reasoningContent = reasoningContent ?? '';
     } else {
       updatedStep.reasoningContent =
         (updatedStep.reasoningContent ?? '') + (reasoningContent ?? '');
     }
+    // Also merge tool call state from any tool_use XML in the stream
+    updateToolCallsFromXml(updatedStep, step, content ?? '');

Based on coding guidelines


440-472: Good integration of tool-call stream; minor follow-ups.

  • Artifact handling and step cache updates are correct.
  • Optionally also merge skillEvent.toolCallResult when present to support backends that send structured payloads.
-    // merge tool calls status
-    updateToolCallsFromXml(updatedStep, step, content);
+    // merge tool calls status
+    updateToolCallsFromXml(updatedStep, step, content);
+    if (skillEvent.toolCallResult) {
+      updatedStep.toolCalls = mergeToolCallById(
+        updatedStep.toolCalls ?? [],
+        skillEvent.toolCallResult
+      );
+    }

591-596: Avoid any in event wrapper.

Use unknown[] (or a generic rest) to preserve type-safety.

-      const wrapEventHandler =
-        (handler: (...args: any[]) => void) =>
-        (...args: any[]) => {
+      const wrapEventHandler =
+        (handler: (...args: unknown[]) => void) =>
+        (...args: unknown[]) => {
           resetTimeout();
           handler(...args);
         };

Based on coding guidelines


618-621: Optional: clear step cache on invoke cleanup.

Prevents stale cache if stream ends without end/error.

-      return () => {
-        timeoutCleanup();
-      };
+      return () => {
+        timeoutCleanup();
+        clearLatestSteps(resultId);
+      };
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx (6)

2-2: Import useCallback for stable handlers.

-import React, { useMemo, useState } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';

Based on coding guidelines


19-21: Remove inline style; use Tailwind for animation timing.

-    className="w-[18px] h-[18px] text-gray-500 dark:text-gray-400 animate-spin"
-    style={{ animationDuration: '1.1s' }}
+    className="w-[18px] h-[18px] text-gray-500 dark:text-gray-400 animate-[spin_1100ms_linear_infinite]"

Based on coding guidelines


38-39: Use brand colors for status icons.

-    className="w-[18px] h-[18px] text-green-500 dark:text-green-400"
+    className="w-[18px] h-[18px] text-[#12B76A]"
-    className="w-[18px] h-[18px] text-red-500 dark:text-red-400"
+    className="w-[18px] h-[18px] text-[#F04438]"

As per coding guidelines

Also applies to: 55-56


91-101: Add stable toggle handler and localization for fallback labels.

-  const [isCollapsed, setIsCollapsed] = useState(true);
+  const [isCollapsed, setIsCollapsed] = useState(true);
+  const handleToggle = useCallback(() => setIsCollapsed((v) => !v), []);
@@
-  const toolName = props['data-tool-name'] || 'unknown';
-  const toolsetKey = props['data-tool-toolset-key'] || 'unknown';
+  const unknownText = t('components.markdown.unknown', 'Unknown');
+  const toolName = props['data-tool-name'] ?? unknownText;
+  const toolsetKey = props['data-tool-toolset-key'] ?? unknownText;

As per coding guidelines


148-154: Accessibility + remove inline styles on header toggle.

-        <div
-          className="flex items-center px-4 py-2 cursor-pointer select-none bg-gray-50 dark:bg-gray-700 min-h-[44px]"
-          onClick={() => setIsCollapsed(!isCollapsed)}
+        <div
+          className="flex items-center px-4 py-2 cursor-pointer select-none bg-gray-50 dark:bg-gray-700 min-h-[44px]"
+          role="button"
+          tabIndex={0}
+          aria-expanded={!isCollapsed}
+          onClick={handleToggle}
+          onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && (e.preventDefault(), handleToggle())}
           style={{
             fontFamily: 'Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
           }}
         >
@@
-          <ToolOutlined
-            className="text-gray-500 dark:text-gray-400"
-            style={{
-              fontSize: '16px',
-              marginRight: '12px', // Adjusted margin for spacing
-              transition: 'transform 0.2s ease-in-out',
-              transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)',
-            }}
-          />
+          <ToolOutlined
+            className={[
+              'text-gray-500 dark:text-gray-400',
+              'mr-3 text-[16px]',
+              'transition-transform duration-200 ease-in-out',
+              isCollapsed ? '-rotate-90' : 'rotate-0',
+            ].join(' ')}
+          />

As per coding guidelines

Also applies to: 156-164


114-116: Minor: nullish coalescing is redundant here.

parseToolCallStatus already defaults; keep as-is or simplify.

-  const toolCallStatus =
-    parseToolCallStatus(props['data-tool-call-status']) ?? ToolCallStatus.EXECUTING;
+  const toolCallStatus = parseToolCallStatus(props['data-tool-call-status']);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09ebf73 and 268b9a0.

📒 Files selected for processing (13)
  • .vscode/launch.json (1 hunks)
  • apps/api/src/modules/skill/skill-invoker.service.ts (5 hunks)
  • apps/api/src/modules/tool-call/tool-call.service.ts (1 hunks)
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx (5 hunks)
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (4 hunks)
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (7 hunks)
  • packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts (5 hunks)
  • packages/ai-workspace-common/src/requests/types.gen.ts (1 hunks)
  • packages/ai-workspace-common/src/utils/sse-post.ts (5 hunks)
  • packages/openapi-schema/schema.yml (4 hunks)
  • packages/openapi-schema/src/schemas.gen.ts (4 hunks)
  • packages/openapi-schema/src/types.gen.ts (5 hunks)
  • packages/skill-template/src/base.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/skill-template/src/base.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,jsx,ts,tsx}: Always use optional chaining (?.) when accessing object properties.
Always use nullish coalescing (??) or default values for potentially undefined values.
Always check array existence before using array methods.
Always validate object properties before destructuring.
Always use single quotes for string literals in JavaScript/TypeScript code.

Use JSDoc style comments for functions and classes in JavaScript/TypeScript

**/*.{js,jsx,ts,tsx}: Use single quotes for string literals
Always use optional chaining (?.) when accessing object properties
Always use nullish coalescing (??) or default values for potentially undefined values
Always check array existence before using array methods
Validate object properties before destructuring
Use ES6+ features like arrow functions, destructuring, and spread operators
Use async/await instead of raw promises for asynchronous code

**/*.{js,jsx,ts,tsx}: Maximum line length of 100 characters
Use 2 spaces for indentation, no tabs
Use semicolons at the end of statements
Include spaces around operators (e.g., a + b instead of a+b)
Always use curly braces for control statements
Place opening braces on the same line as their statement
No trailing whitespace at the end of lines

**/*.{js,jsx,ts,tsx}: Each file should contain only one main component
File names should match the component name
Keep component files focused with minimal dependencies
Group import statements in the following order: 1. React/framework libraries 2. Third-party libraries 3. Internal modules 4. Relative path imports (parent directories first, then child directories) 5. Type imports 6. Style imports
Sort imports alphabetically within each group
Leave a blank line between import groups
Components should be organized in the following order: 1. Import statements 2. Type definitions 3. Constant declarations 4. Component function 5. Hook calls 6. Event handlers 7. Helper render functions 8. JSX return statement
Extract complex logic in...

Files:

  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • packages/ai-workspace-common/src/requests/types.gen.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/openapi-schema/src/types.gen.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/03-typescript-guidelines.mdc)

**/*.{ts,tsx}: Avoid using any type whenever possible - it defeats the purpose of TypeScript
When tempted to use any, use unknown type instead and add proper type guards
Always define explicit return types for functions, especially for public APIs
Prefer extending existing types over creating entirely new ones
Use TypeScript's utility types to derive new types: Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Record<K, T>
Use union types and intersection types to combine existing types
Always import types explicitly using the import type syntax
Group type imports separately from value imports
Minimize creating local type aliases for imported types

Explicitly type props interfaces/types

Files:

  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • packages/ai-workspace-common/src/requests/types.gen.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/openapi-schema/src/types.gen.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

Follow NestJS patterns for backend code

Files:

  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • packages/ai-workspace-common/src/requests/types.gen.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/openapi-schema/src/types.gen.ts
**/*.{css,scss,sass,less,styl,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/09-design-system.mdc)

**/*.{css,scss,sass,less,styl,js,jsx,ts,tsx}: Use Primary color (#155EEF) as the main brand color for buttons, links, and accents
Use Error color (#F04438) for error states and destructive actions
Use Success color (#12B76A) for success states and confirmations
Use Warning color (#F79009) for warnings and important notifications
Use Info color (#0BA5EC) for informational elements

Files:

  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • packages/ai-workspace-common/src/requests/types.gen.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/openapi-schema/src/types.gen.ts
**/*.{css,scss,less,styl,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{css,scss,less,styl,js,jsx,ts,tsx}: Use the primary blue (#155EEF) for main UI elements, CTAs, and active states
Use red (#F04438) only for errors, warnings, and destructive actions
Use green (#12B76A) for success states and confirmations
Use orange (#F79009) for warning states and important notifications
Use blue (#0BA5EC) for informational elements
Maintain a minimum contrast ratio of 4.5:1 for text

Files:

  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts
  • packages/ai-workspace-common/src/requests/types.gen.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts
  • apps/api/src/modules/skill/skill-invoker.service.ts
  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
  • packages/openapi-schema/src/schemas.gen.ts
  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
  • packages/ai-workspace-common/src/utils/sse-post.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
  • packages/openapi-schema/src/types.gen.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{jsx,tsx}: Always use tailwind css to style the component.
Always wrap pure components with React.memo to prevent unnecessary re-renders.
Always use useMemo for expensive computations or complex object creation.
Always use useCallback for function props to maintain referential equality.
Always specify proper dependency arrays in useEffect to prevent infinite loops.
Always avoid inline object/array creation in render to prevent unnecessary re-renders.
Always use proper key props when rendering lists.
Always split nested components with closures into separate components to avoid performance issues and improve code maintainability.

**/*.{jsx,tsx}: Always wrap pure components with React.memo to prevent unnecessary re-renders
Always use useMemo for expensive computations or complex object creation
Always use useCallback for function props to maintain referential equality
Always specify proper dependency arrays in useEffect to prevent infinite loops
Always avoid inline object/array creation in render to prevent unnecessary re-renders
Always use proper key props when rendering lists (avoid using index when possible)
Always split nested components with closures into separate components
Use lazy loading for components that are not immediately needed
Debounce handlers for events that might fire rapidly (resize, scroll, input)
Implement fallback UI for components that might fail
Use error boundaries to catch and handle runtime errors
Use Tailwind CSS for styling components
Group related utility classes together
Prefer Tailwind utilities over custom CSS when possible

**/*.{jsx,tsx}: Place each attribute on a new line when a component has multiple attributes in JSX
Use self-closing tags for elements without children in JSX
Keep JSX expressions simple, extract complex logic to variables
Put closing brackets for multi-line JSX on a new line

Use React best practices for frontend code

Files:

  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

**/*.tsx: Use the translation wrapper component when displaying user-facing text in components
Ensure all user-facing text in components is translatable

Files:

  • packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx
🧠 Learnings (2)
📚 Learning: 2025-07-21T01:56:17.029Z
Learnt from: CR
PR: refly-ai/refly#0
File: .cursor/rules/03-typescript-guidelines.mdc:0-0
Timestamp: 2025-07-21T01:56:17.029Z
Learning: Applies to **/*.{ts,tsx} : When tempted to use `any`, use `unknown` type instead and add proper type guards

Applied to files:

  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
📚 Learning: 2025-07-21T01:56:17.029Z
Learnt from: CR
PR: refly-ai/refly#0
File: .cursor/rules/03-typescript-guidelines.mdc:0-0
Timestamp: 2025-07-21T01:56:17.029Z
Learning: Applies to **/*.{ts,tsx} : Avoid using `any` type whenever possible - it defeats the purpose of TypeScript

Applied to files:

  • packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts
🧬 Code graph analysis (8)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (2)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/rehypePlugin.ts (1)
  • TOOL_USE_TAG_RENDER (5-5)
packages/openapi-schema/src/types.gen.ts (2)
  • ToolCallResult (3189-3246)
  • status (297-297)
packages/ai-workspace-common/src/hooks/canvas/use-update-action-result.ts (1)
packages/openapi-schema/src/types.gen.ts (2)
  • ActionResult (1726-1837)
  • status (297-297)
apps/api/src/modules/skill/skill-invoker.service.ts (3)
packages/openapi-schema/src/types.gen.ts (2)
  • ToolCallResult (3189-3246)
  • status (297-297)
packages/ai-workspace-common/src/utils/parse.ts (1)
  • safeParseJSON (1-10)
apps/api/src/modules/tool-call/tool-call.service.ts (1)
  • persistToolCall (301-366)
packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (3)
packages/openapi-schema/src/types.gen.ts (3)
  • ActionStep (1634-1669)
  • ActionStepMeta (1602-1607)
  • SkillEvent (3124-3187)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (2)
  • parseToolCallFromChunk (422-480)
  • mergeToolCallById (485-497)
packages/stores/src/stores/action-result.ts (1)
  • useActionResultStore (77-427)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/render.tsx (1)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/types.ts (1)
  • parseToolCallStatus (8-17)
packages/ai-workspace-common/src/utils/sse-post.ts (3)
packages/ai-workspace-common/src/requests/types.gen.ts (1)
  • SkillEvent (3116-3175)
packages/openapi-schema/src/types.gen.ts (1)
  • SkillEvent (3124-3187)
packages/request/src/requests/types.gen.ts (1)
  • SkillEvent (3114-3173)
apps/api/src/modules/tool-call/tool-call.service.ts (2)
packages/openapi-schema/src/types.gen.ts (5)
  • type (748-748)
  • status (297-297)
  • ToolCallResult (3189-3246)
  • ActionResult (1726-1837)
  • ActionStatus (1547-1547)
apps/api/src/modules/action/action.dto.ts (1)
  • toolCallResultPO2DTO (38-52)
packages/openapi-schema/src/types.gen.ts (3)
packages/agent-tools/src/base.ts (1)
  • ToolCallResult (4-25)
packages/ai-workspace-common/src/requests/types.gen.ts (1)
  • status2 (5350-5350)
packages/request/src/requests/types.gen.ts (1)
  • status2 (5348-5348)
🪛 ast-grep (0.39.6)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts

[warning] 8-8: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(<${TOOL_USE_TAG}[^>]*>([\\s\\S]*?)</${TOOL_USE_TAG}>, 'i')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build / Build
🔇 Additional comments (9)
packages/ai-workspace-common/src/components/markdown/plugins/tool-call/toolProcessor.ts (1)

8-10: Regex from constant is fine; ignore ReDoS warning.

TOOL_USE_TAG is a constant, not user input. The dynamic RegExp here is safe.

packages/ai-workspace-common/src/requests/types.gen.ts (1)

3112-3114: Verified: SkillEvent and ActionResult types are missing toolCallResult and toolCalls fields respectively.

The openapi-schema file includes toolCallResult?: ToolCallResult; in SkillEvent, but packages/ai-workspace-common/src/requests/types.gen.ts lacks this field. Similarly, ActionResult in openapi-schema includes toolCalls?: Array<ToolCallResult>;, which is also absent from the ai-workspace-common version. These generated files are out of sync. Regenerate from the updated OpenAPI schema to restore these properties and enable typed access to tool-related data in client flows.

packages/openapi-schema/src/schemas.gen.ts (4)

2179-2185: Schema extension looks good (ActionStep.toolCalls).

References the shared ToolCallResult; consistent with surrounding patterns.


2350-2356: Schema extension looks good (ActionResult.toolCalls).

Aligns with step-level field and enables action-level aggregation.


4350-4352: New skill event types acknowledged.

Enum additions for tool_call_start/tool_call_stream are appropriate.


4416-4421: SkillEvent.toolCallResult addition is correct.

Keeps event payload self-contained for tool-call streams.

packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts (3)

193-214: Latest-steps cache utilities look good.

Clear, minimal, and scoped by resultId; aligns with the streaming use case.


435-437: Event aliasing is fine.

Forwarding onToolCallStart to onToolCallStream keeps handling unified.


323-324: Cache invalidation on end/error looks good.

Prevents mixing stale steps across runs.

Also applies to: 389-390

Comment on lines +144 to +145
console.log('aiMessages', aiMessages);
return [new HumanMessage({ content: messageContent }), ...aiMessages];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove raw console.log of AI messages

Dumping the entire aiMessages array to stdout leaks user input and intermediate tool data into logs. That’s a privacy/compliance problem and bloats log volume. Please drop this statement or replace it with a sanitized Logger.debug that redacts content.

-    console.log('aiMessages', aiMessages);
🤖 Prompt for AI Agents
In apps/api/src/modules/skill/skill-invoker.service.ts around lines 144-145,
remove the raw console.log('aiMessages', aiMessages) which leaks user/input/tool
data; either delete the statement or replace it with a call to the module's
Logger at debug level that only logs non-sensitive metadata (e.g.,
aiMessages.length, message types, or a redacted summary) using a redaction
helper before logging so no raw content is emitted to stdout or persisted.

Comment on lines +156 to +165
// Update toolCalls on a step by parsing tool_use XML in the current chunk content
const updateToolCallsFromXml = (
updatedStep: ActionStep,
step: ActionStepMeta | undefined,
content: string,
): void => {
const parsed = parseToolCallFromChunk(content, step?.name);
if (!parsed) return;
updatedStep.toolCalls = mergeToolCallById(updatedStep.toolCalls, parsed);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix type mismatch when merging tool calls.

updatedStep.toolCalls is optional; mergeToolCallById expects an array.

Apply this diff:

-    updatedStep.toolCalls = mergeToolCallById(updatedStep.toolCalls, parsed);
+    updatedStep.toolCalls = mergeToolCallById(updatedStep.toolCalls ?? [], parsed);
🤖 Prompt for AI Agents
In packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts around
lines 156 to 165, updatedStep.toolCalls is optional but mergeToolCallById
requires an array; ensure you pass a non-undefined array and assign the merged
result back. Change the call to use a fallback array (e.g.,
updatedStep.toolCalls ?? []) when invoking mergeToolCallById and set
updatedStep.toolCalls to the returned array so the types align and the optional
field is populated after merging.

Comment on lines +3112 to 3114
| 'tool_call_start'
| 'tool_call_end'
| 'error';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Align event names with runtime usage and server schema.

Code elsewhere handles tool_call_stream, but this union adds tool_call_start and tool_call_end only. Pick one naming set and use it consistently across schema, generated types, and clients. If streaming emits tool_call_stream, include it here; if the server sends tool_call_end, update clients accordingly.


🏁 Script executed:

#!/bin/bash
# Locate all tool_call* event literals used across the repo to confirm canon names.
rg -nP --type=ts -C2 "tool_call_(start|end|stream)" | sed -n '1,200p'

Length of output: 4785



---

🏁 Script executed:

```shell
#!/bin/bash
# Check if tool_call_end is referenced anywhere in the codebase
rg -nP "tool_call_end" --type=ts --type=js

Length of output: 79


🏁 Script executed:

#!/bin/bash
# Also check for tool_call_end in all file types (not just ts/js)
rg "tool_call_end" -n

Length of output: 79


Replace tool_call_end with tool_call_stream at line 3113.

The union type contains tool_call_end, which is never emitted or handled anywhere in the codebase. All runtime code emits tool_call_stream instead (see skill-invoker.service.ts, sse-post.ts handlers, and tool-call.service.ts type signature). The openapi-schema source also defines tool_call_start + tool_call_stream, not tool_call_end. This mismatch will cause type errors in consuming code.

Change line 3113 from | 'tool_call_end' to | 'tool_call_stream'.

🤖 Prompt for AI Agents
In packages/ai-workspace-common/src/requests/types.gen.ts around lines 3112 to
3114, the union type incorrectly includes 'tool_call_end' which is never
emitted; replace the 'tool_call_end' member with 'tool_call_stream' so the type
matches runtime code and OpenAPI schema, updating the union to include
'tool_call_stream' instead of 'tool_call_end'.

@mrcfps mrcfps merged commit 3ed0bbf into main Oct 21, 2025
2 checks passed
@mrcfps mrcfps deleted the feat/tool_call_status branch October 21, 2025 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet