@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,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-iteration goal-anchor injector for ToolUseLoop.runWithMessages.
|
|
3
|
+
*
|
|
4
|
+
* Re-injects the original user goal into the conversation when the
|
|
5
|
+
* loop is at risk of drifting. Two observed drift modes the anchor
|
|
6
|
+
* defends against:
|
|
7
|
+
*
|
|
8
|
+
* 1. Recency bias — long tool-result chains push the goal down the
|
|
9
|
+
* attention window; model answers about whatever was salient in
|
|
10
|
+
* the most recent tool reads.
|
|
11
|
+
* 2. Multi-turn pivot — after a stretch of failed tool calls,
|
|
12
|
+
* compaction cleans them up and the model re-answers an EARLIER
|
|
13
|
+
* user prompt that's cleaner to address.
|
|
14
|
+
*
|
|
15
|
+
* Eligibility: `iterations >= 2 AND messageTokens > 4000`. Re-fire
|
|
16
|
+
* allowed after `GOAL_ANCHOR_REFIRE_GAP` (4) iterations on sticky turns.
|
|
17
|
+
*
|
|
18
|
+
* Aggressive compaction this iteration overrides BOTH the refire-gap
|
|
19
|
+
* AND the eligibility floor — it's the highest-risk drift trigger we
|
|
20
|
+
* have. Compaction stripped most of the file content the model was
|
|
21
|
+
* reasoning over; without an immediate re-anchor the model falls back
|
|
22
|
+
* to imitating tool-result formats from training (the 2026-05-06
|
|
23
|
+
* fabricated `read_file` envelope) or pivoting to a tangent that
|
|
24
|
+
* sounded plausible from the surviving tool names.
|
|
25
|
+
*
|
|
26
|
+
* Post-aggressive-compaction anchors also include:
|
|
27
|
+
* - a "context just compacted" preamble explaining what the
|
|
28
|
+
* `[earlier run, N lines elided]` markers actually mean
|
|
29
|
+
* - the live tool list (MCP-namespaced first, capped at 40) so a
|
|
30
|
+
* surviving "tool not registered" error doesn't convince the model
|
|
31
|
+
* that real tools are unavailable
|
|
32
|
+
*
|
|
33
|
+
* The whole module returns the new `lastGoalAnchorIteration` so the
|
|
34
|
+
* orchestrator can update its mutable counter; no other state escapes.
|
|
35
|
+
*/
|
|
36
|
+
import type { ToolLoopMessage } from '../tool-types';
|
|
37
|
+
import type { ToolRegistry } from '../tool-registry';
|
|
38
|
+
export type AnchorEmit = (type: string, payload?: unknown) => void;
|
|
39
|
+
export declare const GOAL_ANCHOR_REFIRE_GAP = 4;
|
|
40
|
+
export interface ApplyGoalAnchorArgs {
|
|
41
|
+
/** The user's goal for THIS turn (the most-recent substantive prompt). */
|
|
42
|
+
originalGoal: string;
|
|
43
|
+
/** Number of earlier user prompts in the seed — used by the
|
|
44
|
+
* "ignore prior prompts" suffix that defends against the multi-turn
|
|
45
|
+
* pivot failure mode. */
|
|
46
|
+
priorUserPromptCount: number;
|
|
47
|
+
/** When true, no anchor injection (the loop is wrapping up). */
|
|
48
|
+
hitLimit: boolean;
|
|
49
|
+
/** Current iteration counter. */
|
|
50
|
+
iteration: number;
|
|
51
|
+
/** Iteration at which the anchor last fired (-1 if never). */
|
|
52
|
+
lastGoalAnchorIteration: number;
|
|
53
|
+
/** When true, compaction this iteration dropped >=25% of tokens OR
|
|
54
|
+
* >=10k absolute. Overrides eligibility floor and refire gap. */
|
|
55
|
+
aggressiveCompactionThisIteration: boolean;
|
|
56
|
+
/** Mutable message log — anchor is pushed in place when fired. */
|
|
57
|
+
messages: ToolLoopMessage[];
|
|
58
|
+
/** Tool registry — read for the tool-list block when compaction is
|
|
59
|
+
* aggressive. */
|
|
60
|
+
registry: ToolRegistry;
|
|
61
|
+
/** Event sink for `tool_loop:goal_anchor`. */
|
|
62
|
+
emit: AnchorEmit;
|
|
63
|
+
}
|
|
64
|
+
export interface ApplyGoalAnchorResult {
|
|
65
|
+
/** New value for `lastGoalAnchorIteration` (caller assigns it back).
|
|
66
|
+
* Unchanged when the anchor did not fire. */
|
|
67
|
+
lastGoalAnchorIteration: number;
|
|
68
|
+
/** True when the anchor message was appended. */
|
|
69
|
+
anchored: boolean;
|
|
70
|
+
}
|
|
71
|
+
export declare function applyGoalAnchorIfNeeded(args: ApplyGoalAnchorArgs): ApplyGoalAnchorResult;
|
|
72
|
+
//# sourceMappingURL=goalAnchor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goalAnchor.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/goalAnchor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAEnE,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB;;6BAEyB;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,EAAE,OAAO,CAAC;IAClB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,uBAAuB,EAAE,MAAM,CAAC;IAChC;qEACiE;IACjE,iCAAiC,EAAE,OAAO,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;qBACiB;IACjB,QAAQ,EAAE,YAAY,CAAC;IACvB,8CAA8C;IAC9C,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC;iDAC6C;IAC7C,uBAAuB,EAAE,MAAM,CAAC;IAChC,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAyFxF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GOAL_ANCHOR_REFIRE_GAP = void 0;
|
|
4
|
+
exports.applyGoalAnchorIfNeeded = applyGoalAnchorIfNeeded;
|
|
5
|
+
exports.GOAL_ANCHOR_REFIRE_GAP = 4;
|
|
6
|
+
function applyGoalAnchorIfNeeded(args) {
|
|
7
|
+
const { originalGoal, priorUserPromptCount, hitLimit, iteration, lastGoalAnchorIteration, aggressiveCompactionThisIteration, messages, registry, emit } = args;
|
|
8
|
+
if (!originalGoal || hitLimit) {
|
|
9
|
+
return { lastGoalAnchorIteration, anchored: false };
|
|
10
|
+
}
|
|
11
|
+
const messageTokens = messages.reduce((acc, m) => acc + (m.content?.length ?? 0), 0);
|
|
12
|
+
const eligible = iteration >= 2 && messageTokens > 4000;
|
|
13
|
+
const canRefire = lastGoalAnchorIteration >= 0
|
|
14
|
+
&& (iteration - lastGoalAnchorIteration) >= exports.GOAL_ANCHOR_REFIRE_GAP;
|
|
15
|
+
const shouldAnchor = aggressiveCompactionThisIteration
|
|
16
|
+
|| (eligible && (lastGoalAnchorIteration < 0 || canRefire));
|
|
17
|
+
if (!shouldAnchor) {
|
|
18
|
+
return { lastGoalAnchorIteration, anchored: false };
|
|
19
|
+
}
|
|
20
|
+
emit('tool_loop:goal_anchor', {
|
|
21
|
+
iteration,
|
|
22
|
+
goalPreview: originalGoal.slice(0, 120),
|
|
23
|
+
priorUserPromptCount,
|
|
24
|
+
refire: canRefire,
|
|
25
|
+
postAggressiveCompaction: aggressiveCompactionThisIteration
|
|
26
|
+
});
|
|
27
|
+
// Multi-turn-aware anchor. When prior user prompts exist in history,
|
|
28
|
+
// explicitly tell the model to ignore them — that's the failure mode
|
|
29
|
+
// boolean recency-bias guards don't catch. Structured header +
|
|
30
|
+
// bullet so even small models can lock onto the format.
|
|
31
|
+
const ignoreEarlier = priorUserPromptCount > 0
|
|
32
|
+
? `\n - There ${priorUserPromptCount === 1 ? 'is 1 earlier user prompt' : `are ${priorUserPromptCount} earlier user prompts`} in this conversation. Do NOT answer ${priorUserPromptCount === 1 ? 'it' : 'them'}. They were settled in prior turns.`
|
|
33
|
+
: '';
|
|
34
|
+
// Re-inject the current tool list whenever compaction has been
|
|
35
|
+
// aggressive — defends against the failure mode where an earlier
|
|
36
|
+
// error message ("tool not registered" or "expected object received
|
|
37
|
+
// string") survives compaction while the success path doesn't, and
|
|
38
|
+
// the model concludes the tool doesn't exist. The model would
|
|
39
|
+
// normally see the tools in the native-tools schema sent on every
|
|
40
|
+
// call, but small/mid models trust prose history over schema, so we
|
|
41
|
+
// make the availability claim textual and authoritative.
|
|
42
|
+
let toolListBlock = '';
|
|
43
|
+
if (aggressiveCompactionThisIteration) {
|
|
44
|
+
const allNames = registry.getAll().map((t) => t.name);
|
|
45
|
+
if (allNames.length > 0) {
|
|
46
|
+
// Cap to keep the prompt budget bounded — surface MCP-namespaced
|
|
47
|
+
// tools (the ones most likely to be hallucinated as absent)
|
|
48
|
+
// first, then any remaining names, then truncate.
|
|
49
|
+
const mcpNames = allNames.filter((n) => n.includes('.') || n.startsWith('mcp__'));
|
|
50
|
+
const otherNames = allNames.filter((n) => !mcpNames.includes(n));
|
|
51
|
+
const ordered = [...mcpNames, ...otherNames];
|
|
52
|
+
const TOOL_LIST_CAP = 40;
|
|
53
|
+
const shown = ordered.slice(0, TOOL_LIST_CAP);
|
|
54
|
+
const more = ordered.length > TOOL_LIST_CAP ? ` (+${ordered.length - TOOL_LIST_CAP} more)` : '';
|
|
55
|
+
toolListBlock =
|
|
56
|
+
`## TOOLS CURRENTLY AVAILABLE THIS TURN${more}\n\n` +
|
|
57
|
+
shown.map((n) => ` - ${n}`).join('\n') +
|
|
58
|
+
'\n\nIf you think a tool is missing, re-check this list before saying so. ' +
|
|
59
|
+
'The names above were sent to you in the native-tools schema on THIS call.\n\n';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const compactionPreamble = aggressiveCompactionThisIteration
|
|
63
|
+
? '## CONTEXT JUST COMPACTED — read this first.\n\n' +
|
|
64
|
+
'Most of the tool-result content from this turn was just collapsed to one-line placeholders to fit the context window. ' +
|
|
65
|
+
'Those `[earlier run, N lines elided]` markers represent real reads whose content is no longer in front of you. ' +
|
|
66
|
+
'Do NOT fabricate `<tool_result>` blocks pretending to read files; do NOT pivot to a topic that looks salient based on which tool names survived in the placeholders. ' +
|
|
67
|
+
'Answer from what you ALREADY learned in this turn, owning honestly anything you cannot recall in detail.\n\n' +
|
|
68
|
+
toolListBlock
|
|
69
|
+
: '';
|
|
70
|
+
messages.push({
|
|
71
|
+
role: 'user',
|
|
72
|
+
content: `${compactionPreamble}## CURRENT GOAL — answer THIS, nothing else:\n\n "${originalGoal.trim()}"\n\nRules:\n - Use what you have gathered to answer the goal above.\n - Do not pivot to a related topic that happens to be salient in recent tool results.${ignoreEarlier}\n - If the available tools cannot finish the goal, own that honestly in your final answer — do NOT redirect to an easier question.`
|
|
73
|
+
});
|
|
74
|
+
return { lastGoalAnchorIteration: iteration, anchored: true };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=goalAnchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goalAnchor.js","sourceRoot":"","sources":["../../../src/tools/loop/goalAnchor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM streaming with intra-response loop detection and same-channel
|
|
3
|
+
* transient-error retry. Extracted from ToolUseLoop.runWithMessages so
|
|
4
|
+
* the orchestrator only holds high-level control flow, not the chunk-
|
|
5
|
+
* level fingerprint accounting and the retry ladder.
|
|
6
|
+
*
|
|
7
|
+
* Behavior pinned by `packages/agent-core/test/llmStreamContract.test.ts`:
|
|
8
|
+
*
|
|
9
|
+
* - Chunks emit through `tool_loop:llm_chunk` in arrival order; the
|
|
10
|
+
* aggregated text is returned in full when no detector trips.
|
|
11
|
+
* - HARD_MAX cap (24,000 chars) aborts the stream, marks it, and
|
|
12
|
+
* appends a sentinel so the downstream prose-loop detector can route
|
|
13
|
+
* to the nudge path. Real trace it caught: a single response of
|
|
14
|
+
* 24,663 chars of "Wait, I'll try X. Actually..." prose.
|
|
15
|
+
* - Line-level fast path: 5 identical short lines (<= 120 chars,
|
|
16
|
+
* non-empty, trimmed) in a row aborts the stream. Catches
|
|
17
|
+
* "I can't install things." × 24 before the 800-char fingerprint
|
|
18
|
+
* window can fire (24 short lines ≈ 550 chars, below CHECK_EVERY).
|
|
19
|
+
* - Window-level fingerprint: every 800 chars, hash the last 400
|
|
20
|
+
* (whitespace-normalized, numeric tokens collapsed to '#') and check
|
|
21
|
+
* for 3 identical fingerprints in a row. Catches re-numbered list
|
|
22
|
+
* repetition the line-level path misses.
|
|
23
|
+
* - User abort: `signal.aborted` mid-stream emits `stream_abort` with
|
|
24
|
+
* reason `cancelled` and breaks. No retry.
|
|
25
|
+
* - Retryable error WITH zero text streamed AND (no native tools OR
|
|
26
|
+
* not yet streamed): exponential backoff retry (1.2s × 2^n, two
|
|
27
|
+
* attempts). Emits `tool_loop:llm_retry` per attempt.
|
|
28
|
+
* - Non-retryable error OR partial text already streamed: re-throw
|
|
29
|
+
* (tagged with `UPSTREAM_MODEL` code on retryable to preserve the
|
|
30
|
+
* existing error-summarization contract for outer-layer handlers).
|
|
31
|
+
*
|
|
32
|
+
* Gate widening note (preserved from the in-class implementation):
|
|
33
|
+
* the original retry gate was `!tools || tools.length === 0` — only
|
|
34
|
+
* text-tools turns got the same-channel ladder. That meant native-tools
|
|
35
|
+
* turns hit a transient 500 once and jumped to the disruptive native→
|
|
36
|
+
* text envelope switch (a second hiccup hard-failed). Widening to
|
|
37
|
+
* `!tools || tools.length === 0 || safeToReplay` keeps the original
|
|
38
|
+
* guarantee (no duplicated tool_call deltas after partial output) but
|
|
39
|
+
* lets the native path retry transient infra blips before the envelope
|
|
40
|
+
* switch escalates.
|
|
41
|
+
*/
|
|
42
|
+
import type { ChatFn, ToolLoopMessage, ChatCallOptions, NativeToolSchema } from '../tool-types';
|
|
43
|
+
export type StreamEmit = (type: string, payload?: unknown) => void;
|
|
44
|
+
export interface StreamAndAggregateArgs {
|
|
45
|
+
chat: ChatFn;
|
|
46
|
+
messages: ToolLoopMessage[];
|
|
47
|
+
emit: StreamEmit;
|
|
48
|
+
iteration: number;
|
|
49
|
+
tools?: NativeToolSchema[];
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
callOptions?: ChatCallOptions;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Stream from the chat function and return the fully aggregated response
|
|
55
|
+
* string. Aborts early if we detect the model is trapped in a self-
|
|
56
|
+
* contradicting prose loop — repeating phrases like "Wait, I see X
|
|
57
|
+
* isn't listed. Let me check X. Actually, I'll try to read X." without
|
|
58
|
+
* ever emitting a tool call. This was observed Apr 2026 on
|
|
59
|
+
* bandit-core-1: a single stream produced 24k chars of such prose.
|
|
60
|
+
* Without this guard the turn terminates naturally (no tool calls →
|
|
61
|
+
* final response), but the user sees the entire wall of repetition.
|
|
62
|
+
*
|
|
63
|
+
* Detection is cheap: after every ~800 chars we compute a hash of the
|
|
64
|
+
* last ~400 chars and check if the same hash appeared recently. Three
|
|
65
|
+
* repeats in a row means we're looping — abort and return what we
|
|
66
|
+
* have. The upstream loop will then still call the prose-loop detector
|
|
67
|
+
* and apply its nudge on the next iteration.
|
|
68
|
+
*/
|
|
69
|
+
export declare function streamAndAggregate(args: StreamAndAggregateArgs): Promise<string>;
|
|
70
|
+
//# sourceMappingURL=llmStream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llmStream.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/llmStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGhG,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAEnE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmKtF"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.streamAndAggregate = streamAndAggregate;
|
|
4
|
+
const tool_use_loop_1 = require("../tool-use-loop");
|
|
5
|
+
/**
|
|
6
|
+
* Stream from the chat function and return the fully aggregated response
|
|
7
|
+
* string. Aborts early if we detect the model is trapped in a self-
|
|
8
|
+
* contradicting prose loop — repeating phrases like "Wait, I see X
|
|
9
|
+
* isn't listed. Let me check X. Actually, I'll try to read X." without
|
|
10
|
+
* ever emitting a tool call. This was observed Apr 2026 on
|
|
11
|
+
* bandit-core-1: a single stream produced 24k chars of such prose.
|
|
12
|
+
* Without this guard the turn terminates naturally (no tool calls →
|
|
13
|
+
* final response), but the user sees the entire wall of repetition.
|
|
14
|
+
*
|
|
15
|
+
* Detection is cheap: after every ~800 chars we compute a hash of the
|
|
16
|
+
* last ~400 chars and check if the same hash appeared recently. Three
|
|
17
|
+
* repeats in a row means we're looping — abort and return what we
|
|
18
|
+
* have. The upstream loop will then still call the prose-loop detector
|
|
19
|
+
* and apply its nudge on the next iteration.
|
|
20
|
+
*/
|
|
21
|
+
async function streamAndAggregate(args) {
|
|
22
|
+
const { chat, messages, emit, iteration, tools, signal, callOptions } = args;
|
|
23
|
+
let text = '';
|
|
24
|
+
// Intra-response loop guard. Tuned by observation, not theory:
|
|
25
|
+
// - CHECK_EVERY=800: a single "Actually, I'll try to read X." cycle
|
|
26
|
+
// in the real trace was ~120-180 chars, so we want multiple
|
|
27
|
+
// samples per cycle but not one per chunk (that would be slow).
|
|
28
|
+
// - WINDOW=400: the tail chunk used to build the fingerprint.
|
|
29
|
+
// - THRESHOLD=3: three identical fingerprints in a row is
|
|
30
|
+
// overwhelmingly a loop; two could be a natural repetition
|
|
31
|
+
// (e.g. a code block shown twice).
|
|
32
|
+
// - HARD_MAX=24000: a soft upper bound so a runaway generation
|
|
33
|
+
// doesn't burn unbounded tokens even if the fingerprint shifts
|
|
34
|
+
// slightly each cycle. The real trace was 24663 chars.
|
|
35
|
+
const CHECK_EVERY = 800;
|
|
36
|
+
const WINDOW = 400;
|
|
37
|
+
const THRESHOLD = 3;
|
|
38
|
+
const HARD_MAX = 24000;
|
|
39
|
+
const recentFingerprints = [];
|
|
40
|
+
let nextCheckAt = CHECK_EVERY;
|
|
41
|
+
let aborted = false;
|
|
42
|
+
// Line-level repetition guard. Catches "I can't install things." × 24
|
|
43
|
+
// long before the 800-char fingerprint check fires (24 short lines is
|
|
44
|
+
// ~550 chars, below CHECK_EVERY). Tracks the last completed line
|
|
45
|
+
// (text up to the most recent \n) and counts consecutive identical
|
|
46
|
+
// non-empty short lines. Five in a row is a clear loop; trips
|
|
47
|
+
// earlier than the full-fingerprint detector ever can.
|
|
48
|
+
const REPEAT_LINE_THRESHOLD = 5;
|
|
49
|
+
const REPEAT_LINE_MAXLEN = 120; // only short lines — long paragraphs aren't loops
|
|
50
|
+
let lastLine = '';
|
|
51
|
+
let repeatCount = 1;
|
|
52
|
+
let lastEmittedLineLength = 0;
|
|
53
|
+
const MAX_TRANSIENT_RETRIES = 2;
|
|
54
|
+
const BASE_RETRY_MS = 1200;
|
|
55
|
+
let transientRetries = 0;
|
|
56
|
+
while (true) {
|
|
57
|
+
try {
|
|
58
|
+
for await (const chunk of chat(messages, tools, callOptions)) {
|
|
59
|
+
if (signal?.aborted) {
|
|
60
|
+
aborted = true;
|
|
61
|
+
emit('tool_loop:stream_abort', { iteration, reason: 'cancelled', length: text.length });
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
text += chunk;
|
|
65
|
+
emit('tool_loop:llm_chunk', { iteration, chunk });
|
|
66
|
+
if (text.length >= HARD_MAX) {
|
|
67
|
+
aborted = true;
|
|
68
|
+
emit('tool_loop:stream_abort', { iteration, reason: 'hard_max', length: text.length });
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
// Line-repetition fast path. Walk newly-completed lines (between
|
|
72
|
+
// lastEmittedLineLength and the most recent \n) and bump a
|
|
73
|
+
// consecutive-identical counter. Trips faster than the
|
|
74
|
+
// 800-char fingerprint window for short-phrase loops.
|
|
75
|
+
const lastNewline = text.lastIndexOf('\n');
|
|
76
|
+
if (lastNewline > lastEmittedLineLength) {
|
|
77
|
+
const newSegment = text.slice(lastEmittedLineLength, lastNewline);
|
|
78
|
+
for (const rawLine of newSegment.split('\n')) {
|
|
79
|
+
const line = rawLine.trim();
|
|
80
|
+
if (!line || line.length > REPEAT_LINE_MAXLEN) {
|
|
81
|
+
lastLine = line;
|
|
82
|
+
repeatCount = 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (line === lastLine) {
|
|
86
|
+
repeatCount += 1;
|
|
87
|
+
if (repeatCount >= REPEAT_LINE_THRESHOLD) {
|
|
88
|
+
aborted = true;
|
|
89
|
+
emit('tool_loop:stream_abort', {
|
|
90
|
+
iteration,
|
|
91
|
+
reason: 'line_repetition_loop',
|
|
92
|
+
length: text.length,
|
|
93
|
+
fingerprintPreview: line.slice(0, 120),
|
|
94
|
+
repeatCount
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
lastLine = line;
|
|
101
|
+
repeatCount = 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
lastEmittedLineLength = lastNewline + 1;
|
|
105
|
+
if (aborted)
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
if (text.length >= nextCheckAt) {
|
|
109
|
+
nextCheckAt = text.length + CHECK_EVERY;
|
|
110
|
+
const tail = text.slice(Math.max(0, text.length - WINDOW));
|
|
111
|
+
// Normalize whitespace + drop numeric noise so "3." vs "4." don't
|
|
112
|
+
// defeat the comparison when a model re-numbers each attempt.
|
|
113
|
+
const fingerprint = tail
|
|
114
|
+
.toLowerCase()
|
|
115
|
+
.replace(/\s+/g, ' ')
|
|
116
|
+
.replace(/\d+/g, '#')
|
|
117
|
+
.trim();
|
|
118
|
+
recentFingerprints.push(fingerprint);
|
|
119
|
+
if (recentFingerprints.length > THRESHOLD)
|
|
120
|
+
recentFingerprints.shift();
|
|
121
|
+
if (recentFingerprints.length === THRESHOLD &&
|
|
122
|
+
recentFingerprints.every(f => f === fingerprint)) {
|
|
123
|
+
aborted = true;
|
|
124
|
+
emit('tool_loop:stream_abort', {
|
|
125
|
+
iteration,
|
|
126
|
+
reason: 'intra_response_loop',
|
|
127
|
+
length: text.length,
|
|
128
|
+
fingerprintPreview: fingerprint.slice(0, 120)
|
|
129
|
+
});
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const retryable = (0, tool_use_loop_1.isRetryableLlmError)(error);
|
|
138
|
+
const safeToReplay = text.length === 0;
|
|
139
|
+
// Original gate was `!tools || tools.length === 0` — i.e. only
|
|
140
|
+
// text-tools turns got the same-channel backoff ladder. Native-tools
|
|
141
|
+
// turns hit a transient 500 once and jumped straight to the
|
|
142
|
+
// disruptive native→text envelope switch (and a second hiccup
|
|
143
|
+
// hard-failed). The gate exists to prevent re-emitting tool-call
|
|
144
|
+
// deltas after partial output was already streamed; when
|
|
145
|
+
// `safeToReplay` is true NO chunks have arrived yet, so there are
|
|
146
|
+
// no deltas to duplicate. Widening the gate to allow retry when
|
|
147
|
+
// safeToReplay holds catches transient infra blips (gateway load
|
|
148
|
+
// shedding, ollama restart, brief network glitches) on the native
|
|
149
|
+
// path without risking duplicated tool calls.
|
|
150
|
+
const sameTransportRetryAllowed = !tools || tools.length === 0 || safeToReplay;
|
|
151
|
+
if (retryable && safeToReplay && sameTransportRetryAllowed && !signal?.aborted && transientRetries < MAX_TRANSIENT_RETRIES) {
|
|
152
|
+
transientRetries++;
|
|
153
|
+
const delayMs = BASE_RETRY_MS * Math.pow(2, transientRetries - 1);
|
|
154
|
+
emit('tool_loop:llm_retry', {
|
|
155
|
+
iteration,
|
|
156
|
+
attempt: transientRetries + 1,
|
|
157
|
+
maxAttempts: MAX_TRANSIENT_RETRIES + 1,
|
|
158
|
+
delayMs,
|
|
159
|
+
reason: (0, tool_use_loop_1.summarizeLlmError)(error)
|
|
160
|
+
});
|
|
161
|
+
await (0, tool_use_loop_1.sleep)(delayMs);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (retryable)
|
|
165
|
+
(0, tool_use_loop_1.tagRetryableLlmError)(error);
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// If we aborted a runaway stream, trim trailing garbage and append
|
|
170
|
+
// a sentinel so the downstream final-response detector can recognize
|
|
171
|
+
// this and route to the prose-loop nudge (or a clean termination).
|
|
172
|
+
if (aborted) {
|
|
173
|
+
// Drop the last (likely-truncated) repetition cycle from the
|
|
174
|
+
// output — cleaner to end at a previous paragraph boundary than
|
|
175
|
+
// mid-sentence.
|
|
176
|
+
const trimmed = text.replace(/\s+$/, '');
|
|
177
|
+
text = trimmed + '\n\n[stream aborted: self-contradicting prose loop detected]';
|
|
178
|
+
}
|
|
179
|
+
return text;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=llmStream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llmStream.js","sourceRoot":"","sources":["../../../src/tools/loop/llmStream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output-budget-aware batch dispatch for ToolUseLoop.runWithMessages.
|
|
3
|
+
*
|
|
4
|
+
* Takes a normalized batch (already trimmed by maxParallelTools +
|
|
5
|
+
* dedup'd by `normalizeToolCallBatch`) and runs it. Two execution
|
|
6
|
+
* modes:
|
|
7
|
+
*
|
|
8
|
+
* Parallel (default): `Promise.all(toolCalls.map(dispatchOne))`.
|
|
9
|
+
*
|
|
10
|
+
* Serial: when the estimated combined output of the batch would
|
|
11
|
+
* exceed `outputBudgetTokens * outputBudgetRatio`, the batch runs
|
|
12
|
+
* one tool at a time. Each call short-circuits on `signal.aborted`.
|
|
13
|
+
*
|
|
14
|
+
* Why the serial mode exists: smaller models (4B–12B) generate
|
|
15
|
+
* malformed JSON in the tail of a multi-file emission once their
|
|
16
|
+
* effective output budget is exhausted. on a
|
|
17
|
+
* portfolio build — even a strong model produced a malformed
|
|
18
|
+
* `todo_write` after writing four files of ~7 KB each in one
|
|
19
|
+
* assistant turn. Serialising lets the model react to each result
|
|
20
|
+
* before committing further output, and gives the user one approval
|
|
21
|
+
* at a time instead of a queued pile.
|
|
22
|
+
*
|
|
23
|
+
* Single-call batches skip the threshold check entirely — there's no
|
|
24
|
+
* parallel/serial distinction with one call, and the gate's purpose
|
|
25
|
+
* is preventing a *batch* from overrunning the assistant turn.
|
|
26
|
+
*
|
|
27
|
+
* Token estimate is intentionally coarse (heavy payload fields × ¼).
|
|
28
|
+
* Reads and small calls never trip the gate; only writes/edits whose
|
|
29
|
+
* `content`/`replace`/`find`/`text` fields dominate the output budget.
|
|
30
|
+
* Accuracy isn't the goal — order-of-magnitude is enough to gate.
|
|
31
|
+
*/
|
|
32
|
+
import type { ParsedToolCall } from '../tool-use-parser';
|
|
33
|
+
import type { ToolDispatchResult } from './singleToolExecute';
|
|
34
|
+
export type BatchEmit = (type: string, payload?: unknown) => void;
|
|
35
|
+
export interface ExecuteParallelBatchArgs {
|
|
36
|
+
toolCalls: ParsedToolCall[];
|
|
37
|
+
dispatchOne: (tc: ParsedToolCall) => Promise<ToolDispatchResult>;
|
|
38
|
+
outputBudgetTokens: number;
|
|
39
|
+
outputBudgetRatio: number;
|
|
40
|
+
emit: BatchEmit;
|
|
41
|
+
iteration: number;
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Coarse token estimate for a single tool call's contribution to the
|
|
46
|
+
* assistant turn's output. Heavy fields (file content, edit
|
|
47
|
+
* replacements, apply_edit find/replace blocks) dominate; everything
|
|
48
|
+
* else is negligible. Uses chars/4 as a rough byte→token approximation
|
|
49
|
+
* — fast and good enough to gate batches; we don't need accuracy, just
|
|
50
|
+
* an order-of-magnitude check.
|
|
51
|
+
*/
|
|
52
|
+
export declare function estimateToolCallOutputTokens(tc: {
|
|
53
|
+
name: string;
|
|
54
|
+
params: Record<string, string>;
|
|
55
|
+
}): number;
|
|
56
|
+
export declare function executeParallelBatch(args: ExecuteParallelBatchArgs): Promise<ToolDispatchResult[]>;
|
|
57
|
+
//# sourceMappingURL=parallelExecute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallelExecute.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/parallelExecute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAElE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,MAAM,CAMzG;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAmCxG"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.estimateToolCallOutputTokens = estimateToolCallOutputTokens;
|
|
4
|
+
exports.executeParallelBatch = executeParallelBatch;
|
|
5
|
+
/**
|
|
6
|
+
* Coarse token estimate for a single tool call's contribution to the
|
|
7
|
+
* assistant turn's output. Heavy fields (file content, edit
|
|
8
|
+
* replacements, apply_edit find/replace blocks) dominate; everything
|
|
9
|
+
* else is negligible. Uses chars/4 as a rough byte→token approximation
|
|
10
|
+
* — fast and good enough to gate batches; we don't need accuracy, just
|
|
11
|
+
* an order-of-magnitude check.
|
|
12
|
+
*/
|
|
13
|
+
function estimateToolCallOutputTokens(tc) {
|
|
14
|
+
const params = tc.params ?? {};
|
|
15
|
+
const heavy = [params.content, params.replace, params.find, params.text]
|
|
16
|
+
.filter((v) => typeof v === 'string' && v.length > 0)
|
|
17
|
+
.reduce((sum, s) => sum + s.length, 0);
|
|
18
|
+
return Math.ceil(heavy / 4);
|
|
19
|
+
}
|
|
20
|
+
async function executeParallelBatch(args) {
|
|
21
|
+
const { toolCalls, dispatchOne, outputBudgetTokens, outputBudgetRatio, emit, iteration, signal } = args;
|
|
22
|
+
// Output-budget gate. Only meaningful for multi-call batches —
|
|
23
|
+
// single-call iterations never have a parallel/serial choice.
|
|
24
|
+
let serializeBatch = false;
|
|
25
|
+
if (Number.isFinite(outputBudgetTokens) && toolCalls.length > 1) {
|
|
26
|
+
let estimatedBatchOutputTokens = 0;
|
|
27
|
+
for (const tc of toolCalls) {
|
|
28
|
+
estimatedBatchOutputTokens += estimateToolCallOutputTokens(tc);
|
|
29
|
+
}
|
|
30
|
+
const threshold = outputBudgetTokens * outputBudgetRatio;
|
|
31
|
+
if (estimatedBatchOutputTokens > threshold) {
|
|
32
|
+
serializeBatch = true;
|
|
33
|
+
emit('tool_loop:batch_serialized', {
|
|
34
|
+
iteration,
|
|
35
|
+
toolCount: toolCalls.length,
|
|
36
|
+
estimatedTokens: estimatedBatchOutputTokens,
|
|
37
|
+
budgetTokens: outputBudgetTokens,
|
|
38
|
+
threshold: Math.floor(threshold),
|
|
39
|
+
reason: 'output-budget-exceeded'
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (serializeBatch) {
|
|
44
|
+
const results = [];
|
|
45
|
+
for (const tc of toolCalls) {
|
|
46
|
+
if (signal?.aborted)
|
|
47
|
+
break;
|
|
48
|
+
results.push(await dispatchOne(tc));
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
return Promise.all(toolCalls.map(dispatchOne));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=parallelExecute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallelExecute.js","sourceRoot":"","sources":["../../../src/tools/loop/parallelExecute.ts"],"names":[],"mappings":";;AAsDA,oEAMC;AAED,oDAmCC;AAnDD;;;;;;;GAOG;AACH,SAAgB,4BAA4B,CAAC,EAAoD;IAC/F,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;SACrE,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SACjE,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;AAC9B,CAAC;AAEM,KAAK,UAAU,oBAAoB,CAAC,IAA8B;IACvE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAExG,+DAA+D;IAC/D,8DAA8D;IAC9D,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChE,IAAI,0BAA0B,GAAG,CAAC,CAAC;QACnC,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;YAC3B,0BAA0B,IAAI,4BAA4B,CAAC,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,SAAS,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;QACzD,IAAI,0BAA0B,GAAG,SAAS,EAAE,CAAC;YAC3C,cAAc,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC,4BAA4B,EAAE;gBACjC,SAAS;gBACT,SAAS,EAAE,SAAS,CAAC,MAAM;gBAC3B,eAAe,EAAE,0BAA0B;gBAC3C,YAAY,EAAE,kBAAkB;gBAChC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;gBAChC,MAAM,EAAE,wBAAwB;aACjC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,OAAO,GAAyB,EAAE,CAAC;QACzC,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;YAC3B,IAAI,MAAM,EAAE,OAAO;gBAAE,MAAM;YAC3B,OAAO,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-tool execution closure for ToolUseLoop.runWithMessages.
|
|
3
|
+
*
|
|
4
|
+
* The dispatcher takes one ParsedToolCall and runs it end-to-end:
|
|
5
|
+
*
|
|
6
|
+
* - Build a coarse signature for the repeat-call breaker (the model's
|
|
7
|
+
* "I'll just retry the same broken write 4 times in a row" failure
|
|
8
|
+
* mode). The window is `repeatLimit` calls; once all entries are
|
|
9
|
+
* identical, the dispatcher short-circuits with a stop-this-now error
|
|
10
|
+
* instead of running the tool.
|
|
11
|
+
* - Look up the tool in the registry. Unknown tool → error result.
|
|
12
|
+
* - Run the host's `beforeToolExecute` gate. Blocked → error result.
|
|
13
|
+
* - Execute the tool. On success: bump the success-only edit counter
|
|
14
|
+
* via `onEditToolSucceeded()` for file-editing tools, and record the
|
|
15
|
+
* touched file (normalized to basename) in `filesReadThisTurn` /
|
|
16
|
+
* `filesWrittenThisTurn`. On exception: catch + emit `tool_error`.
|
|
17
|
+
* - Emit `tool_execute` before invocation and `tool_result` (with a
|
|
18
|
+
* redacted output snippet) after.
|
|
19
|
+
*
|
|
20
|
+
* The factory captures all the per-turn deps in a closure and returns
|
|
21
|
+
* a single async function. Mutable turn-state — `recentCallKeys`,
|
|
22
|
+
* `filesReadThisTurn`, `filesWrittenThisTurn` — is passed by reference
|
|
23
|
+
* so the dispatcher mutates the same objects the orchestrator (and the
|
|
24
|
+
* loop's detector pipeline) read.
|
|
25
|
+
*
|
|
26
|
+
* The success-only edit counter (`editToolsInvoked` in the orchestrator)
|
|
27
|
+
* has multiple downstream readers (false-completion detector, wrap-up
|
|
28
|
+
* template chooser), so it's routed through `onEditToolSucceeded` rather
|
|
29
|
+
* than incremented directly here.
|
|
30
|
+
*/
|
|
31
|
+
import type { ParsedToolCall } from '../tool-use-parser';
|
|
32
|
+
import type { ToolExecutionContext } from '../tool-types';
|
|
33
|
+
import type { ToolRegistry } from '../tool-registry';
|
|
34
|
+
export type DispatchEmit = (type: string, payload?: unknown) => void;
|
|
35
|
+
export type BeforeToolExecuteFn = (call: {
|
|
36
|
+
name: string;
|
|
37
|
+
params: Record<string, string>;
|
|
38
|
+
}) => Promise<{
|
|
39
|
+
allow: boolean;
|
|
40
|
+
reason?: string;
|
|
41
|
+
}> | {
|
|
42
|
+
allow: boolean;
|
|
43
|
+
reason?: string;
|
|
44
|
+
};
|
|
45
|
+
export interface ToolDispatchDeps {
|
|
46
|
+
registry: ToolRegistry;
|
|
47
|
+
ctx: ToolExecutionContext;
|
|
48
|
+
beforeToolExecute: BeforeToolExecuteFn;
|
|
49
|
+
emit: DispatchEmit;
|
|
50
|
+
/** Mutable turn-state — rolling window of recent call signatures. */
|
|
51
|
+
recentCallKeys: string[];
|
|
52
|
+
/** Cap on the rolling window; identical keys filling it trip the breaker. */
|
|
53
|
+
repeatLimit: number;
|
|
54
|
+
/** Mutable set of basenames read this turn (used by false-completion detector). */
|
|
55
|
+
filesReadThisTurn: Set<string>;
|
|
56
|
+
/** Mutable set of basenames written this turn. */
|
|
57
|
+
filesWrittenThisTurn: Set<string>;
|
|
58
|
+
/** Predicate identifying file-editing tools (write/apply_edit/replace_range). */
|
|
59
|
+
isFileEditTool: (name: string) => boolean;
|
|
60
|
+
/** Called once per successful file-editing tool result. Hosts use this
|
|
61
|
+
* to bump the `editToolsInvoked` counter that gates false-completion
|
|
62
|
+
* nudges and wrap-up template selection. */
|
|
63
|
+
onEditToolSucceeded: () => void;
|
|
64
|
+
}
|
|
65
|
+
export type ToolDispatchResult = {
|
|
66
|
+
name: string;
|
|
67
|
+
output: string;
|
|
68
|
+
isError?: boolean;
|
|
69
|
+
};
|
|
70
|
+
export declare function createToolDispatcher(deps: ToolDispatchDeps): (tc: ParsedToolCall) => Promise<ToolDispatchResult>;
|
|
71
|
+
//# sourceMappingURL=singleToolExecute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleToolExecute.d.ts","sourceRoot":"","sources":["../../../src/tools/loop/singleToolExecute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,eAAe,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AACrE,MAAM,MAAM,mBAAmB,GAAG,CAChC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KACnD,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,oBAAoB,CAAC;IAC1B,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,qEAAqE;IACrE,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,kDAAkD;IAClD,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC,iFAAiF;IACjF,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C;;gDAE4C;IAC5C,mBAAmB,EAAE,MAAM,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAErF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,GAAG,CAAC,EAAE,EAAE,cAAc,KAAK,OAAO,CAAC,kBAAkB,CAAC,CA4IhH"}
|