@librechat/agents 3.1.83 → 3.1.85

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 (75) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +26 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +1 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/events.cjs +2 -1
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +5 -1
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -2
  10. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +4 -0
  12. package/dist/cjs/main.cjs.map +1 -1
  13. package/dist/cjs/tools/BashExecutor.cjs +5 -2
  14. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  15. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +26 -24
  16. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  17. package/dist/cjs/tools/CodeExecutor.cjs +28 -2
  18. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  19. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +130 -56
  20. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  21. package/dist/cjs/tools/ToolNode.cjs +7 -5
  22. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  23. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +52 -13
  24. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  25. package/dist/cjs/tools/ptcTimeout.cjs +56 -0
  26. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -0
  27. package/dist/esm/agents/AgentContext.mjs +27 -4
  28. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  29. package/dist/esm/common/enum.mjs +1 -0
  30. package/dist/esm/common/enum.mjs.map +1 -1
  31. package/dist/esm/events.mjs +2 -1
  32. package/dist/esm/events.mjs.map +1 -1
  33. package/dist/esm/graphs/Graph.mjs +5 -1
  34. package/dist/esm/graphs/Graph.mjs.map +1 -1
  35. package/dist/esm/graphs/MultiAgentGraph.mjs +3 -2
  36. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  37. package/dist/esm/main.mjs +3 -3
  38. package/dist/esm/tools/BashExecutor.mjs +6 -3
  39. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  40. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +26 -25
  41. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  42. package/dist/esm/tools/CodeExecutor.mjs +27 -3
  43. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  44. package/dist/esm/tools/ProgrammaticToolCalling.mjs +131 -58
  45. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  46. package/dist/esm/tools/ToolNode.mjs +7 -5
  47. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  48. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +54 -15
  49. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  50. package/dist/esm/tools/ptcTimeout.mjs +50 -0
  51. package/dist/esm/tools/ptcTimeout.mjs.map +1 -0
  52. package/dist/types/agents/AgentContext.d.ts +3 -1
  53. package/dist/types/common/enum.d.ts +2 -1
  54. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +4 -36
  55. package/dist/types/tools/CodeExecutor.d.ts +5 -0
  56. package/dist/types/tools/ProgrammaticToolCalling.d.ts +18 -39
  57. package/dist/types/tools/ptcTimeout.d.ts +25 -0
  58. package/dist/types/types/tools.d.ts +8 -0
  59. package/package.json +1 -1
  60. package/src/agents/AgentContext.ts +32 -3
  61. package/src/agents/__tests__/AgentContext.test.ts +36 -3
  62. package/src/common/enum.ts +1 -0
  63. package/src/events.ts +4 -1
  64. package/src/graphs/MultiAgentGraph.ts +3 -2
  65. package/src/graphs/__tests__/composition.smoke.test.ts +84 -2
  66. package/src/tools/BashExecutor.ts +14 -3
  67. package/src/tools/BashProgrammaticToolCalling.ts +37 -25
  68. package/src/tools/CodeExecutor.ts +36 -2
  69. package/src/tools/ProgrammaticToolCalling.ts +206 -53
  70. package/src/tools/ToolNode.ts +3 -4
  71. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +424 -0
  72. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +31 -1
  73. package/src/tools/local/LocalProgrammaticToolCalling.ts +94 -13
  74. package/src/tools/ptcTimeout.ts +89 -0
  75. package/src/types/tools.ts +12 -0
@@ -4,8 +4,19 @@ import fetch, { RequestInit } from 'node-fetch';
4
4
  import { HttpsProxyAgent } from 'https-proxy-agent';
5
5
  import { tool, DynamicStructuredTool } from '@langchain/core/tools';
6
6
  import type { ToolCall } from '@langchain/core/messages/tool';
7
+ import type { ProgrammaticToolCallingJsonSchema } from './ptcTimeout';
7
8
  import type * as t from '@/types';
8
- import { emptyOutputMessage, getCodeBaseURL } from './CodeExecutor';
9
+ import {
10
+ buildCodeApiHttpErrorMessage,
11
+ emptyOutputMessage,
12
+ getCodeBaseURL,
13
+ resolveCodeApiAuthHeaders,
14
+ } from './CodeExecutor';
15
+ import {
16
+ clampCodeApiRunTimeoutMs,
17
+ createCodeApiRunTimeoutSchema,
18
+ resolveCodeApiRunTimeoutMs,
19
+ } from './ptcTimeout';
9
20
  import { Constants } from '@/common';
10
21
 
11
22
  config();
@@ -13,8 +24,7 @@ config();
13
24
  /** Default max round-trips to prevent infinite loops */
14
25
  const DEFAULT_MAX_ROUND_TRIPS = 20;
15
26
 
16
- /** Default execution timeout in milliseconds */
17
- const DEFAULT_TIMEOUT = 60000;
27
+ const DEFAULT_RUN_TIMEOUT_MS = resolveCodeApiRunTimeoutMs();
18
28
 
19
29
  // ============================================================================
20
30
  // Description Components (Single Source of Truth)
@@ -30,7 +40,8 @@ const CORE_RULES = `Rules:
30
40
  - Just write code with await—auto-wrapped in async context
31
41
  - DO NOT define async def main() or call asyncio.run()
32
42
  - Tools are pre-defined—DO NOT write function definitions
33
- - Only print() output returns to the model`;
43
+ - Only print() output returns to the model
44
+ - timeout caps one sandbox run/replay iteration, not the total multi-round-trip workflow`;
34
45
 
35
46
  const ADDITIONAL_RULES = `- Generated files are automatically available in /mnt/data/ for subsequent executions
36
47
  - Tool names normalized: hyphens→underscores, keywords get \`_tool\` suffix`;
@@ -63,25 +74,25 @@ ${EXAMPLES}
63
74
 
64
75
  ${CORE_RULES}`;
65
76
 
66
- export const ProgrammaticToolCallingSchema = {
67
- type: 'object',
68
- properties: {
69
- code: {
70
- type: 'string',
71
- minLength: 1,
72
- description: CODE_PARAM_DESCRIPTION,
73
- },
74
- timeout: {
75
- type: 'integer',
76
- minimum: 1000,
77
- maximum: 300000,
78
- default: DEFAULT_TIMEOUT,
79
- description:
80
- 'Maximum execution time in milliseconds. Default: 60 seconds. Max: 5 minutes.',
77
+ export function createProgrammaticToolCallingSchema(
78
+ maxRunTimeoutMs = DEFAULT_RUN_TIMEOUT_MS
79
+ ): ProgrammaticToolCallingJsonSchema {
80
+ return {
81
+ type: 'object',
82
+ properties: {
83
+ code: {
84
+ type: 'string',
85
+ minLength: 1,
86
+ description: CODE_PARAM_DESCRIPTION,
87
+ },
88
+ timeout: createCodeApiRunTimeoutSchema(maxRunTimeoutMs),
81
89
  },
82
- },
83
- required: ['code'],
84
- } as const;
90
+ required: ['code'],
91
+ } as const;
92
+ }
93
+
94
+ export const ProgrammaticToolCallingSchema =
95
+ createProgrammaticToolCallingSchema();
85
96
 
86
97
  export const ProgrammaticToolCallingName = Constants.PROGRAMMATIC_TOOL_CALLING;
87
98
 
@@ -147,6 +158,113 @@ const PYTHON_KEYWORDS = new Set([
147
158
  'yield',
148
159
  ]);
149
160
 
161
+ export type FetchSessionFilesScope =
162
+ | { kind: 'skill'; id: string; version: number }
163
+ | { kind: 'agent' | 'user'; id: string; version?: never };
164
+
165
+ type CodeApiSessionFileWire = {
166
+ id?: unknown;
167
+ name?: unknown;
168
+ metadata?: unknown;
169
+ resource_id?: unknown;
170
+ storage_session_id?: unknown;
171
+ };
172
+
173
+ type CodeApiSessionFileMetadata = {
174
+ 'original-filename'?: unknown;
175
+ };
176
+
177
+ function isFetchSessionFilesScope(
178
+ value: unknown
179
+ ): value is FetchSessionFilesScope {
180
+ if (value == null || typeof value !== 'object') {
181
+ return false;
182
+ }
183
+ const scope = value as { kind?: unknown; id?: unknown; version?: unknown };
184
+ if (
185
+ (scope.kind === 'agent' || scope.kind === 'user') &&
186
+ typeof scope.id === 'string'
187
+ ) {
188
+ return true;
189
+ }
190
+ return (
191
+ scope.kind === 'skill' &&
192
+ typeof scope.id === 'string' &&
193
+ typeof scope.version === 'number'
194
+ );
195
+ }
196
+
197
+ function isCodeApiAuthHeaders(
198
+ value: string | t.CodeApiAuthHeaders | undefined
199
+ ): value is t.CodeApiAuthHeaders {
200
+ return value != null && typeof value !== 'string';
201
+ }
202
+
203
+ function isCodeApiSessionFileWire(
204
+ value: unknown
205
+ ): value is CodeApiSessionFileWire {
206
+ return value != null && typeof value === 'object';
207
+ }
208
+
209
+ function isCodeApiSessionFileMetadata(
210
+ value: unknown
211
+ ): value is CodeApiSessionFileMetadata {
212
+ return value != null && typeof value === 'object';
213
+ }
214
+
215
+ function normalizeSessionFile(
216
+ file: CodeApiSessionFileWire,
217
+ sessionId: string,
218
+ scope?: FetchSessionFilesScope
219
+ ): t.CodeEnvFile {
220
+ const metadata = isCodeApiSessionFileMetadata(file.metadata)
221
+ ? file.metadata
222
+ : undefined;
223
+ const rawName = typeof file.name === 'string' ? file.name : '';
224
+ const nameParts = rawName.split('/');
225
+ const fallbackId = nameParts.length > 1 ? nameParts[1].split('.')[0] : '';
226
+ const id =
227
+ typeof file.id === 'string' && file.id !== '' ? file.id : fallbackId;
228
+ const originalFilename = metadata?.['original-filename'];
229
+ const name =
230
+ typeof originalFilename === 'string' ? originalFilename : rawName;
231
+ const storage_session_id =
232
+ typeof file.storage_session_id === 'string'
233
+ ? file.storage_session_id
234
+ : sessionId;
235
+ const resource_id =
236
+ typeof file.resource_id === 'string' && file.resource_id !== ''
237
+ ? file.resource_id
238
+ : (scope?.id ?? id);
239
+
240
+ if (scope?.kind === 'skill') {
241
+ return {
242
+ storage_session_id,
243
+ kind: 'skill',
244
+ id,
245
+ resource_id,
246
+ name,
247
+ version: scope.version,
248
+ };
249
+ }
250
+ if (scope != null) {
251
+ return {
252
+ storage_session_id,
253
+ kind: scope.kind,
254
+ id,
255
+ resource_id,
256
+ name,
257
+ };
258
+ }
259
+ return {
260
+ storage_session_id,
261
+ kind: 'user',
262
+ id,
263
+ resource_id: id,
264
+ name,
265
+ };
266
+ }
267
+
150
268
  /**
151
269
  * Normalizes a tool name to Python identifier format.
152
270
  * Must match the Code API's `normalizePythonFunctionName` exactly:
@@ -250,20 +368,62 @@ export function filterToolsByUsage(
250
368
  * Files are returned as CodeEnvFile references to be included in the request.
251
369
  * @param baseUrl - The base URL for the Code API
252
370
  * @param sessionId - The session ID to fetch files from
371
+ * @param scope - Resource scope used by CodeAPI to authorize the session
253
372
  * @param proxy - Optional HTTP proxy URL
254
373
  * @returns Array of CodeEnvFile references, or empty array if fetch fails
255
374
  */
256
375
  export async function fetchSessionFiles(
257
376
  baseUrl: string,
258
377
  sessionId: string,
259
- proxy?: string
378
+ proxy?: string,
379
+ authHeaders?: t.CodeApiAuthHeaders
380
+ ): Promise<t.CodeEnvFile[]>;
381
+ export async function fetchSessionFiles(
382
+ baseUrl: string,
383
+ sessionId: string,
384
+ scope: FetchSessionFilesScope,
385
+ proxyOrAuthHeaders?: string | t.CodeApiAuthHeaders,
386
+ authHeaders?: t.CodeApiAuthHeaders
387
+ ): Promise<t.CodeEnvFile[]>;
388
+ export async function fetchSessionFiles(
389
+ baseUrl: string,
390
+ sessionId: string,
391
+ scopeOrProxy?: FetchSessionFilesScope | string,
392
+ proxyOrAuthHeaders?: string | t.CodeApiAuthHeaders,
393
+ scopedAuthHeaders?: t.CodeApiAuthHeaders
260
394
  ): Promise<t.CodeEnvFile[]> {
261
395
  try {
262
- const filesEndpoint = `${baseUrl}/files/${sessionId}?detail=full`;
396
+ const scope = isFetchSessionFilesScope(scopeOrProxy)
397
+ ? scopeOrProxy
398
+ : undefined;
399
+ let proxy: string | undefined;
400
+ let authHeaders: t.CodeApiAuthHeaders | undefined;
401
+ if (scope == null) {
402
+ proxy = typeof scopeOrProxy === 'string' ? scopeOrProxy : undefined;
403
+ authHeaders = isCodeApiAuthHeaders(proxyOrAuthHeaders)
404
+ ? proxyOrAuthHeaders
405
+ : undefined;
406
+ } else if (typeof proxyOrAuthHeaders === 'string') {
407
+ proxy = proxyOrAuthHeaders;
408
+ authHeaders = scopedAuthHeaders;
409
+ } else {
410
+ authHeaders = proxyOrAuthHeaders ?? scopedAuthHeaders;
411
+ }
412
+ const query = new URLSearchParams({ detail: 'full' });
413
+ if (scope != null) {
414
+ query.set('kind', scope.kind);
415
+ query.set('id', scope.id);
416
+ if (scope.kind === 'skill') {
417
+ query.set('version', String(scope.version));
418
+ }
419
+ }
420
+ const filesEndpoint = `${baseUrl}/files/${encodeURIComponent(sessionId)}?${query.toString()}`;
421
+ const resolvedAuthHeaders = await resolveCodeApiAuthHeaders(authHeaders);
263
422
  const fetchOptions: RequestInit = {
264
423
  method: 'GET',
265
424
  headers: {
266
425
  'User-Agent': 'LibreChat/1.0',
426
+ ...resolvedAuthHeaders,
267
427
  },
268
428
  };
269
429
 
@@ -273,7 +433,9 @@ export async function fetchSessionFiles(
273
433
 
274
434
  const response = await fetch(filesEndpoint, fetchOptions);
275
435
  if (!response.ok) {
276
- throw new Error(`Failed to fetch files for session: ${response.status}`);
436
+ throw new Error(
437
+ await buildCodeApiHttpErrorMessage('GET', filesEndpoint, response)
438
+ );
277
439
  }
278
440
 
279
441
  const files = await response.json();
@@ -281,25 +443,9 @@ export async function fetchSessionFiles(
281
443
  return [];
282
444
  }
283
445
 
284
- return files.map((file: Record<string, unknown>) => {
285
- // Extract the ID from the file name (part after session ID prefix and before extension)
286
- const nameParts = (file.name as string).split('/');
287
- const id = nameParts.length > 1 ? nameParts[1].split('.')[0] : '';
288
-
289
- return {
290
- storage_session_id: sessionId,
291
- /* `/files` fallback returns code-output files belonging to
292
- * the user; tag them user-private. */
293
- kind: 'user' as const,
294
- id,
295
- /* `resource_id` informational for `kind: 'user'` —
296
- * codeapi derives sessionKey from auth context. */
297
- resource_id: id,
298
- name: (file.metadata as Record<string, unknown>)[
299
- 'original-filename'
300
- ] as string,
301
- };
302
- });
446
+ return files
447
+ .filter(isCodeApiSessionFileWire)
448
+ .map((file) => normalizeSessionFile(file, sessionId, scope));
303
449
  } catch (error) {
304
450
  // eslint-disable-next-line no-console
305
451
  console.warn(
@@ -319,13 +465,16 @@ export async function fetchSessionFiles(
319
465
  export async function makeRequest(
320
466
  endpoint: string,
321
467
  body: Record<string, unknown>,
322
- proxy?: string
468
+ proxy?: string,
469
+ authHeaders?: t.CodeApiAuthHeaders
323
470
  ): Promise<t.ProgrammaticExecutionResponse> {
471
+ const resolvedAuthHeaders = await resolveCodeApiAuthHeaders(authHeaders);
324
472
  const fetchOptions: RequestInit = {
325
473
  method: 'POST',
326
474
  headers: {
327
475
  'Content-Type': 'application/json',
328
476
  'User-Agent': 'LibreChat/1.0',
477
+ ...resolvedAuthHeaders,
329
478
  },
330
479
  body: JSON.stringify(body),
331
480
  };
@@ -337,9 +486,8 @@ export async function makeRequest(
337
486
  const response = await fetch(endpoint, fetchOptions);
338
487
 
339
488
  if (!response.ok) {
340
- const errorText = await response.text();
341
489
  throw new Error(
342
- `HTTP error! status: ${response.status}, body: ${errorText}`
490
+ await buildCodeApiHttpErrorMessage('POST', endpoint, response)
343
491
  );
344
492
  }
345
493
 
@@ -486,7 +634,8 @@ export function unwrapToolResponse(
486
634
  */
487
635
  export async function executeTools(
488
636
  toolCalls: t.PTCToolCall[],
489
- toolMap: t.ToolMap
637
+ toolMap: t.ToolMap,
638
+ programmaticToolName = Constants.PROGRAMMATIC_TOOL_CALLING
490
639
  ): Promise<t.PTCToolResult[]> {
491
640
  const executions = toolCalls.map(async (call): Promise<t.PTCToolResult> => {
492
641
  const tool = toolMap.get(call.name);
@@ -502,7 +651,7 @@ export async function executeTools(
502
651
 
503
652
  try {
504
653
  const result = await tool.invoke(call.input, {
505
- metadata: { [Constants.PROGRAMMATIC_TOOL_CALLING]: true },
654
+ metadata: { [programmaticToolName]: true },
506
655
  });
507
656
 
508
657
  const isMCPTool = tool.mcp === true;
@@ -588,6 +737,7 @@ export function createProgrammaticToolCallingTool(
588
737
  ): DynamicStructuredTool {
589
738
  const baseUrl = initParams.baseUrl ?? getCodeBaseURL();
590
739
  const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;
740
+ const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);
591
741
  const proxy = initParams.proxy ?? process.env.PROXY;
592
742
  const debug = initParams.debug ?? process.env.PTC_DEBUG === 'true';
593
743
  const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;
@@ -595,7 +745,8 @@ export function createProgrammaticToolCallingTool(
595
745
  return tool(
596
746
  async (rawParams, config) => {
597
747
  const params = rawParams as { code: string; timeout?: number };
598
- const { code, timeout = DEFAULT_TIMEOUT } = params;
748
+ const { code } = params;
749
+ const timeout = clampCodeApiRunTimeoutMs(params.timeout, maxRunTimeoutMs);
599
750
 
600
751
  // Extra params injected by ToolNode (follows web_search pattern).
601
752
  const toolCall = (config.toolCall ?? {}) as ToolCall &
@@ -661,7 +812,8 @@ export function createProgrammaticToolCallingTool(
661
812
  timeout,
662
813
  ...(files && files.length > 0 ? { files } : {}),
663
814
  },
664
- proxy
815
+ proxy,
816
+ initParams.authHeaders
665
817
  );
666
818
 
667
819
  // ====================================================================
@@ -697,7 +849,8 @@ export function createProgrammaticToolCallingTool(
697
849
  continuation_token: response.continuation_token,
698
850
  tool_results: toolResults,
699
851
  },
700
- proxy
852
+ proxy,
853
+ initParams.authHeaders
701
854
  );
702
855
  }
703
856
 
@@ -728,7 +881,7 @@ export function createProgrammaticToolCallingTool(
728
881
  {
729
882
  name: Constants.PROGRAMMATIC_TOOL_CALLING,
730
883
  description: ProgrammaticToolCallingDescription,
731
- schema: ProgrammaticToolCallingSchema,
884
+ schema: createProgrammaticToolCallingSchema(maxRunTimeoutMs),
732
885
  responseFormat: Constants.CONTENT_AND_ARTIFACT,
733
886
  }
734
887
  );
@@ -844,10 +844,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
844
844
  // Plumb the hook context into the programmatic-tool path so
845
845
  // inner tool calls made via the in-process bridge can run
846
846
  // through `PreToolUse` (deny / updatedInput) before reaching
847
- // the underlying tool. Without this, `run_tools_with_code`
848
- // bypassed every PreToolUse hook the host registered for
849
- // the tools it dispatches — including HITL gates on
850
- // `write_file` / `edit_file` (manual review finding A).
847
+ // the underlying tool. Without this, programmatic tool calls
848
+ // bypass every PreToolUse hook the host registered for the tools
849
+ // they dispatch — including HITL gates on `write_file` / `edit_file`.
851
850
  hookContext: {
852
851
  registry: this.hookRegistry,
853
852
  runId: (config.configurable?.run_id as string | undefined) ?? '',