@librechat/agents 3.5.1 → 3.6.0

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 (93) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +5 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +148 -22
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +2 -1
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/main.cjs +4 -0
  8. package/dist/cjs/run.cjs +29 -11
  9. package/dist/cjs/run.cjs.map +1 -1
  10. package/dist/cjs/stream.cjs +8 -6
  11. package/dist/cjs/stream.cjs.map +1 -1
  12. package/dist/cjs/tools/BashExecutor.cjs +8 -7
  13. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +5 -4
  15. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  16. package/dist/cjs/tools/CodeExecutor.cjs +26 -7
  17. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  18. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +7 -6
  19. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  20. package/dist/cjs/tools/ToolNode.cjs +33 -10
  21. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  22. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  23. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  24. package/dist/cjs/tools/runStepResume.cjs +51 -0
  25. package/dist/cjs/tools/runStepResume.cjs.map +1 -0
  26. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +15 -43
  27. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  28. package/dist/cjs/tools/subagent/SubagentReplay.cjs +6 -1
  29. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  30. package/dist/cjs/utils/toolSessions.cjs +63 -0
  31. package/dist/cjs/utils/toolSessions.cjs.map +1 -0
  32. package/dist/esm/agents/AgentContext.mjs +5 -2
  33. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  34. package/dist/esm/graphs/Graph.mjs +148 -22
  35. package/dist/esm/graphs/Graph.mjs.map +1 -1
  36. package/dist/esm/graphs/MultiAgentGraph.mjs +2 -1
  37. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  38. package/dist/esm/main.mjs +2 -2
  39. package/dist/esm/run.mjs +29 -11
  40. package/dist/esm/run.mjs.map +1 -1
  41. package/dist/esm/stream.mjs +8 -6
  42. package/dist/esm/stream.mjs.map +1 -1
  43. package/dist/esm/tools/BashExecutor.mjs +9 -8
  44. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  45. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +6 -5
  46. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  47. package/dist/esm/tools/CodeExecutor.mjs +23 -8
  48. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  49. package/dist/esm/tools/ProgrammaticToolCalling.mjs +8 -7
  50. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  51. package/dist/esm/tools/ToolNode.mjs +33 -10
  52. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  53. package/dist/esm/tools/ToolSearch.mjs +2 -2
  54. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  55. package/dist/esm/tools/runStepResume.mjs +48 -0
  56. package/dist/esm/tools/runStepResume.mjs.map +1 -0
  57. package/dist/esm/tools/subagent/SubagentExecutor.mjs +15 -43
  58. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  59. package/dist/esm/tools/subagent/SubagentReplay.mjs +6 -1
  60. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  61. package/dist/esm/utils/toolSessions.mjs +62 -0
  62. package/dist/esm/utils/toolSessions.mjs.map +1 -0
  63. package/dist/types/agents/AgentContext.d.ts +4 -1
  64. package/dist/types/graphs/Graph.d.ts +11 -3
  65. package/dist/types/tools/CodeExecutor.d.ts +4 -0
  66. package/dist/types/tools/ProgrammaticToolCalling.d.ts +1 -1
  67. package/dist/types/tools/ToolNode.d.ts +3 -1
  68. package/dist/types/tools/runStepResume.d.ts +5 -0
  69. package/dist/types/tools/subagent/SubagentExecutor.d.ts +25 -0
  70. package/dist/types/tools/subagent/SubagentReplay.d.ts +2 -1
  71. package/dist/types/types/graph.d.ts +10 -1
  72. package/dist/types/types/stream.d.ts +18 -0
  73. package/dist/types/types/tools.d.ts +36 -16
  74. package/dist/types/utils/toolSessions.d.ts +18 -0
  75. package/package.json +1 -1
  76. package/src/agents/AgentContext.ts +7 -0
  77. package/src/graphs/Graph.ts +192 -13
  78. package/src/graphs/MultiAgentGraph.ts +1 -0
  79. package/src/run.ts +62 -11
  80. package/src/stream.ts +28 -9
  81. package/src/tools/BashExecutor.ts +31 -13
  82. package/src/tools/BashProgrammaticToolCalling.ts +19 -9
  83. package/src/tools/CodeExecutor.ts +57 -16
  84. package/src/tools/ProgrammaticToolCalling.ts +26 -12
  85. package/src/tools/ToolNode.ts +58 -8
  86. package/src/tools/ToolSearch.ts +2 -2
  87. package/src/tools/runStepResume.ts +121 -0
  88. package/src/tools/subagent/SubagentExecutor.ts +67 -65
  89. package/src/tools/subagent/SubagentReplay.ts +22 -1
  90. package/src/types/graph.ts +10 -0
  91. package/src/types/stream.ts +17 -0
  92. package/src/types/tools.ts +50 -21
  93. package/src/utils/toolSessions.ts +113 -0
@@ -1,9 +1,9 @@
1
1
  import type { StructuredToolInterface } from '@langchain/core/tools';
2
- import type { RunnableToolLike } from '@langchain/core/runnables';
2
+ import type { RunnableConfig, RunnableToolLike } from '@langchain/core/runnables';
3
3
  import type { ToolCall } from '@langchain/core/messages/tool';
4
4
  import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
5
5
  import type { RunBreakerScope } from '@/llm/streamLimits';
6
- import type { MessageContentComplex, ToolErrorData } from './stream';
6
+ import type { MessageContentComplex, RunStepResumeState, ToolErrorData } from './stream';
7
7
  import type { HumanInTheLoopConfig } from './hitl';
8
8
  import type { LangfuseConfig } from './graph';
9
9
  import type { HookRegistry } from '@/hooks';
@@ -118,6 +118,8 @@ export type ToolNodeOptions = {
118
118
  toolRegistry?: LCToolRegistry;
119
119
  /** Reference to Graph's sessions map for automatic session injection */
120
120
  sessions?: ToolSessionMap;
121
+ /** Partition within `sessions` used by this agent's code tools. */
122
+ codeSessionKey?: string;
121
123
  /** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
122
124
  eventDrivenMode?: boolean;
123
125
  /** Tool definitions for event-driven mode (used for context, not invocation) */
@@ -264,6 +266,10 @@ export type ToolNodeOptions = {
264
266
  * controller.
265
267
  */
266
268
  getRunScope?: () => RunBreakerScope;
269
+ /** SDK-owned checkpoint bridge for open run-step lifecycle state. */
270
+ restoreRunStepResumeState?: (state?: RunStepResumeState, config?: RunnableConfig) => void;
271
+ /** SDK-owned checkpoint snapshot for open run-step lifecycle state. */
272
+ createRunStepResumeState?: () => RunStepResumeState;
267
273
  };
268
274
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
269
275
  export type ToolEndEvent = {
@@ -361,6 +367,19 @@ export type CodeEnvFile = (CodeEnvFileBase & {
361
367
  kind: 'user';
362
368
  });
363
369
  export type CodeExecutionToolParams = undefined | {
370
+ /** Trusted Code API endpoint selected by the host for this agent. */
371
+ baseUrl?: string;
372
+ /**
373
+ * Expected Code API execution profile. Sent as a request assertion so
374
+ * endpoint/configuration drift fails closed instead of silently using
375
+ * the wrong sandbox backend.
376
+ */
377
+ executionProfile?: CodeApiExecutionProfile;
378
+ /**
379
+ * Trusted warm-runtime affinity hint selected by the host for this
380
+ * agent invocation. This overrides the legacy ToolNode-injected hint.
381
+ */
382
+ runtimeSessionHint?: string;
364
383
  /** Execution session — see `CodeSessionContext.session_id`. */
365
384
  session_id?: string;
366
385
  user_id?: string;
@@ -368,15 +387,14 @@ export type CodeExecutionToolParams = undefined | {
368
387
  /** Optional host-supplied Code API auth headers. */
369
388
  authHeaders?: CodeApiAuthHeaders;
370
389
  /**
371
- * Advertise best-effort stateful sessions in the tool description
372
- * (variables/files may persist between calls, may reset). Prompt text
373
- * only, and it must be set here because the description is bound to the
374
- * LLM at construction time. Pair it with the run-scoped
375
- * `toolExecution.sandbox.statefulSessions` gate, which drives the wire
376
- * hint — set both from one flag so the prompt and the backend agree.
390
+ * Advertise filesystem-tier stateful sessions in the tool description
391
+ * and allow a trusted runtime affinity hint onto the wire. Resolve this,
392
+ * `baseUrl`, `executionProfile`, and `runtimeSessionHint` together from
393
+ * the executing agent so the prompt and backend agree.
377
394
  */
378
395
  statefulSessions?: boolean;
379
396
  };
397
+ export type CodeApiExecutionProfile = 'default' | 'stateful';
380
398
  export type CodeApiAuthHeaderMap = Record<string, string>;
381
399
  export type CodeApiAuthHeaders = CodeApiAuthHeaderMap | (() => CodeApiAuthHeaderMap | Promise<CodeApiAuthHeaderMap>);
382
400
  export type FileRef = {
@@ -1032,19 +1050,17 @@ export type CloudflareSandboxExecutionConfig = {
1032
1050
  };
1033
1051
  export type SandboxExecutionConfig = {
1034
1052
  /**
1035
- * Opt into best-effort stateful runtime sessions on the remote Code API
1036
- * (its warm per-session MicroVM backend). This gate is run-scoped: it only
1037
- * controls the wire behavior (ToolNode injecting the session hint on
1038
- * execute_code/bash calls). The transport is otherwise unchanged.
1053
+ * Legacy run-scoped opt-in for warm runtime hint injection. New hosts that
1054
+ * need mixed execution profiles should resolve `statefulSessions`,
1055
+ * `baseUrl`, `executionProfile`, and `runtimeSessionHint` on each executing
1056
+ * agent's tool factories instead. Direct executors ignore this injected hint
1057
+ * unless their own factory has `statefulSessions: true`.
1039
1058
  *
1040
1059
  * It does NOT change the model-facing tool description. Tool descriptions are
1041
1060
  * bound to the LLM at construction time (`createCodeExecutionTool` /
1042
1061
  * `createBashExecutionTool`), before this run config is applied inside the
1043
1062
  * graph, so they can only be adjusted via the tools' own `statefulSessions`
1044
- * factory param. Set BOTH from one flag (as LibreChat does): with this on but
1045
- * the factory param off, the backend runs statefully while the model is still
1046
- * told the environment is stateless (non-corrupting — the model just won't
1047
- * exploit persistence).
1063
+ * factory param.
1048
1064
  */
1049
1065
  statefulSessions?: boolean;
1050
1066
  /**
@@ -1193,6 +1209,10 @@ export type BashProgrammaticToolCallingParams = ProgrammaticToolCallingParams;
1193
1209
  export type ProgrammaticToolCallingParams = {
1194
1210
  /** Code API base URL (or use CODE_BASEURL env var) */
1195
1211
  baseUrl?: string;
1212
+ /** Expected Code API execution profile (host-controlled). */
1213
+ executionProfile?: CodeApiExecutionProfile;
1214
+ /** Trusted warm-runtime affinity hint for this agent invocation. */
1215
+ runtimeSessionHint?: string;
1196
1216
  /** Safety limit for round-trips (default: 20) */
1197
1217
  maxRoundTrips?: number;
1198
1218
  /** Maximum per-sandbox-run timeout for PTC's legacy `timeout` field. */
@@ -0,0 +1,18 @@
1
+ import type * as t from '@/types';
2
+ type CodeSessionAgent = Pick<t.AgentInputs, 'codeSessionKey' | 'initialSessions'>;
3
+ /**
4
+ * Seeds a top-level graph while preserving the legacy run-wide session map.
5
+ * A legacy `execute_code` seed is copied into every custom agent partition
6
+ * unless the host already supplied an explicit seed for that partition.
7
+ */
8
+ export declare function seedRunInitialSessions(args: {
9
+ sessions: t.ToolSessionMap;
10
+ initialSessions: t.ToolSessionMap;
11
+ agents: Iterable<Pick<t.AgentInputs, 'codeSessionKey'>>;
12
+ }): void;
13
+ /**
14
+ * Seeds an isolated child graph from each selected agent. Legacy code-session
15
+ * entries are remapped to the owning agent's partition before they are merged.
16
+ */
17
+ export declare function seedAgentInitialSessions(sessions: t.ToolSessionMap, agents: Iterable<CodeSessionAgent>): void;
18
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.5.1",
3
+ "version": "3.6.0",
4
4
  "reova": {
5
5
  "enabled": true,
6
6
  "endpoint": "https://telemetry.reo.dev/data"
@@ -63,6 +63,7 @@ export class AgentContext {
63
63
  ): AgentContext {
64
64
  const {
65
65
  agentId,
66
+ codeSessionKey,
66
67
  name,
67
68
  provider,
68
69
  clientOptions,
@@ -92,6 +93,7 @@ export class AgentContext {
92
93
 
93
94
  const agentContext = new AgentContext({
94
95
  agentId,
96
+ codeSessionKey,
95
97
  name: name ?? agentId,
96
98
  provider,
97
99
  clientOptions,
@@ -167,6 +169,8 @@ export class AgentContext {
167
169
 
168
170
  /** Agent identifier */
169
171
  agentId: string;
172
+ /** Partition for this agent's transient code session and file refs. */
173
+ codeSessionKey?: string;
170
174
  /** Human-readable name for this agent (used in handoff context). Falls back to agentId if not provided. */
171
175
  name?: string;
172
176
  /** Provider for this specific agent */
@@ -370,6 +374,7 @@ export class AgentContext {
370
374
 
371
375
  constructor({
372
376
  agentId,
377
+ codeSessionKey,
373
378
  name,
374
379
  provider,
375
380
  clientOptions,
@@ -394,6 +399,7 @@ export class AgentContext {
394
399
  maxToolResultChars,
395
400
  }: {
396
401
  agentId: string;
402
+ codeSessionKey?: string;
397
403
  name?: string;
398
404
  provider: Providers;
399
405
  clientOptions?: t.ClientOptions;
@@ -418,6 +424,7 @@ export class AgentContext {
418
424
  maxToolResultChars?: number;
419
425
  }) {
420
426
  this.agentId = agentId;
427
+ this.codeSessionKey = codeSessionKey;
421
428
  this.name = name;
422
429
  this.provider = provider;
423
430
  this.clientOptions = clientOptions;
@@ -16,6 +16,7 @@ import type {
16
16
  BaseMessage,
17
17
  MessageContent,
18
18
  } from '@langchain/core/messages';
19
+ import type { BaseChannel, OverwriteValue } from '@langchain/langgraph';
19
20
  import type { ToolCall } from '@langchain/core/messages/tool';
20
21
  import type {
21
22
  ReplayableSubagentTool,
@@ -167,6 +168,7 @@ import { createSchemaOnlyTools } from '@/tools/schema';
167
168
  import { AgentContext } from '@/agents/AgentContext';
168
169
  import { createFakeStreamingLLM } from '@/llm/fake';
169
170
  import { handleToolCalls } from '@/tools/handlers';
171
+ import { isRunStepResumeState } from '@/tools/runStepResume';
170
172
  import { isThinkingEnabled } from '@/llm/request';
171
173
  import { resolveMaxSeals } from '@/llm/preempt';
172
174
  import { initializeModel } from '@/llm/init';
@@ -673,11 +675,36 @@ function getDispatchableFinalReasoningContent({
673
675
  return responseReasoningContent;
674
676
  }
675
677
 
678
+ function createEmptyRunStepResumeState(): t.RunStepResumeState {
679
+ return {
680
+ version: 1,
681
+ revision: 0,
682
+ nextIndex: 0,
683
+ toolCallSteps: [],
684
+ steps: [],
685
+ };
686
+ }
687
+
688
+ type RunStepStateChannel = BaseChannel<
689
+ t.RunStepResumeState,
690
+ t.RunStepResumeState | OverwriteValue<t.RunStepResumeState>
691
+ >;
692
+
693
+ function buildRunStepStateAnnotation(): RunStepStateChannel {
694
+ return Annotation<t.RunStepResumeState>({
695
+ reducer: (current, update) =>
696
+ update.revision >= current.revision ? update : current,
697
+ default: createEmptyRunStepResumeState,
698
+ });
699
+ }
700
+
676
701
  export abstract class Graph<
677
702
  T extends t.BaseGraphState = t.BaseGraphState,
678
703
  _TNodeName extends string = string,
679
704
  > {
680
705
  abstract resetValues(keepContent?: boolean, checkpointScope?: string): void;
706
+ abstract createRunStepResumeState(): t.RunStepResumeState;
707
+ abstract restoreRunStepResumeState(state?: t.RunStepResumeState): void;
681
708
  restoreCheckpointMessages(
682
709
  _messages: BaseMessage[],
683
710
  _pendingMessages?: BaseMessage[]
@@ -739,6 +766,8 @@ export abstract class Graph<
739
766
  prelimMessageIdsByStepKey: Map<string, string> = new Map();
740
767
  config: RunnableConfig | undefined;
741
768
  contentData: t.RunStep[] = [];
769
+ protected nextContentIndex = 0;
770
+ protected runStepStateRevision = 0;
742
771
  stepKeyIds: Map<string, string[]> = new Map<string, string[]>();
743
772
  contentIndexMap: Map<string, number> = new Map();
744
773
  toolCallStepIds: Map<string, string> = new Map();
@@ -866,6 +895,8 @@ export abstract class Graph<
866
895
  this.signal = undefined;
867
896
  this.callerSignal = undefined;
868
897
  this.contentData = [];
898
+ this.nextContentIndex = 0;
899
+ this.runStepStateRevision = 0;
869
900
  this.contentIndexMap = new Map();
870
901
  this.stepKeyIds = new Map();
871
902
  this.toolCallStepIds.clear();
@@ -950,15 +981,18 @@ export abstract class Graph<
950
981
 
951
982
  /**
952
983
  * Tracks a tool call whose completion must arrive before its step can be
953
- * considered finished. Registered wherever `toolCallStepIds` gains entries,
954
- * except cross-process subagent resume restoration, where pending state
955
- * cannot be faithfully rebuilt and closes fall back to completions/sweep.
984
+ * considered finished. Registered wherever `toolCallStepIds` gains entries.
956
985
  */
957
986
  registerPendingToolCall(toolCallId: string, stepId: string): void {
958
987
  if (!toolCallId || !stepId) {
959
988
  return;
960
989
  }
961
- this.getPendingToolCallSet(stepId).add(toolCallId);
990
+ const pending = this.getPendingToolCallSet(stepId);
991
+ const size = pending.size;
992
+ pending.add(toolCallId);
993
+ if (pending.size !== size) {
994
+ this.runStepStateRevision += 1;
995
+ }
962
996
  }
963
997
 
964
998
  /** Lazily creates a step's pending-completions set; callers registering a
@@ -1063,6 +1097,7 @@ export abstract class Graph<
1063
1097
  ),
1064
1098
  ],
1065
1099
  eagerToolSuppressions: [...this.eagerEventToolSuppressions],
1100
+ runStepState: this.createRunStepResumeState(),
1066
1101
  ...(this._toolOutputRegistry == null
1067
1102
  ? {}
1068
1103
  : {
@@ -1099,6 +1134,7 @@ export abstract class Graph<
1099
1134
  throw new Error('Cannot restore disabled tool output references.');
1100
1135
  }
1101
1136
 
1137
+ this.restoreRunStepResumeState(state.runStepState);
1102
1138
  this.toolCallStepIds.clear();
1103
1139
  for (const { toolCallId, stepId } of state.toolCallSteps) {
1104
1140
  this.toolCallStepIds.set(toolCallId, stepId);
@@ -1469,6 +1505,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1469
1505
  this.config = resetIfNotEmpty(this.config, undefined);
1470
1506
  if (keepContent !== true) {
1471
1507
  this.contentData = resetIfNotEmpty(this.contentData, []);
1508
+ this.nextContentIndex = 0;
1509
+ this.runStepStateRevision = 0;
1472
1510
  this.contentIndexMap = resetIfNotEmpty(this.contentIndexMap, new Map());
1473
1511
  }
1474
1512
  this.stepKeyIds = resetIfNotEmpty(this.stepKeyIds, new Map());
@@ -1737,6 +1775,78 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1737
1775
 
1738
1776
  /* Run Step Processing */
1739
1777
 
1778
+ createRunStepResumeState(): t.RunStepResumeState {
1779
+ const steps: t.RunStepResumeEntry[] = [];
1780
+ for (const runStep of this.contentData) {
1781
+ if (runStep.status != null && runStep.status !== 'in_progress') {
1782
+ continue;
1783
+ }
1784
+ const agentKey = runStep.agentId ?? '';
1785
+ steps.push({
1786
+ step: structuredClone(runStep),
1787
+ pendingToolCallIds: [
1788
+ ...(this.pendingToolCallsByStep.get(runStep.id) ?? []),
1789
+ ],
1790
+ ...(this.latestCompletionByStep.has(runStep.id)
1791
+ ? {
1792
+ latestCompletionAt: this.latestCompletionByStep.get(runStep.id),
1793
+ }
1794
+ : {}),
1795
+ openMessageStep:
1796
+ this.openMessageStepByAgent.get(agentKey) === runStep.id,
1797
+ });
1798
+ }
1799
+ return {
1800
+ version: 1,
1801
+ revision: this.runStepStateRevision,
1802
+ nextIndex: this.nextContentIndex,
1803
+ toolCallSteps: [...this.toolCallStepIds].map(
1804
+ ([toolCallId, stepId]) => ({ toolCallId, stepId })
1805
+ ),
1806
+ steps,
1807
+ };
1808
+ }
1809
+
1810
+ restoreRunStepResumeState(state?: t.RunStepResumeState): void {
1811
+ if (
1812
+ !isRunStepResumeState(state) ||
1813
+ this.contentData.length > 0 ||
1814
+ this.runStepStateRevision > 0
1815
+ ) {
1816
+ return;
1817
+ }
1818
+
1819
+ this.nextContentIndex = state.nextIndex;
1820
+ this.runStepStateRevision = state.revision;
1821
+ for (const { toolCallId, stepId } of state.toolCallSteps) {
1822
+ this.toolCallStepIds.set(toolCallId, stepId);
1823
+ }
1824
+ for (const entry of state.steps) {
1825
+ const runStep = structuredClone(entry.step);
1826
+ runStep.status = 'in_progress';
1827
+ this.nextContentIndex = Math.max(
1828
+ this.nextContentIndex,
1829
+ runStep.index + 1
1830
+ );
1831
+ const position = this.contentData.length;
1832
+ this.contentData.push(runStep);
1833
+ this.contentIndexMap.set(runStep.id, position);
1834
+ if (entry.pendingToolCallIds.length > 0) {
1835
+ const pending = new Set(entry.pendingToolCallIds);
1836
+ this.pendingToolCallsByStep.set(runStep.id, pending);
1837
+ }
1838
+ if (entry.latestCompletionAt != null) {
1839
+ this.latestCompletionByStep.set(
1840
+ runStep.id,
1841
+ entry.latestCompletionAt
1842
+ );
1843
+ }
1844
+ if (entry.openMessageStep) {
1845
+ this.openMessageStepByAgent.set(runStep.agentId ?? '', runStep.id);
1846
+ }
1847
+ }
1848
+ }
1849
+
1740
1850
  getRunStep(stepId: string): t.RunStep | undefined {
1741
1851
  const index = this.contentIndexMap.get(stepId);
1742
1852
  if (index !== undefined) {
@@ -1813,9 +1923,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1813
1923
  * would mutate the wrong agent's step. Assigning the index at push time
1814
1924
  * also supersedes whatever the caller computed before this await point.
1815
1925
  */
1816
- runStep.index = this.contentData.length;
1926
+ runStep.index = Math.max(this.nextContentIndex, this.contentData.length);
1927
+ this.nextContentIndex = runStep.index + 1;
1928
+ const position = this.contentData.length;
1817
1929
  this.contentData.push(runStep);
1818
- this.contentIndexMap.set(runStep.id, runStep.index);
1930
+ this.contentIndexMap.set(runStep.id, position);
1819
1931
  if (trackAsOpenMessageStep && runStep.type === StepTypes.MESSAGE_CREATION) {
1820
1932
  this.openMessageStepByAgent.set(agentKey, runStep.id);
1821
1933
  } else {
@@ -1846,6 +1958,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1846
1958
  * absorb the latency of an arbitrarily slow predecessor handler.
1847
1959
  */
1848
1960
  runStep.created_at = Date.now();
1961
+ this.runStepStateRevision += 1;
1849
1962
  }
1850
1963
 
1851
1964
  /**
@@ -1906,6 +2019,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1906
2019
  closedEvent.stepIndex = runStep.stepIndex;
1907
2020
  }
1908
2021
  this.untrackRunStep(runStep);
2022
+ this.runStepStateRevision += 1;
1909
2023
 
1910
2024
  const handler = this.handlerRegistry?.getHandler(
1911
2025
  GraphEvents.ON_RUN_STEP_CLOSED
@@ -1950,10 +2064,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1950
2064
  return;
1951
2065
  }
1952
2066
  const { toolCallId, metadata, at } = options ?? {};
2067
+ let lifecycleChanged = false;
1953
2068
  if (at != null) {
1954
2069
  const latest = this.latestCompletionByStep.get(stepId);
1955
2070
  if (latest == null || at > latest) {
1956
2071
  this.latestCompletionByStep.set(stepId, at);
2072
+ lifecycleChanged = true;
1957
2073
  }
1958
2074
  }
1959
2075
  const closeAt = this.latestCompletionByStep.get(stepId) ?? at;
@@ -1963,9 +2079,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1963
2079
  return;
1964
2080
  }
1965
2081
  if (toolCallId != null && toolCallId !== '') {
1966
- pending.delete(toolCallId);
2082
+ lifecycleChanged = pending.delete(toolCallId) || lifecycleChanged;
1967
2083
  }
1968
2084
  if (pending.size > 0) {
2085
+ if (lifecycleChanged) {
2086
+ this.runStepStateRevision += 1;
2087
+ }
1969
2088
  return;
1970
2089
  }
1971
2090
  await this.closeRunStep(stepId, 'completed', { metadata, at: closeAt });
@@ -2391,6 +2510,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2391
2510
  agentLangfuse: agentContext?.langfuse,
2392
2511
  eventDrivenMode: true,
2393
2512
  sessions: this.sessions,
2513
+ codeSessionKey: agentContext?.codeSessionKey,
2394
2514
  toolDefinitions: toolDefMap,
2395
2515
  // `agentId` is the subagent-scope marker — set ONLY for child-run
2396
2516
  // graphs (hooks fire for child scopes too, via the inherited
@@ -2417,6 +2537,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2417
2537
  fileCheckpointer: this.getOrCreateFileCheckpointer(),
2418
2538
  getBreakerSignal: (): AbortSignal => this.breakerAbort.signal,
2419
2539
  getRunScope: (): RunBreakerScope => this.runScope,
2540
+ restoreRunStepResumeState: (state, config): void => {
2541
+ this.config = config;
2542
+ this.restoreRunStepResumeState(state);
2543
+ },
2544
+ createRunStepResumeState: (): t.RunStepResumeState =>
2545
+ this.createRunStepResumeState(),
2420
2546
  errorHandler: (data, metadata): Promise<boolean> =>
2421
2547
  StandardGraph.handleToolCallErrorStatic(this, data, metadata),
2422
2548
  });
@@ -2471,6 +2597,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2471
2597
  StandardGraph.handleToolCallErrorStatic(this, data, metadata),
2472
2598
  toolRegistry: agentContext?.toolRegistry,
2473
2599
  sessions: this.sessions,
2600
+ codeSessionKey: agentContext?.codeSessionKey,
2474
2601
  toolExecution: this.toolExecution,
2475
2602
  codeSessionToolNames: this.codeSessionToolNames,
2476
2603
  interruptingToolNames: effectiveInterruptingToolNames,
@@ -2482,6 +2609,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2482
2609
  fileCheckpointer: this.getOrCreateFileCheckpointer(),
2483
2610
  getBreakerSignal: (): AbortSignal => this.breakerAbort.signal,
2484
2611
  getRunScope: (): RunBreakerScope => this.runScope,
2612
+ restoreRunStepResumeState: (state, config): void => {
2613
+ this.config = config;
2614
+ this.restoreRunStepResumeState(state);
2615
+ },
2616
+ createRunStepResumeState: (): t.RunStepResumeState =>
2617
+ this.createRunStepResumeState(),
2485
2618
  });
2486
2619
  this.registerCompiledToolNode(node);
2487
2620
  return node;
@@ -4571,6 +4704,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4571
4704
  };
4572
4705
  }
4573
4706
 
4707
+ protected createRunStepStateAnnotation(): RunStepStateChannel {
4708
+ return buildRunStepStateAnnotation();
4709
+ }
4710
+
4574
4711
  createAgentNode(agentId: string): t.CompiledAgentWorfklow {
4575
4712
  const getConfig = (): RunnableConfig | undefined => this.config;
4576
4713
  const agentContext = this.agentContexts.get(agentId);
@@ -4779,6 +4916,22 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4779
4916
  const agentNode = `${AGENT}${agentId}` as const;
4780
4917
  const toolNode = `${TOOLS}${agentId}` as const;
4781
4918
  const summarizeNode = `${SUMMARIZE}${agentId}` as const;
4919
+ const callModel = this.createCallModel(agentId);
4920
+ const callTools = this.initializeTools({
4921
+ currentTools: agentContext.tools,
4922
+ currentToolMap: agentContext.toolMap,
4923
+ agentContext,
4924
+ });
4925
+ const invokeWithRunStepState = async (
4926
+ state: t.AgentSubgraphState,
4927
+ config: RunnableConfig | undefined,
4928
+ invoke: () => Promise<Partial<t.AgentSubgraphState>>
4929
+ ): Promise<Partial<t.AgentSubgraphState>> => {
4930
+ this.config = config;
4931
+ this.restoreRunStepResumeState(state.runStepState);
4932
+ const result = await invoke();
4933
+ return { ...result, runStepState: this.createRunStepResumeState() };
4934
+ };
4782
4935
 
4783
4936
  const routeMessage = (
4784
4937
  state: t.AgentSubgraphState,
@@ -4815,6 +4968,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4815
4968
  ) => b,
4816
4969
  default: () => undefined,
4817
4970
  }),
4971
+ runStepState: this.createRunStepStateAnnotation(),
4818
4972
  });
4819
4973
 
4820
4974
  const readChargeCredits = ():
@@ -4833,14 +4987,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4833
4987
  };
4834
4988
 
4835
4989
  const workflow = new StateGraph(StateAnnotation)
4836
- .addNode(agentNode, this.createCallModel(agentId))
4990
+ .addNode(agentNode, (state, config) =>
4991
+ invokeWithRunStepState(state, config, () => callModel(state, config))
4992
+ )
4837
4993
  .addNode(
4838
4994
  toolNode,
4839
- this.initializeTools({
4840
- currentTools: agentContext.tools,
4841
- currentToolMap: agentContext.toolMap,
4842
- agentContext,
4843
- })
4995
+ callTools
4844
4996
  )
4845
4997
  .addNode(
4846
4998
  summarizeNode,
@@ -5019,6 +5171,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5019
5171
  },
5020
5172
  default: () => [],
5021
5173
  }),
5174
+ runStepState: this.createRunStepStateAnnotation(),
5022
5175
  });
5023
5176
  const workflow = new StateGraph(StateAnnotation)
5024
5177
  .addNode(
@@ -5097,6 +5250,32 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5097
5250
  throw new Error('No config provided');
5098
5251
  }
5099
5252
 
5253
+ if (stepDetails.type === StepTypes.TOOL_CALLS && stepDetails.tool_calls) {
5254
+ let replayStepId: string | undefined;
5255
+ let reusesOpenStep = stepDetails.tool_calls.length > 0;
5256
+ for (const toolCall of stepDetails.tool_calls) {
5257
+ const toolCallId = toolCall.id ?? '';
5258
+ const mappedStepId = this.toolCallStepIds.get(toolCallId);
5259
+ if (!toolCallId || mappedStepId == null) {
5260
+ reusesOpenStep = false;
5261
+ continue;
5262
+ }
5263
+ replayStepId ??= mappedStepId;
5264
+ if (mappedStepId !== replayStepId) {
5265
+ reusesOpenStep = false;
5266
+ }
5267
+ }
5268
+ const replayStep =
5269
+ replayStepId == null ? undefined : this.getRunStep(replayStepId);
5270
+ if (
5271
+ reusesOpenStep &&
5272
+ replayStep != null &&
5273
+ (replayStep.status == null || replayStep.status === 'in_progress')
5274
+ ) {
5275
+ return replayStep.id;
5276
+ }
5277
+ }
5278
+
5100
5279
  const [stepId, stepIndex] = this.generateStepId(stepKey);
5101
5280
  if (stepDetails.type === StepTypes.TOOL_CALLS && stepDetails.tool_calls) {
5102
5281
  let pendingToolCalls: Set<string> | undefined;
@@ -1036,6 +1036,7 @@ export class MultiAgentGraph extends StandardGraph {
1036
1036
  reducer: (_current, update) => update,
1037
1037
  default: () => undefined,
1038
1038
  }),
1039
+ runStepState: this.createRunStepStateAnnotation(),
1039
1040
  });
1040
1041
 
1041
1042
  const builder = new StateGraph(StateAnnotation);