@kenkaiiii/gg-agent 5.68.1 → 5.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +264 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +243 -44
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -41,11 +41,11 @@ __export(index_exports, {
|
|
|
41
41
|
module.exports = __toCommonJS(index_exports);
|
|
42
42
|
|
|
43
43
|
// src/agent.ts
|
|
44
|
-
var
|
|
44
|
+
var import_gg_ai3 = require("@kenkaiiii/gg-ai");
|
|
45
45
|
|
|
46
46
|
// src/agent-loop.ts
|
|
47
47
|
var import_zod = require("zod");
|
|
48
|
-
var
|
|
48
|
+
var import_gg_ai2 = require("@kenkaiiii/gg-ai");
|
|
49
49
|
|
|
50
50
|
// src/local-backend.ts
|
|
51
51
|
function isLocalBackendUrl(baseUrl) {
|
|
@@ -69,6 +69,107 @@ function isLocalBackendUrl(baseUrl) {
|
|
|
69
69
|
return false;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// src/tool-call-hints.ts
|
|
73
|
+
var import_gg_ai = require("@kenkaiiii/gg-ai");
|
|
74
|
+
function normalizeName(name) {
|
|
75
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
76
|
+
}
|
|
77
|
+
function editDistance(a, b) {
|
|
78
|
+
let beforePrevious = [];
|
|
79
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
80
|
+
for (let i = 1; i <= a.length; i++) {
|
|
81
|
+
const current = [i];
|
|
82
|
+
for (let j = 1; j <= b.length; j++) {
|
|
83
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
84
|
+
let distance = Math.min(
|
|
85
|
+
(previous[j] ?? 0) + 1,
|
|
86
|
+
(current[j - 1] ?? 0) + 1,
|
|
87
|
+
(previous[j - 1] ?? 0) + cost
|
|
88
|
+
);
|
|
89
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
90
|
+
distance = Math.min(distance, (beforePrevious[j - 2] ?? 0) + 1);
|
|
91
|
+
}
|
|
92
|
+
current[j] = distance;
|
|
93
|
+
}
|
|
94
|
+
beforePrevious = previous;
|
|
95
|
+
previous = current;
|
|
96
|
+
}
|
|
97
|
+
return previous[b.length] ?? 0;
|
|
98
|
+
}
|
|
99
|
+
function closestName(name, candidates) {
|
|
100
|
+
const target = normalizeName(name);
|
|
101
|
+
if (!target) return void 0;
|
|
102
|
+
const limit = Math.min(3, Math.max(1, Math.floor(target.length / 3)));
|
|
103
|
+
let best;
|
|
104
|
+
for (const candidate of [...candidates].sort()) {
|
|
105
|
+
if (candidate === name) continue;
|
|
106
|
+
const distance = editDistance(target, normalizeName(candidate));
|
|
107
|
+
if (distance > limit) continue;
|
|
108
|
+
if (!best || distance < best.distance) best = { name: candidate, distance };
|
|
109
|
+
}
|
|
110
|
+
return best?.name;
|
|
111
|
+
}
|
|
112
|
+
function unknownToolMessage(name, available) {
|
|
113
|
+
const suggestion = closestName(name, available);
|
|
114
|
+
if (suggestion) return `Unknown tool: ${name}. Did you mean \`${suggestion}\`?`;
|
|
115
|
+
const names = [...available].sort();
|
|
116
|
+
const listed = names.length > 40 ? `${names.slice(0, 40).join(", ")}, \u2026` : names.join(", ");
|
|
117
|
+
const searchHint = available.includes("tool_search") ? " If the capability is not listed, call `tool_search` to load it." : "";
|
|
118
|
+
return `Unknown tool: ${name}. Available tools: ${listed}.${searchHint}`;
|
|
119
|
+
}
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
function typeLabel(node) {
|
|
124
|
+
if (!isRecord(node)) return "any";
|
|
125
|
+
if (Array.isArray(node.enum)) {
|
|
126
|
+
return node.enum.map((value) => JSON.stringify(value)).join(" | ");
|
|
127
|
+
}
|
|
128
|
+
const variants = node.anyOf ?? node.oneOf;
|
|
129
|
+
if (Array.isArray(variants)) {
|
|
130
|
+
return [...new Set(variants.map(typeLabel))].filter((t) => t !== "null").join(" | ") || "any";
|
|
131
|
+
}
|
|
132
|
+
if (node.type === "array") return `${typeLabel(node.items)}[]`;
|
|
133
|
+
if (Array.isArray(node.type)) {
|
|
134
|
+
return node.type.filter((t) => t !== "null").join(" | ") || "any";
|
|
135
|
+
}
|
|
136
|
+
return typeof node.type === "string" ? node.type : "any";
|
|
137
|
+
}
|
|
138
|
+
function objectShape(tool) {
|
|
139
|
+
let schema;
|
|
140
|
+
try {
|
|
141
|
+
schema = (0, import_gg_ai.resolveToolSchema)(tool);
|
|
142
|
+
} catch {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
if (!isRecord(schema) || !isRecord(schema.properties)) return void 0;
|
|
146
|
+
const required = new Set(
|
|
147
|
+
Array.isArray(schema.required) ? schema.required.filter((r) => typeof r === "string") : []
|
|
148
|
+
);
|
|
149
|
+
return { properties: schema.properties, required };
|
|
150
|
+
}
|
|
151
|
+
function argumentHints(tool, args) {
|
|
152
|
+
const shape = objectShape(tool);
|
|
153
|
+
if (!shape) return "";
|
|
154
|
+
const names = Object.keys(shape.properties);
|
|
155
|
+
if (names.length === 0) return `Expected arguments: none.`;
|
|
156
|
+
const fields = names.map((name) => {
|
|
157
|
+
const optional = shape.required.has(name) ? "" : "?";
|
|
158
|
+
return `${name}${optional}: ${typeLabel(shape.properties[name])}`;
|
|
159
|
+
});
|
|
160
|
+
const lines = [`Expected arguments: { ${fields.join(", ")} }`];
|
|
161
|
+
if (isRecord(args)) {
|
|
162
|
+
for (const key of Object.keys(args).sort()) {
|
|
163
|
+
if (key in shape.properties) continue;
|
|
164
|
+
const suggestion = closestName(key, names);
|
|
165
|
+
lines.push(
|
|
166
|
+
suggestion ? `Unknown field \`${key}\` \u2014 did you mean \`${suggestion}\`?` : `Unknown field \`${key}\` is not a parameter of this tool.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|
|
172
|
+
|
|
72
173
|
// src/output-ceiling.ts
|
|
73
174
|
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
74
175
|
var MIN_PLAUSIBLE_CEILING = 256;
|
|
@@ -253,9 +354,32 @@ ${rule.reminder.trim()}
|
|
|
253
354
|
// src/agent-loop.ts
|
|
254
355
|
var DEFAULT_MAX_TURNS = 300;
|
|
255
356
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
|
|
357
|
+
var PREEMPT_GRACE_MS = 500;
|
|
358
|
+
var STEER_INTERRUPTED_TEXT = "Interrupted: the user sent a new message.";
|
|
359
|
+
var ATOMIC_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "multi_edit", "apply_patch", "notebook_edit"]);
|
|
360
|
+
function isInterruptibleTool(tool, name) {
|
|
361
|
+
if (tool?.interruptible !== void 0) return tool.interruptible;
|
|
362
|
+
return !ATOMIC_TOOL_NAMES.has(name);
|
|
363
|
+
}
|
|
364
|
+
function markSteerInterrupted(record) {
|
|
365
|
+
const content = record.content;
|
|
366
|
+
let next;
|
|
367
|
+
if (typeof content === "string") {
|
|
368
|
+
next = content.trim() ? `${STEER_INTERRUPTED_TEXT}
|
|
369
|
+
Partial output:
|
|
370
|
+
${content}` : STEER_INTERRUPTED_TEXT;
|
|
371
|
+
} else {
|
|
372
|
+
next = [
|
|
373
|
+
{ type: "text", text: `${STEER_INTERRUPTED_TEXT}
|
|
374
|
+
Partial output:` },
|
|
375
|
+
...content
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
return { toolCallId: record.toolCallId, content: next, isError: true };
|
|
379
|
+
}
|
|
256
380
|
var _toolRedaction;
|
|
257
381
|
function toolRedactionOptions() {
|
|
258
|
-
_toolRedaction ??= { secrets: (0,
|
|
382
|
+
_toolRedaction ??= { secrets: (0, import_gg_ai2.environmentSecrets)(process.env) };
|
|
259
383
|
return _toolRedaction;
|
|
260
384
|
}
|
|
261
385
|
var _diagFn = null;
|
|
@@ -338,7 +462,7 @@ function isBillingError(err) {
|
|
|
338
462
|
if (!(err instanceof Error)) return false;
|
|
339
463
|
const statusCode = err.statusCode;
|
|
340
464
|
if (statusCode === 402) return true;
|
|
341
|
-
return (0,
|
|
465
|
+
return (0, import_gg_ai2.isHardBillingMessage)(err.message);
|
|
342
466
|
}
|
|
343
467
|
function isUsageLimitError(err) {
|
|
344
468
|
if (!(err instanceof Error)) return false;
|
|
@@ -707,7 +831,7 @@ async function* agentLoop(messages, options) {
|
|
|
707
831
|
});
|
|
708
832
|
}
|
|
709
833
|
}
|
|
710
|
-
const result = (0,
|
|
834
|
+
const result = (0, import_gg_ai2.stream)({
|
|
711
835
|
provider: options.provider,
|
|
712
836
|
model: options.model,
|
|
713
837
|
messages,
|
|
@@ -1170,7 +1294,7 @@ async function* agentLoop(messages, options) {
|
|
|
1170
1294
|
});
|
|
1171
1295
|
yield {
|
|
1172
1296
|
type: "error",
|
|
1173
|
-
error: new
|
|
1297
|
+
error: new import_gg_ai2.GGAIError(
|
|
1174
1298
|
`The connection to the API provider stopped responding after ${MAX_STALL_RETRIES} automatic retries. Your conversation is preserved.`,
|
|
1175
1299
|
{
|
|
1176
1300
|
source: "network",
|
|
@@ -1403,10 +1527,14 @@ async function* agentLoop(messages, options) {
|
|
|
1403
1527
|
fatalToolArgumentRecoverable = recoverable;
|
|
1404
1528
|
fatalToolArgumentToolName = toolName;
|
|
1405
1529
|
};
|
|
1530
|
+
const preempt = new AbortController();
|
|
1531
|
+
const unsubscribeSteering = options.onSteeringAvailable?.(() => preempt.abort());
|
|
1406
1532
|
const executionOptions = {
|
|
1407
1533
|
signal: options.signal,
|
|
1534
|
+
preemptSignal: options.onSteeringAvailable ? preempt.signal : void 0,
|
|
1408
1535
|
maxToolResultChars: options.maxToolResultChars,
|
|
1409
1536
|
maxTurnToolResultChars: options.maxTurnToolResultChars,
|
|
1537
|
+
transformToolResult: options.transformToolResult,
|
|
1410
1538
|
toolMap,
|
|
1411
1539
|
invalidToolArgumentCounts,
|
|
1412
1540
|
markFatalToolArgumentError,
|
|
@@ -1415,7 +1543,15 @@ async function* agentLoop(messages, options) {
|
|
|
1415
1543
|
const hasSequentialToolCall = toolCalls.some(
|
|
1416
1544
|
(toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
|
|
1417
1545
|
);
|
|
1418
|
-
|
|
1546
|
+
let executionResult;
|
|
1547
|
+
try {
|
|
1548
|
+
executionResult = hasSequentialToolCall ? yield* executeToolCallsMixed(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
|
|
1549
|
+
} finally {
|
|
1550
|
+
unsubscribeSteering?.();
|
|
1551
|
+
}
|
|
1552
|
+
if (executionResult.preempted) {
|
|
1553
|
+
diag("steer_preempted_tools", { turn, provider: options.provider, model: options.model });
|
|
1554
|
+
}
|
|
1419
1555
|
messages.push({ role: "tool", content: executionResult.toolResults });
|
|
1420
1556
|
yield { type: "checkpoint", turn };
|
|
1421
1557
|
const toolsAborted = executionResult.aborted;
|
|
@@ -1572,7 +1708,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1572
1708
|
options.seenToolCalls.add(signature);
|
|
1573
1709
|
}
|
|
1574
1710
|
if (!tool) {
|
|
1575
|
-
resultContent =
|
|
1711
|
+
resultContent = unknownToolMessage(toolCall.name, [...options.toolMap.keys()]);
|
|
1576
1712
|
isError = true;
|
|
1577
1713
|
} else {
|
|
1578
1714
|
try {
|
|
@@ -1589,8 +1725,12 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1589
1725
|
}
|
|
1590
1726
|
const callerSignal = options.signal;
|
|
1591
1727
|
const toolTimeout = AbortSignal.timeout(tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS);
|
|
1728
|
+
const preemptSignal = isInterruptibleTool(tool, toolCall.name) ? options.preemptSignal : void 0;
|
|
1729
|
+
const toolSignals = [callerSignal, toolTimeout, preemptSignal].filter(
|
|
1730
|
+
(s) => s !== void 0
|
|
1731
|
+
);
|
|
1592
1732
|
const ctx = {
|
|
1593
|
-
signal:
|
|
1733
|
+
signal: toolSignals.length === 1 ? toolTimeout : AbortSignal.any(toolSignals),
|
|
1594
1734
|
toolCallId: toolCall.id,
|
|
1595
1735
|
onUpdate: (update) => {
|
|
1596
1736
|
pushEvent({
|
|
@@ -1602,8 +1742,17 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1602
1742
|
};
|
|
1603
1743
|
const raw = await tool.execute(parsed, ctx);
|
|
1604
1744
|
const normalized = normalizeToolResult(raw);
|
|
1605
|
-
resultContent = (0,
|
|
1606
|
-
details = (0,
|
|
1745
|
+
resultContent = (0, import_gg_ai2.redactValue)(normalized.content, toolRedactionOptions());
|
|
1746
|
+
details = (0, import_gg_ai2.redactValue)(normalized.details, toolRedactionOptions());
|
|
1747
|
+
if (options.transformToolResult) {
|
|
1748
|
+
try {
|
|
1749
|
+
resultContent = options.transformToolResult(
|
|
1750
|
+
{ name: toolCall.name, args: toolCall.args },
|
|
1751
|
+
resultContent
|
|
1752
|
+
);
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1607
1756
|
for (const key of options.invalidToolArgumentCounts.keys()) {
|
|
1608
1757
|
if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
|
|
1609
1758
|
}
|
|
@@ -1615,12 +1764,14 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1615
1764
|
const failureCount = (options.invalidToolArgumentCounts.get(failureKey) ?? 0) + 1;
|
|
1616
1765
|
options.invalidToolArgumentCounts.set(failureKey, failureCount);
|
|
1617
1766
|
invalidArgAttempt = failureCount;
|
|
1767
|
+
const hints = argumentHints(tool, toolCall.args);
|
|
1618
1768
|
resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
|
|
1619
|
-
` + prettyError +
|
|
1769
|
+
` + prettyError + (hints ? `
|
|
1770
|
+
${hints}` : "") + "\nRe-issue the call with each field as the correct type.";
|
|
1620
1771
|
if (failureCount >= 3) {
|
|
1621
1772
|
const recoverable = Object.keys(toolCall.args ?? {}).length === 0;
|
|
1622
1773
|
options.markFatalToolArgumentError(
|
|
1623
|
-
new
|
|
1774
|
+
new import_gg_ai2.GGAIError(
|
|
1624
1775
|
`The model repeatedly issued invalid arguments for tool \`${toolCall.name}\`. This is usually an upstream model/tool-calling bug` + (recoverable ? " (the provider's stream returned empty tool-call arguments)" : "") + `. Your conversation is preserved; send another message or switch models to continue.`,
|
|
1625
1776
|
{
|
|
1626
1777
|
source: "provider",
|
|
@@ -1632,15 +1783,24 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1632
1783
|
);
|
|
1633
1784
|
}
|
|
1634
1785
|
} else {
|
|
1635
|
-
resultContent = (0,
|
|
1786
|
+
resultContent = (0, import_gg_ai2.redactValue)(
|
|
1636
1787
|
err instanceof Error ? err.message : String(err),
|
|
1637
1788
|
toolRedactionOptions()
|
|
1638
1789
|
);
|
|
1639
1790
|
}
|
|
1640
1791
|
}
|
|
1641
1792
|
}
|
|
1642
|
-
resultContent = (0,
|
|
1643
|
-
details = (0,
|
|
1793
|
+
resultContent = (0, import_gg_ai2.redactValue)(resultContent, toolRedactionOptions());
|
|
1794
|
+
details = (0, import_gg_ai2.redactValue)(details, toolRedactionOptions());
|
|
1795
|
+
if (options.preemptSignal?.aborted && isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name)) {
|
|
1796
|
+
const marked = markSteerInterrupted({
|
|
1797
|
+
toolCallId: toolCall.id,
|
|
1798
|
+
content: resultContent,
|
|
1799
|
+
isError
|
|
1800
|
+
});
|
|
1801
|
+
resultContent = marked.content;
|
|
1802
|
+
isError = true;
|
|
1803
|
+
}
|
|
1644
1804
|
const durationMs = Date.now() - startTime;
|
|
1645
1805
|
pushEvent({
|
|
1646
1806
|
type: "tool_call_end",
|
|
@@ -1653,13 +1813,67 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1653
1813
|
});
|
|
1654
1814
|
return { toolCallId: toolCall.id, content: resultContent, isError };
|
|
1655
1815
|
}
|
|
1816
|
+
function createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds) {
|
|
1817
|
+
const running = /* @__PURE__ */ new Map();
|
|
1818
|
+
let graceTimer;
|
|
1819
|
+
const preemptSignal = options.preemptSignal;
|
|
1820
|
+
const abandon = () => {
|
|
1821
|
+
graceTimer = void 0;
|
|
1822
|
+
for (const [id, entry] of running) {
|
|
1823
|
+
if (!entry.interruptible || resultsById.has(id)) continue;
|
|
1824
|
+
resultsById.set(id, { toolCallId: id, content: STEER_INTERRUPTED_TEXT, isError: true });
|
|
1825
|
+
pushToolEvent(eventStream, state, {
|
|
1826
|
+
type: "tool_call_end",
|
|
1827
|
+
toolCallId: id,
|
|
1828
|
+
result: STEER_INTERRUPTED_TEXT,
|
|
1829
|
+
isError: true,
|
|
1830
|
+
durationMs: Date.now() - entry.startedAt
|
|
1831
|
+
});
|
|
1832
|
+
running.delete(id);
|
|
1833
|
+
}
|
|
1834
|
+
if (running.size === 0) eventStream.close();
|
|
1835
|
+
};
|
|
1836
|
+
const onPreempt = () => {
|
|
1837
|
+
if (graceTimer === void 0) graceTimer = setTimeout(abandon, PREEMPT_GRACE_MS);
|
|
1838
|
+
};
|
|
1839
|
+
preemptSignal?.addEventListener("abort", onPreempt, { once: true });
|
|
1840
|
+
return {
|
|
1841
|
+
async run(toolCall) {
|
|
1842
|
+
if (options.preemptSignal?.aborted) return;
|
|
1843
|
+
dispatchedIds.add(toolCall.id);
|
|
1844
|
+
const interruptible = isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name);
|
|
1845
|
+
running.set(toolCall.id, { toolCall, interruptible, startedAt: Date.now() });
|
|
1846
|
+
const record = await executeSingleToolCall(
|
|
1847
|
+
toolCall,
|
|
1848
|
+
options,
|
|
1849
|
+
(event) => pushToolEvent(eventStream, state, event)
|
|
1850
|
+
);
|
|
1851
|
+
running.delete(toolCall.id);
|
|
1852
|
+
if (resultsById.has(toolCall.id)) return;
|
|
1853
|
+
const alreadyMarked = typeof record.content === "string" ? record.content.startsWith(STEER_INTERRUPTED_TEXT) : record.content[0]?.type === "text" && record.content[0].text.startsWith(STEER_INTERRUPTED_TEXT);
|
|
1854
|
+
resultsById.set(
|
|
1855
|
+
toolCall.id,
|
|
1856
|
+
interruptible && preemptSignal?.aborted && !alreadyMarked ? markSteerInterrupted(record) : record
|
|
1857
|
+
);
|
|
1858
|
+
if (preemptSignal?.aborted && graceTimer === void 0 && running.size === 0) {
|
|
1859
|
+
eventStream.close();
|
|
1860
|
+
}
|
|
1861
|
+
},
|
|
1862
|
+
preempted: () => preemptSignal?.aborted === true,
|
|
1863
|
+
dispose: () => {
|
|
1864
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
1865
|
+
preemptSignal?.removeEventListener("abort", onPreempt);
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1656
1869
|
async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
1657
|
-
const eventStream = new
|
|
1870
|
+
const eventStream = new import_gg_ai2.EventStream();
|
|
1658
1871
|
const state = { finalized: false };
|
|
1659
1872
|
const resultsById = /* @__PURE__ */ new Map();
|
|
1660
1873
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1661
1874
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1662
1875
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1876
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1663
1877
|
const phases = [];
|
|
1664
1878
|
let currentParallel = [];
|
|
1665
1879
|
for (const toolCall of toolCalls) {
|
|
@@ -1680,40 +1894,18 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1680
1894
|
void (async () => {
|
|
1681
1895
|
try {
|
|
1682
1896
|
for (const phase of phases) {
|
|
1683
|
-
if (options.signal?.aborted) break;
|
|
1897
|
+
if (options.signal?.aborted || options.preemptSignal?.aborted) break;
|
|
1684
1898
|
if (phase.sequential) {
|
|
1685
1899
|
const signature = JSON.stringify([
|
|
1686
1900
|
phase.sequential.name,
|
|
1687
1901
|
canonicalToolArgs(phase.sequential.args)
|
|
1688
1902
|
]);
|
|
1689
1903
|
if (!options.seenToolCalls.has(signature)) options.seenToolCalls.clear();
|
|
1690
|
-
|
|
1691
|
-
const record = await executeSingleToolCall(
|
|
1692
|
-
phase.sequential,
|
|
1693
|
-
options,
|
|
1694
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1695
|
-
);
|
|
1696
|
-
resultsById.set(record.toolCallId, record);
|
|
1904
|
+
await tracker.run(phase.sequential);
|
|
1697
1905
|
} else if (phase.parallel.length === 1) {
|
|
1698
|
-
|
|
1699
|
-
const record = await executeSingleToolCall(
|
|
1700
|
-
phase.parallel[0],
|
|
1701
|
-
options,
|
|
1702
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1703
|
-
);
|
|
1704
|
-
resultsById.set(record.toolCallId, record);
|
|
1906
|
+
await tracker.run(phase.parallel[0]);
|
|
1705
1907
|
} else {
|
|
1706
|
-
await Promise.all(
|
|
1707
|
-
phase.parallel.map(async (toolCall) => {
|
|
1708
|
-
dispatchedIds.add(toolCall.id);
|
|
1709
|
-
const record = await executeSingleToolCall(
|
|
1710
|
-
toolCall,
|
|
1711
|
-
options,
|
|
1712
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1713
|
-
);
|
|
1714
|
-
resultsById.set(record.toolCallId, record);
|
|
1715
|
-
})
|
|
1716
|
-
);
|
|
1908
|
+
await Promise.all(phase.parallel.map((toolCall) => tracker.run(toolCall)));
|
|
1717
1909
|
}
|
|
1718
1910
|
}
|
|
1719
1911
|
if (!state.finalized) eventStream.close();
|
|
@@ -1734,31 +1926,23 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1734
1926
|
}
|
|
1735
1927
|
} finally {
|
|
1736
1928
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1929
|
+
tracker.dispose();
|
|
1737
1930
|
state.finalized = true;
|
|
1738
1931
|
}
|
|
1739
1932
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1740
1933
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1741
1934
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1742
|
-
return { toolResults, aborted };
|
|
1935
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1743
1936
|
}
|
|
1744
1937
|
async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
|
|
1745
|
-
const eventStream = new
|
|
1938
|
+
const eventStream = new import_gg_ai2.EventStream();
|
|
1746
1939
|
const state = { finalized: false };
|
|
1747
1940
|
const resultsById = /* @__PURE__ */ new Map();
|
|
1748
1941
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1749
1942
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1750
1943
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
dispatchedIds.add(toolCall.id);
|
|
1754
|
-
const record = await executeSingleToolCall(
|
|
1755
|
-
toolCall,
|
|
1756
|
-
options,
|
|
1757
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1758
|
-
);
|
|
1759
|
-
resultsById.set(record.toolCallId, record);
|
|
1760
|
-
})
|
|
1761
|
-
).then(() => {
|
|
1944
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1945
|
+
Promise.all(toolCalls.map((toolCall) => tracker.run(toolCall))).then(() => {
|
|
1762
1946
|
if (!state.finalized) eventStream.close();
|
|
1763
1947
|
}).catch((err) => {
|
|
1764
1948
|
if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1776,12 +1960,13 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1776
1960
|
}
|
|
1777
1961
|
} finally {
|
|
1778
1962
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1963
|
+
tracker.dispose();
|
|
1779
1964
|
state.finalized = true;
|
|
1780
1965
|
}
|
|
1781
1966
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1782
1967
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1783
1968
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1784
|
-
return { toolResults, aborted };
|
|
1969
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1785
1970
|
}
|
|
1786
1971
|
function cancelledBeforeStartText(name) {
|
|
1787
1972
|
return `\`${name}\` was cancelled before it started, so it had no effect. Safe to retry.`;
|
|
@@ -1821,8 +2006,8 @@ function capToolResults(toolResults, maxToolResultChars) {
|
|
|
1821
2006
|
const originalChars = toolResult.content.length;
|
|
1822
2007
|
const headChars = Math.floor(max * 0.7);
|
|
1823
2008
|
const tailChars = max - headChars;
|
|
1824
|
-
const head = (0,
|
|
1825
|
-
const tail = (0,
|
|
2009
|
+
const head = (0, import_gg_ai2.sliceHead)(toolResult.content, headChars);
|
|
2010
|
+
const tail = (0, import_gg_ai2.sliceTail)(toolResult.content, tailChars);
|
|
1826
2011
|
const omitted = originalChars - head.length - tail.length;
|
|
1827
2012
|
toolResult.content = head + `
|
|
1828
2013
|
|
|
@@ -1858,11 +2043,11 @@ function capTurnToolResults(toolResults, maxTurnToolResultChars) {
|
|
|
1858
2043
|
const headChars = Math.floor(fairShare * 0.7);
|
|
1859
2044
|
const tailChars = fairShare - headChars;
|
|
1860
2045
|
const omitted = originalChars - fairShare;
|
|
1861
|
-
toolResult.content = (0,
|
|
2046
|
+
toolResult.content = (0, import_gg_ai2.sliceHead)(toolResult.content, headChars) + `
|
|
1862
2047
|
|
|
1863
2048
|
[... ${omitted} characters trimmed: this turn's combined tool results exceeded the per-turn budget. Re-run this call alone with narrower filters or offset/limit if you need the omitted content ...]
|
|
1864
2049
|
|
|
1865
|
-
` + (0,
|
|
2050
|
+
` + (0, import_gg_ai2.sliceTail)(toolResult.content, tailChars);
|
|
1866
2051
|
toolResult.capped = {
|
|
1867
2052
|
originalChars: toolResult.capped?.originalChars ?? originalChars,
|
|
1868
2053
|
keptChars: toolResult.content.length,
|
|
@@ -1881,8 +2066,8 @@ function truncateToolResultText(text, maxChars) {
|
|
|
1881
2066
|
if (text.length <= maxChars) return text;
|
|
1882
2067
|
const tailChars = Math.min(Math.floor(maxChars * 0.3), 2e4);
|
|
1883
2068
|
const headChars = Math.max(maxChars - tailChars, 0);
|
|
1884
|
-
const head = (0,
|
|
1885
|
-
const tail = (0,
|
|
2069
|
+
const head = (0, import_gg_ai2.sliceHead)(text, headChars);
|
|
2070
|
+
const tail = (0, import_gg_ai2.sliceTail)(text, tailChars);
|
|
1886
2071
|
const omitted = text.length - head.length - tail.length;
|
|
1887
2072
|
return `${head}
|
|
1888
2073
|
|
|
@@ -2031,7 +2216,7 @@ var AgentStream = class {
|
|
|
2031
2216
|
rejectResult;
|
|
2032
2217
|
hasConsumer = false;
|
|
2033
2218
|
constructor(generator, onDone) {
|
|
2034
|
-
this.events = new
|
|
2219
|
+
this.events = new import_gg_ai3.EventStream();
|
|
2035
2220
|
this.resultPromise = new Promise((resolve, reject) => {
|
|
2036
2221
|
this.resolveResult = resolve;
|
|
2037
2222
|
this.rejectResult = reject;
|
|
@@ -2077,6 +2262,7 @@ var Agent = class {
|
|
|
2077
2262
|
options;
|
|
2078
2263
|
steeringQueue = [];
|
|
2079
2264
|
followUpQueue = [];
|
|
2265
|
+
steeringListeners = /* @__PURE__ */ new Set();
|
|
2080
2266
|
constructor(options) {
|
|
2081
2267
|
this.options = options;
|
|
2082
2268
|
if (options.system) {
|
|
@@ -2101,9 +2287,14 @@ var Agent = class {
|
|
|
2101
2287
|
get running() {
|
|
2102
2288
|
return this._running;
|
|
2103
2289
|
}
|
|
2104
|
-
/**
|
|
2290
|
+
/**
|
|
2291
|
+
* Queue a steering message. Instant interrupt: if tools are running, the
|
|
2292
|
+
* interruptible ones are preempted right away and the message is injected
|
|
2293
|
+
* before the next model call (the turn continues — unlike abort/Stop).
|
|
2294
|
+
*/
|
|
2105
2295
|
steer(msg) {
|
|
2106
2296
|
this.steeringQueue.push(msg);
|
|
2297
|
+
for (const listener of [...this.steeringListeners]) listener();
|
|
2107
2298
|
}
|
|
2108
2299
|
/** Queue a follow-up message for injection when the agent would otherwise stop. */
|
|
2109
2300
|
followUp(msg) {
|
|
@@ -2123,6 +2314,14 @@ var Agent = class {
|
|
|
2123
2314
|
const all = [...callerResult ?? [], ...queued];
|
|
2124
2315
|
return all.length > 0 ? all : null;
|
|
2125
2316
|
},
|
|
2317
|
+
onSteeringAvailable: (listener) => {
|
|
2318
|
+
this.steeringListeners.add(listener);
|
|
2319
|
+
const unsubscribeCaller = this.options.onSteeringAvailable?.(listener);
|
|
2320
|
+
return () => {
|
|
2321
|
+
this.steeringListeners.delete(listener);
|
|
2322
|
+
unsubscribeCaller?.();
|
|
2323
|
+
};
|
|
2324
|
+
},
|
|
2126
2325
|
getFollowUpMessages: async () => {
|
|
2127
2326
|
const callerResult = await this.options.getFollowUpMessages?.() ?? [];
|
|
2128
2327
|
const queued = this.followUpQueue.splice(0);
|