@librechat/agents 3.1.84 → 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 (50) hide show
  1. package/dist/cjs/common/enum.cjs +1 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +5 -1
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -2
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/main.cjs +2 -0
  8. package/dist/cjs/main.cjs.map +1 -1
  9. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +23 -21
  10. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  11. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +23 -22
  12. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  13. package/dist/cjs/tools/ToolNode.cjs +4 -1
  14. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  15. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +52 -13
  16. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  17. package/dist/cjs/tools/ptcTimeout.cjs +56 -0
  18. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -0
  19. package/dist/esm/common/enum.mjs +1 -0
  20. package/dist/esm/common/enum.mjs.map +1 -1
  21. package/dist/esm/graphs/Graph.mjs +5 -1
  22. package/dist/esm/graphs/Graph.mjs.map +1 -1
  23. package/dist/esm/graphs/MultiAgentGraph.mjs +3 -2
  24. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  25. package/dist/esm/main.mjs +2 -2
  26. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +23 -22
  27. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  28. package/dist/esm/tools/ProgrammaticToolCalling.mjs +23 -23
  29. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  30. package/dist/esm/tools/ToolNode.mjs +4 -1
  31. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  32. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +54 -15
  33. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  34. package/dist/esm/tools/ptcTimeout.mjs +50 -0
  35. package/dist/esm/tools/ptcTimeout.mjs.map +1 -0
  36. package/dist/types/common/enum.d.ts +2 -1
  37. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +4 -36
  38. package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -36
  39. package/dist/types/tools/ptcTimeout.d.ts +25 -0
  40. package/dist/types/types/tools.d.ts +2 -0
  41. package/package.json +1 -1
  42. package/src/common/enum.ts +1 -0
  43. package/src/graphs/MultiAgentGraph.ts +3 -2
  44. package/src/graphs/__tests__/composition.smoke.test.ts +84 -2
  45. package/src/tools/BashProgrammaticToolCalling.ts +31 -22
  46. package/src/tools/ProgrammaticToolCalling.ts +31 -23
  47. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +103 -0
  48. package/src/tools/local/LocalProgrammaticToolCalling.ts +94 -13
  49. package/src/tools/ptcTimeout.ts +89 -0
  50. package/src/types/tools.ts +2 -0
@@ -1,6 +1,7 @@
1
1
  import { config } from 'dotenv';
2
2
  import { tool, DynamicStructuredTool } from '@langchain/core/tools';
3
3
  import type { ToolCall } from '@langchain/core/messages/tool';
4
+ import type { ProgrammaticToolCallingJsonSchema } from './ptcTimeout';
4
5
  import type * as t from '@/types';
5
6
  import {
6
7
  makeRequest,
@@ -8,6 +9,11 @@ import {
8
9
  formatCompletedResponse,
9
10
  } from './ProgrammaticToolCalling';
10
11
  import { getCodeBaseURL } from './CodeExecutor';
12
+ import {
13
+ clampCodeApiRunTimeoutMs,
14
+ createCodeApiRunTimeoutSchema,
15
+ resolveCodeApiRunTimeoutMs,
16
+ } from './ptcTimeout';
11
17
  import { Constants } from '@/common';
12
18
 
13
19
  config();
@@ -17,7 +23,7 @@ config();
17
23
  // ============================================================================
18
24
 
19
25
  const DEFAULT_MAX_ROUND_TRIPS = 20;
20
- const DEFAULT_TIMEOUT = 60000;
26
+ const DEFAULT_RUN_TIMEOUT_MS = resolveCodeApiRunTimeoutMs();
21
27
 
22
28
  /** Bash reserved words that get `_tool` suffix when used as function names */
23
29
  const BASH_RESERVED = new Set([
@@ -60,7 +66,8 @@ const CORE_RULES = `Rules:
60
66
  - Tools are pre-defined as bash functions—DO NOT redefine them
61
67
  - Each tool function accepts a JSON string argument
62
68
  - Only echo/printf output returns to the model
63
- - Generated files are automatically available in /mnt/data/ for subsequent executions`;
69
+ - Generated files are automatically available in /mnt/data/ for subsequent executions
70
+ - timeout caps one sandbox run/replay iteration, not the total multi-round-trip workflow`;
64
71
 
65
72
  const ADDITIONAL_RULES =
66
73
  '- Tool names normalized: hyphens→underscores, reserved words get `_tool` suffix';
@@ -92,25 +99,25 @@ ${CORE_RULES}`;
92
99
  // Schema
93
100
  // ============================================================================
94
101
 
95
- export const BashProgrammaticToolCallingSchema = {
96
- type: 'object',
97
- properties: {
98
- code: {
99
- type: 'string',
100
- minLength: 1,
101
- description: CODE_PARAM_DESCRIPTION,
102
- },
103
- timeout: {
104
- type: 'integer',
105
- minimum: 1000,
106
- maximum: 300000,
107
- default: DEFAULT_TIMEOUT,
108
- description:
109
- 'Maximum execution time in milliseconds. Default: 60 seconds. Max: 5 minutes.',
102
+ export function createBashProgrammaticToolCallingSchema(
103
+ maxRunTimeoutMs = DEFAULT_RUN_TIMEOUT_MS
104
+ ): ProgrammaticToolCallingJsonSchema {
105
+ return {
106
+ type: 'object',
107
+ properties: {
108
+ code: {
109
+ type: 'string',
110
+ minLength: 1,
111
+ description: CODE_PARAM_DESCRIPTION,
112
+ },
113
+ timeout: createCodeApiRunTimeoutSchema(maxRunTimeoutMs),
110
114
  },
111
- },
112
- required: ['code'],
113
- } as const;
115
+ required: ['code'],
116
+ } as const;
117
+ }
118
+
119
+ export const BashProgrammaticToolCallingSchema =
120
+ createBashProgrammaticToolCallingSchema();
114
121
 
115
122
  export const BashProgrammaticToolCallingName =
116
123
  Constants.BASH_PROGRAMMATIC_TOOL_CALLING;
@@ -242,6 +249,7 @@ export function createBashProgrammaticToolCallingTool(
242
249
  ): DynamicStructuredTool {
243
250
  const baseUrl = initParams.baseUrl ?? getCodeBaseURL();
244
251
  const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;
252
+ const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);
245
253
  const proxy = initParams.proxy ?? process.env.PROXY;
246
254
  const debug = initParams.debug ?? process.env.BASH_PTC_DEBUG === 'true';
247
255
  const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;
@@ -249,7 +257,8 @@ export function createBashProgrammaticToolCallingTool(
249
257
  return tool(
250
258
  async (rawParams, config) => {
251
259
  const params = rawParams as { code: string; timeout?: number };
252
- const { code, timeout = DEFAULT_TIMEOUT } = params;
260
+ const { code } = params;
261
+ const timeout = clampCodeApiRunTimeoutMs(params.timeout, maxRunTimeoutMs);
253
262
 
254
263
  const toolCall = (config.toolCall ?? {}) as ToolCall &
255
264
  Partial<t.ProgrammaticCache> & {
@@ -382,7 +391,7 @@ export function createBashProgrammaticToolCallingTool(
382
391
  {
383
392
  name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
384
393
  description: BashProgrammaticToolCallingDescription,
385
- schema: BashProgrammaticToolCallingSchema,
394
+ schema: createBashProgrammaticToolCallingSchema(maxRunTimeoutMs),
386
395
  responseFormat: Constants.CONTENT_AND_ARTIFACT,
387
396
  }
388
397
  );
@@ -4,6 +4,7 @@ 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
9
  import {
9
10
  buildCodeApiHttpErrorMessage,
@@ -11,6 +12,11 @@ import {
11
12
  getCodeBaseURL,
12
13
  resolveCodeApiAuthHeaders,
13
14
  } from './CodeExecutor';
15
+ import {
16
+ clampCodeApiRunTimeoutMs,
17
+ createCodeApiRunTimeoutSchema,
18
+ resolveCodeApiRunTimeoutMs,
19
+ } from './ptcTimeout';
14
20
  import { Constants } from '@/common';
15
21
 
16
22
  config();
@@ -18,8 +24,7 @@ config();
18
24
  /** Default max round-trips to prevent infinite loops */
19
25
  const DEFAULT_MAX_ROUND_TRIPS = 20;
20
26
 
21
- /** Default execution timeout in milliseconds */
22
- const DEFAULT_TIMEOUT = 60000;
27
+ const DEFAULT_RUN_TIMEOUT_MS = resolveCodeApiRunTimeoutMs();
23
28
 
24
29
  // ============================================================================
25
30
  // Description Components (Single Source of Truth)
@@ -35,7 +40,8 @@ const CORE_RULES = `Rules:
35
40
  - Just write code with await—auto-wrapped in async context
36
41
  - DO NOT define async def main() or call asyncio.run()
37
42
  - Tools are pre-defined—DO NOT write function definitions
38
- - 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`;
39
45
 
40
46
  const ADDITIONAL_RULES = `- Generated files are automatically available in /mnt/data/ for subsequent executions
41
47
  - Tool names normalized: hyphens→underscores, keywords get \`_tool\` suffix`;
@@ -68,25 +74,25 @@ ${EXAMPLES}
68
74
 
69
75
  ${CORE_RULES}`;
70
76
 
71
- export const ProgrammaticToolCallingSchema = {
72
- type: 'object',
73
- properties: {
74
- code: {
75
- type: 'string',
76
- minLength: 1,
77
- description: CODE_PARAM_DESCRIPTION,
78
- },
79
- timeout: {
80
- type: 'integer',
81
- minimum: 1000,
82
- maximum: 300000,
83
- default: DEFAULT_TIMEOUT,
84
- description:
85
- '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),
86
89
  },
87
- },
88
- required: ['code'],
89
- } as const;
90
+ required: ['code'],
91
+ } as const;
92
+ }
93
+
94
+ export const ProgrammaticToolCallingSchema =
95
+ createProgrammaticToolCallingSchema();
90
96
 
91
97
  export const ProgrammaticToolCallingName = Constants.PROGRAMMATIC_TOOL_CALLING;
92
98
 
@@ -731,6 +737,7 @@ export function createProgrammaticToolCallingTool(
731
737
  ): DynamicStructuredTool {
732
738
  const baseUrl = initParams.baseUrl ?? getCodeBaseURL();
733
739
  const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;
740
+ const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);
734
741
  const proxy = initParams.proxy ?? process.env.PROXY;
735
742
  const debug = initParams.debug ?? process.env.PTC_DEBUG === 'true';
736
743
  const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;
@@ -738,7 +745,8 @@ export function createProgrammaticToolCallingTool(
738
745
  return tool(
739
746
  async (rawParams, config) => {
740
747
  const params = rawParams as { code: string; timeout?: number };
741
- const { code, timeout = DEFAULT_TIMEOUT } = params;
748
+ const { code } = params;
749
+ const timeout = clampCodeApiRunTimeoutMs(params.timeout, maxRunTimeoutMs);
742
750
 
743
751
  // Extra params injected by ToolNode (follows web_search pattern).
744
752
  const toolCall = (config.toolCall ?? {}) as ToolCall &
@@ -873,7 +881,7 @@ export function createProgrammaticToolCallingTool(
873
881
  {
874
882
  name: Constants.PROGRAMMATIC_TOOL_CALLING,
875
883
  description: ProgrammaticToolCallingDescription,
876
- schema: ProgrammaticToolCallingSchema,
884
+ schema: createProgrammaticToolCallingSchema(maxRunTimeoutMs),
877
885
  responseFormat: Constants.CONTENT_AND_ARTIFACT,
878
886
  }
879
887
  );
@@ -13,6 +13,14 @@ import {
13
13
  makeRequest,
14
14
  } from '../ProgrammaticToolCalling';
15
15
  import { createBashProgrammaticToolCallingTool } from '../BashProgrammaticToolCalling';
16
+ import {
17
+ clampCodeApiRunTimeoutMs,
18
+ createCodeApiRunTimeoutSchema,
19
+ } from '../ptcTimeout';
20
+ import {
21
+ createLocalProgrammaticToolCallingTool,
22
+ createLocalBashProgrammaticToolCallingTool,
23
+ } from '../local/LocalProgrammaticToolCalling';
16
24
 
17
25
  jest.mock('node-fetch', () => ({
18
26
  __esModule: true,
@@ -23,8 +31,33 @@ type FetchMock = jest.MockedFunction<
23
31
  (url: unknown, init?: unknown) => Promise<unknown>
24
32
  >;
25
33
 
34
+ type CodeApiRequestBody = {
35
+ timeout?: number;
36
+ };
37
+
38
+ type TimeoutSchemaForTest = {
39
+ default: number;
40
+ maximum: number;
41
+ description: string;
42
+ };
43
+
44
+ type ToolSchemaForTest = {
45
+ properties: {
46
+ timeout: TimeoutSchemaForTest;
47
+ };
48
+ };
49
+
26
50
  const fetchMock = fetch as unknown as FetchMock;
27
51
 
52
+ function requestBodyAt(callIndex: number): CodeApiRequestBody {
53
+ const init = fetchMock.mock.calls[callIndex]?.[1] as RequestInit;
54
+ return JSON.parse(init.body as string) as CodeApiRequestBody;
55
+ }
56
+
57
+ function timeoutSchemaForTest(toolSchema: unknown): TimeoutSchemaForTest {
58
+ return (toolSchema as ToolSchemaForTest).properties.timeout;
59
+ }
60
+
28
61
  function jsonResponse(body: unknown): unknown {
29
62
  return {
30
63
  ok: true,
@@ -203,6 +236,76 @@ describe('CodeAPI auth header injection', () => {
203
236
  }
204
237
  });
205
238
 
239
+ it('defaults programmatic timeout to the configured CodeAPI run cap', async () => {
240
+ const tool = createProgrammaticToolCallingTool({
241
+ runTimeoutMs: 15000,
242
+ });
243
+
244
+ await tool.invoke(
245
+ { code: 'result = await lookup_user()\nprint(result)' },
246
+ {
247
+ toolCall: {
248
+ name: 'programmatic_code_execution',
249
+ args: {},
250
+ toolMap: toolMap(),
251
+ toolDefs,
252
+ },
253
+ }
254
+ );
255
+
256
+ expect(requestBodyAt(0).timeout).toBe(15000);
257
+ });
258
+
259
+ it('defaults bash programmatic timeout to the configured CodeAPI run cap', async () => {
260
+ const tool = createBashProgrammaticToolCallingTool({
261
+ runTimeoutMs: 15000,
262
+ });
263
+
264
+ await tool.invoke(
265
+ { code: 'lookup_user "{}"' },
266
+ {
267
+ toolCall: {
268
+ name: 'bash_programmatic_code_execution',
269
+ args: {},
270
+ toolMap: toolMap(),
271
+ toolDefs,
272
+ },
273
+ }
274
+ );
275
+
276
+ expect(requestBodyAt(0).timeout).toBe(15000);
277
+ });
278
+
279
+ it('describes the PTC timeout as a single sandbox run cap', () => {
280
+ const schema = createCodeApiRunTimeoutSchema(15000);
281
+
282
+ expect(clampCodeApiRunTimeoutMs(60000, 15000)).toBe(15000);
283
+ expect(schema.default).toBe(15000);
284
+ expect(schema.maximum).toBe(15000);
285
+ expect(schema.description).toContain('one sandbox run');
286
+ expect(schema.description).toContain('not the total multi-round-trip');
287
+ });
288
+
289
+ it('keeps local programmatic timeout schemas aligned with local execution defaults', () => {
290
+ const pythonTimeout = timeoutSchemaForTest(
291
+ createLocalProgrammaticToolCallingTool().schema
292
+ );
293
+ const bashTimeout = timeoutSchemaForTest(
294
+ createLocalBashProgrammaticToolCallingTool().schema
295
+ );
296
+ const configuredTimeout = timeoutSchemaForTest(
297
+ createLocalProgrammaticToolCallingTool({ timeoutMs: 120000 }).schema
298
+ );
299
+
300
+ expect(pythonTimeout.default).toBe(60000);
301
+ expect(pythonTimeout.maximum).toBe(300000);
302
+ expect(pythonTimeout.description).toContain('local execution time');
303
+ expect(bashTimeout.default).toBe(60000);
304
+ expect(bashTimeout.maximum).toBe(300000);
305
+ expect(configuredTimeout.default).toBe(120000);
306
+ expect(configuredTimeout.maximum).toBe(300000);
307
+ });
308
+
206
309
  it('forwards Authorization for bash programmatic requests', async () => {
207
310
  const tool = createBashProgrammaticToolCallingTool({
208
311
  authHeaders: { Authorization: 'Bearer bash-ptc-token' },
@@ -30,19 +30,100 @@ import {
30
30
  import { Constants } from '@/common';
31
31
 
32
32
  const DEFAULT_TIMEOUT = 60000;
33
- const LocalProgrammaticToolCallingSchema = {
34
- ...ProgrammaticToolCallingSchema,
35
- properties: {
36
- ...ProgrammaticToolCallingSchema.properties,
33
+ const LOCAL_MIN_TIMEOUT = 1000;
34
+ const LOCAL_MAX_TIMEOUT = 300000;
35
+
36
+ type LocalTimeoutSchema = {
37
+ type: 'integer';
38
+ minimum: number;
39
+ maximum: number;
40
+ default: number;
41
+ description: string;
42
+ };
43
+
44
+ type LocalProgrammaticToolCallingJsonSchema = {
45
+ type: 'object';
46
+ properties: typeof ProgrammaticToolCallingSchema.properties & {
47
+ timeout: LocalTimeoutSchema;
37
48
  lang: {
38
- type: 'string',
39
- enum: ['py', 'python', 'bash', 'sh'],
40
- default: 'bash',
41
- description:
42
- 'Local engine runtime for orchestration code. Defaults to bash; use py/python for Python orchestration.',
49
+ type: 'string';
50
+ enum: readonly ['py', 'python', 'bash', 'sh'];
51
+ default: 'bash';
52
+ description: string;
53
+ };
54
+ };
55
+ required: readonly ['code'];
56
+ };
57
+
58
+ type LocalBashProgrammaticToolCallingJsonSchema = {
59
+ type: 'object';
60
+ properties: typeof BashProgrammaticToolCallingSchema.properties & {
61
+ timeout: LocalTimeoutSchema;
62
+ };
63
+ required: readonly ['code'];
64
+ };
65
+
66
+ function normalizeLocalTimeout(timeoutMs: number | undefined): number {
67
+ if (timeoutMs == null || !Number.isFinite(timeoutMs)) {
68
+ return DEFAULT_TIMEOUT;
69
+ }
70
+
71
+ return Math.max(LOCAL_MIN_TIMEOUT, Math.floor(timeoutMs));
72
+ }
73
+
74
+ function formatLocalTimeout(timeoutMs: number): string {
75
+ return timeoutMs % 1000 === 0
76
+ ? `${timeoutMs / 1000} seconds`
77
+ : `${timeoutMs} milliseconds`;
78
+ }
79
+
80
+ function createLocalTimeoutSchema(timeoutMs?: number): LocalTimeoutSchema {
81
+ const defaultTimeout = normalizeLocalTimeout(timeoutMs);
82
+ const maxTimeout = Math.max(LOCAL_MAX_TIMEOUT, defaultTimeout);
83
+ const formattedDefault = formatLocalTimeout(defaultTimeout);
84
+ const formattedMax = formatLocalTimeout(maxTimeout);
85
+
86
+ return {
87
+ type: 'integer',
88
+ minimum: LOCAL_MIN_TIMEOUT,
89
+ maximum: maxTimeout,
90
+ default: defaultTimeout,
91
+ description:
92
+ 'Maximum local execution time in milliseconds. ' +
93
+ `Default: ${formattedDefault}. Max: ${formattedMax}.`,
94
+ };
95
+ }
96
+
97
+ function createLocalProgrammaticToolCallingSchema(
98
+ localConfig: t.LocalExecutionConfig = {}
99
+ ): LocalProgrammaticToolCallingJsonSchema {
100
+ return {
101
+ ...ProgrammaticToolCallingSchema,
102
+ properties: {
103
+ ...ProgrammaticToolCallingSchema.properties,
104
+ timeout: createLocalTimeoutSchema(localConfig.timeoutMs),
105
+ lang: {
106
+ type: 'string',
107
+ enum: ['py', 'python', 'bash', 'sh'],
108
+ default: 'bash',
109
+ description:
110
+ 'Local engine runtime for orchestration code. Defaults to bash; use py/python for Python orchestration.',
111
+ },
43
112
  },
44
- },
45
- } as const;
113
+ } as const;
114
+ }
115
+
116
+ function createLocalBashProgrammaticToolCallingSchema(
117
+ localConfig: t.LocalExecutionConfig = {}
118
+ ): LocalBashProgrammaticToolCallingJsonSchema {
119
+ return {
120
+ ...BashProgrammaticToolCallingSchema,
121
+ properties: {
122
+ ...BashProgrammaticToolCallingSchema.properties,
123
+ timeout: createLocalTimeoutSchema(localConfig.timeoutMs),
124
+ },
125
+ } as const;
126
+ }
46
127
 
47
128
  type ToolBridge = {
48
129
  url: string;
@@ -582,7 +663,7 @@ export function createLocalProgrammaticToolCallingTool(
582
663
  {
583
664
  name: ProgrammaticToolCallingName,
584
665
  description: `${ProgrammaticToolCallingDescription}\n\nLocal engine: runs bash by default, or Python when \`lang\` is \`py\` or \`python\`, on the host machine and calls tools through an in-process localhost bridge.`,
585
- schema: LocalProgrammaticToolCallingSchema,
666
+ schema: createLocalProgrammaticToolCallingSchema(localConfig),
586
667
  responseFormat: Constants.CONTENT_AND_ARTIFACT,
587
668
  }
588
669
  );
@@ -604,7 +685,7 @@ export function createLocalBashProgrammaticToolCallingTool(
604
685
  {
605
686
  name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
606
687
  description: `${BashProgrammaticToolCallingDescription}\n\nLocal engine: runs this bash orchestration code on the host machine and calls tools through an in-process localhost bridge.`,
607
- schema: BashProgrammaticToolCallingSchema,
688
+ schema: createLocalBashProgrammaticToolCallingSchema(localConfig),
608
689
  responseFormat: Constants.CONTENT_AND_ARTIFACT,
609
690
  }
610
691
  );
@@ -0,0 +1,89 @@
1
+ import { EnvVar } from '@/common';
2
+
3
+ export const DEFAULT_CODE_API_RUN_TIMEOUT_MS = 15_000;
4
+ export const MIN_CODE_API_RUN_TIMEOUT_MS = 1_000;
5
+
6
+ type TimeoutSchema = {
7
+ type: 'integer';
8
+ minimum: number;
9
+ maximum: number;
10
+ default: number;
11
+ description: string;
12
+ };
13
+
14
+ export type ProgrammaticToolCallingJsonSchema = {
15
+ type: 'object';
16
+ properties: {
17
+ code: {
18
+ type: 'string';
19
+ minLength: number;
20
+ description: string;
21
+ };
22
+ timeout: TimeoutSchema;
23
+ };
24
+ required: readonly ['code'];
25
+ };
26
+
27
+ function normalizeTimeoutMs(value: number | undefined): number | undefined {
28
+ if (value == null || !Number.isFinite(value)) {
29
+ return undefined;
30
+ }
31
+
32
+ return Math.max(MIN_CODE_API_RUN_TIMEOUT_MS, Math.floor(value));
33
+ }
34
+
35
+ function parseTimeoutMs(value: string | undefined): number | undefined {
36
+ if (value == null || value.trim() === '') {
37
+ return undefined;
38
+ }
39
+
40
+ return normalizeTimeoutMs(Number(value));
41
+ }
42
+
43
+ function formatTimeout(timeoutMs: number): string {
44
+ return timeoutMs % 1000 === 0
45
+ ? `${timeoutMs / 1000} seconds`
46
+ : `${timeoutMs} milliseconds`;
47
+ }
48
+
49
+ export function resolveCodeApiRunTimeoutMs(override?: number): number {
50
+ return (
51
+ normalizeTimeoutMs(override) ??
52
+ parseTimeoutMs(process.env[EnvVar.CODE_API_RUN_TIMEOUT_MS]) ??
53
+ DEFAULT_CODE_API_RUN_TIMEOUT_MS
54
+ );
55
+ }
56
+
57
+ export function clampCodeApiRunTimeoutMs(
58
+ timeoutMs: number | undefined,
59
+ maxRunTimeoutMs = resolveCodeApiRunTimeoutMs()
60
+ ): number {
61
+ const normalizedMaxRunTimeoutMs =
62
+ normalizeTimeoutMs(maxRunTimeoutMs) ?? DEFAULT_CODE_API_RUN_TIMEOUT_MS;
63
+ const normalizedTimeoutMs = normalizeTimeoutMs(timeoutMs);
64
+
65
+ if (normalizedTimeoutMs == null) {
66
+ return normalizedMaxRunTimeoutMs;
67
+ }
68
+
69
+ return Math.min(normalizedTimeoutMs, normalizedMaxRunTimeoutMs);
70
+ }
71
+
72
+ export function createCodeApiRunTimeoutSchema(
73
+ maxRunTimeoutMs = resolveCodeApiRunTimeoutMs()
74
+ ): TimeoutSchema {
75
+ const normalizedMaxRunTimeoutMs =
76
+ normalizeTimeoutMs(maxRunTimeoutMs) ?? DEFAULT_CODE_API_RUN_TIMEOUT_MS;
77
+ const formattedTimeout = formatTimeout(normalizedMaxRunTimeoutMs);
78
+
79
+ return {
80
+ type: 'integer',
81
+ minimum: MIN_CODE_API_RUN_TIMEOUT_MS,
82
+ maximum: normalizedMaxRunTimeoutMs,
83
+ default: normalizedMaxRunTimeoutMs,
84
+ description:
85
+ 'Maximum wall-clock time in milliseconds for one sandbox run or replay iteration. ' +
86
+ 'This is not the total multi-round-trip task budget. ' +
87
+ `Default: ${formattedTimeout}. Max: ${formattedTimeout}.`,
88
+ };
89
+ }
@@ -900,6 +900,8 @@ export type ProgrammaticToolCallingParams = {
900
900
  baseUrl?: string;
901
901
  /** Safety limit for round-trips (default: 20) */
902
902
  maxRoundTrips?: number;
903
+ /** Maximum per-sandbox-run timeout for PTC's legacy `timeout` field. */
904
+ runTimeoutMs?: number;
903
905
  /** HTTP proxy URL */
904
906
  proxy?: string;
905
907
  /** Enable debug logging (or set PTC_DEBUG=true env var) */