@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.
- package/dist/cjs/tools/BashExecutor.cjs +13 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +21 -5
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +12 -4
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +73 -40
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +13 -2
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +21 -5
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +12 -4
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +73 -40
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
- package/dist/types/types/tools.d.ts +106 -17
- package/package.json +1 -1
- package/src/scripts/code_exec_multi_session.ts +4 -4
- package/src/tools/BashExecutor.ts +14 -3
- package/src/tools/BashProgrammaticToolCalling.ts +6 -6
- package/src/tools/CodeExecutor.ts +22 -6
- package/src/tools/ProgrammaticToolCalling.ts +17 -10
- package/src/tools/ToolNode.ts +96 -48
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +9 -2
- package/src/tools/__tests__/ToolNode.session.test.ts +152 -50
- package/src/tools/local/LocalExecutionTools.ts +2 -2
- package/src/tools/local/LocalProgrammaticToolCalling.ts +23 -6
- 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
|
|
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
|
-
{
|
|
61
|
-
|
|
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
|
-
{
|
|
76
|
-
|
|
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
|
|
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
|
|
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
|
-
{
|
|
122
|
-
|
|
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].
|
|
135
|
-
expect(files[1].
|
|
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
|
|
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-
|
|
181
|
+
id: 'skill-123',
|
|
146
182
|
name: 'demo/SKILL.md',
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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: [
|
|
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: [
|
|
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({
|
|
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
|
|
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: '
|
|
314
|
+
id: 'skill-abc',
|
|
254
315
|
name: 'demo/SKILL.md',
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
388
|
-
{ id: 'f2', name: 'chart.png',
|
|
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!.
|
|
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!.
|
|
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',
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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].
|
|
680
|
-
/* Fallback only when the per-file id is missing
|
|
681
|
-
|
|
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: [
|
|
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: [
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
);
|
package/src/types/tools.ts
CHANGED
|
@@ -138,42 +138,114 @@ export type ToolEndEvent = {
|
|
|
138
138
|
type?: 'tool_call';
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
-
|
|
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
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
172
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
};
|