@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
package/src/tools/ToolNode.ts
CHANGED
|
@@ -249,22 +249,73 @@ 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.
|
|
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 `
|
|
257
|
-
* because `_injected_files` are looked up against the
|
|
258
|
-
* storage path on subsequent tool calls. Stomping the
|
|
259
|
-
* the exec id silently 404s every follow-up tool call
|
|
260
|
-
* run — `cat /mnt/data/foo.txt` reports "No such file
|
|
261
|
-
* because the worker can't mount a file at a path the
|
|
262
|
-
* know about. Fall back to
|
|
263
|
-
* absent (
|
|
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
|
+
* `resource_id` carries the entity-that-owns-this-file's-session
|
|
279
|
+
* identity (skill `_id` etc.); falls back to `id` (the storage
|
|
280
|
+
* file_id) for inputs that haven't been updated to send the field
|
|
281
|
+
* explicitly. The fallback degrades sessionKey resolution on the
|
|
282
|
+
* codeapi side for shared kinds (it'll match the storage nanoid
|
|
283
|
+
* against a skill _id and 403) — but won't crash, so an unmigrated
|
|
284
|
+
* client still produces a diagnosable error instead of a stack
|
|
285
|
+
* trace.
|
|
286
|
+
*/
|
|
287
|
+
function toInjectedFileRef(
|
|
288
|
+
file: {
|
|
289
|
+
id: string;
|
|
290
|
+
resource_id?: string;
|
|
291
|
+
name: string;
|
|
292
|
+
storage_session_id?: string;
|
|
293
|
+
kind?: t.CodeEnvKind;
|
|
294
|
+
version?: number;
|
|
295
|
+
},
|
|
296
|
+
execSessionId: string
|
|
297
|
+
): t.CodeEnvFile {
|
|
298
|
+
const base = {
|
|
299
|
+
id: file.id,
|
|
300
|
+
resource_id: file.resource_id ?? file.id,
|
|
301
|
+
name: file.name,
|
|
302
|
+
/* Inline `content` files have no persistent storage location;
|
|
303
|
+
* fall back to the execution session id for those entries. */
|
|
304
|
+
storage_session_id: file.storage_session_id ?? execSessionId,
|
|
305
|
+
};
|
|
306
|
+
const kind = file.kind ?? 'user';
|
|
307
|
+
if (kind === 'skill' && file.version != null) {
|
|
308
|
+
return { ...base, kind: 'skill', version: file.version };
|
|
309
|
+
}
|
|
310
|
+
if (kind === 'agent') {
|
|
311
|
+
return { ...base, kind: 'agent' };
|
|
312
|
+
}
|
|
313
|
+
return { ...base, kind: 'user' };
|
|
314
|
+
}
|
|
315
|
+
|
|
265
316
|
function updateCodeSession(
|
|
266
317
|
sessions: t.ToolSessionMap,
|
|
267
|
-
|
|
318
|
+
execSessionId: string,
|
|
268
319
|
files: t.FileRefs | undefined
|
|
269
320
|
): void {
|
|
270
321
|
const newFiles = files ?? [];
|
|
@@ -276,20 +327,20 @@ function updateCodeSession(
|
|
|
276
327
|
if (newFiles.length > 0) {
|
|
277
328
|
const filesWithSession: t.FileRefs = newFiles.map((file) => ({
|
|
278
329
|
...file,
|
|
279
|
-
|
|
330
|
+
storage_session_id: file.storage_session_id ?? execSessionId,
|
|
280
331
|
}));
|
|
281
332
|
const newFileNames = new Set(filesWithSession.map((f) => f.name));
|
|
282
333
|
const filteredExisting = existingFiles.filter(
|
|
283
334
|
(f) => !newFileNames.has(f.name)
|
|
284
335
|
);
|
|
285
336
|
sessions.set(Constants.EXECUTE_CODE, {
|
|
286
|
-
session_id:
|
|
337
|
+
session_id: execSessionId,
|
|
287
338
|
files: [...filteredExisting, ...filesWithSession],
|
|
288
339
|
lastUpdated: Date.now(),
|
|
289
340
|
});
|
|
290
341
|
} else {
|
|
291
342
|
sessions.set(Constants.EXECUTE_CODE, {
|
|
292
|
-
session_id:
|
|
343
|
+
session_id: execSessionId,
|
|
293
344
|
files: existingFiles,
|
|
294
345
|
lastUpdated: Date.now(),
|
|
295
346
|
});
|
|
@@ -400,7 +451,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
400
451
|
this.loadRuntimeTools = loadRuntimeTools;
|
|
401
452
|
this.errorHandler = errorHandler;
|
|
402
453
|
this.toolUsageCount = new Map<string, number>();
|
|
403
|
-
this.toolRegistry = resolveLocalToolRegistry({
|
|
454
|
+
this.toolRegistry = resolveLocalToolRegistry({
|
|
455
|
+
toolRegistry,
|
|
456
|
+
toolExecution,
|
|
457
|
+
});
|
|
404
458
|
this.sessions = sessions;
|
|
405
459
|
this.eventDrivenMode = eventDrivenMode ?? false;
|
|
406
460
|
this.agentId = agentId;
|
|
@@ -581,8 +635,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
581
635
|
resolveFn = <T>(_runId: string | undefined, args: T): ResolveResult<T> =>
|
|
582
636
|
preBatchSnapshot.resolve(args);
|
|
583
637
|
} else if (registry != null) {
|
|
584
|
-
resolveFn = <T>(
|
|
585
|
-
|
|
638
|
+
resolveFn = <T>(
|
|
639
|
+
runIdArg: string | undefined,
|
|
640
|
+
args: T
|
|
641
|
+
): ResolveResult<T> => registry.resolve(runIdArg, args);
|
|
586
642
|
}
|
|
587
643
|
/**
|
|
588
644
|
* Precompute the reference key once per call — captured locally
|
|
@@ -717,20 +773,17 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
717
773
|
const codeSession = this.sessions?.get(Constants.EXECUTE_CODE) as
|
|
718
774
|
| t.CodeSessionContext
|
|
719
775
|
| undefined;
|
|
720
|
-
|
|
776
|
+
const execSessionId = codeSession?.session_id;
|
|
777
|
+
if (execSessionId != null && execSessionId !== '') {
|
|
721
778
|
invokeParams = {
|
|
722
779
|
...invokeParams,
|
|
723
|
-
session_id:
|
|
780
|
+
session_id: execSessionId,
|
|
724
781
|
};
|
|
725
782
|
|
|
726
|
-
if (codeSession
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
name: file.name,
|
|
731
|
-
...(file.entity_id != null ? { entity_id: file.entity_id } : {}),
|
|
732
|
-
}));
|
|
733
|
-
invokeParams._injected_files = fileRefs;
|
|
783
|
+
if (codeSession?.files != null && codeSession.files.length > 0) {
|
|
784
|
+
invokeParams._injected_files = codeSession.files.map((file) =>
|
|
785
|
+
toInjectedFileRef(file, execSessionId)
|
|
786
|
+
);
|
|
734
787
|
}
|
|
735
788
|
}
|
|
736
789
|
}
|
|
@@ -950,10 +1003,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
950
1003
|
): Promise<BaseMessage | Command> {
|
|
951
1004
|
const runId = (config.configurable?.run_id as string | undefined) ?? '';
|
|
952
1005
|
const hookRegistry = this.hookRegistry;
|
|
953
|
-
const hasPreHook =
|
|
954
|
-
|
|
955
|
-
const hasPostHook =
|
|
956
|
-
hookRegistry?.hasHookFor('PostToolUse', runId) === true;
|
|
1006
|
+
const hasPreHook = hookRegistry?.hasHookFor('PreToolUse', runId) === true;
|
|
1007
|
+
const hasPostHook = hookRegistry?.hasHookFor('PostToolUse', runId) === true;
|
|
957
1008
|
const hasFailureHook =
|
|
958
1009
|
hookRegistry?.hasHookFor('PostToolUseFailure', runId) === true;
|
|
959
1010
|
|
|
@@ -989,8 +1040,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
989
1040
|
// assignment.)
|
|
990
1041
|
const cachedTurn =
|
|
991
1042
|
call.id != null && call.id !== ''
|
|
992
|
-
? this.directPathTurns.get(call.id) ??
|
|
993
|
-
this.toolCallTurns.get(call.id)
|
|
1043
|
+
? (this.directPathTurns.get(call.id) ?? this.toolCallTurns.get(call.id))
|
|
994
1044
|
: undefined;
|
|
995
1045
|
if (cachedTurn != null) {
|
|
996
1046
|
usageCount = cachedTurn;
|
|
@@ -1156,10 +1206,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1156
1206
|
return this.blockDirectCall({
|
|
1157
1207
|
call,
|
|
1158
1208
|
resolvedArgs,
|
|
1159
|
-
reason:
|
|
1160
|
-
decision.reason ??
|
|
1161
|
-
preResult.reason ??
|
|
1162
|
-
'Rejected by user',
|
|
1209
|
+
reason: decision.reason ?? preResult.reason ?? 'Rejected by user',
|
|
1163
1210
|
hookRegistry,
|
|
1164
1211
|
runId,
|
|
1165
1212
|
threadId,
|
|
@@ -1173,7 +1220,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1173
1220
|
return this.blockDirectCall({
|
|
1174
1221
|
call,
|
|
1175
1222
|
resolvedArgs,
|
|
1176
|
-
reason:
|
|
1223
|
+
reason:
|
|
1224
|
+
'Approval payload `respond` was missing a string `responseText`',
|
|
1177
1225
|
hookRegistry,
|
|
1178
1226
|
runId,
|
|
1179
1227
|
threadId,
|
|
@@ -1464,17 +1512,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1464
1512
|
return undefined;
|
|
1465
1513
|
}
|
|
1466
1514
|
|
|
1515
|
+
const execSessionId = codeSession.session_id;
|
|
1467
1516
|
const context: NonNullable<t.ToolCallRequest['codeSessionContext']> = {
|
|
1468
|
-
session_id:
|
|
1517
|
+
session_id: execSessionId,
|
|
1469
1518
|
};
|
|
1470
1519
|
|
|
1471
1520
|
if (codeSession.files && codeSession.files.length > 0) {
|
|
1472
|
-
context.files = codeSession.files.map((file) =>
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
name: file.name,
|
|
1476
|
-
...(file.entity_id != null ? { entity_id: file.entity_id } : {}),
|
|
1477
|
-
}));
|
|
1521
|
+
context.files = codeSession.files.map((file) =>
|
|
1522
|
+
toInjectedFileRef(file, execSessionId)
|
|
1523
|
+
);
|
|
1478
1524
|
}
|
|
1479
1525
|
|
|
1480
1526
|
return context;
|
|
@@ -1509,11 +1555,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1509
1555
|
}
|
|
1510
1556
|
|
|
1511
1557
|
const artifact = result.artifact as t.CodeExecutionArtifact | undefined;
|
|
1512
|
-
|
|
1558
|
+
const execSessionId = artifact?.session_id;
|
|
1559
|
+
if (execSessionId == null || execSessionId === '') {
|
|
1513
1560
|
continue;
|
|
1514
1561
|
}
|
|
1515
1562
|
|
|
1516
|
-
updateCodeSession(this.sessions,
|
|
1563
|
+
updateCodeSession(this.sessions, execSessionId, artifact?.files);
|
|
1517
1564
|
}
|
|
1518
1565
|
}
|
|
1519
1566
|
|
|
@@ -1558,8 +1605,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1558
1605
|
const artifact = toolMessage.artifact as
|
|
1559
1606
|
| t.CodeExecutionArtifact
|
|
1560
1607
|
| undefined;
|
|
1561
|
-
|
|
1562
|
-
|
|
1608
|
+
const execSessionId = artifact?.session_id;
|
|
1609
|
+
if (execSessionId != null && execSessionId !== '') {
|
|
1610
|
+
updateCodeSession(this.sessions, execSessionId, artifact?.files);
|
|
1563
1611
|
}
|
|
1564
1612
|
}
|
|
1565
1613
|
|
|
@@ -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
|
-
|
|
1068
|
-
const {
|
|
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
|
|