@librechat/agents 3.4.4 → 3.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/cjs/common/enum.cjs +2 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +268 -14
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/hitl/askUserQuestions.cjs +66 -0
  6. package/dist/cjs/hitl/askUserQuestions.cjs.map +1 -0
  7. package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs +46 -0
  8. package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs.map +1 -0
  9. package/dist/cjs/hitl/index.cjs +2 -0
  10. package/dist/cjs/langfuseTraceShaping.cjs +9 -6
  11. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  12. package/dist/cjs/main.cjs +6 -0
  13. package/dist/cjs/run.cjs +147 -14
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/session/AgentSession.cjs +93 -31
  16. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  17. package/dist/cjs/session/handlers.cjs +10 -0
  18. package/dist/cjs/session/handlers.cjs.map +1 -1
  19. package/dist/cjs/stream.cjs +17 -3
  20. package/dist/cjs/stream.cjs.map +1 -1
  21. package/dist/cjs/summarization/node.cjs +1 -0
  22. package/dist/cjs/summarization/node.cjs.map +1 -1
  23. package/dist/cjs/tools/ToolNode.cjs +4 -2
  24. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  25. package/dist/cjs/tools/handlers.cjs +1 -0
  26. package/dist/cjs/tools/handlers.cjs.map +1 -1
  27. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +28 -0
  28. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  29. package/dist/cjs/types/hitl.cjs +13 -0
  30. package/dist/cjs/types/hitl.cjs.map +1 -0
  31. package/dist/cjs/utils/handlers.cjs +3 -0
  32. package/dist/cjs/utils/handlers.cjs.map +1 -1
  33. package/dist/esm/common/enum.mjs +2 -0
  34. package/dist/esm/common/enum.mjs.map +1 -1
  35. package/dist/esm/graphs/Graph.mjs +268 -14
  36. package/dist/esm/graphs/Graph.mjs.map +1 -1
  37. package/dist/esm/hitl/askUserQuestions.mjs +66 -0
  38. package/dist/esm/hitl/askUserQuestions.mjs.map +1 -0
  39. package/dist/esm/hitl/askUserQuestionsInterrupt.mjs +43 -0
  40. package/dist/esm/hitl/askUserQuestionsInterrupt.mjs.map +1 -0
  41. package/dist/esm/hitl/index.mjs +2 -0
  42. package/dist/esm/langfuseTraceShaping.mjs +9 -6
  43. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  44. package/dist/esm/main.mjs +3 -1
  45. package/dist/esm/run.mjs +147 -14
  46. package/dist/esm/run.mjs.map +1 -1
  47. package/dist/esm/session/AgentSession.mjs +93 -31
  48. package/dist/esm/session/AgentSession.mjs.map +1 -1
  49. package/dist/esm/session/handlers.mjs +10 -0
  50. package/dist/esm/session/handlers.mjs.map +1 -1
  51. package/dist/esm/stream.mjs +17 -3
  52. package/dist/esm/stream.mjs.map +1 -1
  53. package/dist/esm/summarization/node.mjs +1 -0
  54. package/dist/esm/summarization/node.mjs.map +1 -1
  55. package/dist/esm/tools/ToolNode.mjs +4 -2
  56. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  57. package/dist/esm/tools/handlers.mjs +1 -0
  58. package/dist/esm/tools/handlers.mjs.map +1 -1
  59. package/dist/esm/tools/subagent/SubagentExecutor.mjs +28 -0
  60. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  61. package/dist/esm/types/hitl.mjs +13 -0
  62. package/dist/esm/types/hitl.mjs.map +1 -0
  63. package/dist/esm/utils/handlers.mjs +3 -0
  64. package/dist/esm/utils/handlers.mjs.map +1 -1
  65. package/dist/types/common/enum.d.ts +2 -0
  66. package/dist/types/graphs/Graph.d.ts +87 -0
  67. package/dist/types/hitl/askUserQuestions.d.ts +24 -0
  68. package/dist/types/hitl/askUserQuestionsInterrupt.d.ts +11 -0
  69. package/dist/types/hitl/index.d.ts +2 -0
  70. package/dist/types/langfuseTraceShaping.d.ts +4 -3
  71. package/dist/types/run.d.ts +9 -1
  72. package/dist/types/session/types.d.ts +1 -1
  73. package/dist/types/summarization/node.d.ts +6 -0
  74. package/dist/types/types/graph.d.ts +3 -3
  75. package/dist/types/types/hitl.d.ts +31 -2
  76. package/dist/types/types/stream.d.ts +57 -0
  77. package/dist/types/utils/handlers.d.ts +1 -0
  78. package/package.json +2 -1
  79. package/src/aggregator.test.ts +127 -0
  80. package/src/common/enum.ts +2 -0
  81. package/src/graphs/Graph.ts +365 -7
  82. package/src/graphs/__tests__/Graph.closeRunStep.test.ts +385 -0
  83. package/src/hitl/askUserQuestions.ts +126 -0
  84. package/src/hitl/askUserQuestionsInterrupt.ts +115 -0
  85. package/src/hitl/index.ts +6 -0
  86. package/src/langfuseTraceShaping.ts +14 -7
  87. package/src/run.ts +219 -41
  88. package/src/session/AgentSession.ts +105 -13
  89. package/src/session/handlers.ts +14 -2
  90. package/src/session/types.ts +1 -0
  91. package/src/specs/activity-label-observability.live.test.ts +262 -0
  92. package/src/specs/activity-label-observability.test.ts +167 -0
  93. package/src/specs/ask-user-questions.live.test.ts +185 -0
  94. package/src/specs/ask-user-questions.test.ts +293 -0
  95. package/src/specs/langfuse-trace-shaping.test.ts +32 -0
  96. package/src/specs/run-step-timestamps.test.ts +414 -0
  97. package/src/stream.ts +29 -6
  98. package/src/summarization/node.ts +11 -0
  99. package/src/tools/ToolNode.ts +2 -0
  100. package/src/tools/__tests__/SubagentExecutor.test.ts +52 -10
  101. package/src/tools/__tests__/handlers.test.ts +2 -0
  102. package/src/tools/handlers.ts +1 -0
  103. package/src/tools/subagent/SubagentExecutor.ts +46 -4
  104. package/src/types/graph.ts +9 -6
  105. package/src/types/hitl.ts +36 -2
  106. package/src/types/stream.ts +65 -12
  107. package/src/utils/handlers.ts +13 -0
@@ -99,6 +99,7 @@ const handleToolCalls = async (toolCalls, metadata, graph) => {
99
99
  const details = prevRunStep.stepDetails;
100
100
  if ((!details.tool_calls || details.tool_calls.length === 0) && prevStepId !== reusedChunkStepId) {
101
101
  graph.toolCallStepIds.set(toolCallId, prevStepId);
102
+ graph.registerPendingToolCall(toolCallId, prevStepId);
102
103
  reusedChunkStepId = prevStepId;
103
104
  continue;
104
105
  }
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.mjs","names":[],"sources":["../../../src/tools/handlers.ts"],"sourcesContent":["/* eslint-disable no-console */\n// src/tools/handlers.ts\nimport { nanoid } from 'nanoid';\nimport { ToolMessage } from '@langchain/core/messages';\nimport type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';\nimport type { AnthropicWebSearchResultBlockParam } from '@/llm/anthropic/types';\nimport type { Graph, MultiAgentGraph, StandardGraph } from '@/graphs';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type * as t from '@/types';\nimport {\n coerceAnthropicSearchResults,\n isAnthropicWebSearchResult,\n} from '@/tools/search/anthropic';\nimport {\n ToolCallTypes,\n GraphEvents,\n StepTypes,\n Providers,\n Constants,\n} from '@/common';\nimport { formatResultsForLLM } from '@/tools/search/format';\nimport { getMessageId } from '@/messages';\n\nexport async function handleToolCallChunks({\n graph,\n stepKey,\n toolCallChunks,\n metadata,\n}: {\n graph: StandardGraph | MultiAgentGraph;\n stepKey: string;\n toolCallChunks: ToolCallChunk[];\n metadata?: Record<string, unknown>;\n}): Promise<void> {\n let prevStepId: string;\n let prevRunStep: t.RunStep | undefined;\n try {\n prevStepId = graph.getStepIdByKey(stepKey);\n prevRunStep = graph.getRunStep(prevStepId);\n } catch {\n /** Edge Case: If no previous step exists, create a new message creation step */\n const message_id = getMessageId(stepKey, graph, true) ?? '';\n prevStepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: {\n message_id,\n },\n },\n metadata\n );\n prevRunStep = graph.getRunStep(prevStepId);\n }\n\n const _stepId = graph.getStepIdByKey(stepKey);\n\n /** Edge Case: Tool Call Run Step or `tool_call_ids` never dispatched */\n const tool_calls: ToolCall[] | undefined =\n prevStepId && prevRunStep && prevRunStep.type === StepTypes.MESSAGE_CREATION\n ? []\n : undefined;\n\n /** Edge Case: `id` and `name` fields cannot be empty strings */\n for (const toolCallChunk of toolCallChunks) {\n if (toolCallChunk.name === '') {\n toolCallChunk.name = undefined;\n }\n if (toolCallChunk.id === '') {\n toolCallChunk.id = undefined;\n } else if (\n tool_calls != null &&\n toolCallChunk.id != null &&\n toolCallChunk.name != null\n ) {\n tool_calls.push({\n args: {},\n id: toolCallChunk.id,\n name: toolCallChunk.name,\n type: ToolCallTypes.TOOL_CALL,\n });\n }\n }\n\n let stepId: string = _stepId;\n const alreadyDispatched =\n prevRunStep?.type === StepTypes.MESSAGE_CREATION &&\n graph.messageStepHasToolCalls.has(prevStepId);\n\n if (prevRunStep?.type === StepTypes.TOOL_CALLS) {\n /**\n * If previous step is already a tool_calls step, use that step ID\n * This ensures tool call deltas are dispatched to the correct step\n */\n stepId = prevStepId;\n } else if (\n !alreadyDispatched &&\n prevRunStep?.type === StepTypes.MESSAGE_CREATION\n ) {\n /**\n * Create tool_calls step as soon as we receive the first tool call chunk\n * This ensures deltas are always associated with the correct step\n *\n * NOTE: We do NOT dispatch an empty text block here because:\n * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages\n * - The tool_calls themselves are sufficient for the step\n * - Empty content with tool_call_ids gets stored in conversation history\n * and causes \"messages must have non-empty content\" errors on replay\n */\n graph.messageStepHasToolCalls.set(prevStepId, true);\n stepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: tool_calls ?? [],\n },\n metadata\n );\n }\n\n await graph.dispatchRunStepDelta(\n stepId,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: toolCallChunks,\n },\n metadata\n );\n}\n\nexport const handleToolCalls = async (\n toolCalls?: ToolCall[],\n metadata?: Record<string, unknown>,\n graph?: Graph | StandardGraph | MultiAgentGraph\n): Promise<void> => {\n if (!graph || !metadata) {\n console.warn('Graph or metadata not found in `handleToolCalls`');\n return;\n }\n\n if (!toolCalls) {\n return;\n }\n\n if (toolCalls.length === 0) {\n return;\n }\n\n const stepKey = graph.getStepKey(metadata);\n\n /**\n * Track whether we've already reused an empty TOOL_CALLS step created by\n * handleToolCallChunks during streaming. Only reuse it once (for the first\n * tool call); subsequent parallel tool calls must create their own steps.\n */\n let reusedChunkStepId: string | undefined;\n\n for (const tool_call of toolCalls) {\n const toolCallId = tool_call.id ?? `toolu_${nanoid()}`;\n tool_call.id = toolCallId;\n if (!toolCallId || graph.toolCallStepIds.has(toolCallId)) {\n continue;\n }\n\n let prevStepId = '';\n let prevRunStep: t.RunStep | undefined;\n try {\n prevStepId = graph.getStepIdByKey(stepKey);\n prevRunStep = graph.getRunStep(prevStepId);\n } catch {\n // no previous step\n }\n\n /**\n * If the previous step is TOOL_CALLS (from handleToolCallChunks or a prior\n * iteration), either reuse it (if empty) or dispatch a new TOOL_CALLS step\n * directly — skip the intermediate MESSAGE_CREATION to avoid orphaned gaps.\n */\n if (prevRunStep?.type === StepTypes.TOOL_CALLS) {\n const details = prevRunStep.stepDetails as t.ToolCallsDetails;\n const isEmpty = !details.tool_calls || details.tool_calls.length === 0;\n if (isEmpty && prevStepId !== reusedChunkStepId) {\n graph.toolCallStepIds.set(toolCallId, prevStepId);\n reusedChunkStepId = prevStepId;\n continue;\n }\n await graph.dispatchRunStep(\n stepKey,\n { type: StepTypes.TOOL_CALLS, tool_calls: [tool_call] },\n metadata\n );\n continue;\n }\n\n /**\n * NOTE: We do NOT dispatch empty text blocks with tool_call_ids because:\n * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages\n * - They get stored in conversation history and cause errors on replay:\n * \"messages must have non-empty content\" (Anthropic)\n * \"The content field in the Message object is empty\" (Bedrock)\n * - The tool_calls themselves are sufficient\n */\n if (prevStepId && prevRunStep) {\n graph.messageStepHasToolCalls.set(prevStepId, true);\n } else if (!prevRunStep) {\n const messageId = getMessageId(stepKey, graph, true) ?? '';\n const stepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: {\n message_id: messageId,\n },\n },\n metadata\n );\n graph.messageStepHasToolCalls.set(stepId, true);\n }\n\n await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: [tool_call],\n },\n metadata\n );\n }\n};\n\nexport const toolResultTypes = new Set([\n // 'tool_use',\n // 'server_tool_use',\n // 'input_json_delta',\n 'tool_result',\n 'web_search_result',\n 'web_search_tool_result',\n]);\n\n/**\n * Handles the result of a server tool call; in other words, a provider's built-in tool.\n * As of 2025-07-06, only Anthropic handles server tool calls with this pattern.\n */\nexport async function handleServerToolResult({\n graph,\n content,\n metadata,\n agentContext,\n}: {\n graph: StandardGraph | MultiAgentGraph;\n content?: string | t.MessageContentComplex[];\n metadata?: Record<string, unknown>;\n agentContext?: AgentContext;\n}): Promise<boolean> {\n let skipHandling = false;\n if (agentContext?.provider !== Providers.ANTHROPIC) {\n return skipHandling;\n }\n if (\n typeof content === 'string' ||\n content == null ||\n content.length === 0 ||\n (content.length === 1 &&\n (content[0] as t.ToolResultContent).tool_use_id == null)\n ) {\n return skipHandling;\n }\n\n for (const contentPart of content) {\n const toolUseId = (contentPart as t.ToolResultContent).tool_use_id;\n if (toolUseId == null || toolUseId === '') {\n continue;\n }\n const stepId = graph.toolCallStepIds.get(toolUseId);\n if (stepId == null || stepId === '') {\n console.warn(\n `Tool use ID ${toolUseId} not found in graph, cannot dispatch tool result.`\n );\n continue;\n }\n const runStep = graph.getRunStep(stepId);\n if (!runStep) {\n console.warn(\n `Run step for ${stepId} does not exist, cannot dispatch tool result.`\n );\n continue;\n } else if (runStep.type !== StepTypes.TOOL_CALLS) {\n console.warn(\n `Run step for ${stepId} is not a tool call step, cannot dispatch tool result.`\n );\n continue;\n }\n\n const toolCall =\n runStep.stepDetails.type === StepTypes.TOOL_CALLS\n ? (runStep.stepDetails.tool_calls?.find(\n (toolCall) => toolCall.id === toolUseId\n ) as ToolCall)\n : undefined;\n\n if (!toolCall) {\n continue;\n }\n\n if (\n contentPart.type === 'web_search_result' ||\n contentPart.type === 'web_search_tool_result'\n ) {\n await handleAnthropicSearchResults({\n contentPart: contentPart as t.ToolResultContent,\n toolCall,\n metadata,\n graph,\n });\n }\n\n if (!skipHandling) {\n skipHandling = true;\n }\n }\n\n return skipHandling;\n}\n\nasync function handleAnthropicSearchResults({\n contentPart,\n toolCall,\n metadata,\n graph,\n}: {\n contentPart: t.ToolResultContent;\n toolCall: Partial<ToolCall>;\n metadata?: Record<string, unknown>;\n graph: StandardGraph | MultiAgentGraph;\n}): Promise<void> {\n if (!Array.isArray(contentPart.content)) {\n console.warn(\n `Expected content to be an array, got ${typeof contentPart.content}`\n );\n return;\n }\n\n if (!isAnthropicWebSearchResult(contentPart.content[0])) {\n console.warn(\n `Expected content to be an Anthropic web search result, got ${JSON.stringify(\n contentPart.content\n )}`\n );\n return;\n }\n\n const turn = graph.invokedToolIds?.size ?? 0;\n const searchResultData = coerceAnthropicSearchResults({\n turn,\n results: contentPart.content as AnthropicWebSearchResultBlockParam[],\n });\n\n const name = toolCall.name;\n const input = toolCall.args ?? {};\n const artifact = {\n [Constants.WEB_SEARCH]: searchResultData,\n };\n const { output: formattedOutput } = formatResultsForLLM(\n turn,\n searchResultData\n );\n const output = new ToolMessage({\n name,\n artifact,\n content: formattedOutput,\n tool_call_id: toolCall.id!,\n });\n const toolEndData: t.ToolEndData = {\n input,\n output,\n };\n await graph.handlerRegistry\n ?.getHandler(GraphEvents.TOOL_END)\n ?.handle(GraphEvents.TOOL_END, toolEndData, metadata, graph);\n\n if (graph.invokedToolIds == null) {\n graph.invokedToolIds = new Set<string>();\n }\n\n graph.invokedToolIds.add(toolCall.id!);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"handlers.mjs","names":[],"sources":["../../../src/tools/handlers.ts"],"sourcesContent":["/* eslint-disable no-console */\n// src/tools/handlers.ts\nimport { nanoid } from 'nanoid';\nimport { ToolMessage } from '@langchain/core/messages';\nimport type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';\nimport type { AnthropicWebSearchResultBlockParam } from '@/llm/anthropic/types';\nimport type { Graph, MultiAgentGraph, StandardGraph } from '@/graphs';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type * as t from '@/types';\nimport {\n coerceAnthropicSearchResults,\n isAnthropicWebSearchResult,\n} from '@/tools/search/anthropic';\nimport {\n ToolCallTypes,\n GraphEvents,\n StepTypes,\n Providers,\n Constants,\n} from '@/common';\nimport { formatResultsForLLM } from '@/tools/search/format';\nimport { getMessageId } from '@/messages';\n\nexport async function handleToolCallChunks({\n graph,\n stepKey,\n toolCallChunks,\n metadata,\n}: {\n graph: StandardGraph | MultiAgentGraph;\n stepKey: string;\n toolCallChunks: ToolCallChunk[];\n metadata?: Record<string, unknown>;\n}): Promise<void> {\n let prevStepId: string;\n let prevRunStep: t.RunStep | undefined;\n try {\n prevStepId = graph.getStepIdByKey(stepKey);\n prevRunStep = graph.getRunStep(prevStepId);\n } catch {\n /** Edge Case: If no previous step exists, create a new message creation step */\n const message_id = getMessageId(stepKey, graph, true) ?? '';\n prevStepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: {\n message_id,\n },\n },\n metadata\n );\n prevRunStep = graph.getRunStep(prevStepId);\n }\n\n const _stepId = graph.getStepIdByKey(stepKey);\n\n /** Edge Case: Tool Call Run Step or `tool_call_ids` never dispatched */\n const tool_calls: ToolCall[] | undefined =\n prevStepId && prevRunStep && prevRunStep.type === StepTypes.MESSAGE_CREATION\n ? []\n : undefined;\n\n /** Edge Case: `id` and `name` fields cannot be empty strings */\n for (const toolCallChunk of toolCallChunks) {\n if (toolCallChunk.name === '') {\n toolCallChunk.name = undefined;\n }\n if (toolCallChunk.id === '') {\n toolCallChunk.id = undefined;\n } else if (\n tool_calls != null &&\n toolCallChunk.id != null &&\n toolCallChunk.name != null\n ) {\n tool_calls.push({\n args: {},\n id: toolCallChunk.id,\n name: toolCallChunk.name,\n type: ToolCallTypes.TOOL_CALL,\n });\n }\n }\n\n let stepId: string = _stepId;\n const alreadyDispatched =\n prevRunStep?.type === StepTypes.MESSAGE_CREATION &&\n graph.messageStepHasToolCalls.has(prevStepId);\n\n if (prevRunStep?.type === StepTypes.TOOL_CALLS) {\n /**\n * If previous step is already a tool_calls step, use that step ID\n * This ensures tool call deltas are dispatched to the correct step\n */\n stepId = prevStepId;\n } else if (\n !alreadyDispatched &&\n prevRunStep?.type === StepTypes.MESSAGE_CREATION\n ) {\n /**\n * Create tool_calls step as soon as we receive the first tool call chunk\n * This ensures deltas are always associated with the correct step\n *\n * NOTE: We do NOT dispatch an empty text block here because:\n * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages\n * - The tool_calls themselves are sufficient for the step\n * - Empty content with tool_call_ids gets stored in conversation history\n * and causes \"messages must have non-empty content\" errors on replay\n */\n graph.messageStepHasToolCalls.set(prevStepId, true);\n stepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: tool_calls ?? [],\n },\n metadata\n );\n }\n\n await graph.dispatchRunStepDelta(\n stepId,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: toolCallChunks,\n },\n metadata\n );\n}\n\nexport const handleToolCalls = async (\n toolCalls?: ToolCall[],\n metadata?: Record<string, unknown>,\n graph?: Graph | StandardGraph | MultiAgentGraph\n): Promise<void> => {\n if (!graph || !metadata) {\n console.warn('Graph or metadata not found in `handleToolCalls`');\n return;\n }\n\n if (!toolCalls) {\n return;\n }\n\n if (toolCalls.length === 0) {\n return;\n }\n\n const stepKey = graph.getStepKey(metadata);\n\n /**\n * Track whether we've already reused an empty TOOL_CALLS step created by\n * handleToolCallChunks during streaming. Only reuse it once (for the first\n * tool call); subsequent parallel tool calls must create their own steps.\n */\n let reusedChunkStepId: string | undefined;\n\n for (const tool_call of toolCalls) {\n const toolCallId = tool_call.id ?? `toolu_${nanoid()}`;\n tool_call.id = toolCallId;\n if (!toolCallId || graph.toolCallStepIds.has(toolCallId)) {\n continue;\n }\n\n let prevStepId = '';\n let prevRunStep: t.RunStep | undefined;\n try {\n prevStepId = graph.getStepIdByKey(stepKey);\n prevRunStep = graph.getRunStep(prevStepId);\n } catch {\n // no previous step\n }\n\n /**\n * If the previous step is TOOL_CALLS (from handleToolCallChunks or a prior\n * iteration), either reuse it (if empty) or dispatch a new TOOL_CALLS step\n * directly — skip the intermediate MESSAGE_CREATION to avoid orphaned gaps.\n */\n if (prevRunStep?.type === StepTypes.TOOL_CALLS) {\n const details = prevRunStep.stepDetails as t.ToolCallsDetails;\n const isEmpty = !details.tool_calls || details.tool_calls.length === 0;\n if (isEmpty && prevStepId !== reusedChunkStepId) {\n graph.toolCallStepIds.set(toolCallId, prevStepId);\n graph.registerPendingToolCall(toolCallId, prevStepId);\n reusedChunkStepId = prevStepId;\n continue;\n }\n await graph.dispatchRunStep(\n stepKey,\n { type: StepTypes.TOOL_CALLS, tool_calls: [tool_call] },\n metadata\n );\n continue;\n }\n\n /**\n * NOTE: We do NOT dispatch empty text blocks with tool_call_ids because:\n * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages\n * - They get stored in conversation history and cause errors on replay:\n * \"messages must have non-empty content\" (Anthropic)\n * \"The content field in the Message object is empty\" (Bedrock)\n * - The tool_calls themselves are sufficient\n */\n if (prevStepId && prevRunStep) {\n graph.messageStepHasToolCalls.set(prevStepId, true);\n } else if (!prevRunStep) {\n const messageId = getMessageId(stepKey, graph, true) ?? '';\n const stepId = await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: {\n message_id: messageId,\n },\n },\n metadata\n );\n graph.messageStepHasToolCalls.set(stepId, true);\n }\n\n await graph.dispatchRunStep(\n stepKey,\n {\n type: StepTypes.TOOL_CALLS,\n tool_calls: [tool_call],\n },\n metadata\n );\n }\n};\n\nexport const toolResultTypes = new Set([\n // 'tool_use',\n // 'server_tool_use',\n // 'input_json_delta',\n 'tool_result',\n 'web_search_result',\n 'web_search_tool_result',\n]);\n\n/**\n * Handles the result of a server tool call; in other words, a provider's built-in tool.\n * As of 2025-07-06, only Anthropic handles server tool calls with this pattern.\n */\nexport async function handleServerToolResult({\n graph,\n content,\n metadata,\n agentContext,\n}: {\n graph: StandardGraph | MultiAgentGraph;\n content?: string | t.MessageContentComplex[];\n metadata?: Record<string, unknown>;\n agentContext?: AgentContext;\n}): Promise<boolean> {\n let skipHandling = false;\n if (agentContext?.provider !== Providers.ANTHROPIC) {\n return skipHandling;\n }\n if (\n typeof content === 'string' ||\n content == null ||\n content.length === 0 ||\n (content.length === 1 &&\n (content[0] as t.ToolResultContent).tool_use_id == null)\n ) {\n return skipHandling;\n }\n\n for (const contentPart of content) {\n const toolUseId = (contentPart as t.ToolResultContent).tool_use_id;\n if (toolUseId == null || toolUseId === '') {\n continue;\n }\n const stepId = graph.toolCallStepIds.get(toolUseId);\n if (stepId == null || stepId === '') {\n console.warn(\n `Tool use ID ${toolUseId} not found in graph, cannot dispatch tool result.`\n );\n continue;\n }\n const runStep = graph.getRunStep(stepId);\n if (!runStep) {\n console.warn(\n `Run step for ${stepId} does not exist, cannot dispatch tool result.`\n );\n continue;\n } else if (runStep.type !== StepTypes.TOOL_CALLS) {\n console.warn(\n `Run step for ${stepId} is not a tool call step, cannot dispatch tool result.`\n );\n continue;\n }\n\n const toolCall =\n runStep.stepDetails.type === StepTypes.TOOL_CALLS\n ? (runStep.stepDetails.tool_calls?.find(\n (toolCall) => toolCall.id === toolUseId\n ) as ToolCall)\n : undefined;\n\n if (!toolCall) {\n continue;\n }\n\n if (\n contentPart.type === 'web_search_result' ||\n contentPart.type === 'web_search_tool_result'\n ) {\n await handleAnthropicSearchResults({\n contentPart: contentPart as t.ToolResultContent,\n toolCall,\n metadata,\n graph,\n });\n }\n\n if (!skipHandling) {\n skipHandling = true;\n }\n }\n\n return skipHandling;\n}\n\nasync function handleAnthropicSearchResults({\n contentPart,\n toolCall,\n metadata,\n graph,\n}: {\n contentPart: t.ToolResultContent;\n toolCall: Partial<ToolCall>;\n metadata?: Record<string, unknown>;\n graph: StandardGraph | MultiAgentGraph;\n}): Promise<void> {\n if (!Array.isArray(contentPart.content)) {\n console.warn(\n `Expected content to be an array, got ${typeof contentPart.content}`\n );\n return;\n }\n\n if (!isAnthropicWebSearchResult(contentPart.content[0])) {\n console.warn(\n `Expected content to be an Anthropic web search result, got ${JSON.stringify(\n contentPart.content\n )}`\n );\n return;\n }\n\n const turn = graph.invokedToolIds?.size ?? 0;\n const searchResultData = coerceAnthropicSearchResults({\n turn,\n results: contentPart.content as AnthropicWebSearchResultBlockParam[],\n });\n\n const name = toolCall.name;\n const input = toolCall.args ?? {};\n const artifact = {\n [Constants.WEB_SEARCH]: searchResultData,\n };\n const { output: formattedOutput } = formatResultsForLLM(\n turn,\n searchResultData\n );\n const output = new ToolMessage({\n name,\n artifact,\n content: formattedOutput,\n tool_call_id: toolCall.id!,\n });\n const toolEndData: t.ToolEndData = {\n input,\n output,\n };\n await graph.handlerRegistry\n ?.getHandler(GraphEvents.TOOL_END)\n ?.handle(GraphEvents.TOOL_END, toolEndData, metadata, graph);\n\n if (graph.invokedToolIds == null) {\n graph.invokedToolIds = new Set<string>();\n }\n\n graph.invokedToolIds.add(toolCall.id!);\n}\n"],"mappings":"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"}
@@ -1380,6 +1380,10 @@ var SubagentExecutor = class {
1380
1380
  scheduleWrap(eventName, "run_step_completed", data, memberAgentId);
1381
1381
  return;
1382
1382
  }
1383
+ if (eventName === "on_run_step_closed") {
1384
+ scheduleWrap(eventName, "run_step_closed", data, memberAgentId);
1385
+ return;
1386
+ }
1383
1387
  if (eventName === "on_message_delta") {
1384
1388
  scheduleWrap(eventName, "message_delta", data, memberAgentId);
1385
1389
  return;
@@ -1585,6 +1589,7 @@ function sanitizeForwardedSubagentUpdateData(eventName, data) {
1585
1589
  if (eventName === "on_run_step") return sanitizeRunStepUpdateData(data);
1586
1590
  if (eventName === "on_run_step_delta") return sanitizeRunStepDeltaUpdateData(data);
1587
1591
  if (eventName === "on_run_step_completed") return sanitizeRunStepCompletedUpdateData(data);
1592
+ if (eventName === "on_run_step_closed") return sanitizeRunStepClosedUpdateData(data);
1588
1593
  if (eventName === "on_message_delta") return sanitizeMessageDeltaUpdateData(data);
1589
1594
  if (eventName === "on_reasoning_delta") return sanitizeReasoningDeltaUpdateData(data);
1590
1595
  }
@@ -1609,10 +1614,12 @@ function sanitizeRunStepUpdateData(data) {
1609
1614
  const step = data;
1610
1615
  const sanitized = {};
1611
1616
  assignString(sanitized, "agentId", step.agentId);
1617
+ assignNumber(sanitized, "created_at", step.created_at);
1612
1618
  assignNumber(sanitized, "groupId", step.groupId);
1613
1619
  assignString(sanitized, "id", step.id);
1614
1620
  assignNumber(sanitized, "index", step.index);
1615
1621
  assignString(sanitized, "runId", step.runId);
1622
+ assignString(sanitized, "status", step.status);
1616
1623
  assignNumber(sanitized, "stepIndex", step.stepIndex);
1617
1624
  assignString(sanitized, "type", step.type);
1618
1625
  if (step.summary !== void 0) sanitized.summary = step.summary;
@@ -1620,6 +1627,22 @@ function sanitizeRunStepUpdateData(data) {
1620
1627
  sanitized.stepDetails = sanitizeStepDetails(step.stepDetails);
1621
1628
  return sanitized;
1622
1629
  }
1630
+ function sanitizeRunStepClosedUpdateData(data) {
1631
+ if (!isObjectLike(data)) return;
1632
+ const event = data;
1633
+ const sanitized = {};
1634
+ assignString(sanitized, "agentId", event.agentId);
1635
+ assignNumber(sanitized, "closed_at", event.closed_at);
1636
+ assignNumber(sanitized, "created_at", event.created_at);
1637
+ assignNumber(sanitized, "groupId", event.groupId);
1638
+ assignString(sanitized, "id", event.id);
1639
+ assignNumber(sanitized, "index", event.index);
1640
+ assignString(sanitized, "runId", event.runId);
1641
+ assignString(sanitized, "status", event.status);
1642
+ assignNumber(sanitized, "stepIndex", event.stepIndex);
1643
+ assignString(sanitized, "type", event.type);
1644
+ return sanitized;
1645
+ }
1623
1646
  function sanitizeRunStepDeltaUpdateData(data) {
1624
1647
  if (!isObjectLike(data)) return;
1625
1648
  const event = data;
@@ -1695,6 +1718,7 @@ function sanitizeStepCompleted(data) {
1695
1718
  const sanitized = { type: "tool_call" };
1696
1719
  assignString(sanitized, "id", completed.id);
1697
1720
  assignNumber(sanitized, "index", completed.index);
1721
+ assignNumber(sanitized, "completed_at", completed.completed_at);
1698
1722
  sanitized.tool_call = sanitizeProcessedToolCall(completed.tool_call);
1699
1723
  return sanitized;
1700
1724
  }
@@ -1760,6 +1784,10 @@ function summarizeEvent(eventName, data) {
1760
1784
  if (tool?.name != null && tool.name !== "") return `Tool ${tool.name} complete`;
1761
1785
  return "Step complete";
1762
1786
  }
1787
+ if (eventName === "on_run_step_closed") {
1788
+ const closed = data;
1789
+ return closed.status != null && closed.status !== "" ? `Step ${closed.status}` : "Step closed";
1790
+ }
1763
1791
  if (eventName === "on_message_delta") return "Streaming…";
1764
1792
  return eventName;
1765
1793
  }