@librechat/agents 3.6.8 → 3.6.10

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 (158) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +46 -39
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +2 -2
  4. package/dist/cjs/agents/projection.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +28 -18
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +87 -16
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/openai/index.cjs +10 -0
  10. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +14 -0
  12. package/dist/cjs/messages/alternation.cjs +125 -20
  13. package/dist/cjs/messages/alternation.cjs.map +1 -1
  14. package/dist/cjs/messages/core.cjs +195 -18
  15. package/dist/cjs/messages/core.cjs.map +1 -1
  16. package/dist/cjs/messages/format.cjs +488 -118
  17. package/dist/cjs/messages/format.cjs.map +1 -1
  18. package/dist/cjs/messages/handoffCue.cjs +3 -2
  19. package/dist/cjs/messages/handoffCue.cjs.map +1 -1
  20. package/dist/cjs/messages/index.cjs +1 -0
  21. package/dist/cjs/messages/injected.cjs +5 -2
  22. package/dist/cjs/messages/injected.cjs.map +1 -1
  23. package/dist/cjs/messages/provenance.cjs +411 -0
  24. package/dist/cjs/messages/provenance.cjs.map +1 -0
  25. package/dist/cjs/messages/prune.cjs +18 -13
  26. package/dist/cjs/messages/prune.cjs.map +1 -1
  27. package/dist/cjs/messages/toolResultTypes.cjs +663 -0
  28. package/dist/cjs/messages/toolResultTypes.cjs.map +1 -0
  29. package/dist/cjs/run.cjs +7 -4
  30. package/dist/cjs/run.cjs.map +1 -1
  31. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +21 -4
  32. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  33. package/dist/cjs/tools/CallerCapabilities.cjs +48 -0
  34. package/dist/cjs/tools/CallerCapabilities.cjs.map +1 -0
  35. package/dist/cjs/tools/ProgrammaticCallerPolicy.cjs +34 -0
  36. package/dist/cjs/tools/ProgrammaticCallerPolicy.cjs.map +1 -0
  37. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +21 -4
  38. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolNode.cjs +25 -24
  40. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  41. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +12 -4
  42. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  43. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +13 -4
  44. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  45. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +47 -11
  46. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
  47. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
  48. package/dist/cjs/tools/search/search.cjs +19 -9
  49. package/dist/cjs/tools/search/search.cjs.map +1 -1
  50. package/dist/cjs/tools/search/tool.cjs +2 -1
  51. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  52. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
  53. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  54. package/dist/cjs/utils/toolContent.cjs +2 -2
  55. package/dist/cjs/utils/toolContent.cjs.map +1 -1
  56. package/dist/esm/agents/AgentContext.mjs +46 -39
  57. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  58. package/dist/esm/agents/projection.mjs +2 -2
  59. package/dist/esm/agents/projection.mjs.map +1 -1
  60. package/dist/esm/graphs/Graph.mjs +30 -20
  61. package/dist/esm/graphs/Graph.mjs.map +1 -1
  62. package/dist/esm/graphs/MultiAgentGraph.mjs +87 -16
  63. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  64. package/dist/esm/llm/openai/index.mjs +10 -0
  65. package/dist/esm/llm/openai/index.mjs.map +1 -1
  66. package/dist/esm/main.mjs +4 -3
  67. package/dist/esm/messages/alternation.mjs +125 -20
  68. package/dist/esm/messages/alternation.mjs.map +1 -1
  69. package/dist/esm/messages/core.mjs +196 -19
  70. package/dist/esm/messages/core.mjs.map +1 -1
  71. package/dist/esm/messages/format.mjs +488 -119
  72. package/dist/esm/messages/format.mjs.map +1 -1
  73. package/dist/esm/messages/handoffCue.mjs +3 -2
  74. package/dist/esm/messages/handoffCue.mjs.map +1 -1
  75. package/dist/esm/messages/index.mjs +1 -0
  76. package/dist/esm/messages/injected.mjs +5 -2
  77. package/dist/esm/messages/injected.mjs.map +1 -1
  78. package/dist/esm/messages/provenance.mjs +401 -0
  79. package/dist/esm/messages/provenance.mjs.map +1 -0
  80. package/dist/esm/messages/prune.mjs +18 -13
  81. package/dist/esm/messages/prune.mjs.map +1 -1
  82. package/dist/esm/messages/toolResultTypes.mjs +657 -0
  83. package/dist/esm/messages/toolResultTypes.mjs.map +1 -0
  84. package/dist/esm/run.mjs +7 -4
  85. package/dist/esm/run.mjs.map +1 -1
  86. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +21 -4
  87. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  88. package/dist/esm/tools/CallerCapabilities.mjs +45 -0
  89. package/dist/esm/tools/CallerCapabilities.mjs.map +1 -0
  90. package/dist/esm/tools/ProgrammaticCallerPolicy.mjs +32 -0
  91. package/dist/esm/tools/ProgrammaticCallerPolicy.mjs.map +1 -0
  92. package/dist/esm/tools/ProgrammaticToolCalling.mjs +21 -4
  93. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  94. package/dist/esm/tools/ToolNode.mjs +25 -24
  95. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  96. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +14 -6
  97. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  98. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +15 -6
  99. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  100. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +47 -12
  101. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  102. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  103. package/dist/esm/tools/search/search.mjs +19 -9
  104. package/dist/esm/tools/search/search.mjs.map +1 -1
  105. package/dist/esm/tools/search/tool.mjs +2 -1
  106. package/dist/esm/tools/search/tool.mjs.map +1 -1
  107. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
  108. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  109. package/dist/esm/utils/toolContent.mjs +2 -2
  110. package/dist/esm/utils/toolContent.mjs.map +1 -1
  111. package/dist/types/agents/AgentContext.d.ts +8 -13
  112. package/dist/types/agents/projection.d.ts +3 -1
  113. package/dist/types/graphs/Graph.d.ts +1 -1
  114. package/dist/types/llm/openai/index.d.ts +20 -0
  115. package/dist/types/messages/format.d.ts +5 -0
  116. package/dist/types/messages/index.d.ts +1 -0
  117. package/dist/types/messages/provenance.d.ts +85 -0
  118. package/dist/types/messages/toolResultTypes.d.ts +33 -0
  119. package/dist/types/tools/CallerCapabilities.d.ts +12 -0
  120. package/dist/types/tools/ProgrammaticCallerPolicy.d.ts +20 -0
  121. package/dist/types/tools/ToolNode.d.ts +4 -7
  122. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +4 -0
  123. package/dist/types/tools/ptcTimeout.d.ts +8 -0
  124. package/dist/types/tools/search/types.d.ts +15 -0
  125. package/dist/types/types/graph.d.ts +3 -1
  126. package/dist/types/types/tools.d.ts +13 -2
  127. package/package.json +1 -1
  128. package/src/agents/AgentContext.ts +109 -67
  129. package/src/agents/projection.ts +9 -1
  130. package/src/graphs/Graph.ts +56 -23
  131. package/src/graphs/MultiAgentGraph.ts +175 -33
  132. package/src/llm/openai/index.ts +43 -0
  133. package/src/messages/alternation.ts +217 -34
  134. package/src/messages/core.ts +358 -38
  135. package/src/messages/format.ts +958 -137
  136. package/src/messages/handoffCue.ts +7 -4
  137. package/src/messages/index.ts +1 -0
  138. package/src/messages/injected.ts +9 -6
  139. package/src/messages/provenance.ts +897 -0
  140. package/src/messages/prune.ts +28 -10
  141. package/src/messages/toolResultTypes.ts +1174 -0
  142. package/src/run.ts +9 -4
  143. package/src/tools/BashProgrammaticToolCalling.ts +46 -12
  144. package/src/tools/CallerCapabilities.ts +78 -0
  145. package/src/tools/ProgrammaticCallerPolicy.ts +97 -0
  146. package/src/tools/ProgrammaticToolCalling.ts +41 -7
  147. package/src/tools/ToolNode.ts +70 -52
  148. package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +24 -26
  149. package/src/tools/local/LocalProgrammaticToolCalling.ts +28 -23
  150. package/src/tools/local/resolveLocalExecutionTools.ts +132 -13
  151. package/src/tools/ptcTimeout.ts +6 -0
  152. package/src/tools/search/search.ts +25 -8
  153. package/src/tools/search/tool.ts +2 -0
  154. package/src/tools/search/types.ts +16 -0
  155. package/src/tools/subagent/SubagentExecutor.ts +8 -4
  156. package/src/types/graph.ts +3 -0
  157. package/src/types/tools.ts +14 -3
  158. package/src/utils/toolContent.ts +2 -2
package/src/run.ts CHANGED
@@ -93,6 +93,7 @@ import {
93
93
  import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
94
94
  import { LANGFUSE_OPERATION_METADATA_KEY } from '@/langfuseOperation';
95
95
  import { createTokenCounter, encodingForModel } from '@/utils/tokens';
96
+ import { stampSyntheticProviderMessage } from '@/messages/provenance';
96
97
  import { initializeLangfuseTracing } from './instrumentation';
97
98
  import { seedRunInitialSessions } from '@/utils/toolSessions';
98
99
  import { getTraceIdSeed } from '@/langfuseRuntimeContext';
@@ -469,6 +470,7 @@ export class Run<_T extends t.BaseGraphState> {
469
470
  subagentTasks: this.subagentTasks,
470
471
  preemption: this.preemption,
471
472
  streamLimits: this.streamLimits,
473
+ toolExecution: this.toolExecution,
472
474
  },
473
475
  });
474
476
  /** Propagate compile options from graph config */
@@ -508,6 +510,7 @@ export class Run<_T extends t.BaseGraphState> {
508
510
  subagentTasks: this.subagentTasks,
509
511
  preemption: this.preemption,
510
512
  streamLimits: this.streamLimits,
513
+ toolExecution: this.toolExecution,
511
514
  },
512
515
  });
513
516
 
@@ -699,10 +702,12 @@ export class Run<_T extends t.BaseGraphState> {
699
702
  * it into the agent's `instructions` config instead.
700
703
  */
701
704
  stateInputs.messages.push(
702
- new HumanMessage({
703
- content: preStreamContexts.join('\n\n'),
704
- additional_kwargs: { role: 'system', source: 'hook' },
705
- })
705
+ stampSyntheticProviderMessage(
706
+ new HumanMessage({
707
+ content: preStreamContexts.join('\n\n'),
708
+ additional_kwargs: { role: 'system', source: 'hook' },
709
+ })
710
+ )
706
711
  );
707
712
  }
708
713
 
@@ -13,16 +13,22 @@ import {
13
13
  getCodeBaseURL,
14
14
  selectRuntimeSessionHint,
15
15
  } from './CodeExecutor';
16
- import {
17
- clampCodeApiRunTimeoutMs,
18
- createCodeApiRunTimeoutSchema,
19
- resolveCodeApiRunTimeoutMs,
20
- } from './ptcTimeout';
21
16
  import {
22
17
  makeRequest,
23
18
  executeTools,
24
19
  formatCompletedResponse,
25
20
  } from './ProgrammaticToolCalling';
21
+ import {
22
+ projectProgrammaticToolMap,
23
+ resolveProgrammaticToolDefinitions,
24
+ selectProgrammaticTools,
25
+ type ProgrammaticInvocationParams,
26
+ } from './ProgrammaticCallerPolicy';
27
+ import {
28
+ clampCodeApiRunTimeoutMs,
29
+ createCodeApiRunTimeoutSchema,
30
+ resolveCodeApiRunTimeoutMs,
31
+ } from './ptcTimeout';
26
32
  import { INTENT_PROPERTY } from '@/tools/intentArg';
27
33
  import { Constants } from '@/common';
28
34
 
@@ -108,6 +114,10 @@ ${EXAMPLES}
108
114
 
109
115
  ${CORE_RULES}`;
110
116
 
117
+ const TOOL_MANIFEST_DESCRIPTION =
118
+ 'Exact registered tool names used by the code. Required when direct-only tools are configured; ' +
119
+ 'validated before execution starts.';
120
+
111
121
  // ============================================================================
112
122
  // Schema
113
123
  // ============================================================================
@@ -124,6 +134,12 @@ export function createBashProgrammaticToolCallingSchema(
124
134
  minLength: 1,
125
135
  description: CODE_PARAM_DESCRIPTION,
126
136
  },
137
+ tool_manifest: {
138
+ type: 'array',
139
+ items: { type: 'string' },
140
+ uniqueItems: true,
141
+ description: TOOL_MANIFEST_DESCRIPTION,
142
+ },
127
143
  timeout: createCodeApiRunTimeoutSchema(maxRunTimeoutMs),
128
144
  },
129
145
  required: ['code'],
@@ -302,7 +318,7 @@ export function createBashProgrammaticToolCallingTool(
302
318
 
303
319
  return tool(
304
320
  async (rawParams, config) => {
305
- const params = rawParams as { code: string; timeout?: number };
321
+ const params = rawParams as ProgrammaticInvocationParams;
306
322
  const { code } = params;
307
323
  const timeout = clampCodeApiRunTimeoutMs(params.timeout, maxRunTimeoutMs);
308
324
 
@@ -314,11 +330,26 @@ export function createBashProgrammaticToolCallingTool(
314
330
  };
315
331
  const {
316
332
  toolMap,
317
- toolDefs,
333
+ disallowedToolDefs,
318
334
  session_id,
319
335
  _injected_files,
320
336
  _runtime_session_hint,
321
337
  } = toolCall;
338
+ const toolDefs = resolveProgrammaticToolDefinitions(
339
+ toolCall as typeof toolCall & { tools?: t.LCTool[] }
340
+ );
341
+
342
+ const programmaticToolName =
343
+ toolCall.programmaticToolName ??
344
+ (typeof toolCall.name === 'string' && toolCall.name !== ''
345
+ ? toolCall.name
346
+ : Constants.BASH_PROGRAMMATIC_TOOL_CALLING);
347
+ const effectiveTools = selectProgrammaticTools({
348
+ requestedToolNames: params.tool_manifest,
349
+ allowedToolDefs: toolDefs,
350
+ disallowedToolDefs,
351
+ programmaticToolName,
352
+ });
322
353
 
323
354
  if (toolMap == null || toolMap.size === 0) {
324
355
  throw new Error(
@@ -334,20 +365,23 @@ export function createBashProgrammaticToolCallingTool(
334
365
  );
335
366
  }
336
367
 
368
+ const effectiveToolMap = projectProgrammaticToolMap(
369
+ toolMap,
370
+ effectiveTools
371
+ );
372
+
337
373
  let roundTrip = 0;
338
374
 
339
375
  try {
340
376
  // ====================================================================
341
- // Phase 1: Filter tools and make initial request
377
+ // Phase 1: Send the validated tool manifest with the initial request
342
378
  // ====================================================================
343
379
 
344
- const effectiveTools = filterBashToolsByUsage(toolDefs, code, debug);
345
-
346
380
  if (debug) {
347
381
  // eslint-disable-next-line no-console
348
382
  console.log(
349
383
  `[BashPTC Debug] Sending ${effectiveTools.length} tools to API ` +
350
- `(filtered from ${toolDefs.length})`
384
+ `(selected from ${toolDefs.length})`
351
385
  );
352
386
  }
353
387
 
@@ -422,7 +456,7 @@ export function createBashProgrammaticToolCallingTool(
422
456
  const toolResults = normalizeBashToolResultsForReplay(
423
457
  await executeTools(
424
458
  response.tool_calls ?? [],
425
- toolMap,
459
+ effectiveToolMap,
426
460
  Constants.BASH_PROGRAMMATIC_TOOL_CALLING
427
461
  )
428
462
  );
@@ -0,0 +1,78 @@
1
+ import type * as t from '@/types';
2
+ import { Constants } from '@/common';
3
+
4
+ const PROGRAMMATIC_CONTROL_TOOL_NAMES = new Set<string>([
5
+ Constants.PROGRAMMATIC_TOOL_CALLING,
6
+ Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
7
+ Constants.TOOL_SEARCH,
8
+ ]);
9
+
10
+ export type CallerCapabilityProjection = {
11
+ directTools: t.LCTool[];
12
+ codeExecutionTools: t.LCTool[];
13
+ directOnlyTools: t.LCTool[];
14
+ codeExecutionOnlyTools: t.LCTool[];
15
+ };
16
+
17
+ export function getAllowedCallers(
18
+ toolDef: t.LCTool
19
+ ): readonly t.AllowedCaller[] {
20
+ return toolDef.allowed_callers ?? ['direct'];
21
+ }
22
+
23
+ export function allowsToolCaller(
24
+ toolDef: t.LCTool,
25
+ caller: t.AllowedCaller
26
+ ): boolean {
27
+ return getAllowedCallers(toolDef).includes(caller);
28
+ }
29
+
30
+ export function isProgrammaticControlTool(name: string): boolean {
31
+ return PROGRAMMATIC_CONTROL_TOOL_NAMES.has(name);
32
+ }
33
+
34
+ export function isToolDefinitionActive(
35
+ toolDef: t.LCTool,
36
+ discoveredToolNames: ReadonlySet<string>
37
+ ): boolean {
38
+ return (
39
+ toolDef.defer_loading !== true || discoveredToolNames.has(toolDef.name)
40
+ );
41
+ }
42
+
43
+ export function resolveCallerCapabilityProjection(
44
+ toolDefs: Iterable<t.LCTool>,
45
+ isActive: (toolDef: t.LCTool) => boolean = () => true
46
+ ): CallerCapabilityProjection {
47
+ const directTools: t.LCTool[] = [];
48
+ const codeExecutionTools: t.LCTool[] = [];
49
+ const directOnlyTools: t.LCTool[] = [];
50
+ const codeExecutionOnlyTools: t.LCTool[] = [];
51
+
52
+ for (const toolDef of toolDefs) {
53
+ if (!isActive(toolDef)) {
54
+ continue;
55
+ }
56
+ const allowsDirect = allowsToolCaller(toolDef, 'direct');
57
+ const allowsCodeExecution = allowsToolCaller(toolDef, 'code_execution');
58
+ if (allowsDirect) {
59
+ directTools.push(toolDef);
60
+ }
61
+ if (allowsCodeExecution) {
62
+ codeExecutionTools.push(toolDef);
63
+ }
64
+ if (allowsDirect && !allowsCodeExecution) {
65
+ directOnlyTools.push(toolDef);
66
+ }
67
+ if (allowsCodeExecution && !allowsDirect) {
68
+ codeExecutionOnlyTools.push(toolDef);
69
+ }
70
+ }
71
+
72
+ return {
73
+ directTools,
74
+ codeExecutionTools,
75
+ directOnlyTools,
76
+ codeExecutionOnlyTools,
77
+ };
78
+ }
@@ -0,0 +1,97 @@
1
+ import type * as t from '@/types';
2
+
3
+ export type ProgrammaticInvocationParams = {
4
+ code: string;
5
+ tool_manifest?: string[];
6
+ timeout?: number;
7
+ lang?: string;
8
+ runtime?: string;
9
+ language?: string;
10
+ };
11
+
12
+ export function resolveProgrammaticToolDefinitions(
13
+ context: Partial<t.ProgrammaticCache> & { tools?: t.LCTool[] }
14
+ ): t.LCTool[] | undefined {
15
+ return context.toolDefs ?? context.tools;
16
+ }
17
+
18
+ export function projectProgrammaticToolMap(
19
+ toolMap: t.ToolMap,
20
+ selectedToolDefs: readonly t.LCTool[]
21
+ ): t.ToolMap {
22
+ const selectedNames = new Set(selectedToolDefs.map((toolDef) => toolDef.name));
23
+ return new Map(
24
+ [...toolMap].filter(([name]) => selectedNames.has(name))
25
+ );
26
+ }
27
+
28
+ export function assertDisallowedToolUsage(
29
+ disallowedNames: ReadonlySet<string>,
30
+ programmaticToolName: string
31
+ ): void {
32
+ if (disallowedNames.size === 0) {
33
+ return;
34
+ }
35
+
36
+ const names = [...disallowedNames];
37
+ throw new Error(
38
+ `Tool${names.length === 1 ? '' : 's'} ${names
39
+ .map((name) => `"${name}"`)
40
+ .join(', ')} cannot be called from "${programmaticToolName}" because ` +
41
+ `the${names.length === 1 ? ' tool is' : 'se tools are'} not marked for code_execution. ` +
42
+ `Call ${names.length === 1 ? 'it' : 'them'} directly instead.`
43
+ );
44
+ }
45
+
46
+ export function selectProgrammaticTools(args: {
47
+ requestedToolNames?: string[];
48
+ allowedToolDefs?: t.LCTool[];
49
+ disallowedToolDefs?: t.LCTool[];
50
+ programmaticToolName: string;
51
+ }): t.LCTool[] {
52
+ const allowedToolDefs = args.allowedToolDefs ?? [];
53
+ const disallowedToolDefs = args.disallowedToolDefs ?? [];
54
+ const requestedToolNames = args.requestedToolNames;
55
+
56
+ if (requestedToolNames == null) {
57
+ if (disallowedToolDefs.length > 0) {
58
+ throw new Error(
59
+ `"${args.programmaticToolName}" requires a tool_manifest when direct-only tools are configured. ` +
60
+ 'List every registered tool name used by the submitted code in the tool_manifest field.'
61
+ );
62
+ }
63
+ return allowedToolDefs;
64
+ }
65
+
66
+ const disallowedNames = new Set(
67
+ disallowedToolDefs.map((toolDef) => toolDef.name)
68
+ );
69
+ const requestedDisallowedNames = new Set(
70
+ requestedToolNames.filter((name) => disallowedNames.has(name))
71
+ );
72
+ assertDisallowedToolUsage(
73
+ requestedDisallowedNames,
74
+ args.programmaticToolName
75
+ );
76
+
77
+ const allowedByName = new Map(
78
+ allowedToolDefs.map((toolDef) => [toolDef.name, toolDef])
79
+ );
80
+ const unknownNames = new Set(
81
+ requestedToolNames.filter((name) => !allowedByName.has(name))
82
+ );
83
+ if (unknownNames.size > 0) {
84
+ throw new Error(
85
+ `Tool${unknownNames.size === 1 ? '' : 's'} ${[...unknownNames]
86
+ .map((name) => `"${name}"`)
87
+ .join(
88
+ ', '
89
+ )} cannot be used by "${args.programmaticToolName}" because ` +
90
+ `${unknownNames.size === 1 ? 'it is' : 'they are'} not available for code_execution.`
91
+ );
92
+ }
93
+
94
+ return [...new Set(requestedToolNames)].map(
95
+ (name) => allowedByName.get(name) as t.LCTool
96
+ );
97
+ }
@@ -26,6 +26,12 @@ import {
26
26
  createCodeApiRunTimeoutSchema,
27
27
  resolveCodeApiRunTimeoutMs,
28
28
  } from './ptcTimeout';
29
+ import {
30
+ projectProgrammaticToolMap,
31
+ resolveProgrammaticToolDefinitions,
32
+ selectProgrammaticTools,
33
+ type ProgrammaticInvocationParams,
34
+ } from './ProgrammaticCallerPolicy';
29
35
  import { resolveFetchProxyAgent } from '@/utils/proxy';
30
36
  import { INTENT_PROPERTY } from '@/tools/intentArg';
31
37
  import { Constants } from '@/common';
@@ -87,6 +93,10 @@ ${EXAMPLES}
87
93
 
88
94
  ${CORE_RULES}`;
89
95
 
96
+ const TOOL_MANIFEST_DESCRIPTION =
97
+ 'Exact registered tool names used by the code. Required when direct-only tools are configured; ' +
98
+ 'validated before execution starts.';
99
+
90
100
  export function createProgrammaticToolCallingSchema(
91
101
  maxRunTimeoutMs = DEFAULT_RUN_TIMEOUT_MS
92
102
  ): ProgrammaticToolCallingJsonSchema {
@@ -99,6 +109,12 @@ export function createProgrammaticToolCallingSchema(
99
109
  minLength: 1,
100
110
  description: CODE_PARAM_DESCRIPTION,
101
111
  },
112
+ tool_manifest: {
113
+ type: 'array',
114
+ items: { type: 'string' },
115
+ uniqueItems: true,
116
+ description: TOOL_MANIFEST_DESCRIPTION,
117
+ },
102
118
  timeout: createCodeApiRunTimeoutSchema(maxRunTimeoutMs),
103
119
  },
104
120
  required: ['code'],
@@ -893,7 +909,7 @@ export function createProgrammaticToolCallingTool(
893
909
 
894
910
  return tool(
895
911
  async (rawParams, config) => {
896
- const params = rawParams as { code: string; timeout?: number };
912
+ const params = rawParams as ProgrammaticInvocationParams;
897
913
  const { code } = params;
898
914
  const timeout = clampCodeApiRunTimeoutMs(params.timeout, maxRunTimeoutMs);
899
915
 
@@ -906,11 +922,26 @@ export function createProgrammaticToolCallingTool(
906
922
  };
907
923
  const {
908
924
  toolMap,
909
- toolDefs,
925
+ disallowedToolDefs,
910
926
  session_id,
911
927
  _injected_files,
912
928
  _runtime_session_hint,
913
929
  } = toolCall;
930
+ const toolDefs = resolveProgrammaticToolDefinitions(
931
+ toolCall as typeof toolCall & { tools?: t.LCTool[] }
932
+ );
933
+
934
+ const programmaticToolName =
935
+ toolCall.programmaticToolName ??
936
+ (typeof toolCall.name === 'string' && toolCall.name !== ''
937
+ ? toolCall.name
938
+ : Constants.PROGRAMMATIC_TOOL_CALLING);
939
+ const effectiveTools = selectProgrammaticTools({
940
+ requestedToolNames: params.tool_manifest,
941
+ allowedToolDefs: toolDefs,
942
+ disallowedToolDefs,
943
+ programmaticToolName,
944
+ });
914
945
 
915
946
  if (toolMap == null || toolMap.size === 0) {
916
947
  throw new Error(
@@ -926,20 +957,23 @@ export function createProgrammaticToolCallingTool(
926
957
  );
927
958
  }
928
959
 
960
+ const effectiveToolMap = projectProgrammaticToolMap(
961
+ toolMap,
962
+ effectiveTools
963
+ );
964
+
929
965
  let roundTrip = 0;
930
966
 
931
967
  try {
932
968
  // ====================================================================
933
- // Phase 1: Filter tools and make initial request
969
+ // Phase 1: Send the validated tool manifest with the initial request
934
970
  // ====================================================================
935
971
 
936
- const effectiveTools = filterToolsByUsage(toolDefs, code, debug);
937
-
938
972
  if (debug) {
939
973
  // eslint-disable-next-line no-console
940
974
  console.log(
941
975
  `[PTC Debug] Sending ${effectiveTools.length} tools to API ` +
942
- `(filtered from ${toolDefs.length})`
976
+ `(selected from ${toolDefs.length})`
943
977
  );
944
978
  }
945
979
 
@@ -1015,7 +1049,7 @@ export function createProgrammaticToolCallingTool(
1015
1049
 
1016
1050
  const toolResults = await executeTools(
1017
1051
  response.tool_calls ?? [],
1018
- toolMap
1052
+ effectiveToolMap
1019
1053
  );
1020
1054
 
1021
1055
  response = await makeRequest(
@@ -62,7 +62,6 @@ import {
62
62
  SUBAGENT_PARENT_BATCH_CONFIG_KEY,
63
63
  SUBAGENT_REPLAY_CONTROLLER,
64
64
  } from '@/tools/subagent/SubagentReplay';
65
- import { attachRunStepResumeState } from '@/tools/runStepResume';
66
65
  import {
67
66
  INTENT_ARG,
68
67
  readOutcomeFields,
@@ -95,8 +94,14 @@ import {
95
94
  resolveLocalToolRegistry,
96
95
  resolveLocalExecutionTools,
97
96
  } from '@/tools/local';
97
+ import {
98
+ isToolDefinitionActive,
99
+ isProgrammaticControlTool,
100
+ resolveCallerCapabilityProjection,
101
+ } from '@/tools/CallerCapabilities';
98
102
  import { stripCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
99
103
  import { Constants, GraphEvents, CODE_EXECUTION_TOOLS } from '@/common';
104
+ import { attachRunStepResumeState } from '@/tools/runStepResume';
100
105
 
101
106
  /** Host-facing batch requests must not carry the batch's breaker scope —
102
107
  * hosts spread `configurable` into their own run configs. */
@@ -110,6 +115,7 @@ function stripRunBreakerScope(
110
115
  return rest;
111
116
  }
112
117
  import { convertInjectedMessages } from '@/messages/injected';
118
+ import { stampSyntheticProviderMessage } from '@/messages/provenance';
113
119
  import { safeDispatchCustomEvent } from '@/utils/events';
114
120
  import { RunnableCallable, composeAbortSignals } from '@/utils';
115
121
  import {
@@ -692,8 +698,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
692
698
  >();
693
699
  /** Tool registry for filtering (lazy computation of programmatic maps) */
694
700
  private toolRegistry?: t.LCToolRegistry;
695
- /** Cached programmatic tools (computed once on first PTC call) */
696
- private programmaticCache?: t.ProgrammaticCache;
701
+ /** Reads deferred-tool discovery state from the owning agent context. */
702
+ private getDiscoveredToolNames?: () => readonly string[];
697
703
  /** Reference to Graph's sessions map for automatic session injection */
698
704
  private sessions?: t.ToolSessionMap;
699
705
  /** Partition within `sessions` owned by this agent's execution profile. */
@@ -797,6 +803,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
797
803
  handleToolErrors,
798
804
  loadRuntimeTools,
799
805
  toolRegistry,
806
+ getDiscoveredToolNames,
800
807
  sessions,
801
808
  codeSessionKey,
802
809
  eventDrivenMode,
@@ -872,6 +879,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
872
879
  toolRegistry,
873
880
  toolExecution,
874
881
  });
882
+ this.getDiscoveredToolNames = getDiscoveredToolNames;
875
883
  this.sessions = sessions;
876
884
  this.codeSessionKey = codeSessionKey ?? Constants.EXECUTE_CODE;
877
885
  this.eventDrivenMode = eventDrivenMode ?? false;
@@ -986,6 +994,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
986
994
  const resolved = resolveLocalExecutionTools({
987
995
  toolMap: this.toolMap,
988
996
  toolExecution: this.toolExecution,
997
+ toolRegistry: this.toolRegistry,
989
998
  fileCheckpointer: this.fileCheckpointer,
990
999
  });
991
1000
 
@@ -1001,7 +1010,6 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1001
1010
  ...(this.directToolNames ?? new Set<string>()),
1002
1011
  ...resolved.directToolNames,
1003
1012
  ]);
1004
- this.programmaticCache = undefined;
1005
1013
  }
1006
1014
 
1007
1015
  /**
@@ -1097,30 +1105,30 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1097
1105
  this.settledInterruptingResults.clear();
1098
1106
  }
1099
1107
 
1100
- /**
1101
- * Returns cached programmatic tools, computing once on first access.
1102
- * Single iteration builds both toolMap and toolDefs simultaneously.
1103
- */
1104
- private getProgrammaticTools(): { toolMap: t.ToolMap; toolDefs: t.LCTool[] } {
1105
- if (this.programmaticCache) return this.programmaticCache;
1106
-
1108
+ /** Returns active tools projected by their effective caller capabilities. */
1109
+ private getProgrammaticTools(): t.ProgrammaticCache {
1107
1110
  const toolMap: t.ToolMap = new Map();
1108
- const toolDefs: t.LCTool[] = [];
1109
-
1110
- if (this.toolRegistry) {
1111
- for (const [name, toolDef] of this.toolRegistry) {
1112
- if (
1113
- (toolDef.allowed_callers ?? ['direct']).includes('code_execution')
1114
- ) {
1115
- toolDefs.push(toolDef);
1116
- const tool = this.toolMap.get(name);
1117
- if (tool) toolMap.set(name, tool);
1118
- }
1111
+ const discoveredToolNames = new Set(
1112
+ this.getDiscoveredToolNames?.() ?? []
1113
+ );
1114
+ const capabilities = resolveCallerCapabilityProjection(
1115
+ this.toolRegistry?.values() ?? [],
1116
+ (toolDef) => isToolDefinitionActive(toolDef, discoveredToolNames)
1117
+ );
1118
+ for (const toolDef of capabilities.codeExecutionTools) {
1119
+ const tool = this.toolMap.get(toolDef.name);
1120
+ if (tool != null) {
1121
+ toolMap.set(toolDef.name, tool);
1119
1122
  }
1120
1123
  }
1121
1124
 
1122
- this.programmaticCache = { toolMap, toolDefs };
1123
- return this.programmaticCache;
1125
+ return {
1126
+ toolMap,
1127
+ toolDefs: capabilities.codeExecutionTools,
1128
+ disallowedToolDefs: capabilities.directOnlyTools
1129
+ .filter((toolDef) => !isProgrammaticControlTool(toolDef.name))
1130
+ .map((toolDef) => ({ name: toolDef.name })),
1131
+ };
1124
1132
  }
1125
1133
 
1126
1134
  /**
@@ -1331,11 +1339,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1331
1339
  call.name === Constants.PROGRAMMATIC_TOOL_CALLING ||
1332
1340
  call.name === Constants.BASH_PROGRAMMATIC_TOOL_CALLING
1333
1341
  ) {
1334
- const { toolMap, toolDefs } = this.getProgrammaticTools();
1342
+ const { toolMap, toolDefs, disallowedToolDefs } =
1343
+ this.getProgrammaticTools();
1335
1344
  invokeParams = {
1336
1345
  ...invokeParams,
1337
1346
  toolMap,
1338
1347
  toolDefs,
1348
+ disallowedToolDefs,
1349
+ programmaticToolName: call.name,
1339
1350
  // Plumb the hook context into the programmatic-tool path so
1340
1351
  // inner tool calls made via the in-process bridge can run
1341
1352
  // through `PreToolUse` (deny / updatedInput) before reaching
@@ -3927,10 +3938,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3927
3938
  * a user message; `role` is metadata only.
3928
3939
  */
3929
3940
  injected.push(
3930
- new HumanMessage({
3931
- content: batchAdditionalContexts.join('\n\n'),
3932
- additional_kwargs: { role: 'system', source: 'hook' },
3933
- })
3941
+ stampSyntheticProviderMessage(
3942
+ new HumanMessage({
3943
+ content: batchAdditionalContexts.join('\n\n'),
3944
+ additional_kwargs: { role: 'system', source: 'hook' },
3945
+ })
3946
+ )
3934
3947
  );
3935
3948
  }
3936
3949
 
@@ -4423,13 +4436,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
4423
4436
  directAdditionalContexts.length > 0
4424
4437
  ? [
4425
4438
  sendOutput,
4426
- new HumanMessage({
4427
- content: directAdditionalContexts.join('\n\n'),
4428
- // Match the event-driven path's marker so hosts /
4429
- // model-side annotators treat this as system intent
4430
- // rather than ordinary user text. Codex P2 [46].
4431
- additional_kwargs: { role: 'system', source: 'hook' },
4432
- }),
4439
+ stampSyntheticProviderMessage(
4440
+ new HumanMessage({
4441
+ content: directAdditionalContexts.join('\n\n'),
4442
+ // Match the event-driven path's marker so hosts /
4443
+ // model-side annotators treat this as system intent
4444
+ // rather than ordinary user text. Codex P2 [46].
4445
+ additional_kwargs: { role: 'system', source: 'hook' },
4446
+ })
4447
+ ),
4433
4448
  ]
4434
4449
  : [sendOutput];
4435
4450
  await this.handleRunToolCompletions(
@@ -4477,7 +4492,6 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
4477
4492
  this.toolMap =
4478
4493
  toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
4479
4494
  this.applyToolExecutionOverrides();
4480
- this.programmaticCache = undefined; // Invalidate cache on toolMap change
4481
4495
  }
4482
4496
 
4483
4497
  const filteredCalls =
@@ -4721,14 +4735,16 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
4721
4735
  const directInjected: BaseMessage[] =
4722
4736
  directAdditionalContexts.length > 0
4723
4737
  ? [
4724
- new HumanMessage({
4725
- content: directAdditionalContexts.join('\n\n'),
4726
- // System-role metadata to match the event-driven
4727
- // path so policy/recovery guidance is treated
4728
- // consistently regardless of whether the tool ran
4729
- // direct or dispatched. Codex P2 [46].
4730
- additional_kwargs: { role: 'system', source: 'hook' },
4731
- }),
4738
+ stampSyntheticProviderMessage(
4739
+ new HumanMessage({
4740
+ content: directAdditionalContexts.join('\n\n'),
4741
+ // System-role metadata to match the event-driven
4742
+ // path so policy/recovery guidance is treated
4743
+ // consistently regardless of whether the tool ran
4744
+ // direct or dispatched. Codex P2 [46].
4745
+ additional_kwargs: { role: 'system', source: 'hook' },
4746
+ })
4747
+ ),
4732
4748
  ]
4733
4749
  : [];
4734
4750
  outputs = [
@@ -4782,13 +4798,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
4782
4798
  ...promotedPrefix,
4783
4799
  ...toolOutputs,
4784
4800
  ...invalidCallResults,
4785
- new HumanMessage({
4786
- content: directAdditionalContexts.join('\n\n'),
4787
- // Same system-role marker the event-driven path
4788
- // uses so direct vs dispatched is invisible to
4789
- // downstream consumers. Codex P2 [46].
4790
- additional_kwargs: { role: 'system', source: 'hook' },
4791
- }),
4801
+ stampSyntheticProviderMessage(
4802
+ new HumanMessage({
4803
+ content: directAdditionalContexts.join('\n\n'),
4804
+ // Same system-role marker the event-driven path
4805
+ // uses so direct vs dispatched is invisible to
4806
+ // downstream consumers. Codex P2 [46].
4807
+ additional_kwargs: { role: 'system', source: 'hook' },
4808
+ })
4809
+ ),
4792
4810
  ]
4793
4811
  : [...promotedPrefix, ...toolOutputs, ...invalidCallResults];
4794
4812
  }