@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.
- package/README.md +17 -0
- package/README.zh.md +17 -0
- package/dist/contracts/runtime-observability.d.ts +25 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/adapter/flow/stream-runtime.js +16 -1
- package/dist/runtime/adapter/local-tool-invocation.js +242 -14
- package/dist/runtime/adapter/middleware-assembly.js +1 -1
- package/dist/runtime/adapter/model/invocation-request.js +1 -1
- package/dist/runtime/adapter/model/model-providers.js +178 -119
- package/dist/runtime/adapter/stream-event-projection.js +70 -5
- package/dist/runtime/adapter/tool/tool-hitl.js +49 -6
- package/dist/runtime/agent-runtime-adapter.d.ts +2 -0
- package/dist/runtime/agent-runtime-adapter.js +329 -36
- package/dist/runtime/harness/bindings.js +2 -0
- package/dist/runtime/harness/tool-gateway/index.d.ts +2 -0
- package/dist/runtime/harness/tool-gateway/index.js +2 -0
- package/dist/runtime/harness/tool-gateway/policy.d.ts +2 -0
- package/dist/runtime/harness/tool-gateway/policy.js +45 -0
- package/dist/runtime/harness/tool-gateway/validation.d.ts +33 -0
- package/dist/runtime/harness/tool-gateway/validation.js +176 -0
- package/dist/runtime/parsing/output-recovery.js +1 -4
- package/package.json +15 -15
|
@@ -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}
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
||
|
|
45
|
-
||
|
|
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
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,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;
|