@librechat/agents 3.2.62 → 3.2.63

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.
@@ -111,6 +111,7 @@ var SubagentExecutor = class {
111
111
  agents: [childInputs],
112
112
  langfuse: this.langfuse,
113
113
  tokenCounter: this.tokenCounter,
114
+ subagentScope: true,
114
115
  /**
115
116
  * Forwarded so the child graph's own `SubagentExecutor` (created in
116
117
  * its `createAgentNode` when `allowNested` keeps subagentConfigs)
@@ -1 +1 @@
1
- {"version":3,"file":"SubagentExecutor.mjs","names":["event","req","step"],"sources":["../../../../src/tools/subagent/SubagentExecutor.ts"],"sourcesContent":["import { nanoid } from 'nanoid';\nimport { HumanMessage } from '@langchain/core/messages';\nimport { BaseCallbackHandler } from '@langchain/core/callbacks/base';\nimport type { BaseMessage, UsageMetadata } from '@langchain/core/messages';\nimport type { ChatGeneration, LLMResult } from '@langchain/core/outputs';\nimport type { Callbacks } from '@langchain/core/callbacks/manager';\nimport type {\n AgentInputs,\n InjectedMessage,\n MessageDeltaEvent,\n ProcessedToolCall,\n ReasoningDeltaEvent,\n RunStep,\n RunStepDeltaEvent,\n StandardGraphInput,\n ResolvedSubagentConfig,\n StepCompleted,\n SubagentConfig,\n SubagentUpdateEvent,\n SubagentUpdatePhase,\n SubagentUsageSink,\n ToolExecuteBatchRequest,\n ToolCallDelta,\n TokenCounter,\n} from '@/types';\nimport type { AggregatedHookResult, HookRegistry } from '@/hooks';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type { StandardGraph } from '@/graphs/Graph';\nimport type { HandlerRegistry } from '@/events';\nimport { Constants, GraphEvents, Callback, StepTypes } from '@/common';\nimport { executeHooks } from '@/hooks';\n\nconst DEFAULT_MAX_TURNS = 25;\nconst RECURSION_MULTIPLIER = 3;\nconst ERROR_MESSAGE_MAX_CHARS = 200;\nconst MAX_PENDING_SUBAGENT_UPDATES = 64;\n\nconst HOOK_FALLBACK: AggregatedHookResult = Object.freeze({\n additionalContexts: [] as string[],\n injectedMessages: [] as InjectedMessage[],\n errors: [] as string[],\n});\n\ntype SanitizedSubagentToolCall = {\n id: string;\n name: string;\n args?: ToolExecuteBatchRequest['toolCalls'][number]['args'];\n};\n\ntype SanitizedSubagentToolExecuteData = {\n toolCalls: SanitizedSubagentToolCall[];\n agentId?: string;\n};\n\ntype SanitizedRunStep = Partial<\n Pick<\n RunStep,\n | 'agentId'\n | 'groupId'\n | 'id'\n | 'index'\n | 'runId'\n | 'stepIndex'\n | 'summary'\n | 'type'\n | 'usage'\n >\n> & {\n stepDetails?: SanitizedStepDetails;\n};\n\ntype SanitizedStepDetails =\n | {\n type: StepTypes.MESSAGE_CREATION;\n message_creation?: {\n message_id?: string;\n };\n }\n | {\n type: StepTypes.TOOL_CALLS;\n tool_calls?: SanitizedAgentToolCall[];\n };\n\ntype SanitizedAgentToolCall = {\n id?: string;\n name?: string;\n args?: string | object;\n type?: string;\n function?: {\n name?: string;\n arguments?: string | object;\n };\n};\n\ntype SanitizedRunStepDelta = Partial<Pick<RunStepDeltaEvent, 'id'>> & {\n delta?: SanitizedToolCallDelta;\n};\n\ntype SanitizedToolCallDelta = Partial<\n Pick<ToolCallDelta, 'auth' | 'expires_at' | 'summary' | 'type'>\n> & {\n tool_calls?: SanitizedAgentToolCall[];\n};\n\ntype SanitizedStepCompleted =\n | {\n id?: string;\n index?: number;\n type: 'tool_call';\n tool_call?: SanitizedProcessedToolCall;\n }\n | {\n type: 'summary';\n summary?: Extract<StepCompleted, { type: 'summary' }>['summary'];\n };\n\ntype SanitizedProcessedToolCall = Partial<\n Pick<ProcessedToolCall, 'args' | 'id' | 'name' | 'output' | 'progress'>\n>;\n\ntype SanitizedRunStepCompleted = {\n result?: SanitizedStepCompleted;\n};\n\ntype SanitizedMessageDelta = Partial<Pick<MessageDeltaEvent, 'id'>> & {\n delta?: {\n content?: MessageDeltaEvent['delta']['content'];\n tool_call_ids?: MessageDeltaEvent['delta']['tool_call_ids'];\n };\n};\n\ntype SanitizedReasoningDelta = Partial<Pick<ReasoningDeltaEvent, 'id'>> & {\n delta?: {\n content?: ReasoningDeltaEvent['delta']['content'];\n };\n};\n\ntype QueuedSubagentUpdate = {\n eventName: string;\n phase: SubagentUpdatePhase;\n data: unknown;\n};\n\ntype ForwarderCallback = {\n handler: BaseCallbackHandler;\n drain: () => Promise<void>;\n};\n\nconst LANGGRAPH_RUNTIME_CONFIG_PREFIX = '__pregel_';\nconst LANGGRAPH_CHECKPOINT_CONFIG_KEYS = new Set([\n 'checkpoint_id',\n 'checkpoint_map',\n 'checkpoint_ns',\n]);\n\nexport type SubagentExecuteParams = {\n description: string;\n subagentType: string;\n threadId?: string;\n /**\n * Parent-side `tool_call_id` of the `subagent` tool invocation that\n * triggered this execution. Surfaced on {@link SubagentUpdateEvent} so\n * hosts can correlate child updates back to the originating tool call\n * without relying on event ordering heuristics.\n */\n parentToolCallId?: string;\n /**\n * Snapshot of the parent invocation's host `config.configurable` at\n * the spawn-tool call site. Host-set fields (`requestBody`, `user`,\n * `userMCPAuthMap`, etc.) propagate into the child workflow's\n * `configurable` — fixing MCP body-placeholder substitution and\n * per-user lookups for subagent tool calls. LangGraph runtime keys\n * (`__pregel_*`, checkpoint bookkeeping) are intentionally not\n * inherited; the child graph recreates its own runtime config.\n *\n * Inheritance details (verified empirically against LangGraph):\n * - host-set keys propagate as-is into the child's tool dispatches;\n * - `thread_id` propagates (with `childRunId` as a fallback when\n * parent did not supply one) — matches the \"subagent is part of\n * the same conversation\" mental model and aligns with the\n * `sessionId: this.parentRunId` convention this executor already\n * uses for `SubagentStart` / `SubagentStop` hooks;\n * - `parent_run_id` propagates when the host put it on parent's\n * configurable;\n * - `run_id` is *overwritten by the LangGraph runtime* at child\n * invoke time regardless of what we forward — child's tool\n * dispatches see the child graph's runtime runId in\n * `configurable.run_id`, not the parent's. Hosts that need\n * parent-scoped run identity for downstream consumers should\n * plumb it via a host-defined key (e.g. `requestBody.messageId`),\n * not `run_id`.\n *\n * A future revision will likely make this inheritance configurable\n * per spawn type — background / async subagents may want isolation\n * rather than sharing parent's host context.\n */\n parentConfigurable?: Record<string, unknown>;\n};\n\nexport type SubagentExecuteResult = {\n content: string;\n messages: BaseMessage[];\n};\n\n/**\n * Factory that constructs a child graph for subagent execution. Injected\n * rather than imported so that `SubagentExecutor` does not have a runtime\n * dependency on `StandardGraph` — this avoids a circular dependency between\n * `src/graphs/Graph.ts` and `src/tools/subagent/` that would otherwise break\n * Rollup's chunking under `preserveModules`.\n */\nexport type ChildGraphFactory = (input: StandardGraphInput) => StandardGraph;\n\nexport type SubagentExecutorOptions = {\n configs: Map<string, ResolvedSubagentConfig>;\n parentSignal?: AbortSignal;\n hookRegistry?: HookRegistry;\n parentRunId: string;\n parentAgentId?: string;\n langfuse?: StandardGraphInput['langfuse'];\n tokenCounter?: TokenCounter;\n /** Remaining nesting budget. 0 or negative blocks execution. */\n maxDepth?: number;\n /**\n * Factory for constructing the isolated child graph. Callers pass\n * `(input) => new StandardGraph(input)` — injected to break a circular\n * module dependency.\n */\n createChildGraph: ChildGraphFactory;\n /**\n * Parent's event handler registry. When provided, child-graph events are\n * forwarded through this registry so hosts can:\n * (a) execute event-driven tools (`ON_TOOL_EXECUTE` routed to parent's handler),\n * (b) surface child activity to a UI via wrapped {@link GraphEvents.ON_SUBAGENT_UPDATE}.\n * When omitted, the child runs fully isolated (legacy behavior).\n *\n * Can be a direct `HandlerRegistry` or a zero-arg getter — use the getter\n * form when the registry is assigned to the graph AFTER the executor is\n * constructed (the current `Run.create` flow sets `handlerRegistry`\n * post-`createWorkflow`, so `createAgentNode` must capture lazily).\n */\n parentHandlerRegistry?: HandlerRegistry | (() => HandlerRegistry | undefined);\n /**\n * Receives a usage event for every model call the child run makes. The\n * child workflow executes via `invoke()` with a detached callbacks array,\n * so its `on_chat_model_end` events never reach the parent's handler\n * registry — without this sink, child token usage is invisible to the\n * host (unbilled model calls). Forwarded into the child graph's input so\n * nested subagents report through the same sink.\n */\n usageSink?: SubagentUsageSink;\n};\n\nexport class SubagentExecutor {\n private readonly configs: Map<string, ResolvedSubagentConfig>;\n private readonly parentSignal?: AbortSignal;\n private readonly hookRegistry?: HookRegistry;\n private readonly parentRunId: string;\n private readonly parentAgentId?: string;\n private readonly langfuse?: StandardGraphInput['langfuse'];\n private readonly tokenCounter?: TokenCounter;\n private readonly maxDepth: number;\n private readonly createChildGraph: ChildGraphFactory;\n private readonly usageSink?: SubagentUsageSink;\n private readonly resolveParentHandlerRegistry?: () =>\n | HandlerRegistry\n | undefined;\n\n constructor(options: SubagentExecutorOptions) {\n this.configs = options.configs;\n this.parentSignal = options.parentSignal;\n this.hookRegistry = options.hookRegistry;\n this.parentRunId = options.parentRunId;\n this.parentAgentId = options.parentAgentId;\n this.langfuse = options.langfuse;\n this.tokenCounter = options.tokenCounter;\n this.maxDepth = options.maxDepth ?? 1;\n this.createChildGraph = options.createChildGraph;\n this.usageSink = options.usageSink;\n const rawRegistry = options.parentHandlerRegistry;\n if (typeof rawRegistry === 'function') {\n this.resolveParentHandlerRegistry = rawRegistry;\n } else if (rawRegistry != null) {\n this.resolveParentHandlerRegistry = (): HandlerRegistry => rawRegistry;\n }\n }\n\n /** Snapshot of the parent's registry at the moment a subagent is dispatched. */\n private getParentHandlerRegistry(): HandlerRegistry | undefined {\n return this.resolveParentHandlerRegistry?.();\n }\n\n async execute(params: SubagentExecuteParams): Promise<SubagentExecuteResult> {\n const { description, subagentType, threadId, parentToolCallId } = params;\n const config = this.configs.get(subagentType);\n\n if (!config) {\n const available = [...this.configs.keys()].join(', ');\n return {\n content: `Error: Unknown subagent type \"${subagentType}\". Available types: ${available}`,\n messages: [],\n };\n }\n\n if (this.maxDepth <= 0) {\n return {\n content: 'Error: Maximum subagent nesting depth exceeded.',\n messages: [],\n };\n }\n\n const childAgentId =\n config.agentInputs.agentId ||\n `${this.parentAgentId ?? 'agent'}_sub_${nanoid(8)}`;\n\n if (\n this.hookRegistry?.hasHookFor('SubagentStart', this.parentRunId) === true\n ) {\n const hookResult = await executeHooks({\n registry: this.hookRegistry,\n input: {\n hook_event_name: 'SubagentStart',\n runId: this.parentRunId,\n threadId,\n parentAgentId: this.parentAgentId,\n agentId: childAgentId,\n agentType: subagentType,\n inputs: [new HumanMessage(description)],\n },\n sessionId: this.parentRunId,\n matchQuery: subagentType,\n }).catch((): AggregatedHookResult => HOOK_FALLBACK);\n\n /**\n * `ask` is treated identically to `deny` in the subagent context:\n * subagents are non-interactive, so there is no prompt path for `ask`.\n * Both decisions block execution and return a \"Blocked\" tool result.\n */\n if (hookResult.decision === 'deny' || hookResult.decision === 'ask') {\n return {\n content: `Blocked: ${hookResult.reason ?? 'Blocked by hook'}`,\n messages: [],\n };\n }\n }\n\n const parentRegistry = this.getParentHandlerRegistry();\n const forwardingEnabled = parentRegistry != null;\n /**\n * Keep `toolDefinitions` only when the host has actually wired an\n * `ON_TOOL_EXECUTE` handler. `Run` always constructs a `HandlerRegistry`,\n * so treating any registry as \"forwarding enabled\" would leak\n * `toolDefinitions` into children whose hosts cannot execute them — the\n * child's `ToolNode` batch promise would hang forever with no handler to\n * resolve/reject. Gating on the tool-execute handler preserves the\n * recoverable \"no tools\" path for registry-but-no-handler configs.\n */\n const hasToolExecuteHandler =\n parentRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) != null;\n const childInputs = buildChildInputs(\n config,\n childAgentId,\n this.maxDepth,\n /* keepToolDefinitions */ hasToolExecuteHandler\n );\n const childRunId = `${this.parentRunId}_sub_${nanoid(8)}`;\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n\n const hostUsageSink = this.usageSink;\n const childGraph = this.createChildGraph({\n runId: childRunId,\n signal: this.parentSignal,\n agents: [childInputs],\n langfuse: this.langfuse,\n tokenCounter: this.tokenCounter,\n /**\n * Forwarded so the child graph's own `SubagentExecutor` (created in\n * its `createAgentNode` when `allowNested` keeps subagentConfigs)\n * reports nested-child usage through the same host sink. Each nesting\n * level attaches its own capture callback — `workflow.invoke` replaces\n * the inherited callback chain, so a single top-level handler would\n * never see grandchild model calls.\n *\n * The wrapper rewrites `runId` to THIS executor's parent run: nested\n * executors emit with their own `parentRunId` (a `*_sub_*` child id),\n * and each wrapper layer rewrites upward, so by the time an event\n * reaches the host sink its `runId` is the ROOT run — hosts keying\n * billing by run id never see intermediate child run ids there\n * (`subagentRunId` still identifies the emitting child).\n */\n subagentUsageSink:\n hostUsageSink == null\n ? undefined\n : /** Returns the host sink's result so async sinks stay awaited\n * through every wrapper layer. */\n (event): void | Promise<void> =>\n hostUsageSink({ ...event, runId: this.parentRunId }),\n });\n\n let forwarding: ForwarderCallback | undefined;\n if (forwardingEnabled) {\n forwarding = this.createForwarderCallback({\n parentRegistry: parentRegistry!,\n subagentType,\n subagentAgentId: childAgentId,\n childRunId,\n parentToolCallId,\n });\n }\n const forwarder = forwarding?.handler;\n\n if (forwarder) {\n await this.emitSubagentUpdate(parentRegistry!, {\n childRunId,\n subagentType,\n subagentAgentId: childAgentId,\n parentToolCallId,\n phase: 'start',\n label: `Subagent \"${subagentType}\" started`,\n });\n }\n\n let result: { messages: BaseMessage[] };\n try {\n const workflow = childGraph.createWorkflow();\n /**\n * When `parentHandlerRegistry` is provided (forwarding mode), attach a\n * lightweight callback that intercepts the child's `on_custom_event`\n * dispatches and routes them to the parent's registry — either as\n * operational events (ON_TOOL_EXECUTE) or wrapped ON_SUBAGENT_UPDATE\n * envelopes. Native LangChain streaming events (on_chat_model_stream,\n * etc.) still do NOT propagate to the parent's outer streamEvents\n * iterator — the `callbacks` array REPLACES the inherited chain, so\n * parent handlers won't receive child stream chunks and raise \"No\n * agent context found\" lookups on the parent's agentContexts map.\n *\n * When no registry is provided (legacy isolation), `callbacks: []`\n * fully detaches the child.\n *\n * `runName` gives the child a distinct LangSmith trace root (avoids\n * nested trace pollution).\n */\n const callbackHandlers: BaseCallbackHandler[] = [];\n if (forwarder) {\n callbackHandlers.push(forwarder);\n }\n /**\n * Usage capture rides the same detached callbacks array. Because\n * `callbacks` REPLACES the inherited chain (see above), the host's\n * `CHAT_MODEL_END` handler never observes the child's model calls —\n * this handler is the child-side equivalent of `ModelEndHandler`,\n * reporting per-call usage to the host's sink for billing.\n */\n if (this.usageSink) {\n callbackHandlers.push(\n createUsageCaptureHandler({\n sink: this.usageSink,\n subagentType,\n subagentRunId: childRunId,\n subagentAgentId: childAgentId,\n parentRunId: this.parentRunId,\n provider: config.agentInputs.provider,\n fallbackModel: extractConfiguredModel(config.agentInputs),\n })\n );\n }\n const callbacks: Callbacks = callbackHandlers;\n /**\n * Inherit the parent's host `configurable` — host-set fields\n * (`requestBody`, `user`, `userMCPAuthMap`, etc.) AND the run-\n * identity fields (`run_id`, `parent_run_id`, `thread_id`) all\n * propagate. LangGraph's own runtime keys are excluded because the\n * child graph creates its own scratchpad/checkpoint/abort plumbing.\n *\n * Run-identity propagation is intentional and matches the\n * convention this executor itself already uses for `SubagentStart`\n * / `SubagentStop` hooks (`sessionId: this.parentRunId`): the\n * subagent runs under the parent's session scope, not its own.\n * Forwarding `run_id` / `parent_run_id` / `thread_id` makes\n * `ToolNode`'s hook lookups (`hasHookFor(eventName, runId)`),\n * `ToolOutputReferenceRegistry` keying, and trace lineage all\n * resolve to the parent's session for tools dispatched from the\n * subagent — so `PreToolUse` / `PostToolUse` hooks the host\n * registered against the parent's run fire for subagent tool\n * calls too. \"Same run\" matches the user-perceptual mental model.\n *\n * `thread_id` falls back to `childRunId` only when the parent\n * didn't supply one (legacy behavior preserved for hosts that\n * never set thread_id).\n *\n * NOTE: a future revision will likely make this configurable per\n * spawn type — e.g. a background / async subagent that runs after\n * the parent's run completes wants isolation, not inheritance.\n * For now the inheritance path matches LibreChat's primary use\n * case (synchronous subagents within a single user turn).\n */\n const inheritedConfigurable: Record<string, unknown> =\n sanitizeChildConfigurable(params.parentConfigurable);\n result = await workflow.invoke(\n { messages: [new HumanMessage(description)] },\n {\n recursionLimit: maxTurns * RECURSION_MULTIPLIER,\n signal: this.parentSignal,\n callbacks,\n runName: `subagent:${subagentType}`,\n configurable: {\n thread_id: childRunId,\n ...inheritedConfigurable,\n },\n }\n );\n } catch (error) {\n const errorMessage = truncateErrorMessage(error);\n if (forwarding) {\n await forwarding.drain();\n await this.emitSubagentUpdate(parentRegistry!, {\n childRunId,\n subagentType,\n subagentAgentId: childAgentId,\n parentToolCallId,\n phase: 'error',\n label: `Subagent \"${subagentType}\" errored: ${errorMessage}`,\n data: { message: errorMessage },\n });\n }\n childGraph.clearHeavyState();\n return {\n content: `Subagent error: ${errorMessage}`,\n messages: [],\n };\n }\n\n const filteredContent = filterSubagentResult(result.messages);\n\n if (\n this.hookRegistry?.hasHookFor('SubagentStop', this.parentRunId) === true\n ) {\n /**\n * Awaited (not fire-and-forget) for deterministic test synchronization\n * and consistency with PostCompact. The parent is already waiting on the\n * tool result, so the small extra latency is acceptable. Errors are\n * swallowed — SubagentStop is observational.\n */\n await executeHooks({\n registry: this.hookRegistry,\n input: {\n hook_event_name: 'SubagentStop',\n runId: this.parentRunId,\n threadId,\n agentId: childAgentId,\n agentType: subagentType,\n messages: result.messages,\n },\n sessionId: this.parentRunId,\n matchQuery: subagentType,\n }).catch(() => {\n /* SubagentStop is observational — swallow errors */\n });\n }\n\n if (forwarding) {\n await forwarding.drain();\n await this.emitSubagentUpdate(parentRegistry!, {\n childRunId,\n subagentType,\n subagentAgentId: childAgentId,\n parentToolCallId,\n phase: 'stop',\n label: `Subagent \"${subagentType}\" finished`,\n });\n }\n\n childGraph.clearHeavyState();\n\n return { content: filteredContent, messages: result.messages };\n }\n\n /**\n * Emits a single {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope through the\n * parent's handler registry. Silent no-op when no parent registry is set.\n * Errors are swallowed — update events are observational.\n */\n private async emitSubagentUpdate(\n parentRegistry: HandlerRegistry,\n args: {\n childRunId: string;\n subagentType: string;\n subagentAgentId: string;\n parentToolCallId?: string;\n phase: SubagentUpdatePhase;\n data?: unknown;\n label?: string;\n }\n ): Promise<void> {\n const handler = parentRegistry.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);\n if (!handler) {\n return;\n }\n const event: SubagentUpdateEvent = {\n runId: this.parentRunId,\n subagentRunId: args.childRunId,\n subagentType: args.subagentType,\n subagentAgentId: args.subagentAgentId,\n parentAgentId: this.parentAgentId,\n parentToolCallId: args.parentToolCallId,\n phase: args.phase,\n data: args.data,\n label: args.label,\n timestamp: new Date().toISOString(),\n };\n try {\n await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);\n } catch {\n /* observational — swallow */\n }\n }\n\n /**\n * Builds a BaseCallbackHandler that intercepts the child graph's custom\n * events. Routing rules:\n * - `ON_TOOL_EXECUTE` → forwarded as-is to the parent's ON_TOOL_EXECUTE\n * handler (so event-driven tools work identically for child and parent).\n * - `ON_RUN_STEP` / `ON_RUN_STEP_DELTA` / `ON_RUN_STEP_COMPLETED` /\n * `ON_MESSAGE_DELTA` / `ON_REASONING_DELTA` → wrapped in a\n * {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope with a human-readable\n * label, delivered to the parent's subagent-update handler.\n * - Everything else → ignored (keeps parent's UI scoped to the events it\n * cares about; host apps can extend by registering more phases).\n */\n private createForwarderCallback(args: {\n parentRegistry: HandlerRegistry;\n subagentType: string;\n subagentAgentId: string;\n childRunId: string;\n parentToolCallId?: string;\n }): ForwarderCallback {\n const {\n parentRegistry,\n subagentType,\n subagentAgentId,\n childRunId,\n parentToolCallId,\n } = args;\n const parentRunId = this.parentRunId;\n const parentAgentId = this.parentAgentId;\n\n const wrap = async (\n eventName: string,\n phase: SubagentUpdatePhase,\n data: unknown\n ): Promise<void> => {\n const handler = parentRegistry.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);\n if (!handler) {\n return;\n }\n try {\n const event: SubagentUpdateEvent = {\n runId: parentRunId,\n subagentRunId: childRunId,\n subagentType,\n subagentAgentId,\n parentAgentId,\n parentToolCallId,\n phase,\n data: sanitizeForwardedSubagentUpdateData(eventName, data),\n label: summarizeEvent(eventName, data),\n timestamp: new Date().toISOString(),\n };\n await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);\n } catch {\n /* observational — swallow */\n }\n };\n\n const queuedUpdates: QueuedSubagentUpdate[] = [];\n let drainPromise: Promise<void> | undefined;\n\n const enqueue = (update: QueuedSubagentUpdate): void => {\n if (queuedUpdates.length >= MAX_PENDING_SUBAGENT_UPDATES) {\n const dropIndex = queuedUpdates.findIndex((queued) =>\n isDroppableSubagentUpdatePhase(queued.phase)\n );\n if (dropIndex >= 0) {\n queuedUpdates.splice(dropIndex, 1);\n } else if (isDroppableSubagentUpdatePhase(update.phase)) {\n return;\n }\n }\n queuedUpdates.push(update);\n };\n\n const drain = async (): Promise<void> => {\n if (drainPromise != null) {\n await drainPromise;\n return;\n }\n drainPromise = (async (): Promise<void> => {\n while (queuedUpdates.length > 0) {\n const update = queuedUpdates.shift();\n if (update == null) {\n continue;\n }\n await wrap(update.eventName, update.phase, update.data);\n }\n })();\n try {\n await drainPromise;\n } finally {\n drainPromise = undefined;\n if (queuedUpdates.length > 0) {\n await drain();\n }\n }\n };\n\n const scheduleWrap = (\n eventName: string,\n phase: SubagentUpdatePhase,\n data: unknown\n ): void => {\n enqueue({ eventName, phase, data });\n void drain();\n };\n\n const handler = BaseCallbackHandler.fromMethods({\n [Callback.CUSTOM_EVENT]: async (\n eventName: string,\n data: unknown\n ): Promise<void> => {\n if (eventName === GraphEvents.ON_TOOL_EXECUTE) {\n const toolHandler = parentRegistry.getHandler(\n GraphEvents.ON_TOOL_EXECUTE\n );\n if (toolHandler) {\n await toolHandler.handle(\n GraphEvents.ON_TOOL_EXECUTE,\n data as ToolExecuteBatchRequest\n );\n }\n /**\n * We also surface a short notice in the subagent-update stream so\n * the UI can show \"calling <tool>\" for each tool the child spawns.\n */\n scheduleWrap(eventName, 'run_step', data);\n return;\n }\n\n if (eventName === GraphEvents.ON_RUN_STEP) {\n scheduleWrap(eventName, 'run_step', data);\n return;\n }\n if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {\n scheduleWrap(eventName, 'run_step_delta', data);\n return;\n }\n if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {\n scheduleWrap(eventName, 'run_step_completed', data);\n return;\n }\n if (eventName === GraphEvents.ON_MESSAGE_DELTA) {\n scheduleWrap(eventName, 'message_delta', data);\n return;\n }\n if (eventName === GraphEvents.ON_REASONING_DELTA) {\n scheduleWrap(eventName, 'reasoning_delta', data);\n return;\n }\n },\n });\n /**\n * `awaitHandlers = true` is required so the child's `ToolNode` actually\n * blocks on the parent's `ON_TOOL_EXECUTE` handler until it resolves\n * the batch request. Observational `ON_SUBAGENT_UPDATE` calls are queued\n * behind a bounded sequential dispatcher so host UI publication cannot\n * backpressure each child emission or run unbounded concurrent publishes.\n * The executor drains this queue before terminal stop/error envelopes to\n * preserve phase ordering.\n */\n handler.awaitHandlers = true;\n return { handler, drain };\n }\n}\n\n/**\n * Builds the child-run equivalent of a host `CHAT_MODEL_END` handler: a\n * callback that joins per-call model identity (captured from\n * `ls_model_name` at chat-model start) with the usage metadata reported at\n * LLM end, and emits a {@link SubagentUsageEvent} through the host's sink.\n *\n * Attached to the child `workflow.invoke` callbacks array, so it observes\n * every model call inside the child graph — the agent loop and any\n * auxiliary calls (e.g. child-side summarization). It does NOT observe\n * deeper subagent levels: each nesting level replaces the callback chain\n * and attaches its own capture handler via the forwarded\n * `subagentUsageSink` on the child graph's input.\n */\nfunction createUsageCaptureHandler(args: {\n sink: SubagentUsageSink;\n subagentType: string;\n subagentRunId: string;\n subagentAgentId: string;\n parentRunId: string;\n /**\n * Child config's provider enum — the default tag when a call carries no\n * `INVOKED_PROVIDER` metadata (hosts key pricing/cache semantics off it).\n */\n provider?: string;\n /**\n * Child config's model, used when a call carries neither `ls_model_name`\n * nor `INVOKED_MODEL` metadata.\n */\n fallbackModel?: string;\n}): BaseCallbackHandler {\n const {\n sink,\n subagentType,\n subagentRunId,\n subagentAgentId,\n parentRunId,\n provider,\n fallbackModel,\n } = args;\n /**\n * Per-call attribution keyed by LangChain callback runId. `model` joins\n * `ls_model_name` (provider-reported) with `INVOKED_MODEL` (stamped by\n * `tryFallbackProviders` from the fallback's client options); `provider`\n * is `INVOKED_PROVIDER`, stamped by `attemptInvoke` with the SDK enum of\n * the provider that ACTUALLY served the call — correct for\n * fallback-served calls, where the static config provider would mis-tag\n * pricing/cache semantics.\n */\n const callInfoByCallId = new Map<\n string,\n { model?: string; provider?: string }\n >();\n const handler = BaseCallbackHandler.fromMethods({\n handleChatModelStart: (\n _llm: unknown,\n _messages: unknown,\n runId: string,\n _parentRunId?: string,\n _extraParams?: Record<string, unknown>,\n _tags?: string[],\n metadata?: Record<string, unknown>\n ): void => {\n const callModel =\n asNonEmptyString(metadata?.ls_model_name) ??\n asNonEmptyString(metadata?.[Constants.INVOKED_MODEL]);\n const callProvider = asNonEmptyString(\n metadata?.[Constants.INVOKED_PROVIDER]\n );\n if (callModel != null || callProvider != null) {\n callInfoByCallId.set(runId, {\n model: callModel,\n provider: callProvider,\n });\n }\n },\n handleLLMEnd: async (output: LLMResult, runId: string): Promise<void> => {\n const callInfo = callInfoByCallId.get(runId);\n callInfoByCallId.delete(runId);\n const model = callInfo?.model ?? fallbackModel;\n const callProvider = callInfo?.provider ?? provider;\n for (const generationGroup of output.generations) {\n /**\n * At most ONE event per generation group: each group is one\n * provider request (the outer array is per-prompt for batched\n * calls), and with multiple completions (`n > 1`) every choice in\n * a group repeats the request-level `usage_metadata` — emitting\n * per choice would multiply billed tokens.\n */\n for (const generation of generationGroup) {\n const message = (generation as ChatGeneration | undefined)?.message;\n const usage = (\n message as { usage_metadata?: UsageMetadata } | undefined\n )?.usage_metadata;\n if (usage == null) {\n continue;\n }\n /**\n * Awaited so async host sinks (billing/persistence) complete\n * before the model call resolves — `awaitHandlers` only waits on\n * `handleLLMEnd` itself, so a dropped promise here would let the\n * parent run finish before usage is recorded and would turn sink\n * rejections into unhandled rejections.\n */\n try {\n await sink({\n usage,\n model,\n provider: callProvider,\n subagentType,\n subagentRunId,\n subagentAgentId,\n runId: parentRunId,\n });\n } catch {\n /* observational — a throwing/rejecting host sink must not break the child run */\n }\n break;\n }\n }\n },\n handleLLMError: (_err: unknown, runId: string): void => {\n callInfoByCallId.delete(runId);\n },\n });\n /**\n * Dispatch usage synchronously with each model call so all entries are\n * sunk before `workflow.invoke` resolves — hosts read their accumulator\n * right after the parent run completes.\n */\n handler.awaitHandlers = true;\n return handler;\n}\n\nfunction asNonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\n/**\n * Best-effort read of the configured model from a subagent's client\n * options. Providers disagree on the key (`model` vs `modelName`), and the\n * value is only a fallback for calls that carry no `ls_model_name`.\n */\nfunction extractConfiguredModel(agentInputs: AgentInputs): string | undefined {\n const clientOptions = agentInputs.clientOptions as\n | { model?: unknown; modelName?: unknown }\n | undefined;\n if (typeof clientOptions?.model === 'string' && clientOptions.model !== '') {\n return clientOptions.model;\n }\n if (\n typeof clientOptions?.modelName === 'string' &&\n clientOptions.modelName !== ''\n ) {\n return clientOptions.modelName;\n }\n return undefined;\n}\n\nfunction sanitizeChildConfigurable(\n parentConfigurable: Record<string, unknown> | undefined\n): Record<string, unknown> {\n if (parentConfigurable == null) {\n return {};\n }\n return Object.fromEntries(\n Object.entries(parentConfigurable).filter(\n ([key]) => !isLangGraphRuntimeConfigKey(key)\n )\n );\n}\n\nfunction isLangGraphRuntimeConfigKey(key: string): boolean {\n return (\n key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) ||\n LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key)\n );\n}\n\nexport function sanitizeForwardedSubagentUpdateData(\n eventName: string,\n data: unknown\n): unknown {\n if (eventName === GraphEvents.ON_TOOL_EXECUTE) {\n return sanitizeToolExecuteUpdateData(data);\n }\n if (eventName === GraphEvents.ON_RUN_STEP) {\n return sanitizeRunStepUpdateData(data);\n }\n if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {\n return sanitizeRunStepDeltaUpdateData(data);\n }\n if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {\n return sanitizeRunStepCompletedUpdateData(data);\n }\n if (eventName === GraphEvents.ON_MESSAGE_DELTA) {\n return sanitizeMessageDeltaUpdateData(data);\n }\n if (eventName === GraphEvents.ON_REASONING_DELTA) {\n return sanitizeReasoningDeltaUpdateData(data);\n }\n return undefined;\n}\n\nfunction isDroppableSubagentUpdatePhase(phase: SubagentUpdatePhase): boolean {\n return (\n phase === 'message_delta' ||\n phase === 'reasoning_delta' ||\n phase === 'run_step_delta'\n );\n}\n\nfunction sanitizeToolExecuteUpdateData(\n data: unknown\n): SanitizedSubagentToolExecuteData {\n const request = data as Partial<ToolExecuteBatchRequest>;\n const toolCalls = Array.isArray(request.toolCalls)\n ? request.toolCalls.map(sanitizeToolCallForUpdate)\n : [];\n const sanitized: SanitizedSubagentToolExecuteData = { toolCalls };\n if (typeof request.agentId === 'string') {\n sanitized.agentId = request.agentId;\n }\n return sanitized;\n}\n\nfunction sanitizeToolCallForUpdate(\n call: ToolExecuteBatchRequest['toolCalls'][number]\n): SanitizedSubagentToolCall {\n const sanitized: SanitizedSubagentToolCall = {\n id: call.id,\n name: call.name,\n args: call.args,\n };\n return sanitized;\n}\n\nfunction sanitizeRunStepUpdateData(\n data: unknown\n): SanitizedRunStep | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const step = data as Partial<RunStep>;\n const sanitized: SanitizedRunStep = {};\n assignString(sanitized, 'agentId', step.agentId);\n assignNumber(sanitized, 'groupId', step.groupId);\n assignString(sanitized, 'id', step.id);\n assignNumber(sanitized, 'index', step.index);\n assignString(sanitized, 'runId', step.runId);\n assignNumber(sanitized, 'stepIndex', step.stepIndex);\n assignString(sanitized, 'type', step.type);\n if (step.summary !== undefined) {\n sanitized.summary = step.summary;\n }\n if (step.usage !== undefined) {\n sanitized.usage = step.usage;\n }\n sanitized.stepDetails = sanitizeStepDetails(step.stepDetails);\n return sanitized;\n}\n\nfunction sanitizeRunStepDeltaUpdateData(\n data: unknown\n): SanitizedRunStepDelta | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const event = data as Partial<RunStepDeltaEvent>;\n const sanitized: SanitizedRunStepDelta = {};\n assignString(sanitized, 'id', event.id);\n sanitized.delta = sanitizeToolCallDelta(event.delta);\n return sanitized;\n}\n\nfunction sanitizeRunStepCompletedUpdateData(\n data: unknown\n): SanitizedRunStepCompleted | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const event = data as { result?: unknown };\n return { result: sanitizeStepCompleted(event.result) };\n}\n\nfunction sanitizeMessageDeltaUpdateData(\n data: unknown\n): SanitizedMessageDelta | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const event = data as Partial<MessageDeltaEvent>;\n const sanitized: SanitizedMessageDelta = {};\n assignString(sanitized, 'id', event.id);\n if (event.delta != null) {\n sanitized.delta = {};\n if (event.delta.content !== undefined) {\n sanitized.delta.content = event.delta.content;\n }\n if (event.delta.tool_call_ids !== undefined) {\n sanitized.delta.tool_call_ids = event.delta.tool_call_ids;\n }\n }\n return sanitized;\n}\n\nfunction sanitizeReasoningDeltaUpdateData(\n data: unknown\n): SanitizedReasoningDelta | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const event = data as Partial<ReasoningDeltaEvent>;\n const sanitized: SanitizedReasoningDelta = {};\n assignString(sanitized, 'id', event.id);\n if (event.delta?.content !== undefined) {\n sanitized.delta = { content: event.delta.content };\n }\n return sanitized;\n}\n\nfunction sanitizeStepDetails(\n stepDetails: unknown\n): SanitizedStepDetails | undefined {\n if (!isObjectLike(stepDetails)) {\n return undefined;\n }\n const rawDetails = stepDetails as {\n message_creation?: { message_id?: unknown };\n tool_calls?: unknown[];\n type?: unknown;\n };\n if (rawDetails.type === StepTypes.MESSAGE_CREATION) {\n const sanitized: SanitizedStepDetails = {\n type: StepTypes.MESSAGE_CREATION,\n };\n const messageId = rawDetails.message_creation?.message_id;\n if (typeof messageId === 'string') {\n sanitized.message_creation = { message_id: messageId };\n }\n return sanitized;\n }\n if (rawDetails.type === StepTypes.TOOL_CALLS) {\n const sanitized: SanitizedStepDetails = {\n type: StepTypes.TOOL_CALLS,\n };\n if (Array.isArray(rawDetails.tool_calls)) {\n sanitized.tool_calls = rawDetails.tool_calls.map(sanitizeAgentToolCall);\n }\n return sanitized;\n }\n return undefined;\n}\n\nfunction sanitizeToolCallDelta(\n delta: ToolCallDelta | undefined\n): SanitizedToolCallDelta | undefined {\n if (!isObjectLike(delta)) {\n return undefined;\n }\n const sanitized: SanitizedToolCallDelta = {};\n assignString(sanitized, 'auth', delta.auth);\n assignNumber(sanitized, 'expires_at', delta.expires_at);\n assignString(sanitized, 'type', delta.type);\n if (delta.summary !== undefined) {\n sanitized.summary = delta.summary;\n }\n if (Array.isArray(delta.tool_calls)) {\n sanitized.tool_calls = delta.tool_calls.map(sanitizeAgentToolCall);\n }\n return sanitized;\n}\n\nfunction sanitizeStepCompleted(\n data: unknown\n): SanitizedStepCompleted | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const completed = data as Partial<StepCompleted> & {\n id?: unknown;\n index?: unknown;\n tool_call?: unknown;\n };\n if (completed.type === 'summary') {\n return {\n type: 'summary',\n summary: completed.summary,\n };\n }\n if (completed.type !== 'tool_call') {\n return undefined;\n }\n const sanitized: SanitizedStepCompleted = { type: 'tool_call' };\n assignString(sanitized, 'id', completed.id);\n assignNumber(sanitized, 'index', completed.index);\n sanitized.tool_call = sanitizeProcessedToolCall(completed.tool_call);\n return sanitized;\n}\n\nfunction sanitizeProcessedToolCall(\n toolCall: unknown\n): SanitizedProcessedToolCall | undefined {\n if (!isObjectLike(toolCall)) {\n return undefined;\n }\n const call = toolCall as Partial<ProcessedToolCall>;\n const sanitized: SanitizedProcessedToolCall = {};\n assignString(sanitized, 'id', call.id);\n assignString(sanitized, 'name', call.name);\n if (call.args !== undefined) {\n sanitized.args = call.args;\n }\n assignString(sanitized, 'output', call.output);\n assignNumber(sanitized, 'progress', call.progress);\n return sanitized;\n}\n\nfunction sanitizeAgentToolCall(toolCall: unknown): SanitizedAgentToolCall {\n if (!isObjectLike(toolCall)) {\n return {};\n }\n const call = toolCall as SanitizedAgentToolCall;\n const sanitized: SanitizedAgentToolCall = {};\n assignString(sanitized, 'id', call.id);\n assignString(sanitized, 'name', call.name);\n assignString(sanitized, 'type', call.type);\n if (call.args !== undefined) {\n sanitized.args = call.args;\n }\n if (isObjectLike(call.function)) {\n const fn: SanitizedAgentToolCall['function'] = {};\n assignString(fn, 'name', call.function.name);\n if (\n typeof call.function.arguments === 'string' ||\n isObjectLike(call.function.arguments)\n ) {\n fn.arguments = call.function.arguments;\n }\n sanitized.function = fn;\n }\n return sanitized;\n}\n\nfunction isObjectLike(value: unknown): value is object {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assignString<T extends object, K extends keyof T>(\n target: T,\n key: K,\n value: unknown\n): void {\n if (typeof value === 'string') {\n target[key] = value as T[K];\n }\n}\n\nfunction assignNumber<T extends object, K extends keyof T>(\n target: T,\n key: K,\n value: unknown\n): void {\n if (typeof value === 'number') {\n target[key] = value as T[K];\n }\n}\n\n/**\n * Produces a short single-line label for an arbitrary forwarded child event.\n * Used to populate {@link SubagentUpdateEvent.label} so the host UI can show\n * a compact status ticker without parsing the raw payload.\n */\nexport function summarizeEvent(eventName: string, data: unknown): string {\n if (eventName === GraphEvents.ON_TOOL_EXECUTE) {\n const req = data as { toolCalls?: Array<{ name?: string }> };\n const names = (req.toolCalls ?? [])\n .map((c) => c.name)\n .filter((n): n is string => typeof n === 'string');\n return names.length > 0 ? `Calling ${names.join(', ')}` : 'Calling tool';\n }\n if (eventName === GraphEvents.ON_RUN_STEP) {\n const step = data as {\n type?: string;\n stepDetails?: { type?: string; tool_calls?: Array<{ name?: string }> };\n };\n const detailType = step.stepDetails?.type ?? step.type ?? 'step';\n if (detailType === 'tool_calls') {\n const names = (step.stepDetails?.tool_calls ?? [])\n .map((c) => c.name)\n .filter((n): n is string => typeof n === 'string');\n return names.length > 0\n ? `Using tool: ${names.join(', ')}`\n : 'Planning tool call';\n }\n if (detailType === 'message_creation') {\n return 'Thinking…';\n }\n return `Step: ${detailType}`;\n }\n if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {\n const step = data as {\n result?: {\n type?: string;\n tool_call?: { name?: string; output?: string };\n };\n };\n const tool = step.result?.tool_call;\n if (tool?.name != null && tool.name !== '') {\n return `Tool ${tool.name} complete`;\n }\n return 'Step complete';\n }\n if (eventName === GraphEvents.ON_MESSAGE_DELTA) {\n return 'Streaming…';\n }\n return eventName;\n}\n\n/**\n * Walk messages from last to first, returning the text content of the most\n * recent AIMessage that has any. Non-text blocks (tool_use, thinking,\n * redacted_thinking, tool_result) are stripped. If the last AIMessage is\n * pure tool_use (e.g. the subagent hit `maxTurns` mid-tool-call), the walk\n * continues to earlier AIMessages so partial progress is salvaged — this\n * matches Claude Code's behavior in `agentToolUtils.finalizeAgentTool`.\n * Returns \"Task completed\" only when no AIMessage in the history contains\n * any text.\n */\nexport function filterSubagentResult(messages: BaseMessage[]): string {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i]._getType() !== 'ai') {\n continue;\n }\n\n const content = messages[i].content;\n\n if (typeof content === 'string') {\n if (content) return content;\n continue;\n }\n\n if (!Array.isArray(content)) {\n continue;\n }\n\n const textParts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') {\n textParts.push(block);\n } else if ('type' in block && block.type === 'text' && 'text' in block) {\n textParts.push(block.text as string);\n }\n }\n\n if (textParts.length > 0) {\n return textParts.join('\\n');\n }\n }\n\n return 'Task completed';\n}\n\n/**\n * Resolve self-spawn configs by filling in agentInputs from the parent context.\n * Returns configs with agentInputs guaranteed present. Throws on duplicate\n * `type` values to prevent silent config shadowing.\n */\nexport function resolveSubagentConfigs(\n configs: SubagentConfig[],\n parentContext: AgentContext\n): ResolvedSubagentConfig[] {\n const resolved = configs\n .map((config) => {\n if (config.agentInputs != null) {\n return config as ResolvedSubagentConfig;\n }\n if (config.self !== true || parentContext._sourceInputs == null) {\n return null;\n }\n return {\n ...config,\n agentInputs: { ...parentContext._sourceInputs },\n } as ResolvedSubagentConfig;\n })\n .filter((c): c is ResolvedSubagentConfig => c != null);\n\n const seenTypes = new Set<string>();\n for (const config of resolved) {\n if (seenTypes.has(config.type)) {\n throw new Error(\n `Duplicate subagent type \"${config.type}\". Each SubagentConfig must have a unique \"type\" field.`\n );\n }\n seenTypes.add(config.type);\n }\n\n return resolved;\n}\n\n/**\n * Build child AgentInputs from a resolved config, stripping nesting and\n * (optionally) event-driven fields. When `allowNested: true`, the child's\n * `maxSubagentDepth` is decremented so that depth is consumed as the call\n * chain deepens across graph boundaries — the parent's executor-level check\n * alone cannot see into the child graph's separate executor.\n *\n * When `keepToolDefinitions` is `true`, the child retains the parent's\n * `toolDefinitions` so event-driven tools remain usable. This is only safe\n * when the caller has wired a forwarder for `ON_TOOL_EXECUTE` to a\n * registered handler — otherwise the child will hang on tool dispatch.\n *\n * @remarks Advanced utility: exported primarily for testing and by\n * {@link SubagentExecutor}. Host applications configuring subagents should\n * not need to call this directly — it is invoked internally when a subagent\n * tool is dispatched. The depth-countdown contract (parent's `maxDepth` in,\n * child's decremented `maxSubagentDepth` on the returned inputs) is the\n * mechanism that bounds nesting across graph boundaries; callers must\n * respect it.\n */\nexport function buildChildInputs(\n config: ResolvedSubagentConfig,\n childAgentId: string,\n parentMaxDepth: number,\n keepToolDefinitions: boolean = false\n): AgentInputs {\n const { agentInputs } = config;\n const childInputs: AgentInputs = {\n ...agentInputs,\n agentId: childAgentId,\n toolDefinitions: keepToolDefinitions\n ? agentInputs.toolDefinitions\n : undefined,\n /**\n * Subagents run in an isolated context by contract. Parent-run-scoped\n * fields that would otherwise survive the shallow-spread clone — the\n * cross-run conversation summary and the prior-turn tool-discovery\n * set — are cleared here so the child starts fresh. Host applications\n * that want a subagent to see parent context must thread it in\n * explicitly (e.g. via the `description` argument to the subagent\n * tool), not via inherited state.\n */\n initialSummary: undefined,\n discoveredTools: undefined,\n /**\n * Host-supplied direct tools are scrubbed from INHERITED configs only.\n * A self-spawn config's `agentInputs` is a shallow spread of the parent's\n * `_sourceInputs`, so without this a parent-scoped graph tool (e.g. an\n * interrupt-raising ask_user_question, which needs the parent's\n * checkpointer — child graphs compile without one) would silently leak\n * into the child and deterministically throw `No checkpointer set`. An\n * EXPLICIT child config that lists its own `graphTools` is a deliberate\n * host choice and keeps them (Codex #289 P2).\n */\n graphTools: config.self === true ? undefined : agentInputs.graphTools,\n };\n\n if (config.allowNested === true) {\n childInputs.maxSubagentDepth = Math.max(0, parentMaxDepth - 1);\n } else {\n childInputs.subagentConfigs = undefined;\n childInputs.maxSubagentDepth = undefined;\n }\n\n return childInputs;\n}\n\nfunction truncateErrorMessage(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (message.length <= ERROR_MESSAGE_MAX_CHARS) {\n return message;\n }\n return `${message.slice(0, ERROR_MESSAGE_MAX_CHARS)}...`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SubagentExecutor.mjs","names":["event","req","step"],"sources":["../../../../src/tools/subagent/SubagentExecutor.ts"],"sourcesContent":["import { nanoid } from 'nanoid';\nimport { HumanMessage } from '@langchain/core/messages';\nimport { BaseCallbackHandler } from '@langchain/core/callbacks/base';\nimport type { BaseMessage, UsageMetadata } from '@langchain/core/messages';\nimport type { ChatGeneration, LLMResult } from '@langchain/core/outputs';\nimport type { Callbacks } from '@langchain/core/callbacks/manager';\nimport type {\n AgentInputs,\n InjectedMessage,\n MessageDeltaEvent,\n ProcessedToolCall,\n ReasoningDeltaEvent,\n RunStep,\n RunStepDeltaEvent,\n StandardGraphInput,\n ResolvedSubagentConfig,\n StepCompleted,\n SubagentConfig,\n SubagentUpdateEvent,\n SubagentUpdatePhase,\n SubagentUsageSink,\n ToolExecuteBatchRequest,\n ToolCallDelta,\n TokenCounter,\n} from '@/types';\nimport type { AggregatedHookResult, HookRegistry } from '@/hooks';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type { StandardGraph } from '@/graphs/Graph';\nimport type { HandlerRegistry } from '@/events';\nimport { Constants, GraphEvents, Callback, StepTypes } from '@/common';\nimport { executeHooks } from '@/hooks';\n\nconst DEFAULT_MAX_TURNS = 25;\nconst RECURSION_MULTIPLIER = 3;\nconst ERROR_MESSAGE_MAX_CHARS = 200;\nconst MAX_PENDING_SUBAGENT_UPDATES = 64;\n\nconst HOOK_FALLBACK: AggregatedHookResult = Object.freeze({\n additionalContexts: [] as string[],\n injectedMessages: [] as InjectedMessage[],\n errors: [] as string[],\n});\n\ntype SanitizedSubagentToolCall = {\n id: string;\n name: string;\n args?: ToolExecuteBatchRequest['toolCalls'][number]['args'];\n};\n\ntype SanitizedSubagentToolExecuteData = {\n toolCalls: SanitizedSubagentToolCall[];\n agentId?: string;\n};\n\ntype SanitizedRunStep = Partial<\n Pick<\n RunStep,\n | 'agentId'\n | 'groupId'\n | 'id'\n | 'index'\n | 'runId'\n | 'stepIndex'\n | 'summary'\n | 'type'\n | 'usage'\n >\n> & {\n stepDetails?: SanitizedStepDetails;\n};\n\ntype SanitizedStepDetails =\n | {\n type: StepTypes.MESSAGE_CREATION;\n message_creation?: {\n message_id?: string;\n };\n }\n | {\n type: StepTypes.TOOL_CALLS;\n tool_calls?: SanitizedAgentToolCall[];\n };\n\ntype SanitizedAgentToolCall = {\n id?: string;\n name?: string;\n args?: string | object;\n type?: string;\n function?: {\n name?: string;\n arguments?: string | object;\n };\n};\n\ntype SanitizedRunStepDelta = Partial<Pick<RunStepDeltaEvent, 'id'>> & {\n delta?: SanitizedToolCallDelta;\n};\n\ntype SanitizedToolCallDelta = Partial<\n Pick<ToolCallDelta, 'auth' | 'expires_at' | 'summary' | 'type'>\n> & {\n tool_calls?: SanitizedAgentToolCall[];\n};\n\ntype SanitizedStepCompleted =\n | {\n id?: string;\n index?: number;\n type: 'tool_call';\n tool_call?: SanitizedProcessedToolCall;\n }\n | {\n type: 'summary';\n summary?: Extract<StepCompleted, { type: 'summary' }>['summary'];\n };\n\ntype SanitizedProcessedToolCall = Partial<\n Pick<ProcessedToolCall, 'args' | 'id' | 'name' | 'output' | 'progress'>\n>;\n\ntype SanitizedRunStepCompleted = {\n result?: SanitizedStepCompleted;\n};\n\ntype SanitizedMessageDelta = Partial<Pick<MessageDeltaEvent, 'id'>> & {\n delta?: {\n content?: MessageDeltaEvent['delta']['content'];\n tool_call_ids?: MessageDeltaEvent['delta']['tool_call_ids'];\n };\n};\n\ntype SanitizedReasoningDelta = Partial<Pick<ReasoningDeltaEvent, 'id'>> & {\n delta?: {\n content?: ReasoningDeltaEvent['delta']['content'];\n };\n};\n\ntype QueuedSubagentUpdate = {\n eventName: string;\n phase: SubagentUpdatePhase;\n data: unknown;\n};\n\ntype ForwarderCallback = {\n handler: BaseCallbackHandler;\n drain: () => Promise<void>;\n};\n\nconst LANGGRAPH_RUNTIME_CONFIG_PREFIX = '__pregel_';\nconst LANGGRAPH_CHECKPOINT_CONFIG_KEYS = new Set([\n 'checkpoint_id',\n 'checkpoint_map',\n 'checkpoint_ns',\n]);\n\nexport type SubagentExecuteParams = {\n description: string;\n subagentType: string;\n threadId?: string;\n /**\n * Parent-side `tool_call_id` of the `subagent` tool invocation that\n * triggered this execution. Surfaced on {@link SubagentUpdateEvent} so\n * hosts can correlate child updates back to the originating tool call\n * without relying on event ordering heuristics.\n */\n parentToolCallId?: string;\n /**\n * Snapshot of the parent invocation's host `config.configurable` at\n * the spawn-tool call site. Host-set fields (`requestBody`, `user`,\n * `userMCPAuthMap`, etc.) propagate into the child workflow's\n * `configurable` — fixing MCP body-placeholder substitution and\n * per-user lookups for subagent tool calls. LangGraph runtime keys\n * (`__pregel_*`, checkpoint bookkeeping) are intentionally not\n * inherited; the child graph recreates its own runtime config.\n *\n * Inheritance details (verified empirically against LangGraph):\n * - host-set keys propagate as-is into the child's tool dispatches;\n * - `thread_id` propagates (with `childRunId` as a fallback when\n * parent did not supply one) — matches the \"subagent is part of\n * the same conversation\" mental model and aligns with the\n * `sessionId: this.parentRunId` convention this executor already\n * uses for `SubagentStart` / `SubagentStop` hooks;\n * - `parent_run_id` propagates when the host put it on parent's\n * configurable;\n * - `run_id` is *overwritten by the LangGraph runtime* at child\n * invoke time regardless of what we forward — child's tool\n * dispatches see the child graph's runtime runId in\n * `configurable.run_id`, not the parent's. Hosts that need\n * parent-scoped run identity for downstream consumers should\n * plumb it via a host-defined key (e.g. `requestBody.messageId`),\n * not `run_id`.\n *\n * A future revision will likely make this inheritance configurable\n * per spawn type — background / async subagents may want isolation\n * rather than sharing parent's host context.\n */\n parentConfigurable?: Record<string, unknown>;\n};\n\nexport type SubagentExecuteResult = {\n content: string;\n messages: BaseMessage[];\n};\n\n/**\n * Factory that constructs a child graph for subagent execution. Injected\n * rather than imported so that `SubagentExecutor` does not have a runtime\n * dependency on `StandardGraph` — this avoids a circular dependency between\n * `src/graphs/Graph.ts` and `src/tools/subagent/` that would otherwise break\n * Rollup's chunking under `preserveModules`.\n */\nexport type ChildGraphFactory = (input: StandardGraphInput) => StandardGraph;\n\nexport type SubagentExecutorOptions = {\n configs: Map<string, ResolvedSubagentConfig>;\n parentSignal?: AbortSignal;\n hookRegistry?: HookRegistry;\n parentRunId: string;\n parentAgentId?: string;\n langfuse?: StandardGraphInput['langfuse'];\n tokenCounter?: TokenCounter;\n /** Remaining nesting budget. 0 or negative blocks execution. */\n maxDepth?: number;\n /**\n * Factory for constructing the isolated child graph. Callers pass\n * `(input) => new StandardGraph(input)` — injected to break a circular\n * module dependency.\n */\n createChildGraph: ChildGraphFactory;\n /**\n * Parent's event handler registry. When provided, child-graph events are\n * forwarded through this registry so hosts can:\n * (a) execute event-driven tools (`ON_TOOL_EXECUTE` routed to parent's handler),\n * (b) surface child activity to a UI via wrapped {@link GraphEvents.ON_SUBAGENT_UPDATE}.\n * When omitted, the child runs fully isolated (legacy behavior).\n *\n * Can be a direct `HandlerRegistry` or a zero-arg getter — use the getter\n * form when the registry is assigned to the graph AFTER the executor is\n * constructed (the current `Run.create` flow sets `handlerRegistry`\n * post-`createWorkflow`, so `createAgentNode` must capture lazily).\n */\n parentHandlerRegistry?: HandlerRegistry | (() => HandlerRegistry | undefined);\n /**\n * Receives a usage event for every model call the child run makes. The\n * child workflow executes via `invoke()` with a detached callbacks array,\n * so its `on_chat_model_end` events never reach the parent's handler\n * registry — without this sink, child token usage is invisible to the\n * host (unbilled model calls). Forwarded into the child graph's input so\n * nested subagents report through the same sink.\n */\n usageSink?: SubagentUsageSink;\n};\n\nexport class SubagentExecutor {\n private readonly configs: Map<string, ResolvedSubagentConfig>;\n private readonly parentSignal?: AbortSignal;\n private readonly hookRegistry?: HookRegistry;\n private readonly parentRunId: string;\n private readonly parentAgentId?: string;\n private readonly langfuse?: StandardGraphInput['langfuse'];\n private readonly tokenCounter?: TokenCounter;\n private readonly maxDepth: number;\n private readonly createChildGraph: ChildGraphFactory;\n private readonly usageSink?: SubagentUsageSink;\n private readonly resolveParentHandlerRegistry?: () =>\n | HandlerRegistry\n | undefined;\n\n constructor(options: SubagentExecutorOptions) {\n this.configs = options.configs;\n this.parentSignal = options.parentSignal;\n this.hookRegistry = options.hookRegistry;\n this.parentRunId = options.parentRunId;\n this.parentAgentId = options.parentAgentId;\n this.langfuse = options.langfuse;\n this.tokenCounter = options.tokenCounter;\n this.maxDepth = options.maxDepth ?? 1;\n this.createChildGraph = options.createChildGraph;\n this.usageSink = options.usageSink;\n const rawRegistry = options.parentHandlerRegistry;\n if (typeof rawRegistry === 'function') {\n this.resolveParentHandlerRegistry = rawRegistry;\n } else if (rawRegistry != null) {\n this.resolveParentHandlerRegistry = (): HandlerRegistry => rawRegistry;\n }\n }\n\n /** Snapshot of the parent's registry at the moment a subagent is dispatched. */\n private getParentHandlerRegistry(): HandlerRegistry | undefined {\n return this.resolveParentHandlerRegistry?.();\n }\n\n async execute(params: SubagentExecuteParams): Promise<SubagentExecuteResult> {\n const { description, subagentType, threadId, parentToolCallId } = params;\n const config = this.configs.get(subagentType);\n\n if (!config) {\n const available = [...this.configs.keys()].join(', ');\n return {\n content: `Error: Unknown subagent type \"${subagentType}\". Available types: ${available}`,\n messages: [],\n };\n }\n\n if (this.maxDepth <= 0) {\n return {\n content: 'Error: Maximum subagent nesting depth exceeded.',\n messages: [],\n };\n }\n\n const childAgentId =\n config.agentInputs.agentId ||\n `${this.parentAgentId ?? 'agent'}_sub_${nanoid(8)}`;\n\n if (\n this.hookRegistry?.hasHookFor('SubagentStart', this.parentRunId) === true\n ) {\n const hookResult = await executeHooks({\n registry: this.hookRegistry,\n input: {\n hook_event_name: 'SubagentStart',\n runId: this.parentRunId,\n threadId,\n parentAgentId: this.parentAgentId,\n agentId: childAgentId,\n agentType: subagentType,\n inputs: [new HumanMessage(description)],\n },\n sessionId: this.parentRunId,\n matchQuery: subagentType,\n }).catch((): AggregatedHookResult => HOOK_FALLBACK);\n\n /**\n * `ask` is treated identically to `deny` in the subagent context:\n * subagents are non-interactive, so there is no prompt path for `ask`.\n * Both decisions block execution and return a \"Blocked\" tool result.\n */\n if (hookResult.decision === 'deny' || hookResult.decision === 'ask') {\n return {\n content: `Blocked: ${hookResult.reason ?? 'Blocked by hook'}`,\n messages: [],\n };\n }\n }\n\n const parentRegistry = this.getParentHandlerRegistry();\n const forwardingEnabled = parentRegistry != null;\n /**\n * Keep `toolDefinitions` only when the host has actually wired an\n * `ON_TOOL_EXECUTE` handler. `Run` always constructs a `HandlerRegistry`,\n * so treating any registry as \"forwarding enabled\" would leak\n * `toolDefinitions` into children whose hosts cannot execute them — the\n * child's `ToolNode` batch promise would hang forever with no handler to\n * resolve/reject. Gating on the tool-execute handler preserves the\n * recoverable \"no tools\" path for registry-but-no-handler configs.\n */\n const hasToolExecuteHandler =\n parentRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) != null;\n const childInputs = buildChildInputs(\n config,\n childAgentId,\n this.maxDepth,\n /* keepToolDefinitions */ hasToolExecuteHandler\n );\n const childRunId = `${this.parentRunId}_sub_${nanoid(8)}`;\n const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;\n\n const hostUsageSink = this.usageSink;\n const childGraph = this.createChildGraph({\n runId: childRunId,\n signal: this.parentSignal,\n agents: [childInputs],\n langfuse: this.langfuse,\n tokenCounter: this.tokenCounter,\n subagentScope: true,\n /**\n * Forwarded so the child graph's own `SubagentExecutor` (created in\n * its `createAgentNode` when `allowNested` keeps subagentConfigs)\n * reports nested-child usage through the same host sink. Each nesting\n * level attaches its own capture callback — `workflow.invoke` replaces\n * the inherited callback chain, so a single top-level handler would\n * never see grandchild model calls.\n *\n * The wrapper rewrites `runId` to THIS executor's parent run: nested\n * executors emit with their own `parentRunId` (a `*_sub_*` child id),\n * and each wrapper layer rewrites upward, so by the time an event\n * reaches the host sink its `runId` is the ROOT run — hosts keying\n * billing by run id never see intermediate child run ids there\n * (`subagentRunId` still identifies the emitting child).\n */\n subagentUsageSink:\n hostUsageSink == null\n ? undefined\n : /** Returns the host sink's result so async sinks stay awaited\n * through every wrapper layer. */\n (event): void | Promise<void> =>\n hostUsageSink({ ...event, runId: this.parentRunId }),\n });\n\n let forwarding: ForwarderCallback | undefined;\n if (forwardingEnabled) {\n forwarding = this.createForwarderCallback({\n parentRegistry: parentRegistry!,\n subagentType,\n subagentAgentId: childAgentId,\n childRunId,\n parentToolCallId,\n });\n }\n const forwarder = forwarding?.handler;\n\n if (forwarder) {\n await this.emitSubagentUpdate(parentRegistry!, {\n childRunId,\n subagentType,\n subagentAgentId: childAgentId,\n parentToolCallId,\n phase: 'start',\n label: `Subagent \"${subagentType}\" started`,\n });\n }\n\n let result: { messages: BaseMessage[] };\n try {\n const workflow = childGraph.createWorkflow();\n /**\n * When `parentHandlerRegistry` is provided (forwarding mode), attach a\n * lightweight callback that intercepts the child's `on_custom_event`\n * dispatches and routes them to the parent's registry — either as\n * operational events (ON_TOOL_EXECUTE) or wrapped ON_SUBAGENT_UPDATE\n * envelopes. Native LangChain streaming events (on_chat_model_stream,\n * etc.) still do NOT propagate to the parent's outer streamEvents\n * iterator — the `callbacks` array REPLACES the inherited chain, so\n * parent handlers won't receive child stream chunks and raise \"No\n * agent context found\" lookups on the parent's agentContexts map.\n *\n * When no registry is provided (legacy isolation), `callbacks: []`\n * fully detaches the child.\n *\n * `runName` gives the child a distinct LangSmith trace root (avoids\n * nested trace pollution).\n */\n const callbackHandlers: BaseCallbackHandler[] = [];\n if (forwarder) {\n callbackHandlers.push(forwarder);\n }\n /**\n * Usage capture rides the same detached callbacks array. Because\n * `callbacks` REPLACES the inherited chain (see above), the host's\n * `CHAT_MODEL_END` handler never observes the child's model calls —\n * this handler is the child-side equivalent of `ModelEndHandler`,\n * reporting per-call usage to the host's sink for billing.\n */\n if (this.usageSink) {\n callbackHandlers.push(\n createUsageCaptureHandler({\n sink: this.usageSink,\n subagentType,\n subagentRunId: childRunId,\n subagentAgentId: childAgentId,\n parentRunId: this.parentRunId,\n provider: config.agentInputs.provider,\n fallbackModel: extractConfiguredModel(config.agentInputs),\n })\n );\n }\n const callbacks: Callbacks = callbackHandlers;\n /**\n * Inherit the parent's host `configurable` — host-set fields\n * (`requestBody`, `user`, `userMCPAuthMap`, etc.) AND the run-\n * identity fields (`run_id`, `parent_run_id`, `thread_id`) all\n * propagate. LangGraph's own runtime keys are excluded because the\n * child graph creates its own scratchpad/checkpoint/abort plumbing.\n *\n * Run-identity propagation is intentional and matches the\n * convention this executor itself already uses for `SubagentStart`\n * / `SubagentStop` hooks (`sessionId: this.parentRunId`): the\n * subagent runs under the parent's session scope, not its own.\n * Forwarding `run_id` / `parent_run_id` / `thread_id` makes\n * `ToolNode`'s hook lookups (`hasHookFor(eventName, runId)`),\n * `ToolOutputReferenceRegistry` keying, and trace lineage all\n * resolve to the parent's session for tools dispatched from the\n * subagent — so `PreToolUse` / `PostToolUse` hooks the host\n * registered against the parent's run fire for subagent tool\n * calls too. \"Same run\" matches the user-perceptual mental model.\n *\n * `thread_id` falls back to `childRunId` only when the parent\n * didn't supply one (legacy behavior preserved for hosts that\n * never set thread_id).\n *\n * NOTE: a future revision will likely make this configurable per\n * spawn type — e.g. a background / async subagent that runs after\n * the parent's run completes wants isolation, not inheritance.\n * For now the inheritance path matches LibreChat's primary use\n * case (synchronous subagents within a single user turn).\n */\n const inheritedConfigurable: Record<string, unknown> =\n sanitizeChildConfigurable(params.parentConfigurable);\n result = await workflow.invoke(\n { messages: [new HumanMessage(description)] },\n {\n recursionLimit: maxTurns * RECURSION_MULTIPLIER,\n signal: this.parentSignal,\n callbacks,\n runName: `subagent:${subagentType}`,\n configurable: {\n thread_id: childRunId,\n ...inheritedConfigurable,\n },\n }\n );\n } catch (error) {\n const errorMessage = truncateErrorMessage(error);\n if (forwarding) {\n await forwarding.drain();\n await this.emitSubagentUpdate(parentRegistry!, {\n childRunId,\n subagentType,\n subagentAgentId: childAgentId,\n parentToolCallId,\n phase: 'error',\n label: `Subagent \"${subagentType}\" errored: ${errorMessage}`,\n data: { message: errorMessage },\n });\n }\n childGraph.clearHeavyState();\n return {\n content: `Subagent error: ${errorMessage}`,\n messages: [],\n };\n }\n\n const filteredContent = filterSubagentResult(result.messages);\n\n if (\n this.hookRegistry?.hasHookFor('SubagentStop', this.parentRunId) === true\n ) {\n /**\n * Awaited (not fire-and-forget) for deterministic test synchronization\n * and consistency with PostCompact. The parent is already waiting on the\n * tool result, so the small extra latency is acceptable. Errors are\n * swallowed — SubagentStop is observational.\n */\n await executeHooks({\n registry: this.hookRegistry,\n input: {\n hook_event_name: 'SubagentStop',\n runId: this.parentRunId,\n threadId,\n agentId: childAgentId,\n agentType: subagentType,\n messages: result.messages,\n },\n sessionId: this.parentRunId,\n matchQuery: subagentType,\n }).catch(() => {\n /* SubagentStop is observational — swallow errors */\n });\n }\n\n if (forwarding) {\n await forwarding.drain();\n await this.emitSubagentUpdate(parentRegistry!, {\n childRunId,\n subagentType,\n subagentAgentId: childAgentId,\n parentToolCallId,\n phase: 'stop',\n label: `Subagent \"${subagentType}\" finished`,\n });\n }\n\n childGraph.clearHeavyState();\n\n return { content: filteredContent, messages: result.messages };\n }\n\n /**\n * Emits a single {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope through the\n * parent's handler registry. Silent no-op when no parent registry is set.\n * Errors are swallowed — update events are observational.\n */\n private async emitSubagentUpdate(\n parentRegistry: HandlerRegistry,\n args: {\n childRunId: string;\n subagentType: string;\n subagentAgentId: string;\n parentToolCallId?: string;\n phase: SubagentUpdatePhase;\n data?: unknown;\n label?: string;\n }\n ): Promise<void> {\n const handler = parentRegistry.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);\n if (!handler) {\n return;\n }\n const event: SubagentUpdateEvent = {\n runId: this.parentRunId,\n subagentRunId: args.childRunId,\n subagentType: args.subagentType,\n subagentAgentId: args.subagentAgentId,\n parentAgentId: this.parentAgentId,\n parentToolCallId: args.parentToolCallId,\n phase: args.phase,\n data: args.data,\n label: args.label,\n timestamp: new Date().toISOString(),\n };\n try {\n await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);\n } catch {\n /* observational — swallow */\n }\n }\n\n /**\n * Builds a BaseCallbackHandler that intercepts the child graph's custom\n * events. Routing rules:\n * - `ON_TOOL_EXECUTE` → forwarded as-is to the parent's ON_TOOL_EXECUTE\n * handler (so event-driven tools work identically for child and parent).\n * - `ON_RUN_STEP` / `ON_RUN_STEP_DELTA` / `ON_RUN_STEP_COMPLETED` /\n * `ON_MESSAGE_DELTA` / `ON_REASONING_DELTA` → wrapped in a\n * {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope with a human-readable\n * label, delivered to the parent's subagent-update handler.\n * - Everything else → ignored (keeps parent's UI scoped to the events it\n * cares about; host apps can extend by registering more phases).\n */\n private createForwarderCallback(args: {\n parentRegistry: HandlerRegistry;\n subagentType: string;\n subagentAgentId: string;\n childRunId: string;\n parentToolCallId?: string;\n }): ForwarderCallback {\n const {\n parentRegistry,\n subagentType,\n subagentAgentId,\n childRunId,\n parentToolCallId,\n } = args;\n const parentRunId = this.parentRunId;\n const parentAgentId = this.parentAgentId;\n\n const wrap = async (\n eventName: string,\n phase: SubagentUpdatePhase,\n data: unknown\n ): Promise<void> => {\n const handler = parentRegistry.getHandler(GraphEvents.ON_SUBAGENT_UPDATE);\n if (!handler) {\n return;\n }\n try {\n const event: SubagentUpdateEvent = {\n runId: parentRunId,\n subagentRunId: childRunId,\n subagentType,\n subagentAgentId,\n parentAgentId,\n parentToolCallId,\n phase,\n data: sanitizeForwardedSubagentUpdateData(eventName, data),\n label: summarizeEvent(eventName, data),\n timestamp: new Date().toISOString(),\n };\n await handler.handle(GraphEvents.ON_SUBAGENT_UPDATE, event);\n } catch {\n /* observational — swallow */\n }\n };\n\n const queuedUpdates: QueuedSubagentUpdate[] = [];\n let drainPromise: Promise<void> | undefined;\n\n const enqueue = (update: QueuedSubagentUpdate): void => {\n if (queuedUpdates.length >= MAX_PENDING_SUBAGENT_UPDATES) {\n const dropIndex = queuedUpdates.findIndex((queued) =>\n isDroppableSubagentUpdatePhase(queued.phase)\n );\n if (dropIndex >= 0) {\n queuedUpdates.splice(dropIndex, 1);\n } else if (isDroppableSubagentUpdatePhase(update.phase)) {\n return;\n }\n }\n queuedUpdates.push(update);\n };\n\n const drain = async (): Promise<void> => {\n if (drainPromise != null) {\n await drainPromise;\n return;\n }\n drainPromise = (async (): Promise<void> => {\n while (queuedUpdates.length > 0) {\n const update = queuedUpdates.shift();\n if (update == null) {\n continue;\n }\n await wrap(update.eventName, update.phase, update.data);\n }\n })();\n try {\n await drainPromise;\n } finally {\n drainPromise = undefined;\n if (queuedUpdates.length > 0) {\n await drain();\n }\n }\n };\n\n const scheduleWrap = (\n eventName: string,\n phase: SubagentUpdatePhase,\n data: unknown\n ): void => {\n enqueue({ eventName, phase, data });\n void drain();\n };\n\n const handler = BaseCallbackHandler.fromMethods({\n [Callback.CUSTOM_EVENT]: async (\n eventName: string,\n data: unknown\n ): Promise<void> => {\n if (eventName === GraphEvents.ON_TOOL_EXECUTE) {\n const toolHandler = parentRegistry.getHandler(\n GraphEvents.ON_TOOL_EXECUTE\n );\n if (toolHandler) {\n await toolHandler.handle(\n GraphEvents.ON_TOOL_EXECUTE,\n data as ToolExecuteBatchRequest\n );\n }\n /**\n * We also surface a short notice in the subagent-update stream so\n * the UI can show \"calling <tool>\" for each tool the child spawns.\n */\n scheduleWrap(eventName, 'run_step', data);\n return;\n }\n\n if (eventName === GraphEvents.ON_RUN_STEP) {\n scheduleWrap(eventName, 'run_step', data);\n return;\n }\n if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {\n scheduleWrap(eventName, 'run_step_delta', data);\n return;\n }\n if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {\n scheduleWrap(eventName, 'run_step_completed', data);\n return;\n }\n if (eventName === GraphEvents.ON_MESSAGE_DELTA) {\n scheduleWrap(eventName, 'message_delta', data);\n return;\n }\n if (eventName === GraphEvents.ON_REASONING_DELTA) {\n scheduleWrap(eventName, 'reasoning_delta', data);\n return;\n }\n },\n });\n /**\n * `awaitHandlers = true` is required so the child's `ToolNode` actually\n * blocks on the parent's `ON_TOOL_EXECUTE` handler until it resolves\n * the batch request. Observational `ON_SUBAGENT_UPDATE` calls are queued\n * behind a bounded sequential dispatcher so host UI publication cannot\n * backpressure each child emission or run unbounded concurrent publishes.\n * The executor drains this queue before terminal stop/error envelopes to\n * preserve phase ordering.\n */\n handler.awaitHandlers = true;\n return { handler, drain };\n }\n}\n\n/**\n * Builds the child-run equivalent of a host `CHAT_MODEL_END` handler: a\n * callback that joins per-call model identity (captured from\n * `ls_model_name` at chat-model start) with the usage metadata reported at\n * LLM end, and emits a {@link SubagentUsageEvent} through the host's sink.\n *\n * Attached to the child `workflow.invoke` callbacks array, so it observes\n * every model call inside the child graph — the agent loop and any\n * auxiliary calls (e.g. child-side summarization). It does NOT observe\n * deeper subagent levels: each nesting level replaces the callback chain\n * and attaches its own capture handler via the forwarded\n * `subagentUsageSink` on the child graph's input.\n */\nfunction createUsageCaptureHandler(args: {\n sink: SubagentUsageSink;\n subagentType: string;\n subagentRunId: string;\n subagentAgentId: string;\n parentRunId: string;\n /**\n * Child config's provider enum — the default tag when a call carries no\n * `INVOKED_PROVIDER` metadata (hosts key pricing/cache semantics off it).\n */\n provider?: string;\n /**\n * Child config's model, used when a call carries neither `ls_model_name`\n * nor `INVOKED_MODEL` metadata.\n */\n fallbackModel?: string;\n}): BaseCallbackHandler {\n const {\n sink,\n subagentType,\n subagentRunId,\n subagentAgentId,\n parentRunId,\n provider,\n fallbackModel,\n } = args;\n /**\n * Per-call attribution keyed by LangChain callback runId. `model` joins\n * `ls_model_name` (provider-reported) with `INVOKED_MODEL` (stamped by\n * `tryFallbackProviders` from the fallback's client options); `provider`\n * is `INVOKED_PROVIDER`, stamped by `attemptInvoke` with the SDK enum of\n * the provider that ACTUALLY served the call — correct for\n * fallback-served calls, where the static config provider would mis-tag\n * pricing/cache semantics.\n */\n const callInfoByCallId = new Map<\n string,\n { model?: string; provider?: string }\n >();\n const handler = BaseCallbackHandler.fromMethods({\n handleChatModelStart: (\n _llm: unknown,\n _messages: unknown,\n runId: string,\n _parentRunId?: string,\n _extraParams?: Record<string, unknown>,\n _tags?: string[],\n metadata?: Record<string, unknown>\n ): void => {\n const callModel =\n asNonEmptyString(metadata?.ls_model_name) ??\n asNonEmptyString(metadata?.[Constants.INVOKED_MODEL]);\n const callProvider = asNonEmptyString(\n metadata?.[Constants.INVOKED_PROVIDER]\n );\n if (callModel != null || callProvider != null) {\n callInfoByCallId.set(runId, {\n model: callModel,\n provider: callProvider,\n });\n }\n },\n handleLLMEnd: async (output: LLMResult, runId: string): Promise<void> => {\n const callInfo = callInfoByCallId.get(runId);\n callInfoByCallId.delete(runId);\n const model = callInfo?.model ?? fallbackModel;\n const callProvider = callInfo?.provider ?? provider;\n for (const generationGroup of output.generations) {\n /**\n * At most ONE event per generation group: each group is one\n * provider request (the outer array is per-prompt for batched\n * calls), and with multiple completions (`n > 1`) every choice in\n * a group repeats the request-level `usage_metadata` — emitting\n * per choice would multiply billed tokens.\n */\n for (const generation of generationGroup) {\n const message = (generation as ChatGeneration | undefined)?.message;\n const usage = (\n message as { usage_metadata?: UsageMetadata } | undefined\n )?.usage_metadata;\n if (usage == null) {\n continue;\n }\n /**\n * Awaited so async host sinks (billing/persistence) complete\n * before the model call resolves — `awaitHandlers` only waits on\n * `handleLLMEnd` itself, so a dropped promise here would let the\n * parent run finish before usage is recorded and would turn sink\n * rejections into unhandled rejections.\n */\n try {\n await sink({\n usage,\n model,\n provider: callProvider,\n subagentType,\n subagentRunId,\n subagentAgentId,\n runId: parentRunId,\n });\n } catch {\n /* observational — a throwing/rejecting host sink must not break the child run */\n }\n break;\n }\n }\n },\n handleLLMError: (_err: unknown, runId: string): void => {\n callInfoByCallId.delete(runId);\n },\n });\n /**\n * Dispatch usage synchronously with each model call so all entries are\n * sunk before `workflow.invoke` resolves — hosts read their accumulator\n * right after the parent run completes.\n */\n handler.awaitHandlers = true;\n return handler;\n}\n\nfunction asNonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\n/**\n * Best-effort read of the configured model from a subagent's client\n * options. Providers disagree on the key (`model` vs `modelName`), and the\n * value is only a fallback for calls that carry no `ls_model_name`.\n */\nfunction extractConfiguredModel(agentInputs: AgentInputs): string | undefined {\n const clientOptions = agentInputs.clientOptions as\n | { model?: unknown; modelName?: unknown }\n | undefined;\n if (typeof clientOptions?.model === 'string' && clientOptions.model !== '') {\n return clientOptions.model;\n }\n if (\n typeof clientOptions?.modelName === 'string' &&\n clientOptions.modelName !== ''\n ) {\n return clientOptions.modelName;\n }\n return undefined;\n}\n\nfunction sanitizeChildConfigurable(\n parentConfigurable: Record<string, unknown> | undefined\n): Record<string, unknown> {\n if (parentConfigurable == null) {\n return {};\n }\n return Object.fromEntries(\n Object.entries(parentConfigurable).filter(\n ([key]) => !isLangGraphRuntimeConfigKey(key)\n )\n );\n}\n\nfunction isLangGraphRuntimeConfigKey(key: string): boolean {\n return (\n key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) ||\n LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key)\n );\n}\n\nexport function sanitizeForwardedSubagentUpdateData(\n eventName: string,\n data: unknown\n): unknown {\n if (eventName === GraphEvents.ON_TOOL_EXECUTE) {\n return sanitizeToolExecuteUpdateData(data);\n }\n if (eventName === GraphEvents.ON_RUN_STEP) {\n return sanitizeRunStepUpdateData(data);\n }\n if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {\n return sanitizeRunStepDeltaUpdateData(data);\n }\n if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {\n return sanitizeRunStepCompletedUpdateData(data);\n }\n if (eventName === GraphEvents.ON_MESSAGE_DELTA) {\n return sanitizeMessageDeltaUpdateData(data);\n }\n if (eventName === GraphEvents.ON_REASONING_DELTA) {\n return sanitizeReasoningDeltaUpdateData(data);\n }\n return undefined;\n}\n\nfunction isDroppableSubagentUpdatePhase(phase: SubagentUpdatePhase): boolean {\n return (\n phase === 'message_delta' ||\n phase === 'reasoning_delta' ||\n phase === 'run_step_delta'\n );\n}\n\nfunction sanitizeToolExecuteUpdateData(\n data: unknown\n): SanitizedSubagentToolExecuteData {\n const request = data as Partial<ToolExecuteBatchRequest>;\n const toolCalls = Array.isArray(request.toolCalls)\n ? request.toolCalls.map(sanitizeToolCallForUpdate)\n : [];\n const sanitized: SanitizedSubagentToolExecuteData = { toolCalls };\n if (typeof request.agentId === 'string') {\n sanitized.agentId = request.agentId;\n }\n return sanitized;\n}\n\nfunction sanitizeToolCallForUpdate(\n call: ToolExecuteBatchRequest['toolCalls'][number]\n): SanitizedSubagentToolCall {\n const sanitized: SanitizedSubagentToolCall = {\n id: call.id,\n name: call.name,\n args: call.args,\n };\n return sanitized;\n}\n\nfunction sanitizeRunStepUpdateData(\n data: unknown\n): SanitizedRunStep | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const step = data as Partial<RunStep>;\n const sanitized: SanitizedRunStep = {};\n assignString(sanitized, 'agentId', step.agentId);\n assignNumber(sanitized, 'groupId', step.groupId);\n assignString(sanitized, 'id', step.id);\n assignNumber(sanitized, 'index', step.index);\n assignString(sanitized, 'runId', step.runId);\n assignNumber(sanitized, 'stepIndex', step.stepIndex);\n assignString(sanitized, 'type', step.type);\n if (step.summary !== undefined) {\n sanitized.summary = step.summary;\n }\n if (step.usage !== undefined) {\n sanitized.usage = step.usage;\n }\n sanitized.stepDetails = sanitizeStepDetails(step.stepDetails);\n return sanitized;\n}\n\nfunction sanitizeRunStepDeltaUpdateData(\n data: unknown\n): SanitizedRunStepDelta | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const event = data as Partial<RunStepDeltaEvent>;\n const sanitized: SanitizedRunStepDelta = {};\n assignString(sanitized, 'id', event.id);\n sanitized.delta = sanitizeToolCallDelta(event.delta);\n return sanitized;\n}\n\nfunction sanitizeRunStepCompletedUpdateData(\n data: unknown\n): SanitizedRunStepCompleted | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const event = data as { result?: unknown };\n return { result: sanitizeStepCompleted(event.result) };\n}\n\nfunction sanitizeMessageDeltaUpdateData(\n data: unknown\n): SanitizedMessageDelta | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const event = data as Partial<MessageDeltaEvent>;\n const sanitized: SanitizedMessageDelta = {};\n assignString(sanitized, 'id', event.id);\n if (event.delta != null) {\n sanitized.delta = {};\n if (event.delta.content !== undefined) {\n sanitized.delta.content = event.delta.content;\n }\n if (event.delta.tool_call_ids !== undefined) {\n sanitized.delta.tool_call_ids = event.delta.tool_call_ids;\n }\n }\n return sanitized;\n}\n\nfunction sanitizeReasoningDeltaUpdateData(\n data: unknown\n): SanitizedReasoningDelta | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const event = data as Partial<ReasoningDeltaEvent>;\n const sanitized: SanitizedReasoningDelta = {};\n assignString(sanitized, 'id', event.id);\n if (event.delta?.content !== undefined) {\n sanitized.delta = { content: event.delta.content };\n }\n return sanitized;\n}\n\nfunction sanitizeStepDetails(\n stepDetails: unknown\n): SanitizedStepDetails | undefined {\n if (!isObjectLike(stepDetails)) {\n return undefined;\n }\n const rawDetails = stepDetails as {\n message_creation?: { message_id?: unknown };\n tool_calls?: unknown[];\n type?: unknown;\n };\n if (rawDetails.type === StepTypes.MESSAGE_CREATION) {\n const sanitized: SanitizedStepDetails = {\n type: StepTypes.MESSAGE_CREATION,\n };\n const messageId = rawDetails.message_creation?.message_id;\n if (typeof messageId === 'string') {\n sanitized.message_creation = { message_id: messageId };\n }\n return sanitized;\n }\n if (rawDetails.type === StepTypes.TOOL_CALLS) {\n const sanitized: SanitizedStepDetails = {\n type: StepTypes.TOOL_CALLS,\n };\n if (Array.isArray(rawDetails.tool_calls)) {\n sanitized.tool_calls = rawDetails.tool_calls.map(sanitizeAgentToolCall);\n }\n return sanitized;\n }\n return undefined;\n}\n\nfunction sanitizeToolCallDelta(\n delta: ToolCallDelta | undefined\n): SanitizedToolCallDelta | undefined {\n if (!isObjectLike(delta)) {\n return undefined;\n }\n const sanitized: SanitizedToolCallDelta = {};\n assignString(sanitized, 'auth', delta.auth);\n assignNumber(sanitized, 'expires_at', delta.expires_at);\n assignString(sanitized, 'type', delta.type);\n if (delta.summary !== undefined) {\n sanitized.summary = delta.summary;\n }\n if (Array.isArray(delta.tool_calls)) {\n sanitized.tool_calls = delta.tool_calls.map(sanitizeAgentToolCall);\n }\n return sanitized;\n}\n\nfunction sanitizeStepCompleted(\n data: unknown\n): SanitizedStepCompleted | undefined {\n if (!isObjectLike(data)) {\n return undefined;\n }\n const completed = data as Partial<StepCompleted> & {\n id?: unknown;\n index?: unknown;\n tool_call?: unknown;\n };\n if (completed.type === 'summary') {\n return {\n type: 'summary',\n summary: completed.summary,\n };\n }\n if (completed.type !== 'tool_call') {\n return undefined;\n }\n const sanitized: SanitizedStepCompleted = { type: 'tool_call' };\n assignString(sanitized, 'id', completed.id);\n assignNumber(sanitized, 'index', completed.index);\n sanitized.tool_call = sanitizeProcessedToolCall(completed.tool_call);\n return sanitized;\n}\n\nfunction sanitizeProcessedToolCall(\n toolCall: unknown\n): SanitizedProcessedToolCall | undefined {\n if (!isObjectLike(toolCall)) {\n return undefined;\n }\n const call = toolCall as Partial<ProcessedToolCall>;\n const sanitized: SanitizedProcessedToolCall = {};\n assignString(sanitized, 'id', call.id);\n assignString(sanitized, 'name', call.name);\n if (call.args !== undefined) {\n sanitized.args = call.args;\n }\n assignString(sanitized, 'output', call.output);\n assignNumber(sanitized, 'progress', call.progress);\n return sanitized;\n}\n\nfunction sanitizeAgentToolCall(toolCall: unknown): SanitizedAgentToolCall {\n if (!isObjectLike(toolCall)) {\n return {};\n }\n const call = toolCall as SanitizedAgentToolCall;\n const sanitized: SanitizedAgentToolCall = {};\n assignString(sanitized, 'id', call.id);\n assignString(sanitized, 'name', call.name);\n assignString(sanitized, 'type', call.type);\n if (call.args !== undefined) {\n sanitized.args = call.args;\n }\n if (isObjectLike(call.function)) {\n const fn: SanitizedAgentToolCall['function'] = {};\n assignString(fn, 'name', call.function.name);\n if (\n typeof call.function.arguments === 'string' ||\n isObjectLike(call.function.arguments)\n ) {\n fn.arguments = call.function.arguments;\n }\n sanitized.function = fn;\n }\n return sanitized;\n}\n\nfunction isObjectLike(value: unknown): value is object {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assignString<T extends object, K extends keyof T>(\n target: T,\n key: K,\n value: unknown\n): void {\n if (typeof value === 'string') {\n target[key] = value as T[K];\n }\n}\n\nfunction assignNumber<T extends object, K extends keyof T>(\n target: T,\n key: K,\n value: unknown\n): void {\n if (typeof value === 'number') {\n target[key] = value as T[K];\n }\n}\n\n/**\n * Produces a short single-line label for an arbitrary forwarded child event.\n * Used to populate {@link SubagentUpdateEvent.label} so the host UI can show\n * a compact status ticker without parsing the raw payload.\n */\nexport function summarizeEvent(eventName: string, data: unknown): string {\n if (eventName === GraphEvents.ON_TOOL_EXECUTE) {\n const req = data as { toolCalls?: Array<{ name?: string }> };\n const names = (req.toolCalls ?? [])\n .map((c) => c.name)\n .filter((n): n is string => typeof n === 'string');\n return names.length > 0 ? `Calling ${names.join(', ')}` : 'Calling tool';\n }\n if (eventName === GraphEvents.ON_RUN_STEP) {\n const step = data as {\n type?: string;\n stepDetails?: { type?: string; tool_calls?: Array<{ name?: string }> };\n };\n const detailType = step.stepDetails?.type ?? step.type ?? 'step';\n if (detailType === 'tool_calls') {\n const names = (step.stepDetails?.tool_calls ?? [])\n .map((c) => c.name)\n .filter((n): n is string => typeof n === 'string');\n return names.length > 0\n ? `Using tool: ${names.join(', ')}`\n : 'Planning tool call';\n }\n if (detailType === 'message_creation') {\n return 'Thinking…';\n }\n return `Step: ${detailType}`;\n }\n if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {\n const step = data as {\n result?: {\n type?: string;\n tool_call?: { name?: string; output?: string };\n };\n };\n const tool = step.result?.tool_call;\n if (tool?.name != null && tool.name !== '') {\n return `Tool ${tool.name} complete`;\n }\n return 'Step complete';\n }\n if (eventName === GraphEvents.ON_MESSAGE_DELTA) {\n return 'Streaming…';\n }\n return eventName;\n}\n\n/**\n * Walk messages from last to first, returning the text content of the most\n * recent AIMessage that has any. Non-text blocks (tool_use, thinking,\n * redacted_thinking, tool_result) are stripped. If the last AIMessage is\n * pure tool_use (e.g. the subagent hit `maxTurns` mid-tool-call), the walk\n * continues to earlier AIMessages so partial progress is salvaged — this\n * matches Claude Code's behavior in `agentToolUtils.finalizeAgentTool`.\n * Returns \"Task completed\" only when no AIMessage in the history contains\n * any text.\n */\nexport function filterSubagentResult(messages: BaseMessage[]): string {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i]._getType() !== 'ai') {\n continue;\n }\n\n const content = messages[i].content;\n\n if (typeof content === 'string') {\n if (content) return content;\n continue;\n }\n\n if (!Array.isArray(content)) {\n continue;\n }\n\n const textParts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') {\n textParts.push(block);\n } else if ('type' in block && block.type === 'text' && 'text' in block) {\n textParts.push(block.text as string);\n }\n }\n\n if (textParts.length > 0) {\n return textParts.join('\\n');\n }\n }\n\n return 'Task completed';\n}\n\n/**\n * Resolve self-spawn configs by filling in agentInputs from the parent context.\n * Returns configs with agentInputs guaranteed present. Throws on duplicate\n * `type` values to prevent silent config shadowing.\n */\nexport function resolveSubagentConfigs(\n configs: SubagentConfig[],\n parentContext: AgentContext\n): ResolvedSubagentConfig[] {\n const resolved = configs\n .map((config) => {\n if (config.agentInputs != null) {\n return config as ResolvedSubagentConfig;\n }\n if (config.self !== true || parentContext._sourceInputs == null) {\n return null;\n }\n return {\n ...config,\n agentInputs: { ...parentContext._sourceInputs },\n } as ResolvedSubagentConfig;\n })\n .filter((c): c is ResolvedSubagentConfig => c != null);\n\n const seenTypes = new Set<string>();\n for (const config of resolved) {\n if (seenTypes.has(config.type)) {\n throw new Error(\n `Duplicate subagent type \"${config.type}\". Each SubagentConfig must have a unique \"type\" field.`\n );\n }\n seenTypes.add(config.type);\n }\n\n return resolved;\n}\n\n/**\n * Build child AgentInputs from a resolved config, stripping nesting and\n * (optionally) event-driven fields. When `allowNested: true`, the child's\n * `maxSubagentDepth` is decremented so that depth is consumed as the call\n * chain deepens across graph boundaries — the parent's executor-level check\n * alone cannot see into the child graph's separate executor.\n *\n * When `keepToolDefinitions` is `true`, the child retains the parent's\n * `toolDefinitions` so event-driven tools remain usable. This is only safe\n * when the caller has wired a forwarder for `ON_TOOL_EXECUTE` to a\n * registered handler — otherwise the child will hang on tool dispatch.\n *\n * @remarks Advanced utility: exported primarily for testing and by\n * {@link SubagentExecutor}. Host applications configuring subagents should\n * not need to call this directly — it is invoked internally when a subagent\n * tool is dispatched. The depth-countdown contract (parent's `maxDepth` in,\n * child's decremented `maxSubagentDepth` on the returned inputs) is the\n * mechanism that bounds nesting across graph boundaries; callers must\n * respect it.\n */\nexport function buildChildInputs(\n config: ResolvedSubagentConfig,\n childAgentId: string,\n parentMaxDepth: number,\n keepToolDefinitions: boolean = false\n): AgentInputs {\n const { agentInputs } = config;\n const childInputs: AgentInputs = {\n ...agentInputs,\n agentId: childAgentId,\n toolDefinitions: keepToolDefinitions\n ? agentInputs.toolDefinitions\n : undefined,\n /**\n * Subagents run in an isolated context by contract. Parent-run-scoped\n * fields that would otherwise survive the shallow-spread clone — the\n * cross-run conversation summary and the prior-turn tool-discovery\n * set — are cleared here so the child starts fresh. Host applications\n * that want a subagent to see parent context must thread it in\n * explicitly (e.g. via the `description` argument to the subagent\n * tool), not via inherited state.\n */\n initialSummary: undefined,\n discoveredTools: undefined,\n /**\n * Host-supplied direct tools are scrubbed from INHERITED configs only.\n * A self-spawn config's `agentInputs` is a shallow spread of the parent's\n * `_sourceInputs`, so without this a parent-scoped graph tool (e.g. an\n * interrupt-raising ask_user_question, which needs the parent's\n * checkpointer — child graphs compile without one) would silently leak\n * into the child and deterministically throw `No checkpointer set`. An\n * EXPLICIT child config that lists its own `graphTools` is a deliberate\n * host choice and keeps them (Codex #289 P2).\n */\n graphTools: config.self === true ? undefined : agentInputs.graphTools,\n };\n\n if (config.allowNested === true) {\n childInputs.maxSubagentDepth = Math.max(0, parentMaxDepth - 1);\n } else {\n childInputs.subagentConfigs = undefined;\n childInputs.maxSubagentDepth = undefined;\n }\n\n return childInputs;\n}\n\nfunction truncateErrorMessage(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (message.length <= ERROR_MESSAGE_MAX_CHARS) {\n return message;\n }\n return `${message.slice(0, ERROR_MESSAGE_MAX_CHARS)}...`;\n}\n"],"mappings":"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"}
@@ -195,7 +195,9 @@ export declare class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode>
195
195
  * {@link t.StandardGraphInput.subagentUsageSink}.
196
196
  */
197
197
  subagentUsageSink?: t.SubagentUsageSink;
198
- constructor({ runId, signal, agents, langfuse, tokenCounter, indexTokenCountMap, calibrationRatio, subagentUsageSink, }: t.StandardGraphInput);
198
+ /** See {@link t.StandardGraphInput.subagentScope}. */
199
+ subagentScope: boolean;
200
+ constructor({ runId, signal, agents, langfuse, tokenCounter, indexTokenCountMap, calibrationRatio, subagentUsageSink, subagentScope, }: t.StandardGraphInput);
199
201
  resetValues(keepContent?: boolean): void;
200
202
  clearHeavyState(): void;
201
203
  getRunStep(stepId: string): t.RunStep | undefined;
@@ -233,6 +233,16 @@ export type StandardGraphInput = {
233
233
  * they already flow through the registry's `CHAT_MODEL_END` handler.
234
234
  */
235
235
  subagentUsageSink?: SubagentUsageSink;
236
+ /**
237
+ * True when this graph IS a subagent child run (set by `SubagentExecutor`
238
+ * when it constructs the child graph). Drives the hook-input `agentId`
239
+ * subagent-scope marker: hook dispatches from this graph's tool nodes
240
+ * carry `agentId` so run-scoped host hooks — which fire for child scopes
241
+ * too, because children inherit the parent's `run_id` — can tell child
242
+ * scope from the top level. Top-level graphs leave this unset and their
243
+ * hook inputs carry only `executingAgentId`.
244
+ */
245
+ subagentScope?: boolean;
236
246
  };
237
247
  export type GraphEdge = {
238
248
  /** Agent ID, use a list for multiple sources */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.2.62",
3
+ "version": "3.2.63",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -870,6 +870,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
870
870
  * {@link t.StandardGraphInput.subagentUsageSink}.
871
871
  */
872
872
  subagentUsageSink?: t.SubagentUsageSink;
873
+ /** See {@link t.StandardGraphInput.subagentScope}. */
874
+ subagentScope: boolean;
873
875
 
874
876
  constructor({
875
877
  runId,
@@ -880,12 +882,14 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
880
882
  indexTokenCountMap,
881
883
  calibrationRatio,
882
884
  subagentUsageSink,
885
+ subagentScope,
883
886
  }: t.StandardGraphInput) {
884
887
  super();
885
888
  this.runId = runId;
886
889
  this.signal = signal;
887
890
  this.langfuse = langfuse;
888
891
  this.subagentUsageSink = subagentUsageSink;
892
+ this.subagentScope = subagentScope === true;
889
893
 
890
894
  if (agents.length === 0) {
891
895
  throw new Error('At least one agent configuration is required');
@@ -1272,7 +1276,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1272
1276
  eventDrivenMode: true,
1273
1277
  sessions: this.sessions,
1274
1278
  toolDefinitions: toolDefMap,
1275
- agentId: agentContext?.agentId,
1279
+ // `agentId` is the subagent-scope marker — set ONLY for child-run
1280
+ // graphs (hooks fire for child scopes too, via the inherited
1281
+ // run_id); `executingAgentId` always identifies the owning agent.
1282
+ agentId: this.subagentScope ? agentContext?.agentId : undefined,
1276
1283
  executingAgentId: agentContext?.agentId,
1277
1284
  toolCallStepIds: this.toolCallStepIds,
1278
1285
  toolRegistry: agentContext?.toolRegistry,
@@ -1339,9 +1346,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1339
1346
  trace: traceToolNode,
1340
1347
  runLangfuse: this.langfuse,
1341
1348
  agentLangfuse: agentContext?.langfuse,
1342
- // `agentId` is intentionally left unset on this path (it is the
1343
- // subagent-scope marker); `executingAgentId` always identifies the owning
1344
- // agent so hooks can attribute the batch even at the top level.
1349
+ // `agentId` is the subagent-scope marker set ONLY for child-run
1350
+ // graphs; `executingAgentId` always identifies the owning agent so
1351
+ // hooks can attribute the batch even at the top level.
1352
+ agentId: this.subagentScope ? agentContext?.agentId : undefined,
1345
1353
  executingAgentId: agentContext?.agentId,
1346
1354
  toolCallStepIds: this.toolCallStepIds,
1347
1355
  errorHandler: (data, metadata): Promise<boolean> =>
@@ -2746,7 +2746,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2746
2746
  const batchRequest: t.ToolExecuteBatchRequest = {
2747
2747
  toolCalls: dispatchRequests,
2748
2748
  userId: config.configurable?.user_id as string | undefined,
2749
- agentId: this.agentId,
2749
+ // Dispatch attribution, NOT the hook subagent-scope marker:
2750
+ // hosts key tool/credential lookup on the owning agent, and
2751
+ // the eager path sends `agentContext.agentId` — this must
2752
+ // match it at the top level too.
2753
+ agentId: this.executingAgentId,
2750
2754
  configurable: config.configurable as
2751
2755
  | Record<string, unknown>
2752
2756
  | undefined,
@@ -4,6 +4,7 @@ import type { ToolCall } from '@langchain/core/messages/tool';
4
4
  import type {
5
5
  HookCallback,
6
6
  PermissionDeniedHookOutput,
7
+ PostToolBatchHookOutput,
7
8
  PostToolUseHookOutput,
8
9
  PreToolUseHookOutput,
9
10
  SubagentStartHookInput,
@@ -365,6 +366,129 @@ describe('Subagent hook integration (end-to-end via Run)', () => {
365
366
  expect(postExecEvents).toContain('researcher-child:calculator');
366
367
  });
367
368
 
369
+ it('top-level event-driven dispatches leave agentId unset (subagent-scope marker)', async () => {
370
+ getChatModelClassSpy.mockImplementation(((provider: Providers) => {
371
+ if (provider === Providers.OPENAI) {
372
+ return class extends FakeChatModel {
373
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
374
+ constructor(_options: any) {
375
+ super({
376
+ responses: ['Calculating.', 'All done.'],
377
+ sleep: 1,
378
+ toolCalls: [createCalculatorToolCall()],
379
+ });
380
+ }
381
+ bindTools(tools: unknown): ReturnType<FakeChatModel['withConfig']> {
382
+ const config = {
383
+ tools,
384
+ } as Parameters<FakeChatModel['withConfig']>[0];
385
+ return this.withConfig(config);
386
+ }
387
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
388
+ } as any;
389
+ }
390
+ return originalGetChatModelClass(provider);
391
+ }) as typeof providers.getChatModelClass);
392
+
393
+ const registry = new HookRegistry();
394
+ const scopeEvents: string[] = [];
395
+
396
+ const preHook: HookCallback<'PreToolUse'> = async (
397
+ input
398
+ ): Promise<PreToolUseHookOutput> => {
399
+ scopeEvents.push(
400
+ `pre:${input.agentId ?? '-'}:${input.executingAgentId ?? '-'}`
401
+ );
402
+ return { decision: 'allow' };
403
+ };
404
+ registry.register('PreToolUse', { hooks: [preHook] });
405
+
406
+ const postHook: HookCallback<'PostToolUse'> = async (
407
+ input
408
+ ): Promise<PostToolUseHookOutput> => {
409
+ scopeEvents.push(
410
+ `post:${input.agentId ?? '-'}:${input.executingAgentId ?? '-'}`
411
+ );
412
+ return {};
413
+ };
414
+ registry.register('PostToolUse', { hooks: [postHook] });
415
+
416
+ const batchHook: HookCallback<'PostToolBatch'> = async (
417
+ input
418
+ ): Promise<PostToolBatchHookOutput> => {
419
+ scopeEvents.push(
420
+ `batch:${input.agentId ?? '-'}:${input.executingAgentId ?? '-'}`
421
+ );
422
+ return {};
423
+ };
424
+ registry.register('PostToolBatch', { hooks: [batchHook] });
425
+
426
+ const dispatchAgentIds: Array<string | undefined> = [];
427
+ const customHandlers: Record<string, t.EventHandler> = {
428
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
429
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
430
+ [GraphEvents.ON_TOOL_EXECUTE]: {
431
+ handle: (_event, rawData): void => {
432
+ const request = rawData as t.ToolExecuteBatchRequest;
433
+ dispatchAgentIds.push(request.agentId);
434
+ const results: t.ToolExecuteResult[] = request.toolCalls.map(
435
+ (call) => ({
436
+ toolCallId: call.id,
437
+ status: 'success',
438
+ content: '42',
439
+ })
440
+ );
441
+ request.resolve(results);
442
+ },
443
+ },
444
+ };
445
+
446
+ /**
447
+ * The LibreChat shape: a TOP-LEVEL agent whose tools ride
448
+ * `toolDefinitions` (event-driven ToolNode, host executes via
449
+ * ON_TOOL_EXECUTE). Hook inputs here must NOT carry the subagent-scope
450
+ * marker — a host hook keying on `agentId != null` (e.g. a steering
451
+ * drain that must never inject into child state) would otherwise skip
452
+ * every top-level batch. The DISPATCH payload is the opposite: hosts
453
+ * key tool/credential lookup on `request.agentId`, so it must keep
454
+ * identifying the owning agent.
455
+ */
456
+ const run = await Run.create<t.IState>({
457
+ runId: `toplevel-event-hook-${Date.now()}`,
458
+ graphConfig: {
459
+ type: 'standard',
460
+ agents: [
461
+ {
462
+ agentId: 'hook-parent',
463
+ provider: Providers.OPENAI,
464
+ clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
465
+ instructions: 'Use the calculator.',
466
+ maxContextTokens: 8000,
467
+ toolDefinitions: [calculatorDef],
468
+ },
469
+ ],
470
+ },
471
+ returnContent: true,
472
+ skipCleanup: true,
473
+ customHandlers,
474
+ hooks: registry,
475
+ });
476
+
477
+ run.Graph!.overrideTestModel(['Calculating.', 'All done.'], 5, [
478
+ createCalculatorToolCall(),
479
+ ]);
480
+
481
+ await run.processStream(
482
+ { messages: [new HumanMessage('what is 21 * 2?')] },
483
+ callerConfig
484
+ );
485
+
486
+ expect(scopeEvents).toContain('pre:-:hook-parent');
487
+ expect(scopeEvents).toContain('post:-:hook-parent');
488
+ expect(scopeEvents).toContain('batch:-:hook-parent');
489
+ expect(dispatchAgentIds).toEqual(['hook-parent']);
490
+ });
491
+
368
492
  it('child subagent tool ask hooks fail closed instead of starting unsupported nested HITL', async () => {
369
493
  getChatModelClassSpy.mockImplementation(((provider: Providers) => {
370
494
  if (provider === Providers.OPENAI) {
@@ -373,6 +373,7 @@ export class SubagentExecutor {
373
373
  agents: [childInputs],
374
374
  langfuse: this.langfuse,
375
375
  tokenCounter: this.tokenCounter,
376
+ subagentScope: true,
376
377
  /**
377
378
  * Forwarded so the child graph's own `SubagentExecutor` (created in
378
379
  * its `createAgentNode` when `allowNested` keeps subagentConfigs)
@@ -336,6 +336,16 @@ export type StandardGraphInput = {
336
336
  * they already flow through the registry's `CHAT_MODEL_END` handler.
337
337
  */
338
338
  subagentUsageSink?: SubagentUsageSink;
339
+ /**
340
+ * True when this graph IS a subagent child run (set by `SubagentExecutor`
341
+ * when it constructs the child graph). Drives the hook-input `agentId`
342
+ * subagent-scope marker: hook dispatches from this graph's tool nodes
343
+ * carry `agentId` so run-scoped host hooks — which fire for child scopes
344
+ * too, because children inherit the parent's `run_id` — can tell child
345
+ * scope from the top level. Top-level graphs leave this unset and their
346
+ * hook inputs carry only `executingAgentId`.
347
+ */
348
+ subagentScope?: boolean;
339
349
  };
340
350
 
341
351
  export type GraphEdge = {