@librechat/agents 3.1.89 → 3.1.90

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 (77) hide show
  1. package/dist/cjs/graphs/Graph.cjs +7 -0
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/executeHooks.cjs +14 -7
  4. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/index.cjs +8 -2
  6. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  7. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +34 -0
  8. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  9. package/dist/cjs/main.cjs +9 -0
  10. package/dist/cjs/main.cjs.map +1 -1
  11. package/dist/cjs/tools/BashExecutor.cjs +10 -9
  12. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  13. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  15. package/dist/cjs/tools/CodeExecutor.cjs +35 -11
  16. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  17. package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
  18. package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
  19. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
  20. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  21. package/dist/cjs/tools/ToolNode.cjs +8 -5
  22. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  23. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
  24. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  25. package/dist/esm/graphs/Graph.mjs +7 -0
  26. package/dist/esm/graphs/Graph.mjs.map +1 -1
  27. package/dist/esm/hooks/executeHooks.mjs +14 -7
  28. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  29. package/dist/esm/llm/anthropic/index.mjs +9 -3
  30. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  31. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
  32. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  33. package/dist/esm/main.mjs +2 -1
  34. package/dist/esm/main.mjs.map +1 -1
  35. package/dist/esm/tools/BashExecutor.mjs +11 -10
  36. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  37. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
  38. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  39. package/dist/esm/tools/CodeExecutor.mjs +29 -12
  40. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  41. package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
  42. package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
  43. package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
  44. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  45. package/dist/esm/tools/ToolNode.mjs +8 -5
  46. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  47. package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
  48. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  49. package/dist/types/llm/anthropic/index.d.ts +3 -1
  50. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
  51. package/dist/types/tools/BashExecutor.d.ts +3 -3
  52. package/dist/types/tools/CodeExecutor.d.ts +10 -3
  53. package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
  54. package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
  55. package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
  56. package/dist/types/types/tools.d.ts +2 -3
  57. package/package.json +1 -1
  58. package/src/graphs/Graph.ts +7 -0
  59. package/src/hooks/__tests__/executeHooks.test.ts +38 -0
  60. package/src/hooks/executeHooks.ts +27 -7
  61. package/src/llm/anthropic/index.ts +27 -3
  62. package/src/llm/anthropic/llm.spec.ts +60 -1
  63. package/src/llm/anthropic/utils/message_inputs.ts +46 -0
  64. package/src/tools/BashExecutor.ts +21 -10
  65. package/src/tools/BashProgrammaticToolCalling.ts +21 -9
  66. package/src/tools/CodeExecutor.ts +55 -12
  67. package/src/tools/CodeSessionFileSummary.ts +80 -0
  68. package/src/tools/ProgrammaticToolCalling.ts +25 -12
  69. package/src/tools/ToolNode.ts +8 -5
  70. package/src/tools/__tests__/BashExecutor.test.ts +9 -0
  71. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
  72. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
  73. package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
  74. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
  75. package/src/tools/__tests__/subagentHooks.test.ts +237 -0
  76. package/src/tools/subagent/SubagentExecutor.ts +514 -36
  77. package/src/types/tools.ts +2 -3
@@ -46,6 +46,7 @@ import {
46
46
  buildReferenceKey,
47
47
  ToolOutputReferenceRegistry,
48
48
  } from '@/tools/toolOutputReferences';
49
+ import { stripCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
49
50
  import {
50
51
  resolveLocalToolRegistry,
51
52
  resolveLocalExecutionTools,
@@ -911,8 +912,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
911
912
  * Both session_id and _injected_files are injected directly to invokeParams
912
913
  * (not inside args) so they bypass Zod schema validation and reach config.toolCall.
913
914
  *
914
- * session_id is always injected when available (even without tracked files)
915
- * so the CodeExecutor can fall back to the /files endpoint for session continuity.
915
+ * session_id is always injected when available, but concrete file refs
916
+ * still need to travel through `_injected_files`; the legacy
917
+ * `/files/<session_id>` fallback was removed from the executors.
916
918
  */
917
919
  if (CODE_EXECUTION_TOOLS.has(call.name)) {
918
920
  const codeSession = this.sessions?.get(Constants.EXECUTE_CODE) as
@@ -959,6 +961,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
959
961
  if (this.toolOutputRegistry != null || unresolvedRefs.length > 0) {
960
962
  if (typeof toolMsg.content === 'string') {
961
963
  const rawContent = toolMsg.content;
964
+ const registryContent = stripCodeSessionFileSummary(rawContent);
962
965
  const llmContent = truncateToolResultContent(
963
966
  rawContent,
964
967
  this.maxToolResultChars
@@ -966,7 +969,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
966
969
  toolMsg.content = llmContent;
967
970
  const refMeta = this.recordOutputReference(
968
971
  runId,
969
- rawContent,
972
+ registryContent,
970
973
  refKey,
971
974
  unresolvedRefs
972
975
  );
@@ -1015,7 +1018,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1015
1018
  );
1016
1019
  const refMeta = this.recordOutputReference(
1017
1020
  runId,
1018
- rawContent,
1021
+ stripCodeSessionFileSummary(rawContent),
1019
1022
  refKey,
1020
1023
  unresolvedRefs
1021
1024
  );
@@ -2661,7 +2664,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2661
2664
  : undefined;
2662
2665
  const successRefMeta = this.recordOutputReference(
2663
2666
  registryRunId,
2664
- registryRaw,
2667
+ stripCodeSessionFileSummary(registryRaw),
2665
2668
  refKey,
2666
2669
  unresolved
2667
2670
  );
@@ -18,6 +18,15 @@ describe('buildBashExecutionToolDescription', () => {
18
18
  ).toBe(BashExecutionToolDescription);
19
19
  });
20
20
 
21
+ it('warns about compact bash shell pitfalls', () => {
22
+ expect(BashExecutionToolDescription).toContain('heredoc/printf');
23
+ expect(BashExecutionToolDescription).toContain('not bare Python');
24
+ expect(BashExecutionToolDescription).toContain(
25
+ 'failed executions do not register new files'
26
+ );
27
+ expect(BashExecutionToolDescription).toContain('not later-call storage');
28
+ });
29
+
21
30
  it('appends the tool-output references guide when enabled', () => {
22
31
  const composed = buildBashExecutionToolDescription({
23
32
  enableToolOutputReferences: true,
@@ -165,6 +165,17 @@ describe('CodeAPI auth header injection', () => {
165
165
  ).not.toHaveProperty('authHeaders');
166
166
  });
167
167
 
168
+ it('tolerates null params for direct code execution', async () => {
169
+ fetchMock.mockResolvedValueOnce(
170
+ jsonResponse({ session_id: 'session_123', stdout: '1\n' })
171
+ );
172
+ const tool = createCodeExecutionTool(null);
173
+
174
+ await expect(
175
+ tool.invoke({ lang: 'py', code: 'print(1)' })
176
+ ).resolves.toBeDefined();
177
+ });
178
+
168
179
  it('forwards Authorization for bash execution', async () => {
169
180
  fetchMock.mockResolvedValueOnce(
170
181
  jsonResponse({ session_id: 'session_123', stdout: '1\n' })
@@ -333,6 +344,38 @@ describe('CodeAPI auth header injection', () => {
333
344
  );
334
345
  });
335
346
 
347
+ it('reminds that failed bash programmatic executions do not register new files', async () => {
348
+ fetchMock.mockResolvedValueOnce(
349
+ jsonResponse({
350
+ status: 'error',
351
+ error: 'jq failed',
352
+ stderr: 'jq: Cannot index string with string "name"',
353
+ })
354
+ );
355
+ const tool = createBashProgrammaticToolCallingTool();
356
+
357
+ await expect(
358
+ tool.invoke(
359
+ {
360
+ code: [
361
+ 'lookup_user "{}" > /mnt/data/user.json',
362
+ 'jq -r \'.result.name\' /mnt/data/user.json',
363
+ ].join('\n'),
364
+ },
365
+ {
366
+ toolCall: {
367
+ name: 'bash_programmatic_code_execution',
368
+ args: {},
369
+ toolMap: toolMap(),
370
+ toolDefs,
371
+ },
372
+ }
373
+ )
374
+ ).rejects.toThrow(
375
+ 'files written during this failed call were not registered for later calls'
376
+ );
377
+ });
378
+
336
379
  it('fetches session files with the CodeAPI resource scope and auth headers', async () => {
337
380
  fetchMock.mockResolvedValueOnce(
338
381
  jsonResponse([
@@ -8,6 +8,7 @@ import type * as t from '@/types';
8
8
  import { Constants } from '@/common';
9
9
  import {
10
10
  createProgrammaticToolCallingTool,
11
+ createProgrammaticToolCallingSchema,
11
12
  formatCompletedResponse,
12
13
  extractUsedToolNames,
13
14
  filterToolsByUsage,
@@ -15,6 +16,7 @@ import {
15
16
  normalizeToPythonIdentifier,
16
17
  unwrapToolResponse,
17
18
  } from '../ProgrammaticToolCalling';
19
+ import { createBashProgrammaticToolCallingSchema } from '../BashProgrammaticToolCalling';
18
20
  import {
19
21
  createProgrammaticToolRegistry,
20
22
  createGetTeamMembersTool,
@@ -24,6 +26,33 @@ import {
24
26
  } from '@/test/mockTools';
25
27
 
26
28
  describe('ProgrammaticToolCalling', () => {
29
+ describe('tool descriptions', () => {
30
+ it('explains Python inner-tool call and result shape', () => {
31
+ const schema = createProgrammaticToolCallingSchema();
32
+ const description = schema.properties.code.description;
33
+
34
+ expect(description).toContain('keyword args only');
35
+ expect(description).toContain('never pass a dict');
36
+ expect(description).toContain('Tool results are decoded Python values');
37
+ });
38
+
39
+ it('explains bash inner-tool stdout shape', () => {
40
+ const schema = createBashProgrammaticToolCallingSchema();
41
+ const description = schema.properties.code.description;
42
+
43
+ expect(description).toContain('jq: use fromjson? // .');
44
+ expect(description).toContain('again on JSON-string fields');
45
+ expect(description).toContain('arrays may contain strings');
46
+ expect(description).toContain('raw=$(tool');
47
+ expect(description).toContain('direct tool > file may be empty');
48
+ expect(description).toContain('/mnt/data/sf.json');
49
+ expect(description).toContain(
50
+ 'failed executions do not register new files'
51
+ );
52
+ expect(description).toContain('not later-call storage');
53
+ });
54
+ });
55
+
27
56
  describe('executeTools', () => {
28
57
  let toolMap: t.ToolMap;
29
58
 
@@ -656,13 +685,26 @@ for member in team:
656
685
  expect(output).toContain('stderr:\nWarning: deprecated function');
657
686
  });
658
687
 
659
- it('preserves files on the artifact but omits them from the LLM-facing output', () => {
660
- /* The post-execution file summary was removed because it
661
- * misled the model more than it helped — especially with
662
- * bash, where models naturally `ls /mnt/data/` to discover
663
- * available files. The artifact still carries every file
664
- * so the host's session-tracking layer stays in sync;
665
- * the LLM just doesn't see the prescriptive listing. */
688
+ it('adds a /tmp scratch reminder when source code used /tmp', () => {
689
+ const response: t.ProgrammaticExecutionResponse = {
690
+ status: 'completed',
691
+ stdout: 'done\n',
692
+ stderr: '',
693
+ files: [],
694
+ session_id: 'sess_abc123',
695
+ };
696
+
697
+ const [output] = formatCompletedResponse(
698
+ response,
699
+ 'tool "{}" > /tmp/result.json'
700
+ );
701
+
702
+ expect(output).toContain('stdout:\ndone');
703
+ expect(output).toContain('/tmp files are same-call scratch only');
704
+ expect(output).toContain('use /mnt/data for files needed later');
705
+ });
706
+
707
+ it('preserves files on the artifact and summarizes them without listing paths', () => {
666
708
  const response: t.ProgrammaticExecutionResponse = {
667
709
  status: 'completed',
668
710
  stdout: 'Generated report\n',
@@ -677,9 +719,12 @@ for member in team:
677
719
 
678
720
  const [output, artifact] = formatCompletedResponse(response);
679
721
 
680
- /* Tool result text is stdout/stderr only. */
681
722
  expect(output).toContain('stdout:\nGenerated report');
682
- expect(output).not.toContain('Generated files:');
723
+ expect(output).toContain('Generated files:');
724
+ expect(output).toContain(
725
+ 'Session files: 2 persisted file(s) are available in /mnt/data, including 0 image(s).'
726
+ );
727
+ expect(output).toContain('do not invent download links');
683
728
  expect(output).not.toContain('Available files');
684
729
  expect(output).not.toContain('report.pdf');
685
730
  expect(output).not.toContain('SKILL.md');
@@ -691,6 +736,25 @@ for member in team:
691
736
  expect(artifact.files).toHaveLength(3);
692
737
  expect(artifact.files).toEqual(response.files);
693
738
  });
739
+
740
+ it('omits the generated-file summary for inherited-only files', () => {
741
+ const response: t.ProgrammaticExecutionResponse = {
742
+ status: 'completed',
743
+ stdout: 'No new files\n',
744
+ stderr: '',
745
+ files: [
746
+ { id: 'i1', name: 'skills/SKILL.md', inherited: true },
747
+ { id: 'i2', name: 'inputs/source.csv', inherited: true },
748
+ ],
749
+ session_id: 'sess_abc123',
750
+ };
751
+
752
+ const [output, artifact] = formatCompletedResponse(response);
753
+
754
+ expect(output).toBe('stdout:\nNo new files');
755
+ expect(output).not.toContain('Generated files:');
756
+ expect(artifact.files).toEqual(response.files);
757
+ });
694
758
  });
695
759
 
696
760
  describe('createProgrammaticToolCallingTool - Manual Invocation', () => {
@@ -889,11 +953,7 @@ for member in team:
889
953
  });
890
954
  });
891
955
 
892
- it('passes files through on the artifact, never on the LLM-facing output', () => {
893
- /* Output stays stdout/stderr-only regardless of file count or
894
- * filename shape. The artifact is the sole sink for file refs;
895
- * hosts thread them into `_injected_files` on subsequent
896
- * tool calls via `storeCodeSessionFromResults`. */
956
+ it('summarizes files in output while keeping exact refs on the artifact', () => {
897
957
  const response: t.ProgrammaticExecutionResponse = {
898
958
  status: 'completed',
899
959
  stdout: 'Report generated\n',
@@ -909,14 +969,38 @@ for member in team:
909
969
 
910
970
  const [output, artifact] = formatCompletedResponse(response);
911
971
 
912
- expect(output).toBe('stdout:\nReport generated');
972
+ expect(output).toContain('stdout:\nReport generated');
973
+ expect(output).toContain(
974
+ 'Session files: 4 persisted file(s) are available in /mnt/data, including 1 image(s).'
975
+ );
913
976
  expect(output).not.toContain('report.csv');
914
977
  expect(output).not.toContain('chart.png');
915
- expect(output).not.toContain('/mnt/data/');
916
978
 
917
979
  expect(artifact.files).toHaveLength(4);
918
980
  expect(artifact.files).toEqual(response.files);
919
981
  });
982
+
983
+ it('treats malformed file refs as non-image files', () => {
984
+ const malformedFile = { id: 'broken' } as t.FileRef;
985
+ const response: t.ProgrammaticExecutionResponse = {
986
+ status: 'completed',
987
+ stdout: 'Report generated\n',
988
+ stderr: '',
989
+ files: [
990
+ { id: '1', name: 'chart.png' },
991
+ malformedFile,
992
+ { id: '3', name: 'inherited.png', inherited: true },
993
+ ],
994
+ session_id: 'sess_xyz',
995
+ };
996
+
997
+ const [output, artifact] = formatCompletedResponse(response);
998
+
999
+ expect(output).toContain(
1000
+ 'Session files: 2 persisted file(s) are available in /mnt/data, including 1 image(s).'
1001
+ );
1002
+ expect(artifact.files).toEqual(response.files);
1003
+ });
920
1004
  });
921
1005
 
922
1006
  describe('Tool Data Extraction', () => {