@botbotgo/agent-harness 0.0.403 → 0.0.405
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/dist/config/catalogs/embedding-models.yaml +1 -1
- package/dist/config/models.yaml +2 -2
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/adapter/flow/stream-runtime.js +14 -3
- package/dist/runtime/adapter/local-tool-invocation.js +1 -211
- package/dist/runtime/adapter/model/model-providers.js +8 -126
- package/dist/runtime/adapter/tool/tool-arguments.js +70 -2
- package/dist/runtime/parsing/output-tool-args.js +8 -3
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ spec:
|
|
|
13
13
|
# LangChain aligned feature: concrete embedding model identifier passed to the provider integration.
|
|
14
14
|
model: nomic-embed-text
|
|
15
15
|
# LangChain aligned feature: provider-specific initialization options for embeddings.
|
|
16
|
-
baseUrl:
|
|
16
|
+
baseUrl: http://127.0.0.1:11434
|
|
17
17
|
|
|
18
18
|
# ===================
|
|
19
19
|
# DeepAgents Features
|
package/dist/config/models.yaml
CHANGED
|
@@ -18,12 +18,12 @@ spec:
|
|
|
18
18
|
provider: ollama
|
|
19
19
|
# LangChain aligned feature: concrete model identifier passed to the selected provider integration.
|
|
20
20
|
# Example values depend on `provider`, such as `gpt-oss:latest` for `ollama`.
|
|
21
|
-
model:
|
|
21
|
+
model: granite4.1:3b
|
|
22
22
|
# LangChain aligned feature: provider-specific initialization options.
|
|
23
23
|
# Write these fields directly on the model object.
|
|
24
24
|
# Common examples include `baseUrl`, `temperature`, and auth/client settings.
|
|
25
25
|
# `baseUrl` configures the Ollama-compatible endpoint used by the model client.
|
|
26
26
|
# For `openai-compatible`, `baseUrl` is normalized into the ChatOpenAI `configuration.baseURL` field.
|
|
27
|
-
baseUrl:
|
|
27
|
+
baseUrl: http://127.0.0.1:11434
|
|
28
28
|
# LangChain aligned feature: provider/model initialization option controlling sampling temperature.
|
|
29
29
|
temperature: 0.2
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.405";
|
|
2
2
|
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-02";
|
package/dist/package-version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.405";
|
|
2
2
|
export const AGENT_HARNESS_RELEASE_DATE = "2026-05-02";
|
|
@@ -708,7 +708,9 @@ export async function* streamRuntimeExecution(options) {
|
|
|
708
708
|
}
|
|
709
709
|
const streamedDelegatedRecoveryInstruction = resolveDelegatedExecutionRecoveryInstruction(streamedExecutionEvidence);
|
|
710
710
|
const streamedDelegationOnlyRecoveryInstruction = resolveDelegationOnlyRecoveryInstruction(options.binding, streamedExecutionEvidence);
|
|
711
|
-
const streamedIncompletePlanRecoveryInstruction = requiresPlanEvidence(options.binding)
|
|
711
|
+
const streamedIncompletePlanRecoveryInstruction = requiresPlanEvidence(options.binding)
|
|
712
|
+
&& streamedExecutionEvidence.hasIncompletePlanState
|
|
713
|
+
&& streamedExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence
|
|
712
714
|
? CLOSE_REQUIRED_PLAN_RECOVERY_INSTRUCTION
|
|
713
715
|
: null;
|
|
714
716
|
const streamedPrematurePlanCloseRecoveryInstruction = requiresPlanEvidence(options.binding)
|
|
@@ -721,7 +723,8 @@ export async function* streamRuntimeExecution(options) {
|
|
|
721
723
|
: null;
|
|
722
724
|
if (hasUnresolvedExecution(streamedExecutionEvidence)
|
|
723
725
|
&& !delegatedExecutionRecoveryInstruction
|
|
724
|
-
&& !streamedIncompletePlanRecoveryInstruction
|
|
726
|
+
&& !streamedIncompletePlanRecoveryInstruction
|
|
727
|
+
&& !streamedPrematurePlanCloseRecoveryInstruction) {
|
|
725
728
|
throw createUnresolvedExecutionError(streamedExecutionEvidence);
|
|
726
729
|
}
|
|
727
730
|
const executionWithoutToolEvidenceInstruction = projectionState.emittedOutput
|
|
@@ -968,10 +971,18 @@ export async function* streamRuntimeExecution(options) {
|
|
|
968
971
|
})
|
|
969
972
|
: null;
|
|
970
973
|
const invokeFallbackDelegationOnlyRecoveryInstruction = resolveDelegationOnlyRecoveryInstruction(options.binding, invokeExecutionEvidence);
|
|
971
|
-
const invokeFallbackIncompletePlanRecoveryInstruction = requiresPlanEvidence(options.binding)
|
|
974
|
+
const invokeFallbackIncompletePlanRecoveryInstruction = requiresPlanEvidence(options.binding)
|
|
975
|
+
&& invokeExecutionEvidence.hasIncompletePlanState
|
|
976
|
+
&& invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence
|
|
972
977
|
? CLOSE_REQUIRED_PLAN_RECOVERY_INSTRUCTION
|
|
973
978
|
: null;
|
|
979
|
+
const invokeFallbackPlanWithoutEvidenceRecoveryInstruction = requiresPlanEvidence(options.binding)
|
|
980
|
+
&& invokeExecutionEvidence.hasPlanStateEvidence
|
|
981
|
+
&& !invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence
|
|
982
|
+
? RUN_EVIDENCE_AFTER_PREMATURE_PLAN_CLOSE_INSTRUCTION
|
|
983
|
+
: null;
|
|
974
984
|
const effectiveInvokeFallbackRecoveryInstruction = invokeFallbackIncompletePlanRecoveryInstruction
|
|
985
|
+
?? invokeFallbackPlanWithoutEvidenceRecoveryInstruction
|
|
975
986
|
?? invokeFallbackMissingPlanRecoveryInstruction
|
|
976
987
|
?? invokeFallbackDelegationOnlyRecoveryInstruction
|
|
977
988
|
?? invokeFallbackRecoveryInstruction;
|
|
@@ -10,9 +10,6 @@ import { appendToolRecoveryInstruction, extractVisibleOutput, resolveMissingPlan
|
|
|
10
10
|
import { salvageJsonToolCalls } from "../parsing/output-tool-args.js";
|
|
11
11
|
import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../prompts/runtime-prompts.js";
|
|
12
12
|
const TOOL_FOLLOW_UP_INSTRUCTION = "One or more tool results are already available in this conversation. Answer the user's current request directly from the existing context and tool results. Do not ask the user to repeat inputs that are already present above.";
|
|
13
|
-
function isObject(value) {
|
|
14
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
-
}
|
|
16
13
|
function readPlanStateSummary(output) {
|
|
17
14
|
if (typeof output !== "object" || output === null) {
|
|
18
15
|
return null;
|
|
@@ -61,159 +58,6 @@ function isFallbackTodoCompletionToolCall(toolCall) {
|
|
|
61
58
|
&& toolCall.id.startsWith("fallback-complete-")
|
|
62
59
|
&& (toolCall.name === "write_todos" || toolCall.name === "tool_call_write_todos");
|
|
63
60
|
}
|
|
64
|
-
function isTerminalTodoUpdateToolCall(toolCall) {
|
|
65
|
-
if (!isPlanToolName(toolCall.name) || normalizeToolName(toolCall.name).includes("read_todos")) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
if (typeof toolCall.args !== "object" || toolCall.args === null || !Array.isArray(toolCall.args.todos)) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
const todos = toolCall.args.todos;
|
|
72
|
-
return todos.length > 0 && todos.every((todo) => {
|
|
73
|
-
if (typeof todo !== "object" || todo === null || typeof todo.status !== "string") {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
const status = todo.status.trim().toLowerCase();
|
|
77
|
-
return status !== "pending" && status !== "in_progress";
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
function readSchemaShape(schema) {
|
|
81
|
-
if (!isObject(schema)) {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
if (isObject(schema.properties)) {
|
|
85
|
-
return schema.properties;
|
|
86
|
-
}
|
|
87
|
-
if (isObject(schema.shape)) {
|
|
88
|
-
return schema.shape;
|
|
89
|
-
}
|
|
90
|
-
const def = schema._def;
|
|
91
|
-
if (!def) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
95
|
-
return isObject(shape) ? shape : null;
|
|
96
|
-
}
|
|
97
|
-
function readSchemaDescription(schemaPart) {
|
|
98
|
-
if (!isObject(schemaPart)) {
|
|
99
|
-
return "";
|
|
100
|
-
}
|
|
101
|
-
const direct = schemaPart.description;
|
|
102
|
-
if (typeof direct === "string") {
|
|
103
|
-
return direct;
|
|
104
|
-
}
|
|
105
|
-
const nested = schemaPart._def;
|
|
106
|
-
if (typeof nested?.description === "string") {
|
|
107
|
-
return nested.description;
|
|
108
|
-
}
|
|
109
|
-
return readSchemaDescription(nested?.innerType);
|
|
110
|
-
}
|
|
111
|
-
function readSchemaDefault(schemaPart) {
|
|
112
|
-
if (!isObject(schemaPart)) {
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
const typed = schemaPart;
|
|
116
|
-
const hasJsonDefault = Object.prototype.hasOwnProperty.call(schemaPart, "default") && typeof typed.default !== "function";
|
|
117
|
-
if (hasJsonDefault) {
|
|
118
|
-
return typed.default;
|
|
119
|
-
}
|
|
120
|
-
if (Object.prototype.hasOwnProperty.call(schemaPart, "const")) {
|
|
121
|
-
return typed.const;
|
|
122
|
-
}
|
|
123
|
-
const def = schemaPart._def;
|
|
124
|
-
if (!def) {
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
if (def.defaultValue !== undefined) {
|
|
128
|
-
return typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
|
|
129
|
-
}
|
|
130
|
-
return readSchemaDefault(def.innerType);
|
|
131
|
-
}
|
|
132
|
-
function parseFirstStringArrayExample(description) {
|
|
133
|
-
const arrayMatch = description.match(/\[[^\]]+\]/u);
|
|
134
|
-
if (!arrayMatch) {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
const values = [...arrayMatch[0].matchAll(/["']([^"']+)["']/gu)].map((match) => match[1]).filter(Boolean);
|
|
138
|
-
return values.length > 0 ? values : null;
|
|
139
|
-
}
|
|
140
|
-
function buildGenericFallbackArgsFromSchema(schema, latestUserInput) {
|
|
141
|
-
const shape = readSchemaShape(schema);
|
|
142
|
-
if (!shape) {
|
|
143
|
-
return {};
|
|
144
|
-
}
|
|
145
|
-
const args = {};
|
|
146
|
-
for (const [key, schemaPart] of Object.entries(shape)) {
|
|
147
|
-
const defaultValue = readSchemaDefault(schemaPart);
|
|
148
|
-
if (defaultValue !== undefined) {
|
|
149
|
-
args[key] = defaultValue;
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
const description = readSchemaDescription(schemaPart);
|
|
153
|
-
const arrayExample = parseFirstStringArrayExample(description);
|
|
154
|
-
if (arrayExample) {
|
|
155
|
-
args[key] = arrayExample;
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
if (latestUserInput
|
|
159
|
-
&& !args[key]
|
|
160
|
-
&& /(?:query|question|prompt|input|text)/iu.test(`${key} ${description}`)) {
|
|
161
|
-
args[key] = latestUserInput;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return args;
|
|
165
|
-
}
|
|
166
|
-
function readTodoPlanTextFromToolCalls(toolCalls) {
|
|
167
|
-
const fragments = [];
|
|
168
|
-
for (const toolCall of toolCalls) {
|
|
169
|
-
if (typeof toolCall.args !== "object" || toolCall.args === null) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
const todos = toolCall.args.todos;
|
|
173
|
-
if (!Array.isArray(todos)) {
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
for (const todo of todos) {
|
|
177
|
-
if (typeof todo === "object" && todo !== null && typeof todo.content === "string") {
|
|
178
|
-
fragments.push(todo.content);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return fragments.join("\n");
|
|
183
|
-
}
|
|
184
|
-
function selectGenericFallbackEvidenceTool(params) {
|
|
185
|
-
const candidates = [];
|
|
186
|
-
const appendCandidate = (name) => {
|
|
187
|
-
if (isPlanToolName(name)) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const resolved = resolveModelFacingToolName(name, params.toolNameMapping, params.primaryTools);
|
|
191
|
-
const executable = params.executableTools.get(name)
|
|
192
|
-
?? params.executableTools.get(resolved)
|
|
193
|
-
?? params.builtinExecutableTools.get(name)
|
|
194
|
-
?? params.builtinExecutableTools.get(resolved);
|
|
195
|
-
if (!executable || candidates.some((candidate) => candidate.executable.name === executable.name)) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
candidates.push({ requestedName: name, executable });
|
|
199
|
-
};
|
|
200
|
-
for (const tool of params.primaryTools) {
|
|
201
|
-
appendCandidate(tool.name);
|
|
202
|
-
const modelFacing = params.toolNameMapping.originalToModelFacing.get(tool.name);
|
|
203
|
-
if (modelFacing) {
|
|
204
|
-
appendCandidate(modelFacing);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
for (const name of [...params.executableTools.keys(), ...params.builtinExecutableTools.keys()]) {
|
|
208
|
-
appendCandidate(name);
|
|
209
|
-
}
|
|
210
|
-
if (candidates.length === 0) {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
const normalizedPlanText = params.planText.toLowerCase();
|
|
214
|
-
return candidates.find((candidate) => normalizedPlanText.includes(candidate.requestedName.toLowerCase())
|
|
215
|
-
|| normalizedPlanText.includes(candidate.executable.name.toLowerCase())) ?? candidates[0];
|
|
216
|
-
}
|
|
217
61
|
function buildDeterministicFinalFromToolEvidence(executedToolResults) {
|
|
218
62
|
const evidence = executedToolResults
|
|
219
63
|
.filter((item) => item.isError !== true && item.toolName !== "write_todos" && item.toolName !== "read_todos")
|
|
@@ -294,19 +138,12 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
|
|
|
294
138
|
const hasIncompletePlanState = hasIncompleteExecutedPlan(executedToolResults);
|
|
295
139
|
const shouldEnforceIncompletePlan = requiresPlanEvidence(binding) && hasIncompletePlanState;
|
|
296
140
|
const hasExecutionBeyondTodoPlanning = hasNonTodoToolEvidence(executedToolResults);
|
|
297
|
-
const missingInitialPlanRecoveryInstruction = resolveMissingPlanRecoveryInstruction({
|
|
298
|
-
request: activeRequest,
|
|
299
|
-
requiresPlan: requiresPlanEvidence(binding),
|
|
300
|
-
hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
|
|
301
|
-
hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
|
|
302
|
-
hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
|
|
303
|
-
});
|
|
304
141
|
const toolErrorRecoveryInstruction = latestToolErrorRecoveryInstruction(executedToolResults)
|
|
305
142
|
?? terminalToolErrorRecoveryInstruction(terminalText);
|
|
306
143
|
const leakedJsonToolCallRecoveryInstruction = terminalText && salvageJsonToolCalls(terminalText).length > 0
|
|
307
144
|
? STRICT_TOOL_JSON_INSTRUCTION
|
|
308
145
|
: null;
|
|
309
|
-
const recoveryInstruction = toolErrorRecoveryInstruction ?? leakedJsonToolCallRecoveryInstruction ??
|
|
146
|
+
const recoveryInstruction = toolErrorRecoveryInstruction ?? leakedJsonToolCallRecoveryInstruction ?? (terminalText
|
|
310
147
|
? resolveExecutionWithoutToolEvidenceTextInstruction(activeRequest, terminalText, false, {
|
|
311
148
|
hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
|
|
312
149
|
hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
|
|
@@ -415,53 +252,6 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
|
|
|
415
252
|
content: stringifyToolOutput(safeToolResult),
|
|
416
253
|
}));
|
|
417
254
|
}
|
|
418
|
-
if (requiresPlanEvidence(binding)
|
|
419
|
-
&& !hadNonTodoEvidenceBeforeToolReplay
|
|
420
|
-
&& !hasNonTodoToolEvidence(executedToolResults)
|
|
421
|
-
&& toolCalls.length > 0
|
|
422
|
-
&& toolCalls.every((toolCall) => isPlanToolName(toolCall.name))
|
|
423
|
-
&& toolCalls.some(isTerminalTodoUpdateToolCall)) {
|
|
424
|
-
const fallbackEvidenceTool = selectGenericFallbackEvidenceTool({
|
|
425
|
-
planText: readTodoPlanTextFromToolCalls(toolCalls),
|
|
426
|
-
primaryTools,
|
|
427
|
-
toolNameMapping,
|
|
428
|
-
executableTools,
|
|
429
|
-
builtinExecutableTools,
|
|
430
|
-
});
|
|
431
|
-
if (fallbackEvidenceTool) {
|
|
432
|
-
const fallbackArgs = buildGenericFallbackArgsFromSchema(fallbackEvidenceTool.executable.schema, latestUserInput);
|
|
433
|
-
const normalizedArgs = normalizeToolArgsForSchema(fallbackArgs, fallbackEvidenceTool.executable.schema, undefined, {
|
|
434
|
-
latestUserInput,
|
|
435
|
-
});
|
|
436
|
-
const compiledTool = toolCatalog.get(fallbackEvidenceTool.requestedName) ?? toolCatalog.get(fallbackEvidenceTool.executable.name);
|
|
437
|
-
const gateway = validateToolGatewayInput({
|
|
438
|
-
toolName: fallbackEvidenceTool.executable.name,
|
|
439
|
-
schema: fallbackEvidenceTool.executable.schema,
|
|
440
|
-
args: normalizedArgs,
|
|
441
|
-
requiresApproval: compiledTool ? toolRequiresRuntimeApproval(compiledTool) : false,
|
|
442
|
-
});
|
|
443
|
-
if (gateway.ok) {
|
|
444
|
-
const toolResult = toolRuntimeContext
|
|
445
|
-
? await fallbackEvidenceTool.executable.invoke(gateway.input, { toolRuntimeContext })
|
|
446
|
-
: await fallbackEvidenceTool.executable.invoke(gateway.input);
|
|
447
|
-
const memoryCandidates = compiledTool ? extractMemoryCandidatesFromToolOutput(compiledTool, toolResult) : [];
|
|
448
|
-
const safeToolResult = await maybePersistLargeToolOutput({
|
|
449
|
-
toolName: fallbackEvidenceTool.executable.name,
|
|
450
|
-
output: toolResult,
|
|
451
|
-
toolRuntimeContext: toolRuntimeContext,
|
|
452
|
-
});
|
|
453
|
-
executedToolResults.push({
|
|
454
|
-
toolName: fallbackEvidenceTool.executable.name,
|
|
455
|
-
output: safeToolResult,
|
|
456
|
-
...(memoryCandidates.length > 0 ? { memoryCandidates } : {}),
|
|
457
|
-
});
|
|
458
|
-
return {
|
|
459
|
-
result: buildDeterministicFinalFromToolEvidence(executedToolResults),
|
|
460
|
-
executedToolResults,
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
255
|
if (requiresPlanEvidence(binding)
|
|
466
256
|
&& toolCalls.length > 0
|
|
467
257
|
&& toolCalls.every((toolCall) => isPlanToolName(toolCall.name))
|
|
@@ -253,26 +253,6 @@ function readLatestToolResultContent(input) {
|
|
|
253
253
|
}
|
|
254
254
|
return null;
|
|
255
255
|
}
|
|
256
|
-
function readLatestUserContent(input) {
|
|
257
|
-
if (Array.isArray(input)) {
|
|
258
|
-
for (let index = input.length - 1; index >= 0; index -= 1) {
|
|
259
|
-
const message = input[index];
|
|
260
|
-
if (mapMessageRole(message) !== "USER") {
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
const content = readPromptContent(message).trim();
|
|
264
|
-
if (content) {
|
|
265
|
-
return content;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return undefined;
|
|
269
|
-
}
|
|
270
|
-
if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
|
|
271
|
-
return readLatestUserContent(input.messages);
|
|
272
|
-
}
|
|
273
|
-
const content = readPromptContent(input).trim();
|
|
274
|
-
return content || undefined;
|
|
275
|
-
}
|
|
276
256
|
function readBoundToolName(tool) {
|
|
277
257
|
return typeof tool === "object" && tool !== null && typeof tool.name === "string"
|
|
278
258
|
? tool.name.trim()
|
|
@@ -598,33 +578,10 @@ function isLowSignalPlanningLine(value) {
|
|
|
598
578
|
|| /^#+\s*/.test(normalized)
|
|
599
579
|
|| /^(?:ok|okay|sure|understood|got it|plan|todo|steps?)\.?$/.test(normalized));
|
|
600
580
|
}
|
|
601
|
-
function
|
|
602
|
-
return name.startsWith("tool_call_") ? name.slice("tool_call_".length) : name;
|
|
603
|
-
}
|
|
604
|
-
function selectFallbackEvidenceToolName(tools, rawText = "") {
|
|
605
|
-
const normalizedRawText = rawText.toLowerCase();
|
|
606
|
-
const available = tools
|
|
607
|
-
.map((tool) => normalizeDomainToolName(readBoundToolName(tool)))
|
|
608
|
-
.filter((name) => name && !isTodoPlanningToolName(name) && !isTodoPlanningToolName(`tool_call_${name}`));
|
|
609
|
-
if (available.length === 0) {
|
|
610
|
-
return null;
|
|
611
|
-
}
|
|
612
|
-
const mentioned = available.find((name) => normalizedRawText.includes(name.toLowerCase()));
|
|
613
|
-
return mentioned ?? available[0] ?? null;
|
|
614
|
-
}
|
|
615
|
-
function buildToolAwareFallbackTodoContents(tools, rawText = "") {
|
|
616
|
-
const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
|
|
617
|
-
if (!evidenceToolName) {
|
|
618
|
-
return [
|
|
619
|
-
"Identify the concrete evidence tool required for this request",
|
|
620
|
-
"Run the selected non-planning evidence tool and inspect its result",
|
|
621
|
-
"Update TODO status from the observed evidence",
|
|
622
|
-
"Return the final answer grounded in tool output",
|
|
623
|
-
];
|
|
624
|
-
}
|
|
581
|
+
function buildFallbackTodoContents() {
|
|
625
582
|
return [
|
|
626
|
-
|
|
627
|
-
|
|
583
|
+
"Identify the concrete evidence tool required for this request",
|
|
584
|
+
"Run the selected non-planning evidence tool and inspect its result",
|
|
628
585
|
"Update TODO status from the observed evidence",
|
|
629
586
|
"Return the final answer grounded in tool output",
|
|
630
587
|
];
|
|
@@ -632,7 +589,7 @@ function buildToolAwareFallbackTodoContents(tools, rawText = "") {
|
|
|
632
589
|
function isGenericFallbackTodoContent(value) {
|
|
633
590
|
return /^(?:gather concrete evidence|inspect the most relevant runtime signals|analyze (?:the )?evidence|produce the final rca)/i.test(value.trim());
|
|
634
591
|
}
|
|
635
|
-
function buildFallbackPlanningToolCall(input, planningTools,
|
|
592
|
+
function buildFallbackPlanningToolCall(input, planningTools, rawText) {
|
|
636
593
|
const toolName = planningTools.map((tool) => readBoundToolName(tool)).find((name) => name === "write_todos" || name === "tool_call_write_todos");
|
|
637
594
|
if (!toolName) {
|
|
638
595
|
return null;
|
|
@@ -641,7 +598,7 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
|
|
|
641
598
|
const hasUsefulModelPlan = modelPlannedItems.length >= 2 && !modelPlannedItems.every(isGenericFallbackTodoContent);
|
|
642
599
|
const fallbackItems = hasUsefulModelPlan
|
|
643
600
|
? modelPlannedItems
|
|
644
|
-
:
|
|
601
|
+
: buildFallbackTodoContents();
|
|
645
602
|
const todos = fallbackItems.map((content, index) => ({
|
|
646
603
|
content,
|
|
647
604
|
status: index === 0 ? "in_progress" : "pending",
|
|
@@ -656,63 +613,6 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
|
|
|
656
613
|
}],
|
|
657
614
|
});
|
|
658
615
|
}
|
|
659
|
-
function buildFallbackEvidenceToolArgs(tool, latestUserInput) {
|
|
660
|
-
const schema = normalizeModelFacingToolSchema(tool);
|
|
661
|
-
if (typeof schema.properties !== "object" || schema.properties === null || Array.isArray(schema.properties)) {
|
|
662
|
-
return {};
|
|
663
|
-
}
|
|
664
|
-
const required = Array.isArray(schema.required)
|
|
665
|
-
? schema.required.filter((name) => typeof name === "string")
|
|
666
|
-
: [];
|
|
667
|
-
const values = {};
|
|
668
|
-
for (const [key, value] of Object.entries(schema.properties)) {
|
|
669
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
const property = value;
|
|
673
|
-
if ("default" in property) {
|
|
674
|
-
values[key] = property.default;
|
|
675
|
-
continue;
|
|
676
|
-
}
|
|
677
|
-
if ("const" in property) {
|
|
678
|
-
values[key] = property.const;
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (required.includes(key) && Array.isArray(property.enum) && property.enum.length > 0) {
|
|
682
|
-
values[key] = property.enum[0];
|
|
683
|
-
continue;
|
|
684
|
-
}
|
|
685
|
-
if (latestUserInput
|
|
686
|
-
&& !values[key]
|
|
687
|
-
&& /(?:query|question|prompt|input|text)/iu.test(`${key} ${typeof property.description === "string" ? property.description : ""}`)) {
|
|
688
|
-
values[key] = latestUserInput;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
return values;
|
|
692
|
-
}
|
|
693
|
-
function buildFallbackEvidenceToolCall(input, tools, rawText = "") {
|
|
694
|
-
if (!hasPriorToolResultForToolName(input, "write_todos") && !hasPriorToolResultForToolName(input, "tool_call_write_todos")) {
|
|
695
|
-
return null;
|
|
696
|
-
}
|
|
697
|
-
const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
|
|
698
|
-
if (!evidenceToolName) {
|
|
699
|
-
return null;
|
|
700
|
-
}
|
|
701
|
-
const boundTool = tools.find((tool) => normalizeDomainToolName(readBoundToolName(tool)) === evidenceToolName);
|
|
702
|
-
const boundToolName = readBoundToolName(boundTool);
|
|
703
|
-
if (!boundTool || !boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
|
-
return new AIMessage({
|
|
707
|
-
content: "",
|
|
708
|
-
tool_calls: [{
|
|
709
|
-
id: `fallback-evidence-${Math.random().toString(36).slice(2, 10)}`,
|
|
710
|
-
name: boundToolName,
|
|
711
|
-
args: buildFallbackEvidenceToolArgs(boundTool, readLatestUserContent(input)),
|
|
712
|
-
type: "tool_call",
|
|
713
|
-
}],
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
616
|
function buildFallbackTodoCompletionToolCall(input, tools) {
|
|
717
617
|
const prompt = stringifyNodeLlamaCppInput(input);
|
|
718
618
|
if (/TODO completed:|\[x\]/i.test(prompt)) {
|
|
@@ -722,17 +622,10 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
|
|
|
722
622
|
if (!planningToolName) {
|
|
723
623
|
return null;
|
|
724
624
|
}
|
|
725
|
-
|
|
726
|
-
if (!evidenceToolName) {
|
|
727
|
-
return null;
|
|
728
|
-
}
|
|
729
|
-
const hasEvidenceResult = hasPriorToolResultForToolName(input, evidenceToolName)
|
|
730
|
-
|| hasPriorToolResultForToolName(input, `tool_call_${evidenceToolName}`)
|
|
731
|
-
|| hasPriorNonPlanningToolResult(input, tools);
|
|
732
|
-
if (!hasEvidenceResult) {
|
|
625
|
+
if (!hasPriorNonPlanningToolResult(input, tools)) {
|
|
733
626
|
return null;
|
|
734
627
|
}
|
|
735
|
-
const todos =
|
|
628
|
+
const todos = buildFallbackTodoContents().map((content) => ({
|
|
736
629
|
content,
|
|
737
630
|
status: "completed",
|
|
738
631
|
}));
|
|
@@ -848,7 +741,7 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
848
741
|
const parsedToolCall = normalizeParsedToolCall(extractToolCallPayload(text));
|
|
849
742
|
if (!parsedToolCall) {
|
|
850
743
|
if (forcePlanningToolCall) {
|
|
851
|
-
const fallbackToolCall = buildFallbackPlanningToolCall(input, effectiveBoundTools,
|
|
744
|
+
const fallbackToolCall = buildFallbackPlanningToolCall(input, effectiveBoundTools, text);
|
|
852
745
|
if (fallbackToolCall) {
|
|
853
746
|
return fallbackToolCall;
|
|
854
747
|
}
|
|
@@ -858,23 +751,12 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
858
751
|
if (fallbackCompletionToolCall) {
|
|
859
752
|
return fallbackCompletionToolCall;
|
|
860
753
|
}
|
|
861
|
-
const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
|
|
862
|
-
if (fallbackToolCall) {
|
|
863
|
-
return fallbackToolCall;
|
|
864
|
-
}
|
|
865
754
|
}
|
|
866
755
|
return rawResult;
|
|
867
756
|
}
|
|
868
757
|
const effectiveParsedToolCall = forcePlanningToolCall
|
|
869
758
|
? normalizeInitialTodoPlanToolCall(parsedToolCall)
|
|
870
759
|
: parsedToolCall;
|
|
871
|
-
if (parsedToolCallCompletesTodoPlan(effectiveParsedToolCall)
|
|
872
|
-
&& !hasPriorNonPlanningToolResult(input, effectiveBoundTools)) {
|
|
873
|
-
const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
|
|
874
|
-
if (fallbackToolCall) {
|
|
875
|
-
return fallbackToolCall;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
760
|
if (hasPriorToolResultForToolName(input, effectiveParsedToolCall.name) || hasAnyPriorToolResult(input)) {
|
|
879
761
|
return rawResult;
|
|
880
762
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { salvageToolArgs } from "../../parsing/output-parsing.js";
|
|
2
|
+
import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
|
|
2
3
|
import { isRecord } from "../../../utils/object.js";
|
|
3
4
|
import { extractExplicitResourceReferences, hasExplicitResourceReference } from "../../harness/system/runtime-memory-policy.js";
|
|
4
5
|
function isObject(value) {
|
|
@@ -70,6 +71,44 @@ function mapSingleRemainingScalarArg(args, expectedKey) {
|
|
|
70
71
|
[expectedKey]: value,
|
|
71
72
|
};
|
|
72
73
|
}
|
|
74
|
+
function readSchemaDescription(schemaPart) {
|
|
75
|
+
if (!isObject(schemaPart)) {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
const direct = schemaPart.description;
|
|
79
|
+
if (typeof direct === "string") {
|
|
80
|
+
return direct;
|
|
81
|
+
}
|
|
82
|
+
const def = schemaPart._def;
|
|
83
|
+
if (typeof def?.description === "string") {
|
|
84
|
+
return def.description;
|
|
85
|
+
}
|
|
86
|
+
return readSchemaDescription(def?.innerType);
|
|
87
|
+
}
|
|
88
|
+
function fillLatestUserInputForQueryLikeFields(args, shape, latestUserInput) {
|
|
89
|
+
const userInput = typeof latestUserInput === "string" ? latestUserInput.trim() : "";
|
|
90
|
+
if (!userInput) {
|
|
91
|
+
return args;
|
|
92
|
+
}
|
|
93
|
+
let next = args;
|
|
94
|
+
for (const [key, schemaPart] of Object.entries(shape)) {
|
|
95
|
+
if (key in next) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
99
|
+
const description = readSchemaDescription(schemaPart);
|
|
100
|
+
const keyIsQueryLike = ["query", "question", "prompt", "input", "text"].includes(normalizedKey);
|
|
101
|
+
const descriptionIsQueryLike = /\b(?:query|question|prompt|input|text)\b/iu.test(description);
|
|
102
|
+
if (!keyIsQueryLike && !descriptionIsQueryLike) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
next = {
|
|
106
|
+
...next,
|
|
107
|
+
[key]: userInput,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return next;
|
|
111
|
+
}
|
|
73
112
|
export function normalizeToolArgsForSchema(args, schema, rawArgsInput, options = {}) {
|
|
74
113
|
const schemaDef = isObject(schema) ? schema._def : undefined;
|
|
75
114
|
const zodShape = schemaDef
|
|
@@ -88,7 +127,7 @@ export function normalizeToolArgsForSchema(args, schema, rawArgsInput, options =
|
|
|
88
127
|
}
|
|
89
128
|
const keys = Object.keys(shape);
|
|
90
129
|
if (keys.length !== 1) {
|
|
91
|
-
return args;
|
|
130
|
+
return fillLatestUserInputForQueryLikeFields(args, shape, options.latestUserInput);
|
|
92
131
|
}
|
|
93
132
|
const [expectedKey] = keys;
|
|
94
133
|
if (expectedKey in args) {
|
|
@@ -126,7 +165,7 @@ export function extractToolCallsFromResult(result) {
|
|
|
126
165
|
: Array.isArray(messageKwargs?.tool_calls)
|
|
127
166
|
? messageKwargs.tool_calls
|
|
128
167
|
: [];
|
|
129
|
-
|
|
168
|
+
const extracted = rawToolCalls
|
|
130
169
|
.map((toolCall) => {
|
|
131
170
|
if (!isObject(toolCall)) {
|
|
132
171
|
return null;
|
|
@@ -149,4 +188,33 @@ export function extractToolCallsFromResult(result) {
|
|
|
149
188
|
return { id, name, args: rawArgs, rawArgsInput };
|
|
150
189
|
})
|
|
151
190
|
.filter((item) => item !== null);
|
|
191
|
+
if (extracted.length > 0) {
|
|
192
|
+
return extracted;
|
|
193
|
+
}
|
|
194
|
+
const directRole = typeof lastMessage.role === "string"
|
|
195
|
+
? lastMessage.role.trim().toLowerCase()
|
|
196
|
+
: undefined;
|
|
197
|
+
const messageType = typeof lastMessage._getType === "function"
|
|
198
|
+
? String(lastMessage._getType())
|
|
199
|
+
: undefined;
|
|
200
|
+
const constructorType = Array.isArray(lastMessage.id)
|
|
201
|
+
? lastMessage.id.at(-1)
|
|
202
|
+
: undefined;
|
|
203
|
+
if (directRole === "tool" || messageType === "tool" || constructorType === "ToolMessage") {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const content = typeof lastMessage.content === "string"
|
|
207
|
+
? lastMessage.content
|
|
208
|
+
: typeof messageKwargs?.content === "string"
|
|
209
|
+
? messageKwargs.content
|
|
210
|
+
: "";
|
|
211
|
+
if (!content.trim()) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
return salvageJsonToolCalls(content).map((toolCall, index) => ({
|
|
215
|
+
id: `salvaged-json-${index + 1}`,
|
|
216
|
+
name: toolCall.name,
|
|
217
|
+
args: toolCall.args,
|
|
218
|
+
rawArgsInput: content,
|
|
219
|
+
}));
|
|
152
220
|
}
|
|
@@ -420,12 +420,17 @@ function normalizeWriteTodosArgs(args) {
|
|
|
420
420
|
? record.content
|
|
421
421
|
: typeof record.description === "string" && record.description.trim().length > 0
|
|
422
422
|
? record.description
|
|
423
|
-
:
|
|
423
|
+
: typeof record.title === "string" && record.title.trim().length > 0
|
|
424
|
+
? record.title
|
|
425
|
+
: typeof record.name === "string" && record.name.trim().length > 0
|
|
426
|
+
? record.name
|
|
427
|
+
: typeof record.text === "string" && record.text.trim().length > 0
|
|
428
|
+
? record.text
|
|
429
|
+
: `Step ${index + 1}`;
|
|
424
430
|
const normalized = {};
|
|
425
431
|
if (content !== undefined)
|
|
426
432
|
normalized.content = content;
|
|
427
|
-
|
|
428
|
-
normalized.status = record.status;
|
|
433
|
+
normalized.status = typeof record.status === "string" && record.status.trim().length > 0 ? record.status : "pending";
|
|
429
434
|
return Object.keys(normalized).length > 0 ? normalized : todo;
|
|
430
435
|
}),
|
|
431
436
|
};
|