@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
|
@@ -211,6 +211,68 @@ function hasPriorToolResultForToolName(input, toolName) {
|
|
|
211
211
|
}
|
|
212
212
|
return false;
|
|
213
213
|
}
|
|
214
|
+
function hasAnyPriorToolResult(input) {
|
|
215
|
+
if (Array.isArray(input)) {
|
|
216
|
+
return input.some((message) => mapMessageRole(message) === "TOOL");
|
|
217
|
+
}
|
|
218
|
+
if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
|
|
219
|
+
return hasAnyPriorToolResult(input.messages);
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
function hasPriorNonPlanningToolCall(input) {
|
|
224
|
+
if (Array.isArray(input)) {
|
|
225
|
+
return input.some((message) => readToolCalls(message).some((toolCall) => {
|
|
226
|
+
if (typeof toolCall !== "object" || toolCall === null) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const name = typeof toolCall.name === "string"
|
|
230
|
+
? toolCall.name
|
|
231
|
+
: "";
|
|
232
|
+
return name.length > 0 && !isTodoPlanningToolName(name);
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
|
|
236
|
+
return hasPriorNonPlanningToolCall(input.messages);
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
function readLatestToolResultContent(input) {
|
|
241
|
+
if (Array.isArray(input)) {
|
|
242
|
+
for (let index = input.length - 1; index >= 0; index -= 1) {
|
|
243
|
+
const message = input[index];
|
|
244
|
+
if (mapMessageRole(message) === "TOOL") {
|
|
245
|
+
const content = readPromptContent(message);
|
|
246
|
+
return content || null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
|
|
252
|
+
return readLatestToolResultContent(input.messages);
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
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
|
+
}
|
|
214
276
|
function readBoundToolName(tool) {
|
|
215
277
|
return typeof tool === "object" && tool !== null && typeof tool.name === "string"
|
|
216
278
|
? tool.name.trim()
|
|
@@ -238,6 +300,12 @@ function selectPlanningToolsForTurn(input, boundTools) {
|
|
|
238
300
|
if (!shouldLimitToolsToPlanning(input)) {
|
|
239
301
|
return boundTools;
|
|
240
302
|
}
|
|
303
|
+
if (!boundTools.some((tool) => {
|
|
304
|
+
const name = readBoundToolName(tool);
|
|
305
|
+
return name && !isTodoPlanningToolName(name);
|
|
306
|
+
})) {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
241
309
|
const planningTools = boundTools.filter((tool) => isTodoPlanningToolName(readBoundToolName(tool)));
|
|
242
310
|
return planningTools.length > 0 ? planningTools : boundTools;
|
|
243
311
|
}
|
|
@@ -357,47 +425,6 @@ function stringifyNodeLlamaCppInput(input) {
|
|
|
357
425
|
}
|
|
358
426
|
return readPromptContent(input);
|
|
359
427
|
}
|
|
360
|
-
function readLatestUserPromptContent(input) {
|
|
361
|
-
const messages = typeof input === "object"
|
|
362
|
-
&& input !== null
|
|
363
|
-
&& Array.isArray(input.messages)
|
|
364
|
-
? input.messages
|
|
365
|
-
: Array.isArray(input)
|
|
366
|
-
? input
|
|
367
|
-
: null;
|
|
368
|
-
if (!messages) {
|
|
369
|
-
return readPromptContent(input);
|
|
370
|
-
}
|
|
371
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
372
|
-
if (mapMessageRole(messages[index]) !== "USER") {
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
const content = readPromptContent(messages[index]);
|
|
376
|
-
if (content) {
|
|
377
|
-
return content;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
return stringifyNodeLlamaCppInput(input);
|
|
381
|
-
}
|
|
382
|
-
function inferStockRequest(input) {
|
|
383
|
-
const prompt = readLatestUserPromptContent(input);
|
|
384
|
-
const directSymbol = prompt.match(/\b[A-Z]{1,5}(?:\.[A-Z])?\b/u)?.[0];
|
|
385
|
-
const lower = prompt.toLowerCase();
|
|
386
|
-
const enterpriseHcmName = ["work", "day"].join("");
|
|
387
|
-
const symbol = directSymbol
|
|
388
|
-
?? (/\bapple\b/u.test(lower) || /苹果/u.test(prompt) ? "AAPL" : undefined)
|
|
389
|
-
?? (new RegExp(`\\b${enterpriseHcmName}\\b`, "u").test(lower) ? "WDAY" : undefined);
|
|
390
|
-
const company = symbol === "AAPL"
|
|
391
|
-
? "Apple Inc."
|
|
392
|
-
: symbol === "WDAY"
|
|
393
|
-
? `${enterpriseHcmName[0].toUpperCase()}${enterpriseHcmName.slice(1)} Inc.`
|
|
394
|
-
: undefined;
|
|
395
|
-
return {
|
|
396
|
-
...(symbol ? { symbol } : {}),
|
|
397
|
-
...(company ? { company } : {}),
|
|
398
|
-
query: symbol ? `${company ?? symbol} ${symbol} stock briefing` : prompt || "public company stock briefing",
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
428
|
function extractToolCallPayload(text) {
|
|
402
429
|
const trimmed = text.trim();
|
|
403
430
|
if (!trimmed) {
|
|
@@ -580,34 +607,19 @@ function isLowSignalPlanningLine(value) {
|
|
|
580
607
|
function normalizeDomainToolName(name) {
|
|
581
608
|
return name.startsWith("tool_call_") ? name.slice("tool_call_".length) : name;
|
|
582
609
|
}
|
|
583
|
-
function selectFallbackEvidenceToolName(
|
|
584
|
-
const
|
|
610
|
+
function selectFallbackEvidenceToolName(tools, rawText = "") {
|
|
611
|
+
const normalizedRawText = rawText.toLowerCase();
|
|
585
612
|
const available = tools
|
|
586
613
|
.map((tool) => normalizeDomainToolName(readBoundToolName(tool)))
|
|
587
614
|
.filter((name) => name && !isTodoPlanningToolName(name) && !isTodoPlanningToolName(`tool_call_${name}`));
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
[/\b(k8s|kubernetes|kubectl|pod|pods|node|nodes|cluster)\b|集群|节点|调度/u, ["k8s_cluster_investigate", "k8s_cluster_triage", "kubectl_command"]],
|
|
591
|
-
[/\b(disk|cache|storage|workspace)\b|磁盘|缓存|占用/u, ["disk_space_investigate"]],
|
|
592
|
-
[/\b(agent config|agent configuration|repository structure|repo structure|code evidence|specialist)\b|agent\s*配置|配置结构|新增\s*specialist|代码层证据|改哪些文件/u, ["git_command", "codex_repo_analysis"]],
|
|
593
|
-
[/\b(stock|ticker|finance|market brief|aapl|wday)\b|股票|公开股票简报|金融|市场简报/u, ["finance_stock_report", "finance_quote_snapshot"]],
|
|
594
|
-
[/\b(test|qa|coverage|regression|playwright)\b|测试|覆盖率|回归|验证/u, ["git_command", "codex_repo_analysis", "playwright_capture"]],
|
|
595
|
-
[/\b(release|readiness|branch|tag|github actions|ci)\b|发版|分支|流水线|可发版/u, ["git_command", "gh_actions_command"]],
|
|
596
|
-
[/\b(youtube|transcript|brief|briefing|summary)\b|摘要|简报|讲稿/u, ["youtube_video_summary", "llamaindex_source_analysis", "finance_stock_report"]],
|
|
597
|
-
];
|
|
598
|
-
for (const [pattern, toolNames] of rules) {
|
|
599
|
-
if (!pattern.test(prompt)) {
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
const matched = toolNames.find((name) => hasTool(name));
|
|
603
|
-
if (matched) {
|
|
604
|
-
return matched;
|
|
605
|
-
}
|
|
615
|
+
if (available.length === 0) {
|
|
616
|
+
return null;
|
|
606
617
|
}
|
|
607
|
-
|
|
618
|
+
const mentioned = available.find((name) => normalizedRawText.includes(name.toLowerCase()));
|
|
619
|
+
return mentioned ?? available[0] ?? null;
|
|
608
620
|
}
|
|
609
|
-
function buildToolAwareFallbackTodoContents(
|
|
610
|
-
const evidenceToolName = selectFallbackEvidenceToolName(
|
|
621
|
+
function buildToolAwareFallbackTodoContents(tools, rawText = "") {
|
|
622
|
+
const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
|
|
611
623
|
if (!evidenceToolName) {
|
|
612
624
|
return [
|
|
613
625
|
"Identify the concrete evidence tool required for this request",
|
|
@@ -633,9 +645,13 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
|
|
|
633
645
|
}
|
|
634
646
|
const modelPlannedItems = extractFallbackTodoContentsFromText(rawText);
|
|
635
647
|
const hasUsefulModelPlan = modelPlannedItems.length >= 2 && !modelPlannedItems.every(isGenericFallbackTodoContent);
|
|
648
|
+
const hasEvidenceTool = selectFallbackEvidenceToolName(allTools, rawText) !== null;
|
|
649
|
+
if (!hasUsefulModelPlan && !hasEvidenceTool) {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
636
652
|
const fallbackItems = hasUsefulModelPlan
|
|
637
653
|
? modelPlannedItems
|
|
638
|
-
: buildToolAwareFallbackTodoContents(
|
|
654
|
+
: buildToolAwareFallbackTodoContents(allTools, rawText);
|
|
639
655
|
const todos = fallbackItems.map((content, index) => ({
|
|
640
656
|
content,
|
|
641
657
|
status: index === 0 ? "in_progress" : "pending",
|
|
@@ -650,60 +666,51 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
|
|
|
650
666
|
}],
|
|
651
667
|
});
|
|
652
668
|
}
|
|
653
|
-
function buildFallbackEvidenceToolArgs(
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
if (/\b(agent config|agent configuration|specialist)\b|agent\s*配置|配置结构|新增\s*specialist|代码层证据|改哪些文件/u.test(lower)) {
|
|
658
|
-
return {
|
|
659
|
-
args: ["ls-files", "config/agents", "config/agent-context.md", "config/runtime", "config/models.yaml"],
|
|
660
|
-
cwd: ".",
|
|
661
|
-
timeoutMs: 10000,
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
|
-
return { args: ["status", "--short"], cwd: ".", timeoutMs: 10000 };
|
|
665
|
-
}
|
|
666
|
-
if (toolName === "disk_space_investigate") {
|
|
667
|
-
return { targetPath: ".", cwd: ".", timeoutMs: 10000 };
|
|
668
|
-
}
|
|
669
|
-
if (toolName === "finance_stock_report") {
|
|
670
|
-
return {
|
|
671
|
-
...inferStockRequest(input),
|
|
672
|
-
market: "us",
|
|
673
|
-
count: 5,
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
if (toolName === "codex_repo_analysis") {
|
|
677
|
-
return {
|
|
678
|
-
repoPath: ".",
|
|
679
|
-
question: "Analyze the repository structure and provide concrete file-level evidence for the requested code/configuration question.",
|
|
680
|
-
timeoutMs: 30000,
|
|
681
|
-
skipGitRepoCheck: true,
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
if (toolName === "k8s_cluster_investigate") {
|
|
685
|
-
return { timeoutMs: 30000 };
|
|
686
|
-
}
|
|
687
|
-
if (toolName === "k8s_cluster_triage") {
|
|
688
|
-
return { timeoutMs: 30000 };
|
|
669
|
+
function buildFallbackEvidenceToolArgs(tool, latestUserInput) {
|
|
670
|
+
const schema = normalizeModelFacingToolSchema(tool);
|
|
671
|
+
if (typeof schema.properties !== "object" || schema.properties === null || Array.isArray(schema.properties)) {
|
|
672
|
+
return {};
|
|
689
673
|
}
|
|
690
|
-
|
|
691
|
-
|
|
674
|
+
const required = Array.isArray(schema.required)
|
|
675
|
+
? schema.required.filter((name) => typeof name === "string")
|
|
676
|
+
: [];
|
|
677
|
+
const values = {};
|
|
678
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
679
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const property = value;
|
|
683
|
+
if ("default" in property) {
|
|
684
|
+
values[key] = property.default;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if ("const" in property) {
|
|
688
|
+
values[key] = property.const;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (required.includes(key) && Array.isArray(property.enum) && property.enum.length > 0) {
|
|
692
|
+
values[key] = property.enum[0];
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (latestUserInput
|
|
696
|
+
&& !values[key]
|
|
697
|
+
&& /(?:query|question|prompt|input|text)/iu.test(`${key} ${typeof property.description === "string" ? property.description : ""}`)) {
|
|
698
|
+
values[key] = latestUserInput;
|
|
699
|
+
}
|
|
692
700
|
}
|
|
693
|
-
return
|
|
701
|
+
return values;
|
|
694
702
|
}
|
|
695
|
-
function buildFallbackEvidenceToolCall(input, tools) {
|
|
703
|
+
function buildFallbackEvidenceToolCall(input, tools, rawText = "") {
|
|
696
704
|
if (!hasPriorToolResultForToolName(input, "write_todos") && !hasPriorToolResultForToolName(input, "tool_call_write_todos")) {
|
|
697
705
|
return null;
|
|
698
706
|
}
|
|
699
|
-
const evidenceToolName = selectFallbackEvidenceToolName(
|
|
707
|
+
const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
|
|
700
708
|
if (!evidenceToolName) {
|
|
701
709
|
return null;
|
|
702
710
|
}
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
if (!boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
|
|
711
|
+
const boundTool = tools.find((tool) => normalizeDomainToolName(readBoundToolName(tool)) === evidenceToolName);
|
|
712
|
+
const boundToolName = readBoundToolName(boundTool);
|
|
713
|
+
if (!boundTool || !boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
|
|
707
714
|
return null;
|
|
708
715
|
}
|
|
709
716
|
return new AIMessage({
|
|
@@ -711,7 +718,7 @@ function buildFallbackEvidenceToolCall(input, tools) {
|
|
|
711
718
|
tool_calls: [{
|
|
712
719
|
id: `fallback-evidence-${Math.random().toString(36).slice(2, 10)}`,
|
|
713
720
|
name: boundToolName,
|
|
714
|
-
args: buildFallbackEvidenceToolArgs(
|
|
721
|
+
args: buildFallbackEvidenceToolArgs(boundTool, readLatestUserContent(input)),
|
|
715
722
|
type: "tool_call",
|
|
716
723
|
}],
|
|
717
724
|
});
|
|
@@ -725,7 +732,7 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
|
|
|
725
732
|
if (!planningToolName) {
|
|
726
733
|
return null;
|
|
727
734
|
}
|
|
728
|
-
const evidenceToolName = selectFallbackEvidenceToolName(
|
|
735
|
+
const evidenceToolName = selectFallbackEvidenceToolName(tools, prompt);
|
|
729
736
|
if (!evidenceToolName) {
|
|
730
737
|
return null;
|
|
731
738
|
}
|
|
@@ -735,7 +742,7 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
|
|
|
735
742
|
if (!hasEvidenceResult) {
|
|
736
743
|
return null;
|
|
737
744
|
}
|
|
738
|
-
const todos = buildToolAwareFallbackTodoContents(
|
|
745
|
+
const todos = buildToolAwareFallbackTodoContents(tools, prompt).map((content) => ({
|
|
739
746
|
content,
|
|
740
747
|
status: "completed",
|
|
741
748
|
}));
|
|
@@ -749,6 +756,33 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
|
|
|
749
756
|
}],
|
|
750
757
|
});
|
|
751
758
|
}
|
|
759
|
+
function parsedToolCallCompletesTodoPlan(toolCall) {
|
|
760
|
+
if (!isTodoPlanningToolName(toolCall.name)) {
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
const todos = toolCall.args.todos;
|
|
764
|
+
if (!Array.isArray(todos) || todos.length === 0) {
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
return todos.every((todo) => typeof todo === "object"
|
|
768
|
+
&& todo !== null
|
|
769
|
+
&& todo.status === "completed");
|
|
770
|
+
}
|
|
771
|
+
function normalizeInitialTodoPlanToolCall(toolCall) {
|
|
772
|
+
if (!parsedToolCallCompletesTodoPlan(toolCall)) {
|
|
773
|
+
return toolCall;
|
|
774
|
+
}
|
|
775
|
+
const todos = toolCall.args.todos.map((todo, index) => typeof todo === "object" && todo !== null && !Array.isArray(todo)
|
|
776
|
+
? { ...todo, status: index === 0 ? "in_progress" : "pending" }
|
|
777
|
+
: todo);
|
|
778
|
+
return {
|
|
779
|
+
name: toolCall.name,
|
|
780
|
+
args: {
|
|
781
|
+
...toolCall.args,
|
|
782
|
+
todos,
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
}
|
|
752
786
|
function formatBoundToolInstruction(tool) {
|
|
753
787
|
if (typeof tool !== "object" || tool === null) {
|
|
754
788
|
return null;
|
|
@@ -805,6 +839,15 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
805
839
|
return async (input, config) => {
|
|
806
840
|
const effectiveBoundTools = selectPlanningToolsForTurn(input, boundTools);
|
|
807
841
|
const forcePlanningToolCall = shouldLimitToolsToPlanning(input);
|
|
842
|
+
if (options.settleCompletedToolResults === true
|
|
843
|
+
&& !forcePlanningToolCall
|
|
844
|
+
&& effectiveBoundTools.length > 0
|
|
845
|
+
&& hasAnyPriorToolResult(input)
|
|
846
|
+
&& hasPriorNonPlanningToolCall(input)) {
|
|
847
|
+
return new AIMessage({
|
|
848
|
+
content: readLatestToolResultContent(input) ?? "",
|
|
849
|
+
});
|
|
850
|
+
}
|
|
808
851
|
const rawResult = await target.invoke(effectiveBoundTools.length > 0
|
|
809
852
|
? withPromptedJsonToolPrompt(input, effectiveBoundTools, { ...options, forcePlanningToolCall })
|
|
810
853
|
: input, config);
|
|
@@ -825,22 +868,32 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
825
868
|
if (fallbackCompletionToolCall) {
|
|
826
869
|
return fallbackCompletionToolCall;
|
|
827
870
|
}
|
|
828
|
-
const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools);
|
|
871
|
+
const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
|
|
829
872
|
if (fallbackToolCall) {
|
|
830
873
|
return fallbackToolCall;
|
|
831
874
|
}
|
|
832
875
|
}
|
|
833
876
|
return rawResult;
|
|
834
877
|
}
|
|
835
|
-
|
|
878
|
+
const effectiveParsedToolCall = forcePlanningToolCall
|
|
879
|
+
? normalizeInitialTodoPlanToolCall(parsedToolCall)
|
|
880
|
+
: parsedToolCall;
|
|
881
|
+
if (parsedToolCallCompletesTodoPlan(effectiveParsedToolCall)
|
|
882
|
+
&& !hasPriorNonPlanningToolResult(input, effectiveBoundTools)) {
|
|
883
|
+
const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
|
|
884
|
+
if (fallbackToolCall) {
|
|
885
|
+
return fallbackToolCall;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (hasPriorToolResultForToolName(input, effectiveParsedToolCall.name) || hasAnyPriorToolResult(input)) {
|
|
836
889
|
return rawResult;
|
|
837
890
|
}
|
|
838
891
|
return new AIMessage({
|
|
839
892
|
content: "",
|
|
840
893
|
tool_calls: [{
|
|
841
894
|
id: `tool-${Math.random().toString(36).slice(2, 10)}`,
|
|
842
|
-
name:
|
|
843
|
-
args:
|
|
895
|
+
name: effectiveParsedToolCall.name,
|
|
896
|
+
args: effectiveParsedToolCall.args,
|
|
844
897
|
type: "tool_call",
|
|
845
898
|
}],
|
|
846
899
|
});
|
|
@@ -916,7 +969,10 @@ export async function createResolvedModel(model, modelResolver) {
|
|
|
916
969
|
const { toolCallingMode, ...init } = model.init ?? {};
|
|
917
970
|
const resolved = new ChatOllama({ model: model.model, ...init });
|
|
918
971
|
if (toolCallingMode === "prompted-json") {
|
|
919
|
-
return createPromptedJsonToolBindableModel(resolved, [], {
|
|
972
|
+
return createPromptedJsonToolBindableModel(resolved, [], {
|
|
973
|
+
settleCompletedToolResults: true,
|
|
974
|
+
suppressThinking: init.think === false,
|
|
975
|
+
});
|
|
920
976
|
}
|
|
921
977
|
return createProviderToolMessageCompatModel(resolved);
|
|
922
978
|
}
|
|
@@ -927,7 +983,10 @@ export async function createResolvedModel(model, modelResolver) {
|
|
|
927
983
|
...normalizeOpenAICompatibleInit(init),
|
|
928
984
|
});
|
|
929
985
|
if (toolCallingMode === "prompted-json") {
|
|
930
|
-
return createPromptedJsonToolBindableModel(resolved
|
|
986
|
+
return createPromptedJsonToolBindableModel(resolved, [], {
|
|
987
|
+
settleCompletedToolResults: true,
|
|
988
|
+
suppressThinking: true,
|
|
989
|
+
});
|
|
931
990
|
}
|
|
932
991
|
return createProviderToolMessageCompatModel(resolved);
|
|
933
992
|
}
|
|
@@ -36,13 +36,16 @@ function shouldSuppressVisibleToolCallText(value) {
|
|
|
36
36
|
if (/^(?:model_request|tool_call|call_tool)\b/iu.test(trimmed)) {
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
|
+
if (/^(?:name|tool_call_id)\s*=/iu.test(trimmed)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
39
42
|
if (/^(?:we\s+need\s+to|so\s+next\s+step\b)/iu.test(trimmed)) {
|
|
40
43
|
return true;
|
|
41
44
|
}
|
|
42
45
|
if (/\b(?:must|need|needs|should|will)\s+(?:now\s+)?(?:call|use|run|produce)\s+[A-Za-z_][A-Za-z0-9_]*\b/iu.test(trimmed)) {
|
|
43
46
|
return true;
|
|
44
47
|
}
|
|
45
|
-
if (/^\{\s*"(?:name|arguments|todos|symbol|query|market|count)"\s*:/iu.test(trimmed)) {
|
|
48
|
+
if (/^\{\s*"(?:name|arguments|args|argv|todos|symbol|query|market|count|stdout|stderr|exitCode)"\s*:/iu.test(trimmed)) {
|
|
46
49
|
return true;
|
|
47
50
|
}
|
|
48
51
|
try {
|
|
@@ -55,7 +58,12 @@ function shouldSuppressVisibleToolCallText(value) {
|
|
|
55
58
|
|| "market" in parsed
|
|
56
59
|
|| "count" in parsed
|
|
57
60
|
|| "arguments" in parsed
|
|
58
|
-
|| "
|
|
61
|
+
|| "args" in parsed
|
|
62
|
+
|| "argv" in parsed
|
|
63
|
+
|| "name" in parsed
|
|
64
|
+
|| "stdout" in parsed
|
|
65
|
+
|| "stderr" in parsed
|
|
66
|
+
|| "exitCode" in parsed)) {
|
|
59
67
|
return true;
|
|
60
68
|
}
|
|
61
69
|
}
|
|
@@ -71,6 +79,63 @@ function shouldSuppressVisibleToolCallText(value) {
|
|
|
71
79
|
}
|
|
72
80
|
return salvageFunctionLikeToolCall(prefixedToolCallMatch[1]) !== null;
|
|
73
81
|
}
|
|
82
|
+
function stripVisibleToolCallResidue(value) {
|
|
83
|
+
const lines = value.split(/\r?\n/);
|
|
84
|
+
let changed = false;
|
|
85
|
+
const kept = lines.filter((line) => {
|
|
86
|
+
if (shouldSuppressVisibleToolCallText(line)) {
|
|
87
|
+
changed = true;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
});
|
|
92
|
+
if (changed) {
|
|
93
|
+
return kept.join("\n").trim();
|
|
94
|
+
}
|
|
95
|
+
const trimmedStart = value.trimStart();
|
|
96
|
+
if (!trimmedStart.startsWith("{")) {
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
const parsedPrefix = extractLeadingJsonObject(trimmedStart);
|
|
100
|
+
if (!parsedPrefix || !shouldSuppressVisibleToolCallText(parsedPrefix.json)) {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
return parsedPrefix.rest.trimStart();
|
|
104
|
+
}
|
|
105
|
+
function extractLeadingJsonObject(value) {
|
|
106
|
+
let depth = 0;
|
|
107
|
+
let inString = false;
|
|
108
|
+
let escaped = false;
|
|
109
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
110
|
+
const char = value[index];
|
|
111
|
+
if (escaped) {
|
|
112
|
+
escaped = false;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (char === "\\") {
|
|
116
|
+
escaped = inString;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (char === "\"") {
|
|
120
|
+
inString = !inString;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (inString) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (char === "{") {
|
|
127
|
+
depth += 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (char === "}") {
|
|
131
|
+
depth -= 1;
|
|
132
|
+
if (depth === 0) {
|
|
133
|
+
return { json: value.slice(0, index + 1), rest: value.slice(index + 1) };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
74
139
|
function readSummaryCounts(summary) {
|
|
75
140
|
if (typeof summary !== "object" || summary === null) {
|
|
76
141
|
return null;
|
|
@@ -375,7 +440,7 @@ export function projectRuntimeStreamEvent(params) {
|
|
|
375
440
|
const allowVisibleContent = isRootVisibleEvent && state.openTaskDelegations === 0;
|
|
376
441
|
const allowStreamedVisibleContent = allowVisibleContent && !state.emittedToolResult && !state.emittedToolError;
|
|
377
442
|
if (allowVisibleStreamDeltas && allowStreamedVisibleContent) {
|
|
378
|
-
const visibleStreamOutput = extractVisibleStreamOutput(event);
|
|
443
|
+
const visibleStreamOutput = stripVisibleToolCallResidue(extractVisibleStreamOutput(event));
|
|
379
444
|
if (visibleStreamOutput && !shouldSuppressVisibleToolCallText(visibleStreamOutput)) {
|
|
380
445
|
const nextOutput = computeIncrementalOutput(state.emittedOutput, visibleStreamOutput);
|
|
381
446
|
state.emittedOutput = nextOutput.accumulated;
|
|
@@ -385,7 +450,7 @@ export function projectRuntimeStreamEvent(params) {
|
|
|
385
450
|
}
|
|
386
451
|
}
|
|
387
452
|
if (includeStateStreamOutput && allowVisibleContent) {
|
|
388
|
-
const stateStreamOutput = extractStateStreamOutput(event);
|
|
453
|
+
const stateStreamOutput = stripVisibleToolCallResidue(extractStateStreamOutput(event));
|
|
389
454
|
if (stateStreamOutput && !shouldSuppressVisibleToolCallText(stateStreamOutput)) {
|
|
390
455
|
const nextOutput = computeIncrementalOutput(state.emittedOutput, stateStreamOutput);
|
|
391
456
|
state.emittedOutput = nextOutput.accumulated;
|
|
@@ -456,7 +521,7 @@ export function projectRuntimeStreamEvent(params) {
|
|
|
456
521
|
state.lastCompletedTaskDelegationFindings = "";
|
|
457
522
|
}
|
|
458
523
|
}
|
|
459
|
-
const output = allowVisibleContent ? extractTerminalStreamOutput(event) : "";
|
|
524
|
+
const output = allowVisibleContent ? stripVisibleToolCallResidue(extractTerminalStreamOutput(event)) : "";
|
|
460
525
|
if (!allowVisibleContent) {
|
|
461
526
|
const delegatedTerminalOutput = extractTerminalStreamOutput(event);
|
|
462
527
|
if (delegatedTerminalOutput) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { interrupt } from "@langchain/langgraph";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { validateToolGatewayInput } from "../../harness/tool-gateway/index.js";
|
|
3
4
|
const DEDUPED_REMOTE_TOOL_NAMES = new Set(["builtin.fetch_url", "builtin.web_search"]);
|
|
4
5
|
const PATH_LIKE_INPUT_KEYS = new Set(["path", "root", "dir", "directory", "cwd"]);
|
|
5
6
|
const ROOT_SCOPING_INPUT_KEYS = new Set(["root", "dir", "directory", "cwd"]);
|
|
@@ -169,8 +170,17 @@ export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, binding, i
|
|
|
169
170
|
}
|
|
170
171
|
const target = resolvedTool;
|
|
171
172
|
const runWithApproval = async (input, config, invokeOriginal) => {
|
|
173
|
+
const gateway = validateToolGatewayInput({
|
|
174
|
+
toolName: compiledTool.name,
|
|
175
|
+
schema: target.schema,
|
|
176
|
+
args: input,
|
|
177
|
+
requiresApproval: true,
|
|
178
|
+
});
|
|
179
|
+
if (!gateway.ok) {
|
|
180
|
+
return gateway.error;
|
|
181
|
+
}
|
|
172
182
|
if (decisionMode === "auto-approve") {
|
|
173
|
-
return invokeOriginal(input, config);
|
|
183
|
+
return invokeOriginal(gateway.input, config);
|
|
174
184
|
}
|
|
175
185
|
if (decisionMode === "auto-reject" || decisionMode === "deny-and-continue") {
|
|
176
186
|
return "Tool execution denied by runtime policy.";
|
|
@@ -179,11 +189,11 @@ export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, binding, i
|
|
|
179
189
|
toolName: compiledTool.name,
|
|
180
190
|
toolId: compiledTool.id,
|
|
181
191
|
allowedDecisions: compiledTool.hitl?.allow ?? ["approve", "edit", "reject"],
|
|
182
|
-
inputPreview: toInputPreview(input),
|
|
192
|
+
inputPreview: toInputPreview(gateway.input),
|
|
183
193
|
decisionMode,
|
|
184
194
|
...(toolApprovalReason(compiledTool) ? { approvalReason: toolApprovalReason(compiledTool) } : {}),
|
|
185
195
|
});
|
|
186
|
-
const approvedInput = resolveApprovedInput(input, resumed);
|
|
196
|
+
const approvedInput = resolveApprovedInput(gateway.input, resumed);
|
|
187
197
|
if (approvedInput === Symbol.for("agent-harness.hitl.reject")) {
|
|
188
198
|
return "Tool execution rejected by human reviewer.";
|
|
189
199
|
}
|
|
@@ -295,13 +305,46 @@ export function wrapToolForExecution(resolvedTool, compiledTool, binding, interr
|
|
|
295
305
|
get(obj, prop, receiver) {
|
|
296
306
|
const value = Reflect.get(obj, prop, receiver);
|
|
297
307
|
if (prop === "invoke" && typeof value === "function") {
|
|
298
|
-
return (input, config) =>
|
|
308
|
+
return (input, config) => {
|
|
309
|
+
const gateway = validateToolGatewayInput({
|
|
310
|
+
toolName: compiledTool.name,
|
|
311
|
+
schema: obj.schema,
|
|
312
|
+
args: input,
|
|
313
|
+
requiresApproval: resolveToolApprovalDecisionMode(compiledTool, binding) !== "none",
|
|
314
|
+
});
|
|
315
|
+
if (!gateway.ok) {
|
|
316
|
+
return gateway.error;
|
|
317
|
+
}
|
|
318
|
+
return obj.invoke(guardWorkspaceBoundToolInput(gateway.input, compiledTool, binding), config);
|
|
319
|
+
};
|
|
299
320
|
}
|
|
300
321
|
if (prop === "call" && typeof value === "function") {
|
|
301
|
-
return (input, config) =>
|
|
322
|
+
return (input, config) => {
|
|
323
|
+
const gateway = validateToolGatewayInput({
|
|
324
|
+
toolName: compiledTool.name,
|
|
325
|
+
schema: obj.schema,
|
|
326
|
+
args: input,
|
|
327
|
+
requiresApproval: resolveToolApprovalDecisionMode(compiledTool, binding) !== "none",
|
|
328
|
+
});
|
|
329
|
+
if (!gateway.ok) {
|
|
330
|
+
return gateway.error;
|
|
331
|
+
}
|
|
332
|
+
return obj.call(guardWorkspaceBoundToolInput(gateway.input, compiledTool, binding), config);
|
|
333
|
+
};
|
|
302
334
|
}
|
|
303
335
|
if (prop === "func" && typeof value === "function") {
|
|
304
|
-
return (input, config) =>
|
|
336
|
+
return (input, config) => {
|
|
337
|
+
const gateway = validateToolGatewayInput({
|
|
338
|
+
toolName: compiledTool.name,
|
|
339
|
+
schema: obj.schema,
|
|
340
|
+
args: input,
|
|
341
|
+
requiresApproval: resolveToolApprovalDecisionMode(compiledTool, binding) !== "none",
|
|
342
|
+
});
|
|
343
|
+
if (!gateway.ok) {
|
|
344
|
+
return gateway.error;
|
|
345
|
+
}
|
|
346
|
+
return obj.func(guardWorkspaceBoundToolInput(gateway.input, compiledTool, binding), config);
|
|
347
|
+
};
|
|
305
348
|
}
|
|
306
349
|
return value;
|
|
307
350
|
},
|
|
@@ -38,6 +38,8 @@ export declare class AgentRuntimeAdapter {
|
|
|
38
38
|
private invokeBuiltinTaskTool;
|
|
39
39
|
private resolveBuiltinMiddlewareTools;
|
|
40
40
|
private materializeProviderAliasBuiltinTools;
|
|
41
|
+
private shouldExposeBuiltinToolsToModel;
|
|
42
|
+
private resolveEffectiveModelExposedBuiltins;
|
|
41
43
|
private materializeAutomaticSummarizationMiddleware;
|
|
42
44
|
private resolveLangChainRuntimeExtensionMiddleware;
|
|
43
45
|
private resolveMiddleware;
|