@botbotgo/agent-harness 0.0.399 → 0.0.401
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 +19 -1
- package/dist/runtime/adapter/local-tool-invocation.js +234 -13
- 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 +142 -119
- package/dist/runtime/adapter/stream-event-projection.js +73 -5
- package/dist/runtime/adapter/tool/tool-hitl.js +49 -6
- package/dist/runtime/agent-runtime-adapter.js +269 -23
- 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/package.json +15 -15
|
@@ -211,6 +211,48 @@ 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
|
+
}
|
|
214
256
|
function readBoundToolName(tool) {
|
|
215
257
|
return typeof tool === "object" && tool !== null && typeof tool.name === "string"
|
|
216
258
|
? tool.name.trim()
|
|
@@ -357,47 +399,6 @@ function stringifyNodeLlamaCppInput(input) {
|
|
|
357
399
|
}
|
|
358
400
|
return readPromptContent(input);
|
|
359
401
|
}
|
|
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
402
|
function extractToolCallPayload(text) {
|
|
402
403
|
const trimmed = text.trim();
|
|
403
404
|
if (!trimmed) {
|
|
@@ -580,34 +581,19 @@ function isLowSignalPlanningLine(value) {
|
|
|
580
581
|
function normalizeDomainToolName(name) {
|
|
581
582
|
return name.startsWith("tool_call_") ? name.slice("tool_call_".length) : name;
|
|
582
583
|
}
|
|
583
|
-
function selectFallbackEvidenceToolName(
|
|
584
|
-
const
|
|
584
|
+
function selectFallbackEvidenceToolName(tools, rawText = "") {
|
|
585
|
+
const normalizedRawText = rawText.toLowerCase();
|
|
585
586
|
const available = tools
|
|
586
587
|
.map((tool) => normalizeDomainToolName(readBoundToolName(tool)))
|
|
587
588
|
.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
|
-
}
|
|
589
|
+
if (available.length === 0) {
|
|
590
|
+
return null;
|
|
606
591
|
}
|
|
607
|
-
|
|
592
|
+
const mentioned = available.find((name) => normalizedRawText.includes(name.toLowerCase()));
|
|
593
|
+
return mentioned ?? available[0] ?? null;
|
|
608
594
|
}
|
|
609
|
-
function buildToolAwareFallbackTodoContents(
|
|
610
|
-
const evidenceToolName = selectFallbackEvidenceToolName(
|
|
595
|
+
function buildToolAwareFallbackTodoContents(tools, rawText = "") {
|
|
596
|
+
const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
|
|
611
597
|
if (!evidenceToolName) {
|
|
612
598
|
return [
|
|
613
599
|
"Identify the concrete evidence tool required for this request",
|
|
@@ -635,7 +621,7 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
|
|
|
635
621
|
const hasUsefulModelPlan = modelPlannedItems.length >= 2 && !modelPlannedItems.every(isGenericFallbackTodoContent);
|
|
636
622
|
const fallbackItems = hasUsefulModelPlan
|
|
637
623
|
? modelPlannedItems
|
|
638
|
-
: buildToolAwareFallbackTodoContents(
|
|
624
|
+
: buildToolAwareFallbackTodoContents(allTools, rawText);
|
|
639
625
|
const todos = fallbackItems.map((content, index) => ({
|
|
640
626
|
content,
|
|
641
627
|
status: index === 0 ? "in_progress" : "pending",
|
|
@@ -650,60 +636,45 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
|
|
|
650
636
|
}],
|
|
651
637
|
});
|
|
652
638
|
}
|
|
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 };
|
|
639
|
+
function buildFallbackEvidenceToolArgs(tool) {
|
|
640
|
+
const schema = normalizeModelFacingToolSchema(tool);
|
|
641
|
+
if (typeof schema.properties !== "object" || schema.properties === null || Array.isArray(schema.properties)) {
|
|
642
|
+
return {};
|
|
689
643
|
}
|
|
690
|
-
|
|
691
|
-
|
|
644
|
+
const required = Array.isArray(schema.required)
|
|
645
|
+
? schema.required.filter((name) => typeof name === "string")
|
|
646
|
+
: [];
|
|
647
|
+
const values = {};
|
|
648
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
649
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const property = value;
|
|
653
|
+
if ("default" in property) {
|
|
654
|
+
values[key] = property.default;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if ("const" in property) {
|
|
658
|
+
values[key] = property.const;
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (required.includes(key) && Array.isArray(property.enum) && property.enum.length > 0) {
|
|
662
|
+
values[key] = property.enum[0];
|
|
663
|
+
}
|
|
692
664
|
}
|
|
693
|
-
return
|
|
665
|
+
return values;
|
|
694
666
|
}
|
|
695
|
-
function buildFallbackEvidenceToolCall(input, tools) {
|
|
667
|
+
function buildFallbackEvidenceToolCall(input, tools, rawText = "") {
|
|
696
668
|
if (!hasPriorToolResultForToolName(input, "write_todos") && !hasPriorToolResultForToolName(input, "tool_call_write_todos")) {
|
|
697
669
|
return null;
|
|
698
670
|
}
|
|
699
|
-
const evidenceToolName = selectFallbackEvidenceToolName(
|
|
671
|
+
const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
|
|
700
672
|
if (!evidenceToolName) {
|
|
701
673
|
return null;
|
|
702
674
|
}
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
if (!boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
|
|
675
|
+
const boundTool = tools.find((tool) => normalizeDomainToolName(readBoundToolName(tool)) === evidenceToolName);
|
|
676
|
+
const boundToolName = readBoundToolName(boundTool);
|
|
677
|
+
if (!boundTool || !boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
|
|
707
678
|
return null;
|
|
708
679
|
}
|
|
709
680
|
return new AIMessage({
|
|
@@ -711,7 +682,7 @@ function buildFallbackEvidenceToolCall(input, tools) {
|
|
|
711
682
|
tool_calls: [{
|
|
712
683
|
id: `fallback-evidence-${Math.random().toString(36).slice(2, 10)}`,
|
|
713
684
|
name: boundToolName,
|
|
714
|
-
args: buildFallbackEvidenceToolArgs(
|
|
685
|
+
args: buildFallbackEvidenceToolArgs(boundTool),
|
|
715
686
|
type: "tool_call",
|
|
716
687
|
}],
|
|
717
688
|
});
|
|
@@ -725,7 +696,7 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
|
|
|
725
696
|
if (!planningToolName) {
|
|
726
697
|
return null;
|
|
727
698
|
}
|
|
728
|
-
const evidenceToolName = selectFallbackEvidenceToolName(
|
|
699
|
+
const evidenceToolName = selectFallbackEvidenceToolName(tools, prompt);
|
|
729
700
|
if (!evidenceToolName) {
|
|
730
701
|
return null;
|
|
731
702
|
}
|
|
@@ -735,7 +706,7 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
|
|
|
735
706
|
if (!hasEvidenceResult) {
|
|
736
707
|
return null;
|
|
737
708
|
}
|
|
738
|
-
const todos = buildToolAwareFallbackTodoContents(
|
|
709
|
+
const todos = buildToolAwareFallbackTodoContents(tools, prompt).map((content) => ({
|
|
739
710
|
content,
|
|
740
711
|
status: "completed",
|
|
741
712
|
}));
|
|
@@ -749,6 +720,33 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
|
|
|
749
720
|
}],
|
|
750
721
|
});
|
|
751
722
|
}
|
|
723
|
+
function parsedToolCallCompletesTodoPlan(toolCall) {
|
|
724
|
+
if (!isTodoPlanningToolName(toolCall.name)) {
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
const todos = toolCall.args.todos;
|
|
728
|
+
if (!Array.isArray(todos) || todos.length === 0) {
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
return todos.every((todo) => typeof todo === "object"
|
|
732
|
+
&& todo !== null
|
|
733
|
+
&& todo.status === "completed");
|
|
734
|
+
}
|
|
735
|
+
function normalizeInitialTodoPlanToolCall(toolCall) {
|
|
736
|
+
if (!parsedToolCallCompletesTodoPlan(toolCall)) {
|
|
737
|
+
return toolCall;
|
|
738
|
+
}
|
|
739
|
+
const todos = toolCall.args.todos.map((todo, index) => typeof todo === "object" && todo !== null && !Array.isArray(todo)
|
|
740
|
+
? { ...todo, status: index === 0 ? "in_progress" : "pending" }
|
|
741
|
+
: todo);
|
|
742
|
+
return {
|
|
743
|
+
name: toolCall.name,
|
|
744
|
+
args: {
|
|
745
|
+
...toolCall.args,
|
|
746
|
+
todos,
|
|
747
|
+
},
|
|
748
|
+
};
|
|
749
|
+
}
|
|
752
750
|
function formatBoundToolInstruction(tool) {
|
|
753
751
|
if (typeof tool !== "object" || tool === null) {
|
|
754
752
|
return null;
|
|
@@ -805,6 +803,15 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
805
803
|
return async (input, config) => {
|
|
806
804
|
const effectiveBoundTools = selectPlanningToolsForTurn(input, boundTools);
|
|
807
805
|
const forcePlanningToolCall = shouldLimitToolsToPlanning(input);
|
|
806
|
+
if (options.settleCompletedToolResults === true
|
|
807
|
+
&& !forcePlanningToolCall
|
|
808
|
+
&& effectiveBoundTools.length > 0
|
|
809
|
+
&& hasAnyPriorToolResult(input)
|
|
810
|
+
&& hasPriorNonPlanningToolCall(input)) {
|
|
811
|
+
return new AIMessage({
|
|
812
|
+
content: readLatestToolResultContent(input) ?? "",
|
|
813
|
+
});
|
|
814
|
+
}
|
|
808
815
|
const rawResult = await target.invoke(effectiveBoundTools.length > 0
|
|
809
816
|
? withPromptedJsonToolPrompt(input, effectiveBoundTools, { ...options, forcePlanningToolCall })
|
|
810
817
|
: input, config);
|
|
@@ -825,22 +832,32 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
825
832
|
if (fallbackCompletionToolCall) {
|
|
826
833
|
return fallbackCompletionToolCall;
|
|
827
834
|
}
|
|
828
|
-
const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools);
|
|
835
|
+
const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
|
|
829
836
|
if (fallbackToolCall) {
|
|
830
837
|
return fallbackToolCall;
|
|
831
838
|
}
|
|
832
839
|
}
|
|
833
840
|
return rawResult;
|
|
834
841
|
}
|
|
835
|
-
|
|
842
|
+
const effectiveParsedToolCall = forcePlanningToolCall
|
|
843
|
+
? normalizeInitialTodoPlanToolCall(parsedToolCall)
|
|
844
|
+
: parsedToolCall;
|
|
845
|
+
if (parsedToolCallCompletesTodoPlan(effectiveParsedToolCall)
|
|
846
|
+
&& !hasPriorNonPlanningToolResult(input, effectiveBoundTools)) {
|
|
847
|
+
const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
|
|
848
|
+
if (fallbackToolCall) {
|
|
849
|
+
return fallbackToolCall;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (hasPriorToolResultForToolName(input, effectiveParsedToolCall.name) || hasAnyPriorToolResult(input)) {
|
|
836
853
|
return rawResult;
|
|
837
854
|
}
|
|
838
855
|
return new AIMessage({
|
|
839
856
|
content: "",
|
|
840
857
|
tool_calls: [{
|
|
841
858
|
id: `tool-${Math.random().toString(36).slice(2, 10)}`,
|
|
842
|
-
name:
|
|
843
|
-
args:
|
|
859
|
+
name: effectiveParsedToolCall.name,
|
|
860
|
+
args: effectiveParsedToolCall.args,
|
|
844
861
|
type: "tool_call",
|
|
845
862
|
}],
|
|
846
863
|
});
|
|
@@ -916,7 +933,10 @@ export async function createResolvedModel(model, modelResolver) {
|
|
|
916
933
|
const { toolCallingMode, ...init } = model.init ?? {};
|
|
917
934
|
const resolved = new ChatOllama({ model: model.model, ...init });
|
|
918
935
|
if (toolCallingMode === "prompted-json") {
|
|
919
|
-
return createPromptedJsonToolBindableModel(resolved, [], {
|
|
936
|
+
return createPromptedJsonToolBindableModel(resolved, [], {
|
|
937
|
+
settleCompletedToolResults: true,
|
|
938
|
+
suppressThinking: init.think === false,
|
|
939
|
+
});
|
|
920
940
|
}
|
|
921
941
|
return createProviderToolMessageCompatModel(resolved);
|
|
922
942
|
}
|
|
@@ -927,7 +947,10 @@ export async function createResolvedModel(model, modelResolver) {
|
|
|
927
947
|
...normalizeOpenAICompatibleInit(init),
|
|
928
948
|
});
|
|
929
949
|
if (toolCallingMode === "prompted-json") {
|
|
930
|
-
return createPromptedJsonToolBindableModel(resolved
|
|
950
|
+
return createPromptedJsonToolBindableModel(resolved, [], {
|
|
951
|
+
settleCompletedToolResults: true,
|
|
952
|
+
suppressThinking: true,
|
|
953
|
+
});
|
|
931
954
|
}
|
|
932
955
|
return createProviderToolMessageCompatModel(resolved);
|
|
933
956
|
}
|
|
@@ -36,10 +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
|
+
}
|
|
42
|
+
if (/^(?:we\s+need\s+to|so\s+next\s+step\b)/iu.test(trimmed)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
39
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)) {
|
|
40
46
|
return true;
|
|
41
47
|
}
|
|
42
|
-
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)) {
|
|
43
49
|
return true;
|
|
44
50
|
}
|
|
45
51
|
try {
|
|
@@ -52,7 +58,12 @@ function shouldSuppressVisibleToolCallText(value) {
|
|
|
52
58
|
|| "market" in parsed
|
|
53
59
|
|| "count" in parsed
|
|
54
60
|
|| "arguments" in parsed
|
|
55
|
-
|| "
|
|
61
|
+
|| "args" in parsed
|
|
62
|
+
|| "argv" in parsed
|
|
63
|
+
|| "name" in parsed
|
|
64
|
+
|| "stdout" in parsed
|
|
65
|
+
|| "stderr" in parsed
|
|
66
|
+
|| "exitCode" in parsed)) {
|
|
56
67
|
return true;
|
|
57
68
|
}
|
|
58
69
|
}
|
|
@@ -68,6 +79,63 @@ function shouldSuppressVisibleToolCallText(value) {
|
|
|
68
79
|
}
|
|
69
80
|
return salvageFunctionLikeToolCall(prefixedToolCallMatch[1]) !== null;
|
|
70
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
|
+
}
|
|
71
139
|
function readSummaryCounts(summary) {
|
|
72
140
|
if (typeof summary !== "object" || summary === null) {
|
|
73
141
|
return null;
|
|
@@ -372,7 +440,7 @@ export function projectRuntimeStreamEvent(params) {
|
|
|
372
440
|
const allowVisibleContent = isRootVisibleEvent && state.openTaskDelegations === 0;
|
|
373
441
|
const allowStreamedVisibleContent = allowVisibleContent && !state.emittedToolResult && !state.emittedToolError;
|
|
374
442
|
if (allowVisibleStreamDeltas && allowStreamedVisibleContent) {
|
|
375
|
-
const visibleStreamOutput = extractVisibleStreamOutput(event);
|
|
443
|
+
const visibleStreamOutput = stripVisibleToolCallResidue(extractVisibleStreamOutput(event));
|
|
376
444
|
if (visibleStreamOutput && !shouldSuppressVisibleToolCallText(visibleStreamOutput)) {
|
|
377
445
|
const nextOutput = computeIncrementalOutput(state.emittedOutput, visibleStreamOutput);
|
|
378
446
|
state.emittedOutput = nextOutput.accumulated;
|
|
@@ -382,7 +450,7 @@ export function projectRuntimeStreamEvent(params) {
|
|
|
382
450
|
}
|
|
383
451
|
}
|
|
384
452
|
if (includeStateStreamOutput && allowVisibleContent) {
|
|
385
|
-
const stateStreamOutput = extractStateStreamOutput(event);
|
|
453
|
+
const stateStreamOutput = stripVisibleToolCallResidue(extractStateStreamOutput(event));
|
|
386
454
|
if (stateStreamOutput && !shouldSuppressVisibleToolCallText(stateStreamOutput)) {
|
|
387
455
|
const nextOutput = computeIncrementalOutput(state.emittedOutput, stateStreamOutput);
|
|
388
456
|
state.emittedOutput = nextOutput.accumulated;
|
|
@@ -453,7 +521,7 @@ export function projectRuntimeStreamEvent(params) {
|
|
|
453
521
|
state.lastCompletedTaskDelegationFindings = "";
|
|
454
522
|
}
|
|
455
523
|
}
|
|
456
|
-
const output = allowVisibleContent ? extractTerminalStreamOutput(event) : "";
|
|
524
|
+
const output = allowVisibleContent ? stripVisibleToolCallResidue(extractTerminalStreamOutput(event)) : "";
|
|
457
525
|
if (!allowVisibleContent) {
|
|
458
526
|
const delegatedTerminalOutput = extractTerminalStreamOutput(event);
|
|
459
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
|
},
|