@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
package/src/run.ts CHANGED
@@ -4,21 +4,28 @@ import { PromptTemplate } from '@langchain/core/prompts';
4
4
  import { RunnableLambda } from '@langchain/core/runnables';
5
5
  import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
6
6
  import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
7
- import { HumanMessage, SystemMessage } from '@langchain/core/messages';
7
+ import {
8
+ BaseMessage,
9
+ HumanMessage,
10
+ SystemMessage,
11
+ } from '@langchain/core/messages';
8
12
  import {
9
13
  Command,
10
14
  INTERRUPT,
11
15
  MemorySaver,
12
16
  isInterrupted,
13
17
  } from '@langchain/langgraph';
14
- import type {
15
- MessageContentComplex,
16
- BaseMessage,
17
- } from '@langchain/core/messages';
18
18
  import type { StringPromptValue } from '@langchain/core/prompt_values';
19
+ import type { MessageContentComplex } from '@langchain/core/messages';
19
20
  import type { RunnableConfig } from '@langchain/core/runnables';
20
21
  import type { HookRegistry } from '@/hooks';
21
22
  import type * as t from '@/types';
23
+ import {
24
+ requireValidSubagentResumeManifest,
25
+ stripSubagentResumeManifest,
26
+ SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
27
+ SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
28
+ } from '@/tools/subagent/SubagentReplay';
22
29
  import {
23
30
  createLangfuseTraceMetadata,
24
31
  createLangfuseHandler,
@@ -121,19 +128,83 @@ function isLangGraphResumeMapForInterrupt(
121
128
  return Object.prototype.hasOwnProperty.call(value, interruptId);
122
129
  }
123
130
 
131
+ function getInterruptHookSessionId(payload: unknown): string | undefined {
132
+ const publicPayload = stripSubagentResumeManifest(payload);
133
+ if (
134
+ publicPayload == null ||
135
+ typeof publicPayload !== 'object' ||
136
+ (publicPayload as { type?: unknown }).type !== 'tool_approval'
137
+ ) {
138
+ return undefined;
139
+ }
140
+ const sessionId = (publicPayload as { hook_session_id?: unknown })
141
+ .hook_session_id;
142
+ return typeof sessionId === 'string' && sessionId.length > 0
143
+ ? sessionId
144
+ : undefined;
145
+ }
146
+
124
147
  type InterruptStateSnapshot = {
125
148
  config?: RunnableConfig;
149
+ values?: { messages?: BaseMessage[] };
126
150
  tasks?: Array<{
127
- interrupts?: Array<{ id?: string }>;
151
+ interrupts?: Array<{ id?: string; value?: unknown }>;
128
152
  }>;
129
153
  };
130
154
 
131
155
  type WorkflowWithStateHistory = {
156
+ getState?(config: RunnableConfig): Promise<InterruptStateSnapshot>;
132
157
  getStateHistory?(
133
158
  config: RunnableConfig
134
159
  ): AsyncIterableIterator<InterruptStateSnapshot>;
135
160
  };
136
161
 
162
+ function getFirstPersistedInterrupt(
163
+ snapshot: InterruptStateSnapshot
164
+ ): { id: string; value: unknown } | undefined {
165
+ for (const task of snapshot.tasks ?? []) {
166
+ for (const pendingInterrupt of task.interrupts ?? []) {
167
+ if (
168
+ typeof pendingInterrupt.id === 'string' &&
169
+ pendingInterrupt.id.length > 0
170
+ ) {
171
+ return { id: pendingInterrupt.id, value: pendingInterrupt.value };
172
+ }
173
+ }
174
+ }
175
+ return undefined;
176
+ }
177
+
178
+ function getPersistedMessages(
179
+ snapshot: InterruptStateSnapshot
180
+ ): BaseMessage[] | undefined {
181
+ const messages = snapshot.values?.messages;
182
+ if (!Array.isArray(messages) || !messages.every(BaseMessage.isInstance)) {
183
+ return undefined;
184
+ }
185
+ return messages;
186
+ }
187
+
188
+ type ResumeCommandUpdate = ConstructorParameters<typeof Command>[0]['update'];
189
+
190
+ function getResumeUpdateMessages(
191
+ update: ResumeCommandUpdate
192
+ ): BaseMessage[] | undefined {
193
+ if (update == null) {
194
+ return undefined;
195
+ }
196
+ const messages = Array.isArray(update)
197
+ ? update.find(([key]) => key === 'messages')?.[1]
198
+ : update.messages;
199
+ if (BaseMessage.isInstance(messages)) {
200
+ return [messages];
201
+ }
202
+ if (!Array.isArray(messages) || !messages.every(BaseMessage.isInstance)) {
203
+ return undefined;
204
+ }
205
+ return messages;
206
+ }
207
+
137
208
  export class Run<_T extends t.BaseGraphState> {
138
209
  id: string;
139
210
  private tokenCounter?: t.TokenCounter;
@@ -227,8 +298,6 @@ export class Run<_T extends t.BaseGraphState> {
227
298
  /** Default to legacy graph for 'standard' or undefined type */
228
299
  this.graphRunnable = this.createLegacyGraph(config.graphConfig);
229
300
  if (this.Graph) {
230
- this.Graph.compileOptions =
231
- config.graphConfig.compileOptions ?? this.Graph.compileOptions;
232
301
  this.Graph.handlerRegistry = handlerRegistry;
233
302
  }
234
303
  }
@@ -542,6 +611,10 @@ export class Run<_T extends t.BaseGraphState> {
542
611
  return this.Graph.getRunMessages();
543
612
  }
544
613
 
614
+ getChildCheckpointThreadIds(): string[] {
615
+ return this.Graph?.getChildCheckpointThreadIds() ?? [];
616
+ }
617
+
545
618
  /**
546
619
  * Returns a defensive snapshot of tools discovered by the current run.
547
620
  * Pass an agent id for that context, or omit it for the ordered union across
@@ -759,6 +832,10 @@ export class Run<_T extends t.BaseGraphState> {
759
832
  recursionLimit,
760
833
  configurable: { ...callerConfig.configurable },
761
834
  };
835
+ if (!isResume) {
836
+ delete config.configurable?.[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
837
+ delete config.configurable?.[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY];
838
+ }
762
839
 
763
840
  /**
764
841
  * Cancellation can arrive either at graph construction or per-call through
@@ -1248,7 +1325,13 @@ export class Run<_T extends t.BaseGraphState> {
1248
1325
  getInterrupt<TPayload = t.HumanInterruptPayload>():
1249
1326
  | t.RunInterruptResult<TPayload>
1250
1327
  | undefined {
1251
- return this._interrupt as t.RunInterruptResult<TPayload> | undefined;
1328
+ if (this._interrupt == null) {
1329
+ return undefined;
1330
+ }
1331
+ return {
1332
+ ...this._interrupt,
1333
+ payload: stripSubagentResumeManifest(this._interrupt.payload),
1334
+ } as t.RunInterruptResult<TPayload>;
1252
1335
  }
1253
1336
 
1254
1337
  /**
@@ -1331,6 +1414,10 @@ export class Run<_T extends t.BaseGraphState> {
1331
1414
  'update' | 'goto'
1332
1415
  >
1333
1416
  ): Promise<MessageContentComplex[] | undefined> {
1417
+ const resumeConfig = await this.resolveInterruptResumeConfig(
1418
+ callerConfig,
1419
+ commandOptions?.update
1420
+ );
1334
1421
  const interruptId = this._interrupt?.interruptId;
1335
1422
  const scopedResume =
1336
1423
  typeof interruptId === 'string' &&
@@ -1338,7 +1425,6 @@ export class Run<_T extends t.BaseGraphState> {
1338
1425
  !isLangGraphResumeMapForInterrupt(resumeValue, interruptId)
1339
1426
  ? { [interruptId]: resumeValue }
1340
1427
  : resumeValue;
1341
- const resumeConfig = await this.resolveInterruptResumeConfig(callerConfig);
1342
1428
  // langgraph 1.4.5 applies resume + state update + reroute in one superstep
1343
1429
  // (single checkpoint). `update`/`goto` are omitted unless the caller sets them.
1344
1430
  return this.processStream(
@@ -1357,9 +1443,29 @@ export class Run<_T extends t.BaseGraphState> {
1357
1443
  }
1358
1444
 
1359
1445
  private async resolveInterruptResumeConfig(
1360
- callerConfig: t.RunStreamConfig
1446
+ callerConfig: t.RunStreamConfig,
1447
+ resumeUpdate?: ResumeCommandUpdate
1361
1448
  ): Promise<t.RunStreamConfig> {
1449
+ await this.restoreInterruptFromCheckpoint(callerConfig, resumeUpdate);
1362
1450
  const interrupt = this._interrupt;
1451
+ const resumeManifest = requireValidSubagentResumeManifest(
1452
+ interrupt?.payload
1453
+ );
1454
+ const resumeConfigurable = { ...callerConfig.configurable };
1455
+ delete resumeConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
1456
+ delete resumeConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY];
1457
+ resumeConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY] = nanoid();
1458
+ if (resumeManifest != null) {
1459
+ resumeConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY] = resumeManifest;
1460
+ }
1461
+ const manifestConfig = {
1462
+ ...callerConfig,
1463
+ configurable: resumeConfigurable,
1464
+ };
1465
+ const hookSessionId = getInterruptHookSessionId(interrupt?.payload);
1466
+ if (hookSessionId != null) {
1467
+ this.hookRegistry?.copySession(hookSessionId, this.id);
1468
+ }
1363
1469
  const interruptId = interrupt?.interruptId;
1364
1470
  const workflow = this.graphRunnable as
1365
1471
  | (t.CompiledStateWorkflow & WorkflowWithStateHistory)
@@ -1367,9 +1473,9 @@ export class Run<_T extends t.BaseGraphState> {
1367
1473
  const stateHistory = workflow?.getStateHistory;
1368
1474
  if (interrupt?.checkpointId != null && interrupt.checkpointId.length > 0) {
1369
1475
  return {
1370
- ...callerConfig,
1476
+ ...manifestConfig,
1371
1477
  configurable: {
1372
- ...callerConfig.configurable,
1478
+ ...manifestConfig.configurable,
1373
1479
  checkpoint_id: interrupt.checkpointId,
1374
1480
  ...(typeof interrupt.checkpointNs === 'string'
1375
1481
  ? { checkpoint_ns: interrupt.checkpointNs }
@@ -1383,12 +1489,12 @@ export class Run<_T extends t.BaseGraphState> {
1383
1489
  interruptId.length === 0 ||
1384
1490
  typeof stateHistory !== 'function'
1385
1491
  ) {
1386
- return callerConfig;
1492
+ return manifestConfig;
1387
1493
  }
1388
1494
 
1389
1495
  for await (const snapshot of stateHistory.call(
1390
1496
  this.graphRunnable,
1391
- callerConfig as RunnableConfig
1497
+ manifestConfig as RunnableConfig
1392
1498
  )) {
1393
1499
  const hasMatchingInterrupt =
1394
1500
  snapshot.tasks?.some(
@@ -1411,9 +1517,9 @@ export class Run<_T extends t.BaseGraphState> {
1411
1517
  ...(typeof checkpointNs === 'string' ? { checkpointNs } : {}),
1412
1518
  };
1413
1519
  return {
1414
- ...callerConfig,
1520
+ ...manifestConfig,
1415
1521
  configurable: {
1416
- ...callerConfig.configurable,
1522
+ ...manifestConfig.configurable,
1417
1523
  checkpoint_id: checkpointId,
1418
1524
  ...(typeof checkpointNs === 'string'
1419
1525
  ? { checkpoint_ns: checkpointNs }
@@ -1423,7 +1529,47 @@ export class Run<_T extends t.BaseGraphState> {
1423
1529
  }
1424
1530
  }
1425
1531
 
1426
- return callerConfig;
1532
+ return manifestConfig;
1533
+ }
1534
+
1535
+ private async restoreInterruptFromCheckpoint(
1536
+ callerConfig: t.RunStreamConfig,
1537
+ resumeUpdate?: ResumeCommandUpdate
1538
+ ): Promise<void> {
1539
+ if (this._interrupt != null || this.humanInTheLoop?.enabled !== true) {
1540
+ return;
1541
+ }
1542
+ const workflow = this.graphRunnable as
1543
+ | (t.CompiledStateWorkflow & WorkflowWithStateHistory)
1544
+ | undefined;
1545
+ if (typeof workflow?.getState !== 'function') {
1546
+ return;
1547
+ }
1548
+
1549
+ const snapshot = await workflow.getState(callerConfig as RunnableConfig);
1550
+ const persistedInterrupt = getFirstPersistedInterrupt(snapshot);
1551
+ if (persistedInterrupt == null) {
1552
+ return;
1553
+ }
1554
+ const persistedMessages = getPersistedMessages(snapshot);
1555
+ if (persistedMessages != null) {
1556
+ this.Graph?.restoreCheckpointMessages(
1557
+ persistedMessages,
1558
+ getResumeUpdateMessages(resumeUpdate)
1559
+ );
1560
+ }
1561
+
1562
+ const checkpointConfigurable = snapshot.config?.configurable;
1563
+ const checkpointId = checkpointConfigurable?.checkpoint_id;
1564
+ const checkpointNs = checkpointConfigurable?.checkpoint_ns;
1565
+ const threadId = callerConfig.configurable?.thread_id;
1566
+ this._interrupt = {
1567
+ interruptId: persistedInterrupt.id,
1568
+ payload: persistedInterrupt.value,
1569
+ ...(typeof threadId === 'string' ? { threadId } : {}),
1570
+ ...(typeof checkpointId === 'string' ? { checkpointId } : {}),
1571
+ ...(typeof checkpointNs === 'string' ? { checkpointNs } : {}),
1572
+ };
1427
1573
  }
1428
1574
 
1429
1575
  private createSystemCallback<K extends keyof t.ClientCallbacks>(
@@ -934,6 +934,30 @@ export class AgentSession {
934
934
  });
935
935
  }
936
936
 
937
+ private async recordChildCheckpointThreads(params: {
938
+ source: 'run' | 'resume';
939
+ runId: string;
940
+ run: Run<t.IState>;
941
+ }): Promise<void> {
942
+ if (!this.checkpointing.enabled || this.store == null) {
943
+ return;
944
+ }
945
+ const recordedThreadIds = new Set(
946
+ this.store.getCheckpoints().map((checkpoint) => checkpoint.data.threadId)
947
+ );
948
+ for (const threadId of params.run.getChildCheckpointThreadIds()) {
949
+ if (recordedThreadIds.has(threadId)) {
950
+ continue;
951
+ }
952
+ recordedThreadIds.add(threadId);
953
+ await this.store.appendCheckpoint({
954
+ source: params.source,
955
+ runId: params.runId,
956
+ threadId,
957
+ });
958
+ }
959
+ }
960
+
937
961
  private getCheckpointThreadIds(): string[] {
938
962
  const threadIds = new Set<string>([this.threadId]);
939
963
  for (const checkpoint of this.store?.getCheckpoints() ?? []) {
@@ -1006,6 +1030,7 @@ export class AgentSession {
1006
1030
  const sessionState = createSessionRunState(
1007
1031
  isSessionThread ? (this.store?.getPath() ?? []) : []
1008
1032
  );
1033
+ let run: Run<t.IState> | undefined;
1009
1034
  try {
1010
1035
  const runConfig: t.RunConfig = {
1011
1036
  ...this.runConfig,
@@ -1024,7 +1049,7 @@ export class AgentSession {
1024
1049
  ...handlerResult.handlers,
1025
1050
  },
1026
1051
  };
1027
- const run = await Run.create<t.IState>(runConfig);
1052
+ run = await Run.create<t.IState>(runConfig);
1028
1053
  let messages = inputMessages;
1029
1054
  if (!useCheckpointState && sessionState.messages.length > 0) {
1030
1055
  messages = sessionState.messages;
@@ -1070,6 +1095,11 @@ export class AgentSession {
1070
1095
  checkpointId: interrupt?.checkpointId,
1071
1096
  checkpointNs: interrupt?.checkpointNs,
1072
1097
  });
1098
+ await this.recordChildCheckpointThreads({
1099
+ source: 'run',
1100
+ runId,
1101
+ run,
1102
+ });
1073
1103
  const contentParts = (content ?? handlerResult.contentParts).filter(
1074
1104
  (part): part is t.MessageContentComplex => part != null
1075
1105
  );
@@ -1099,6 +1129,13 @@ export class AgentSession {
1099
1129
  threadId,
1100
1130
  config: callerConfig,
1101
1131
  });
1132
+ if (run != null) {
1133
+ await this.recordChildCheckpointThreads({
1134
+ source: 'run',
1135
+ runId,
1136
+ run,
1137
+ });
1138
+ }
1102
1139
  throw error;
1103
1140
  }
1104
1141
  }
@@ -1348,8 +1385,9 @@ export class AgentSession {
1348
1385
  const sessionState = createSessionRunState(
1349
1386
  isSessionThread ? (this.store?.getPath() ?? []) : []
1350
1387
  );
1388
+ let run: Run<t.IState> | undefined;
1351
1389
  try {
1352
- const run = await Run.create<t.IState>({
1390
+ run = await Run.create<t.IState>({
1353
1391
  ...this.runConfig,
1354
1392
  runId,
1355
1393
  graphConfig: applyCheckpointingToGraphConfig(
@@ -1400,6 +1438,11 @@ export class AgentSession {
1400
1438
  checkpointId: interrupt?.checkpointId,
1401
1439
  checkpointNs: interrupt?.checkpointNs,
1402
1440
  });
1441
+ await this.recordChildCheckpointThreads({
1442
+ source: 'resume',
1443
+ runId,
1444
+ run,
1445
+ });
1403
1446
  const contentParts = (content ?? handlerResult.contentParts).filter(
1404
1447
  (part): part is t.MessageContentComplex => part != null
1405
1448
  );
@@ -1425,6 +1468,13 @@ export class AgentSession {
1425
1468
  threadId,
1426
1469
  config: callerConfig,
1427
1470
  });
1471
+ if (run != null) {
1472
+ await this.recordChildCheckpointThreads({
1473
+ source: 'resume',
1474
+ runId,
1475
+ run,
1476
+ });
1477
+ }
1428
1478
  throw error;
1429
1479
  }
1430
1480
  }
@@ -25,6 +25,9 @@ type MockRun = {
25
25
  >;
26
26
  getInterrupt: jest.MockedFunction<Run<t.IState>['getInterrupt']>;
27
27
  getHaltReason: jest.MockedFunction<Run<t.IState>['getHaltReason']>;
28
+ getChildCheckpointThreadIds: jest.MockedFunction<
29
+ Run<t.IState>['getChildCheckpointThreadIds']
30
+ >;
28
31
  };
29
32
 
30
33
  function createMockRun(outputText = 'ok'): MockRun {
@@ -45,6 +48,7 @@ function createMockRun(outputText = 'ok'): MockRun {
45
48
  getCalibrationRatio: jest.fn(() => 1),
46
49
  getInterrupt: jest.fn(() => undefined),
47
50
  getHaltReason: jest.fn(() => undefined),
51
+ getChildCheckpointThreadIds: jest.fn(() => []),
48
52
  };
49
53
  }
50
54
 
@@ -1119,6 +1123,55 @@ describe('JsonlSessionStore', () => {
1119
1123
  });
1120
1124
  });
1121
1125
 
1126
+ it('records and resets child checkpoint threads owned by a run', async () => {
1127
+ const checkpointer = new MemorySaver();
1128
+ const childThreadId = 'subagent:owned-child';
1129
+ const mockRun = createMockRun('child result');
1130
+ mockRun.getChildCheckpointThreadIds.mockReturnValue([childThreadId]);
1131
+ mockRunCreate(mockRun);
1132
+ const session = await createAgentSession({
1133
+ cwd: dir,
1134
+ runId: 'template-run',
1135
+ checkpointing: { checkpointer },
1136
+ graphConfig: {
1137
+ type: 'standard',
1138
+ llmConfig: {
1139
+ provider: 'openAI' as never,
1140
+ model: 'test-model',
1141
+ },
1142
+ instructions: 'test',
1143
+ },
1144
+ });
1145
+ await putCheckpoint({
1146
+ checkpointer,
1147
+ threadId: childThreadId,
1148
+ id: 'checkpoint_child',
1149
+ });
1150
+
1151
+ await session.run('fresh turn', { runId: 'run_with_child' });
1152
+
1153
+ expect(
1154
+ session.getSessionStore()?.getCheckpoints(childThreadId).at(-1)?.data
1155
+ ).toMatchObject({
1156
+ source: 'run',
1157
+ runId: 'run_with_child',
1158
+ threadId: childThreadId,
1159
+ });
1160
+ const firstMessage = session
1161
+ .getSessionStore()
1162
+ ?.getPath()
1163
+ .find((entry) => entry.type === 'message');
1164
+ await session.branch(firstMessage?.id ?? '', { position: 'at' });
1165
+
1166
+ const tuple = await checkpointer.getTuple({
1167
+ configurable: { thread_id: childThreadId },
1168
+ });
1169
+ expect(tuple).toBeUndefined();
1170
+ expect(
1171
+ session.getSessionStore()?.getCheckpoints(childThreadId).at(-1)?.data
1172
+ ).toMatchObject({ source: 'reset', reason: 'branch' });
1173
+ });
1174
+
1122
1175
  it('keeps checkpoint state when branching to the active JSONL leaf', async () => {
1123
1176
  const checkpointer = new MemorySaver();
1124
1177
  const session = await createAgentSession({
@@ -21,6 +21,7 @@ import * as providers from '@/llm/providers';
21
21
  import { Run } from '@/run';
22
22
 
23
23
  const CHILD_RESPONSE = 'Research result: Paris is the capital of France.';
24
+ const OVERRIDDEN_CHILD_RESPONSE = 'Deterministic child override result.';
24
25
 
25
26
  const callerConfig: Partial<RunnableConfig> & {
26
27
  version: 'v1' | 'v2';
@@ -226,6 +227,49 @@ describe('Subagent Integration', () => {
226
227
  expect(subagentTool).toBeDefined();
227
228
  });
228
229
 
230
+ it('only applies an explicitly configured subagent model override', async () => {
231
+ const invokeSubagent = async (
232
+ overrideSubagents: boolean
233
+ ): Promise<string> => {
234
+ const run = await Run.create<t.IState>({
235
+ runId: `subagent-model-override-${overrideSubagents}-${Date.now()}`,
236
+ graphConfig: {
237
+ type: 'standard',
238
+ agents: [createParentAgent()],
239
+ },
240
+ returnContent: true,
241
+ skipCleanup: true,
242
+ });
243
+ const graph = run.Graph as StandardGraph;
244
+ const model = new FakeListChatModel({
245
+ responses: [OVERRIDDEN_CHILD_RESPONSE],
246
+ });
247
+ graph.overrideModel = model;
248
+ if (overrideSubagents) {
249
+ graph.setSubagentModelOverride(model);
250
+ }
251
+
252
+ const context = graph.agentContexts.get('parent');
253
+ const subagentTool = (context?.graphTools as t.GenericTool[]).find(
254
+ (tool) => 'name' in tool && tool.name === Constants.SUBAGENT
255
+ );
256
+ expect(subagentTool).toBeDefined();
257
+
258
+ return String(
259
+ await subagentTool!.invoke(
260
+ {
261
+ description: 'What is the capital of France?',
262
+ subagent_type: 'researcher',
263
+ },
264
+ callerConfig
265
+ )
266
+ );
267
+ };
268
+
269
+ await expect(invokeSubagent(false)).resolves.toBe(CHILD_RESPONSE);
270
+ await expect(invokeSubagent(true)).resolves.toBe(OVERRIDDEN_CHILD_RESPONSE);
271
+ });
272
+
229
273
  it('inherits eager event-tool settings into self-spawn child graphs', async () => {
230
274
  const originalCreateWorkflow = StandardGraph.prototype.createWorkflow;
231
275
  const observedChildGraphs: Array<{