@librechat/agents 3.3.13 → 3.4.1

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 (192) hide show
  1. package/dist/cjs/graphs/Graph.cjs +121 -27
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/HookRegistry.cjs +83 -0
  4. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  5. package/dist/cjs/hooks/executeHooks.cjs +60 -14
  6. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  7. package/dist/cjs/hooks/index.cjs.map +1 -1
  8. package/dist/cjs/hooks/types.cjs +2 -0
  9. package/dist/cjs/hooks/types.cjs.map +1 -1
  10. package/dist/cjs/llm/anthropic/index.cjs +35 -206
  11. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  12. package/dist/cjs/llm/bedrock/index.cjs +121 -241
  13. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  14. package/dist/cjs/llm/google/index.cjs +19 -12
  15. package/dist/cjs/llm/google/index.cjs.map +1 -1
  16. package/dist/cjs/llm/init.cjs +3 -3
  17. package/dist/cjs/llm/invoke.cjs +2 -2
  18. package/dist/cjs/llm/mistral/index.cjs +26 -0
  19. package/dist/cjs/llm/mistral/index.cjs.map +1 -0
  20. package/dist/cjs/llm/openai/index.cjs +82 -80
  21. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  22. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  23. package/dist/cjs/llm/providers.cjs +19 -19
  24. package/dist/cjs/llm/providers.cjs.map +1 -1
  25. package/dist/cjs/llm/stream/chunkAdapters.cjs +198 -0
  26. package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -0
  27. package/dist/cjs/llm/stream/smoother.cjs +369 -0
  28. package/dist/cjs/llm/stream/smoother.cjs.map +1 -0
  29. package/dist/cjs/llm/vertexai/index.cjs +13 -1
  30. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  31. package/dist/cjs/main.cjs +25 -16
  32. package/dist/cjs/messages/format.cjs +40 -23
  33. package/dist/cjs/messages/format.cjs.map +1 -1
  34. package/dist/cjs/run.cjs +82 -15
  35. package/dist/cjs/run.cjs.map +1 -1
  36. package/dist/cjs/session/AgentSession.cjs +37 -2
  37. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  38. package/dist/cjs/stream.cjs +1 -1
  39. package/dist/cjs/summarization/node.cjs +6 -3
  40. package/dist/cjs/summarization/node.cjs.map +1 -1
  41. package/dist/cjs/tools/BashExecutor.cjs +1 -1
  42. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  43. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
  44. package/dist/cjs/tools/ToolNode.cjs +316 -94
  45. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  46. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  47. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +664 -80
  48. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  49. package/dist/cjs/tools/subagent/SubagentReplay.cjs +175 -0
  50. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -0
  51. package/dist/cjs/tools/toolOutputReferences.cjs +20 -0
  52. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  53. package/dist/cjs/utils/index.cjs +2 -2
  54. package/dist/cjs/utils/tokens.cjs +76 -29
  55. package/dist/cjs/utils/tokens.cjs.map +1 -1
  56. package/dist/esm/graphs/Graph.mjs +121 -27
  57. package/dist/esm/graphs/Graph.mjs.map +1 -1
  58. package/dist/esm/hooks/HookRegistry.mjs +83 -0
  59. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  60. package/dist/esm/hooks/executeHooks.mjs +60 -14
  61. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  62. package/dist/esm/hooks/index.mjs.map +1 -1
  63. package/dist/esm/hooks/types.mjs +2 -1
  64. package/dist/esm/hooks/types.mjs.map +1 -1
  65. package/dist/esm/llm/anthropic/index.mjs +34 -205
  66. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  67. package/dist/esm/llm/bedrock/index.mjs +120 -240
  68. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  69. package/dist/esm/llm/google/index.mjs +19 -12
  70. package/dist/esm/llm/google/index.mjs.map +1 -1
  71. package/dist/esm/llm/init.mjs +1 -1
  72. package/dist/esm/llm/invoke.mjs +2 -2
  73. package/dist/esm/llm/mistral/index.mjs +26 -0
  74. package/dist/esm/llm/mistral/index.mjs.map +1 -0
  75. package/dist/esm/llm/openai/index.mjs +82 -80
  76. package/dist/esm/llm/openai/index.mjs.map +1 -1
  77. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  78. package/dist/esm/llm/providers.mjs +3 -3
  79. package/dist/esm/llm/providers.mjs.map +1 -1
  80. package/dist/esm/llm/stream/chunkAdapters.mjs +195 -0
  81. package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -0
  82. package/dist/esm/llm/stream/smoother.mjs +365 -0
  83. package/dist/esm/llm/stream/smoother.mjs.map +1 -0
  84. package/dist/esm/llm/vertexai/index.mjs +13 -1
  85. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  86. package/dist/esm/main.mjs +13 -11
  87. package/dist/esm/messages/format.mjs +40 -23
  88. package/dist/esm/messages/format.mjs.map +1 -1
  89. package/dist/esm/run.mjs +83 -16
  90. package/dist/esm/run.mjs.map +1 -1
  91. package/dist/esm/session/AgentSession.mjs +37 -2
  92. package/dist/esm/session/AgentSession.mjs.map +1 -1
  93. package/dist/esm/stream.mjs +1 -1
  94. package/dist/esm/summarization/node.mjs +6 -3
  95. package/dist/esm/summarization/node.mjs.map +1 -1
  96. package/dist/esm/tools/BashExecutor.mjs +1 -1
  97. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  98. package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
  99. package/dist/esm/tools/ToolNode.mjs +317 -95
  100. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  101. package/dist/esm/tools/ToolSearch.mjs +1 -1
  102. package/dist/esm/tools/subagent/SubagentExecutor.mjs +665 -81
  103. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  104. package/dist/esm/tools/subagent/SubagentReplay.mjs +168 -0
  105. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -0
  106. package/dist/esm/tools/toolOutputReferences.mjs +20 -0
  107. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  108. package/dist/esm/utils/index.mjs +2 -2
  109. package/dist/esm/utils/tokens.mjs +76 -30
  110. package/dist/esm/utils/tokens.mjs.map +1 -1
  111. package/dist/types/graphs/Graph.d.ts +19 -1
  112. package/dist/types/hooks/HookRegistry.d.ts +10 -1
  113. package/dist/types/hooks/executeHooks.d.ts +5 -1
  114. package/dist/types/hooks/index.d.ts +2 -2
  115. package/dist/types/hooks/types.d.ts +12 -0
  116. package/dist/types/index.d.ts +3 -0
  117. package/dist/types/llm/google/index.d.ts +2 -0
  118. package/dist/types/llm/mistral/index.d.ts +11 -0
  119. package/dist/types/llm/openai/index.d.ts +20 -4
  120. package/dist/types/llm/openrouter/index.d.ts +4 -1
  121. package/dist/types/llm/stream/chunkAdapters.d.ts +48 -0
  122. package/dist/types/llm/stream/smoother.d.ts +95 -0
  123. package/dist/types/llm/vertexai/index.d.ts +2 -0
  124. package/dist/types/run.d.ts +4 -1
  125. package/dist/types/session/AgentSession.d.ts +1 -0
  126. package/dist/types/tools/ToolNode.d.ts +23 -15
  127. package/dist/types/tools/subagent/SubagentExecutor.d.ts +49 -7
  128. package/dist/types/tools/subagent/SubagentReplay.d.ts +81 -0
  129. package/dist/types/tools/toolOutputReferences.d.ts +12 -0
  130. package/dist/types/types/graph.d.ts +5 -5
  131. package/dist/types/types/hitl.d.ts +15 -0
  132. package/dist/types/types/llm.d.ts +21 -14
  133. package/dist/types/utils/tokens.d.ts +10 -0
  134. package/package.json +6 -2
  135. package/src/graphs/Graph.ts +257 -52
  136. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +48 -9
  137. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1 -1
  138. package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +80 -0
  139. package/src/hooks/HookRegistry.ts +151 -1
  140. package/src/hooks/__tests__/HookRegistry.test.ts +96 -0
  141. package/src/hooks/__tests__/executeHooks.test.ts +160 -0
  142. package/src/hooks/executeHooks.ts +133 -15
  143. package/src/hooks/index.ts +3 -1
  144. package/src/hooks/types.ts +16 -0
  145. package/src/index.ts +8 -0
  146. package/src/llm/anthropic/index.ts +85 -354
  147. package/src/llm/bedrock/index.ts +174 -390
  148. package/src/llm/bedrock/llm.spec.ts +2 -0
  149. package/src/llm/bedrock/streamSealDispatch.test.ts +76 -0
  150. package/src/llm/custom-chat-models.smoke.test.ts +16 -1
  151. package/src/llm/google/index.ts +17 -18
  152. package/src/llm/google/streamSmoothing.test.ts +121 -0
  153. package/src/llm/mistral/index.ts +33 -0
  154. package/src/llm/mistral/streamSmoothing.test.ts +97 -0
  155. package/src/llm/openai/deepseek.test.ts +56 -0
  156. package/src/llm/openai/index.ts +119 -126
  157. package/src/llm/openrouter/index.ts +4 -1
  158. package/src/llm/providers.ts +3 -3
  159. package/src/llm/stream/chunkAdapters.test.ts +202 -0
  160. package/src/llm/stream/chunkAdapters.ts +317 -0
  161. package/src/llm/stream/reassembly.test.ts +241 -0
  162. package/src/llm/stream/smoother.bench.test.ts +155 -0
  163. package/src/llm/stream/smoother.test.ts +519 -0
  164. package/src/llm/stream/smoother.ts +574 -0
  165. package/src/llm/vertexai/index.ts +17 -1
  166. package/src/llm/vertexai/streamSmoothing.test.ts +109 -0
  167. package/src/messages/format.ts +54 -26
  168. package/src/messages/formatAgentMessages.reducer.test.ts +162 -0
  169. package/src/messages/formatAgentMessages.steer.test.ts +16 -11
  170. package/src/messages/formatAgentMessages.test.ts +7 -2
  171. package/src/run.ts +164 -18
  172. package/src/session/AgentSession.ts +52 -2
  173. package/src/session/__tests__/JsonlSessionStore.test.ts +53 -0
  174. package/src/specs/subagent.test.ts +44 -0
  175. package/src/specs/tokens.test.ts +159 -14
  176. package/src/summarization/__tests__/node.test.ts +32 -14
  177. package/src/summarization/node.ts +15 -11
  178. package/src/tools/ToolNode.ts +631 -157
  179. package/src/tools/__tests__/SubagentExecutor.test.ts +590 -7
  180. package/src/tools/__tests__/SubagentReplay.test.ts +300 -0
  181. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +208 -2
  182. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +486 -2
  183. package/src/tools/__tests__/hitl.test.ts +234 -5
  184. package/src/tools/__tests__/subagentHooks.test.ts +1040 -62
  185. package/src/tools/__tests__/toolOutputReferences.test.ts +19 -2
  186. package/src/tools/subagent/SubagentExecutor.ts +1438 -136
  187. package/src/tools/subagent/SubagentReplay.ts +575 -0
  188. package/src/tools/toolOutputReferences.ts +40 -1
  189. package/src/types/graph.ts +5 -5
  190. package/src/types/hitl.ts +16 -0
  191. package/src/types/llm.ts +53 -36
  192. package/src/utils/tokens.ts +115 -30
@@ -524,11 +524,11 @@ export interface AgentInputs {
524
524
  * raise a LangGraph `interrupt()` (e.g. a tool built on `askUserQuestion()`) —
525
525
  * the host-side event handler runs outside the graph task, where `interrupt()`
526
526
  * throws. Do NOT also list these tools in `toolDefinitions` (they would be bound
527
- * twice). NOT inherited by SELF-SPAWNED subagent children (their config is a
528
- * shallow spread of the parent's inputs, and child graphs compile without a
529
- * checkpointer, so an interrupt-capable tool could never pause there)
530
- * `buildChildInputs` scrubs the inherited copy; an EXPLICIT child config that
531
- * lists its own `graphTools` keeps them.
527
+ * twice). NOT inherited by SELF-SPAWNED subagent children: `buildChildInputs`
528
+ * scrubs the shallow-spread parent copy so parent-scoped direct tools are not
529
+ * exposed to a child implicitly. An EXPLICIT child config that lists its own
530
+ * `graphTools` keeps them; with HITL enabled, those tools share the parent's
531
+ * checkpointer and may pause and resume inside the child graph.
532
532
  *
533
533
  * Deliberately `GenericTool[]`, not `GraphTools`: the wider union admits
534
534
  * schema-only shapes (OpenAI `BindToolsInput`, Google tool objects) that
@@ -97,6 +97,17 @@ export type ToolApprovalDecisionMap = Record<string, ToolApprovalDecision>;
97
97
  * below; this type alias is primarily an integration-layer contract.
98
98
  */
99
99
  export type HumanInterruptType = 'tool_approval' | 'ask_user_question';
100
+ /** Identifies an interrupt that originated inside a checkpointed subagent. */
101
+ export interface SubagentInterruptScope {
102
+ /** Child execution run id used by subagent update and usage events. */
103
+ run_id: string;
104
+ /** Child agent id that owns the interrupted tool call. */
105
+ agent_id: string;
106
+ /** Configured subagent type selected by the parent tool call. */
107
+ subagent_type: string;
108
+ /** Parent `subagent` tool call that launched this child. */
109
+ parent_tool_call_id?: string;
110
+ }
100
111
  /**
101
112
  * Structured payload the SDK passes to `interrupt()` when one or more
102
113
  * pending tool calls require host approval. All `ask`-decision tool calls
@@ -110,6 +121,10 @@ export interface ToolApprovalInterruptPayload {
110
121
  type: 'tool_approval';
111
122
  action_requests: ToolApprovalRequest[];
112
123
  review_configs: ToolApprovalReviewConfig[];
124
+ /** Hook-registry session whose policy raised this interrupt. */
125
+ hook_session_id?: string;
126
+ /** Present when the approval request was bridged from a child graph. */
127
+ subagent?: SubagentInterruptScope;
113
128
  }
114
129
  /**
115
130
  * Pre-defined option the user can pick when answering an
@@ -28,7 +28,7 @@ export type AzureClientOptions = Partial<OpenAIChatInput> & Partial<AzureOpenAII
28
28
  deploymentName?: string;
29
29
  } & BaseChatModelParams & {
30
30
  configuration?: OAIClientOptions;
31
- } & ManagedRequestOptions;
31
+ } & ManagedRequestOptions & StreamSmoothingOptions;
32
32
  /**
33
33
  * Controls whether Claude's reasoning content is returned in adaptive
34
34
  * thinking responses. Added for Claude Opus 4.7, which omits thinking by
@@ -58,8 +58,19 @@ export type ManagedRequestOptions = {
58
58
  promptCacheExplicit?: boolean;
59
59
  safety_identifier?: string;
60
60
  };
61
- export type OpenAIClientOptions = ChatOpenAIFields & ManagedRequestOptions;
62
- export type AnthropicClientOptions = Omit<AnthropicInput, 'thinking'> & {
61
+ /**
62
+ * Adaptive stream-smoothing configuration shared by every provider client.
63
+ */
64
+ export type StreamSmoothingOptions = {
65
+ /**
66
+ * Minimum delay in milliseconds between visible streamed content deltas.
67
+ * Defaults to 25; piece sizes adapt to the backlog so render lag stays
68
+ * bounded regardless of provider chunk size. Set 0 to disable smoothing.
69
+ */
70
+ _lc_stream_delay?: number;
71
+ };
72
+ export type OpenAIClientOptions = ChatOpenAIFields & ManagedRequestOptions & StreamSmoothingOptions;
73
+ export type AnthropicClientOptions = Omit<AnthropicInput, 'thinking'> & StreamSmoothingOptions & {
63
74
  thinking?: ThinkingConfig;
64
75
  promptCache?: boolean;
65
76
  /**
@@ -69,12 +80,12 @@ export type AnthropicClientOptions = Omit<AnthropicInput, 'thinking'> & {
69
80
  */
70
81
  promptCacheTtl?: PromptCacheTtl;
71
82
  };
72
- export type MistralAIClientOptions = ChatMistralAIInput;
73
- export type VertexAIClientOptions = ChatVertexAIInput & {
83
+ export type MistralAIClientOptions = ChatMistralAIInput & StreamSmoothingOptions;
84
+ export type VertexAIClientOptions = ChatVertexAIInput & StreamSmoothingOptions & {
74
85
  includeThoughts?: boolean;
75
86
  thinkingConfig?: GoogleThinkingConfig;
76
87
  };
77
- export type BedrockAnthropicInput = ChatBedrockConverseInput & {
88
+ export type BedrockAnthropicInput = ChatBedrockConverseInput & StreamSmoothingOptions & {
78
89
  additionalModelRequestFields?: ChatBedrockConverseInput['additionalModelRequestFields'] & AnthropicReasoning;
79
90
  promptCache?: boolean;
80
91
  /**
@@ -84,20 +95,16 @@ export type BedrockAnthropicInput = ChatBedrockConverseInput & {
84
95
  * server-side, so the default is safe to leave on.
85
96
  */
86
97
  promptCacheTtl?: PromptCacheTtl;
87
- /**
88
- * Minimum delay in milliseconds between visible streamed content deltas.
89
- */
90
- _lc_stream_delay?: number;
91
98
  };
92
99
  export type BedrockConverseClientOptions = BedrockAnthropicInput;
93
100
  export type BedrockAnthropicClientOptions = BedrockAnthropicInput;
94
- export type GoogleClientOptions = GoogleGenerativeAIChatInput & {
101
+ export type GoogleClientOptions = GoogleGenerativeAIChatInput & StreamSmoothingOptions & {
95
102
  customHeaders?: RequestOptions['customHeaders'];
96
103
  thinkingConfig?: GoogleThinkingConfig;
97
104
  includeServerSideToolInvocations?: boolean;
98
105
  };
99
- export type DeepSeekClientOptions = Partial<ChatDeepSeekInput>;
100
- export type XAIClientOptions = ChatXAIInput;
106
+ export type DeepSeekClientOptions = Partial<ChatDeepSeekInput> & StreamSmoothingOptions;
107
+ export type XAIClientOptions = ChatXAIInput & StreamSmoothingOptions;
101
108
  export type ClientOptions = OpenAIClientOptions | AzureClientOptions | AnthropicClientOptions | MistralAIClientOptions | VertexAIClientOptions | BedrockConverseClientOptions | GoogleClientOptions | DeepSeekClientOptions | XAIClientOptions;
102
109
  export type SharedLLMConfig = {
103
110
  provider: Providers;
@@ -122,7 +129,7 @@ export type ProviderOptionsMap = {
122
129
  [Providers.ANTHROPIC]: AnthropicClientOptions;
123
130
  [Providers.MISTRALAI]: MistralAIClientOptions;
124
131
  [Providers.MISTRAL]: MistralAIClientOptions;
125
- [Providers.OPENROUTER]: ChatOpenRouterCallOptions;
132
+ [Providers.OPENROUTER]: ChatOpenRouterCallOptions & StreamSmoothingOptions;
126
133
  [Providers.BEDROCK]: BedrockAnthropicClientOptions;
127
134
  [Providers.XAI]: XAIClientOptions;
128
135
  [Providers.MOONSHOT]: OpenAIClientOptions;
@@ -1,5 +1,15 @@
1
1
  import type { BaseMessage } from '@langchain/core/messages';
2
2
  export type EncodingName = 'o200k_base' | 'claude';
3
+ export type UnsafeTokenMeasurementReason = 'message_proxy' | 'content_proxy' | 'metadata_proxy' | 'metadata_accessor' | 'invalid_count';
4
+ export declare class UnsafeTokenMeasurementError extends Error {
5
+ readonly type = "unsafe_token_measurement";
6
+ readonly reason: UnsafeTokenMeasurementReason;
7
+ readonly path: string;
8
+ constructor({ reason, path, }: {
9
+ reason: UnsafeTokenMeasurementReason;
10
+ path: string;
11
+ });
12
+ }
3
13
  /** Safety margin for image and document token estimates (5% overestimate). */
4
14
  export declare const IMAGE_TOKEN_SAFETY_MARGIN = 1.05;
5
15
  /**
package/package.json CHANGED
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.3.13",
3
+ "version": "3.4.1",
4
+ "reova": {
5
+ "enabled": true,
6
+ "endpoint": "https://telemetry.reo.dev/data"
7
+ },
4
8
  "main": "./dist/cjs/main.cjs",
5
9
  "module": "./dist/esm/main.mjs",
6
10
  "types": "./dist/types/index.d.ts",
@@ -248,7 +252,7 @@
248
252
  "nanoid": "^3.3.7",
249
253
  "okapibm25": "^1.4.1",
250
254
  "openai": "^6.46.0",
251
- "reo-census": "^1.2.9",
255
+ "reova": "^0.4.1",
252
256
  "socks-proxy-agent": "^8.0.5",
253
257
  "uuid": "^11.1.1"
254
258
  },
@@ -17,6 +17,13 @@ import type {
17
17
  MessageContent,
18
18
  } from '@langchain/core/messages';
19
19
  import type { ToolCall } from '@langchain/core/messages/tool';
20
+ import type {
21
+ ReplayableSubagentTool,
22
+ SubagentGraphResumeState,
23
+ SubagentResumeManifest,
24
+ SubagentToolNodeResumeState,
25
+ SettledSubagentToolOutput,
26
+ } from '@/tools/subagent/SubagentReplay';
20
27
  import type {
21
28
  ResolvedStreamLimits,
22
29
  RunBreakerScope,
@@ -60,14 +67,6 @@ import {
60
67
  appendPredecessorHandoffCue,
61
68
  removePredecessorHandoffCue,
62
69
  } from '@/messages';
63
- import {
64
- attemptInvoke,
65
- tryFallbackProviders,
66
- getFallbackErrorContext,
67
- getFallbackOverflowCandidates,
68
- projectMessagesForProvider,
69
- resolveServingModelId,
70
- } from '@/llm/invoke';
71
70
  import {
72
71
  resetIfNotEmpty,
73
72
  isAnthropicLike,
@@ -79,6 +78,21 @@ import {
79
78
  joinKeys,
80
79
  sleep,
81
80
  } from '@/utils';
81
+ import {
82
+ attemptInvoke,
83
+ tryFallbackProviders,
84
+ getFallbackErrorContext,
85
+ getFallbackOverflowCandidates,
86
+ projectMessagesForProvider,
87
+ resolveServingModelId,
88
+ } from '@/llm/invoke';
89
+ import {
90
+ resolveStreamLimits,
91
+ StreamLimitExceededError,
92
+ sweepStaleStreamLimitEntries,
93
+ STREAM_LIMIT_EPOCH_KEY,
94
+ RUN_BREAKER_SCOPE_CONFIG_KEY,
95
+ } from '@/llm/streamLimits';
82
96
  import {
83
97
  Constants,
84
98
  GraphNodeKeys,
@@ -122,17 +136,11 @@ import {
122
136
  findCallback,
123
137
  type CallbackEntry,
124
138
  } from '@/utils/callbacks';
125
- import {
126
- resolveStreamLimits,
127
- StreamLimitExceededError,
128
- sweepStaleStreamLimitEntries,
129
- STREAM_LIMIT_EPOCH_KEY,
130
- RUN_BREAKER_SCOPE_CONFIG_KEY,
131
- } from '@/llm/streamLimits';
132
139
  import { partitionAndMarkOpenRouterToolCache } from '@/llm/openrouter/toolCache';
133
140
  import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
134
141
  import { shouldTraceToolNodeForLangfuse } from '@/langfuseToolOutputTracing';
135
142
  import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
143
+ import { SUBAGENT_REPLAY_CONTROLLER } from '@/tools/subagent/SubagentReplay';
136
144
  import { SubagentExecutor, resolveSubagentConfigs } from '@/tools/subagent';
137
145
  import { partitionAndMarkBedrockToolCache } from '@/llm/bedrock/toolCache';
138
146
  import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
@@ -630,6 +638,10 @@ export abstract class Graph<
630
638
  _TNodeName extends string = string,
631
639
  > {
632
640
  abstract resetValues(keepContent?: boolean, checkpointScope?: string): void;
641
+ restoreCheckpointMessages(
642
+ _messages: BaseMessage[],
643
+ _pendingMessages?: BaseMessage[]
644
+ ): void {}
633
645
  abstract initializeTools({
634
646
  currentTools,
635
647
  currentToolMap,
@@ -848,11 +860,18 @@ export abstract class Graph<
848
860
  // Flush each compiled ToolNode's direct-path turn cache so it
849
861
  // doesn't leak across Runs (Codex P2 #33). The cache survives
850
862
  // `run()` re-entry by design (resume-stable), but end-of-Run
851
- // is the right point to reset it.
863
+ // is the right point to reset it. Retain the registrations because
864
+ // the compiled workflow can be reused for later Runs; compilation
865
+ // will not register these instances again.
852
866
  for (const node of this._compiledToolNodes) {
853
867
  node.clearDirectPathTurns();
854
868
  }
855
- this._compiledToolNodes.clear();
869
+ // Subagent executors are likewise compiled once and reused. Clear
870
+ // their per-Run state without dropping the registrations needed by
871
+ // subsequent cleanup cycles.
872
+ for (const executor of this._subagentExecutors) {
873
+ executor.clearHeavyState();
874
+ }
856
875
  this.sessions.clear();
857
876
  }
858
877
 
@@ -904,10 +923,137 @@ export abstract class Graph<
904
923
  */
905
924
  protected registerCompiledToolNode(node: {
906
925
  clearDirectPathTurns(): void;
926
+ createSubagentResumeState(): SubagentToolNodeResumeState;
927
+ restoreSubagentResumeState(state: SubagentToolNodeResumeState): void;
907
928
  }): void {
908
929
  this._compiledToolNodes.add(node);
909
930
  }
910
931
 
932
+ protected registerSubagentExecutor(executor: SubagentExecutor): void {
933
+ this._subagentExecutors.add(executor);
934
+ }
935
+
936
+ protected resetSubagentCheckpointThreadIds(): void {
937
+ for (const executor of this._subagentExecutors) {
938
+ executor.resetCheckpointThreadIds();
939
+ }
940
+ }
941
+
942
+ getChildCheckpointThreadIds(): string[] {
943
+ const threadIds = new Set<string>();
944
+ for (const executor of this._subagentExecutors) {
945
+ for (const threadId of executor.getChildCheckpointThreadIds()) {
946
+ threadIds.add(threadId);
947
+ }
948
+ }
949
+ return [...threadIds];
950
+ }
951
+
952
+ createSubagentResumeState(runId: string): SubagentGraphResumeState {
953
+ return {
954
+ toolCallSteps: [...this.toolCallStepIds].map(([toolCallId, stepId]) => ({
955
+ toolCallId,
956
+ stepId,
957
+ })),
958
+ toolSessions: [...this.sessions].map(([toolName, context]) => ({
959
+ toolName,
960
+ context: {
961
+ ...context,
962
+ ...(context.files == null
963
+ ? {}
964
+ : { files: context.files.map((file) => ({ ...file })) }),
965
+ },
966
+ })),
967
+ toolNodes: [...this._compiledToolNodes].map((node) =>
968
+ node.createSubagentResumeState()
969
+ ),
970
+ eagerToolUsage: [
971
+ {
972
+ agentId: '',
973
+ toolUsageCounts: [...this.eagerEventToolUsageCount].map(
974
+ ([toolName, count]) => ({ toolName, count })
975
+ ),
976
+ },
977
+ ...[...this.eagerEventToolUsageCountsByAgentId].map(
978
+ ([agentId, usageCounts]) => ({
979
+ agentId,
980
+ toolUsageCounts: [...usageCounts].map(([toolName, count]) => ({
981
+ toolName,
982
+ count,
983
+ })),
984
+ })
985
+ ),
986
+ ],
987
+ eagerToolSuppressions: [...this.eagerEventToolSuppressions],
988
+ ...(this._toolOutputRegistry == null
989
+ ? {}
990
+ : {
991
+ toolOutputReferences: this._toolOutputRegistry.snapshotState(runId),
992
+ }),
993
+ };
994
+ }
995
+
996
+ restoreSubagentResumeState(
997
+ state: SubagentGraphResumeState,
998
+ runId: string
999
+ ): void {
1000
+ const toolNodesByKey = new Map(
1001
+ [...this._compiledToolNodes].map((node) => {
1002
+ const nodeState = node.createSubagentResumeState();
1003
+ return [nodeState.stateKey, node] as const;
1004
+ })
1005
+ );
1006
+ if (toolNodesByKey.size !== state.toolNodes.length) {
1007
+ throw new Error('Cannot restore changed subagent tool topology.');
1008
+ }
1009
+ for (const nodeState of state.toolNodes) {
1010
+ if (!toolNodesByKey.has(nodeState.stateKey)) {
1011
+ throw new Error(
1012
+ `Cannot restore subagent tool state for "${nodeState.stateKey}".`
1013
+ );
1014
+ }
1015
+ }
1016
+ const registry =
1017
+ state.toolOutputReferences == null
1018
+ ? undefined
1019
+ : this.getOrCreateToolOutputRegistry();
1020
+ if (state.toolOutputReferences != null && registry == null) {
1021
+ throw new Error('Cannot restore disabled tool output references.');
1022
+ }
1023
+
1024
+ this.toolCallStepIds.clear();
1025
+ for (const { toolCallId, stepId } of state.toolCallSteps) {
1026
+ this.toolCallStepIds.set(toolCallId, stepId);
1027
+ }
1028
+ this.sessions.clear();
1029
+ for (const { toolName, context } of state.toolSessions) {
1030
+ this.sessions.set(toolName, {
1031
+ ...context,
1032
+ ...(context.files == null
1033
+ ? {}
1034
+ : { files: context.files.map((file) => ({ ...file })) }),
1035
+ });
1036
+ }
1037
+ for (const nodeState of state.toolNodes) {
1038
+ const node = toolNodesByKey.get(nodeState.stateKey)!;
1039
+ node.restoreSubagentResumeState(nodeState);
1040
+ }
1041
+ this.clearEagerEventToolUsageCounts();
1042
+ for (const usageState of state.eagerToolUsage) {
1043
+ const usageCounts = this.getEagerEventToolUsageCount(usageState.agentId);
1044
+ for (const { toolName, count } of usageState.toolUsageCounts) {
1045
+ usageCounts.set(toolName, count);
1046
+ }
1047
+ }
1048
+ this.eagerEventToolSuppressions.clear();
1049
+ for (const toolName of state.eagerToolSuppressions) {
1050
+ this.eagerEventToolSuppressions.add(toolName);
1051
+ }
1052
+ if (state.toolOutputReferences != null && registry != null) {
1053
+ registry.restoreState(runId, state.toolOutputReferences);
1054
+ }
1055
+ }
1056
+
911
1057
  /**
912
1058
  * Returns the shared `ToolOutputReferenceRegistry` for this run,
913
1059
  * constructing it on first access. Returns `undefined` when the
@@ -957,7 +1103,10 @@ export abstract class Graph<
957
1103
  */
958
1104
  private _compiledToolNodes: Set<{
959
1105
  clearDirectPathTurns(): void;
1106
+ createSubagentResumeState(): SubagentToolNodeResumeState;
1107
+ restoreSubagentResumeState(state: SubagentToolNodeResumeState): void;
960
1108
  }> = new Set();
1109
+ private _subagentExecutors = new Set<SubagentExecutor>();
961
1110
  public getOrCreateFileCheckpointer(): t.LocalFileCheckpointer | undefined {
962
1111
  // Return the cached instance unconditionally if one exists. The
963
1112
  // toolExecution check below decides whether to *create* a new
@@ -998,11 +1147,14 @@ export abstract class Graph<
998
1147
 
999
1148
  export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1000
1149
  overrideModel?: t.ChatModel;
1150
+ private subagentModelOverride?: t.ChatModel;
1001
1151
  /** Optional compile options passed into workflow.compile() */
1002
1152
  compileOptions?: t.CompileOptions | undefined;
1003
1153
  /** Whether the workflow was actually compiled with a checkpointer. */
1004
1154
  hasCompiledCheckpointer: boolean = false;
1005
1155
  messages: BaseMessage[] = [];
1156
+ /** Whether a rebuilt resume seeded the message baseline from its checkpoint. */
1157
+ private hasRestoredCheckpointMessages = false;
1006
1158
  /** Cached run messages preserved before clearHeavyState() so getRunMessages() works after cleanup. */
1007
1159
  private cachedRunMessages?: BaseMessage[];
1008
1160
  /** Per-agent discovery snapshots preserved before contexts are reset on cleanup. */
@@ -1057,8 +1209,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1057
1209
  * generation key + chunk index (see `resolveGenerationKey`). Per-run
1058
1210
  * accumulation state, cleared by both reset paths.
1059
1211
  */
1060
- streamedToolCallArgTallies: Map<string, StreamedToolCallArgTally> =
1061
- new Map();
1212
+ streamedToolCallArgTallies: Map<string, StreamedToolCallArgTally> = new Map();
1062
1213
  /** Streamed chunk events per model generation, keyed by generation key. */
1063
1214
  streamDeltaEventCounts: Map<string, StreamDeltaEventTally> = new Map();
1064
1215
  /** Per-chunk-object, per-generation charge balances (lazily created; see
@@ -1212,7 +1363,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1212
1363
  /* Init */
1213
1364
 
1214
1365
  resetValues(keepContent?: boolean, checkpointScope?: string): void {
1366
+ this.resetSubagentCheckpointThreadIds();
1215
1367
  this.messages = [];
1368
+ this.hasRestoredCheckpointMessages = false;
1216
1369
  this.cachedRunMessages = undefined;
1217
1370
  this.cachedDiscoveredTools = undefined;
1218
1371
  this.config = resetIfNotEmpty(this.config, undefined);
@@ -1309,6 +1462,23 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1309
1462
  : undefined;
1310
1463
  }
1311
1464
 
1465
+ /** Seeds the sidecar message view that checkpoint restoration bypasses. */
1466
+ override restoreCheckpointMessages(
1467
+ messages: BaseMessage[],
1468
+ pendingMessages?: BaseMessage[]
1469
+ ): void {
1470
+ if (this.messages.length > 0) {
1471
+ return;
1472
+ }
1473
+ this.messages =
1474
+ pendingMessages == null
1475
+ ? [...messages]
1476
+ : messagesStateReducer(messages, pendingMessages);
1477
+ this.startIndex = this.messages.length;
1478
+ this.hasRestoredCheckpointMessages = true;
1479
+ this.cachedRunMessages = undefined;
1480
+ }
1481
+
1312
1482
  override clearHeavyState(): void {
1313
1483
  this.cachedRunMessages = this.messages.slice(this.startIndex);
1314
1484
  this.cachedDiscoveredTools = new Map(
@@ -1320,6 +1490,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1320
1490
  super.clearHeavyState();
1321
1491
  this.messages = [];
1322
1492
  this.overrideModel = undefined;
1493
+ this.subagentModelOverride = undefined;
1323
1494
  /** Stream-limit accounting (argument tallies, event counts, charge
1324
1495
  * credits) deliberately SURVIVES cleanup: this runs in `processStream`'s
1325
1496
  * finally, which an ordinary parallel-branch failure reaches while
@@ -1796,6 +1967,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1796
1967
  runLangfuse: this.langfuse,
1797
1968
  agentLangfuse: agentContext?.langfuse,
1798
1969
  });
1970
+ const interruptingToolNames = new Set(this.interruptingToolNames ?? []);
1971
+ if (
1972
+ this.humanInTheLoop?.enabled === true &&
1973
+ (agentContext?.subagentConfigs?.length ?? 0) > 0
1974
+ ) {
1975
+ interruptingToolNames.add(Constants.SUBAGENT);
1976
+ }
1977
+ const effectiveInterruptingToolNames =
1978
+ interruptingToolNames.size > 0 ? interruptingToolNames : undefined;
1799
1979
 
1800
1980
  if (eventDrivenMode) {
1801
1981
  const schemaTools = createSchemaOnlyTools(toolDefinitions);
@@ -1847,11 +2027,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1847
2027
  eagerEventToolSuppressions: this.eagerEventToolSuppressions,
1848
2028
  toolExecution: this.toolExecution,
1849
2029
  directToolNames: directToolNames.size > 0 ? directToolNames : undefined,
1850
- interruptingToolNames:
1851
- this.interruptingToolNames != null &&
1852
- this.interruptingToolNames.length > 0
1853
- ? new Set(this.interruptingToolNames)
1854
- : undefined,
2030
+ interruptingToolNames: effectiveInterruptingToolNames,
1855
2031
  maxContextTokens: agentContext?.maxContextTokens,
1856
2032
  maxToolResultChars: agentContext?.maxToolResultChars,
1857
2033
  toolOutputRegistry: this.getOrCreateToolOutputRegistry(),
@@ -1914,11 +2090,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1914
2090
  sessions: this.sessions,
1915
2091
  toolExecution: this.toolExecution,
1916
2092
  codeSessionToolNames: this.codeSessionToolNames,
1917
- interruptingToolNames:
1918
- this.interruptingToolNames != null &&
1919
- this.interruptingToolNames.length > 0
1920
- ? new Set(this.interruptingToolNames)
1921
- : undefined,
2093
+ interruptingToolNames: effectiveInterruptingToolNames,
1922
2094
  hookRegistry: this.hookRegistry,
1923
2095
  humanInTheLoop: this.humanInTheLoop,
1924
2096
  maxContextTokens: agentContext?.maxContextTokens,
@@ -1944,6 +2116,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1944
2116
  });
1945
2117
  }
1946
2118
 
2119
+ /** Explicitly overrides the model used by isolated descendant subagent graphs. */
2120
+ setSubagentModelOverride(model: t.ChatModel): void {
2121
+ this.subagentModelOverride = model;
2122
+ }
2123
+
1947
2124
  getUsageMetadata(
1948
2125
  finalMessage?: BaseMessage
1949
2126
  ): Partial<UsageMetadata> | undefined {
@@ -3370,7 +3547,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3370
3547
  * would start new provider work after the run-wide breaker fired.
3371
3548
  * Rethrow the breaker's own stream-limit reason instead. */
3372
3549
  {
3373
- const trippedReason = this.resolveTrippedBreakerReason(attemptBreaker.signal);
3550
+ const trippedReason = this.resolveTrippedBreakerReason(
3551
+ attemptBreaker.signal
3552
+ );
3374
3553
  if (trippedReason != null) {
3375
3554
  throw trippedReason;
3376
3555
  }
@@ -3617,7 +3796,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3617
3796
  }
3618
3797
  {
3619
3798
  /** Same sibling-abort translation guard as the primary catch. */
3620
- const trippedReason = this.resolveTrippedBreakerReason(attemptBreaker.signal);
3799
+ const trippedReason = this.resolveTrippedBreakerReason(
3800
+ attemptBreaker.signal
3801
+ );
3621
3802
  if (trippedReason != null) {
3622
3803
  throw trippedReason;
3623
3804
  }
@@ -4054,19 +4235,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4054
4235
  tokenCounter: agentContext.tokenCounter,
4055
4236
  usageSink: this.subagentUsageSink,
4056
4237
  streamLimits: this.streamLimits,
4238
+ humanInTheLoop: this.humanInTheLoop,
4239
+ checkpointer: this.compileOptions?.checkpointer,
4057
4240
  maxDepth: effectiveSubagentDepth,
4058
4241
  createChildGraph: (input): StandardGraph => {
4059
4242
  const childGraph = new StandardGraph(input);
4243
+ if (this.subagentModelOverride != null) {
4244
+ childGraph.overrideModel = this.subagentModelOverride;
4245
+ childGraph.setSubagentModelOverride(this.subagentModelOverride);
4246
+ }
4060
4247
  const toolHandlerRegistry = createToolHandlerRegistry(
4061
4248
  getParentHandlerRegistry()
4062
4249
  );
4063
4250
  childGraph.hookRegistry = this.hookRegistry;
4064
- /**
4065
- * Do not propagate `humanInTheLoop` into the child graph yet:
4066
- * nested subagent interrupts need a stable child checkpoint and
4067
- * resume bridge. Child hooks still fire; `ask` decisions fail
4068
- * closed inside the subagent until that flow is implemented.
4069
- */
4251
+ childGraph.humanInTheLoop = this.humanInTheLoop;
4252
+ if (this.humanInTheLoop?.enabled === true) {
4253
+ childGraph.compileOptions = {
4254
+ checkpointer: this.compileOptions?.checkpointer,
4255
+ };
4256
+ }
4070
4257
  childGraph.toolOutputReferences = this.toolOutputReferences;
4071
4258
  childGraph.eagerEventToolExecution = this.eagerEventToolExecution;
4072
4259
  childGraph.codeSessionToolNames = this.codeSessionToolNames;
@@ -4088,6 +4275,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4088
4275
  return childGraph;
4089
4276
  },
4090
4277
  });
4278
+ this.registerSubagentExecutor(executor);
4091
4279
 
4092
4280
  const subagentTool = tool(async (rawInput, config) => {
4093
4281
  const input = rawInput as {
@@ -4102,20 +4290,22 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4102
4290
  const subagentType =
4103
4291
  typeof input.subagent_type === 'string' ? input.subagent_type : '';
4104
4292
  const threadId = config.configurable?.thread_id as string | undefined;
4105
- /**
4106
- * When the tool is dispatched from an LLM's `tool_call`, LangChain
4107
- * threads the originating `ToolCall` onto the RunnableConfig as
4108
- * `config.toolCall` (see `ToolRunnableConfig` in
4109
- * `@langchain/core/tools` internal but stable since ≥0.3.x).
4110
- * Surfacing its id lets hosts correlate `SubagentUpdateEvent`s
4111
- * back to the parent's `tool_call_id` deterministically — no
4112
- * temporal heuristics needed. If a future LangChain version
4113
- * changes the threading, the type-guarded read falls back to
4114
- * `undefined` and the correlation degrades gracefully.
4115
- */
4116
- const toolCall = (config as { toolCall?: { id?: string } }).toolCall;
4117
- const parentToolCallId =
4118
- typeof toolCall?.id === 'string' ? toolCall.id : undefined;
4293
+ /** Surface the parent call id so child checkpoints, interrupts, and
4294
+ * update events remain correlated across replay and resume. */
4295
+ const toolRuntime = config as {
4296
+ toolCallId?: string;
4297
+ toolCall?: { id?: string };
4298
+ };
4299
+ const toolCall = toolRuntime.toolCall;
4300
+ let parentToolCallId: string | undefined;
4301
+ if (
4302
+ typeof toolRuntime.toolCallId === 'string' &&
4303
+ toolRuntime.toolCallId !== ''
4304
+ ) {
4305
+ parentToolCallId = toolRuntime.toolCallId;
4306
+ } else if (typeof toolCall?.id === 'string' && toolCall.id !== '') {
4307
+ parentToolCallId = toolCall.id;
4308
+ }
4119
4309
  /** The parent tool batch's entry-captured scope (stamped by
4120
4310
  * ToolNode before PreToolUse hooks) — binds this child to the
4121
4311
  * run that dispatched it, not to whatever controller a reset
@@ -4141,6 +4331,21 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4141
4331
  });
4142
4332
  return result.content;
4143
4333
  }, buildSubagentToolParams(resolvedConfigs));
4334
+ const replayableSubagentTool = subagentTool as typeof subagentTool &
4335
+ ReplayableSubagentTool;
4336
+ replayableSubagentTool[SUBAGENT_REPLAY_CONTROLLER] = {
4337
+ getResumeManifest: (
4338
+ parentToolCallIds
4339
+ ): Promise<SubagentResumeManifest | undefined> =>
4340
+ executor.getResumeManifest(parentToolCallIds),
4341
+ getSettledOutput: (
4342
+ call,
4343
+ config
4344
+ ): Promise<SettledSubagentToolOutput | undefined> =>
4345
+ executor.getSettledToolOutput(call, config),
4346
+ persistSettledOutput: (call, config, output): Promise<void> =>
4347
+ executor.persistSettledToolOutput(call, config, output),
4348
+ };
4144
4349
 
4145
4350
  if (!agentContext.graphTools) {
4146
4351
  agentContext.graphTools = [];
@@ -4388,7 +4593,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4388
4593
  const StateAnnotation = Annotation.Root({
4389
4594
  messages: Annotation<BaseMessage[]>({
4390
4595
  reducer: (a, b) => {
4391
- if (!this.messages.length) {
4596
+ if (!this.messages.length && !this.hasRestoredCheckpointMessages) {
4392
4597
  this.startIndex = a.length + b.length;
4393
4598
  }
4394
4599
  const result = messagesStateReducer(a, b);