@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
@@ -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.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
+ * `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
- sessionId: string,
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
- session_id: file.session_id ?? sessionId,
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: sessionId,
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: sessionId,
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({ toolRegistry, toolExecution });
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>(runIdArg: string | undefined, args: T): ResolveResult<T> =>
585
- registry.resolve(runIdArg, args);
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
- if (codeSession?.session_id != null && codeSession.session_id !== '') {
776
+ const execSessionId = codeSession?.session_id;
777
+ if (execSessionId != null && execSessionId !== '') {
721
778
  invokeParams = {
722
779
  ...invokeParams,
723
- session_id: codeSession.session_id,
780
+ session_id: execSessionId,
724
781
  };
725
782
 
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;
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
- hookRegistry?.hasHookFor('PreToolUse', runId) === true;
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: 'Approval payload `respond` was missing a string `responseText`',
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: codeSession.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
- 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
- }));
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
- if (artifact?.session_id == null || artifact.session_id === '') {
1558
+ const execSessionId = artifact?.session_id;
1559
+ if (execSessionId == null || execSessionId === '') {
1513
1560
  continue;
1514
1561
  }
1515
1562
 
1516
- updateCodeSession(this.sessions, artifact.session_id!, artifact.files);
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
- if (artifact?.session_id != null && artifact.session_id !== '') {
1562
- updateCodeSession(this.sessions, artifact.session_id, artifact.files);
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
- // 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