@librechat/agents 3.1.78 → 3.1.80-dev.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 (76) hide show
  1. package/dist/cjs/llm/anthropic/index.cjs +44 -55
  2. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +33 -21
  4. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +0 -4
  6. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  7. package/dist/cjs/messages/anthropicToolCache.cjs +48 -15
  8. package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
  9. package/dist/cjs/messages/format.cjs +97 -14
  10. package/dist/cjs/messages/format.cjs.map +1 -1
  11. package/dist/cjs/tools/BashExecutor.cjs +10 -2
  12. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  13. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -1
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  15. package/dist/cjs/tools/CodeExecutor.cjs +16 -5
  16. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  17. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +9 -4
  18. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +63 -40
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +14 -16
  22. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
  23. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
  24. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  25. package/dist/esm/llm/anthropic/index.mjs +43 -54
  26. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  27. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -21
  28. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  29. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +0 -4
  30. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  31. package/dist/esm/messages/anthropicToolCache.mjs +48 -15
  32. package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
  33. package/dist/esm/messages/format.mjs +97 -14
  34. package/dist/esm/messages/format.mjs.map +1 -1
  35. package/dist/esm/tools/BashExecutor.mjs +10 -2
  36. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  37. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -1
  38. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  39. package/dist/esm/tools/CodeExecutor.mjs +16 -5
  40. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  41. package/dist/esm/tools/ProgrammaticToolCalling.mjs +9 -4
  42. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  43. package/dist/esm/tools/ToolNode.mjs +63 -40
  44. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  45. package/dist/esm/tools/local/LocalExecutionEngine.mjs +14 -16
  46. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
  47. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
  48. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  49. package/dist/types/llm/anthropic/index.d.ts +1 -9
  50. package/dist/types/messages/anthropicToolCache.d.ts +5 -5
  51. package/dist/types/types/tools.d.ts +82 -17
  52. package/package.json +1 -1
  53. package/src/llm/anthropic/index.ts +55 -64
  54. package/src/llm/anthropic/llm.spec.ts +585 -0
  55. package/src/llm/anthropic/utils/message_inputs.ts +36 -21
  56. package/src/llm/anthropic/utils/message_outputs.ts +0 -4
  57. package/src/llm/anthropic/utils/server-tool-inputs.test.ts +95 -13
  58. package/src/messages/__tests__/anthropicToolCache.test.ts +46 -0
  59. package/src/messages/anthropicToolCache.ts +70 -25
  60. package/src/messages/format.ts +117 -18
  61. package/src/messages/formatAgentMessages.test.ts +202 -1
  62. package/src/scripts/code_exec_multi_session.ts +4 -4
  63. package/src/specs/summarization.test.ts +3 -3
  64. package/src/tools/BashExecutor.ts +11 -3
  65. package/src/tools/BashProgrammaticToolCalling.ts +6 -6
  66. package/src/tools/CodeExecutor.ts +17 -6
  67. package/src/tools/ProgrammaticToolCalling.ts +14 -10
  68. package/src/tools/ToolNode.ts +85 -48
  69. package/src/tools/__tests__/LocalExecutionRoots.test.ts +8 -0
  70. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +9 -2
  71. package/src/tools/__tests__/ToolNode.session.test.ts +131 -50
  72. package/src/tools/local/LocalExecutionEngine.ts +55 -54
  73. package/src/tools/local/LocalExecutionTools.ts +2 -2
  74. package/src/tools/local/LocalProgrammaticToolCalling.ts +23 -6
  75. package/src/types/diff.d.ts +15 -0
  76. package/src/types/tools.ts +79 -17
@@ -249,22 +249,62 @@ function normalizeApprovalDecisions(
249
249
  * - `artifact.session_id` (the `sessionId` arg here) is the EXEC session
250
250
  * — the sandbox VM that ran the code. It's transient and torn down
251
251
  * post-execution; subsequent calls cannot reuse it as a sandbox.
252
- * - `file.session_id` on each `artifact.files[i]` is the STORAGE
252
+ * - `file.storage_session_id` on each `artifact.files[i]` is the STORAGE
253
253
  * session — the file-server bucket prefix where the artifact actually
254
254
  * lives and is served from.
255
255
  *
256
- * Per-file `session_id` is preserved (not overwritten with the exec id)
257
- * because `_injected_files` are looked up against the file-server's
258
- * storage path on subsequent tool calls. Stomping the storage id with
259
- * the exec id silently 404s every follow-up tool call within the same
260
- * run — `cat /mnt/data/foo.txt` reports "No such file or directory"
261
- * because the worker can't mount a file at a path the storage doesn't
262
- * know about. Fall back to `sessionId` only when the per-file id is
263
- * absent (older worker payloads).
256
+ * Per-file `storage_session_id` is preserved (not overwritten with the
257
+ * exec id) because `_injected_files` are looked up against the
258
+ * file-server's storage path on subsequent tool calls. Stomping the
259
+ * storage id with the exec id silently 404s every follow-up tool call
260
+ * within the same run — `cat /mnt/data/foo.txt` reports "No such file
261
+ * or directory" because the worker can't mount a file at a path the
262
+ * storage doesn't know about. Fall back to the exec id only when the
263
+ * per-file id is absent (e.g. inline `content` files have no persistent
264
+ * storage location).
264
265
  */
266
+ /**
267
+ * Builds a `CodeEnvFile` ref from an arbitrary `FileRef`-like input,
268
+ * narrowing onto the discriminated union: `kind: 'skill'` requires
269
+ * `version`, other kinds forbid it.
270
+ *
271
+ * Defaults `kind` to `'user'` when unset — most ad-hoc files are
272
+ * user-private; shared resources (skills/agents) populate their kind
273
+ * upstream. A skill ref missing `version` falls back to `'user'` so
274
+ * the upstream contract bug surfaces as a degraded sessionKey rather
275
+ * than a runtime crash; primeSkillFiles is the only writer, and it
276
+ * always sets `version` — see LC packages/api/src/agents/skillFiles.ts.
277
+ */
278
+ function toInjectedFileRef(
279
+ file: {
280
+ id: string;
281
+ name: string;
282
+ storage_session_id?: string;
283
+ kind?: t.CodeEnvKind;
284
+ version?: number;
285
+ },
286
+ execSessionId: string
287
+ ): t.CodeEnvFile {
288
+ const base = {
289
+ id: file.id,
290
+ name: file.name,
291
+ /* Inline `content` files have no persistent storage location;
292
+ * fall back to the execution session id for those entries. */
293
+ storage_session_id: file.storage_session_id ?? execSessionId,
294
+ };
295
+ const kind = file.kind ?? 'user';
296
+ if (kind === 'skill' && file.version != null) {
297
+ return { ...base, kind: 'skill', version: file.version };
298
+ }
299
+ if (kind === 'agent') {
300
+ return { ...base, kind: 'agent' };
301
+ }
302
+ return { ...base, kind: 'user' };
303
+ }
304
+
265
305
  function updateCodeSession(
266
306
  sessions: t.ToolSessionMap,
267
- sessionId: string,
307
+ execSessionId: string,
268
308
  files: t.FileRefs | undefined
269
309
  ): void {
270
310
  const newFiles = files ?? [];
@@ -276,20 +316,20 @@ function updateCodeSession(
276
316
  if (newFiles.length > 0) {
277
317
  const filesWithSession: t.FileRefs = newFiles.map((file) => ({
278
318
  ...file,
279
- session_id: file.session_id ?? sessionId,
319
+ storage_session_id: file.storage_session_id ?? execSessionId,
280
320
  }));
281
321
  const newFileNames = new Set(filesWithSession.map((f) => f.name));
282
322
  const filteredExisting = existingFiles.filter(
283
323
  (f) => !newFileNames.has(f.name)
284
324
  );
285
325
  sessions.set(Constants.EXECUTE_CODE, {
286
- session_id: sessionId,
326
+ session_id: execSessionId,
287
327
  files: [...filteredExisting, ...filesWithSession],
288
328
  lastUpdated: Date.now(),
289
329
  });
290
330
  } else {
291
331
  sessions.set(Constants.EXECUTE_CODE, {
292
- session_id: sessionId,
332
+ session_id: execSessionId,
293
333
  files: existingFiles,
294
334
  lastUpdated: Date.now(),
295
335
  });
@@ -400,7 +440,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
400
440
  this.loadRuntimeTools = loadRuntimeTools;
401
441
  this.errorHandler = errorHandler;
402
442
  this.toolUsageCount = new Map<string, number>();
403
- this.toolRegistry = resolveLocalToolRegistry({ toolRegistry, toolExecution });
443
+ this.toolRegistry = resolveLocalToolRegistry({
444
+ toolRegistry,
445
+ toolExecution,
446
+ });
404
447
  this.sessions = sessions;
405
448
  this.eventDrivenMode = eventDrivenMode ?? false;
406
449
  this.agentId = agentId;
@@ -581,8 +624,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
581
624
  resolveFn = <T>(_runId: string | undefined, args: T): ResolveResult<T> =>
582
625
  preBatchSnapshot.resolve(args);
583
626
  } else if (registry != null) {
584
- resolveFn = <T>(runIdArg: string | undefined, args: T): ResolveResult<T> =>
585
- registry.resolve(runIdArg, args);
627
+ resolveFn = <T>(
628
+ runIdArg: string | undefined,
629
+ args: T
630
+ ): ResolveResult<T> => registry.resolve(runIdArg, args);
586
631
  }
587
632
  /**
588
633
  * Precompute the reference key once per call — captured locally
@@ -717,20 +762,17 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
717
762
  const codeSession = this.sessions?.get(Constants.EXECUTE_CODE) as
718
763
  | t.CodeSessionContext
719
764
  | undefined;
720
- if (codeSession?.session_id != null && codeSession.session_id !== '') {
765
+ const execSessionId = codeSession?.session_id;
766
+ if (execSessionId != null && execSessionId !== '') {
721
767
  invokeParams = {
722
768
  ...invokeParams,
723
- session_id: codeSession.session_id,
769
+ session_id: execSessionId,
724
770
  };
725
771
 
726
- if (codeSession.files != null && codeSession.files.length > 0) {
727
- const fileRefs: t.CodeEnvFile[] = codeSession.files.map((file) => ({
728
- session_id: file.session_id ?? codeSession.session_id,
729
- id: file.id,
730
- name: file.name,
731
- ...(file.entity_id != null ? { entity_id: file.entity_id } : {}),
732
- }));
733
- invokeParams._injected_files = fileRefs;
772
+ if (codeSession?.files != null && codeSession.files.length > 0) {
773
+ invokeParams._injected_files = codeSession.files.map((file) =>
774
+ toInjectedFileRef(file, execSessionId)
775
+ );
734
776
  }
735
777
  }
736
778
  }
@@ -950,10 +992,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
950
992
  ): Promise<BaseMessage | Command> {
951
993
  const runId = (config.configurable?.run_id as string | undefined) ?? '';
952
994
  const hookRegistry = this.hookRegistry;
953
- const hasPreHook =
954
- hookRegistry?.hasHookFor('PreToolUse', runId) === true;
955
- const hasPostHook =
956
- hookRegistry?.hasHookFor('PostToolUse', runId) === true;
995
+ const hasPreHook = hookRegistry?.hasHookFor('PreToolUse', runId) === true;
996
+ const hasPostHook = hookRegistry?.hasHookFor('PostToolUse', runId) === true;
957
997
  const hasFailureHook =
958
998
  hookRegistry?.hasHookFor('PostToolUseFailure', runId) === true;
959
999
 
@@ -989,8 +1029,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
989
1029
  // assignment.)
990
1030
  const cachedTurn =
991
1031
  call.id != null && call.id !== ''
992
- ? this.directPathTurns.get(call.id) ??
993
- this.toolCallTurns.get(call.id)
1032
+ ? (this.directPathTurns.get(call.id) ?? this.toolCallTurns.get(call.id))
994
1033
  : undefined;
995
1034
  if (cachedTurn != null) {
996
1035
  usageCount = cachedTurn;
@@ -1156,10 +1195,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1156
1195
  return this.blockDirectCall({
1157
1196
  call,
1158
1197
  resolvedArgs,
1159
- reason:
1160
- decision.reason ??
1161
- preResult.reason ??
1162
- 'Rejected by user',
1198
+ reason: decision.reason ?? preResult.reason ?? 'Rejected by user',
1163
1199
  hookRegistry,
1164
1200
  runId,
1165
1201
  threadId,
@@ -1173,7 +1209,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1173
1209
  return this.blockDirectCall({
1174
1210
  call,
1175
1211
  resolvedArgs,
1176
- reason: 'Approval payload `respond` was missing a string `responseText`',
1212
+ reason:
1213
+ 'Approval payload `respond` was missing a string `responseText`',
1177
1214
  hookRegistry,
1178
1215
  runId,
1179
1216
  threadId,
@@ -1464,17 +1501,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1464
1501
  return undefined;
1465
1502
  }
1466
1503
 
1504
+ const execSessionId = codeSession.session_id;
1467
1505
  const context: NonNullable<t.ToolCallRequest['codeSessionContext']> = {
1468
- session_id: codeSession.session_id,
1506
+ session_id: execSessionId,
1469
1507
  };
1470
1508
 
1471
1509
  if (codeSession.files && codeSession.files.length > 0) {
1472
- context.files = codeSession.files.map((file) => ({
1473
- session_id: file.session_id ?? codeSession.session_id,
1474
- id: file.id,
1475
- name: file.name,
1476
- ...(file.entity_id != null ? { entity_id: file.entity_id } : {}),
1477
- }));
1510
+ context.files = codeSession.files.map((file) =>
1511
+ toInjectedFileRef(file, execSessionId)
1512
+ );
1478
1513
  }
1479
1514
 
1480
1515
  return context;
@@ -1509,11 +1544,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1509
1544
  }
1510
1545
 
1511
1546
  const artifact = result.artifact as t.CodeExecutionArtifact | undefined;
1512
- if (artifact?.session_id == null || artifact.session_id === '') {
1547
+ const execSessionId = artifact?.session_id;
1548
+ if (execSessionId == null || execSessionId === '') {
1513
1549
  continue;
1514
1550
  }
1515
1551
 
1516
- updateCodeSession(this.sessions, artifact.session_id!, artifact.files);
1552
+ updateCodeSession(this.sessions, execSessionId, artifact?.files);
1517
1553
  }
1518
1554
  }
1519
1555
 
@@ -1558,8 +1594,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1558
1594
  const artifact = toolMessage.artifact as
1559
1595
  | t.CodeExecutionArtifact
1560
1596
  | undefined;
1561
- if (artifact?.session_id != null && artifact.session_id !== '') {
1562
- updateCodeSession(this.sessions, artifact.session_id, artifact.files);
1597
+ const execSessionId = artifact?.session_id;
1598
+ if (execSessionId != null && execSessionId !== '') {
1599
+ updateCodeSession(this.sessions, execSessionId, artifact?.files);
1563
1600
  }
1564
1601
  }
1565
1602
 
@@ -0,0 +1,8 @@
1
+ import { getReadRoots, getWriteRoots } from '../local/LocalExecutionEngine';
2
+
3
+ describe('local execution workspace roots', () => {
4
+ it('uses the current working directory boundary when config is omitted', () => {
5
+ expect(getWriteRoots()).toEqual([process.cwd()]);
6
+ expect(getReadRoots()).toEqual([process.cwd()]);
7
+ });
8
+ });
@@ -1064,8 +1064,11 @@ for member in team:
1064
1064
  });
1065
1065
 
1066
1066
  describe('bash bridge script does not require python3 (Codex P2 #19)', () => {
1067
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1068
- const { _createBashProgramForTests } = require('../local/LocalProgrammaticToolCalling');
1067
+ /* eslint-disable @typescript-eslint/no-require-imports */
1068
+ const {
1069
+ _createBashProgramForTests,
1070
+ } = require('../local/LocalProgrammaticToolCalling');
1071
+ /* eslint-enable @typescript-eslint/no-require-imports */
1069
1072
 
1070
1073
  it('uses curl as the primary HTTP helper with python3 only as fallback', () => {
1071
1074
  const script: string = _createBashProgramForTests(
@@ -1129,6 +1132,7 @@ for member in team:
1129
1132
  const registry = new HookRegistry();
1130
1133
  registry.register('PreToolUse', {
1131
1134
  hooks: [
1135
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
1132
1136
  async (input) => {
1133
1137
  if (input.toolName === 'write_file') {
1134
1138
  return { decision: 'deny', reason: 'no writes from bridge' };
@@ -1179,6 +1183,7 @@ for member in team:
1179
1183
 
1180
1184
  const registry = new HookRegistry();
1181
1185
  registry.register('PreToolUse', {
1186
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
1182
1187
  hooks: [async () => ({ decision: 'allow' })],
1183
1188
  });
1184
1189
 
@@ -1200,6 +1205,7 @@ for member in team:
1200
1205
  const registry = new HookRegistry();
1201
1206
  registry.register('PreToolUse', {
1202
1207
  hooks: [
1208
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
1203
1209
  async () => ({
1204
1210
  decision: 'allow',
1205
1211
  updatedInput: { file_path: '/tmp/rewritten' },
@@ -1224,6 +1230,7 @@ for member in team:
1224
1230
 
1225
1231
  const registry = new HookRegistry();
1226
1232
  registry.register('PreToolUse', {
1233
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
1227
1234
  hooks: [async () => ({ decision: 'ask' })],
1228
1235
  });
1229
1236
 
@@ -51,14 +51,22 @@ function createAIMessageWithCodeCall(callId: string): AIMessage {
51
51
 
52
52
  describe('ToolNode code execution session management', () => {
53
53
  describe('session injection via runTool (direct execution)', () => {
54
- it('injects session_id and _injected_files when session has files', async () => {
54
+ it('injects session ids (both names) and _injected_files when session has files', async () => {
55
55
  const capturedConfigs: Record<string, unknown>[] = [];
56
56
  const sessions: t.ToolSessionMap = new Map();
57
57
  sessions.set(Constants.EXECUTE_CODE, {
58
58
  session_id: 'prev-session-abc',
59
59
  files: [
60
- { id: 'file1', name: 'data.csv', session_id: 'prev-session-abc' },
61
- { id: 'file2', name: 'chart.png', session_id: 'prev-session-abc' },
60
+ {
61
+ id: 'file1',
62
+ name: 'data.csv',
63
+ storage_session_id: 'prev-session-abc',
64
+ },
65
+ {
66
+ id: 'file2',
67
+ name: 'chart.png',
68
+ storage_session_id: 'prev-session-abc',
69
+ },
62
70
  ],
63
71
  lastUpdated: Date.now(),
64
72
  } satisfies t.CodeSessionContext);
@@ -70,14 +78,26 @@ describe('ToolNode code execution session management', () => {
70
78
  await toolNode.invoke({ messages: [aiMsg] });
71
79
 
72
80
  expect(capturedConfigs).toHaveLength(1);
81
+ /* Both names injected so pre- and post-rename consumers see the
82
+ * field they expect. */
73
83
  expect(capturedConfigs[0].session_id).toBe('prev-session-abc');
74
84
  expect(capturedConfigs[0]._injected_files).toEqual([
75
- { session_id: 'prev-session-abc', id: 'file1', name: 'data.csv' },
76
- { session_id: 'prev-session-abc', id: 'file2', name: 'chart.png' },
85
+ {
86
+ id: 'file1',
87
+ name: 'data.csv',
88
+ storage_session_id: 'prev-session-abc',
89
+ kind: 'user',
90
+ },
91
+ {
92
+ id: 'file2',
93
+ name: 'chart.png',
94
+ storage_session_id: 'prev-session-abc',
95
+ kind: 'user',
96
+ },
77
97
  ]);
78
98
  });
79
99
 
80
- it('injects session_id even when session has no tracked files', async () => {
100
+ it('injects session ids even when session has no tracked files', async () => {
81
101
  const capturedConfigs: Record<string, unknown>[] = [];
82
102
  const sessions: t.ToolSessionMap = new Map();
83
103
  sessions.set(Constants.EXECUTE_CODE, {
@@ -112,14 +132,22 @@ describe('ToolNode code execution session management', () => {
112
132
  expect(capturedConfigs[0]._injected_files).toBeUndefined();
113
133
  });
114
134
 
115
- it('preserves per-file session_id for multi-session files', async () => {
135
+ it('preserves per-file storage_session_id for multi-session files', async () => {
116
136
  const capturedConfigs: Record<string, unknown>[] = [];
117
137
  const sessions: t.ToolSessionMap = new Map();
118
138
  sessions.set(Constants.EXECUTE_CODE, {
119
139
  session_id: 'session-B',
120
140
  files: [
121
- { id: 'f1', name: 'old.csv', session_id: 'session-A' },
122
- { id: 'f2', name: 'new.png', session_id: 'session-B' },
141
+ {
142
+ id: 'f1',
143
+ name: 'old.csv',
144
+ storage_session_id: 'session-A',
145
+ },
146
+ {
147
+ id: 'f2',
148
+ name: 'new.png',
149
+ storage_session_id: 'session-B',
150
+ },
123
151
  ],
124
152
  lastUpdated: Date.now(),
125
153
  } satisfies t.CodeSessionContext);
@@ -131,26 +159,27 @@ describe('ToolNode code execution session management', () => {
131
159
  await toolNode.invoke({ messages: [aiMsg] });
132
160
 
133
161
  const files = capturedConfigs[0]._injected_files as t.CodeEnvFile[];
134
- expect(files[0].session_id).toBe('session-A');
135
- expect(files[1].session_id).toBe('session-B');
162
+ expect(files[0].storage_session_id).toBe('session-A');
163
+ expect(files[1].storage_session_id).toBe('session-B');
136
164
  });
137
165
 
138
- it('forwards per-file entity_id for mixed-entity sessions', async () => {
166
+ it('forwards per-file kind and version for mixed-kind sessions', async () => {
139
167
  const capturedConfigs: Record<string, unknown>[] = [];
140
168
  const sessions: t.ToolSessionMap = new Map();
141
169
  sessions.set(Constants.EXECUTE_CODE, {
142
170
  session_id: 'session-A',
143
171
  files: [
144
172
  {
145
- id: 'skill-file',
173
+ id: 'skill-123',
146
174
  name: 'demo/SKILL.md',
147
- session_id: 'session-A',
148
- entity_id: 'skill-123',
175
+ storage_session_id: 'session-A',
176
+ kind: 'skill',
177
+ version: 7,
149
178
  },
150
179
  {
151
180
  id: 'user-file',
152
181
  name: 'attachment.csv',
153
- session_id: 'session-B',
182
+ storage_session_id: 'session-B',
154
183
  },
155
184
  ],
156
185
  lastUpdated: Date.now(),
@@ -165,15 +194,17 @@ describe('ToolNode code execution session management', () => {
165
194
  const files = capturedConfigs[0]._injected_files as t.CodeEnvFile[];
166
195
  expect(files).toEqual([
167
196
  {
168
- session_id: 'session-A',
169
- id: 'skill-file',
197
+ id: 'skill-123',
170
198
  name: 'demo/SKILL.md',
171
- entity_id: 'skill-123',
199
+ storage_session_id: 'session-A',
200
+ kind: 'skill',
201
+ version: 7,
172
202
  },
173
203
  {
174
- session_id: 'session-B',
175
204
  id: 'user-file',
176
205
  name: 'attachment.csv',
206
+ storage_session_id: 'session-B',
207
+ kind: 'user',
177
208
  },
178
209
  ]);
179
210
  });
@@ -184,7 +215,13 @@ describe('ToolNode code execution session management', () => {
184
215
  const sessions: t.ToolSessionMap = new Map();
185
216
  sessions.set(Constants.EXECUTE_CODE, {
186
217
  session_id: 'evt-session',
187
- files: [{ id: 'ef1', name: 'out.parquet', session_id: 'evt-session' }],
218
+ files: [
219
+ {
220
+ id: 'ef1',
221
+ name: 'out.parquet',
222
+ storage_session_id: 'evt-session',
223
+ },
224
+ ],
188
225
  lastUpdated: Date.now(),
189
226
  } satisfies t.CodeSessionContext);
190
227
 
@@ -201,7 +238,14 @@ describe('ToolNode code execution session management', () => {
201
238
 
202
239
  expect(context).toEqual({
203
240
  session_id: 'evt-session',
204
- files: [{ session_id: 'evt-session', id: 'ef1', name: 'out.parquet' }],
241
+ files: [
242
+ {
243
+ id: 'ef1',
244
+ name: 'out.parquet',
245
+ storage_session_id: 'evt-session',
246
+ kind: 'user',
247
+ },
248
+ ],
205
249
  });
206
250
  });
207
251
 
@@ -224,7 +268,9 @@ describe('ToolNode code execution session management', () => {
224
268
  toolNode as unknown as { getCodeSessionContext: () => unknown }
225
269
  ).getCodeSessionContext();
226
270
 
227
- expect(context).toEqual({ session_id: 'evt-session-empty' });
271
+ expect(context).toEqual({
272
+ session_id: 'evt-session-empty',
273
+ });
228
274
  });
229
275
 
230
276
  it('returns undefined when no session exists', () => {
@@ -244,18 +290,23 @@ describe('ToolNode code execution session management', () => {
244
290
  expect(context).toBeUndefined();
245
291
  });
246
292
 
247
- it('forwards per-file entity_id to event-driven request context', () => {
293
+ it('forwards per-file kind and version to event-driven request context', () => {
248
294
  const sessions: t.ToolSessionMap = new Map();
249
295
  sessions.set(Constants.EXECUTE_CODE, {
250
296
  session_id: 'evt-session',
251
297
  files: [
252
298
  {
253
- id: 'sk1',
299
+ id: 'skill-abc',
254
300
  name: 'demo/SKILL.md',
255
- session_id: 'evt-session',
256
- entity_id: 'skill-abc',
301
+ storage_session_id: 'evt-session',
302
+ kind: 'skill',
303
+ version: 3,
304
+ },
305
+ {
306
+ id: 'usr1',
307
+ name: 'data.csv',
308
+ storage_session_id: 'evt-session',
257
309
  },
258
- { id: 'usr1', name: 'data.csv', session_id: 'evt-session' },
259
310
  ],
260
311
  lastUpdated: Date.now(),
261
312
  } satisfies t.CodeSessionContext);
@@ -275,12 +326,18 @@ describe('ToolNode code execution session management', () => {
275
326
  session_id: 'evt-session',
276
327
  files: [
277
328
  {
278
- session_id: 'evt-session',
279
- id: 'sk1',
329
+ id: 'skill-abc',
280
330
  name: 'demo/SKILL.md',
281
- entity_id: 'skill-abc',
331
+ storage_session_id: 'evt-session',
332
+ kind: 'skill',
333
+ version: 3,
334
+ },
335
+ {
336
+ id: 'usr1',
337
+ name: 'data.csv',
338
+ storage_session_id: 'evt-session',
339
+ kind: 'user',
282
340
  },
283
- { session_id: 'evt-session', id: 'usr1', name: 'data.csv' },
284
341
  ],
285
342
  });
286
343
  });
@@ -333,7 +390,7 @@ describe('ToolNode code execution session management', () => {
333
390
  expect.objectContaining({
334
391
  id: 'f1',
335
392
  name: 'result.csv',
336
- session_id: 'new-sess',
393
+ storage_session_id: 'new-sess',
337
394
  })
338
395
  );
339
396
  });
@@ -384,8 +441,8 @@ describe('ToolNode code execution session management', () => {
384
441
  sessions.set(Constants.EXECUTE_CODE, {
385
442
  session_id: 'old-sess',
386
443
  files: [
387
- { id: 'f1', name: 'data.csv', session_id: 'old-sess' },
388
- { id: 'f2', name: 'chart.png', session_id: 'old-sess' },
444
+ { id: 'f1', name: 'data.csv', storage_session_id: 'old-sess' },
445
+ { id: 'f2', name: 'chart.png', storage_session_id: 'old-sess' },
389
446
  ],
390
447
  lastUpdated: Date.now(),
391
448
  } satisfies t.CodeSessionContext);
@@ -430,18 +487,18 @@ describe('ToolNode code execution session management', () => {
430
487
  expect(stored.files).toHaveLength(2);
431
488
 
432
489
  const csvFile = stored.files!.find((f) => f.name === 'data.csv');
433
- expect(csvFile!.session_id).toBe('old-sess');
490
+ expect(csvFile!.storage_session_id).toBe('old-sess');
434
491
 
435
492
  const chartFile = stored.files!.find((f) => f.name === 'chart.png');
436
493
  expect(chartFile!.id).toBe('f3');
437
- expect(chartFile!.session_id).toBe('new-sess');
494
+ expect(chartFile!.storage_session_id).toBe('new-sess');
438
495
  });
439
496
 
440
497
  it('preserves existing files when new execution has no files', () => {
441
498
  const sessions: t.ToolSessionMap = new Map();
442
499
  sessions.set(Constants.EXECUTE_CODE, {
443
500
  session_id: 'old-sess',
444
- files: [{ id: 'f1', name: 'data.csv', session_id: 'old-sess' }],
501
+ files: [{ id: 'f1', name: 'data.csv', storage_session_id: 'old-sess' }],
445
502
  lastUpdated: Date.now(),
446
503
  } satisfies t.CodeSessionContext);
447
504
 
@@ -507,7 +564,7 @@ describe('ToolNode code execution session management', () => {
507
564
  {
508
565
  toolCallId: 'tc5',
509
566
  content: 'search results',
510
- artifact: { session_id: 'should-not-store' },
567
+ artifact: { storage_session_id: 'should-not-store' },
511
568
  status: 'success',
512
569
  },
513
570
  ],
@@ -597,9 +654,13 @@ describe('ToolNode code execution session management', () => {
597
654
  {
598
655
  id: 'f1',
599
656
  name: 'sentinel.txt',
600
- session_id: 'storage-session-A',
657
+ storage_session_id: 'storage-session-A',
658
+ },
659
+ {
660
+ id: 'f2',
661
+ name: 'data.csv',
662
+ storage_session_id: 'storage-session-B',
601
663
  },
602
- { id: 'f2', name: 'data.csv', session_id: 'storage-session-B' },
603
664
  ],
604
665
  },
605
666
  status: 'success',
@@ -617,18 +678,20 @@ describe('ToolNode code execution session management', () => {
617
678
  Constants.EXECUTE_CODE
618
679
  ) as t.CodeSessionContext;
619
680
  /* The session-level id is the (latest) exec id — fine for tracking
620
- "what session ran last" — but per-file storage ids must survive. */
681
+ "what session ran last" — but per-file storage ids must survive.
682
+ After the rename, both names appear at the top level (exec) and
683
+ on each file (storage). */
621
684
  expect(stored.session_id).toBe('exec-session-123');
622
685
  expect(stored.files).toHaveLength(2);
623
686
  expect(stored.files![0]).toEqual({
624
687
  id: 'f1',
625
688
  name: 'sentinel.txt',
626
- session_id: 'storage-session-A',
689
+ storage_session_id: 'storage-session-A',
627
690
  });
628
691
  expect(stored.files![1]).toEqual({
629
692
  id: 'f2',
630
693
  name: 'data.csv',
631
- session_id: 'storage-session-B',
694
+ storage_session_id: 'storage-session-B',
632
695
  });
633
696
  });
634
697
 
@@ -658,7 +721,11 @@ describe('ToolNode code execution session management', () => {
658
721
  session_id: 'exec-mixed',
659
722
  files: [
660
723
  /* Mix: one file with storage id, one without (older payload). */
661
- { id: 'f1', name: 'fresh.csv', session_id: 'storage-fresh' },
724
+ {
725
+ id: 'f1',
726
+ name: 'fresh.csv',
727
+ storage_session_id: 'storage-fresh',
728
+ },
662
729
  { id: 'f2', name: 'legacy.csv' },
663
730
  ],
664
731
  },
@@ -676,9 +743,10 @@ describe('ToolNode code execution session management', () => {
676
743
  const stored = sessions.get(
677
744
  Constants.EXECUTE_CODE
678
745
  ) as t.CodeSessionContext;
679
- expect(stored.files![0].session_id).toBe('storage-fresh');
680
- /* Fallback only when the per-file id is missing. */
681
- expect(stored.files![1].session_id).toBe('exec-mixed');
746
+ expect(stored.files![0].storage_session_id).toBe('storage-fresh');
747
+ /* Fallback only when the per-file id is missing — the fallback
748
+ * value is the exec session id. */
749
+ expect(stored.files![1].storage_session_id).toBe('exec-mixed');
682
750
  });
683
751
  });
684
752
 
@@ -728,7 +796,13 @@ describe('ToolNode code execution session management', () => {
728
796
  const sessions: t.ToolSessionMap = new Map();
729
797
  sessions.set(Constants.EXECUTE_CODE, {
730
798
  session_id: 'rf-session',
731
- files: [{ id: 'rf1', name: 'data.csv', session_id: 'rf-session' }],
799
+ files: [
800
+ {
801
+ id: 'rf1',
802
+ name: 'data.csv',
803
+ storage_session_id: 'rf-session',
804
+ },
805
+ ],
732
806
  lastUpdated: Date.now(),
733
807
  } satisfies t.CodeSessionContext);
734
808
 
@@ -758,7 +832,14 @@ describe('ToolNode code execution session management', () => {
758
832
  expect(capturedRequests[0].name).toBe(Constants.READ_FILE);
759
833
  expect(capturedRequests[0].codeSessionContext).toEqual({
760
834
  session_id: 'rf-session',
761
- files: [{ session_id: 'rf-session', id: 'rf1', name: 'data.csv' }],
835
+ files: [
836
+ {
837
+ id: 'rf1',
838
+ name: 'data.csv',
839
+ storage_session_id: 'rf-session',
840
+ kind: 'user',
841
+ },
842
+ ],
762
843
  });
763
844
  });
764
845