@librechat/agents 3.1.79 → 3.1.80-dev.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 (37) hide show
  1. package/dist/cjs/tools/BashExecutor.cjs +13 -2
  2. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  3. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -1
  4. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  5. package/dist/cjs/tools/CodeExecutor.cjs +21 -5
  6. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  7. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +12 -4
  8. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  9. package/dist/cjs/tools/ToolNode.cjs +73 -40
  10. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  11. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
  12. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  13. package/dist/esm/tools/BashExecutor.mjs +13 -2
  14. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  15. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -1
  16. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  17. package/dist/esm/tools/CodeExecutor.mjs +21 -5
  18. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  19. package/dist/esm/tools/ProgrammaticToolCalling.mjs +12 -4
  20. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  21. package/dist/esm/tools/ToolNode.mjs +73 -40
  22. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  23. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
  24. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  25. package/dist/types/types/tools.d.ts +106 -17
  26. package/package.json +1 -1
  27. package/src/scripts/code_exec_multi_session.ts +4 -4
  28. package/src/tools/BashExecutor.ts +14 -3
  29. package/src/tools/BashProgrammaticToolCalling.ts +6 -6
  30. package/src/tools/CodeExecutor.ts +22 -6
  31. package/src/tools/ProgrammaticToolCalling.ts +17 -10
  32. package/src/tools/ToolNode.ts +96 -48
  33. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +9 -2
  34. package/src/tools/__tests__/ToolNode.session.test.ts +152 -50
  35. package/src/tools/local/LocalExecutionTools.ts +2 -2
  36. package/src/tools/local/LocalProgrammaticToolCalling.ts +23 -6
  37. package/src/types/tools.ts +103 -17
@@ -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,34 @@ 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
+ /* `resource_id` defaults to `id` (the storage uuid) when
88
+ * the input ref didn't supply it — informational for
89
+ * `kind: 'user'` since codeapi derives sessionKey from
90
+ * auth context. The real LC priming chain DOES supply
91
+ * resource_id explicitly; the default is a back-compat
92
+ * landing for inputs that haven't been updated. */
93
+ resource_id: 'file1',
94
+ name: 'data.csv',
95
+ storage_session_id: 'prev-session-abc',
96
+ kind: 'user',
97
+ },
98
+ {
99
+ id: 'file2',
100
+ resource_id: 'file2',
101
+ name: 'chart.png',
102
+ storage_session_id: 'prev-session-abc',
103
+ kind: 'user',
104
+ },
77
105
  ]);
78
106
  });
79
107
 
80
- it('injects session_id even when session has no tracked files', async () => {
108
+ it('injects session ids even when session has no tracked files', async () => {
81
109
  const capturedConfigs: Record<string, unknown>[] = [];
82
110
  const sessions: t.ToolSessionMap = new Map();
83
111
  sessions.set(Constants.EXECUTE_CODE, {
@@ -112,14 +140,22 @@ describe('ToolNode code execution session management', () => {
112
140
  expect(capturedConfigs[0]._injected_files).toBeUndefined();
113
141
  });
114
142
 
115
- it('preserves per-file session_id for multi-session files', async () => {
143
+ it('preserves per-file storage_session_id for multi-session files', async () => {
116
144
  const capturedConfigs: Record<string, unknown>[] = [];
117
145
  const sessions: t.ToolSessionMap = new Map();
118
146
  sessions.set(Constants.EXECUTE_CODE, {
119
147
  session_id: 'session-B',
120
148
  files: [
121
- { id: 'f1', name: 'old.csv', session_id: 'session-A' },
122
- { id: 'f2', name: 'new.png', session_id: 'session-B' },
149
+ {
150
+ id: 'f1',
151
+ name: 'old.csv',
152
+ storage_session_id: 'session-A',
153
+ },
154
+ {
155
+ id: 'f2',
156
+ name: 'new.png',
157
+ storage_session_id: 'session-B',
158
+ },
123
159
  ],
124
160
  lastUpdated: Date.now(),
125
161
  } satisfies t.CodeSessionContext);
@@ -131,26 +167,27 @@ describe('ToolNode code execution session management', () => {
131
167
  await toolNode.invoke({ messages: [aiMsg] });
132
168
 
133
169
  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');
170
+ expect(files[0].storage_session_id).toBe('session-A');
171
+ expect(files[1].storage_session_id).toBe('session-B');
136
172
  });
137
173
 
138
- it('forwards per-file entity_id for mixed-entity sessions', async () => {
174
+ it('forwards per-file kind and version for mixed-kind sessions', async () => {
139
175
  const capturedConfigs: Record<string, unknown>[] = [];
140
176
  const sessions: t.ToolSessionMap = new Map();
141
177
  sessions.set(Constants.EXECUTE_CODE, {
142
178
  session_id: 'session-A',
143
179
  files: [
144
180
  {
145
- id: 'skill-file',
181
+ id: 'skill-123',
146
182
  name: 'demo/SKILL.md',
147
- session_id: 'session-A',
148
- entity_id: 'skill-123',
183
+ storage_session_id: 'session-A',
184
+ kind: 'skill',
185
+ version: 7,
149
186
  },
150
187
  {
151
188
  id: 'user-file',
152
189
  name: 'attachment.csv',
153
- session_id: 'session-B',
190
+ storage_session_id: 'session-B',
154
191
  },
155
192
  ],
156
193
  lastUpdated: Date.now(),
@@ -165,15 +202,21 @@ describe('ToolNode code execution session management', () => {
165
202
  const files = capturedConfigs[0]._injected_files as t.CodeEnvFile[];
166
203
  expect(files).toEqual([
167
204
  {
168
- session_id: 'session-A',
169
- id: 'skill-file',
205
+ id: 'skill-123',
206
+ /* Default fallback when input doesn't supply `resource_id`.
207
+ * Production LC sets it explicitly to the skill's `_id`. */
208
+ resource_id: 'skill-123',
170
209
  name: 'demo/SKILL.md',
171
- entity_id: 'skill-123',
210
+ storage_session_id: 'session-A',
211
+ kind: 'skill',
212
+ version: 7,
172
213
  },
173
214
  {
174
- session_id: 'session-B',
175
215
  id: 'user-file',
216
+ resource_id: 'user-file',
176
217
  name: 'attachment.csv',
218
+ storage_session_id: 'session-B',
219
+ kind: 'user',
177
220
  },
178
221
  ]);
179
222
  });
@@ -184,7 +227,13 @@ describe('ToolNode code execution session management', () => {
184
227
  const sessions: t.ToolSessionMap = new Map();
185
228
  sessions.set(Constants.EXECUTE_CODE, {
186
229
  session_id: 'evt-session',
187
- files: [{ id: 'ef1', name: 'out.parquet', session_id: 'evt-session' }],
230
+ files: [
231
+ {
232
+ id: 'ef1',
233
+ name: 'out.parquet',
234
+ storage_session_id: 'evt-session',
235
+ },
236
+ ],
188
237
  lastUpdated: Date.now(),
189
238
  } satisfies t.CodeSessionContext);
190
239
 
@@ -201,7 +250,17 @@ describe('ToolNode code execution session management', () => {
201
250
 
202
251
  expect(context).toEqual({
203
252
  session_id: 'evt-session',
204
- files: [{ session_id: 'evt-session', id: 'ef1', name: 'out.parquet' }],
253
+ files: [
254
+ {
255
+ id: 'ef1',
256
+ /* `resource_id` defaults to `id` when input doesn't carry
257
+ * separate provenance — see toInjectedFileRef. */
258
+ resource_id: 'ef1',
259
+ name: 'out.parquet',
260
+ storage_session_id: 'evt-session',
261
+ kind: 'user',
262
+ },
263
+ ],
205
264
  });
206
265
  });
207
266
 
@@ -224,7 +283,9 @@ describe('ToolNode code execution session management', () => {
224
283
  toolNode as unknown as { getCodeSessionContext: () => unknown }
225
284
  ).getCodeSessionContext();
226
285
 
227
- expect(context).toEqual({ session_id: 'evt-session-empty' });
286
+ expect(context).toEqual({
287
+ session_id: 'evt-session-empty',
288
+ });
228
289
  });
229
290
 
230
291
  it('returns undefined when no session exists', () => {
@@ -244,18 +305,23 @@ describe('ToolNode code execution session management', () => {
244
305
  expect(context).toBeUndefined();
245
306
  });
246
307
 
247
- it('forwards per-file entity_id to event-driven request context', () => {
308
+ it('forwards per-file kind and version to event-driven request context', () => {
248
309
  const sessions: t.ToolSessionMap = new Map();
249
310
  sessions.set(Constants.EXECUTE_CODE, {
250
311
  session_id: 'evt-session',
251
312
  files: [
252
313
  {
253
- id: 'sk1',
314
+ id: 'skill-abc',
254
315
  name: 'demo/SKILL.md',
255
- session_id: 'evt-session',
256
- entity_id: 'skill-abc',
316
+ storage_session_id: 'evt-session',
317
+ kind: 'skill',
318
+ version: 3,
319
+ },
320
+ {
321
+ id: 'usr1',
322
+ name: 'data.csv',
323
+ storage_session_id: 'evt-session',
257
324
  },
258
- { id: 'usr1', name: 'data.csv', session_id: 'evt-session' },
259
325
  ],
260
326
  lastUpdated: Date.now(),
261
327
  } satisfies t.CodeSessionContext);
@@ -275,12 +341,21 @@ describe('ToolNode code execution session management', () => {
275
341
  session_id: 'evt-session',
276
342
  files: [
277
343
  {
278
- session_id: 'evt-session',
279
- id: 'sk1',
344
+ id: 'skill-abc',
345
+ /* Default fallback when input doesn't supply `resource_id`. */
346
+ resource_id: 'skill-abc',
280
347
  name: 'demo/SKILL.md',
281
- entity_id: 'skill-abc',
348
+ storage_session_id: 'evt-session',
349
+ kind: 'skill',
350
+ version: 3,
351
+ },
352
+ {
353
+ id: 'usr1',
354
+ resource_id: 'usr1',
355
+ name: 'data.csv',
356
+ storage_session_id: 'evt-session',
357
+ kind: 'user',
282
358
  },
283
- { session_id: 'evt-session', id: 'usr1', name: 'data.csv' },
284
359
  ],
285
360
  });
286
361
  });
@@ -333,7 +408,7 @@ describe('ToolNode code execution session management', () => {
333
408
  expect.objectContaining({
334
409
  id: 'f1',
335
410
  name: 'result.csv',
336
- session_id: 'new-sess',
411
+ storage_session_id: 'new-sess',
337
412
  })
338
413
  );
339
414
  });
@@ -384,8 +459,8 @@ describe('ToolNode code execution session management', () => {
384
459
  sessions.set(Constants.EXECUTE_CODE, {
385
460
  session_id: 'old-sess',
386
461
  files: [
387
- { id: 'f1', name: 'data.csv', session_id: 'old-sess' },
388
- { id: 'f2', name: 'chart.png', session_id: 'old-sess' },
462
+ { id: 'f1', name: 'data.csv', storage_session_id: 'old-sess' },
463
+ { id: 'f2', name: 'chart.png', storage_session_id: 'old-sess' },
389
464
  ],
390
465
  lastUpdated: Date.now(),
391
466
  } satisfies t.CodeSessionContext);
@@ -430,18 +505,18 @@ describe('ToolNode code execution session management', () => {
430
505
  expect(stored.files).toHaveLength(2);
431
506
 
432
507
  const csvFile = stored.files!.find((f) => f.name === 'data.csv');
433
- expect(csvFile!.session_id).toBe('old-sess');
508
+ expect(csvFile!.storage_session_id).toBe('old-sess');
434
509
 
435
510
  const chartFile = stored.files!.find((f) => f.name === 'chart.png');
436
511
  expect(chartFile!.id).toBe('f3');
437
- expect(chartFile!.session_id).toBe('new-sess');
512
+ expect(chartFile!.storage_session_id).toBe('new-sess');
438
513
  });
439
514
 
440
515
  it('preserves existing files when new execution has no files', () => {
441
516
  const sessions: t.ToolSessionMap = new Map();
442
517
  sessions.set(Constants.EXECUTE_CODE, {
443
518
  session_id: 'old-sess',
444
- files: [{ id: 'f1', name: 'data.csv', session_id: 'old-sess' }],
519
+ files: [{ id: 'f1', name: 'data.csv', storage_session_id: 'old-sess' }],
445
520
  lastUpdated: Date.now(),
446
521
  } satisfies t.CodeSessionContext);
447
522
 
@@ -507,7 +582,7 @@ describe('ToolNode code execution session management', () => {
507
582
  {
508
583
  toolCallId: 'tc5',
509
584
  content: 'search results',
510
- artifact: { session_id: 'should-not-store' },
585
+ artifact: { storage_session_id: 'should-not-store' },
511
586
  status: 'success',
512
587
  },
513
588
  ],
@@ -597,9 +672,13 @@ describe('ToolNode code execution session management', () => {
597
672
  {
598
673
  id: 'f1',
599
674
  name: 'sentinel.txt',
600
- session_id: 'storage-session-A',
675
+ storage_session_id: 'storage-session-A',
676
+ },
677
+ {
678
+ id: 'f2',
679
+ name: 'data.csv',
680
+ storage_session_id: 'storage-session-B',
601
681
  },
602
- { id: 'f2', name: 'data.csv', session_id: 'storage-session-B' },
603
682
  ],
604
683
  },
605
684
  status: 'success',
@@ -617,18 +696,20 @@ describe('ToolNode code execution session management', () => {
617
696
  Constants.EXECUTE_CODE
618
697
  ) as t.CodeSessionContext;
619
698
  /* The session-level id is the (latest) exec id — fine for tracking
620
- "what session ran last" — but per-file storage ids must survive. */
699
+ "what session ran last" — but per-file storage ids must survive.
700
+ After the rename, both names appear at the top level (exec) and
701
+ on each file (storage). */
621
702
  expect(stored.session_id).toBe('exec-session-123');
622
703
  expect(stored.files).toHaveLength(2);
623
704
  expect(stored.files![0]).toEqual({
624
705
  id: 'f1',
625
706
  name: 'sentinel.txt',
626
- session_id: 'storage-session-A',
707
+ storage_session_id: 'storage-session-A',
627
708
  });
628
709
  expect(stored.files![1]).toEqual({
629
710
  id: 'f2',
630
711
  name: 'data.csv',
631
- session_id: 'storage-session-B',
712
+ storage_session_id: 'storage-session-B',
632
713
  });
633
714
  });
634
715
 
@@ -658,7 +739,11 @@ describe('ToolNode code execution session management', () => {
658
739
  session_id: 'exec-mixed',
659
740
  files: [
660
741
  /* Mix: one file with storage id, one without (older payload). */
661
- { id: 'f1', name: 'fresh.csv', session_id: 'storage-fresh' },
742
+ {
743
+ id: 'f1',
744
+ name: 'fresh.csv',
745
+ storage_session_id: 'storage-fresh',
746
+ },
662
747
  { id: 'f2', name: 'legacy.csv' },
663
748
  ],
664
749
  },
@@ -676,9 +761,10 @@ describe('ToolNode code execution session management', () => {
676
761
  const stored = sessions.get(
677
762
  Constants.EXECUTE_CODE
678
763
  ) 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');
764
+ expect(stored.files![0].storage_session_id).toBe('storage-fresh');
765
+ /* Fallback only when the per-file id is missing — the fallback
766
+ * value is the exec session id. */
767
+ expect(stored.files![1].storage_session_id).toBe('exec-mixed');
682
768
  });
683
769
  });
684
770
 
@@ -728,7 +814,13 @@ describe('ToolNode code execution session management', () => {
728
814
  const sessions: t.ToolSessionMap = new Map();
729
815
  sessions.set(Constants.EXECUTE_CODE, {
730
816
  session_id: 'rf-session',
731
- files: [{ id: 'rf1', name: 'data.csv', session_id: 'rf-session' }],
817
+ files: [
818
+ {
819
+ id: 'rf1',
820
+ name: 'data.csv',
821
+ storage_session_id: 'rf-session',
822
+ },
823
+ ],
732
824
  lastUpdated: Date.now(),
733
825
  } satisfies t.CodeSessionContext);
734
826
 
@@ -758,7 +850,17 @@ describe('ToolNode code execution session management', () => {
758
850
  expect(capturedRequests[0].name).toBe(Constants.READ_FILE);
759
851
  expect(capturedRequests[0].codeSessionContext).toEqual({
760
852
  session_id: 'rf-session',
761
- files: [{ session_id: 'rf-session', id: 'rf1', name: 'data.csv' }],
853
+ files: [
854
+ {
855
+ id: 'rf1',
856
+ /* Default fallback for inputs that don't carry separate
857
+ * provenance — see toInjectedFileRef. */
858
+ resource_id: 'rf1',
859
+ name: 'data.csv',
860
+ storage_session_id: 'rf-session',
861
+ kind: 'user',
862
+ },
863
+ ],
762
864
  });
763
865
  });
764
866
 
@@ -115,7 +115,7 @@ export function createLocalCodeExecutionTool(
115
115
  {
116
116
  session_id: getLocalSessionId(config),
117
117
  files: [],
118
- },
118
+ } satisfies t.CodeExecutionArtifact,
119
119
  ];
120
120
  },
121
121
  {
@@ -151,7 +151,7 @@ export function createLocalBashExecutionTool(options?: {
151
151
  {
152
152
  session_id: getLocalSessionId(config),
153
153
  files: [],
154
- },
154
+ } satisfies t.CodeExecutionArtifact,
155
155
  ];
156
156
  },
157
157
  {
@@ -79,7 +79,9 @@ type LocalProgrammaticParams = {
79
79
 
80
80
  type ToolFilter = (toolDefs: t.LCTool[], code: string) => t.LCTool[];
81
81
 
82
- function resolveRuntime(params: LocalProgrammaticParams): LocalProgrammaticRuntime {
82
+ function resolveRuntime(
83
+ params: LocalProgrammaticParams
84
+ ): LocalProgrammaticRuntime {
83
85
  const rawRuntime = params.lang ?? params.runtime ?? params.language ?? 'bash';
84
86
  return rawRuntime === 'py' || rawRuntime === 'python' ? 'python' : 'bash';
85
87
  }
@@ -494,13 +496,17 @@ async function runLocalProgrammaticTool(args: {
494
496
  localConfig: t.LocalExecutionConfig;
495
497
  runtime: LocalProgrammaticRuntime;
496
498
  }): Promise<[string, t.ProgrammaticExecutionArtifact]> {
497
- const { toolMap, toolDefs, hookContext } = getProgrammaticContext(args.config);
499
+ const { toolMap, toolDefs, hookContext } = getProgrammaticContext(
500
+ args.config
501
+ );
498
502
 
499
503
  if (toolMap == null || toolMap.size === 0) {
500
504
  throw new Error('No toolMap provided for local programmatic execution.');
501
505
  }
502
506
  if (toolDefs == null || toolDefs.length === 0) {
503
- throw new Error('No tool definitions provided for local programmatic execution.');
507
+ throw new Error(
508
+ 'No tool definitions provided for local programmatic execution.'
509
+ );
504
510
  }
505
511
 
506
512
  const { effectiveTools, effectiveMap } = createEffectiveToolMap(
@@ -512,17 +518,28 @@ async function runLocalProgrammaticTool(args: {
512
518
  const bridge = await createToolBridge(effectiveMap, hookContext);
513
519
 
514
520
  try {
515
- const timeoutMs = args.params.timeout ?? args.localConfig.timeoutMs ?? DEFAULT_TIMEOUT;
521
+ const timeoutMs =
522
+ args.params.timeout ?? args.localConfig.timeoutMs ?? DEFAULT_TIMEOUT;
516
523
  const result =
517
524
  args.runtime === 'bash'
518
525
  ? await executeLocalBash(
519
- createBashProgram(args.params.code, effectiveTools, bridge.url, bridge.token),
526
+ createBashProgram(
527
+ args.params.code,
528
+ effectiveTools,
529
+ bridge.url,
530
+ bridge.token
531
+ ),
520
532
  { ...args.localConfig, timeoutMs }
521
533
  )
522
534
  : await executeLocalCode(
523
535
  {
524
536
  lang: 'py',
525
- code: createPythonProgram(args.params.code, effectiveTools, bridge.url, bridge.token),
537
+ code: createPythonProgram(
538
+ args.params.code,
539
+ effectiveTools,
540
+ bridge.url,
541
+ bridge.token
542
+ ),
526
543
  },
527
544
  { ...args.localConfig, timeoutMs }
528
545
  );
@@ -138,42 +138,114 @@ export type ToolEndEvent = {
138
138
  type?: 'tool_call';
139
139
  };
140
140
 
141
- export type CodeEnvFile = {
141
+ /**
142
+ * Closed set of resource kinds for sandbox file caching. Defined as a
143
+ * `as const` tuple so the runtime list and the TypeScript union can't
144
+ * drift on future additions — adding a new kind to the tuple updates
145
+ * both at once.
146
+ *
147
+ * - `skill`: shared per skill identity. Cross-user-within-tenant
148
+ * sharing. Codeapi sessionKey omits the user dimension. `version`
149
+ * is required (skill's monotonic counter scopes cache per revision).
150
+ * - `agent`: shared per agent identity. Same sharing semantic as
151
+ * skills.
152
+ * - `user`: user-private. Codeapi sessionKey is keyed by the
153
+ * requesting user from auth context. Used for chat attachments
154
+ * and code-output files.
155
+ */
156
+ export const CODE_ENV_KINDS = ['skill', 'agent', 'user'] as const;
157
+ export type CodeEnvKind = (typeof CODE_ENV_KINDS)[number];
158
+
159
+ type CodeEnvFileBase = {
160
+ /**
161
+ * **Storage file id** — the per-file uuid the file_server returns
162
+ * at upload time. Identifies the bytes at
163
+ * `<storage_session_id>/<id>` in the object bucket; used by the
164
+ * worker to fetch the file and by codeapi's auth layer for the
165
+ * upload-existence check.
166
+ *
167
+ * Distinct from `resource_id` below — that's the entity that owns
168
+ * this file's storage session (skill `_id`, agent id, etc.), used
169
+ * for sessionKey resolution. Conflating the two caused
170
+ * shared-kind authorization to fail because the sessionKey
171
+ * re-derivation used the storage nanoid where the resource id was
172
+ * required. See codeapi #1455 review.
173
+ */
142
174
  id: string;
175
+ /**
176
+ * **Resource id** — the entity that owns this file's storage
177
+ * session. Skill `_id` for `kind: 'skill'`, agent id for `'agent'`,
178
+ * informational only for `'user'` (codeapi resolves user identity
179
+ * from auth context). Kept on the type for shape uniformity across
180
+ * kinds.
181
+ *
182
+ * Drives codeapi's `resolveSessionKey` switch — the sessionKey
183
+ * embeds this value (`<tenant>:<kind>:<resource_id>[:v:<version>]`)
184
+ * so cross-user-within-tenant sharing for shared kinds is a
185
+ * designed property of the kind switch.
186
+ */
187
+ resource_id: string;
143
188
  name: string;
144
- session_id: string;
145
189
  /**
146
- * Identifier of the entity that owns this file's session (skill id,
147
- * agent id, etc). Forwarded to codeapi so it can resolve the
148
- * per-file sessionKey instead of falling back to a single
149
- * request-level entity. Required when a single execute request
150
- * references files uploaded under different entities (e.g. a skill
151
- * file plus a user attachment in the same call).
190
+ * Storage session the long-lived bucket where this file's bytes
191
+ * live in object storage. Distinct from the (transient) execution
192
+ * session id that appears at the top level of an execute response;
193
+ * the two used to share the field name `session_id` and the
194
+ * conflation caused real bugs. See codeapi #1455 / agents #148.
152
195
  */
153
- entity_id?: string;
196
+ storage_session_id: string;
154
197
  };
155
198
 
199
+ /**
200
+ * `CodeEnvFile` is a discriminated union on `kind`. `version` is
201
+ * statically required for `kind: 'skill'` and statically forbidden
202
+ * for `agent` / `user` — the constraint holds at compile time on
203
+ * every consumer, not just on codeapi's runtime validator.
204
+ *
205
+ * Codeapi switches on `kind` to derive the sessionKey for cache
206
+ * scoping (`<tenant>:<kind>:<id>[:v:<version>]`). Cross-user sharing
207
+ * for `kind: 'skill'` / `'agent'` is a designed property of the
208
+ * kind switch.
209
+ */
210
+ export type CodeEnvFile =
211
+ | (CodeEnvFileBase & { kind: 'skill'; version: number })
212
+ | (CodeEnvFileBase & { kind: 'agent' })
213
+ | (CodeEnvFileBase & { kind: 'user' });
214
+
156
215
  export type CodeExecutionToolParams =
157
216
  | undefined
158
217
  | {
218
+ /** Execution session — see `CodeSessionContext.session_id`. */
159
219
  session_id?: string;
160
220
  user_id?: string;
161
221
  files?: CodeEnvFile[];
162
222
  };
163
223
 
164
224
  export type FileRef = {
225
+ /**
226
+ * Storage file id (the per-file uuid). See `CodeEnvFile.id` for
227
+ * the full motivation behind the storage-vs-resource split.
228
+ */
165
229
  id: string;
230
+ /**
231
+ * Resource id — the entity that owns this file's storage session.
232
+ * Optional on `FileRef` (post-execute artifact refs may not carry
233
+ * resource provenance for outputs); required on `CodeEnvFile`
234
+ * (the input wire shape).
235
+ */
236
+ resource_id?: string;
166
237
  name: string;
167
238
  path?: string;
168
- /** Session ID this file belongs to (for multi-session file tracking) */
169
- session_id?: string;
170
239
  /**
171
- * Entity that owns this file's session (skill id, agent id, etc).
172
- * Carried on tracked session files so it can flow through to
173
- * `_injected_files` when a subsequent execute references a mix of
174
- * files uploaded under different entities.
240
+ * Storage session this file lives in. See `CodeEnvFile.storage_session_id`
241
+ * for the full motivation.
175
242
  */
176
- entity_id?: string;
243
+ storage_session_id?: string;
244
+ /** Resource kind — see `CodeEnvFile.kind`. */
245
+ kind?: CodeEnvKind;
246
+ /** Resource version — see `CodeEnvFile.version`. Only meaningful when
247
+ * `kind === 'skill'`. */
248
+ version?: number;
177
249
  /**
178
250
  * `true` when the codeapi sandbox echoed this entry as an unchanged
179
251
  * passthrough of an input the caller already owns (skill files,
@@ -188,6 +260,11 @@ export type FileRef = {
188
260
  export type FileRefs = FileRef[];
189
261
 
190
262
  export type ExecuteResult = {
263
+ /**
264
+ * Execution session id — the (transient) sandbox run that produced
265
+ * this output. Distinct from per-file `storage_session_id` on the
266
+ * files array.
267
+ */
191
268
  session_id: string;
192
269
  stdout: string;
193
270
  stderr: string;
@@ -254,6 +331,7 @@ export type ToolCallRequest = {
254
331
  turn?: number;
255
332
  /** Code execution session context for session continuity in event-driven mode */
256
333
  codeSessionContext?: {
334
+ /** Execution session — see `CodeSessionContext.session_id`. */
257
335
  session_id: string;
258
336
  files?: CodeEnvFile[];
259
337
  };
@@ -775,6 +853,7 @@ export type PTCToolResult = {
775
853
  */
776
854
  export type ProgrammaticExecutionResponse = {
777
855
  status: 'tool_call_required' | 'completed' | 'error' | unknown;
856
+ /** Execution session — see `CodeSessionContext.session_id`. */
778
857
  session_id?: string;
779
858
 
780
859
  /** Present when status='tool_call_required' */
@@ -794,6 +873,7 @@ export type ProgrammaticExecutionResponse = {
794
873
  * Artifact returned by the PTC tool
795
874
  */
796
875
  export type ProgrammaticExecutionArtifact = {
876
+ /** Execution session — see `CodeSessionContext.session_id`. */
797
877
  session_id?: string;
798
878
  files?: FileRefs;
799
879
  };
@@ -827,7 +907,12 @@ export type ProgrammaticToolCallingParams = {
827
907
  * Stored in Graph.sessions and injected into subsequent tool invocations.
828
908
  */
829
909
  export type CodeSessionContext = {
830
- /** Session ID from the code execution environment */
910
+ /**
911
+ * Execution session id — the (transient) sandbox run id. Used by
912
+ * ToolNode to thread session continuity into the next code-execution
913
+ * tool call. Distinct from per-file `storage_session_id` carried on
914
+ * `files`.
915
+ */
831
916
  session_id: string;
832
917
  /** Files generated in this session (for context/tracking) */
833
918
  files?: FileRefs;
@@ -840,6 +925,7 @@ export type CodeSessionContext = {
840
925
  * Used to extract session context after tool completion.
841
926
  */
842
927
  export type CodeExecutionArtifact = {
928
+ /** Execution session — see `CodeSessionContext.session_id`. */
843
929
  session_id?: string;
844
930
  files?: FileRefs;
845
931
  };