@librechat/agents 3.4.5 → 3.4.7

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 (87) 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/langfuseTraceShaping.cjs +9 -6
  6. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  7. package/dist/cjs/llm/google/utils/common.cjs +10 -5
  8. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  9. package/dist/cjs/run.cjs +147 -14
  10. package/dist/cjs/run.cjs.map +1 -1
  11. package/dist/cjs/session/AgentSession.cjs +93 -31
  12. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  13. package/dist/cjs/session/handlers.cjs +10 -0
  14. package/dist/cjs/session/handlers.cjs.map +1 -1
  15. package/dist/cjs/stream.cjs +17 -3
  16. package/dist/cjs/stream.cjs.map +1 -1
  17. package/dist/cjs/summarization/node.cjs +1 -0
  18. package/dist/cjs/summarization/node.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +4 -2
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/cjs/tools/handlers.cjs +1 -0
  22. package/dist/cjs/tools/handlers.cjs.map +1 -1
  23. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +28 -0
  24. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  25. package/dist/cjs/utils/handlers.cjs +3 -0
  26. package/dist/cjs/utils/handlers.cjs.map +1 -1
  27. package/dist/esm/common/enum.mjs +2 -0
  28. package/dist/esm/common/enum.mjs.map +1 -1
  29. package/dist/esm/graphs/Graph.mjs +268 -14
  30. package/dist/esm/graphs/Graph.mjs.map +1 -1
  31. package/dist/esm/langfuseTraceShaping.mjs +9 -6
  32. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  33. package/dist/esm/llm/google/utils/common.mjs +10 -5
  34. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  35. package/dist/esm/run.mjs +147 -14
  36. package/dist/esm/run.mjs.map +1 -1
  37. package/dist/esm/session/AgentSession.mjs +93 -31
  38. package/dist/esm/session/AgentSession.mjs.map +1 -1
  39. package/dist/esm/session/handlers.mjs +10 -0
  40. package/dist/esm/session/handlers.mjs.map +1 -1
  41. package/dist/esm/stream.mjs +17 -3
  42. package/dist/esm/stream.mjs.map +1 -1
  43. package/dist/esm/summarization/node.mjs +1 -0
  44. package/dist/esm/summarization/node.mjs.map +1 -1
  45. package/dist/esm/tools/ToolNode.mjs +4 -2
  46. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  47. package/dist/esm/tools/handlers.mjs +1 -0
  48. package/dist/esm/tools/handlers.mjs.map +1 -1
  49. package/dist/esm/tools/subagent/SubagentExecutor.mjs +28 -0
  50. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  51. package/dist/esm/utils/handlers.mjs +3 -0
  52. package/dist/esm/utils/handlers.mjs.map +1 -1
  53. package/dist/types/common/enum.d.ts +2 -0
  54. package/dist/types/graphs/Graph.d.ts +87 -0
  55. package/dist/types/langfuseTraceShaping.d.ts +4 -3
  56. package/dist/types/run.d.ts +9 -1
  57. package/dist/types/session/types.d.ts +1 -1
  58. package/dist/types/summarization/node.d.ts +6 -0
  59. package/dist/types/types/graph.d.ts +3 -3
  60. package/dist/types/types/stream.d.ts +57 -0
  61. package/dist/types/utils/handlers.d.ts +1 -0
  62. package/package.json +1 -1
  63. package/src/aggregator.test.ts +127 -0
  64. package/src/common/enum.ts +2 -0
  65. package/src/graphs/Graph.ts +365 -7
  66. package/src/graphs/__tests__/Graph.closeRunStep.test.ts +385 -0
  67. package/src/langfuseTraceShaping.ts +14 -7
  68. package/src/llm/google/utils/common.test.ts +11 -0
  69. package/src/llm/google/utils/common.ts +6 -4
  70. package/src/run.ts +219 -41
  71. package/src/session/AgentSession.ts +105 -13
  72. package/src/session/handlers.ts +14 -2
  73. package/src/session/types.ts +1 -0
  74. package/src/specs/activity-label-observability.live.test.ts +262 -0
  75. package/src/specs/activity-label-observability.test.ts +167 -0
  76. package/src/specs/langfuse-trace-shaping.test.ts +32 -0
  77. package/src/specs/run-step-timestamps.test.ts +414 -0
  78. package/src/stream.ts +29 -6
  79. package/src/summarization/node.ts +11 -0
  80. package/src/tools/ToolNode.ts +2 -0
  81. package/src/tools/__tests__/SubagentExecutor.test.ts +52 -10
  82. package/src/tools/__tests__/handlers.test.ts +2 -0
  83. package/src/tools/handlers.ts +1 -0
  84. package/src/tools/subagent/SubagentExecutor.ts +46 -4
  85. package/src/types/graph.ts +9 -6
  86. package/src/types/stream.ts +65 -12
  87. package/src/utils/handlers.ts +13 -0
@@ -14,6 +14,8 @@ let GraphEvents = /* @__PURE__ */ function(GraphEvents) {
14
14
  GraphEvents["ON_RUN_STEP_DELTA"] = "on_run_step_delta";
15
15
  /** [Custom] Completed event for run steps (tool calls) */
16
16
  GraphEvents["ON_RUN_STEP_COMPLETED"] = "on_run_step_completed";
17
+ /** [Custom] Terminal signal for a run step: closed with status + timestamps */
18
+ GraphEvents["ON_RUN_STEP_CLOSED"] = "on_run_step_closed";
17
19
  /** [Custom] Delta events for messages */
18
20
  GraphEvents["ON_MESSAGE_DELTA"] = "on_message_delta";
19
21
  /** [Custom] Reasoning Delta events for messages */
@@ -1 +1 @@
1
- {"version":3,"file":"enum.cjs","names":[],"sources":["../../../src/common/enum.ts"],"sourcesContent":["/**\n * Enum representing the various event types emitted during the execution of runnables.\n * These events provide real-time information about the progress and state of different components.\n *\n * @enum {string}\n */\nexport enum GraphEvents {\n /* Custom Events */\n\n /** [Custom] Agent update event in multi-agent graph/workflow */\n ON_AGENT_UPDATE = 'on_agent_update',\n /** [Custom] Delta event for run steps (message creation and tool calls) */\n ON_RUN_STEP = 'on_run_step',\n /** [Custom] Delta event for run steps (tool calls) */\n ON_RUN_STEP_DELTA = 'on_run_step_delta',\n /** [Custom] Completed event for run steps (tool calls) */\n ON_RUN_STEP_COMPLETED = 'on_run_step_completed',\n /** [Custom] Delta events for messages */\n ON_MESSAGE_DELTA = 'on_message_delta',\n /** [Custom] Reasoning Delta events for messages */\n ON_REASONING_DELTA = 'on_reasoning_delta',\n /** [Custom] Request to execute tools - dispatched by ToolNode, handled by host */\n ON_TOOL_EXECUTE = 'on_tool_execute',\n /** [Custom] Emitted when the summarize node begins generating a summary */\n ON_SUMMARIZE_START = 'on_summarize_start',\n /** [Custom] Delta event carrying the completed summary content */\n ON_SUMMARIZE_DELTA = 'on_summarize_delta',\n /** [Custom] Emitted when the summarize node completes with the final summary */\n ON_SUMMARIZE_COMPLETE = 'on_summarize_complete',\n /** [Custom] Progress update from a running subagent (wraps child-graph events so hosts can display activity separately from parent). */\n ON_SUBAGENT_UPDATE = 'on_subagent_update',\n /** [Custom] Diagnostic logging event for context management observability */\n ON_AGENT_LOG = 'on_agent_log',\n /** [Custom] Per-model-call context window usage snapshot (post-prune token budget) */\n ON_CONTEXT_USAGE = 'on_context_usage',\n\n /* Official Events */\n\n /** Custom event, emitted by system */\n ON_CUSTOM_EVENT = 'on_custom_event',\n /** Emitted when a chat model starts processing. */\n CHAT_MODEL_START = 'on_chat_model_start',\n\n /** Emitted when a chat model streams a chunk of its response. */\n CHAT_MODEL_STREAM = 'on_chat_model_stream',\n\n /** Emitted when a chat model completes its processing. */\n CHAT_MODEL_END = 'on_chat_model_end',\n\n /** Emitted when a language model starts processing. */\n LLM_START = 'on_llm_start',\n\n /** Emitted when a language model streams a chunk of its response. */\n LLM_STREAM = 'on_llm_stream',\n\n /** Emitted when a language model completes its processing. */\n LLM_END = 'on_llm_end',\n\n /** Emitted when a chain starts processing. */\n CHAIN_START = 'on_chain_start',\n\n /** Emitted when a chain streams a chunk of its output. */\n CHAIN_STREAM = 'on_chain_stream',\n\n /** Emitted when a chain completes its processing. */\n CHAIN_END = 'on_chain_end',\n\n /** Emitted when a tool starts its operation. */\n TOOL_START = 'on_tool_start',\n\n /** Emitted when a tool completes its operation. */\n TOOL_END = 'on_tool_end',\n\n /** Emitted when a retriever starts its operation. */\n RETRIEVER_START = 'on_retriever_start',\n\n /** Emitted when a retriever completes its operation. */\n RETRIEVER_END = 'on_retriever_end',\n\n /** Emitted when a prompt starts processing. */\n PROMPT_START = 'on_prompt_start',\n\n /** Emitted when a prompt completes its processing. */\n PROMPT_END = 'on_prompt_end',\n}\n\nexport enum Providers {\n OPENAI = 'openAI',\n VERTEXAI = 'vertexai',\n BEDROCK = 'bedrock',\n ANTHROPIC = 'anthropic',\n MISTRALAI = 'mistralai',\n MISTRAL = 'mistral',\n GOOGLE = 'google',\n AZURE = 'azureOpenAI',\n DEEPSEEK = 'deepseek',\n OPENROUTER = 'openrouter',\n XAI = 'xai',\n MOONSHOT = 'moonshot',\n}\n\nexport enum GraphNodeKeys {\n TOOLS = 'tools=',\n AGENT = 'agent=',\n SUMMARIZE = 'summarize=',\n ROUTER = 'router',\n PRE_TOOLS = 'pre_tools',\n POST_TOOLS = 'post_tools',\n}\n\nexport enum GraphNodeActions {\n TOOL_NODE = 'tool_node',\n CALL_MODEL = 'call_model',\n ROUTE_MESSAGE = 'route_message',\n}\n\nexport enum CommonEvents {\n LANGGRAPH = 'LangGraph',\n}\n\nexport enum StepTypes {\n TOOL_CALLS = 'tool_calls',\n MESSAGE_CREATION = 'message_creation',\n}\n\nexport enum ContentTypes {\n TEXT = 'text',\n ERROR = 'error',\n THINK = 'think',\n TOOL_CALL = 'tool_call',\n IMAGE_URL = 'image_url',\n IMAGE_FILE = 'image_file',\n /** Anthropic */\n THINKING = 'thinking',\n /** Vertex AI / Google Common */\n REASONING = 'reasoning',\n /** Multi-Agent Switch */\n AGENT_UPDATE = 'agent_update',\n /** Framework-level conversation summary block */\n SUMMARY = 'summary',\n /** Bedrock */\n REASONING_CONTENT = 'reasoning_content',\n /** Mid-run user steer persisted inline in an assistant message; replayed as a user turn */\n STEER = 'steer',\n /** Fast-model activity label for a tool/reasoning block; UI-only, never model input */\n ACTIVITY_LABEL = 'activity_label',\n}\n\nexport enum ToolCallTypes {\n FUNCTION = 'function',\n RETRIEVAL = 'retrieval',\n FILE_SEARCH = 'file_search',\n CODE_INTERPRETER = 'code_interpreter',\n /* Agents Tool Call */\n TOOL_CALL = 'tool_call',\n}\n\nexport enum Callback {\n TOOL_ERROR = 'handleToolError',\n TOOL_START = 'handleToolStart',\n TOOL_END = 'handleToolEnd',\n CUSTOM_EVENT = 'handleCustomEvent',\n /*\n LLM_START = 'handleLLMStart',\n LLM_NEW_TOKEN = 'handleLLMNewToken',\n LLM_ERROR = 'handleLLMError',\n LLM_END = 'handleLLMEnd',\n CHAT_MODEL_START = 'handleChatModelStart',\n CHAIN_START = 'handleChainStart',\n CHAIN_ERROR = 'handleChainError',\n CHAIN_END = 'handleChainEnd',\n TEXT = 'handleText',\n AGENT_ACTION = 'handleAgentAction',\n AGENT_END = 'handleAgentEnd',\n RETRIEVER_START = 'handleRetrieverStart',\n RETRIEVER_END = 'handleRetrieverEnd',\n RETRIEVER_ERROR = 'handleRetrieverError',\n */\n}\n\nexport enum Constants {\n OFFICIAL_CODE_BASEURL = 'https://api.librechat.ai/v1',\n EXECUTE_CODE = 'execute_code',\n TOOL_SEARCH = 'tool_search',\n PROGRAMMATIC_TOOL_CALLING = 'run_tools_with_code',\n WEB_SEARCH = 'web_search',\n CONTENT_AND_ARTIFACT = 'content_and_artifact',\n LC_TRANSFER_TO_ = 'lc_transfer_to_',\n HANDOFF_PARALLEL_BATCH = '__handoff_parallel_batch',\n HANDOFF_GROUP_ID = '__handoff_group_id',\n /** Delimiter for MCP tools: toolName_mcp_serverName */\n MCP_DELIMITER = '_mcp_',\n /** Anthropic server tool ID prefix (web_search, code_execution, etc.) */\n ANTHROPIC_SERVER_TOOL_PREFIX = 'srvtoolu_',\n SKILL_TOOL = 'skill',\n /**\n * Callback-metadata keys stamped by `attemptInvoke` /\n * `tryFallbackProviders` carrying the provider (SDK `Providers` enum\n * value) and configured model that actually served a model invocation.\n * Unlike `ls_provider` — which derived providers inherit from their base\n * class (e.g. DeepSeek/OpenRouter report `'openai'`) — these reflect the\n * SDK's own routing, including fallback-provider calls. Consumed by the\n * subagent usage-capture handler to tag billing events.\n */\n INVOKED_PROVIDER = '__invoked_provider',\n INVOKED_MODEL = '__invoked_model',\n READ_FILE = 'read_file',\n BASH_TOOL = 'bash_tool',\n BASH_PROGRAMMATIC_TOOL_CALLING = 'run_tools_with_bash',\n SUBAGENT = 'subagent',\n /**\n * Local-engine coding tool names. Promoted to `Constants.*` (rather\n * than left as per-file `*ToolName` consts) so consumer UIs — most\n * importantly LibreChat's `getToolIconType` map — can match against\n * canonical strings instead of guessing. Existing matched names:\n * `bash_tool`, `read_file`, `execute_code`, `run_tools_with_code`.\n * The rest below are new and currently fall through to the generic\n * tool icon; once LibreChat adds icons keyed on the same names, the\n * wiring will work without an SDK change.\n */\n WRITE_FILE = 'write_file',\n EDIT_FILE = 'edit_file',\n GREP_SEARCH = 'grep_search',\n GLOB_SEARCH = 'glob_search',\n LIST_DIRECTORY = 'list_directory',\n COMPILE_CHECK = 'compile_check',\n}\n\n/** Tool names that use the code execution environment (shared session, file tracking). */\nexport const CODE_EXECUTION_TOOLS: ReadonlySet<string> = new Set([\n Constants.EXECUTE_CODE,\n Constants.BASH_TOOL,\n Constants.PROGRAMMATIC_TOOL_CALLING,\n Constants.BASH_PROGRAMMATIC_TOOL_CALLING,\n]);\n\n/**\n * Canonical names of the local-engine-specific coding tools — the\n * file/edit/search/typecheck surface that doesn't exist in the\n * remote (sandbox-API) engine. Single source of truth; the per-tool\n * factories, registry definitions, and `createWorkspacePolicyHook`\n * default extractors all key off these.\n *\n * `read_file` is on this list (the existing ReadFile tool is\n * remote-specific; the local engine's `read_file` is a parallel\n * implementation that shares the canonical name so consumer UIs\n * — most importantly LibreChat's `getToolIconType` — render both\n * with the same icon).\n */\nexport const LOCAL_CODING_TOOL_NAMES: readonly string[] = [\n Constants.READ_FILE,\n Constants.WRITE_FILE,\n Constants.EDIT_FILE,\n Constants.GREP_SEARCH,\n Constants.GLOB_SEARCH,\n Constants.LIST_DIRECTORY,\n Constants.COMPILE_CHECK,\n];\n\n/**\n * Every tool name the local coding bundle (`createLocalCodingTools`)\n * exposes — the local-specific tools above plus the bash/code/PTC\n * pair that the local engine wraps around the existing factories.\n * Tests pin against this so any addition/removal in the bundle is\n * accompanied by a deliberate canonical-name update here.\n */\nexport const LOCAL_CODING_BUNDLE_NAMES: readonly string[] = [\n ...LOCAL_CODING_TOOL_NAMES,\n Constants.BASH_TOOL,\n Constants.EXECUTE_CODE,\n Constants.PROGRAMMATIC_TOOL_CALLING,\n Constants.BASH_PROGRAMMATIC_TOOL_CALLING,\n];\n\nexport enum TitleMethod {\n STRUCTURED = 'structured',\n FUNCTIONS = 'functions',\n COMPLETION = 'completion',\n}\n\nexport enum EnvVar {\n CODE_BASEURL = 'LIBRECHAT_CODE_BASEURL',\n CODE_API_RUN_TIMEOUT_MS = 'CODE_API_RUN_TIMEOUT_MS',\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"enum.cjs","names":[],"sources":["../../../src/common/enum.ts"],"sourcesContent":["/**\n * Enum representing the various event types emitted during the execution of runnables.\n * These events provide real-time information about the progress and state of different components.\n *\n * @enum {string}\n */\nexport enum GraphEvents {\n /* Custom Events */\n\n /** [Custom] Agent update event in multi-agent graph/workflow */\n ON_AGENT_UPDATE = 'on_agent_update',\n /** [Custom] Delta event for run steps (message creation and tool calls) */\n ON_RUN_STEP = 'on_run_step',\n /** [Custom] Delta event for run steps (tool calls) */\n ON_RUN_STEP_DELTA = 'on_run_step_delta',\n /** [Custom] Completed event for run steps (tool calls) */\n ON_RUN_STEP_COMPLETED = 'on_run_step_completed',\n /** [Custom] Terminal signal for a run step: closed with status + timestamps */\n ON_RUN_STEP_CLOSED = 'on_run_step_closed',\n /** [Custom] Delta events for messages */\n ON_MESSAGE_DELTA = 'on_message_delta',\n /** [Custom] Reasoning Delta events for messages */\n ON_REASONING_DELTA = 'on_reasoning_delta',\n /** [Custom] Request to execute tools - dispatched by ToolNode, handled by host */\n ON_TOOL_EXECUTE = 'on_tool_execute',\n /** [Custom] Emitted when the summarize node begins generating a summary */\n ON_SUMMARIZE_START = 'on_summarize_start',\n /** [Custom] Delta event carrying the completed summary content */\n ON_SUMMARIZE_DELTA = 'on_summarize_delta',\n /** [Custom] Emitted when the summarize node completes with the final summary */\n ON_SUMMARIZE_COMPLETE = 'on_summarize_complete',\n /** [Custom] Progress update from a running subagent (wraps child-graph events so hosts can display activity separately from parent). */\n ON_SUBAGENT_UPDATE = 'on_subagent_update',\n /** [Custom] Diagnostic logging event for context management observability */\n ON_AGENT_LOG = 'on_agent_log',\n /** [Custom] Per-model-call context window usage snapshot (post-prune token budget) */\n ON_CONTEXT_USAGE = 'on_context_usage',\n\n /* Official Events */\n\n /** Custom event, emitted by system */\n ON_CUSTOM_EVENT = 'on_custom_event',\n /** Emitted when a chat model starts processing. */\n CHAT_MODEL_START = 'on_chat_model_start',\n\n /** Emitted when a chat model streams a chunk of its response. */\n CHAT_MODEL_STREAM = 'on_chat_model_stream',\n\n /** Emitted when a chat model completes its processing. */\n CHAT_MODEL_END = 'on_chat_model_end',\n\n /** Emitted when a language model starts processing. */\n LLM_START = 'on_llm_start',\n\n /** Emitted when a language model streams a chunk of its response. */\n LLM_STREAM = 'on_llm_stream',\n\n /** Emitted when a language model completes its processing. */\n LLM_END = 'on_llm_end',\n\n /** Emitted when a chain starts processing. */\n CHAIN_START = 'on_chain_start',\n\n /** Emitted when a chain streams a chunk of its output. */\n CHAIN_STREAM = 'on_chain_stream',\n\n /** Emitted when a chain completes its processing. */\n CHAIN_END = 'on_chain_end',\n\n /** Emitted when a tool starts its operation. */\n TOOL_START = 'on_tool_start',\n\n /** Emitted when a tool completes its operation. */\n TOOL_END = 'on_tool_end',\n\n /** Emitted when a retriever starts its operation. */\n RETRIEVER_START = 'on_retriever_start',\n\n /** Emitted when a retriever completes its operation. */\n RETRIEVER_END = 'on_retriever_end',\n\n /** Emitted when a prompt starts processing. */\n PROMPT_START = 'on_prompt_start',\n\n /** Emitted when a prompt completes its processing. */\n PROMPT_END = 'on_prompt_end',\n}\n\nexport enum Providers {\n OPENAI = 'openAI',\n VERTEXAI = 'vertexai',\n BEDROCK = 'bedrock',\n ANTHROPIC = 'anthropic',\n MISTRALAI = 'mistralai',\n MISTRAL = 'mistral',\n GOOGLE = 'google',\n AZURE = 'azureOpenAI',\n DEEPSEEK = 'deepseek',\n OPENROUTER = 'openrouter',\n XAI = 'xai',\n MOONSHOT = 'moonshot',\n}\n\nexport enum GraphNodeKeys {\n TOOLS = 'tools=',\n AGENT = 'agent=',\n SUMMARIZE = 'summarize=',\n ROUTER = 'router',\n PRE_TOOLS = 'pre_tools',\n POST_TOOLS = 'post_tools',\n}\n\nexport enum GraphNodeActions {\n TOOL_NODE = 'tool_node',\n CALL_MODEL = 'call_model',\n ROUTE_MESSAGE = 'route_message',\n}\n\nexport enum CommonEvents {\n LANGGRAPH = 'LangGraph',\n}\n\nexport enum StepTypes {\n TOOL_CALLS = 'tool_calls',\n MESSAGE_CREATION = 'message_creation',\n}\n\nexport enum ContentTypes {\n TEXT = 'text',\n ERROR = 'error',\n THINK = 'think',\n TOOL_CALL = 'tool_call',\n IMAGE_URL = 'image_url',\n IMAGE_FILE = 'image_file',\n /** Anthropic */\n THINKING = 'thinking',\n /** Vertex AI / Google Common */\n REASONING = 'reasoning',\n /** Multi-Agent Switch */\n AGENT_UPDATE = 'agent_update',\n /** Framework-level conversation summary block */\n SUMMARY = 'summary',\n /** Bedrock */\n REASONING_CONTENT = 'reasoning_content',\n /** Mid-run user steer persisted inline in an assistant message; replayed as a user turn */\n STEER = 'steer',\n /** Fast-model activity label for a tool/reasoning block; UI-only, never model input */\n ACTIVITY_LABEL = 'activity_label',\n}\n\nexport enum ToolCallTypes {\n FUNCTION = 'function',\n RETRIEVAL = 'retrieval',\n FILE_SEARCH = 'file_search',\n CODE_INTERPRETER = 'code_interpreter',\n /* Agents Tool Call */\n TOOL_CALL = 'tool_call',\n}\n\nexport enum Callback {\n TOOL_ERROR = 'handleToolError',\n TOOL_START = 'handleToolStart',\n TOOL_END = 'handleToolEnd',\n CUSTOM_EVENT = 'handleCustomEvent',\n /*\n LLM_START = 'handleLLMStart',\n LLM_NEW_TOKEN = 'handleLLMNewToken',\n LLM_ERROR = 'handleLLMError',\n LLM_END = 'handleLLMEnd',\n CHAT_MODEL_START = 'handleChatModelStart',\n CHAIN_START = 'handleChainStart',\n CHAIN_ERROR = 'handleChainError',\n CHAIN_END = 'handleChainEnd',\n TEXT = 'handleText',\n AGENT_ACTION = 'handleAgentAction',\n AGENT_END = 'handleAgentEnd',\n RETRIEVER_START = 'handleRetrieverStart',\n RETRIEVER_END = 'handleRetrieverEnd',\n RETRIEVER_ERROR = 'handleRetrieverError',\n */\n}\n\nexport enum Constants {\n OFFICIAL_CODE_BASEURL = 'https://api.librechat.ai/v1',\n EXECUTE_CODE = 'execute_code',\n TOOL_SEARCH = 'tool_search',\n PROGRAMMATIC_TOOL_CALLING = 'run_tools_with_code',\n WEB_SEARCH = 'web_search',\n CONTENT_AND_ARTIFACT = 'content_and_artifact',\n LC_TRANSFER_TO_ = 'lc_transfer_to_',\n HANDOFF_PARALLEL_BATCH = '__handoff_parallel_batch',\n HANDOFF_GROUP_ID = '__handoff_group_id',\n /** Delimiter for MCP tools: toolName_mcp_serverName */\n MCP_DELIMITER = '_mcp_',\n /** Anthropic server tool ID prefix (web_search, code_execution, etc.) */\n ANTHROPIC_SERVER_TOOL_PREFIX = 'srvtoolu_',\n SKILL_TOOL = 'skill',\n /**\n * Callback-metadata keys stamped by `attemptInvoke` /\n * `tryFallbackProviders` carrying the provider (SDK `Providers` enum\n * value) and configured model that actually served a model invocation.\n * Unlike `ls_provider` — which derived providers inherit from their base\n * class (e.g. DeepSeek/OpenRouter report `'openai'`) — these reflect the\n * SDK's own routing, including fallback-provider calls. Consumed by the\n * subagent usage-capture handler to tag billing events.\n */\n INVOKED_PROVIDER = '__invoked_provider',\n INVOKED_MODEL = '__invoked_model',\n READ_FILE = 'read_file',\n BASH_TOOL = 'bash_tool',\n BASH_PROGRAMMATIC_TOOL_CALLING = 'run_tools_with_bash',\n SUBAGENT = 'subagent',\n /**\n * Local-engine coding tool names. Promoted to `Constants.*` (rather\n * than left as per-file `*ToolName` consts) so consumer UIs — most\n * importantly LibreChat's `getToolIconType` map — can match against\n * canonical strings instead of guessing. Existing matched names:\n * `bash_tool`, `read_file`, `execute_code`, `run_tools_with_code`.\n * The rest below are new and currently fall through to the generic\n * tool icon; once LibreChat adds icons keyed on the same names, the\n * wiring will work without an SDK change.\n */\n WRITE_FILE = 'write_file',\n EDIT_FILE = 'edit_file',\n GREP_SEARCH = 'grep_search',\n GLOB_SEARCH = 'glob_search',\n LIST_DIRECTORY = 'list_directory',\n COMPILE_CHECK = 'compile_check',\n}\n\n/** Tool names that use the code execution environment (shared session, file tracking). */\nexport const CODE_EXECUTION_TOOLS: ReadonlySet<string> = new Set([\n Constants.EXECUTE_CODE,\n Constants.BASH_TOOL,\n Constants.PROGRAMMATIC_TOOL_CALLING,\n Constants.BASH_PROGRAMMATIC_TOOL_CALLING,\n]);\n\n/**\n * Canonical names of the local-engine-specific coding tools — the\n * file/edit/search/typecheck surface that doesn't exist in the\n * remote (sandbox-API) engine. Single source of truth; the per-tool\n * factories, registry definitions, and `createWorkspacePolicyHook`\n * default extractors all key off these.\n *\n * `read_file` is on this list (the existing ReadFile tool is\n * remote-specific; the local engine's `read_file` is a parallel\n * implementation that shares the canonical name so consumer UIs\n * — most importantly LibreChat's `getToolIconType` — render both\n * with the same icon).\n */\nexport const LOCAL_CODING_TOOL_NAMES: readonly string[] = [\n Constants.READ_FILE,\n Constants.WRITE_FILE,\n Constants.EDIT_FILE,\n Constants.GREP_SEARCH,\n Constants.GLOB_SEARCH,\n Constants.LIST_DIRECTORY,\n Constants.COMPILE_CHECK,\n];\n\n/**\n * Every tool name the local coding bundle (`createLocalCodingTools`)\n * exposes — the local-specific tools above plus the bash/code/PTC\n * pair that the local engine wraps around the existing factories.\n * Tests pin against this so any addition/removal in the bundle is\n * accompanied by a deliberate canonical-name update here.\n */\nexport const LOCAL_CODING_BUNDLE_NAMES: readonly string[] = [\n ...LOCAL_CODING_TOOL_NAMES,\n Constants.BASH_TOOL,\n Constants.EXECUTE_CODE,\n Constants.PROGRAMMATIC_TOOL_CALLING,\n Constants.BASH_PROGRAMMATIC_TOOL_CALLING,\n];\n\nexport enum TitleMethod {\n STRUCTURED = 'structured',\n FUNCTIONS = 'functions',\n COMPLETION = 'completion',\n}\n\nexport enum EnvVar {\n CODE_BASEURL = 'LIBRECHAT_CODE_BASEURL',\n CODE_API_RUN_TIMEOUT_MS = 'CODE_API_RUN_TIMEOUT_MS',\n}\n"],"mappings":"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"}
@@ -294,6 +294,17 @@ var Graph = class {
294
294
  stepKeyIds = /* @__PURE__ */ new Map();
295
295
  contentIndexMap = /* @__PURE__ */ new Map();
296
296
  toolCallStepIds = /* @__PURE__ */ new Map();
297
+ /** Step ID -> tool call IDs whose completions have not yet arrived. */
298
+ pendingToolCallsByStep = /* @__PURE__ */ new Map();
299
+ /**
300
+ * Step ID -> latest producer completion time seen for that step. Parallel
301
+ * calls sharing a step can settle out of producer order when their host
302
+ * handlers differ in latency, so the call that happens to drain the set is
303
+ * not necessarily the one that finished last.
304
+ */
305
+ latestCompletionByStep = /* @__PURE__ */ new Map();
306
+ /** Agent key ('' for single-agent) -> currently open MESSAGE_CREATION step ID. */
307
+ openMessageStepByAgent = /* @__PURE__ */ new Map();
297
308
  /**
298
309
  * Step IDs dispatched through the handler registry during this run.
299
310
  * Event echo suppression is tracked separately so repeated deltas for
@@ -407,6 +418,9 @@ var Graph = class {
407
418
  this.contentIndexMap = /* @__PURE__ */ new Map();
408
419
  this.stepKeyIds = /* @__PURE__ */ new Map();
409
420
  this.toolCallStepIds.clear();
421
+ this.pendingToolCallsByStep.clear();
422
+ this.latestCompletionByStep.clear();
423
+ this.openMessageStepByAgent.clear();
410
424
  this.messageIdsByStepKey = /* @__PURE__ */ new Map();
411
425
  this.messageStepHasTextDeltas = /* @__PURE__ */ new Set();
412
426
  this.reasoningStepHasDeltas = /* @__PURE__ */ new Set();
@@ -454,6 +468,26 @@ var Graph = class {
454
468
  this.eagerEventToolUsageCount.clear();
455
469
  for (const usageCount of this.eagerEventToolUsageCountsByAgentId.values()) usageCount.clear();
456
470
  }
471
+ /**
472
+ * Tracks a tool call whose completion must arrive before its step can be
473
+ * considered finished. Registered wherever `toolCallStepIds` gains entries,
474
+ * except cross-process subagent resume restoration, where pending state
475
+ * cannot be faithfully rebuilt and closes fall back to completions/sweep.
476
+ */
477
+ registerPendingToolCall(toolCallId, stepId) {
478
+ if (!toolCallId || !stepId) return;
479
+ this.getPendingToolCallSet(stepId).add(toolCallId);
480
+ }
481
+ /** Lazily creates a step's pending-completions set; callers registering a
482
+ * batch hoist this lookup out of their per-call loop. */
483
+ getPendingToolCallSet(stepId) {
484
+ let pending = this.pendingToolCallsByStep.get(stepId);
485
+ if (!pending) {
486
+ pending = /* @__PURE__ */ new Set();
487
+ this.pendingToolCallsByStep.set(stepId, pending);
488
+ }
489
+ return pending;
490
+ }
457
491
  markHandlerDispatchedEvent(eventName, stepId) {
458
492
  const key = getHandlerDispatchedEventKey(eventName, stepId);
459
493
  this.handlerDispatchedEventCounts.set(key, (this.handlerDispatchedEventCounts.get(key) ?? 0) + 1);
@@ -803,6 +837,9 @@ var StandardGraph = class StandardGraph extends Graph {
803
837
  * a stale reference on 2nd+ processStream calls.
804
838
  */
805
839
  this.toolCallStepIds.clear();
840
+ this.pendingToolCallsByStep.clear();
841
+ this.latestCompletionByStep.clear();
842
+ this.openMessageStepByAgent.clear();
806
843
  this.runProducedAiMessageIds.clear();
807
844
  this.eagerEventToolExecutions.clear();
808
845
  this.clearEagerEventToolUsageCounts();
@@ -971,6 +1008,196 @@ var StandardGraph = class StandardGraph extends Graph {
971
1008
  const index = this.contentIndexMap.get(stepId);
972
1009
  if (index !== void 0) return this.contentData[index];
973
1010
  }
1011
+ /**
1012
+ * Derives the same lane key `dispatchRunStep` stamps as `runStep.agentId`.
1013
+ * The multi-agent check gates the lookup because `getAgentContext` signals
1014
+ * a miss by throwing: single-agent graphs key every step under `''`, so
1015
+ * resolving the context could only ever produce a thrown-and-discarded
1016
+ * Error on a per-model-call path.
1017
+ */
1018
+ getStepAgentKey(metadata) {
1019
+ if (!metadata || !this.isMultiAgentGraph()) return "";
1020
+ try {
1021
+ const agentContext = this.getAgentContext(metadata);
1022
+ if (agentContext.agentId) return agentContext.agentId;
1023
+ } catch (_e) {}
1024
+ return "";
1025
+ }
1026
+ /**
1027
+ * O(1) reverse lookup: both dispatch funnels key the open-message map by
1028
+ * `runStep.agentId ?? ''`, so the entry is addressable without scanning.
1029
+ */
1030
+ untrackRunStep(runStep) {
1031
+ this.pendingToolCallsByStep.delete(runStep.id);
1032
+ this.latestCompletionByStep.delete(runStep.id);
1033
+ const agentKey = runStep.agentId ?? "";
1034
+ if (this.openMessageStepByAgent.get(agentKey) === runStep.id) this.openMessageStepByAgent.delete(agentKey);
1035
+ }
1036
+ /**
1037
+ * Shared step accounting for both dispatch funnels: a successor step in
1038
+ * the same agent lane marks the previous message step as finished — its
1039
+ * CLOSED event must precede the successor's ON_RUN_STEP so hosts observe
1040
+ * a consistent open-step timeline — then the step is registered in the
1041
+ * content maps and tracked as the lane's open message step when
1042
+ * applicable.
1043
+ */
1044
+ async trackDispatchedRunStep(runStep, metadata, trackAsOpenMessageStep = true) {
1045
+ const agentKey = runStep.agentId ?? "";
1046
+ const openMessageStepId = this.openMessageStepByAgent.get(agentKey);
1047
+ /**
1048
+ * Reserve the content index and register the step SYNCHRONOUSLY, before
1049
+ * awaiting the predecessor's closure. Parallel agent lanes dispatch
1050
+ * successors concurrently: if both yielded here first, they would push
1051
+ * with the same `contentData.length` and `contentIndexMap` would resolve
1052
+ * one step id to the other lane's entry, so later deltas and completions
1053
+ * would mutate the wrong agent's step. Assigning the index at push time
1054
+ * also supersedes whatever the caller computed before this await point.
1055
+ */
1056
+ runStep.index = this.contentData.length;
1057
+ this.contentData.push(runStep);
1058
+ this.contentIndexMap.set(runStep.id, runStep.index);
1059
+ if (trackAsOpenMessageStep && runStep.type === "message_creation") this.openMessageStepByAgent.set(agentKey, runStep.id);
1060
+ else this.openMessageStepByAgent.delete(agentKey);
1061
+ /**
1062
+ * Awaited after registration but before the caller dispatches this step's
1063
+ * ON_RUN_STEP, so the predecessor's CLOSED event still precedes the
1064
+ * successor's start event.
1065
+ */
1066
+ if (openMessageStepId != null && openMessageStepId !== runStep.id)
1067
+ /**
1068
+ * Isolated: this step is already registered in `contentData`, so a
1069
+ * predecessor delivery failure rejecting here would abort the caller
1070
+ * before it publishes this step's ON_RUN_STEP — leaving the sweep to
1071
+ * emit a terminal event for a step that never announced a start.
1072
+ */
1073
+ try {
1074
+ await this.closeRunStep(openMessageStepId, "completed", { metadata });
1075
+ } catch (_e) {}
1076
+ /**
1077
+ * Stamped last, after the predecessor's closure has been delivered and
1078
+ * immediately before the caller publishes this step's ON_RUN_STEP.
1079
+ * `created_at` documents when the step was dispatched, so it must not
1080
+ * absorb the latency of an arbitrarily slow predecessor handler.
1081
+ */
1082
+ runStep.created_at = Date.now();
1083
+ }
1084
+ /**
1085
+ * Closes a run step: stamps its terminal status + timestamp on the stored
1086
+ * `RunStep` and emits `ON_RUN_STEP_CLOSED`. First close wins — later calls
1087
+ * are no-ops — except a `restamp` close, which lets a `completed`
1088
+ * TOOL_CALLS step refresh `completed_at` when a late-registered parallel
1089
+ * tool call finishes after the step already closed (the eager-execution
1090
+ * race). `cancelled`/`failed` are immutable once stamped.
1091
+ */
1092
+ async closeRunStep(stepId, status, options) {
1093
+ if (!stepId) return false;
1094
+ const runStep = this.getRunStep(stepId);
1095
+ if (!runStep) return false;
1096
+ if (runStep.status != null && runStep.status !== "in_progress") {
1097
+ this.untrackRunStep(runStep);
1098
+ return false;
1099
+ }
1100
+ const closedAt = options?.at ?? Date.now();
1101
+ runStep.status = status;
1102
+ if (status === "completed") runStep.completed_at = closedAt;
1103
+ else if (status === "cancelled") runStep.cancelled_at = closedAt;
1104
+ else runStep.failed_at = closedAt;
1105
+ const closedEvent = {
1106
+ id: stepId,
1107
+ index: runStep.index,
1108
+ type: runStep.type,
1109
+ status,
1110
+ closed_at: closedAt
1111
+ };
1112
+ if (runStep.created_at != null) closedEvent.created_at = runStep.created_at;
1113
+ if (runStep.runId != null) closedEvent.runId = runStep.runId;
1114
+ if (runStep.agentId != null) closedEvent.agentId = runStep.agentId;
1115
+ if (runStep.groupId != null) closedEvent.groupId = runStep.groupId;
1116
+ if (runStep.stepIndex != null) closedEvent.stepIndex = runStep.stepIndex;
1117
+ this.untrackRunStep(runStep);
1118
+ const handler = this.handlerRegistry?.getHandler("on_run_step_closed");
1119
+ if (handler) {
1120
+ await handler.handle("on_run_step_closed", closedEvent, options?.metadata, this);
1121
+ this.handlerDispatchedStepIds.add(stepId);
1122
+ }
1123
+ const unmarkHandlerDispatchedEvent = handler ? this.markHandlerDispatchedEvent("on_run_step_closed", stepId) : void 0;
1124
+ try {
1125
+ if (this.config) await require_events.safeDispatchCustomEvent("on_run_step_closed", closedEvent, this.config);
1126
+ } finally {
1127
+ unmarkHandlerDispatchedEvent?.();
1128
+ }
1129
+ return true;
1130
+ }
1131
+ /**
1132
+ * Observes one `ON_RUN_STEP_COMPLETED` for a step and closes the step when
1133
+ * no registered tool calls remain pending. Steps without pending tracking
1134
+ * (summaries, cross-process resume) close on their first completion; the
1135
+ * terminal-status guard in `closeRunStep` absorbs duplicate echoes.
1136
+ */
1137
+ async recordStepCompletion(stepId, options) {
1138
+ if (!stepId) return;
1139
+ const { toolCallId, metadata, at } = options ?? {};
1140
+ if (at != null) {
1141
+ const latest = this.latestCompletionByStep.get(stepId);
1142
+ if (latest == null || at > latest) this.latestCompletionByStep.set(stepId, at);
1143
+ }
1144
+ const closeAt = this.latestCompletionByStep.get(stepId) ?? at;
1145
+ const pending = this.pendingToolCallsByStep.get(stepId);
1146
+ if (pending == null) {
1147
+ await this.closeRunStep(stepId, "completed", {
1148
+ metadata,
1149
+ at: closeAt
1150
+ });
1151
+ return;
1152
+ }
1153
+ if (toolCallId != null && toolCallId !== "") pending.delete(toolCallId);
1154
+ if (pending.size > 0) return;
1155
+ await this.closeRunStep(stepId, "completed", {
1156
+ metadata,
1157
+ at: closeAt
1158
+ });
1159
+ }
1160
+ /**
1161
+ * Closes the tracked open MESSAGE_CREATION step for the event's agent lane.
1162
+ * Fires on every model end, so the empty-map check short-circuits ahead of
1163
+ * resolving the lane key — a turn whose message step already closed through
1164
+ * successor-close does no work here.
1165
+ */
1166
+ async closeOpenMessageStep(metadata, at) {
1167
+ if (this.openMessageStepByAgent.size === 0) return;
1168
+ const agentKey = this.getStepAgentKey(metadata);
1169
+ const openStepId = this.openMessageStepByAgent.get(agentKey);
1170
+ if (openStepId == null) return;
1171
+ await this.closeRunStep(openStepId, "completed", {
1172
+ metadata,
1173
+ at
1174
+ });
1175
+ }
1176
+ /**
1177
+ * End-of-run sweep: closes every step that never reached a terminal
1178
+ * status. Dual-dispatches like any other close — the custom-event channel
1179
+ * is usually already torn down here and `safeDispatchCustomEvent` reports
1180
+ * that quietly, but callback-only subscribers still receive the terminal
1181
+ * signal whenever it is alive.
1182
+ */
1183
+ async closeUnfinishedRunSteps(status, at) {
1184
+ const closedAt = at ?? Date.now();
1185
+ for (const runStep of this.contentData) {
1186
+ if (runStep.status != null && runStep.status !== "in_progress") continue;
1187
+ /**
1188
+ * Isolated per step: one host handler throwing must not strand the
1189
+ * remaining steps `in_progress` with no terminal event. The step is
1190
+ * stamped before dispatch either way, so a thrown handler still leaves
1191
+ * consistent state behind.
1192
+ */
1193
+ try {
1194
+ await this.closeRunStep(runStep.id, status, { at: closedAt });
1195
+ } catch (_e) {}
1196
+ }
1197
+ this.pendingToolCallsByStep.clear();
1198
+ this.latestCompletionByStep.clear();
1199
+ this.openMessageStepByAgent.clear();
1200
+ }
974
1201
  getAgentContext(metadata) {
975
1202
  if (!metadata) throw new Error("No metadata provided to retrieve agent context");
976
1203
  const currentNode = metadata.langgraph_node;
@@ -2644,8 +2871,8 @@ var StandardGraph = class StandardGraph extends Graph {
2644
2871
  const groupId = this.resolveParallelGroupId(runStep.agentId, resolvedConfig?.metadata);
2645
2872
  if (groupId != null) runStep.groupId = groupId;
2646
2873
  }
2647
- this.contentData.push(runStep);
2648
- this.contentIndexMap.set(runStep.id, runStep.index);
2874
+ runStep.status ??= "in_progress";
2875
+ await this.trackDispatchedRunStep(runStep, resolvedConfig?.metadata, false);
2649
2876
  const handler = this.handlerRegistry?.getHandler("on_run_step");
2650
2877
  if (handler) {
2651
2878
  await handler.handle("on_run_step", runStep, resolvedConfig?.configurable, this);
@@ -2658,15 +2885,24 @@ var StandardGraph = class StandardGraph extends Graph {
2658
2885
  unmarkHandlerDispatchedEvent?.();
2659
2886
  }
2660
2887
  },
2888
+ closeRunStep: async (stepId, status, nodeConfig) => {
2889
+ await this.closeRunStep(stepId, status, { metadata: (nodeConfig ?? this.config)?.metadata });
2890
+ },
2661
2891
  dispatchRunStepCompleted: async (stepId, result, nodeConfig) => {
2662
2892
  const resolvedConfig = nodeConfig ?? this.config;
2663
- const runStep = this.contentData.find((s) => s.id === stepId);
2893
+ const completedAt = Date.now();
2894
+ const runStep = this.getRunStep(stepId);
2664
2895
  const handler = this.handlerRegistry?.getHandler("on_run_step_completed");
2665
2896
  if (handler) await handler.handle("on_run_step_completed", { result: {
2666
2897
  ...result,
2667
2898
  id: stepId,
2668
- index: runStep?.index ?? 0
2899
+ index: runStep?.index ?? 0,
2900
+ completed_at: completedAt
2669
2901
  } }, resolvedConfig?.configurable, this);
2902
+ await this.recordStepCompletion(stepId, {
2903
+ metadata: resolvedConfig?.metadata,
2904
+ at: completedAt
2905
+ });
2670
2906
  }
2671
2907
  },
2672
2908
  generateStepId: (stepKey) => this.generateStepId(stepKey)
@@ -2714,10 +2950,15 @@ var StandardGraph = class StandardGraph extends Graph {
2714
2950
  async dispatchRunStep(stepKey, stepDetails, metadata) {
2715
2951
  if (!this.config) throw new Error("No config provided");
2716
2952
  const [stepId, stepIndex] = this.generateStepId(stepKey);
2717
- if (stepDetails.type === "tool_calls" && stepDetails.tool_calls) for (const tool_call of stepDetails.tool_calls) {
2718
- const toolCallId = tool_call.id ?? "";
2719
- if (!toolCallId || this.toolCallStepIds.has(toolCallId)) continue;
2720
- this.toolCallStepIds.set(toolCallId, stepId);
2953
+ if (stepDetails.type === "tool_calls" && stepDetails.tool_calls) {
2954
+ let pendingToolCalls;
2955
+ for (const tool_call of stepDetails.tool_calls) {
2956
+ const toolCallId = tool_call.id ?? "";
2957
+ if (!toolCallId || this.toolCallStepIds.has(toolCallId)) continue;
2958
+ this.toolCallStepIds.set(toolCallId, stepId);
2959
+ pendingToolCalls ??= this.getPendingToolCallSet(stepId);
2960
+ pendingToolCalls.add(toolCallId);
2961
+ }
2721
2962
  }
2722
2963
  const runStep = {
2723
2964
  stepIndex,
@@ -2725,20 +2966,26 @@ var StandardGraph = class StandardGraph extends Graph {
2725
2966
  type: stepDetails.type,
2726
2967
  index: this.contentData.length,
2727
2968
  stepDetails,
2728
- usage: null
2969
+ usage: null,
2970
+ status: "in_progress"
2729
2971
  };
2730
2972
  const runId = this.runId ?? "";
2731
2973
  if (runId) runStep.runId = runId;
2732
- if (metadata) try {
2974
+ /**
2975
+ * `agentId`/`groupId` are multi-agent-only, and `getAgentContext` signals a
2976
+ * miss by throwing — so for a single-agent graph the lookup could only ever
2977
+ * build and discard an Error while producing the same undefined fields.
2978
+ * The constant-time check gates it out of the per-step dispatch path.
2979
+ */
2980
+ if (metadata && this.isMultiAgentGraph()) try {
2733
2981
  const agentContext = this.getAgentContext(metadata);
2734
- if (this.isMultiAgentGraph() && agentContext.agentId) {
2982
+ if (agentContext.agentId) {
2735
2983
  runStep.agentId = agentContext.agentId;
2736
2984
  const groupId = this.resolveParallelGroupId(agentContext.agentId, metadata);
2737
2985
  if (groupId != null) runStep.groupId = groupId;
2738
2986
  }
2739
2987
  } catch (_e) {}
2740
- this.contentData.push(runStep);
2741
- this.contentIndexMap.set(stepId, runStep.index);
2988
+ await this.trackDispatchedRunStep(runStep, metadata);
2742
2989
  const handler = this.handlerRegistry?.getHandler("on_run_step");
2743
2990
  if (handler) {
2744
2991
  await handler.handle("on_run_step", runStep, metadata, this);
@@ -2779,6 +3026,7 @@ var StandardGraph = class StandardGraph extends Graph {
2779
3026
  const errorDetail = error?.message != null ? `: ${require_toolContent.serializeToolContentBounded(error.message, Math.max(0, eventValueLimit - 21 - 2))}` : "";
2780
3027
  const runStep = graph.getRunStep(stepId);
2781
3028
  if (!runStep) return false;
3029
+ const completedAt = Date.now();
2782
3030
  const tool_call = {
2783
3031
  id: data.id,
2784
3032
  name: name || "",
@@ -2792,8 +3040,14 @@ var StandardGraph = class StandardGraph extends Graph {
2792
3040
  id: stepId,
2793
3041
  index: runStep.index,
2794
3042
  type: "tool_call",
2795
- tool_call
3043
+ tool_call,
3044
+ completed_at: completedAt
2796
3045
  } }, metadata, graph);
3046
+ await graph.recordStepCompletion(stepId, {
3047
+ toolCallId: data.id,
3048
+ metadata,
3049
+ at: completedAt
3050
+ });
2797
3051
  return true;
2798
3052
  }
2799
3053
  /**