@librechat/agents 3.6.9 → 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 (141) 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/utils/toolContent.cjs +2 -2
  49. package/dist/cjs/utils/toolContent.cjs.map +1 -1
  50. package/dist/esm/agents/AgentContext.mjs +46 -39
  51. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  52. package/dist/esm/agents/projection.mjs +2 -2
  53. package/dist/esm/agents/projection.mjs.map +1 -1
  54. package/dist/esm/graphs/Graph.mjs +30 -20
  55. package/dist/esm/graphs/Graph.mjs.map +1 -1
  56. package/dist/esm/graphs/MultiAgentGraph.mjs +87 -16
  57. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  58. package/dist/esm/llm/openai/index.mjs +10 -0
  59. package/dist/esm/llm/openai/index.mjs.map +1 -1
  60. package/dist/esm/main.mjs +4 -3
  61. package/dist/esm/messages/alternation.mjs +125 -20
  62. package/dist/esm/messages/alternation.mjs.map +1 -1
  63. package/dist/esm/messages/core.mjs +196 -19
  64. package/dist/esm/messages/core.mjs.map +1 -1
  65. package/dist/esm/messages/format.mjs +488 -119
  66. package/dist/esm/messages/format.mjs.map +1 -1
  67. package/dist/esm/messages/handoffCue.mjs +3 -2
  68. package/dist/esm/messages/handoffCue.mjs.map +1 -1
  69. package/dist/esm/messages/index.mjs +1 -0
  70. package/dist/esm/messages/injected.mjs +5 -2
  71. package/dist/esm/messages/injected.mjs.map +1 -1
  72. package/dist/esm/messages/provenance.mjs +401 -0
  73. package/dist/esm/messages/provenance.mjs.map +1 -0
  74. package/dist/esm/messages/prune.mjs +18 -13
  75. package/dist/esm/messages/prune.mjs.map +1 -1
  76. package/dist/esm/messages/toolResultTypes.mjs +657 -0
  77. package/dist/esm/messages/toolResultTypes.mjs.map +1 -0
  78. package/dist/esm/run.mjs +7 -4
  79. package/dist/esm/run.mjs.map +1 -1
  80. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +21 -4
  81. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  82. package/dist/esm/tools/CallerCapabilities.mjs +45 -0
  83. package/dist/esm/tools/CallerCapabilities.mjs.map +1 -0
  84. package/dist/esm/tools/ProgrammaticCallerPolicy.mjs +32 -0
  85. package/dist/esm/tools/ProgrammaticCallerPolicy.mjs.map +1 -0
  86. package/dist/esm/tools/ProgrammaticToolCalling.mjs +21 -4
  87. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  88. package/dist/esm/tools/ToolNode.mjs +25 -24
  89. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  90. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +14 -6
  91. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  92. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +15 -6
  93. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  94. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +47 -12
  95. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  96. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  97. package/dist/esm/utils/toolContent.mjs +2 -2
  98. package/dist/esm/utils/toolContent.mjs.map +1 -1
  99. package/dist/types/agents/AgentContext.d.ts +8 -13
  100. package/dist/types/agents/projection.d.ts +3 -1
  101. package/dist/types/graphs/Graph.d.ts +1 -1
  102. package/dist/types/llm/openai/index.d.ts +20 -0
  103. package/dist/types/messages/format.d.ts +5 -0
  104. package/dist/types/messages/index.d.ts +1 -0
  105. package/dist/types/messages/provenance.d.ts +85 -0
  106. package/dist/types/messages/toolResultTypes.d.ts +33 -0
  107. package/dist/types/tools/CallerCapabilities.d.ts +12 -0
  108. package/dist/types/tools/ProgrammaticCallerPolicy.d.ts +20 -0
  109. package/dist/types/tools/ToolNode.d.ts +4 -7
  110. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +4 -0
  111. package/dist/types/tools/ptcTimeout.d.ts +8 -0
  112. package/dist/types/types/graph.d.ts +3 -1
  113. package/dist/types/types/tools.d.ts +13 -2
  114. package/package.json +1 -1
  115. package/src/agents/AgentContext.ts +109 -67
  116. package/src/agents/projection.ts +9 -1
  117. package/src/graphs/Graph.ts +56 -23
  118. package/src/graphs/MultiAgentGraph.ts +175 -33
  119. package/src/llm/openai/index.ts +43 -0
  120. package/src/messages/alternation.ts +217 -34
  121. package/src/messages/core.ts +358 -38
  122. package/src/messages/format.ts +958 -137
  123. package/src/messages/handoffCue.ts +7 -4
  124. package/src/messages/index.ts +1 -0
  125. package/src/messages/injected.ts +9 -6
  126. package/src/messages/provenance.ts +897 -0
  127. package/src/messages/prune.ts +28 -10
  128. package/src/messages/toolResultTypes.ts +1174 -0
  129. package/src/run.ts +9 -4
  130. package/src/tools/BashProgrammaticToolCalling.ts +46 -12
  131. package/src/tools/CallerCapabilities.ts +78 -0
  132. package/src/tools/ProgrammaticCallerPolicy.ts +97 -0
  133. package/src/tools/ProgrammaticToolCalling.ts +41 -7
  134. package/src/tools/ToolNode.ts +70 -52
  135. package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +24 -26
  136. package/src/tools/local/LocalProgrammaticToolCalling.ts +28 -23
  137. package/src/tools/local/resolveLocalExecutionTools.ts +132 -13
  138. package/src/tools/ptcTimeout.ts +6 -0
  139. package/src/types/graph.ts +3 -0
  140. package/src/types/tools.ts +14 -3
  141. package/src/utils/toolContent.ts +2 -2
@@ -0,0 +1,20 @@
1
+ import type * as t from '@/types';
2
+ export type ProgrammaticInvocationParams = {
3
+ code: string;
4
+ tool_manifest?: string[];
5
+ timeout?: number;
6
+ lang?: string;
7
+ runtime?: string;
8
+ language?: string;
9
+ };
10
+ export declare function resolveProgrammaticToolDefinitions(context: Partial<t.ProgrammaticCache> & {
11
+ tools?: t.LCTool[];
12
+ }): t.LCTool[] | undefined;
13
+ export declare function projectProgrammaticToolMap(toolMap: t.ToolMap, selectedToolDefs: readonly t.LCTool[]): t.ToolMap;
14
+ export declare function assertDisallowedToolUsage(disallowedNames: ReadonlySet<string>, programmaticToolName: string): void;
15
+ export declare function selectProgrammaticTools(args: {
16
+ requestedToolNames?: string[];
17
+ allowedToolDefs?: t.LCTool[];
18
+ disallowedToolDefs?: t.LCTool[];
19
+ programmaticToolName: string;
20
+ }): t.LCTool[];
@@ -122,8 +122,8 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
122
122
  private settledInterruptingResults;
123
123
  /** Tool registry for filtering (lazy computation of programmatic maps) */
124
124
  private toolRegistry?;
125
- /** Cached programmatic tools (computed once on first PTC call) */
126
- private programmaticCache?;
125
+ /** Reads deferred-tool discovery state from the owning agent context. */
126
+ private getDiscoveredToolNames?;
127
127
  /** Reference to Graph's sessions map for automatic session injection */
128
128
  private sessions?;
129
129
  /** Partition within `sessions` owned by this agent's execution profile. */
@@ -213,7 +213,7 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
213
213
  * other's in-flight state.
214
214
  */
215
215
  private anonBatchCounter;
216
- constructor({ tools, toolMap, name, tags, trace, runLangfuse, agentLangfuse, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, codeSessionKey, eventDrivenMode, eagerEventToolExecution, eagerEventToolExecutions, eagerEventToolUsageCount, eagerEventToolSuppressions, agentId, executingAgentId, directToolNames, interruptingToolNames, codeSessionToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer, getBreakerSignal, getRunScope, restoreRunStepResumeState, createRunStepResumeState, }: t.ToolNodeConstructorParams);
216
+ constructor({ tools, toolMap, name, tags, trace, runLangfuse, agentLangfuse, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, getDiscoveredToolNames, sessions, codeSessionKey, eventDrivenMode, eagerEventToolExecution, eagerEventToolExecutions, eagerEventToolUsageCount, eagerEventToolSuppressions, agentId, executingAgentId, directToolNames, interruptingToolNames, codeSessionToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer, getBreakerSignal, getRunScope, restoreRunStepResumeState, createRunStepResumeState, }: t.ToolNodeConstructorParams);
217
217
  invoke(input: any, options?: Partial<RunnableConfig>): Promise<any>;
218
218
  /**
219
219
  * Returns the run-scoped tool output registry, or `undefined` when
@@ -257,10 +257,7 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
257
257
  * they reuse a ToolNode across batches outside of a Graph.
258
258
  */
259
259
  clearDirectPathTurns(): void;
260
- /**
261
- * Returns cached programmatic tools, computing once on first access.
262
- * Single iteration builds both toolMap and toolDefs simultaneously.
263
- */
260
+ /** Returns active tools projected by their effective caller capabilities. */
264
261
  private getProgrammaticTools;
265
262
  /**
266
263
  * Returns a snapshot of the current tool usage counts.
@@ -14,9 +14,12 @@ type ResolveLocalToolsResult = {
14
14
  */
15
15
  fileCheckpointer?: t.LocalFileCheckpointer;
16
16
  };
17
+ export declare function isProgrammaticRunnerAutoBound(name: string, config?: t.ToolExecutionConfig): boolean;
17
18
  export declare function resolveLocalToolsForBinding(args: {
18
19
  tools?: t.GraphTools;
19
20
  toolExecution?: t.ToolExecutionConfig;
21
+ toolRegistry?: t.LCToolRegistry;
22
+ discoveredToolNames?: ReadonlySet<string>;
20
23
  }): t.GraphTools | undefined;
21
24
  export declare function resolveLocalToolRegistry(args: {
22
25
  toolRegistry?: t.LCToolRegistry;
@@ -25,6 +28,7 @@ export declare function resolveLocalToolRegistry(args: {
25
28
  export declare function resolveLocalExecutionTools(args: {
26
29
  toolMap: t.ToolMap;
27
30
  toolExecution?: t.ToolExecutionConfig;
31
+ toolRegistry?: t.LCToolRegistry;
28
32
  /**
29
33
  * Caller-provided checkpointer that overrides the bundle's
30
34
  * auto-created one. The Graph layer threads a single per-Run
@@ -18,6 +18,14 @@ export type ProgrammaticToolCallingJsonSchema = {
18
18
  minLength: number;
19
19
  description: string;
20
20
  };
21
+ tool_manifest: {
22
+ type: 'array';
23
+ items: {
24
+ type: 'string';
25
+ };
26
+ uniqueItems: true;
27
+ description: string;
28
+ };
21
29
  timeout: TimeoutSchema;
22
30
  };
23
31
  required: readonly ['code'];
@@ -6,7 +6,7 @@ import type { ChatGenerationChunk } from '@langchain/core/outputs';
6
6
  import type { GoogleAIToolType } from '@langchain/google-common';
7
7
  import type { SummarizationNodeInput, SummarizeCompleteEvent, SummarizationConfig, SummarizeStartEvent, SummarizeDeltaEvent } from '@/types/summarize';
8
8
  import type { RunStep, RunStepDeltaEvent, RunStepResumeState, RunStepClosedEvent, MessageDeltaEvent, ReasoningDeltaEvent } from '@/types/stream';
9
- import type { ToolMap, ToolSessionMap, ToolEndEvent, GenericTool, LCTool, ToolExecuteBatchRequest } from '@/types/tools';
9
+ import type { ToolMap, ToolSessionMap, ToolEndEvent, GenericTool, LCTool, ToolExecutionConfig, ToolExecuteBatchRequest } from '@/types/tools';
10
10
  import type { TokenCounter, StreamLimits, StreamPreemption, TokenBudgetBreakdown } from '@/types/run';
11
11
  import type { Providers, Callback, GraphNodeKeys } from '@/common';
12
12
  import type { SubagentTaskConfig } from '@/types/subagentTasks';
@@ -225,6 +225,8 @@ export type StandardGraphInput = {
225
225
  runId?: string;
226
226
  signal?: AbortSignal;
227
227
  agents: AgentInputs[];
228
+ /** Execution backend used to resolve the effective tool registry. */
229
+ toolExecution?: ToolExecutionConfig;
228
230
  langfuse?: LangfuseConfig;
229
231
  tokenCounter?: TokenCounter;
230
232
  indexTokenCountMap?: Record<string, number>;
@@ -1,9 +1,9 @@
1
- import type { StructuredToolInterface } from '@langchain/core/tools';
2
1
  import type { RunnableConfig, RunnableToolLike } from '@langchain/core/runnables';
2
+ import type { StructuredToolInterface } from '@langchain/core/tools';
3
3
  import type { ToolCall } from '@langchain/core/messages/tool';
4
+ import type { MessageContentComplex, RunStepResumeState, ToolErrorData } from './stream';
4
5
  import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
5
6
  import type { RunBreakerScope } from '@/llm/streamLimits';
6
- import type { MessageContentComplex, RunStepResumeState, ToolErrorData } from './stream';
7
7
  import type { HumanInTheLoopConfig } from './hitl';
8
8
  import type { LangfuseConfig } from './graph';
9
9
  import type { HookRegistry } from '@/hooks';
@@ -116,6 +116,8 @@ export type ToolNodeOptions = {
116
116
  errorHandler?: (data: ToolErrorData, metadata?: Record<string, unknown>) => Promise<boolean | void>;
117
117
  /** Tool registry for lazy computation of programmatic tools and tool search */
118
118
  toolRegistry?: LCToolRegistry;
119
+ /** Returns the owning agent's currently discovered deferred tool names. */
120
+ getDiscoveredToolNames?: () => readonly string[];
119
121
  /** Reference to Graph's sessions map for automatic session injection */
120
122
  sessions?: ToolSessionMap;
121
123
  /** Partition within `sessions` used by this agent's code tools. */
@@ -1084,6 +1086,15 @@ export type ToolExecutionConfig = {
1084
1086
  export type ProgrammaticCache = {
1085
1087
  toolMap: ToolMap;
1086
1088
  toolDefs: LCTool[];
1089
+ /** Actual outer runner name used for caller-policy diagnostics. */
1090
+ programmaticToolName?: string;
1091
+ /**
1092
+ * Known tools that cannot be invoked from programmatic execution. Kept
1093
+ * separate from `toolDefs` so the Programmatic Tool Manifest can be
1094
+ * validated before the execution runtime starts without exposing their
1095
+ * schemas to that runtime.
1096
+ */
1097
+ disallowedToolDefs?: LCTool[];
1087
1098
  /**
1088
1099
  * Hook context plumbed through by ToolNode for the local
1089
1100
  * programmatic-tool path so the in-process bridge can run
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.6.9",
3
+ "version": "3.6.10",
4
4
  "reova": {
5
5
  "enabled": true,
6
6
  "endpoint": "https://telemetry.reo.dev/data"
@@ -32,6 +32,16 @@ import {
32
32
  Constants,
33
33
  Providers,
34
34
  } from '@/common';
35
+ import {
36
+ isProgrammaticRunnerAutoBound,
37
+ resolveLocalToolRegistry,
38
+ } from '@/tools/local/resolveLocalExecutionTools';
39
+ import {
40
+ allowsToolCaller,
41
+ isToolDefinitionActive,
42
+ isProgrammaticControlTool,
43
+ resolveCallerCapabilityProjection,
44
+ } from '@/tools/CallerCapabilities';
35
45
  import { createSchemaOnlyTools } from '@/tools/schema';
36
46
  import { apportionTokenCounts } from '@/utils/tokens';
37
47
  import { isThinkingEnabled } from '@/llm/request';
@@ -59,7 +69,8 @@ export class AgentContext {
59
69
  static fromConfig(
60
70
  agentConfig: t.AgentInputs,
61
71
  tokenCounter?: t.TokenCounter,
62
- indexTokenCountMap?: Record<string, number>
72
+ indexTokenCountMap?: Record<string, number>,
73
+ toolExecution?: t.ToolExecutionConfig
63
74
  ): AgentContext {
64
75
  const {
65
76
  agentId,
@@ -103,6 +114,7 @@ export class AgentContext {
103
114
  tools,
104
115
  toolMap,
105
116
  toolRegistry,
117
+ toolExecution,
106
118
  toolDefinitions,
107
119
  instructions,
108
120
  additionalInstructions: additional_instructions,
@@ -269,6 +281,8 @@ export class AgentContext {
269
281
  * Used for tool search and programmatic tool calling.
270
282
  */
271
283
  toolRegistry?: t.LCToolRegistry;
284
+ /** Run-scoped backend used to identify auto-bound programmatic runners. */
285
+ private toolExecution?: t.ToolExecutionConfig;
272
286
  /**
273
287
  * Serializable tool definitions for event-driven execution.
274
288
  * When provided, ToolNode operates in event-driven mode.
@@ -385,6 +399,7 @@ export class AgentContext {
385
399
  tools,
386
400
  toolMap,
387
401
  toolRegistry,
402
+ toolExecution,
388
403
  toolDefinitions,
389
404
  instructions,
390
405
  additionalInstructions,
@@ -410,6 +425,7 @@ export class AgentContext {
410
425
  tools?: t.GraphTools;
411
426
  toolMap?: t.ToolMap;
412
427
  toolRegistry?: t.LCToolRegistry;
428
+ toolExecution?: t.ToolExecutionConfig;
413
429
  toolDefinitions?: t.LCTool[];
414
430
  instructions?: string;
415
431
  additionalInstructions?: string;
@@ -434,7 +450,11 @@ export class AgentContext {
434
450
  this.tokenCounter = tokenCounter;
435
451
  this.tools = tools;
436
452
  this.toolMap = toolMap;
437
- this.toolRegistry = toolRegistry;
453
+ this.toolRegistry = resolveLocalToolRegistry({
454
+ toolRegistry,
455
+ toolExecution,
456
+ });
457
+ this.toolExecution = toolExecution;
438
458
  this.toolDefinitions = toolDefinitions;
439
459
  this.instructions = instructions;
440
460
  this.additionalInstructions = additionalInstructions;
@@ -461,37 +481,46 @@ export class AgentContext {
461
481
  }
462
482
  }
463
483
 
464
- /**
465
- * Builds instructions text for tools that are ONLY callable via programmatic code execution.
466
- * These tools cannot be called directly by the LLM but are available through the
467
- * configured programmatic tool.
468
- *
469
- * Includes:
470
- * - Code_execution-only tools that are NOT deferred
471
- * - Code_execution-only tools that ARE deferred but have been discovered via tool search
472
- */
484
+ /** Builds the caller boundary and schemas for programmatic-only tools. */
473
485
  private buildProgrammaticOnlyToolsInstructions(): string {
474
486
  if (!this.toolRegistry) return '';
475
487
 
476
- const programmaticOnlyTools: t.LCTool[] = [];
477
- for (const [name, toolDef] of this.toolRegistry) {
478
- const allowedCallers = toolDef.allowed_callers ?? ['direct'];
479
- const isCodeExecutionOnly =
480
- allowedCallers.includes('code_execution') &&
481
- !allowedCallers.includes('direct');
482
-
483
- if (!isCodeExecutionOnly) continue;
484
-
485
- const isDeferred = toolDef.defer_loading === true;
486
- const isDiscovered = this.discoveredToolNames.has(name);
487
- if (!isDeferred || isDiscovered) {
488
- programmaticOnlyTools.push(toolDef);
489
- }
488
+ const capabilities = resolveCallerCapabilityProjection(
489
+ this.toolRegistry.values(),
490
+ (toolDef) => isToolDefinitionActive(toolDef, this.discoveredToolNames)
491
+ );
492
+ const programmaticOnlyTools = capabilities.codeExecutionOnlyTools;
493
+ const programmaticToolNames = capabilities.codeExecutionTools.map(
494
+ (toolDef) => toolDef.name
495
+ );
496
+ const directOnlyToolNames = capabilities.directOnlyTools
497
+ .map((toolDef) => toolDef.name)
498
+ .filter((name) => !isProgrammaticControlTool(name));
499
+
500
+ const programmaticTools = this.getProgrammaticToolInstructionTargets();
501
+ if (programmaticTools.length === 0) return '';
502
+ const programmaticRunnerNames = programmaticTools
503
+ .map((tool) => `\`${tool.name}\``)
504
+ .join(' or ');
505
+ const quotedProgrammaticNames =
506
+ programmaticToolNames.length > 0
507
+ ? programmaticToolNames.map((name) => `\`${name}\``).join(', ')
508
+ : 'none';
509
+ const directOnlyBoundary =
510
+ directOnlyToolNames.length > 0
511
+ ? `\nCall these tools directly; never list them in the \`tool_manifest\` or reference them inside ${programmaticRunnerNames}: ${directOnlyToolNames
512
+ .map((name) => `\`${name}\``)
513
+ .join(', ')}. Every ${programmaticRunnerNames} call must include a \`tool_manifest\` containing the exact registered names used by its code; the manifest is validated before execution starts.`
514
+ : '';
515
+ const boundary =
516
+ '\n\n## Programmatic Tool Calling\n\n' +
517
+ `Only these tools may be invoked inside ${programmaticRunnerNames}: ${quotedProgrammaticNames}.` +
518
+ directOnlyBoundary;
519
+
520
+ if (programmaticOnlyTools.length === 0) {
521
+ return boundary;
490
522
  }
491
523
 
492
- if (programmaticOnlyTools.length === 0) return '';
493
-
494
- const programmaticTool = this.getProgrammaticToolInstructionTarget();
495
524
  const toolDescriptions = programmaticOnlyTools
496
525
  .map((tool) => {
497
526
  let desc = `- **${tool.name}**`;
@@ -506,41 +535,64 @@ export class AgentContext {
506
535
  .join('\n\n');
507
536
 
508
537
  return (
509
- '\n\n## Programmatic-Only Tools\n\n' +
510
- `The following tools are available exclusively through the \`${programmaticTool.name}\` tool. ` +
511
- `You cannot call these tools directly; instead, use \`${programmaticTool.name}\` with ${programmaticTool.language} code that invokes them.\n\n` +
538
+ boundary +
539
+ '\n\n### Programmatic-Only Tools\n\n' +
540
+ `The following tools are available exclusively through ${programmaticRunnerNames}. ` +
541
+ `You cannot call these tools directly; instead, ${programmaticTools
542
+ .map(
543
+ (tool) =>
544
+ `use \`${tool.name}\` with ${tool.codeGuidance} that invokes them`
545
+ )
546
+ .join(', or ')}.\n\n` +
512
547
  toolDescriptions
513
548
  );
514
549
  }
515
550
 
516
- private getProgrammaticToolInstructionTarget(): {
551
+ private getProgrammaticToolInstructionTargets(): Array<{
517
552
  name: string;
518
- language: 'bash' | 'Python';
519
- } {
520
- if (this.hasAvailableTool(Constants.BASH_PROGRAMMATIC_TOOL_CALLING)) {
521
- return {
553
+ codeGuidance: string;
554
+ }> {
555
+ const targets: Array<{ name: string; codeGuidance: string }> = [];
556
+ if (
557
+ this.hasBoundTool(Constants.BASH_PROGRAMMATIC_TOOL_CALLING) ||
558
+ isProgrammaticRunnerAutoBound(
559
+ Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
560
+ this.toolExecution
561
+ )
562
+ ) {
563
+ targets.push({
522
564
  name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
523
- language: 'bash',
524
- };
565
+ codeGuidance: 'Bash code',
566
+ });
525
567
  }
526
568
 
527
- if (this.hasAvailableTool(Constants.PROGRAMMATIC_TOOL_CALLING)) {
528
- return { name: Constants.PROGRAMMATIC_TOOL_CALLING, language: 'Python' };
569
+ if (
570
+ this.hasBoundTool(Constants.PROGRAMMATIC_TOOL_CALLING) ||
571
+ isProgrammaticRunnerAutoBound(
572
+ Constants.PROGRAMMATIC_TOOL_CALLING,
573
+ this.toolExecution
574
+ )
575
+ ) {
576
+ const localDefault =
577
+ this.toolExecution?.engine === 'local' ||
578
+ this.toolExecution?.engine === 'cloudflare-sandbox';
579
+ targets.push({
580
+ name: Constants.PROGRAMMATIC_TOOL_CALLING,
581
+ codeGuidance: localDefault
582
+ ? 'Bash code by default, or set `lang: "py"` to use Python code'
583
+ : 'Python code',
584
+ });
529
585
  }
530
586
 
531
- return { name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING, language: 'bash' };
587
+ return targets;
532
588
  }
533
589
 
534
- private hasAvailableTool(name: string): boolean {
535
- if (this.toolDefinitions?.some((tool) => tool.name === name) === true)
536
- return true;
537
- if (
538
- this.tools?.some((tool) => 'name' in tool && tool.name === name) === true
539
- ) {
540
- return true;
541
- }
542
- if (this.toolMap?.has(name) === true) return true;
543
- return this.toolRegistry?.has(name) === true;
590
+ private hasBoundTool(name: string): boolean {
591
+ return (
592
+ this.getToolsForBinding()?.some(
593
+ (tool) => 'name' in tool && tool.name === name
594
+ ) === true
595
+ );
544
596
  }
545
597
 
546
598
  /**
@@ -1083,15 +1135,10 @@ export class AgentContext {
1083
1135
  * alone left programmatic-only definitions counted in
1084
1136
  * `toolSchemaTokens` even though they were never bound.
1085
1137
  */
1086
- return this.toolDefinitions.filter((def) => {
1087
- const allowedCallers = def.allowed_callers ?? ['direct'];
1088
- if (!allowedCallers.includes('direct')) {
1089
- return false;
1090
- }
1091
- return (
1092
- def.defer_loading !== true || this.discoveredToolNames.has(def.name)
1093
- );
1094
- });
1138
+ return resolveCallerCapabilityProjection(
1139
+ this.toolDefinitions,
1140
+ (toolDef) => isToolDefinitionActive(toolDef, this.discoveredToolNames)
1141
+ ).directTools;
1095
1142
  }
1096
1143
 
1097
1144
  /**
@@ -1807,14 +1854,9 @@ export class AgentContext {
1807
1854
  return true;
1808
1855
  }
1809
1856
 
1810
- if (this.discoveredToolNames.has(tool.name)) {
1811
- const allowedCallers = toolDef.allowed_callers ?? ['direct'];
1812
- return allowedCallers.includes('direct');
1813
- }
1814
-
1815
- const allowedCallers = toolDef.allowed_callers ?? ['direct'];
1816
1857
  return (
1817
- allowedCallers.includes('direct') && toolDef.defer_loading !== true
1858
+ allowsToolCaller(toolDef, 'direct') &&
1859
+ isToolDefinitionActive(toolDef, this.discoveredToolNames)
1818
1860
  );
1819
1861
  });
1820
1862
  }
@@ -13,6 +13,8 @@ export interface ProjectAgentContextUsageParams {
13
13
  indexTokenCountMap?: Record<string, number>;
14
14
  /** Provider-calibrated ratio from a prior snapshot, applied as a static seed. */
15
15
  calibrationRatio?: number;
16
+ /** Execution backend used to synthesize the live effective tool registry. */
17
+ toolExecution?: t.ToolExecutionConfig;
16
18
  runId?: string;
17
19
  agentId?: string;
18
20
  }
@@ -32,10 +34,16 @@ export async function projectAgentContextUsage({
32
34
  tokenCounter,
33
35
  indexTokenCountMap,
34
36
  calibrationRatio,
37
+ toolExecution,
35
38
  runId,
36
39
  agentId,
37
40
  }: ProjectAgentContextUsageParams): Promise<t.ContextUsageEvent | null> {
38
- const context = AgentContext.fromConfig(agent, tokenCounter, indexTokenCountMap);
41
+ const context = AgentContext.fromConfig(
42
+ agent,
43
+ tokenCounter,
44
+ indexTokenCountMap,
45
+ toolExecution
46
+ );
39
47
  await context.tokenCalculationPromise;
40
48
  return context.projectContextUsage(messages, {
41
49
  runId,
@@ -34,6 +34,7 @@ import type {
34
34
  import type {
35
35
  GraphFactory,
36
36
  GraphFactoryDependencies,
37
+ GraphFactoryRequest,
37
38
  } from '@/graphs/graphFactory';
38
39
  import type { OverflowRecoveryPlan } from '@/llm/contextOverflowRecovery';
39
40
  import type { FallbackErrorContext } from '@/llm/invoke';
@@ -61,6 +62,7 @@ import {
61
62
  resolveBedrockPromptCacheTtl,
62
63
  supportsBedrockToolCache,
63
64
  isSyntheticProviderContextMessage,
65
+ compactSyntheticProviderContextMessage,
64
66
  getMessageId,
65
67
  getMessageCreationContentMetadata,
66
68
  splitAssistantTextContentByPhase,
@@ -73,6 +75,7 @@ import {
73
75
  strictAlternationProviders,
74
76
  appendPredecessorHandoffCue,
75
77
  removePredecessorHandoffCue,
78
+ stampSyntheticProviderMessage,
76
79
  } from '@/messages';
77
80
  import {
78
81
  Constants,
@@ -133,11 +136,6 @@ import {
133
136
  resolveLangfuseConfig,
134
137
  resolveToolOutputTracingConfig,
135
138
  } from '@/langfuseConfig';
136
- import {
137
- compactToolContent,
138
- getToolContentCharLength,
139
- serializeToolContentBounded,
140
- } from '@/utils/toolContent';
141
139
  import {
142
140
  annotateMessagesForLLM,
143
141
  ToolOutputReferenceRegistry,
@@ -146,6 +144,10 @@ import {
146
144
  resolveLangfuseRuntimeScope,
147
145
  withLangfuseRuntimeScope,
148
146
  } from '@/langfuseRuntimeScope';
147
+ import {
148
+ getToolContentCharLength,
149
+ serializeToolContentBounded,
150
+ } from '@/utils/toolContent';
149
151
  import {
150
152
  appendCallbacks,
151
153
  findCallback,
@@ -412,7 +414,9 @@ function getCurrentStepIds({
412
414
  if (stepKey !== baseStepKey && !stepKey.startsWith(`${baseStepKey}_`)) {
413
415
  continue;
414
416
  }
415
- currentStepIds.push(...stepIds);
417
+ for (const stepId of stepIds) {
418
+ currentStepIds.push(stepId);
419
+ }
416
420
  }
417
421
  return currentStepIds;
418
422
  }
@@ -1457,6 +1461,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1457
1461
  subagentExecutionContext,
1458
1462
  preemption,
1459
1463
  streamLimits,
1464
+ toolExecution,
1460
1465
  }: t.StandardGraphInput,
1461
1466
  dependencies?: GraphFactoryDependencies
1462
1467
  ) {
@@ -1482,6 +1487,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1482
1487
  this.subagentExecutionContext = subagentExecutionContext;
1483
1488
  this.preemption = preemption;
1484
1489
  this.streamLimits = resolveStreamLimits(streamLimits);
1490
+ this.toolExecution = toolExecution;
1485
1491
 
1486
1492
  if (agents.length === 0) {
1487
1493
  throw new Error('At least one agent configuration is required');
@@ -1491,7 +1497,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1491
1497
  const agentContext = AgentContext.fromConfig(
1492
1498
  agentConfig,
1493
1499
  tokenCounter,
1494
- indexTokenCountMap
1500
+ indexTokenCountMap,
1501
+ toolExecution
1495
1502
  );
1496
1503
  if (calibrationRatio != null && calibrationRatio > 0) {
1497
1504
  agentContext.calibrationRatio = calibrationRatio;
@@ -2557,6 +2564,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2557
2564
  executingAgentId: agentContext?.agentId,
2558
2565
  toolCallStepIds: this.toolCallStepIds,
2559
2566
  toolRegistry: agentContext?.toolRegistry,
2567
+ getDiscoveredToolNames: (): readonly string[] =>
2568
+ agentContext?.getDiscoveredTools() ?? [],
2560
2569
  hookRegistry: this.hookRegistry,
2561
2570
  humanInTheLoop: this.humanInTheLoop,
2562
2571
  eagerEventToolExecution: this.eagerEventToolExecution,
@@ -2634,6 +2643,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2634
2643
  errorHandler: (data, metadata): Promise<boolean> =>
2635
2644
  StandardGraph.handleToolCallErrorStatic(this, data, metadata),
2636
2645
  toolRegistry: agentContext?.toolRegistry,
2646
+ getDiscoveredToolNames: (): readonly string[] =>
2647
+ agentContext?.getDiscoveredTools() ?? [],
2637
2648
  sessions: this.sessions,
2638
2649
  codeSessionKey: agentContext?.codeSessionKey,
2639
2650
  toolExecution: this.toolExecution,
@@ -2839,6 +2850,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2839
2850
  const rawToolsForBinding = resolveLocalToolsForBinding({
2840
2851
  tools: agentContext.getToolsForBinding(),
2841
2852
  toolExecution: this.toolExecution,
2853
+ toolRegistry: agentContext.toolRegistry,
2854
+ discoveredToolNames: new Set(agentContext.getDiscoveredTools()),
2842
2855
  });
2843
2856
 
2844
2857
  /**
@@ -3553,17 +3566,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3553
3566
  const buildCandidate = (scale: number): BaseMessage[] => {
3554
3567
  const compacted = [...candidate];
3555
3568
  for (const { index, message, chars } of synthetic) {
3556
- const content = compactToolContent(
3557
- message.content,
3569
+ compacted[index] = compactSyntheticProviderContextMessage(
3570
+ message,
3558
3571
  Math.floor(chars * scale)
3559
- ).content;
3560
- compacted[index] = new HumanMessage({
3561
- content,
3562
- id: message.id,
3563
- name: message.name,
3564
- additional_kwargs: message.additional_kwargs,
3565
- response_metadata: message.response_metadata,
3566
- });
3572
+ );
3567
3573
  }
3568
3574
  return compacted;
3569
3575
  };
@@ -4725,15 +4731,26 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4725
4731
  );
4726
4732
  if (contexts.length > 0) {
4727
4733
  injected.push(
4728
- new HumanMessage({
4729
- content: contexts.join('\n\n'),
4730
- additional_kwargs: { role: 'system', isMeta: true, source: 'hook' },
4731
- })
4734
+ stampSyntheticProviderMessage(
4735
+ new HumanMessage({
4736
+ content: contexts.join('\n\n'),
4737
+ additional_kwargs: {
4738
+ role: 'system',
4739
+ isMeta: true,
4740
+ source: 'hook',
4741
+ },
4742
+ })
4743
+ )
4732
4744
  );
4733
4745
  }
4734
4746
  if (result.injectedMessages.length > 0) {
4735
4747
  try {
4736
- injected.push(...convertInjectedMessages(result.injectedMessages));
4748
+ const convertedMessages = convertInjectedMessages(
4749
+ result.injectedMessages
4750
+ );
4751
+ for (const convertedMessage of convertedMessages) {
4752
+ injected.push(convertedMessage);
4753
+ }
4737
4754
  } catch (e) {
4738
4755
  console.warn(
4739
4756
  '[StandardGraph] Failed to convert PreemptBoundary injectedMessages:',
@@ -4805,7 +4822,23 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4805
4822
  };
4806
4823
  const checkpointer = this.compileOptions?.checkpointer;
4807
4824
  return (request): StandardGraph => {
4808
- const childGraph = graphFactory(request);
4825
+ const configuredRequest: GraphFactoryRequest =
4826
+ request.kind === 'multi-agent'
4827
+ ? {
4828
+ kind: 'multi-agent',
4829
+ input: {
4830
+ ...request.input,
4831
+ toolExecution: runtimeConfig.toolExecution,
4832
+ },
4833
+ }
4834
+ : {
4835
+ kind: 'standard',
4836
+ input: {
4837
+ ...request.input,
4838
+ toolExecution: runtimeConfig.toolExecution,
4839
+ },
4840
+ };
4841
+ const childGraph = graphFactory(configuredRequest);
4809
4842
  if (subagentModelOverride != null) {
4810
4843
  childGraph.overrideModel = subagentModelOverride;
4811
4844
  childGraph.setSubagentModelOverride(subagentModelOverride);