@librechat/agents 3.2.61 → 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.
Files changed (125) hide show
  1. package/dist/cjs/common/enum.cjs +2 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +6 -2
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/hooks/HookRegistry.cjs +32 -0
  6. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  7. package/dist/cjs/hooks/executeHooks.cjs +6 -0
  8. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  9. package/dist/cjs/hooks/index.cjs +12 -0
  10. package/dist/cjs/hooks/index.cjs.map +1 -0
  11. package/dist/cjs/hooks/types.cjs.map +1 -1
  12. package/dist/cjs/langfuse.cjs +62 -1
  13. package/dist/cjs/langfuse.cjs.map +1 -1
  14. package/dist/cjs/llm/bedrock/index.cjs +266 -43
  15. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  16. package/dist/cjs/llm/openai/index.cjs +274 -7
  17. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  18. package/dist/cjs/llm/openai/streamMetadata.cjs +69 -0
  19. package/dist/cjs/llm/openai/streamMetadata.cjs.map +1 -0
  20. package/dist/cjs/llm/openrouter/index.cjs +5 -6
  21. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  22. package/dist/cjs/main.cjs +6 -1
  23. package/dist/cjs/messages/format.cjs +61 -0
  24. package/dist/cjs/messages/format.cjs.map +1 -1
  25. package/dist/cjs/messages/prune.cjs +31 -19
  26. package/dist/cjs/messages/prune.cjs.map +1 -1
  27. package/dist/cjs/stream.cjs +13 -3
  28. package/dist/cjs/stream.cjs.map +1 -1
  29. package/dist/cjs/tools/ToolNode.cjs +76 -11
  30. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  31. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +2 -0
  32. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  33. package/dist/cjs/utils/tokens.cjs +111 -0
  34. package/dist/cjs/utils/tokens.cjs.map +1 -1
  35. package/dist/esm/common/enum.mjs +2 -0
  36. package/dist/esm/common/enum.mjs.map +1 -1
  37. package/dist/esm/graphs/Graph.mjs +6 -2
  38. package/dist/esm/graphs/Graph.mjs.map +1 -1
  39. package/dist/esm/hooks/HookRegistry.mjs +32 -0
  40. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  41. package/dist/esm/hooks/executeHooks.mjs +6 -0
  42. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  43. package/dist/esm/hooks/index.mjs +12 -1
  44. package/dist/esm/hooks/index.mjs.map +1 -0
  45. package/dist/esm/hooks/types.mjs.map +1 -1
  46. package/dist/esm/langfuse.mjs +62 -1
  47. package/dist/esm/langfuse.mjs.map +1 -1
  48. package/dist/esm/llm/bedrock/index.mjs +266 -43
  49. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  50. package/dist/esm/llm/openai/index.mjs +274 -7
  51. package/dist/esm/llm/openai/index.mjs.map +1 -1
  52. package/dist/esm/llm/openai/streamMetadata.mjs +69 -0
  53. package/dist/esm/llm/openai/streamMetadata.mjs.map +1 -0
  54. package/dist/esm/llm/openrouter/index.mjs +5 -6
  55. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  56. package/dist/esm/main.mjs +3 -3
  57. package/dist/esm/messages/format.mjs +61 -0
  58. package/dist/esm/messages/format.mjs.map +1 -1
  59. package/dist/esm/messages/prune.mjs +31 -19
  60. package/dist/esm/messages/prune.mjs.map +1 -1
  61. package/dist/esm/stream.mjs +13 -3
  62. package/dist/esm/stream.mjs.map +1 -1
  63. package/dist/esm/tools/ToolNode.mjs +76 -11
  64. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  65. package/dist/esm/tools/subagent/SubagentExecutor.mjs +2 -0
  66. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  67. package/dist/esm/utils/tokens.mjs +108 -1
  68. package/dist/esm/utils/tokens.mjs.map +1 -1
  69. package/dist/types/common/enum.d.ts +3 -1
  70. package/dist/types/graphs/Graph.d.ts +3 -1
  71. package/dist/types/hooks/HookRegistry.d.ts +10 -0
  72. package/dist/types/hooks/index.d.ts +7 -0
  73. package/dist/types/hooks/types.d.ts +18 -1
  74. package/dist/types/llm/bedrock/index.d.ts +5 -0
  75. package/dist/types/llm/openai/index.d.ts +19 -0
  76. package/dist/types/llm/openai/streamMetadata.d.ts +16 -0
  77. package/dist/types/messages/prune.d.ts +4 -3
  78. package/dist/types/tools/ToolNode.d.ts +1 -0
  79. package/dist/types/types/graph.d.ts +10 -0
  80. package/dist/types/types/llm.d.ts +12 -2
  81. package/dist/types/types/tools.d.ts +1 -1
  82. package/dist/types/utils/tokens.d.ts +30 -0
  83. package/package.json +6 -6
  84. package/src/__tests__/stream.eagerEventExecution.test.ts +100 -2
  85. package/src/common/enum.ts +2 -0
  86. package/src/graphs/Graph.ts +12 -4
  87. package/src/hooks/HookRegistry.ts +45 -0
  88. package/src/hooks/__tests__/HookRegistry.test.ts +48 -0
  89. package/src/hooks/__tests__/executeHooks.test.ts +85 -2
  90. package/src/hooks/executeHooks.ts +15 -0
  91. package/src/hooks/index.ts +7 -0
  92. package/src/hooks/types.ts +18 -1
  93. package/src/langfuse.ts +134 -1
  94. package/src/llm/bedrock/index.ts +434 -83
  95. package/src/llm/bedrock/streamSealDispatch.test.ts +97 -0
  96. package/src/llm/custom-chat-models.smoke.test.ts +7 -0
  97. package/src/llm/openai/index.ts +604 -6
  98. package/src/llm/openai/managedRequests.test.ts +182 -0
  99. package/src/llm/openai/streamMetadata.spec.ts +86 -0
  100. package/src/llm/openai/streamMetadata.ts +95 -0
  101. package/src/llm/openai/streamMetadataDedup.spec.ts +166 -0
  102. package/src/llm/openrouter/index.ts +9 -5
  103. package/src/messages/format.ts +96 -3
  104. package/src/messages/formatAgentMessages.steer.test.ts +326 -0
  105. package/src/messages/labelContentByAgent.test.ts +75 -0
  106. package/src/messages/prune.ts +56 -30
  107. package/src/specs/anthropic.simple.test.ts +4 -2
  108. package/src/specs/cache.simple.test.ts +17 -4
  109. package/src/specs/langfuse-callbacks.test.ts +61 -0
  110. package/src/specs/openai.simple.test.ts +4 -2
  111. package/src/specs/spec.utils.ts +12 -0
  112. package/src/specs/summarization.test.ts +9 -13
  113. package/src/specs/token-accounting-pipeline.test.ts +130 -4
  114. package/src/specs/tokens.test.ts +214 -0
  115. package/src/stream.ts +22 -3
  116. package/src/tools/ToolNode.ts +112 -13
  117. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +554 -0
  118. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +49 -2
  119. package/src/tools/__tests__/hitl.test.ts +112 -0
  120. package/src/tools/__tests__/subagentHooks.test.ts +124 -0
  121. package/src/tools/subagent/SubagentExecutor.ts +3 -0
  122. package/src/types/graph.ts +10 -0
  123. package/src/types/llm.ts +12 -2
  124. package/src/types/tools.ts +1 -1
  125. package/src/utils/tokens.ts +181 -3
@@ -12,6 +12,7 @@ const ERROR_MESSAGE_MAX_CHARS = 200;
12
12
  const MAX_PENDING_SUBAGENT_UPDATES = 64;
13
13
  const HOOK_FALLBACK = Object.freeze({
14
14
  additionalContexts: [],
15
+ injectedMessages: [],
15
16
  errors: []
16
17
  });
17
18
  const LANGGRAPH_RUNTIME_CONFIG_PREFIX = "__pregel_";
@@ -110,6 +111,7 @@ var SubagentExecutor = class {
110
111
  agents: [childInputs],
111
112
  langfuse: this.langfuse,
112
113
  tokenCounter: this.tokenCounter,
114
+ subagentScope: true,
113
115
  /**
114
116
  * Forwarded so the child graph's own `SubagentExecutor` (created in
115
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 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 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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"}
@@ -25,6 +25,32 @@ const BASE64_BYTES_PER_PDF_PAGE = 75e3;
25
25
  /** Fallback token cost for URL-referenced documents without local data. */
26
26
  const URL_DOCUMENT_FALLBACK_TOKENS = 2e3;
27
27
  /**
28
+ * Timed media (video/audio) is priced by DURATION, not size, and the content
29
+ * carries no duration — so duration is estimated from encoded size at
30
+ * representative bitrates and priced at Gemini's rates (the dominant provider
31
+ * for native video/audio). Deliberately rough; superseded by real provider
32
+ * usage after the first turn.
33
+ */
34
+ /** Gemini video ≈ 258 tokens/frame (1 fps) + 32 tokens/s audio ≈ 300 tokens/s. */
35
+ const VIDEO_TOKENS_PER_SECOND = 300;
36
+ /** Gemini audio: 32 tokens/s (single channel). */
37
+ const AUDIO_TOKENS_PER_SECOND = 32;
38
+ /** Representative encoded byte rates for duration-from-size estimation. */
39
+ const VIDEO_BYTES_PER_SECOND = 25e4;
40
+ const AUDIO_BYTES_PER_SECOND = 16e3;
41
+ /** Flat fallback when only a URL is present (no size to estimate from) — ~30s. */
42
+ const VIDEO_URL_FALLBACK_TOKENS = 9e3;
43
+ const AUDIO_URL_FALLBACK_TOKENS = 960;
44
+ /** Content block types that can carry timed media (video/audio). `media` is
45
+ * generic (Google) so it is classified by MIME; the rest are unambiguous. */
46
+ const TIMED_MEDIA_TYPES = new Set([
47
+ "media",
48
+ "video",
49
+ "audio",
50
+ "video_url",
51
+ "input_audio"
52
+ ]);
53
+ /**
28
54
  * Extracts image dimensions from the first bytes of a base64-encoded
29
55
  * PNG, JPEG, GIF, or WebP without decoding the full image.
30
56
  * Returns null if the format is unrecognized or data is too short.
@@ -147,6 +173,83 @@ function estimateDocumentBlockTokens(block, encoding, getTokenCount) {
147
173
  }
148
174
  return URL_DOCUMENT_FALLBACK_TOKENS;
149
175
  }
176
+ /** Decoded byte length of base64 or a `data:` URL; 0 for any remote URI (http,
177
+ * gs, s3, file, …) or empty. Base64 never contains `:`, so any scheme prefix
178
+ * marks a remote reference with no local size. */
179
+ function base64ByteLength(value) {
180
+ if (typeof value !== "string" || value.length === 0) return 0;
181
+ if (value.startsWith("data:")) {
182
+ const comma = value.indexOf(",");
183
+ return comma < 0 ? 0 : Math.floor((value.length - comma - 1) * 3 / 4);
184
+ }
185
+ if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return 0;
186
+ return Math.floor(value.length * 3 / 4);
187
+ }
188
+ function timedMediaTokens(bytes, bytesPerSecond, tokensPerSecond, urlFallback) {
189
+ if (bytes <= 0) return urlFallback;
190
+ return Math.max(tokensPerSecond, Math.ceil(bytes / bytesPerSecond * tokensPerSecond));
191
+ }
192
+ /** Decoded byte length of a media block payload — top-level `data` (base64
193
+ * string or `Uint8Array`) or base64 `url`, or the nested `video|audio.{data,
194
+ * url, source.bytes}` shapes (`formatMessage` media arrays / native Bedrock);
195
+ * 0 for a bare remote URL, `fileId`/`fileUri`, S3 location, or empty. */
196
+ function mediaBlockByteLength(block) {
197
+ const data = block.data;
198
+ if (typeof data === "string") return base64ByteLength(data);
199
+ if (data instanceof Uint8Array) return data.length;
200
+ if (typeof block.url === "string") return base64ByteLength(block.url);
201
+ const nested = block.video ?? block.audio;
202
+ if (nested != null) {
203
+ if (typeof nested.data === "string") return base64ByteLength(nested.data);
204
+ if (typeof nested.url === "string") return base64ByteLength(nested.url);
205
+ if (nested.source?.bytes instanceof Uint8Array) return nested.source.bytes.length;
206
+ }
207
+ return 0;
208
+ }
209
+ /** Classifies a block as timed media, or null when it is not video/audio — e.g.
210
+ * a generic Google `media` block carrying an image/document MIME, which must
211
+ * NOT be priced as video. Also handles the Google shape where `type` IS the
212
+ * MIME string (e.g. `{ type: 'audio/wav', data }`). */
213
+ function timedMediaKind(block) {
214
+ const type = typeof block.type === "string" ? block.type : "";
215
+ if (type === "input_audio" || type === "audio") return "audio";
216
+ if (type === "video_url" || type === "video") return "video";
217
+ const mime = type === "media" && typeof block.mimeType === "string" ? block.mimeType : type;
218
+ if (mime.startsWith("audio/")) return "audio";
219
+ if (mime.startsWith("video/")) return "video";
220
+ return null;
221
+ }
222
+ /** Whether a content-block `type` can carry timed media — the fixed set plus the
223
+ * Google MIME-as-type shape (`audio/*` / `video/*`). Gates callers before they
224
+ * hand the block to {@link estimateTimedMediaBlockTokens}. */
225
+ function isTimedMediaType(type) {
226
+ return TIMED_MEDIA_TYPES.has(type) || type.startsWith("audio/") || type.startsWith("video/");
227
+ }
228
+ /**
229
+ * Estimates token cost for a timed-media block (video/audio). Handles Google
230
+ * `{ type: 'media', mimeType, data|url|fileUri }`, OpenRouter
231
+ * `{ type: 'video_url' }` / `{ type: 'input_audio' }`, and standard
232
+ * `{ type: 'video' }` / `{ type: 'audio' }` blocks (payload as `data` base64 or
233
+ * `Uint8Array`, base64 `url`, or `fileId`). Duration is inferred from encoded
234
+ * size (providers price by duration, which the block does not carry) at Gemini's
235
+ * rates; a payload with no size (bare URL / file id) falls back to a ~30s
236
+ * estimate. Returns 0 for non-timed media (e.g. an image/document `media` block)
237
+ * so it is never mispriced as video.
238
+ */
239
+ function estimateTimedMediaBlockTokens(block) {
240
+ const kind = timedMediaKind(block);
241
+ if (kind == null) return 0;
242
+ let bytes;
243
+ if (block.type === "input_audio") {
244
+ const audio = block.input_audio;
245
+ bytes = base64ByteLength(audio?.data);
246
+ } else if (block.type === "video_url") {
247
+ const video = block.video_url;
248
+ bytes = base64ByteLength(typeof video === "string" ? video : video?.url);
249
+ } else bytes = mediaBlockByteLength(block);
250
+ if (kind === "audio") return timedMediaTokens(bytes, AUDIO_BYTES_PER_SECOND, AUDIO_TOKENS_PER_SECOND, AUDIO_URL_FALLBACK_TOKENS);
251
+ return timedMediaTokens(bytes, VIDEO_BYTES_PER_SECOND, VIDEO_TOKENS_PER_SECOND, VIDEO_URL_FALLBACK_TOKENS);
252
+ }
150
253
  const tokenizers = {};
151
254
  async function getTokenizer(encoding = "o200k_base") {
152
255
  const cached = tokenizers[encoding];
@@ -174,6 +277,10 @@ function getTokenCountForMessage(message, getTokenCount, encoding = "o200k_base"
174
277
  numTokens += Math.ceil(estimateDocumentBlockTokens(item, encoding, getTokenCount) * IMAGE_TOKEN_SAFETY_MARGIN);
175
278
  continue;
176
279
  }
280
+ if (isTimedMediaType(item.type)) {
281
+ numTokens += Math.ceil(estimateTimedMediaBlockTokens(item) * IMAGE_TOKEN_SAFETY_MARGIN);
282
+ continue;
283
+ }
177
284
  if (item.type === "tool_call" && item.tool_call != null) {
178
285
  const toolName = item.tool_call.name;
179
286
  if (typeof toolName === "string" && toolName.length > 0) numTokens += getTokenCount(toolName);
@@ -254,6 +361,6 @@ const TokenEncoderManager = {
254
361
  }
255
362
  };
256
363
  //#endregion
257
- export { TokenEncoderManager, apportionTokenCounts, createTokenCounter, encodingForModel, estimateAnthropicImageTokens, estimateOpenAIImageTokens, extractImageDimensions, getTokenCountForMessage };
364
+ export { IMAGE_TOKEN_SAFETY_MARGIN, TokenEncoderManager, apportionTokenCounts, createTokenCounter, encodingForModel, estimateAnthropicImageTokens, estimateDocumentBlockTokens, estimateImageBlockTokens, estimateOpenAIImageTokens, estimateTimedMediaBlockTokens, extractImageDimensions, getTokenCountForMessage };
258
365
 
259
366
  //# sourceMappingURL=tokens.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.mjs","names":[],"sources":["../../../src/utils/tokens.ts"],"sourcesContent":["import { Tokenizer } from 'ai-tokenizer';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { ContentTypes } from '@/common/enum';\n\nexport type EncodingName = 'o200k_base' | 'claude';\n\n/** Anthropic minimum image token cost. */\nconst ANTHROPIC_IMAGE_MIN_TOKENS = 1024;\n/** Anthropic divisor: tokens = width × height / 750. */\nconst ANTHROPIC_IMAGE_DIVISOR = 750;\n/** OpenAI low-detail fixed cost. */\nconst OPENAI_IMAGE_LOW_TOKENS = 85;\n/** OpenAI high-detail tile size. */\nconst OPENAI_IMAGE_TILE_SIZE = 512;\n/** OpenAI high-detail tokens per tile. */\nconst OPENAI_IMAGE_TOKENS_PER_TILE = 170;\n/** Google Gemini fixed per-image cost. */\nconst _GEMINI_IMAGE_TOKENS = 258;\n/** Safety margin for image and document token estimates (5% overestimate). */\nconst IMAGE_TOKEN_SAFETY_MARGIN = 1.05;\n\n/**\n * Anthropic PDF: each page costs image tokens + text tokens.\n * Typical range is 1500-3000 tokens/page. Using 2000 as midpoint.\n */\nconst ANTHROPIC_PDF_TOKENS_PER_PAGE = 2000;\n/** OpenAI PDF: each page rendered as high-detail image. ~1500 tokens typical. */\nconst OPENAI_PDF_TOKENS_PER_PAGE = 1500;\n/** Gemini PDF: fixed 258 tokens per page. */\nconst _GEMINI_PDF_TOKENS_PER_PAGE = 258;\n/** Approximate base64 bytes per PDF page for page count estimation. */\nconst BASE64_BYTES_PER_PDF_PAGE = 75_000;\n/** Fallback token cost for URL-referenced documents without local data. */\nconst URL_DOCUMENT_FALLBACK_TOKENS = 2000;\n\n/**\n * Extracts image dimensions from the first bytes of a base64-encoded\n * PNG, JPEG, GIF, or WebP without decoding the full image.\n * Returns null if the format is unrecognized or data is too short.\n */\nexport function extractImageDimensions(\n base64Data: string\n): { width: number; height: number } | null {\n const raw = base64Data.startsWith('data:')\n ? base64Data.slice(base64Data.indexOf(',') + 1)\n : base64Data;\n\n if (raw.length < 32) {\n return null;\n }\n\n const bytes = new Uint8Array(Buffer.from(raw.slice(0, 80), 'base64'));\n\n if (bytes[0] === 0x89 && bytes[1] === 0x50) {\n // PNG: width at bytes 16-19, height at 20-23 (big-endian)\n const width =\n (bytes[16] << 24) | (bytes[17] << 16) | (bytes[18] << 8) | bytes[19];\n const height =\n (bytes[20] << 24) | (bytes[21] << 16) | (bytes[22] << 8) | bytes[23];\n return { width, height };\n }\n\n if (bytes[0] === 0xff && bytes[1] === 0xd8) {\n // JPEG: scan for SOF0 (0xFFC0) or SOF2 (0xFFC2) marker\n for (let i = 2; i < bytes.length - 9; i++) {\n if (\n bytes[i] === 0xff &&\n (bytes[i + 1] === 0xc0 || bytes[i + 1] === 0xc2)\n ) {\n const height = (bytes[i + 5] << 8) | bytes[i + 6];\n const width = (bytes[i + 7] << 8) | bytes[i + 8];\n return { width, height };\n }\n }\n return null;\n }\n\n if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {\n // GIF: width at bytes 6-7, height at 8-9 (little-endian)\n const width = bytes[6] | (bytes[7] << 8);\n const height = bytes[8] | (bytes[9] << 8);\n return { width, height };\n }\n\n if (\n bytes[0] === 0x52 &&\n bytes[1] === 0x49 &&\n bytes[2] === 0x46 &&\n bytes[3] === 0x46 &&\n bytes[8] === 0x57 &&\n bytes[9] === 0x45 &&\n bytes[10] === 0x42 &&\n bytes[11] === 0x50\n ) {\n // WebP VP8: width at bytes 26-27, height at 28-29\n if (bytes.length > 29) {\n const width = (bytes[26] | (bytes[27] << 8)) & 0x3fff;\n const height = (bytes[28] | (bytes[29] << 8)) & 0x3fff;\n return { width, height };\n }\n return null;\n }\n\n return null;\n}\n\n/** Estimates image token cost for Anthropic/Bedrock (Claude). */\nexport function estimateAnthropicImageTokens(\n width: number,\n height: number\n): number {\n return Math.max(\n ANTHROPIC_IMAGE_MIN_TOKENS,\n Math.ceil((width * height) / ANTHROPIC_IMAGE_DIVISOR)\n );\n}\n\n/** Estimates image token cost for OpenAI (high detail). */\nexport function estimateOpenAIImageTokens(\n width: number,\n height: number,\n detail: string = 'high'\n): number {\n if (detail === 'low') {\n return OPENAI_IMAGE_LOW_TOKENS;\n }\n const tiles =\n Math.ceil(width / OPENAI_IMAGE_TILE_SIZE) *\n Math.ceil(height / OPENAI_IMAGE_TILE_SIZE);\n return OPENAI_IMAGE_LOW_TOKENS + tiles * OPENAI_IMAGE_TOKENS_PER_TILE;\n}\n\n/**\n * Estimates token cost for an image content block.\n * Extracts dimensions from base64 header when available.\n * Falls back to Anthropic minimum (1024) when dimensions can't be determined.\n */\nfunction estimateImageBlockTokens(\n block: Record<string, unknown>,\n encoding: EncodingName\n): number {\n let base64Data: string | undefined;\n\n if (block.type === ContentTypes.IMAGE_URL || block.type === 'image_url') {\n const imageUrl = block.image_url as string | { url?: string } | undefined;\n const url = typeof imageUrl === 'string' ? imageUrl : imageUrl?.url;\n if (typeof url === 'string' && url.startsWith('data:')) {\n base64Data = url;\n } else {\n return ANTHROPIC_IMAGE_MIN_TOKENS;\n }\n } else if (block.type === 'image') {\n const source = block.source as { type?: string; data?: string } | undefined;\n if (source?.type === 'base64' && typeof source.data === 'string') {\n base64Data = source.data;\n } else {\n return ANTHROPIC_IMAGE_MIN_TOKENS;\n }\n } else {\n return ANTHROPIC_IMAGE_MIN_TOKENS;\n }\n\n const dims = extractImageDimensions(base64Data);\n if (dims == null) {\n return ANTHROPIC_IMAGE_MIN_TOKENS;\n }\n\n if (encoding === 'claude') {\n return estimateAnthropicImageTokens(dims.width, dims.height);\n }\n return estimateOpenAIImageTokens(dims.width, dims.height);\n}\n\n/**\n * Estimates token cost for a document/file content block.\n * Handles both LangChain standard format (`type: 'file'` with `source_type`)\n * and Anthropic format (`type: 'document'` with `source`).\n *\n * - Plain text: tokenized directly via `getTokenCount`.\n * - Base64 PDF: page count estimated from base64 length × per-page cost.\n * - URL reference: conservative flat estimate.\n */\nfunction estimateDocumentBlockTokens(\n block: Record<string, unknown>,\n encoding: EncodingName,\n getTokenCount: (text: string) => number\n): number {\n const pdfTokensPerPage =\n encoding === 'claude'\n ? ANTHROPIC_PDF_TOKENS_PER_PAGE\n : OPENAI_PDF_TOKENS_PER_PAGE;\n\n // LangChain standard format: type='file', source_type, data/text/url, mime_type\n const sourceType = block.source_type as string | undefined;\n if (typeof sourceType === 'string') {\n const mimeType = ((block.mime_type as string | undefined) ?? '').split(\n ';'\n )[0];\n\n if (sourceType === 'text' && typeof block.text === 'string') {\n return getTokenCount(block.text as string);\n }\n\n if (sourceType === 'base64' && typeof block.data === 'string') {\n if (mimeType === 'application/pdf' || mimeType === '') {\n const pageEstimate = Math.max(\n 1,\n Math.ceil((block.data as string).length / BASE64_BYTES_PER_PDF_PAGE)\n );\n return pageEstimate * pdfTokensPerPage;\n }\n // Image inside a file block — delegate to image estimation\n if (mimeType.startsWith('image/')) {\n return estimateImageBlockTokens(\n {\n ...block,\n type: 'image',\n source: { type: 'base64', data: block.data },\n },\n encoding\n );\n }\n return getTokenCount(block.data as string);\n }\n\n if (sourceType === 'url') {\n return URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n return URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n // Anthropic format: type='document', source: { type, data, media_type }\n const source = block.source as\n | {\n type?: string;\n data?: string;\n media_type?: string;\n content?: unknown[];\n }\n | undefined;\n\n if (source == null) {\n return URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n if (source.type === 'text' && typeof source.data === 'string') {\n return getTokenCount(source.data);\n }\n\n if (source.type === 'base64' && typeof source.data === 'string') {\n const mediaType = (source.media_type ?? '').split(';')[0];\n if (mediaType === 'application/pdf' || mediaType === '') {\n const pageEstimate = Math.max(\n 1,\n Math.ceil(source.data.length / BASE64_BYTES_PER_PDF_PAGE)\n );\n return pageEstimate * pdfTokensPerPage;\n }\n if (mediaType.startsWith('image/')) {\n return estimateImageBlockTokens(\n { type: 'image', source: { type: 'base64', data: source.data } },\n encoding\n );\n }\n return getTokenCount(source.data);\n }\n\n if (source.type === 'url') {\n return URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n // content-type source (wraps other blocks like images)\n if (source.type === 'content' && Array.isArray(source.content)) {\n let total = 0;\n for (const inner of source.content) {\n if (inner != null && typeof inner === 'object' && 'type' in inner) {\n const innerBlock = inner as Record<string, unknown>;\n if (innerBlock.type === 'image') {\n total += estimateImageBlockTokens(innerBlock, encoding);\n }\n }\n }\n return total > 0 ? total : URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n return URL_DOCUMENT_FALLBACK_TOKENS;\n}\n\nconst tokenizers: Partial<Record<EncodingName, Tokenizer>> = {};\n\nasync function getTokenizer(\n encoding: EncodingName = 'o200k_base'\n): Promise<Tokenizer> {\n const cached = tokenizers[encoding];\n if (cached) {\n return cached;\n }\n const data =\n encoding === 'claude'\n ? await import('ai-tokenizer/encoding/claude')\n : await import('ai-tokenizer/encoding/o200k_base');\n const instance = new Tokenizer(data);\n tokenizers[encoding] = instance;\n return instance;\n}\n\nexport function encodingForModel(model: string): EncodingName {\n if (model.toLowerCase().includes('claude')) {\n return 'claude';\n }\n return 'o200k_base';\n}\n\nexport function getTokenCountForMessage(\n message: BaseMessage,\n getTokenCount: (text: string) => number,\n encoding: EncodingName = 'o200k_base'\n): number {\n const tokensPerMessage = 3;\n\n type ContentBlock = Record<string, unknown> & {\n type?: string;\n tool_call?: { name?: string; args?: string; output?: string };\n };\n\n const processValue = (value: unknown): void => {\n if (Array.isArray(value)) {\n for (const raw of value) {\n const item = raw as ContentBlock | null | undefined;\n if (item == null || typeof item.type !== 'string') {\n continue;\n }\n if (item.type === ContentTypes.ERROR) {\n continue;\n }\n\n if (\n item.type === ContentTypes.IMAGE_URL ||\n item.type === 'image_url' ||\n item.type === 'image'\n ) {\n numTokens += Math.ceil(\n estimateImageBlockTokens(item, encoding) * IMAGE_TOKEN_SAFETY_MARGIN\n );\n continue;\n }\n\n if (\n item.type === 'document' ||\n item.type === 'file' ||\n item.type === ContentTypes.IMAGE_FILE\n ) {\n numTokens += Math.ceil(\n estimateDocumentBlockTokens(item, encoding, getTokenCount) *\n IMAGE_TOKEN_SAFETY_MARGIN\n );\n continue;\n }\n\n if (item.type === ContentTypes.TOOL_CALL && item.tool_call != null) {\n const toolName = item.tool_call.name;\n if (typeof toolName === 'string' && toolName.length > 0) {\n numTokens += getTokenCount(toolName);\n }\n const args = item.tool_call.args;\n if (typeof args === 'string' && args.length > 0) {\n numTokens += getTokenCount(args);\n }\n const output = item.tool_call.output;\n if (typeof output === 'string' && output.length > 0) {\n numTokens += getTokenCount(output);\n }\n continue;\n }\n\n const nestedValue = item[item.type];\n if (nestedValue == null) {\n continue;\n }\n\n processValue(nestedValue);\n }\n } else if (typeof value === 'string') {\n numTokens += getTokenCount(value);\n } else if (typeof value === 'number') {\n numTokens += getTokenCount(value.toString());\n } else if (typeof value === 'boolean') {\n numTokens += getTokenCount(value.toString());\n }\n };\n\n let numTokens = tokensPerMessage;\n processValue(message.content);\n return numTokens;\n}\n\n/**\n * Largest-remainder apportionment: scales each count by `multiplier` and\n * distributes the rounding remainder so the results sum exactly to\n * `targetTotal`. Keeps per-item breakdowns reconciled with an aggregate\n * computed as a single rounded product of the summed raw counts.\n */\nexport function apportionTokenCounts(\n rawCounts: Record<string, number>,\n multiplier: number,\n targetTotal: number\n): Record<string, number> {\n const result: Record<string, number> = Object.create(null);\n const remainders: Array<{ name: string; remainder: number }> = [];\n let floorSum = 0;\n for (const [name, rawCount] of Object.entries(rawCounts)) {\n const scaled = rawCount * multiplier;\n const floored = Math.floor(scaled);\n result[name] = floored;\n floorSum += floored;\n remainders.push({ name, remainder: scaled - floored });\n }\n let leftover = targetTotal - floorSum;\n if (leftover <= 0 || remainders.length === 0) {\n return result;\n }\n remainders.sort((a, b) => b.remainder - a.remainder);\n for (let i = 0; leftover > 0; i = (i + 1) % remainders.length) {\n result[remainders[i].name] += 1;\n leftover--;\n }\n return result;\n}\n\n/**\n * Anthropic's API consistently reports ~10% more tokens than the local\n * claude tokenizer due to internal message framing and content encoding.\n * Verified empirically across content types via the count_tokens endpoint.\n */\nconst CLAUDE_TOKEN_CORRECTION = 1.1;\n\n/**\n * Creates a token counter function using the specified encoding.\n * Lazily loads the encoding data on first use via dynamic import.\n */\nexport const createTokenCounter = async (\n encoding: EncodingName = 'o200k_base'\n): Promise<(message: BaseMessage) => number> => {\n const tok = await getTokenizer(encoding);\n const countTokens = (text: string): number => tok.count(text);\n const isClaude = encoding === 'claude';\n return (message: BaseMessage): number => {\n const count = getTokenCountForMessage(message, countTokens, encoding);\n return isClaude ? Math.ceil(count * CLAUDE_TOKEN_CORRECTION) : count;\n };\n};\n\n/** Utility to manage the token encoder lifecycle explicitly. */\nexport const TokenEncoderManager = {\n async initialize(): Promise<void> {\n // No-op: ai-tokenizer is synchronously initialized from bundled data.\n },\n\n reset(): void {\n for (const key of Object.keys(tokenizers)) {\n delete tokenizers[key as EncodingName];\n }\n },\n\n isInitialized(): boolean {\n return Object.keys(tokenizers).length > 0;\n },\n};\n"],"mappings":";;;;AAOA,MAAM,6BAA6B;;AAEnC,MAAM,0BAA0B;;AAEhC,MAAM,0BAA0B;;AAEhC,MAAM,yBAAyB;;AAE/B,MAAM,+BAA+B;;AAIrC,MAAM,4BAA4B;;;;;AAMlC,MAAM,gCAAgC;;AAEtC,MAAM,6BAA6B;;AAInC,MAAM,4BAA4B;;AAElC,MAAM,+BAA+B;;;;;;AAOrC,SAAgB,uBACd,YAC0C;CAC1C,MAAM,MAAM,WAAW,WAAW,OAAO,IACrC,WAAW,MAAM,WAAW,QAAQ,GAAG,IAAI,CAAC,IAC5C;CAEJ,IAAI,IAAI,SAAS,IACf,OAAO;CAGT,MAAM,QAAQ,IAAI,WAAW,OAAO,KAAK,IAAI,MAAM,GAAG,EAAE,GAAG,QAAQ,CAAC;CAEpE,IAAI,MAAM,OAAO,OAAQ,MAAM,OAAO,IAMpC,OAAO;EAAE,OAHN,MAAM,OAAO,KAAO,MAAM,OAAO,KAAO,MAAM,OAAO,IAAK,MAAM;EAGnD,QADb,MAAM,OAAO,KAAO,MAAM,OAAO,KAAO,MAAM,OAAO,IAAK,MAAM;CAC5C;CAGzB,IAAI,MAAM,OAAO,OAAQ,MAAM,OAAO,KAAM;EAE1C,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KACpC,IACE,MAAM,OAAO,QACZ,MAAM,IAAI,OAAO,OAAQ,MAAM,IAAI,OAAO,MAC3C;GACA,MAAM,SAAU,MAAM,IAAI,MAAM,IAAK,MAAM,IAAI;GAE/C,OAAO;IAAE,OADM,MAAM,IAAI,MAAM,IAAK,MAAM,IAAI;IAC9B;GAAO;EACzB;EAEF,OAAO;CACT;CAEA,IAAI,MAAM,OAAO,MAAQ,MAAM,OAAO,MAAQ,MAAM,OAAO,IAIzD,OAAO;EAAE,OAFK,MAAM,KAAM,MAAM,MAAM;EAEtB,QADD,MAAM,KAAM,MAAM,MAAM;CAChB;CAGzB,IACE,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,QAAQ,MACd,MAAM,QAAQ,IACd;EAEA,IAAI,MAAM,SAAS,IAGjB,OAAO;GAAE,QAFM,MAAM,MAAO,MAAM,OAAO,KAAM;GAE/B,SADA,MAAM,MAAO,MAAM,OAAO,KAAM;EACzB;EAEzB,OAAO;CACT;CAEA,OAAO;AACT;;AAGA,SAAgB,6BACd,OACA,QACQ;CACR,OAAO,KAAK,IACV,4BACA,KAAK,KAAM,QAAQ,SAAU,uBAAuB,CACtD;AACF;;AAGA,SAAgB,0BACd,OACA,QACA,SAAiB,QACT;CACR,IAAI,WAAW,OACb,OAAO;CAKT,OAAO,0BAFL,KAAK,KAAK,QAAQ,sBAAsB,IACxC,KAAK,KAAK,SAAS,sBAAsB,IACF;AAC3C;;;;;;AAOA,SAAS,yBACP,OACA,UACQ;CACR,IAAI;CAEJ,IAAI,MAAM,SAAA,eAAmC,MAAM,SAAS,aAAa;EACvE,MAAM,WAAW,MAAM;EACvB,MAAM,MAAM,OAAO,aAAa,WAAW,WAAW,UAAU;EAChE,IAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,OAAO,GACnD,aAAa;OAEb,OAAO;CAEX,OAAO,IAAI,MAAM,SAAS,SAAS;EACjC,MAAM,SAAS,MAAM;EACrB,IAAI,QAAQ,SAAS,YAAY,OAAO,OAAO,SAAS,UACtD,aAAa,OAAO;OAEpB,OAAO;CAEX,OACE,OAAO;CAGT,MAAM,OAAO,uBAAuB,UAAU;CAC9C,IAAI,QAAQ,MACV,OAAO;CAGT,IAAI,aAAa,UACf,OAAO,6BAA6B,KAAK,OAAO,KAAK,MAAM;CAE7D,OAAO,0BAA0B,KAAK,OAAO,KAAK,MAAM;AAC1D;;;;;;;;;;AAWA,SAAS,4BACP,OACA,UACA,eACQ;CACR,MAAM,mBACJ,aAAa,WACT,gCACA;CAGN,MAAM,aAAa,MAAM;CACzB,IAAI,OAAO,eAAe,UAAU;EAClC,MAAM,YAAa,MAAM,aAAoC,GAAA,CAAI,MAC/D,GACF,CAAC,CAAC;EAEF,IAAI,eAAe,UAAU,OAAO,MAAM,SAAS,UACjD,OAAO,cAAc,MAAM,IAAc;EAG3C,IAAI,eAAe,YAAY,OAAO,MAAM,SAAS,UAAU;GAC7D,IAAI,aAAa,qBAAqB,aAAa,IAKjD,OAJqB,KAAK,IACxB,GACA,KAAK,KAAM,MAAM,KAAgB,SAAS,yBAAyB,CAEnD,IAAI;GAGxB,IAAI,SAAS,WAAW,QAAQ,GAC9B,OAAO,yBACL;IACE,GAAG;IACH,MAAM;IACN,QAAQ;KAAE,MAAM;KAAU,MAAM,MAAM;IAAK;GAC7C,GACA,QACF;GAEF,OAAO,cAAc,MAAM,IAAc;EAC3C;EAEA,IAAI,eAAe,OACjB,OAAO;EAGT,OAAO;CACT;CAGA,MAAM,SAAS,MAAM;CASrB,IAAI,UAAU,MACZ,OAAO;CAGT,IAAI,OAAO,SAAS,UAAU,OAAO,OAAO,SAAS,UACnD,OAAO,cAAc,OAAO,IAAI;CAGlC,IAAI,OAAO,SAAS,YAAY,OAAO,OAAO,SAAS,UAAU;EAC/D,MAAM,aAAa,OAAO,cAAc,GAAA,CAAI,MAAM,GAAG,CAAC,CAAC;EACvD,IAAI,cAAc,qBAAqB,cAAc,IAKnD,OAJqB,KAAK,IACxB,GACA,KAAK,KAAK,OAAO,KAAK,SAAS,yBAAyB,CAExC,IAAI;EAExB,IAAI,UAAU,WAAW,QAAQ,GAC/B,OAAO,yBACL;GAAE,MAAM;GAAS,QAAQ;IAAE,MAAM;IAAU,MAAM,OAAO;GAAK;EAAE,GAC/D,QACF;EAEF,OAAO,cAAc,OAAO,IAAI;CAClC;CAEA,IAAI,OAAO,SAAS,OAClB,OAAO;CAIT,IAAI,OAAO,SAAS,aAAa,MAAM,QAAQ,OAAO,OAAO,GAAG;EAC9D,IAAI,QAAQ;EACZ,KAAK,MAAM,SAAS,OAAO,SACzB,IAAI,SAAS,QAAQ,OAAO,UAAU,YAAY,UAAU,OAAO;GACjE,MAAM,aAAa;GACnB,IAAI,WAAW,SAAS,SACtB,SAAS,yBAAyB,YAAY,QAAQ;EAE1D;EAEF,OAAO,QAAQ,IAAI,QAAQ;CAC7B;CAEA,OAAO;AACT;AAEA,MAAM,aAAuD,CAAC;AAE9D,eAAe,aACb,WAAyB,cACL;CACpB,MAAM,SAAS,WAAW;CAC1B,IAAI,QACF,OAAO;CAMT,MAAM,WAAW,IAAI,UAHnB,aAAa,WACT,MAAM,OAAO,kCACb,MAAM,OAAO,mCACgB;CACnC,WAAW,YAAY;CACvB,OAAO;AACT;AAEA,SAAgB,iBAAiB,OAA6B;CAC5D,IAAI,MAAM,YAAY,CAAC,CAAC,SAAS,QAAQ,GACvC,OAAO;CAET,OAAO;AACT;AAEA,SAAgB,wBACd,SACA,eACA,WAAyB,cACjB;CACR,MAAM,mBAAmB;CAOzB,MAAM,gBAAgB,UAAyB;EAC7C,IAAI,MAAM,QAAQ,KAAK,GACrB,KAAK,MAAM,OAAO,OAAO;GACvB,MAAM,OAAO;GACb,IAAI,QAAQ,QAAQ,OAAO,KAAK,SAAS,UACvC;GAEF,IAAI,KAAK,SAAA,SACP;GAGF,IACE,KAAK,SAAA,eACL,KAAK,SAAS,eACd,KAAK,SAAS,SACd;IACA,aAAa,KAAK,KAChB,yBAAyB,MAAM,QAAQ,IAAI,yBAC7C;IACA;GACF;GAEA,IACE,KAAK,SAAS,cACd,KAAK,SAAS,UACd,KAAK,SAAA,cACL;IACA,aAAa,KAAK,KAChB,4BAA4B,MAAM,UAAU,aAAa,IACvD,yBACJ;IACA;GACF;GAEA,IAAI,KAAK,SAAA,eAAmC,KAAK,aAAa,MAAM;IAClE,MAAM,WAAW,KAAK,UAAU;IAChC,IAAI,OAAO,aAAa,YAAY,SAAS,SAAS,GACpD,aAAa,cAAc,QAAQ;IAErC,MAAM,OAAO,KAAK,UAAU;IAC5B,IAAI,OAAO,SAAS,YAAY,KAAK,SAAS,GAC5C,aAAa,cAAc,IAAI;IAEjC,MAAM,SAAS,KAAK,UAAU;IAC9B,IAAI,OAAO,WAAW,YAAY,OAAO,SAAS,GAChD,aAAa,cAAc,MAAM;IAEnC;GACF;GAEA,MAAM,cAAc,KAAK,KAAK;GAC9B,IAAI,eAAe,MACjB;GAGF,aAAa,WAAW;EAC1B;OACK,IAAI,OAAO,UAAU,UAC1B,aAAa,cAAc,KAAK;OAC3B,IAAI,OAAO,UAAU,UAC1B,aAAa,cAAc,MAAM,SAAS,CAAC;OACtC,IAAI,OAAO,UAAU,WAC1B,aAAa,cAAc,MAAM,SAAS,CAAC;CAE/C;CAEA,IAAI,YAAY;CAChB,aAAa,QAAQ,OAAO;CAC5B,OAAO;AACT;;;;;;;AAQA,SAAgB,qBACd,WACA,YACA,aACwB;CACxB,MAAM,SAAiC,OAAO,OAAO,IAAI;CACzD,MAAM,aAAyD,CAAC;CAChE,IAAI,WAAW;CACf,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,SAAS,GAAG;EACxD,MAAM,SAAS,WAAW;EAC1B,MAAM,UAAU,KAAK,MAAM,MAAM;EACjC,OAAO,QAAQ;EACf,YAAY;EACZ,WAAW,KAAK;GAAE;GAAM,WAAW,SAAS;EAAQ,CAAC;CACvD;CACA,IAAI,WAAW,cAAc;CAC7B,IAAI,YAAY,KAAK,WAAW,WAAW,GACzC,OAAO;CAET,WAAW,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;CACnD,KAAK,IAAI,IAAI,GAAG,WAAW,GAAG,KAAK,IAAI,KAAK,WAAW,QAAQ;EAC7D,OAAO,WAAW,EAAE,CAAC,SAAS;EAC9B;CACF;CACA,OAAO;AACT;;;;;;AAOA,MAAM,0BAA0B;;;;;AAMhC,MAAa,qBAAqB,OAChC,WAAyB,iBACqB;CAC9C,MAAM,MAAM,MAAM,aAAa,QAAQ;CACvC,MAAM,eAAe,SAAyB,IAAI,MAAM,IAAI;CAC5D,MAAM,WAAW,aAAa;CAC9B,QAAQ,YAAiC;EACvC,MAAM,QAAQ,wBAAwB,SAAS,aAAa,QAAQ;EACpE,OAAO,WAAW,KAAK,KAAK,QAAQ,uBAAuB,IAAI;CACjE;AACF;;AAGA,MAAa,sBAAsB;CACjC,MAAM,aAA4B,CAElC;CAEA,QAAc;EACZ,KAAK,MAAM,OAAO,OAAO,KAAK,UAAU,GACtC,OAAO,WAAW;CAEtB;CAEA,gBAAyB;EACvB,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS;CAC1C;AACF"}
1
+ {"version":3,"file":"tokens.mjs","names":[],"sources":["../../../src/utils/tokens.ts"],"sourcesContent":["import { Tokenizer } from 'ai-tokenizer';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { ContentTypes } from '@/common/enum';\n\nexport type EncodingName = 'o200k_base' | 'claude';\n\n/** Anthropic minimum image token cost. */\nconst ANTHROPIC_IMAGE_MIN_TOKENS = 1024;\n/** Anthropic divisor: tokens = width × height / 750. */\nconst ANTHROPIC_IMAGE_DIVISOR = 750;\n/** OpenAI low-detail fixed cost. */\nconst OPENAI_IMAGE_LOW_TOKENS = 85;\n/** OpenAI high-detail tile size. */\nconst OPENAI_IMAGE_TILE_SIZE = 512;\n/** OpenAI high-detail tokens per tile. */\nconst OPENAI_IMAGE_TOKENS_PER_TILE = 170;\n/** Google Gemini fixed per-image cost. */\nconst _GEMINI_IMAGE_TOKENS = 258;\n/** Safety margin for image and document token estimates (5% overestimate). */\nexport const IMAGE_TOKEN_SAFETY_MARGIN = 1.05;\n\n/**\n * Anthropic PDF: each page costs image tokens + text tokens.\n * Typical range is 1500-3000 tokens/page. Using 2000 as midpoint.\n */\nconst ANTHROPIC_PDF_TOKENS_PER_PAGE = 2000;\n/** OpenAI PDF: each page rendered as high-detail image. ~1500 tokens typical. */\nconst OPENAI_PDF_TOKENS_PER_PAGE = 1500;\n/** Gemini PDF: fixed 258 tokens per page. */\nconst _GEMINI_PDF_TOKENS_PER_PAGE = 258;\n/** Approximate base64 bytes per PDF page for page count estimation. */\nconst BASE64_BYTES_PER_PDF_PAGE = 75_000;\n/** Fallback token cost for URL-referenced documents without local data. */\nconst URL_DOCUMENT_FALLBACK_TOKENS = 2000;\n\n/**\n * Timed media (video/audio) is priced by DURATION, not size, and the content\n * carries no duration — so duration is estimated from encoded size at\n * representative bitrates and priced at Gemini's rates (the dominant provider\n * for native video/audio). Deliberately rough; superseded by real provider\n * usage after the first turn.\n */\n/** Gemini video ≈ 258 tokens/frame (1 fps) + 32 tokens/s audio ≈ 300 tokens/s. */\nconst VIDEO_TOKENS_PER_SECOND = 300;\n/** Gemini audio: 32 tokens/s (single channel). */\nconst AUDIO_TOKENS_PER_SECOND = 32;\n/** Representative encoded byte rates for duration-from-size estimation. */\nconst VIDEO_BYTES_PER_SECOND = 250_000; // ~2 Mbps\nconst AUDIO_BYTES_PER_SECOND = 16_000; // ~128 kbps\n/** Flat fallback when only a URL is present (no size to estimate from) — ~30s. */\nconst VIDEO_URL_FALLBACK_TOKENS = 9000;\nconst AUDIO_URL_FALLBACK_TOKENS = 960;\n/** Content block types that can carry timed media (video/audio). `media` is\n * generic (Google) so it is classified by MIME; the rest are unambiguous. */\nconst TIMED_MEDIA_TYPES = new Set([\n 'media',\n 'video',\n 'audio',\n 'video_url',\n 'input_audio',\n]);\n\n/**\n * Extracts image dimensions from the first bytes of a base64-encoded\n * PNG, JPEG, GIF, or WebP without decoding the full image.\n * Returns null if the format is unrecognized or data is too short.\n */\nexport function extractImageDimensions(\n base64Data: string\n): { width: number; height: number } | null {\n const raw = base64Data.startsWith('data:')\n ? base64Data.slice(base64Data.indexOf(',') + 1)\n : base64Data;\n\n if (raw.length < 32) {\n return null;\n }\n\n const bytes = new Uint8Array(Buffer.from(raw.slice(0, 80), 'base64'));\n\n if (bytes[0] === 0x89 && bytes[1] === 0x50) {\n // PNG: width at bytes 16-19, height at 20-23 (big-endian)\n const width =\n (bytes[16] << 24) | (bytes[17] << 16) | (bytes[18] << 8) | bytes[19];\n const height =\n (bytes[20] << 24) | (bytes[21] << 16) | (bytes[22] << 8) | bytes[23];\n return { width, height };\n }\n\n if (bytes[0] === 0xff && bytes[1] === 0xd8) {\n // JPEG: scan for SOF0 (0xFFC0) or SOF2 (0xFFC2) marker\n for (let i = 2; i < bytes.length - 9; i++) {\n if (\n bytes[i] === 0xff &&\n (bytes[i + 1] === 0xc0 || bytes[i + 1] === 0xc2)\n ) {\n const height = (bytes[i + 5] << 8) | bytes[i + 6];\n const width = (bytes[i + 7] << 8) | bytes[i + 8];\n return { width, height };\n }\n }\n return null;\n }\n\n if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {\n // GIF: width at bytes 6-7, height at 8-9 (little-endian)\n const width = bytes[6] | (bytes[7] << 8);\n const height = bytes[8] | (bytes[9] << 8);\n return { width, height };\n }\n\n if (\n bytes[0] === 0x52 &&\n bytes[1] === 0x49 &&\n bytes[2] === 0x46 &&\n bytes[3] === 0x46 &&\n bytes[8] === 0x57 &&\n bytes[9] === 0x45 &&\n bytes[10] === 0x42 &&\n bytes[11] === 0x50\n ) {\n // WebP VP8: width at bytes 26-27, height at 28-29\n if (bytes.length > 29) {\n const width = (bytes[26] | (bytes[27] << 8)) & 0x3fff;\n const height = (bytes[28] | (bytes[29] << 8)) & 0x3fff;\n return { width, height };\n }\n return null;\n }\n\n return null;\n}\n\n/** Estimates image token cost for Anthropic/Bedrock (Claude). */\nexport function estimateAnthropicImageTokens(\n width: number,\n height: number\n): number {\n return Math.max(\n ANTHROPIC_IMAGE_MIN_TOKENS,\n Math.ceil((width * height) / ANTHROPIC_IMAGE_DIVISOR)\n );\n}\n\n/** Estimates image token cost for OpenAI (high detail). */\nexport function estimateOpenAIImageTokens(\n width: number,\n height: number,\n detail: string = 'high'\n): number {\n if (detail === 'low') {\n return OPENAI_IMAGE_LOW_TOKENS;\n }\n const tiles =\n Math.ceil(width / OPENAI_IMAGE_TILE_SIZE) *\n Math.ceil(height / OPENAI_IMAGE_TILE_SIZE);\n return OPENAI_IMAGE_LOW_TOKENS + tiles * OPENAI_IMAGE_TOKENS_PER_TILE;\n}\n\n/**\n * Estimates token cost for an image content block.\n * Extracts dimensions from base64 header when available.\n * Falls back to Anthropic minimum (1024) when dimensions can't be determined.\n */\nexport function estimateImageBlockTokens(\n block: Record<string, unknown>,\n encoding: EncodingName\n): number {\n let base64Data: string | undefined;\n\n if (block.type === ContentTypes.IMAGE_URL || block.type === 'image_url') {\n const imageUrl = block.image_url as string | { url?: string } | undefined;\n const url = typeof imageUrl === 'string' ? imageUrl : imageUrl?.url;\n if (typeof url === 'string' && url.startsWith('data:')) {\n base64Data = url;\n } else {\n return ANTHROPIC_IMAGE_MIN_TOKENS;\n }\n } else if (block.type === 'image') {\n const source = block.source as { type?: string; data?: string } | undefined;\n if (source?.type === 'base64' && typeof source.data === 'string') {\n base64Data = source.data;\n } else {\n return ANTHROPIC_IMAGE_MIN_TOKENS;\n }\n } else {\n return ANTHROPIC_IMAGE_MIN_TOKENS;\n }\n\n const dims = extractImageDimensions(base64Data);\n if (dims == null) {\n return ANTHROPIC_IMAGE_MIN_TOKENS;\n }\n\n if (encoding === 'claude') {\n return estimateAnthropicImageTokens(dims.width, dims.height);\n }\n return estimateOpenAIImageTokens(dims.width, dims.height);\n}\n\n/**\n * Estimates token cost for a document/file content block.\n * Handles both LangChain standard format (`type: 'file'` with `source_type`)\n * and Anthropic format (`type: 'document'` with `source`).\n *\n * - Plain text: tokenized directly via `getTokenCount`.\n * - Base64 PDF: page count estimated from base64 length × per-page cost.\n * - URL reference: conservative flat estimate.\n */\nexport function estimateDocumentBlockTokens(\n block: Record<string, unknown>,\n encoding: EncodingName,\n getTokenCount: (text: string) => number\n): number {\n const pdfTokensPerPage =\n encoding === 'claude'\n ? ANTHROPIC_PDF_TOKENS_PER_PAGE\n : OPENAI_PDF_TOKENS_PER_PAGE;\n\n // LangChain standard format: type='file', source_type, data/text/url, mime_type\n const sourceType = block.source_type as string | undefined;\n if (typeof sourceType === 'string') {\n const mimeType = ((block.mime_type as string | undefined) ?? '').split(\n ';'\n )[0];\n\n if (sourceType === 'text' && typeof block.text === 'string') {\n return getTokenCount(block.text as string);\n }\n\n if (sourceType === 'base64' && typeof block.data === 'string') {\n if (mimeType === 'application/pdf' || mimeType === '') {\n const pageEstimate = Math.max(\n 1,\n Math.ceil((block.data as string).length / BASE64_BYTES_PER_PDF_PAGE)\n );\n return pageEstimate * pdfTokensPerPage;\n }\n // Image inside a file block — delegate to image estimation\n if (mimeType.startsWith('image/')) {\n return estimateImageBlockTokens(\n {\n ...block,\n type: 'image',\n source: { type: 'base64', data: block.data },\n },\n encoding\n );\n }\n return getTokenCount(block.data as string);\n }\n\n if (sourceType === 'url') {\n return URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n return URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n // Anthropic format: type='document', source: { type, data, media_type }\n const source = block.source as\n | {\n type?: string;\n data?: string;\n media_type?: string;\n content?: unknown[];\n }\n | undefined;\n\n if (source == null) {\n return URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n if (source.type === 'text' && typeof source.data === 'string') {\n return getTokenCount(source.data);\n }\n\n if (source.type === 'base64' && typeof source.data === 'string') {\n const mediaType = (source.media_type ?? '').split(';')[0];\n if (mediaType === 'application/pdf' || mediaType === '') {\n const pageEstimate = Math.max(\n 1,\n Math.ceil(source.data.length / BASE64_BYTES_PER_PDF_PAGE)\n );\n return pageEstimate * pdfTokensPerPage;\n }\n if (mediaType.startsWith('image/')) {\n return estimateImageBlockTokens(\n { type: 'image', source: { type: 'base64', data: source.data } },\n encoding\n );\n }\n return getTokenCount(source.data);\n }\n\n if (source.type === 'url') {\n return URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n // content-type source (wraps other blocks like images)\n if (source.type === 'content' && Array.isArray(source.content)) {\n let total = 0;\n for (const inner of source.content) {\n if (inner != null && typeof inner === 'object' && 'type' in inner) {\n const innerBlock = inner as Record<string, unknown>;\n if (innerBlock.type === 'image') {\n total += estimateImageBlockTokens(innerBlock, encoding);\n }\n }\n }\n return total > 0 ? total : URL_DOCUMENT_FALLBACK_TOKENS;\n }\n\n return URL_DOCUMENT_FALLBACK_TOKENS;\n}\n\n/** Decoded byte length of base64 or a `data:` URL; 0 for any remote URI (http,\n * gs, s3, file, …) or empty. Base64 never contains `:`, so any scheme prefix\n * marks a remote reference with no local size. */\nfunction base64ByteLength(value: string | undefined): number {\n if (typeof value !== 'string' || value.length === 0) {\n return 0;\n }\n if (value.startsWith('data:')) {\n const comma = value.indexOf(',');\n return comma < 0 ? 0 : Math.floor(((value.length - comma - 1) * 3) / 4);\n }\n if (/^[a-z][a-z0-9+.-]*:/i.test(value)) {\n return 0;\n }\n return Math.floor((value.length * 3) / 4);\n}\n\nfunction timedMediaTokens(\n bytes: number,\n bytesPerSecond: number,\n tokensPerSecond: number,\n urlFallback: number\n): number {\n if (bytes <= 0) {\n return urlFallback;\n }\n return Math.max(\n tokensPerSecond,\n Math.ceil((bytes / bytesPerSecond) * tokensPerSecond)\n );\n}\n\n/** Decoded byte length of a media block payload — top-level `data` (base64\n * string or `Uint8Array`) or base64 `url`, or the nested `video|audio.{data,\n * url, source.bytes}` shapes (`formatMessage` media arrays / native Bedrock);\n * 0 for a bare remote URL, `fileId`/`fileUri`, S3 location, or empty. */\nfunction mediaBlockByteLength(block: Record<string, unknown>): number {\n const data = block.data;\n if (typeof data === 'string') {\n return base64ByteLength(data);\n }\n if (data instanceof Uint8Array) {\n return data.length;\n }\n if (typeof block.url === 'string') {\n return base64ByteLength(block.url);\n }\n const nested = (block.video ?? block.audio) as\n | { data?: unknown; url?: unknown; source?: { bytes?: unknown } }\n | undefined;\n if (nested != null) {\n if (typeof nested.data === 'string') {\n return base64ByteLength(nested.data);\n }\n if (typeof nested.url === 'string') {\n return base64ByteLength(nested.url);\n }\n if (nested.source?.bytes instanceof Uint8Array) {\n return nested.source.bytes.length;\n }\n }\n return 0;\n}\n\n/** Classifies a block as timed media, or null when it is not video/audio — e.g.\n * a generic Google `media` block carrying an image/document MIME, which must\n * NOT be priced as video. Also handles the Google shape where `type` IS the\n * MIME string (e.g. `{ type: 'audio/wav', data }`). */\nfunction timedMediaKind(\n block: Record<string, unknown>\n): 'video' | 'audio' | null {\n const type = typeof block.type === 'string' ? block.type : '';\n if (type === 'input_audio' || type === 'audio') {\n return 'audio';\n }\n if (type === 'video_url' || type === 'video') {\n return 'video';\n }\n const mime =\n type === 'media' && typeof block.mimeType === 'string' ? block.mimeType : type;\n if (mime.startsWith('audio/')) {\n return 'audio';\n }\n if (mime.startsWith('video/')) {\n return 'video';\n }\n return null;\n}\n\n/** Whether a content-block `type` can carry timed media — the fixed set plus the\n * Google MIME-as-type shape (`audio/*` / `video/*`). Gates callers before they\n * hand the block to {@link estimateTimedMediaBlockTokens}. */\nfunction isTimedMediaType(type: string): boolean {\n return (\n TIMED_MEDIA_TYPES.has(type) ||\n type.startsWith('audio/') ||\n type.startsWith('video/')\n );\n}\n\n/**\n * Estimates token cost for a timed-media block (video/audio). Handles Google\n * `{ type: 'media', mimeType, data|url|fileUri }`, OpenRouter\n * `{ type: 'video_url' }` / `{ type: 'input_audio' }`, and standard\n * `{ type: 'video' }` / `{ type: 'audio' }` blocks (payload as `data` base64 or\n * `Uint8Array`, base64 `url`, or `fileId`). Duration is inferred from encoded\n * size (providers price by duration, which the block does not carry) at Gemini's\n * rates; a payload with no size (bare URL / file id) falls back to a ~30s\n * estimate. Returns 0 for non-timed media (e.g. an image/document `media` block)\n * so it is never mispriced as video.\n */\nexport function estimateTimedMediaBlockTokens(\n block: Record<string, unknown>\n): number {\n const kind = timedMediaKind(block);\n if (kind == null) {\n return 0;\n }\n let bytes: number;\n if (block.type === 'input_audio') {\n const audio = block.input_audio as { data?: string } | undefined;\n bytes = base64ByteLength(audio?.data);\n } else if (block.type === 'video_url') {\n const video = block.video_url as string | { url?: string } | undefined;\n bytes = base64ByteLength(typeof video === 'string' ? video : video?.url);\n } else {\n bytes = mediaBlockByteLength(block);\n }\n if (kind === 'audio') {\n return timedMediaTokens(\n bytes,\n AUDIO_BYTES_PER_SECOND,\n AUDIO_TOKENS_PER_SECOND,\n AUDIO_URL_FALLBACK_TOKENS\n );\n }\n return timedMediaTokens(\n bytes,\n VIDEO_BYTES_PER_SECOND,\n VIDEO_TOKENS_PER_SECOND,\n VIDEO_URL_FALLBACK_TOKENS\n );\n}\n\nconst tokenizers: Partial<Record<EncodingName, Tokenizer>> = {};\n\nasync function getTokenizer(\n encoding: EncodingName = 'o200k_base'\n): Promise<Tokenizer> {\n const cached = tokenizers[encoding];\n if (cached) {\n return cached;\n }\n const data =\n encoding === 'claude'\n ? await import('ai-tokenizer/encoding/claude')\n : await import('ai-tokenizer/encoding/o200k_base');\n const instance = new Tokenizer(data);\n tokenizers[encoding] = instance;\n return instance;\n}\n\nexport function encodingForModel(model: string): EncodingName {\n if (model.toLowerCase().includes('claude')) {\n return 'claude';\n }\n return 'o200k_base';\n}\n\nexport function getTokenCountForMessage(\n message: BaseMessage,\n getTokenCount: (text: string) => number,\n encoding: EncodingName = 'o200k_base'\n): number {\n const tokensPerMessage = 3;\n\n type ContentBlock = Record<string, unknown> & {\n type?: string;\n tool_call?: { name?: string; args?: string; output?: string };\n };\n\n const processValue = (value: unknown): void => {\n if (Array.isArray(value)) {\n for (const raw of value) {\n const item = raw as ContentBlock | null | undefined;\n if (item == null || typeof item.type !== 'string') {\n continue;\n }\n if (item.type === ContentTypes.ERROR) {\n continue;\n }\n\n if (\n item.type === ContentTypes.IMAGE_URL ||\n item.type === 'image_url' ||\n item.type === 'image'\n ) {\n numTokens += Math.ceil(\n estimateImageBlockTokens(item, encoding) * IMAGE_TOKEN_SAFETY_MARGIN\n );\n continue;\n }\n\n if (\n item.type === 'document' ||\n item.type === 'file' ||\n item.type === ContentTypes.IMAGE_FILE\n ) {\n numTokens += Math.ceil(\n estimateDocumentBlockTokens(item, encoding, getTokenCount) *\n IMAGE_TOKEN_SAFETY_MARGIN\n );\n continue;\n }\n\n if (isTimedMediaType(item.type)) {\n numTokens += Math.ceil(\n estimateTimedMediaBlockTokens(item) * IMAGE_TOKEN_SAFETY_MARGIN\n );\n continue;\n }\n\n if (item.type === ContentTypes.TOOL_CALL && item.tool_call != null) {\n const toolName = item.tool_call.name;\n if (typeof toolName === 'string' && toolName.length > 0) {\n numTokens += getTokenCount(toolName);\n }\n const args = item.tool_call.args;\n if (typeof args === 'string' && args.length > 0) {\n numTokens += getTokenCount(args);\n }\n const output = item.tool_call.output;\n if (typeof output === 'string' && output.length > 0) {\n numTokens += getTokenCount(output);\n }\n continue;\n }\n\n const nestedValue = item[item.type];\n if (nestedValue == null) {\n continue;\n }\n\n processValue(nestedValue);\n }\n } else if (typeof value === 'string') {\n numTokens += getTokenCount(value);\n } else if (typeof value === 'number') {\n numTokens += getTokenCount(value.toString());\n } else if (typeof value === 'boolean') {\n numTokens += getTokenCount(value.toString());\n }\n };\n\n let numTokens = tokensPerMessage;\n processValue(message.content);\n return numTokens;\n}\n\n/**\n * Largest-remainder apportionment: scales each count by `multiplier` and\n * distributes the rounding remainder so the results sum exactly to\n * `targetTotal`. Keeps per-item breakdowns reconciled with an aggregate\n * computed as a single rounded product of the summed raw counts.\n */\nexport function apportionTokenCounts(\n rawCounts: Record<string, number>,\n multiplier: number,\n targetTotal: number\n): Record<string, number> {\n const result: Record<string, number> = Object.create(null);\n const remainders: Array<{ name: string; remainder: number }> = [];\n let floorSum = 0;\n for (const [name, rawCount] of Object.entries(rawCounts)) {\n const scaled = rawCount * multiplier;\n const floored = Math.floor(scaled);\n result[name] = floored;\n floorSum += floored;\n remainders.push({ name, remainder: scaled - floored });\n }\n let leftover = targetTotal - floorSum;\n if (leftover <= 0 || remainders.length === 0) {\n return result;\n }\n remainders.sort((a, b) => b.remainder - a.remainder);\n for (let i = 0; leftover > 0; i = (i + 1) % remainders.length) {\n result[remainders[i].name] += 1;\n leftover--;\n }\n return result;\n}\n\n/**\n * Anthropic's API consistently reports ~10% more tokens than the local\n * claude tokenizer due to internal message framing and content encoding.\n * Verified empirically across content types via the count_tokens endpoint.\n */\nconst CLAUDE_TOKEN_CORRECTION = 1.1;\n\n/**\n * Creates a token counter function using the specified encoding.\n * Lazily loads the encoding data on first use via dynamic import.\n */\nexport const createTokenCounter = async (\n encoding: EncodingName = 'o200k_base'\n): Promise<(message: BaseMessage) => number> => {\n const tok = await getTokenizer(encoding);\n const countTokens = (text: string): number => tok.count(text);\n const isClaude = encoding === 'claude';\n return (message: BaseMessage): number => {\n const count = getTokenCountForMessage(message, countTokens, encoding);\n return isClaude ? Math.ceil(count * CLAUDE_TOKEN_CORRECTION) : count;\n };\n};\n\n/** Utility to manage the token encoder lifecycle explicitly. */\nexport const TokenEncoderManager = {\n async initialize(): Promise<void> {\n // No-op: ai-tokenizer is synchronously initialized from bundled data.\n },\n\n reset(): void {\n for (const key of Object.keys(tokenizers)) {\n delete tokenizers[key as EncodingName];\n }\n },\n\n isInitialized(): boolean {\n return Object.keys(tokenizers).length > 0;\n },\n};\n"],"mappings":"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"}
@@ -114,7 +114,9 @@ export declare enum ContentTypes {
114
114
  /** Framework-level conversation summary block */
115
115
  SUMMARY = "summary",
116
116
  /** Bedrock */
117
- REASONING_CONTENT = "reasoning_content"
117
+ REASONING_CONTENT = "reasoning_content",
118
+ /** Mid-run user steer persisted inline in an assistant message; replayed as a user turn */
119
+ STEER = "steer"
118
120
  }
119
121
  export declare enum ToolCallTypes {
120
122
  FUNCTION = "function",
@@ -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;