@burtson-labs/agent-core 1.6.13
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/LICENSE +201 -0
- package/README.md +88 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/activation.d.ts +60 -0
- package/dist/mcp/activation.d.ts.map +1 -0
- package/dist/mcp/activation.js +139 -0
- package/dist/mcp/activation.js.map +1 -0
- package/dist/mcp/clientPool.d.ts +202 -0
- package/dist/mcp/clientPool.d.ts.map +1 -0
- package/dist/mcp/clientPool.js +469 -0
- package/dist/mcp/clientPool.js.map +1 -0
- package/dist/mcp/index.d.ts +18 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +28 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +43 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +130 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/toolAdapter.d.ts +57 -0
- package/dist/mcp/toolAdapter.d.ts.map +1 -0
- package/dist/mcp/toolAdapter.js +223 -0
- package/dist/mcp/toolAdapter.js.map +1 -0
- package/dist/mcp/types.d.ts +122 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +15 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/providers/deterministic-provider.d.ts +21 -0
- package/dist/providers/deterministic-provider.d.ts.map +1 -0
- package/dist/providers/deterministic-provider.js +80 -0
- package/dist/providers/deterministic-provider.js.map +1 -0
- package/dist/providers/provider-client.d.ts +12 -0
- package/dist/providers/provider-client.d.ts.map +1 -0
- package/dist/providers/provider-client.js +11 -0
- package/dist/providers/provider-client.js.map +1 -0
- package/dist/runtime/AgentRuntime.d.ts +67 -0
- package/dist/runtime/AgentRuntime.d.ts.map +1 -0
- package/dist/runtime/AgentRuntime.js +382 -0
- package/dist/runtime/AgentRuntime.js.map +1 -0
- package/dist/security/secretPatterns.d.ts +76 -0
- package/dist/security/secretPatterns.d.ts.map +1 -0
- package/dist/security/secretPatterns.js +290 -0
- package/dist/security/secretPatterns.js.map +1 -0
- package/dist/tools/ask-user-tool.d.ts +19 -0
- package/dist/tools/ask-user-tool.d.ts.map +1 -0
- package/dist/tools/ask-user-tool.js +148 -0
- package/dist/tools/ask-user-tool.js.map +1 -0
- package/dist/tools/compactMessages.d.ts +52 -0
- package/dist/tools/compactMessages.d.ts.map +1 -0
- package/dist/tools/compactMessages.js +158 -0
- package/dist/tools/compactMessages.js.map +1 -0
- package/dist/tools/core-tools.d.ts +29 -0
- package/dist/tools/core-tools.d.ts.map +1 -0
- package/dist/tools/core-tools.js +2214 -0
- package/dist/tools/core-tools.js.map +1 -0
- package/dist/tools/git-tools.d.ts +32 -0
- package/dist/tools/git-tools.d.ts.map +1 -0
- package/dist/tools/git-tools.js +330 -0
- package/dist/tools/git-tools.js.map +1 -0
- package/dist/tools/index.d.ts +15 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +31 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/language-adapters.d.ts +48 -0
- package/dist/tools/language-adapters.d.ts.map +1 -0
- package/dist/tools/language-adapters.js +299 -0
- package/dist/tools/language-adapters.js.map +1 -0
- package/dist/tools/loop/compactionTrigger.d.ts +47 -0
- package/dist/tools/loop/compactionTrigger.d.ts.map +1 -0
- package/dist/tools/loop/compactionTrigger.js +32 -0
- package/dist/tools/loop/compactionTrigger.js.map +1 -0
- package/dist/tools/loop/finalAnswerNudges.d.ts +68 -0
- package/dist/tools/loop/finalAnswerNudges.d.ts.map +1 -0
- package/dist/tools/loop/finalAnswerNudges.js +87 -0
- package/dist/tools/loop/finalAnswerNudges.js.map +1 -0
- package/dist/tools/loop/goalAnchor.d.ts +72 -0
- package/dist/tools/loop/goalAnchor.d.ts.map +1 -0
- package/dist/tools/loop/goalAnchor.js +76 -0
- package/dist/tools/loop/goalAnchor.js.map +1 -0
- package/dist/tools/loop/llmStream.d.ts +70 -0
- package/dist/tools/loop/llmStream.d.ts.map +1 -0
- package/dist/tools/loop/llmStream.js +181 -0
- package/dist/tools/loop/llmStream.js.map +1 -0
- package/dist/tools/loop/parallelExecute.d.ts +57 -0
- package/dist/tools/loop/parallelExecute.d.ts.map +1 -0
- package/dist/tools/loop/parallelExecute.js +54 -0
- package/dist/tools/loop/parallelExecute.js.map +1 -0
- package/dist/tools/loop/singleToolExecute.d.ts +71 -0
- package/dist/tools/loop/singleToolExecute.d.ts.map +1 -0
- package/dist/tools/loop/singleToolExecute.js +139 -0
- package/dist/tools/loop/singleToolExecute.js.map +1 -0
- package/dist/tools/loop/toolCallNormalize.d.ts +57 -0
- package/dist/tools/loop/toolCallNormalize.d.ts.map +1 -0
- package/dist/tools/loop/toolCallNormalize.js +99 -0
- package/dist/tools/loop/toolCallNormalize.js.map +1 -0
- package/dist/tools/loop/turnSetup.d.ts +43 -0
- package/dist/tools/loop/turnSetup.d.ts.map +1 -0
- package/dist/tools/loop/turnSetup.js +48 -0
- package/dist/tools/loop/turnSetup.js.map +1 -0
- package/dist/tools/ocr.d.ts +52 -0
- package/dist/tools/ocr.d.ts.map +1 -0
- package/dist/tools/ocr.js +238 -0
- package/dist/tools/ocr.js.map +1 -0
- package/dist/tools/post-edit-checks.d.ts +46 -0
- package/dist/tools/post-edit-checks.d.ts.map +1 -0
- package/dist/tools/post-edit-checks.js +236 -0
- package/dist/tools/post-edit-checks.js.map +1 -0
- package/dist/tools/skill-loader.d.ts +94 -0
- package/dist/tools/skill-loader.d.ts.map +1 -0
- package/dist/tools/skill-loader.js +422 -0
- package/dist/tools/skill-loader.js.map +1 -0
- package/dist/tools/skill-registry.d.ts +44 -0
- package/dist/tools/skill-registry.d.ts.map +1 -0
- package/dist/tools/skill-registry.js +118 -0
- package/dist/tools/skill-registry.js.map +1 -0
- package/dist/tools/skill-types.d.ts +38 -0
- package/dist/tools/skill-types.d.ts.map +1 -0
- package/dist/tools/skill-types.js +10 -0
- package/dist/tools/skill-types.js.map +1 -0
- package/dist/tools/skills/code-review-skill.d.ts +9 -0
- package/dist/tools/skills/code-review-skill.d.ts.map +1 -0
- package/dist/tools/skills/code-review-skill.js +66 -0
- package/dist/tools/skills/code-review-skill.js.map +1 -0
- package/dist/tools/skills/core-skill.d.ts +13 -0
- package/dist/tools/skills/core-skill.d.ts.map +1 -0
- package/dist/tools/skills/core-skill.js +23 -0
- package/dist/tools/skills/core-skill.js.map +1 -0
- package/dist/tools/skills/git-skill.d.ts +10 -0
- package/dist/tools/skills/git-skill.d.ts.map +1 -0
- package/dist/tools/skills/git-skill.js +30 -0
- package/dist/tools/skills/git-skill.js.map +1 -0
- package/dist/tools/skills/index.d.ts +17 -0
- package/dist/tools/skills/index.d.ts.map +1 -0
- package/dist/tools/skills/index.js +49 -0
- package/dist/tools/skills/index.js.map +1 -0
- package/dist/tools/skills/interaction-skill.d.ts +14 -0
- package/dist/tools/skills/interaction-skill.d.ts.map +1 -0
- package/dist/tools/skills/interaction-skill.js +24 -0
- package/dist/tools/skills/interaction-skill.js.map +1 -0
- package/dist/tools/skills/mail-search-skill.d.ts +25 -0
- package/dist/tools/skills/mail-search-skill.d.ts.map +1 -0
- package/dist/tools/skills/mail-search-skill.js +343 -0
- package/dist/tools/skills/mail-search-skill.js.map +1 -0
- package/dist/tools/skills/plan-skill.d.ts +10 -0
- package/dist/tools/skills/plan-skill.d.ts.map +1 -0
- package/dist/tools/skills/plan-skill.js +126 -0
- package/dist/tools/skills/plan-skill.js.map +1 -0
- package/dist/tools/skills/semantic-search-skill.d.ts +22 -0
- package/dist/tools/skills/semantic-search-skill.d.ts.map +1 -0
- package/dist/tools/skills/semantic-search-skill.js +244 -0
- package/dist/tools/skills/semantic-search-skill.js.map +1 -0
- package/dist/tools/skills/test-gen-skill.d.ts +9 -0
- package/dist/tools/skills/test-gen-skill.d.ts.map +1 -0
- package/dist/tools/skills/test-gen-skill.js +123 -0
- package/dist/tools/skills/test-gen-skill.js.map +1 -0
- package/dist/tools/tool-registry.d.ts +60 -0
- package/dist/tools/tool-registry.d.ts.map +1 -0
- package/dist/tools/tool-registry.js +200 -0
- package/dist/tools/tool-registry.js.map +1 -0
- package/dist/tools/tool-types.d.ts +281 -0
- package/dist/tools/tool-types.d.ts.map +1 -0
- package/dist/tools/tool-types.js +10 -0
- package/dist/tools/tool-types.js.map +1 -0
- package/dist/tools/tool-use-loop.d.ts +231 -0
- package/dist/tools/tool-use-loop.d.ts.map +1 -0
- package/dist/tools/tool-use-loop.js +2057 -0
- package/dist/tools/tool-use-loop.js.map +1 -0
- package/dist/tools/tool-use-parser.d.ts +78 -0
- package/dist/tools/tool-use-parser.d.ts.map +1 -0
- package/dist/tools/tool-use-parser.js +427 -0
- package/dist/tools/tool-use-parser.js.map +1 -0
- package/dist/tools/toolAvailabilityDetector.d.ts +48 -0
- package/dist/tools/toolAvailabilityDetector.d.ts.map +1 -0
- package/dist/tools/toolAvailabilityDetector.js +156 -0
- package/dist/tools/toolAvailabilityDetector.js.map +1 -0
- package/dist/tools/unified-patch.d.ts +87 -0
- package/dist/tools/unified-patch.d.ts.map +1 -0
- package/dist/tools/unified-patch.js +217 -0
- package/dist/tools/unified-patch.js.map +1 -0
- package/dist/types/agent.d.ts +69 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +54 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/tasks.d.ts +22 -0
- package/dist/types/tasks.d.ts.map +1 -0
- package/dist/types/tasks.js +3 -0
- package/dist/types/tasks.js.map +1 -0
- package/dist/utils/event-emitter.d.ts +13 -0
- package/dist/utils/event-emitter.d.ts.map +1 -0
- package/dist/utils/event-emitter.js +54 -0
- package/dist/utils/event-emitter.js.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createToolDispatcher = createToolDispatcher;
|
|
4
|
+
const tool_use_parser_1 = require("../tool-use-parser");
|
|
5
|
+
function createToolDispatcher(deps) {
|
|
6
|
+
const { registry, ctx, beforeToolExecute, emit, recentCallKeys, repeatLimit, filesReadThisTurn, filesWrittenThisTurn, isFileEditTool, onEditToolSucceeded } = deps;
|
|
7
|
+
return async function dispatchOne(tc) {
|
|
8
|
+
// Build a coarse signature for repeat detection. For file-writing
|
|
9
|
+
// tools (write_file, apply_edit, replace_range) we key on path + a short hash of
|
|
10
|
+
// the find/replace/content so we catch "same edit retried with
|
|
11
|
+
// same payload" (the original target — model corrupted a write
|
|
12
|
+
// and is looping) WITHOUT flagging "8 different edits to the
|
|
13
|
+
// same file" (legitimate batch refactor — // on S3Api with gpt-oss commenting all methods in one .cs file).
|
|
14
|
+
// For other tools we fall back to a truncated JSON of the params
|
|
15
|
+
// so unrelated calls don't collide.
|
|
16
|
+
const pathish = tc.params.path ?? tc.params.file ?? tc.params.filepath;
|
|
17
|
+
const isEditTool = tc.name === 'apply_edit' || tc.name === 'replace_range' || tc.name === 'write_file';
|
|
18
|
+
let callKey;
|
|
19
|
+
if (pathish && isEditTool) {
|
|
20
|
+
// Cheap deterministic hash of the payload — enough to
|
|
21
|
+
// distinguish 8 different edits to the same file. Not
|
|
22
|
+
// crypto, just collision-resistant for a window of 3 calls.
|
|
23
|
+
const payload = `${tc.params.find ?? ''}::${tc.params.replace ?? ''}::${tc.params.content ?? ''}::${tc.params.start_line ?? ''}::${tc.params.end_line ?? ''}`;
|
|
24
|
+
let h = 0;
|
|
25
|
+
for (let i = 0; i < payload.length; i++) {
|
|
26
|
+
h = (h * 31 + payload.charCodeAt(i)) | 0;
|
|
27
|
+
}
|
|
28
|
+
callKey = `${tc.name}::${pathish}::${h.toString(36)}`;
|
|
29
|
+
}
|
|
30
|
+
else if (pathish) {
|
|
31
|
+
callKey = `${tc.name}::${pathish}`;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
callKey = `${tc.name}::${JSON.stringify(tc.params).slice(0, 160)}`;
|
|
35
|
+
}
|
|
36
|
+
recentCallKeys.push(callKey);
|
|
37
|
+
if (recentCallKeys.length > repeatLimit)
|
|
38
|
+
recentCallKeys.shift();
|
|
39
|
+
if (recentCallKeys.length === repeatLimit &&
|
|
40
|
+
recentCallKeys.every((k) => k === callKey)) {
|
|
41
|
+
emit('tool_loop:repeat_breaker', { name: tc.name, key: callKey });
|
|
42
|
+
return {
|
|
43
|
+
name: tc.name,
|
|
44
|
+
output: `Loop detected: ${tc.name} has been invoked ${repeatLimit} times in a row against the same target (${pathish ?? 'identical params'}) without progress. This usually means the last write landed malformed — most often an unescaped \`"\` inside the JSON content string truncated the file. STOP retrying. Either (a) produce a final answer that explains the issue to the user, or (b) break the content into smaller edits. Do not call ${tc.name} with these params again.`,
|
|
45
|
+
isError: true
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const tool = registry.get(tc.name);
|
|
49
|
+
if (!tool) {
|
|
50
|
+
emit('tool_loop:tool_not_found', { name: tc.name });
|
|
51
|
+
return { name: tc.name, output: `Error: tool "${tc.name}" is not registered.`, isError: true };
|
|
52
|
+
}
|
|
53
|
+
// Also surface the RAW tool_call block (first 400 chars) so
|
|
54
|
+
// observers can diagnose parser-edge cases. When a param
|
|
55
|
+
// extraction goes wrong (unknown wrapper key, nested array,
|
|
56
|
+
// etc.) the isError result is the symptom; the raw block is
|
|
57
|
+
// the evidence. Without it, debugging an empty-params call
|
|
58
|
+
// requires re-running with extra instrumentation.
|
|
59
|
+
//
|
|
60
|
+
// NOTE: the edit-tool counter is incremented inside the try
|
|
61
|
+
// block below, AFTER the result returns and only when
|
|
62
|
+
// `!result.isError`. Counting attempts (which an earlier
|
|
63
|
+
// version did at this point) made the false-completion
|
|
64
|
+
// detector blind to the worst variant of the hallucination:
|
|
65
|
+
// model fires 8 apply_edits, every single one fails with
|
|
66
|
+
// "find not found", model produces a confident "I have
|
|
67
|
+
// fixed the bug" summary. The counter said 8, the detector
|
|
68
|
+
// saw a non-zero count, the user read the lie. Observed
|
|
69
|
+
// 2026-05-01 on the bandit website's plans.tsx grid bug.
|
|
70
|
+
emit('tool_loop:tool_execute', {
|
|
71
|
+
name: tc.name,
|
|
72
|
+
params: tc.params,
|
|
73
|
+
rawSnippet: tc.raw.slice(0, 400)
|
|
74
|
+
});
|
|
75
|
+
const gate = await beforeToolExecute({ name: tc.name, params: tc.params });
|
|
76
|
+
if (!gate.allow) {
|
|
77
|
+
const reason = gate.reason ?? 'blocked by pre-execute guard';
|
|
78
|
+
emit('tool_loop:tool_blocked', { name: tc.name, reason });
|
|
79
|
+
return { name: tc.name, output: `Blocked: ${reason}`, isError: true };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const result = await tool.execute(tc.params, ctx);
|
|
83
|
+
// Only count edits that actually landed. A `find`-not-found
|
|
84
|
+
// or schema-rejected edit returns isError:true and changed
|
|
85
|
+
// nothing on disk; counting it would let the false-
|
|
86
|
+
// completion detector wave through "I have fixed the bug"
|
|
87
|
+
// claims that are false.
|
|
88
|
+
const normalizeFilePath = (raw) => {
|
|
89
|
+
if (typeof raw !== 'string' || !raw)
|
|
90
|
+
return null;
|
|
91
|
+
// Use basename so `src/App.jsx` and `~/proj/src/App.jsx`
|
|
92
|
+
// collide — the user's goal text typically uses the bare
|
|
93
|
+
// filename so a basename comparison is the most forgiving
|
|
94
|
+
// way to ask "did we touch the file the user named?".
|
|
95
|
+
const parts = raw.split(/[/\\]/);
|
|
96
|
+
return parts[parts.length - 1].toLowerCase();
|
|
97
|
+
};
|
|
98
|
+
if (isFileEditTool(tc.name) && !result.isError) {
|
|
99
|
+
onEditToolSucceeded();
|
|
100
|
+
const p = tc.params?.path;
|
|
101
|
+
const norm = normalizeFilePath(p);
|
|
102
|
+
if (norm)
|
|
103
|
+
filesWrittenThisTurn.add(norm);
|
|
104
|
+
}
|
|
105
|
+
if (tc.name === 'read_file' && !result.isError) {
|
|
106
|
+
const p = tc.params?.path;
|
|
107
|
+
const norm = normalizeFilePath(p);
|
|
108
|
+
if (norm)
|
|
109
|
+
filesReadThisTurn.add(norm);
|
|
110
|
+
}
|
|
111
|
+
// Include a short output snippet in the event — critical for
|
|
112
|
+
// downstream observers (eval runner, turn log) to know WHY
|
|
113
|
+
// a tool errored rather than just THAT it did. Capped so long
|
|
114
|
+
// successful results don't flood the event bus.
|
|
115
|
+
//
|
|
116
|
+
// outputSnippet is rendered to the host UI (tool
|
|
117
|
+
// cards in the extension, dim recap in the CLI). Redact
|
|
118
|
+
// before emitting so the user's terminal scrollback doesn't
|
|
119
|
+
// capture raw secrets even when the model's context
|
|
120
|
+
// already has the redacted version. The model-facing path
|
|
121
|
+
// goes through buildToolResultsMessage → formatToolResult,
|
|
122
|
+
// which already applies the same redactor at the parser
|
|
123
|
+
// boundary, so both paths converge on the same masked text.
|
|
124
|
+
emit('tool_loop:tool_result', {
|
|
125
|
+
name: tc.name,
|
|
126
|
+
isError: result.isError,
|
|
127
|
+
outputLength: result.output.length,
|
|
128
|
+
outputSnippet: (0, tool_use_parser_1.applySecretRedactionIfEnabled)(result.output.slice(0, 280))
|
|
129
|
+
});
|
|
130
|
+
return { name: tc.name, output: result.output, isError: result.isError };
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
134
|
+
emit('tool_loop:tool_error', { name: tc.name, error: msg });
|
|
135
|
+
return { name: tc.name, output: `Error executing tool "${tc.name}": ${msg}`, isError: true };
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=singleToolExecute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleToolExecute.js","sourceRoot":"","sources":["../../../src/tools/loop/singleToolExecute.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-call batch normalization for ToolUseLoop.runWithMessages.
|
|
3
|
+
*
|
|
4
|
+
* After parseToolCalls returns the raw list of `<tool_call>` blocks the
|
|
5
|
+
* model emitted in one iteration, the loop has to:
|
|
6
|
+
*
|
|
7
|
+
* 1. Drop byte-identical duplicate calls (panic-fanout mode emits the
|
|
8
|
+
* same search multiple times — observed 2026-04-26 with gpt-oss:120b
|
|
9
|
+
* on H100 fanning out 25 calls in one iteration, four of them
|
|
10
|
+
* identical `search_code writeInsightsReport`s).
|
|
11
|
+
* 2. Keep at most ONE foreground (synchronous) `task` call per
|
|
12
|
+
* iteration — the model can't make progress on three subagents
|
|
13
|
+
* simultaneously and the wait time stacks.
|
|
14
|
+
* 3. Slice the batch to `maxParallelTools` so a 25-call fanout
|
|
15
|
+
* doesn't drown the next iteration's tool-result payload.
|
|
16
|
+
* 4. Slice further to fit the remaining per-turn budget
|
|
17
|
+
* (`maxTotalTools - totalToolsExecuted`).
|
|
18
|
+
*
|
|
19
|
+
* Each step emits a telemetry event on the loop's `emit` sink so hosts
|
|
20
|
+
* can surface "we dropped N duplicates" / "we capped at N parallel" in
|
|
21
|
+
* the UI. The caller updates `totalToolsExecuted` from the returned
|
|
22
|
+
* `accepted` count.
|
|
23
|
+
*
|
|
24
|
+
* Pure with respect to the loop's mutable state: input is a `ToolCall[]`,
|
|
25
|
+
* output is a new `ToolCall[]` plus drop counts plus the events emitted
|
|
26
|
+
* via the supplied callback. The original input list is not mutated.
|
|
27
|
+
*/
|
|
28
|
+
import type { ParsedToolCall } from '../tool-use-parser';
|
|
29
|
+
export type ToolCallNormalizeEmit = (type: string, payload?: unknown) => void;
|
|
30
|
+
export interface NormalizeToolCallBatchArgs {
|
|
31
|
+
/** The raw parsed list straight off this iteration's chat response. */
|
|
32
|
+
toolCalls: ReadonlyArray<ParsedToolCall>;
|
|
33
|
+
/** Iteration index — used as the event `iteration` field. */
|
|
34
|
+
iteration: number;
|
|
35
|
+
/** Max calls executed in parallel within a single iteration. */
|
|
36
|
+
maxParallelTools: number;
|
|
37
|
+
/** Hard cap on tool calls across the full turn. */
|
|
38
|
+
maxTotalTools: number;
|
|
39
|
+
/** Running total of tools executed prior to this iteration. */
|
|
40
|
+
totalToolsExecuted: number;
|
|
41
|
+
/** Event sink (typically the loop's `emit`). */
|
|
42
|
+
emit: ToolCallNormalizeEmit;
|
|
43
|
+
}
|
|
44
|
+
export interface NormalizeToolCallBatchResult {
|
|
45
|
+
/** The final list of calls to execute this iteration. */
|
|
46
|
+
accepted: ParsedToolCall[];
|
|
47
|
+
/** Byte-identical duplicates removed. */
|
|
48
|
+
dedupedCount: number;
|
|
49
|
+
/** Excess foreground `task` calls dropped (kept at most one). */
|
|
50
|
+
droppedForegroundTaskCalls: number;
|
|
51
|
+
/** Calls dropped because the batch exceeded `maxParallelTools`. */
|
|
52
|
+
droppedParallelCap: number;
|
|
53
|
+
/** Calls dropped because the batch would exceed the per-turn cap. */
|
|
54
|
+
droppedTotalCap: number;
|
|
55
|
+
}
|
|
56
|
+
export declare function normalizeToolCallBatch(args: NormalizeToolCallBatchArgs): NormalizeToolCallBatchResult;
|
|
57
|
+
//# sourceMappingURL=toolCallNormalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolCallNormalize.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/toolCallNormalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE9E,MAAM,WAAW,0BAA0B;IACzC,uEAAuE;IACvE,SAAS,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACzC,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C,yDAAyD;IACzD,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,0BAA0B,EAAE,MAAM,CAAC;IACnC,mEAAmE;IACnE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,0BAA0B,GAAG,4BAA4B,CAoGrG"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeToolCallBatch = normalizeToolCallBatch;
|
|
4
|
+
function normalizeToolCallBatch(args) {
|
|
5
|
+
const { iteration, maxParallelTools, maxTotalTools, totalToolsExecuted, emit } = args;
|
|
6
|
+
let accepted = [...args.toolCalls];
|
|
7
|
+
// 1. Byte-identical dedup. `${name}::${JSON.stringify(params)}` is the
|
|
8
|
+
// signature. Only runs when there are 2+ calls — single-call iterations
|
|
9
|
+
// can't contain a duplicate of themselves.
|
|
10
|
+
let dedupedCount = 0;
|
|
11
|
+
if (accepted.length > 1) {
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
const deduped = [];
|
|
14
|
+
for (const tc of accepted) {
|
|
15
|
+
const sig = `${tc.name}::${JSON.stringify(tc.params)}`;
|
|
16
|
+
if (seen.has(sig)) {
|
|
17
|
+
dedupedCount++;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
seen.add(sig);
|
|
21
|
+
deduped.push(tc);
|
|
22
|
+
}
|
|
23
|
+
if (dedupedCount > 0) {
|
|
24
|
+
emit('tool_loop:tool_call_deduped', {
|
|
25
|
+
iteration,
|
|
26
|
+
removed: dedupedCount,
|
|
27
|
+
kept: deduped.length
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
accepted = deduped;
|
|
31
|
+
}
|
|
32
|
+
// 2. Foreground-task fanout cap. A `task` call with
|
|
33
|
+
// run_in_background != 'true' blocks the parent iteration on the
|
|
34
|
+
// subagent's completion; running multiple in one iteration stacks
|
|
35
|
+
// serially. Keep the first foreground task, drop the rest. Background
|
|
36
|
+
// tasks are unaffected — they're fire-and-forget.
|
|
37
|
+
let droppedForegroundTaskCalls = 0;
|
|
38
|
+
if (accepted.length > 1) {
|
|
39
|
+
let keptForegroundTask = false;
|
|
40
|
+
const scoped = [];
|
|
41
|
+
for (const tc of accepted) {
|
|
42
|
+
const isForegroundTask = tc.name === 'task' &&
|
|
43
|
+
String(tc.params.run_in_background ?? '').toLowerCase() !== 'true';
|
|
44
|
+
if (!isForegroundTask) {
|
|
45
|
+
scoped.push(tc);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!keptForegroundTask) {
|
|
49
|
+
scoped.push(tc);
|
|
50
|
+
keptForegroundTask = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
droppedForegroundTaskCalls++;
|
|
54
|
+
}
|
|
55
|
+
if (droppedForegroundTaskCalls > 0) {
|
|
56
|
+
emit('tool_loop:foreground_task_fanout_capped', {
|
|
57
|
+
iteration,
|
|
58
|
+
kept: 1,
|
|
59
|
+
dropped: droppedForegroundTaskCalls
|
|
60
|
+
});
|
|
61
|
+
accepted = scoped;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 3. Per-iteration parallel cap.
|
|
65
|
+
let droppedParallelCap = 0;
|
|
66
|
+
if (accepted.length > maxParallelTools) {
|
|
67
|
+
droppedParallelCap = accepted.length - maxParallelTools;
|
|
68
|
+
emit('tool_loop:tool_call_capped', {
|
|
69
|
+
iteration,
|
|
70
|
+
requested: accepted.length + 0,
|
|
71
|
+
kept: maxParallelTools,
|
|
72
|
+
dropped: droppedParallelCap
|
|
73
|
+
});
|
|
74
|
+
accepted = accepted.slice(0, maxParallelTools);
|
|
75
|
+
}
|
|
76
|
+
// 4. Per-turn total cap. Slice to fit remaining budget; the next
|
|
77
|
+
// iteration short-circuits on the count check.
|
|
78
|
+
let droppedTotalCap = 0;
|
|
79
|
+
const remainingBudget = Math.max(0, maxTotalTools - totalToolsExecuted);
|
|
80
|
+
if (accepted.length > remainingBudget) {
|
|
81
|
+
droppedTotalCap = accepted.length - remainingBudget;
|
|
82
|
+
emit('tool_loop:tool_call_total_capped', {
|
|
83
|
+
iteration,
|
|
84
|
+
requested: accepted.length,
|
|
85
|
+
kept: remainingBudget,
|
|
86
|
+
totalSoFar: totalToolsExecuted,
|
|
87
|
+
maxTotalTools
|
|
88
|
+
});
|
|
89
|
+
accepted = accepted.slice(0, remainingBudget);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
accepted,
|
|
93
|
+
dedupedCount,
|
|
94
|
+
droppedForegroundTaskCalls,
|
|
95
|
+
droppedParallelCap,
|
|
96
|
+
droppedTotalCap
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=toolCallNormalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolCallNormalize.js","sourceRoot":"","sources":["../../../src/tools/loop/toolCallNormalize.ts"],"names":[],"mappings":";;AA2DA,wDAoGC;AApGD,SAAgB,sBAAsB,CAAC,IAAgC;IACrE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,aAAa,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IACtF,IAAI,QAAQ,GAAqB,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;IAErD,uEAAuE;IACvE,wEAAwE;IACxE,2CAA2C;IAC3C,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,OAAO,GAAqB,EAAE,CAAC;QACrC,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClB,YAAY,EAAE,CAAC;gBACf,SAAS;YACX,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;QACD,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,6BAA6B,EAAE;gBAClC,SAAS;gBACT,OAAO,EAAE,YAAY;gBACrB,IAAI,EAAE,OAAO,CAAC,MAAM;aACrB,CAAC,CAAC;QACL,CAAC;QACD,QAAQ,GAAG,OAAO,CAAC;IACrB,CAAC;IAED,oDAAoD;IACpD,iEAAiE;IACjE,kEAAkE;IAClE,sEAAsE;IACtE,kDAAkD;IAClD,IAAI,0BAA0B,GAAG,CAAC,CAAC;IACnC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,IAAI,kBAAkB,GAAG,KAAK,CAAC;QAC/B,MAAM,MAAM,GAAqB,EAAE,CAAC;QACpC,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,MAAM,gBAAgB,GACpB,EAAE,CAAC,IAAI,KAAK,MAAM;gBAClB,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;YACrE,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACtB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAChB,kBAAkB,GAAG,IAAI,CAAC;gBAC1B,SAAS;YACX,CAAC;YACD,0BAA0B,EAAE,CAAC;QAC/B,CAAC;QACD,IAAI,0BAA0B,GAAG,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,yCAAyC,EAAE;gBAC9C,SAAS;gBACT,IAAI,EAAE,CAAC;gBACP,OAAO,EAAE,0BAA0B;aACpC,CAAC,CAAC;YACH,QAAQ,GAAG,MAAM,CAAC;QACpB,CAAC;IACH,CAAC;IAED,iCAAiC;IACjC,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAC3B,IAAI,QAAQ,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACvC,kBAAkB,GAAG,QAAQ,CAAC,MAAM,GAAG,gBAAgB,CAAC;QACxD,IAAI,CAAC,4BAA4B,EAAE;YACjC,SAAS;YACT,SAAS,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC;YAC9B,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,kBAAkB;SAC5B,CAAC,CAAC;QACH,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACjD,CAAC;IAED,iEAAiE;IACjE,+CAA+C;IAC/C,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,kBAAkB,CAAC,CAAC;IACxE,IAAI,QAAQ,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACtC,eAAe,GAAG,QAAQ,CAAC,MAAM,GAAG,eAAe,CAAC;QACpD,IAAI,CAAC,kCAAkC,EAAE;YACvC,SAAS;YACT,SAAS,EAAE,QAAQ,CAAC,MAAM;YAC1B,IAAI,EAAE,eAAe;YACrB,UAAU,EAAE,kBAAkB;YAC9B,aAAa;SACd,CAAC,CAAC;QACH,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IAChD,CAAC;IAED,OAAO;QACL,QAAQ;QACR,YAAY;QACZ,0BAA0B;QAC1B,kBAAkB;QAClB,eAAe;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot setup helpers for ToolUseLoop.runWithMessages — work that
|
|
3
|
+
* runs once per turn BEFORE the iteration loop.
|
|
4
|
+
*
|
|
5
|
+
* Current contents: `resolveTurnGoal`. Future Session 1/2/3 extractions
|
|
6
|
+
* (system-prompt assembly, native-tools schema build, counter init)
|
|
7
|
+
* will land here too. Kept under `loop/` so the orchestrator imports
|
|
8
|
+
* stay grouped with the other Arc 3 modules.
|
|
9
|
+
*/
|
|
10
|
+
import type { ToolLoopMessage } from '../tool-types';
|
|
11
|
+
export interface ResolveTurnGoalArgs {
|
|
12
|
+
seedMessages: ReadonlyArray<ToolLoopMessage>;
|
|
13
|
+
}
|
|
14
|
+
export interface ResolvedTurnGoal {
|
|
15
|
+
/** The user message that anchors THIS turn — what the model is being
|
|
16
|
+
* asked to do right now. Used by the goal-anchor reminder injected
|
|
17
|
+
* before final-answer iterations to defeat recency bias from long
|
|
18
|
+
* tool-result chains. Empty string when the seed has no user message. */
|
|
19
|
+
originalGoal: string;
|
|
20
|
+
/** Count of earlier user prompts in the seed history (everything
|
|
21
|
+
* before the most-recent substantive one). Used by the goal-anchor
|
|
22
|
+
* injection to add an "ignore prior prompts" note when there are
|
|
23
|
+
* earlier conversation turns the model might confuse for the goal. */
|
|
24
|
+
priorUserPromptCount: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the per-turn goal anchor from the seed message history.
|
|
28
|
+
*
|
|
29
|
+
* Walks the seed messages forward to find the most-recent user prompt.
|
|
30
|
+
* If that prompt is a bare continuation token ("keep going", "yes",
|
|
31
|
+
* "good lets keep going" — see CONTINUATION_PROMPT_PHRASES in
|
|
32
|
+
* tool-use-loop.ts), walks BACKWARD through history for the most
|
|
33
|
+
* recent SUBSTANTIVE prompt and anchors on that instead.
|
|
34
|
+
*
|
|
35
|
+
* Why the walkback: the original bug was a 60-iteration linter-fix
|
|
36
|
+
* turn that anchored every iteration on "good lets keep going"
|
|
37
|
+
* because that was the literal last user message. The recall block
|
|
38
|
+
* became "remind yourself to keep going" and gave the model zero
|
|
39
|
+
* useful steering. Walking back finds the real goal ("fix the
|
|
40
|
+
* remaining TS errors") and uses THAT as the anchor.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveTurnGoal(args: ResolveTurnGoalArgs): ResolvedTurnGoal;
|
|
43
|
+
//# sourceMappingURL=turnSetup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnSetup.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/turnSetup.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;6EAGyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB;;;0EAGsE;IACtE,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,gBAAgB,CAuB3E"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveTurnGoal = resolveTurnGoal;
|
|
4
|
+
const tool_use_loop_1 = require("../tool-use-loop");
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the per-turn goal anchor from the seed message history.
|
|
7
|
+
*
|
|
8
|
+
* Walks the seed messages forward to find the most-recent user prompt.
|
|
9
|
+
* If that prompt is a bare continuation token ("keep going", "yes",
|
|
10
|
+
* "good lets keep going" — see CONTINUATION_PROMPT_PHRASES in
|
|
11
|
+
* tool-use-loop.ts), walks BACKWARD through history for the most
|
|
12
|
+
* recent SUBSTANTIVE prompt and anchors on that instead.
|
|
13
|
+
*
|
|
14
|
+
* Why the walkback: the original bug was a 60-iteration linter-fix
|
|
15
|
+
* turn that anchored every iteration on "good lets keep going"
|
|
16
|
+
* because that was the literal last user message. The recall block
|
|
17
|
+
* became "remind yourself to keep going" and gave the model zero
|
|
18
|
+
* useful steering. Walking back finds the real goal ("fix the
|
|
19
|
+
* remaining TS errors") and uses THAT as the anchor.
|
|
20
|
+
*/
|
|
21
|
+
function resolveTurnGoal(args) {
|
|
22
|
+
const { seedMessages } = args;
|
|
23
|
+
let originalGoal = '';
|
|
24
|
+
let priorUserPromptCount = 0;
|
|
25
|
+
for (const msg of seedMessages) {
|
|
26
|
+
if (msg.role === 'user' && typeof msg.content === 'string' && msg.content.trim()) {
|
|
27
|
+
if (originalGoal)
|
|
28
|
+
priorUserPromptCount++;
|
|
29
|
+
originalGoal = msg.content;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (originalGoal && (0, tool_use_loop_1.isContinuationPrompt)(originalGoal)) {
|
|
33
|
+
for (let i = seedMessages.length - 1; i >= 0; i--) {
|
|
34
|
+
const m = seedMessages[i];
|
|
35
|
+
if (m.role !== 'user' || typeof m.content !== 'string')
|
|
36
|
+
continue;
|
|
37
|
+
const c = m.content.trim();
|
|
38
|
+
if (!c)
|
|
39
|
+
continue;
|
|
40
|
+
if (!(0, tool_use_loop_1.isContinuationPrompt)(c)) {
|
|
41
|
+
originalGoal = m.content;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { originalGoal, priorUserPromptCount };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=turnSetup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnSetup.js","sourceRoot":"","sources":["../../../src/tools/loop/turnSetup.ts"],"names":[],"mappings":";;AA6CA,0CAuBC;AA1DD,oDAAwD;AAmBxD;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,eAAe,CAAC,IAAyB;IACvD,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAC9B,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAC7B,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YACjF,IAAI,YAAY;gBAAE,oBAAoB,EAAE,CAAC;YACzC,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,IAAI,YAAY,IAAI,IAAA,oCAAoB,EAAC,YAAY,CAAC,EAAE,CAAC;QACvD,KAAK,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAClD,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;gBAAE,SAAS;YACjE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC;gBAAE,SAAS;YACjB,IAAI,CAAC,IAAA,oCAAoB,EAAC,CAAC,CAAC,EAAE,CAAC;gBAC7B,YAAY,GAAG,CAAC,CAAC,OAAO,CAAC;gBACzB,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local OCR — extract printed text from an image file without an LLM.
|
|
3
|
+
*
|
|
4
|
+
* Dispatcher picks the best available engine for the platform:
|
|
5
|
+
* macOS → Apple Vision via `swift` running a VNRecognizeTextRequest
|
|
6
|
+
* script. ~100-300ms per image, excellent on rendered text
|
|
7
|
+
* (code, logs, stack traces, dialogs).
|
|
8
|
+
* Linux → tesseract CLI (`apt install tesseract-ocr`). ~500ms-2s.
|
|
9
|
+
* Windows → tesseract CLI (PowerShell.Windows.Media.Ocr could be
|
|
10
|
+
* added later; tesseract covers the common case today).
|
|
11
|
+
*
|
|
12
|
+
* Used by the extension/CLI on bandit-logic turns where a user paste
|
|
13
|
+
* an image. When OCR yields usable text, we inline it into the prompt
|
|
14
|
+
* as an `[Image text (OCR): …]` block and skip the model swap. When
|
|
15
|
+
* OCR returns nothing (diagrams, photos, blurry), we fall back to a
|
|
16
|
+
* vision-capable model for that turn.
|
|
17
|
+
*
|
|
18
|
+
* Binary OCR engines themselves are not bundled — we shell out to what
|
|
19
|
+
* the user's OS already ships. Linux/Windows users see a one-time
|
|
20
|
+
* install hint when tesseract is missing.
|
|
21
|
+
*/
|
|
22
|
+
export interface OcrResult {
|
|
23
|
+
/** Extracted text. May be empty if OCR produced no confident output. */
|
|
24
|
+
text: string;
|
|
25
|
+
/** Engine that produced the text. Useful for telemetry. */
|
|
26
|
+
engine: 'apple-vision' | 'tesseract' | 'windows-ocr' | 'none';
|
|
27
|
+
/** Wall-clock time the OCR call took. */
|
|
28
|
+
durationMs: number;
|
|
29
|
+
}
|
|
30
|
+
export interface OcrOptions {
|
|
31
|
+
/** Abort the OCR run after this many ms. Defaults to 8s — catches
|
|
32
|
+
* a Vision hang without starving a long but legitimate recognition. */
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
}
|
|
35
|
+
export declare function detectOcrAvailability(): {
|
|
36
|
+
apple: boolean;
|
|
37
|
+
tesseract: boolean;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Extract text from an image at `imagePath`. Returns an OcrResult with
|
|
41
|
+
* empty `.text` on failure rather than throwing — callers treat "empty
|
|
42
|
+
* text" as the signal to fall back to an LLM-vision path.
|
|
43
|
+
*/
|
|
44
|
+
export declare function extractImageText(imagePath: string, options?: OcrOptions): Promise<OcrResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Heuristic for "the OCR captured enough to be useful". Used by the
|
|
47
|
+
* image-handling path to decide between "inline OCR text + stay on
|
|
48
|
+
* current model" vs "fall back to a vision-capable model". Very cheap;
|
|
49
|
+
* runs once per image.
|
|
50
|
+
*/
|
|
51
|
+
export declare function ocrYieldedUsefulText(text: string): boolean;
|
|
52
|
+
//# sourceMappingURL=ocr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ocr.d.ts","sourceRoot":"","sources":["../../src/tools/ocr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,MAAM,WAAW,SAAS;IACxB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,EAAE,cAAc,GAAG,WAAW,GAAG,aAAa,GAAG,MAAM,CAAC;IAC9D,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB;4EACwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD,wBAAgB,qBAAqB,IAAI;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAM9E;AAWD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAyBlG;AAwGD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAS1D"}
|