@botbotgo/agent-harness 0.0.400 → 0.0.402

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.
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { createAsyncSubAgentMiddleware, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
3
- import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
3
+ import { AIMessage, createAgent, createMiddleware, humanInTheLoopMiddleware, todoListMiddleware, ToolMessage } from "langchain";
4
4
  import { sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
5
5
  import { salvageJsonToolCalls } from "./parsing/output-tool-args.js";
6
6
  import { extractMessageText } from "../utils/message-content.js";
@@ -27,7 +27,7 @@ export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTime
27
27
  import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingSubagents, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
28
28
  class DelegatedExecutionNoToolEvidenceError extends Error {
29
29
  constructor(agentId) {
30
- super(`Delegated agent ${agentId} completed without tool execution evidence.`);
30
+ super(`Delegated agent ${agentId} lacked non-planning tool evidence.`);
31
31
  this.name = "DelegatedExecutionNoToolEvidenceError";
32
32
  }
33
33
  }
@@ -38,11 +38,245 @@ function hasDelegatedExecutionToolEvidence(result) {
38
38
  return executedToolResults.some((toolResult) => (toolResult.isError !== true
39
39
  && !isPlanToolName(toolResult.toolName)));
40
40
  }
41
+ function buildDelegatedPlanEvidenceBlocker(agentId) {
42
+ return [
43
+ "Status: blocked",
44
+ "Summary:",
45
+ `- Delegated agent ${agentId} ended before producing the required TODO plan evidence.`,
46
+ "",
47
+ "Blockers:",
48
+ "- The delegated run did not expose a valid planning trace, so the framework cannot treat the task as complete.",
49
+ "",
50
+ "Next Actions:",
51
+ "- Retry with the same request or inspect the delegated agent configuration and model/tool-call behavior.",
52
+ ].join("\n");
53
+ }
54
+ function buildDelegatedExecutionEvidenceBlocker(agentId) {
55
+ return [
56
+ "Status: blocked",
57
+ "Summary:",
58
+ `- Delegated agent ${agentId} did not return any non-planning tool evidence after retry.`,
59
+ "",
60
+ "Blockers:",
61
+ "- The TODO board alone is not execution evidence.",
62
+ "- The framework cannot mark the delegated task complete without a non-planning tool result or an explicit blocker from that tool path.",
63
+ "",
64
+ "Next Actions:",
65
+ "- Retry the request or inspect the delegated agent's model/tool-call behavior.",
66
+ ].join("\n");
67
+ }
68
+ function normalizePlanToolName(toolName) {
69
+ return typeof toolName === "string" ? toolName.trim().toLowerCase().replace(/[\s-]+/gu, "_") : "";
70
+ }
41
71
  function isPlanToolName(toolName) {
42
- return toolName === "write_todos"
43
- || toolName === "read_todos"
44
- || toolName === "tool_call_write_todos"
45
- || toolName === "tool_call_read_todos";
72
+ const normalized = normalizePlanToolName(toolName);
73
+ return normalized === "write_todos"
74
+ || normalized === "read_todos"
75
+ || normalized === "tool_call_write_todos"
76
+ || normalized === "tool_call_read_todos";
77
+ }
78
+ function readConfiguredToolName(value) {
79
+ if (typeof value !== "object" || value === null) {
80
+ return "";
81
+ }
82
+ const typed = value;
83
+ return typeof typed.name === "string" ? typed.name.trim() : "";
84
+ }
85
+ function createBootstrapTodoPlan(toolNames) {
86
+ const evidenceToolName = toolNames.find((toolName) => !isPlanToolName(toolName));
87
+ const contents = evidenceToolName
88
+ ? [
89
+ `Run ${evidenceToolName} for the requested evidence`,
90
+ `Inspect the ${evidenceToolName} result and extract concrete findings`,
91
+ "Update TODO status from the observed evidence",
92
+ "Return the final answer grounded in tool output",
93
+ ]
94
+ : [
95
+ "Identify the concrete evidence needed for this request",
96
+ "Collect and inspect the available evidence",
97
+ "Update TODO status from the observed evidence",
98
+ "Return the final answer grounded in evidence",
99
+ ];
100
+ return contents.map((content, index) => ({
101
+ content,
102
+ status: index === 0 ? "in_progress" : "pending",
103
+ }));
104
+ }
105
+ function readMessageContentText(message) {
106
+ if (typeof message !== "object" || message === null) {
107
+ return "";
108
+ }
109
+ const content = message.content;
110
+ if (typeof content === "string") {
111
+ return content.trim();
112
+ }
113
+ if (!Array.isArray(content)) {
114
+ return "";
115
+ }
116
+ return content
117
+ .map((part) => typeof part === "object" && part !== null && typeof part.text === "string"
118
+ ? part.text
119
+ : "")
120
+ .join("")
121
+ .trim();
122
+ }
123
+ function parseToolCallArgs(value) {
124
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
125
+ return value;
126
+ }
127
+ if (typeof value !== "string" || value.trim().length === 0) {
128
+ return {};
129
+ }
130
+ try {
131
+ const parsed = JSON.parse(value);
132
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
133
+ ? parsed
134
+ : {};
135
+ }
136
+ catch {
137
+ return {};
138
+ }
139
+ }
140
+ function readMessageToolCalls(message) {
141
+ if (typeof message !== "object" || message === null) {
142
+ return [];
143
+ }
144
+ const typed = message;
145
+ const raw = Array.isArray(typed.tool_calls) ? typed.tool_calls
146
+ : Array.isArray(typed.kwargs?.tool_calls) ? typed.kwargs.tool_calls
147
+ : Array.isArray(typed.additional_kwargs?.tool_calls) ? typed.additional_kwargs.tool_calls
148
+ : Array.isArray(typed.kwargs?.additional_kwargs?.tool_calls) ? typed.kwargs.additional_kwargs.tool_calls
149
+ : Array.isArray(typed.lc_kwargs?.tool_calls) ? typed.lc_kwargs.tool_calls
150
+ : Array.isArray(typed.lc_kwargs?.additional_kwargs?.tool_calls) ? typed.lc_kwargs.additional_kwargs.tool_calls
151
+ : [];
152
+ return raw
153
+ .map((toolCall) => {
154
+ if (typeof toolCall !== "object" || toolCall === null) {
155
+ return null;
156
+ }
157
+ const call = toolCall;
158
+ const name = typeof call.name === "string"
159
+ ? call.name
160
+ : typeof call.function?.name === "string"
161
+ ? call.function.name
162
+ : undefined;
163
+ const args = parseToolCallArgs(call.args ?? call.function?.arguments);
164
+ return {
165
+ ...(typeof call.id === "string" ? { id: call.id } : {}),
166
+ ...(name ? { name } : {}),
167
+ args,
168
+ };
169
+ })
170
+ .filter((toolCall) => toolCall !== null);
171
+ }
172
+ function todoToolCallIsTerminal(toolCall) {
173
+ const todos = toolCall.args?.todos;
174
+ if (!Array.isArray(todos) || todos.length === 0) {
175
+ return false;
176
+ }
177
+ return todos.every((todo) => {
178
+ if (typeof todo !== "object" || todo === null || typeof todo.status !== "string") {
179
+ return false;
180
+ }
181
+ const status = todo.status.trim().toLowerCase();
182
+ return status !== "pending" && status !== "in_progress";
183
+ });
184
+ }
185
+ function createTodoPlanGuardMiddleware(options = {}) {
186
+ return createMiddleware({
187
+ name: "harnessTodoPlanGuard",
188
+ wrapToolCall: ((request, handler) => {
189
+ const toolName = typeof request.toolCall?.name === "string"
190
+ ? request.toolCall.name
191
+ : typeof request.tool?.name === "string"
192
+ ? request.tool.name
193
+ : "";
194
+ const messages = Array.isArray(request.state?.messages) ? request.state.messages : [];
195
+ const hasNonPlanToolResult = messages.some((message) => {
196
+ if (typeof message !== "object" || message === null) {
197
+ return false;
198
+ }
199
+ const typed = message;
200
+ const messageType = typeof typed.type === "string"
201
+ ? typed.type
202
+ : typeof typed._getType === "function"
203
+ ? String(typed._getType())
204
+ : "";
205
+ if (messageType !== "tool" && typeof typed.tool_call_id !== "string") {
206
+ return false;
207
+ }
208
+ const resultToolName = typeof typed.name === "string" ? typed.name : "";
209
+ return resultToolName.length > 0 && !isPlanToolName(resultToolName);
210
+ });
211
+ if (options.requiresPlan === true
212
+ && !hasNonPlanToolResult
213
+ && isPlanToolName(toolName)
214
+ && normalizePlanToolName(toolName).includes("write_todos")
215
+ && todoToolCallIsTerminal({ args: parseToolCallArgs(request.toolCall?.args) })) {
216
+ return new ToolMessage({
217
+ content: "Error: write_todos cannot mark every todo as terminal before any non-planning evidence tool returns. Keep one todo in_progress and the remaining todos pending until evidence tools return.",
218
+ tool_call_id: typeof request.toolCall?.id === "string" ? request.toolCall.id : `write-todos-tool-guard-${Math.random().toString(36).slice(2, 10)}`,
219
+ status: "error",
220
+ });
221
+ }
222
+ return handler(request);
223
+ }),
224
+ afterModel: (state) => {
225
+ if (!Array.isArray(state.messages) || state.messages.length === 0) {
226
+ return;
227
+ }
228
+ const hasNonPlanToolResult = state.messages.some((message) => {
229
+ if (typeof message !== "object" || message === null) {
230
+ return false;
231
+ }
232
+ const typed = message;
233
+ const messageType = typeof typed.type === "string"
234
+ ? typed.type
235
+ : typeof typed._getType === "function"
236
+ ? String(typed._getType())
237
+ : "";
238
+ if (messageType !== "tool" && typeof typed.tool_call_id !== "string") {
239
+ return false;
240
+ }
241
+ const toolName = typeof typed.name === "string" ? typed.name : "";
242
+ return toolName.length > 0 && !isPlanToolName(toolName);
243
+ });
244
+ if (hasNonPlanToolResult) {
245
+ return;
246
+ }
247
+ const lastAiMessage = [...state.messages].reverse().find((message) => readMessageToolCalls(message).length > 0);
248
+ const lastToolCalls = readMessageToolCalls(lastAiMessage);
249
+ if (!lastAiMessage && options.requiresPlan === true) {
250
+ const latestMessage = state.messages.at(-1);
251
+ const hasVisibleContent = readMessageContentText(latestMessage).length > 0;
252
+ if (!hasVisibleContent) {
253
+ return {
254
+ messages: [new AIMessage({
255
+ content: "",
256
+ tool_calls: [{
257
+ id: `write-todos-bootstrap-${Math.random().toString(36).slice(2, 10)}`,
258
+ name: "write_todos",
259
+ args: { todos: createBootstrapTodoPlan(options.toolNames ?? []) },
260
+ type: "tool_call",
261
+ }],
262
+ })],
263
+ };
264
+ }
265
+ }
266
+ const writeTodosCalls = lastToolCalls.filter((toolCall) => isPlanToolName(toolCall.name));
267
+ const prematureCompletedCalls = writeTodosCalls.filter(todoToolCallIsTerminal);
268
+ if (prematureCompletedCalls.length === 0) {
269
+ return;
270
+ }
271
+ return {
272
+ messages: prematureCompletedCalls.map((toolCall, index) => new ToolMessage({
273
+ content: "Error: write_todos cannot mark every todo as terminal before any non-planning evidence tool returns. Keep one todo in_progress and the remaining todos pending until evidence tools return.",
274
+ tool_call_id: toolCall.id ?? `write-todos-plan-guard-${index}`,
275
+ status: "error",
276
+ })),
277
+ };
278
+ },
279
+ });
46
280
  }
47
281
  function shouldUseConfigurableDeepAgentAssembly(binding) {
48
282
  return getBindingExecutionKind(binding) === "deepagent";
@@ -160,25 +394,21 @@ function parseCompactRouterSelection(value, subagentNames) {
160
394
  }
161
395
  function inferCompactRouterSelectionFromRequest(requestText, subagents) {
162
396
  const normalized = requestText.toLowerCase();
397
+ const requestTokens = extractRouterMatchTokens(normalized);
163
398
  const score = (subagent) => {
164
399
  const name = subagent.name.toLowerCase();
165
400
  const description = (subagent.description ?? "").toLowerCase();
401
+ const descriptionTokens = extractRouterMatchTokens(`${name} ${description}`);
166
402
  let value = 0;
167
403
  if (normalized.includes(name))
168
404
  value += 4;
169
- const keywordGroups = {
170
- k8s: ["k8s", "kubernetes", "kubectl", "cluster", "pod", "node", "节点", "集群", "调度"],
171
- software: ["code", "repo", "implementation", "debug", "代码", "仓库", "实现", "配置"],
172
- ops: ["ci", "cd", "github actions", "disk", "storage", "deploy", "磁盘", "存储", "运维"],
173
- qa: ["test", "coverage", "regression", "验证", "测试", "回归"],
174
- release: ["release", "publish", "version", "tag", "发版", "发布", "版本"],
175
- research: ["research", "web", "latest", "调查资料", "外部", "搜索"],
176
- secretary: ["summary", "transcript", "youtube", "brief", "摘要", "讲稿", "转写"],
177
- };
178
- const keywords = keywordGroups[name] ?? [];
179
- for (const keyword of keywords) {
180
- if (normalized.includes(keyword))
181
- value += description.includes(keyword) || name.includes(keyword) ? 3 : 1;
405
+ for (const token of requestTokens) {
406
+ if (token === name) {
407
+ value += 4;
408
+ }
409
+ else if (descriptionTokens.has(token)) {
410
+ value += token.length > 2 ? 2 : 1;
411
+ }
182
412
  }
183
413
  return value;
184
414
  };
@@ -191,6 +421,16 @@ function inferCompactRouterSelectionFromRequest(requestText, subagents) {
191
421
  }
192
422
  return ranked[0].name;
193
423
  }
424
+ function extractRouterMatchTokens(value) {
425
+ const tokens = new Set();
426
+ for (const match of value.matchAll(/[\p{L}\p{N}_-]+/gu)) {
427
+ const token = match[0].toLowerCase();
428
+ if (token.length >= 2) {
429
+ tokens.add(token);
430
+ }
431
+ }
432
+ return tokens;
433
+ }
194
434
  function isDelegationOnlyDeepAgentBinding(binding) {
195
435
  return isDeepAgentBinding(binding)
196
436
  && getBindingSubagents(binding).length > 0
@@ -454,12 +694,47 @@ export class AgentRuntimeAdapter {
454
694
  invokeBuiltinTaskTool: assembly.invokeBuiltinTaskTool,
455
695
  });
456
696
  }
457
- materializeProviderAliasBuiltinTools(builtinTools) {
697
+ materializeProviderAliasBuiltinTools(builtinTools, modelExposed) {
698
+ if (modelExposed === false) {
699
+ return [];
700
+ }
701
+ const allowedToolNames = Array.isArray(modelExposed) ? new Set(modelExposed) : undefined;
458
702
  const aliasableTools = ["write_todos", "read_todos", "task"]
703
+ .filter((name) => !allowedToolNames || allowedToolNames.has(name))
459
704
  .map((name) => builtinTools.get(name))
460
705
  .filter((tool) => tool !== undefined);
461
706
  return appendProviderToolCallAliasTools(aliasableTools).slice(aliasableTools.length);
462
707
  }
708
+ shouldExposeBuiltinToolsToModel(binding, primaryTools) {
709
+ const modelExposed = getBindingBuiltinToolsConfig(binding)?.modelExposed;
710
+ if (binding.harnessRuntime.executionContract?.requiresPlan === true) {
711
+ return true;
712
+ }
713
+ if (modelExposed === false) {
714
+ return false;
715
+ }
716
+ if (Array.isArray(modelExposed)) {
717
+ return modelExposed.length > 0;
718
+ }
719
+ return (primaryTools.length > 0
720
+ || getBindingSubagents(binding).length > 0
721
+ || getBindingDeepAgentSubagents(binding).length > 0);
722
+ }
723
+ resolveEffectiveModelExposedBuiltins(binding) {
724
+ const configured = getBindingBuiltinToolsConfig(binding)?.modelExposed;
725
+ const requiredNames = new Set();
726
+ if (binding.harnessRuntime.executionContract?.requiresPlan === true) {
727
+ requiredNames.add("write_todos");
728
+ requiredNames.add("read_todos");
729
+ }
730
+ if (configured === false) {
731
+ return requiredNames.size > 0 ? [...requiredNames] : false;
732
+ }
733
+ if (Array.isArray(configured)) {
734
+ return [...new Set([...configured, ...requiredNames])];
735
+ }
736
+ return configured;
737
+ }
463
738
  async materializeAutomaticSummarizationMiddleware(binding) {
464
739
  const assembly = this.createAssemblyResolvers(binding);
465
740
  return materializeAutomaticSummarizationMiddlewareHelper({
@@ -578,12 +853,18 @@ export class AgentRuntimeAdapter {
578
853
  sessionId: options.sessionId ?? options.legacySessionId,
579
854
  }),
580
855
  });
581
- const builtinMiddlewareTools = materializeModelExposedBuiltinMiddlewareTools({
582
- builtinTools: builtinExecutableTools,
583
- explicitToolNames: primaryTools.map((tool) => tool.name),
584
- modelExposed: getBindingBuiltinToolsConfig(binding)?.modelExposed,
585
- });
586
- const providerAliasBuiltinTools = this.materializeProviderAliasBuiltinTools(builtinExecutableTools);
856
+ const modelExposedBuiltins = this.resolveEffectiveModelExposedBuiltins(binding);
857
+ const shouldExposeBuiltinTools = this.shouldExposeBuiltinToolsToModel(binding, primaryTools);
858
+ const builtinMiddlewareTools = shouldExposeBuiltinTools
859
+ ? materializeModelExposedBuiltinMiddlewareTools({
860
+ builtinTools: builtinExecutableTools,
861
+ explicitToolNames: primaryTools.map((tool) => tool.name),
862
+ modelExposed: modelExposedBuiltins,
863
+ })
864
+ : [];
865
+ const providerAliasBuiltinTools = shouldExposeBuiltinTools
866
+ ? this.materializeProviderAliasBuiltinTools(builtinExecutableTools, modelExposedBuiltins)
867
+ : [];
587
868
  const resolvedMiddleware = await this.resolveMiddleware(binding, interruptOn, { sessionId: options.sessionId ?? options.legacySessionId });
588
869
  const resolvedCheckpointer = resolveRunnableCheckpointer(this.options, binding);
589
870
  const resolvedStore = this.options.storeResolver?.(binding);
@@ -630,12 +911,18 @@ export class AgentRuntimeAdapter {
630
911
  sessionId: options.sessionId ?? options.legacySessionId,
631
912
  }),
632
913
  });
633
- const builtinMiddlewareTools = materializeModelExposedBuiltinMiddlewareTools({
634
- builtinTools: builtinExecutableTools,
635
- explicitToolNames: primaryTools.map((tool) => tool.name),
636
- modelExposed: getBindingBuiltinToolsConfig(binding)?.modelExposed,
637
- });
638
- const providerAliasBuiltinTools = this.materializeProviderAliasBuiltinTools(builtinExecutableTools);
914
+ const modelExposedBuiltins = this.resolveEffectiveModelExposedBuiltins(binding);
915
+ const shouldExposeBuiltinTools = this.shouldExposeBuiltinToolsToModel(binding, primaryTools);
916
+ const builtinMiddlewareTools = shouldExposeBuiltinTools
917
+ ? materializeModelExposedBuiltinMiddlewareTools({
918
+ builtinTools: builtinExecutableTools,
919
+ explicitToolNames: primaryTools.map((tool) => tool.name),
920
+ modelExposed: modelExposedBuiltins,
921
+ })
922
+ : [];
923
+ const providerAliasBuiltinTools = shouldExposeBuiltinTools
924
+ ? this.materializeProviderAliasBuiltinTools(builtinExecutableTools, modelExposedBuiltins)
925
+ : [];
639
926
  const modelTools = [
640
927
  ...appendProviderToolCallAliasTools([...resolvedTools, ...builtinMiddlewareTools]),
641
928
  ...providerAliasBuiltinTools,
@@ -679,8 +966,14 @@ export class AgentRuntimeAdapter {
679
966
  const inlineSubagents = input.resolvedSubagents.filter((subagent) => !("graphId" in subagent));
680
967
  const asyncSubagents = input.resolvedSubagents.filter((subagent) => "graphId" in subagent);
681
968
  const subagents = inlineSubagents;
969
+ const requiresPlan = binding.harnessRuntime.executionContract?.requiresPlan === true;
970
+ const resolvedToolNames = input.resolvedTools.map(readConfiguredToolName).filter((name) => name.length > 0);
682
971
  const middleware = [
683
972
  ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
973
+ ...(builtinTools.todos === false ? [] : [createTodoPlanGuardMiddleware({
974
+ requiresPlan,
975
+ toolNames: resolvedToolNames,
976
+ })]),
684
977
  ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({
685
978
  backend,
686
979
  sources: resolveDeepAgentSkillSourceRootPaths({
@@ -1074,7 +1367,7 @@ export class AgentRuntimeAdapter {
1074
1367
  }
1075
1368
  if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1076
1369
  && !hasDelegatedPlanEvidence(delegatedResult)) {
1077
- const output = "runtime_error=Delegated agent ended before producing required plan evidence.";
1370
+ const output = buildDelegatedPlanEvidenceBlocker(selectedBinding.agent.id);
1078
1371
  return {
1079
1372
  toolOutput: output,
1080
1373
  delegatedSubagentType: subagentType,
@@ -1087,7 +1380,7 @@ export class AgentRuntimeAdapter {
1087
1380
  };
1088
1381
  }
1089
1382
  if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
1090
- const output = `runtime_error=Delegated agent ${selectedBinding.agent.id} completed without tool execution evidence.`;
1383
+ const output = buildDelegatedExecutionEvidenceBlocker(selectedBinding.agent.id);
1091
1384
  return {
1092
1385
  toolOutput: output,
1093
1386
  delegatedSubagentType: subagentType,
@@ -1397,7 +1690,7 @@ export class AgentRuntimeAdapter {
1397
1690
  }
1398
1691
  if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1399
1692
  && !hasDelegatedPlanEvidence(delegatedResult)) {
1400
- const output = "runtime_error=Delegated agent ended before producing required plan evidence.";
1693
+ const output = buildDelegatedPlanEvidenceBlocker(selectedBinding.agent.id);
1401
1694
  delegatedResult = {
1402
1695
  ...delegatedResult,
1403
1696
  state: "failed",
@@ -1406,7 +1699,7 @@ export class AgentRuntimeAdapter {
1406
1699
  };
1407
1700
  }
1408
1701
  if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
1409
- const output = `runtime_error=Delegated agent ${selectedBinding.agent.id} completed without tool execution evidence.`;
1702
+ const output = buildDelegatedExecutionEvidenceBlocker(selectedBinding.agent.id);
1410
1703
  delegatedResult = {
1411
1704
  ...delegatedResult,
1412
1705
  state: "failed",
@@ -2,6 +2,7 @@ import { resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTim
2
2
  import { toolRequiresRuntimeApproval } from "../adapter/tool/tool-hitl.js";
3
3
  import { getBindingPrimaryTools } from "../support/compiled-binding.js";
4
4
  import { compiledToolHasInputSchema } from "./tool-schema.js";
5
+ import { projectBindingToolGatewayPolicy } from "./tool-gateway/index.js";
5
6
  export function getWorkspaceBinding(workspace, agentId) {
6
7
  return workspace.bindings.get(agentId);
7
8
  }
@@ -36,6 +37,7 @@ export function projectBindingToolExecutionPolicy(binding) {
36
37
  const retryableToolCount = projectedTools.filter((tool) => tool.retryable).length;
37
38
  return {
38
39
  agentId: binding.agent.id,
40
+ gateway: projectBindingToolGatewayPolicy(binding),
39
41
  invokeTimeoutMs: resolveBindingTimeout(binding),
40
42
  streamIdleTimeoutMs: resolveStreamIdleTimeout(binding) ?? 60_000,
41
43
  providerRetries: resolveProviderRetryPolicy(binding),
@@ -0,0 +1,2 @@
1
+ export * from "./policy.js";
2
+ export * from "./validation.js";
@@ -0,0 +1,2 @@
1
+ export * from "./policy.js";
2
+ export * from "./validation.js";
@@ -0,0 +1,2 @@
1
+ import type { CompiledAgentBinding, RuntimeToolExecutionPolicy } from "../../../contracts/types.js";
2
+ export declare function projectBindingToolGatewayPolicy(binding: CompiledAgentBinding): RuntimeToolExecutionPolicy["gateway"];
@@ -0,0 +1,45 @@
1
+ import { toolRequiresRuntimeApproval } from "../../adapter/tool/tool-hitl.js";
2
+ import { getBindingPrimaryTools } from "../../support/compiled-binding.js";
3
+ import { compiledToolHasInputSchema } from "../tool-schema.js";
4
+ export function projectBindingToolGatewayPolicy(binding) {
5
+ const tools = getBindingPrimaryTools(binding).map((tool) => {
6
+ const hasInputSchema = compiledToolHasInputSchema(tool);
7
+ const requiresApproval = toolRequiresRuntimeApproval(tool);
8
+ return {
9
+ toolId: tool.id,
10
+ name: tool.name,
11
+ retryable: tool.retryable === true,
12
+ hasInputSchema,
13
+ requiresApproval,
14
+ gatewayMode: requiresApproval ? "approval-gated" : hasInputSchema ? "schema-first" : "best-effort",
15
+ modelRole: "propose",
16
+ runtimeRole: requiresApproval
17
+ ? "request-approval"
18
+ : hasInputSchema
19
+ ? "validate-and-execute"
20
+ : "execute-with-runtime-checks",
21
+ };
22
+ });
23
+ const schemaBoundToolCount = tools.filter((tool) => tool.hasInputSchema).length;
24
+ const approvalRequiredToolCount = tools.filter((tool) => tool.requiresApproval).length;
25
+ return {
26
+ layer: "tool-gateway",
27
+ toolScope: {
28
+ source: "agent-binding",
29
+ exposedToolCount: tools.length,
30
+ schemaBoundToolCount,
31
+ approvalRequiredToolCount,
32
+ },
33
+ validation: {
34
+ strategy: "schema-first",
35
+ runtimeValidationRequired: tools.some((tool) => !tool.hasInputSchema || tool.requiresApproval),
36
+ strictProviderSchemaPreferred: tools.length > 0,
37
+ },
38
+ correction: {
39
+ invalidArguments: "structured-error-retry",
40
+ maxModelRetries: 2,
41
+ highRiskInvalidArguments: "approval-or-deny",
42
+ },
43
+ tools,
44
+ };
45
+ }
@@ -0,0 +1,33 @@
1
+ export type ToolGatewayValidationIssue = {
2
+ path: string;
3
+ message: string;
4
+ expected?: string;
5
+ received?: string;
6
+ };
7
+ export type ToolGatewayValidationResult = {
8
+ ok: true;
9
+ input: unknown;
10
+ } | {
11
+ ok: false;
12
+ error: ToolGatewayInvalidArgumentsResult;
13
+ };
14
+ export type ToolGatewayInvalidArgumentsResult = {
15
+ isError: true;
16
+ code: "INVALID_ARGUMENTS";
17
+ toolName: string;
18
+ message: string;
19
+ retryable: boolean;
20
+ requiresApproval: boolean;
21
+ validationErrors: ToolGatewayValidationIssue[];
22
+ };
23
+ export declare function createInvalidToolArgumentsResult(input: {
24
+ toolName: string;
25
+ requiresApproval: boolean;
26
+ issues: ToolGatewayValidationIssue[];
27
+ }): ToolGatewayInvalidArgumentsResult;
28
+ export declare function validateToolGatewayInput(input: {
29
+ toolName: string;
30
+ schema: unknown;
31
+ args: unknown;
32
+ requiresApproval?: boolean;
33
+ }): ToolGatewayValidationResult;