@librechat/agents 3.6.10 → 3.6.11

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 (39) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +63 -10
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +4 -3
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/main.cjs +2 -0
  6. package/dist/cjs/stream.cjs +1 -0
  7. package/dist/cjs/stream.cjs.map +1 -1
  8. package/dist/cjs/tools/CallerCapabilities.cjs +31 -0
  9. package/dist/cjs/tools/CallerCapabilities.cjs.map +1 -1
  10. package/dist/cjs/tools/ToolNode.cjs +23 -6
  11. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  12. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +26 -2
  13. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
  14. package/dist/esm/agents/AgentContext.mjs +65 -12
  15. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  16. package/dist/esm/graphs/Graph.mjs +4 -3
  17. package/dist/esm/graphs/Graph.mjs.map +1 -1
  18. package/dist/esm/main.mjs +2 -2
  19. package/dist/esm/stream.mjs +1 -0
  20. package/dist/esm/stream.mjs.map +1 -1
  21. package/dist/esm/tools/CallerCapabilities.mjs +29 -1
  22. package/dist/esm/tools/CallerCapabilities.mjs.map +1 -1
  23. package/dist/esm/tools/ToolNode.mjs +24 -7
  24. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  25. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +26 -4
  26. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  27. package/dist/types/agents/AgentContext.d.ts +11 -0
  28. package/dist/types/tools/CallerCapabilities.d.ts +13 -0
  29. package/dist/types/tools/ToolNode.d.ts +9 -1
  30. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +6 -0
  31. package/dist/types/types/tools.d.ts +17 -0
  32. package/package.json +1 -1
  33. package/src/agents/AgentContext.ts +157 -15
  34. package/src/graphs/Graph.ts +6 -3
  35. package/src/stream.ts +6 -0
  36. package/src/tools/CallerCapabilities.ts +68 -0
  37. package/src/tools/ToolNode.ts +47 -4
  38. package/src/tools/local/resolveLocalExecutionTools.ts +77 -3
  39. package/src/types/tools.ts +18 -0
@@ -34,12 +34,18 @@ import {
34
34
  } from '@/common';
35
35
  import {
36
36
  isProgrammaticRunnerAutoBound,
37
+ isProgrammaticRunnerResolvedDirectly,
38
+ resolveLocalImplementationNames,
37
39
  resolveLocalToolRegistry,
38
40
  } from '@/tools/local/resolveLocalExecutionTools';
39
41
  import {
42
+ type CallerCapabilityProjection,
40
43
  allowsToolCaller,
44
+ applyCallerCapabilityDefinitionOverrides,
45
+ createCallerCapabilityProjectionSnapshot,
41
46
  isToolDefinitionActive,
42
47
  isProgrammaticControlTool,
48
+ mergeCallerCapabilityDefinitions,
43
49
  resolveCallerCapabilityProjection,
44
50
  } from '@/tools/CallerCapabilities';
45
51
  import { createSchemaOnlyTools } from '@/tools/schema';
@@ -59,6 +65,12 @@ type AgentSystemContentBlock =
59
65
 
60
66
  type PromptCacheProvider = Providers.ANTHROPIC | Providers.OPENROUTER;
61
67
 
68
+ type ProgrammaticToolInstructionTarget = {
69
+ name: string;
70
+ codeGuidance: string;
71
+ executesDirectly: boolean;
72
+ };
73
+
62
74
  /**
63
75
  * Encapsulates agent-specific state that can vary between agents in a multi-agent system
64
76
  */
@@ -483,12 +495,53 @@ export class AgentContext {
483
495
 
484
496
  /** Builds the caller boundary and schemas for programmatic-only tools. */
485
497
  private buildProgrammaticOnlyToolsInstructions(): string {
486
- if (!this.toolRegistry) return '';
487
-
488
- const capabilities = resolveCallerCapabilityProjection(
489
- this.toolRegistry.values(),
490
- (toolDef) => isToolDefinitionActive(toolDef, this.discoveredToolNames)
498
+ const programmaticTools = this.getProgrammaticToolInstructionTargets();
499
+ if (programmaticTools.length === 0) return '';
500
+ const directProgrammaticTools = programmaticTools.filter(
501
+ (tool) => tool.executesDirectly
491
502
  );
503
+ const eventProgrammaticTools = programmaticTools.filter(
504
+ (tool) => !tool.executesDirectly
505
+ );
506
+ const groups: Array<{
507
+ tools: ProgrammaticToolInstructionTarget[];
508
+ capabilities: CallerCapabilityProjection;
509
+ label: string;
510
+ }> = [];
511
+ if (directProgrammaticTools.length > 0) {
512
+ groups.push({
513
+ tools: directProgrammaticTools,
514
+ capabilities: this.getDirectProgrammaticCapabilityProjection(),
515
+ label: 'Direct programmatic runners',
516
+ });
517
+ }
518
+ if (eventProgrammaticTools.length > 0) {
519
+ groups.push({
520
+ tools: eventProgrammaticTools,
521
+ capabilities: this.getCallerCapabilityProjection(),
522
+ label: 'Event-dispatched programmatic runners',
523
+ });
524
+ }
525
+ if (groups.length === 0) {
526
+ return '';
527
+ }
528
+ const showGroupLabels = groups.length > 1;
529
+ return (
530
+ '\n\n## Programmatic Tool Calling' +
531
+ groups
532
+ .map(
533
+ ({ tools, capabilities, label }) =>
534
+ (showGroupLabels ? `\n\n### ${label}` : '') +
535
+ this.buildProgrammaticToolGroupInstructions(tools, capabilities)
536
+ )
537
+ .join('')
538
+ );
539
+ }
540
+
541
+ private buildProgrammaticToolGroupInstructions(
542
+ programmaticTools: ProgrammaticToolInstructionTarget[],
543
+ capabilities: CallerCapabilityProjection
544
+ ): string {
492
545
  const programmaticOnlyTools = capabilities.codeExecutionOnlyTools;
493
546
  const programmaticToolNames = capabilities.codeExecutionTools.map(
494
547
  (toolDef) => toolDef.name
@@ -497,8 +550,6 @@ export class AgentContext {
497
550
  .map((toolDef) => toolDef.name)
498
551
  .filter((name) => !isProgrammaticControlTool(name));
499
552
 
500
- const programmaticTools = this.getProgrammaticToolInstructionTargets();
501
- if (programmaticTools.length === 0) return '';
502
553
  const programmaticRunnerNames = programmaticTools
503
554
  .map((tool) => `\`${tool.name}\``)
504
555
  .join(' or ');
@@ -513,7 +564,7 @@ export class AgentContext {
513
564
  .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
565
  : '';
515
566
  const boundary =
516
- '\n\n## Programmatic Tool Calling\n\n' +
567
+ '\n\n' +
517
568
  `Only these tools may be invoked inside ${programmaticRunnerNames}: ${quotedProgrammaticNames}.` +
518
569
  directOnlyBoundary;
519
570
 
@@ -548,11 +599,8 @@ export class AgentContext {
548
599
  );
549
600
  }
550
601
 
551
- private getProgrammaticToolInstructionTargets(): Array<{
552
- name: string;
553
- codeGuidance: string;
554
- }> {
555
- const targets: Array<{ name: string; codeGuidance: string }> = [];
602
+ private getProgrammaticToolInstructionTargets(): ProgrammaticToolInstructionTarget[] {
603
+ const targets: ProgrammaticToolInstructionTarget[] = [];
556
604
  if (
557
605
  this.hasBoundTool(Constants.BASH_PROGRAMMATIC_TOOL_CALLING) ||
558
606
  isProgrammaticRunnerAutoBound(
@@ -563,6 +611,9 @@ export class AgentContext {
563
611
  targets.push({
564
612
  name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
565
613
  codeGuidance: 'Bash code',
614
+ executesDirectly: this.isProgrammaticRunnerDirectlyBound(
615
+ Constants.BASH_PROGRAMMATIC_TOOL_CALLING
616
+ ),
566
617
  });
567
618
  }
568
619
 
@@ -581,12 +632,73 @@ export class AgentContext {
581
632
  codeGuidance: localDefault
582
633
  ? 'Bash code by default, or set `lang: "py"` to use Python code'
583
634
  : 'Python code',
635
+ executesDirectly: this.isProgrammaticRunnerDirectlyBound(
636
+ Constants.PROGRAMMATIC_TOOL_CALLING
637
+ ),
584
638
  });
585
639
  }
586
640
 
587
641
  return targets;
588
642
  }
589
643
 
644
+ /** Whether ToolNode executes this runner in-process instead of via an event. */
645
+ private isProgrammaticRunnerDirectlyBound(name: string): boolean {
646
+ return (
647
+ isProgrammaticRunnerResolvedDirectly(
648
+ name,
649
+ this.toolExecution,
650
+ this.toolDefinitions?.some((toolDef) => toolDef.name === name) === true
651
+ ) ||
652
+ this.graphTools?.some(
653
+ (tool) => 'name' in tool && tool.name === name
654
+ ) === true
655
+ );
656
+ }
657
+
658
+ /** Mirrors ToolNode's executable implementation gate for direct runners. */
659
+ private getDirectProgrammaticCapabilityProjection(): CallerCapabilityProjection {
660
+ const implementationNames = new Set<string>();
661
+ const isEventDriven = (this.toolDefinitions?.length ?? 0) > 0;
662
+ const resolverInputNames = new Set<string>();
663
+ if (isEventDriven) {
664
+ for (const toolDef of this.toolDefinitions ?? []) {
665
+ resolverInputNames.add(toolDef.name);
666
+ }
667
+ } else {
668
+ for (const tool of (this.tools as t.GenericTool[] | undefined) ?? []) {
669
+ if ('name' in tool && typeof tool.name === 'string') {
670
+ implementationNames.add(tool.name);
671
+ resolverInputNames.add(tool.name);
672
+ }
673
+ }
674
+ }
675
+ for (const tool of (this.graphTools as t.GenericTool[] | undefined) ?? []) {
676
+ if ('name' in tool && typeof tool.name === 'string') {
677
+ implementationNames.add(tool.name);
678
+ resolverInputNames.add(tool.name);
679
+ }
680
+ }
681
+ for (const name of resolveLocalImplementationNames(
682
+ resolverInputNames,
683
+ this.toolExecution
684
+ )) {
685
+ implementationNames.add(name);
686
+ }
687
+ const activeCapabilities = this.getCallerCapabilityProjection();
688
+ const executableCapabilities = resolveCallerCapabilityProjection(
689
+ this.toolRegistry?.values() ?? [],
690
+ (toolDef) =>
691
+ implementationNames.has(toolDef.name) &&
692
+ isToolDefinitionActive(toolDef, this.discoveredToolNames)
693
+ );
694
+ return {
695
+ directTools: activeCapabilities.directTools,
696
+ directOnlyTools: activeCapabilities.directOnlyTools,
697
+ codeExecutionTools: executableCapabilities.codeExecutionTools,
698
+ codeExecutionOnlyTools: executableCapabilities.codeExecutionOnlyTools,
699
+ };
700
+ }
701
+
590
702
  private hasBoundTool(name: string): boolean {
591
703
  return (
592
704
  this.getToolsForBinding()?.some(
@@ -1123,9 +1235,21 @@ export class AgentContext {
1123
1235
  this.indexTokenCountMap = { ...baseTokenMap };
1124
1236
  }
1125
1237
 
1238
+ /** Event definitions with matching runtime caller/defer metadata applied. */
1239
+ getEffectiveToolDefinitions(): t.LCTool[] | undefined {
1240
+ if (!this.toolDefinitions) {
1241
+ return undefined;
1242
+ }
1243
+ return applyCallerCapabilityDefinitionOverrides(
1244
+ this.toolDefinitions,
1245
+ this.toolRegistry?.values()
1246
+ );
1247
+ }
1248
+
1126
1249
  /** Active tool definitions for token accounting (excludes deferred-and-undiscovered entries). */
1127
1250
  private getActiveToolDefinitions(): t.LCTool[] {
1128
- if (!this.toolDefinitions) {
1251
+ const effectiveToolDefinitions = this.getEffectiveToolDefinitions();
1252
+ if (!effectiveToolDefinitions) {
1129
1253
  return [];
1130
1254
  }
1131
1255
  /**
@@ -1136,7 +1260,7 @@ export class AgentContext {
1136
1260
  * `toolSchemaTokens` even though they were never bound.
1137
1261
  */
1138
1262
  return resolveCallerCapabilityProjection(
1139
- this.toolDefinitions,
1263
+ effectiveToolDefinitions,
1140
1264
  (toolDef) => isToolDefinitionActive(toolDef, this.discoveredToolNames)
1141
1265
  ).directTools;
1142
1266
  }
@@ -1767,6 +1891,24 @@ export class AgentContext {
1767
1891
  return Array.from(this.discoveredToolNames);
1768
1892
  }
1769
1893
 
1894
+ /** Returns the live projection shared by prompt and event execution. */
1895
+ private getCallerCapabilityProjection(): CallerCapabilityProjection {
1896
+ return resolveCallerCapabilityProjection(
1897
+ mergeCallerCapabilityDefinitions(
1898
+ this.toolDefinitions,
1899
+ this.toolRegistry?.values()
1900
+ ),
1901
+ (toolDef) => isToolDefinitionActive(toolDef, this.discoveredToolNames)
1902
+ );
1903
+ }
1904
+
1905
+ /** Returns the SDK-owned active caller projection for event-driven hosts. */
1906
+ getCallerCapabilityProjectionSnapshot(): t.CallerCapabilityProjectionSnapshot {
1907
+ return createCallerCapabilityProjectionSnapshot(
1908
+ this.getCallerCapabilityProjection()
1909
+ );
1910
+ }
1911
+
1770
1912
  /**
1771
1913
  * Marks tools as discovered via tool search.
1772
1914
  * Discovered tools will be included in the next model binding.
@@ -2868,6 +2868,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2868
2868
  * the breakpoint and don't invalidate the prefix.
2869
2869
  */
2870
2870
  let toolsForBinding = rawToolsForBinding;
2871
+ const isDeferredTool = makeIsDeferred(
2872
+ agentContext.getEffectiveToolDefinitions()
2873
+ );
2871
2874
  if (
2872
2875
  agentContext.provider === Providers.ANTHROPIC &&
2873
2876
  (agentContext.clientOptions as t.AnthropicClientOptions | undefined)
@@ -2876,7 +2879,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2876
2879
  toolsForBinding =
2877
2880
  partitionAndMarkAnthropicToolCache(
2878
2881
  rawToolsForBinding,
2879
- makeIsDeferred(agentContext.toolDefinitions),
2882
+ isDeferredTool,
2880
2883
  resolvePromptCacheTtl(
2881
2884
  (
2882
2885
  agentContext.clientOptions as
@@ -2896,7 +2899,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2896
2899
  toolsForBinding =
2897
2900
  partitionAndMarkOpenRouterToolCache(
2898
2901
  rawToolsForBinding,
2899
- makeIsDeferred(agentContext.toolDefinitions),
2902
+ isDeferredTool,
2900
2903
  resolvePromptCacheTtl(
2901
2904
  (
2902
2905
  agentContext.clientOptions as
@@ -2923,7 +2926,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
2923
2926
  toolsForBinding =
2924
2927
  partitionAndMarkBedrockToolCache(
2925
2928
  rawToolsForBinding,
2926
- makeIsDeferred(agentContext.toolDefinitions)
2929
+ isDeferredTool
2927
2930
  ) ?? rawToolsForBinding;
2928
2931
  }
2929
2932
  }
package/src/stream.ts CHANGED
@@ -820,6 +820,12 @@ function startEagerToolExecutions(args: {
820
820
  toolCalls: entries.map((entry) => entry.request),
821
821
  userId: graph.config?.configurable?.user_id as string | undefined,
822
822
  agentId: agentContext?.agentId,
823
+ callerCapabilityProjection:
824
+ (
825
+ agentContext as
826
+ | Partial<Pick<AgentContext, 'getCallerCapabilityProjectionSnapshot'>>
827
+ | undefined
828
+ )?.getCallerCapabilityProjectionSnapshot?.(),
823
829
  configurable: graph.config?.configurable as
824
830
  | Record<string, unknown>
825
831
  | undefined,
@@ -14,6 +14,74 @@ export type CallerCapabilityProjection = {
14
14
  codeExecutionOnlyTools: t.LCTool[];
15
15
  };
16
16
 
17
+ /**
18
+ * Combines event schemas with runtime capability metadata. Matching runtime
19
+ * entries override caller/defer policy without replacing the event schema;
20
+ * runtime-only definitions are appended intact.
21
+ */
22
+ export function mergeCallerCapabilityDefinitions(
23
+ toolDefs: Iterable<t.LCTool> | null | undefined,
24
+ overrides: Iterable<t.LCTool> | null | undefined
25
+ ): t.LCTool[] {
26
+ const runtimeDefinitions = Array.from(overrides ?? []);
27
+ const merged = new Map(
28
+ applyCallerCapabilityDefinitionOverrides(
29
+ toolDefs ?? [],
30
+ runtimeDefinitions
31
+ ).map((toolDef) => [toolDef.name, toolDef])
32
+ );
33
+ for (const override of runtimeDefinitions) {
34
+ if (!merged.has(override.name)) {
35
+ merged.set(override.name, override);
36
+ }
37
+ }
38
+ return Array.from(merged.values());
39
+ }
40
+
41
+ /**
42
+ * Applies runtime caller metadata to schema-only event definitions without
43
+ * adding registry-only tools or replacing their model-facing schemas.
44
+ */
45
+ export function applyCallerCapabilityDefinitionOverrides(
46
+ toolDefs: Iterable<t.LCTool>,
47
+ overrides: Iterable<t.LCTool> | null | undefined
48
+ ): t.LCTool[] {
49
+ const overridesByName = new Map<string, t.LCTool>();
50
+ for (const override of overrides ?? []) {
51
+ overridesByName.set(override.name, override);
52
+ }
53
+ return Array.from(toolDefs, (toolDef) => {
54
+ const override = overridesByName.get(toolDef.name);
55
+ if (override == null) {
56
+ return toolDef;
57
+ }
58
+ return {
59
+ ...toolDef,
60
+ allowed_callers: override.allowed_callers,
61
+ defer_loading: override.defer_loading,
62
+ };
63
+ });
64
+ }
65
+
66
+ /** Converts the live projection into the versioned event transport shape. */
67
+ export function createCallerCapabilityProjectionSnapshot(
68
+ projection: CallerCapabilityProjection
69
+ ): t.CallerCapabilityProjectionSnapshot {
70
+ return {
71
+ version: 1,
72
+ directToolNames: projection.directTools.map((toolDef) => toolDef.name),
73
+ codeExecutionToolNames: projection.codeExecutionTools.map(
74
+ (toolDef) => toolDef.name
75
+ ),
76
+ directOnlyToolNames: projection.directOnlyTools.map(
77
+ (toolDef) => toolDef.name
78
+ ),
79
+ codeExecutionOnlyToolNames: projection.codeExecutionOnlyTools.map(
80
+ (toolDef) => toolDef.name
81
+ ),
82
+ };
83
+ }
84
+
17
85
  export function getAllowedCallers(
18
86
  toolDef: t.LCTool
19
87
  ): readonly t.AllowedCaller[] {
@@ -95,8 +95,11 @@ import {
95
95
  resolveLocalExecutionTools,
96
96
  } from '@/tools/local';
97
97
  import {
98
+ type CallerCapabilityProjection,
99
+ createCallerCapabilityProjectionSnapshot,
98
100
  isToolDefinitionActive,
99
101
  isProgrammaticControlTool,
102
+ mergeCallerCapabilityDefinitions,
100
103
  resolveCallerCapabilityProjection,
101
104
  } from '@/tools/CallerCapabilities';
102
105
  import { stripCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
@@ -698,6 +701,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
698
701
  >();
699
702
  /** Tool registry for filtering (lazy computation of programmatic maps) */
700
703
  private toolRegistry?: t.LCToolRegistry;
704
+ /** Schema-only definitions used when event mode has no runtime registry. */
705
+ private toolDefinitions?: t.LCToolRegistry;
706
+ /** Tool-map entries created or replaced by the local execution resolver. */
707
+ private localImplementationNames = new Set<string>();
701
708
  /** Reads deferred-tool discovery state from the owning agent context. */
702
709
  private getDiscoveredToolNames?: () => readonly string[];
703
710
  /** Reference to Graph's sessions map for automatic session injection */
@@ -803,6 +810,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
803
810
  handleToolErrors,
804
811
  loadRuntimeTools,
805
812
  toolRegistry,
813
+ toolDefinitions,
806
814
  getDiscoveredToolNames,
807
815
  sessions,
808
816
  codeSessionKey,
@@ -879,6 +887,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
879
887
  toolRegistry,
880
888
  toolExecution,
881
889
  });
890
+ this.toolDefinitions = toolDefinitions;
882
891
  this.getDiscoveredToolNames = getDiscoveredToolNames;
883
892
  this.sessions = sessions;
884
893
  this.codeSessionKey = codeSessionKey ?? Constants.EXECUTE_CODE;
@@ -999,6 +1008,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
999
1008
  });
1000
1009
 
1001
1010
  this.toolMap = resolved.toolMap;
1011
+ this.localImplementationNames = resolved.localImplementationNames;
1002
1012
  if (resolved.fileCheckpointer != null) {
1003
1013
  this.fileCheckpointer = resolved.fileCheckpointer;
1004
1014
  }
@@ -1105,27 +1115,58 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1105
1115
  this.settledInterruptingResults.clear();
1106
1116
  }
1107
1117
 
1118
+ /** Returns the live caller projection used by direct and event execution. */
1119
+ private getCallerCapabilityProjection(): CallerCapabilityProjection {
1120
+ const discoveredToolNames = new Set(
1121
+ this.getDiscoveredToolNames?.() ?? []
1122
+ );
1123
+ return resolveCallerCapabilityProjection(
1124
+ mergeCallerCapabilityDefinitions(
1125
+ this.toolDefinitions?.values(),
1126
+ this.toolRegistry?.values()
1127
+ ),
1128
+ (toolDef) => isToolDefinitionActive(toolDef, discoveredToolNames)
1129
+ );
1130
+ }
1131
+
1132
+ /** Serializes the live caller projection for event-driven hosts. */
1133
+ private getCallerCapabilityProjectionSnapshot(): t.CallerCapabilityProjectionSnapshot {
1134
+ return createCallerCapabilityProjectionSnapshot(
1135
+ this.getCallerCapabilityProjection()
1136
+ );
1137
+ }
1138
+
1108
1139
  /** Returns active tools projected by their effective caller capabilities. */
1109
1140
  private getProgrammaticTools(): t.ProgrammaticCache {
1110
1141
  const toolMap: t.ToolMap = new Map();
1142
+ const toolDefs: t.LCTool[] = [];
1111
1143
  const discoveredToolNames = new Set(
1112
1144
  this.getDiscoveredToolNames?.() ?? []
1113
1145
  );
1114
- const capabilities = resolveCallerCapabilityProjection(
1146
+ const executableCapabilities = resolveCallerCapabilityProjection(
1115
1147
  this.toolRegistry?.values() ?? [],
1116
1148
  (toolDef) => isToolDefinitionActive(toolDef, discoveredToolNames)
1117
1149
  );
1118
- for (const toolDef of capabilities.codeExecutionTools) {
1150
+ for (const toolDef of executableCapabilities.codeExecutionTools) {
1151
+ if (
1152
+ this.eventDrivenMode &&
1153
+ this.directToolNames?.has(toolDef.name) !== true &&
1154
+ !this.localImplementationNames.has(toolDef.name)
1155
+ ) {
1156
+ continue;
1157
+ }
1119
1158
  const tool = this.toolMap.get(toolDef.name);
1120
1159
  if (tool != null) {
1121
1160
  toolMap.set(toolDef.name, tool);
1161
+ toolDefs.push(toolDef);
1122
1162
  }
1123
1163
  }
1164
+ const activeCapabilities = this.getCallerCapabilityProjection();
1124
1165
 
1125
1166
  return {
1126
1167
  toolMap,
1127
- toolDefs: capabilities.codeExecutionTools,
1128
- disallowedToolDefs: capabilities.directOnlyTools
1168
+ toolDefs,
1169
+ disallowedToolDefs: activeCapabilities.directOnlyTools
1129
1170
  .filter((toolDef) => !isProgrammaticControlTool(toolDef.name))
1130
1171
  .map((toolDef) => ({ name: toolDef.name })),
1131
1172
  };
@@ -3455,6 +3496,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3455
3496
  // the eager path sends `agentContext.agentId` — this must
3456
3497
  // match it at the top level too.
3457
3498
  agentId: this.executingAgentId,
3499
+ callerCapabilityProjection:
3500
+ this.getCallerCapabilityProjectionSnapshot(),
3458
3501
  configurable: stripRunBreakerScope(
3459
3502
  config.configurable as Record<string, unknown> | undefined
3460
3503
  ),
@@ -4,6 +4,7 @@ import {
4
4
  createLocalProgrammaticToolCallingTool,
5
5
  } from './LocalProgrammaticToolCalling';
6
6
  import {
7
+ CLOUDFLARE_CODING_TOOL_NAMES,
7
8
  createCloudflareCodingToolBundle,
8
9
  createCloudflareCodingTools,
9
10
  createCloudflareExecutionTool,
@@ -30,6 +31,8 @@ import {
30
31
  type ResolveLocalToolsResult = {
31
32
  toolMap: t.ToolMap;
32
33
  directToolNames: Set<string>;
34
+ /** Names whose toolMap entries were created or replaced by this resolver. */
35
+ localImplementationNames: Set<string>;
33
36
  /**
34
37
  * Set when `local.fileCheckpointing === true` AND the auto-bind
35
38
  * coding suite is in use. ToolNode stashes this on the node and
@@ -81,6 +84,29 @@ export function isProgrammaticRunnerAutoBound(
81
84
  return shouldUseLocalExecution(config);
82
85
  }
83
86
 
87
+ /** Mirrors whether resolver policy creates or replaces this runner locally. */
88
+ export function isProgrammaticRunnerResolvedDirectly(
89
+ name: string,
90
+ config?: t.ToolExecutionConfig,
91
+ hasExplicitBinding: boolean = false
92
+ ): boolean {
93
+ if (isProgrammaticRunnerAutoBound(name, config)) {
94
+ return true;
95
+ }
96
+ if (
97
+ !hasExplicitBinding ||
98
+ (name !== Constants.PROGRAMMATIC_TOOL_CALLING &&
99
+ name !== Constants.BASH_PROGRAMMATIC_TOOL_CALLING)
100
+ ) {
101
+ return false;
102
+ }
103
+ return (
104
+ (shouldUseLocalExecution(config) ||
105
+ shouldUseCloudflareSandboxExecution(config)) &&
106
+ !shouldIncludeCodingTools(config)
107
+ );
108
+ }
109
+
84
110
  function getCloudflareConfig(
85
111
  config?: t.ToolExecutionConfig
86
112
  ): t.CloudflareSandboxExecutionConfig {
@@ -95,7 +121,12 @@ function getCloudflareConfig(
95
121
  function getSelectedCloudflareCodingToolNames(
96
122
  config: t.CloudflareSandboxExecutionConfig
97
123
  ): Set<string> {
98
- return new Set(config.codingToolNames ?? LOCAL_CODING_BUNDLE_NAMES);
124
+ const requestedNames = new Set(
125
+ config.codingToolNames ?? CLOUDFLARE_CODING_TOOL_NAMES
126
+ );
127
+ return new Set(
128
+ CLOUDFLARE_CODING_TOOL_NAMES.filter((name) => requestedNames.has(name))
129
+ );
99
130
  }
100
131
 
101
132
  function filterCloudflareCodingToolAllowlist(
@@ -340,6 +371,28 @@ export function resolveLocalToolRegistry(args: {
340
371
  return registry;
341
372
  }
342
373
 
374
+ /** Names that this execution config creates or replaces in the local tool map. */
375
+ export function resolveLocalImplementationNames(
376
+ toolNames: Iterable<string>,
377
+ toolExecution?: t.ToolExecutionConfig
378
+ ): Set<string> {
379
+ if (
380
+ !shouldUseLocalExecution(toolExecution) &&
381
+ !shouldUseCloudflareSandboxExecution(toolExecution)
382
+ ) {
383
+ return new Set();
384
+ }
385
+ if (shouldIncludeCodingTools(toolExecution)) {
386
+ return shouldUseCloudflareSandboxExecution(toolExecution)
387
+ ? getSelectedCloudflareCodingToolNames(getCloudflareConfig(toolExecution))
388
+ : new Set(LOCAL_CODING_BUNDLE_NAMES);
389
+ }
390
+ const existingNames = new Set(toolNames);
391
+ return new Set(
392
+ [...CODE_EXECUTION_TOOLS].filter((name) => existingNames.has(name))
393
+ );
394
+ }
395
+
343
396
  export function resolveLocalExecutionTools(args: {
344
397
  toolMap: t.ToolMap;
345
398
  toolExecution?: t.ToolExecutionConfig;
@@ -355,6 +408,10 @@ export function resolveLocalExecutionTools(args: {
355
408
  fileCheckpointer?: t.LocalFileCheckpointer;
356
409
  }): ResolveLocalToolsResult {
357
410
  const directToolNames = new Set<string>();
411
+ const localImplementationNames = resolveLocalImplementationNames(
412
+ args.toolMap.keys(),
413
+ args.toolExecution
414
+ );
358
415
  if (
359
416
  !shouldUseLocalExecution(args.toolExecution) &&
360
417
  !shouldUseCloudflareSandboxExecution(args.toolExecution)
@@ -362,6 +419,7 @@ export function resolveLocalExecutionTools(args: {
362
419
  return {
363
420
  toolMap: args.toolMap,
364
421
  directToolNames,
422
+ localImplementationNames,
365
423
  };
366
424
  }
367
425
 
@@ -384,6 +442,7 @@ export function resolveLocalExecutionTools(args: {
384
442
  fileCheckpointer = bundle.checkpointer;
385
443
  for (const cloudflareTool of bundle.tools) {
386
444
  toolMap.set(cloudflareTool.name, cloudflareTool);
445
+ localImplementationNames.add(cloudflareTool.name);
387
446
  addDirectToolName(
388
447
  directToolNames,
389
448
  cloudflareTool.name,
@@ -395,6 +454,7 @@ export function resolveLocalExecutionTools(args: {
395
454
  cloudflareConfig
396
455
  )) {
397
456
  toolMap.set(cloudflareTool.name, cloudflareTool);
457
+ localImplementationNames.add(cloudflareTool.name);
398
458
  addDirectToolName(
399
459
  directToolNames,
400
460
  cloudflareTool.name,
@@ -418,10 +478,16 @@ export function resolveLocalExecutionTools(args: {
418
478
  }
419
479
 
420
480
  toolMap.set(name, cloudflareTool);
481
+ localImplementationNames.add(name);
421
482
  addDirectToolName(directToolNames, name, args.toolRegistry);
422
483
  }
423
484
 
424
- return { toolMap, directToolNames, fileCheckpointer };
485
+ return {
486
+ toolMap,
487
+ directToolNames,
488
+ localImplementationNames,
489
+ fileCheckpointer,
490
+ };
425
491
  }
426
492
 
427
493
  const localConfig = args.toolExecution?.local ?? {};
@@ -443,6 +509,7 @@ export function resolveLocalExecutionTools(args: {
443
509
  fileCheckpointer = bundle.checkpointer;
444
510
  for (const localTool of bundle.tools) {
445
511
  toolMap.set(localTool.name, localTool);
512
+ localImplementationNames.add(localTool.name);
446
513
  addDirectToolName(
447
514
  directToolNames,
448
515
  localTool.name,
@@ -452,6 +519,7 @@ export function resolveLocalExecutionTools(args: {
452
519
  } else {
453
520
  for (const localTool of createLocalCodingTools(localConfig)) {
454
521
  toolMap.set(localTool.name, localTool);
522
+ localImplementationNames.add(localTool.name);
455
523
  addDirectToolName(
456
524
  directToolNames,
457
525
  localTool.name,
@@ -482,8 +550,14 @@ export function resolveLocalExecutionTools(args: {
482
550
  }
483
551
 
484
552
  toolMap.set(name, localTool);
553
+ localImplementationNames.add(name);
485
554
  addDirectToolName(directToolNames, name, args.toolRegistry);
486
555
  }
487
556
 
488
- return { toolMap, directToolNames, fileCheckpointer };
557
+ return {
558
+ toolMap,
559
+ directToolNames,
560
+ localImplementationNames,
561
+ fileCheckpointer,
562
+ };
489
563
  }