@kenkaiiii/gg-agent 5.68.0 → 5.69.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 +139 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js +139 -42
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -253,6 +253,29 @@ ${rule.reminder.trim()}
|
|
|
253
253
|
// src/agent-loop.ts
|
|
254
254
|
var DEFAULT_MAX_TURNS = 300;
|
|
255
255
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
|
|
256
|
+
var PREEMPT_GRACE_MS = 500;
|
|
257
|
+
var STEER_INTERRUPTED_TEXT = "Interrupted: the user sent a new message.";
|
|
258
|
+
var ATOMIC_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "multi_edit", "apply_patch", "notebook_edit"]);
|
|
259
|
+
function isInterruptibleTool(tool, name) {
|
|
260
|
+
if (tool?.interruptible !== void 0) return tool.interruptible;
|
|
261
|
+
return !ATOMIC_TOOL_NAMES.has(name);
|
|
262
|
+
}
|
|
263
|
+
function markSteerInterrupted(record) {
|
|
264
|
+
const content = record.content;
|
|
265
|
+
let next;
|
|
266
|
+
if (typeof content === "string") {
|
|
267
|
+
next = content.trim() ? `${STEER_INTERRUPTED_TEXT}
|
|
268
|
+
Partial output:
|
|
269
|
+
${content}` : STEER_INTERRUPTED_TEXT;
|
|
270
|
+
} else {
|
|
271
|
+
next = [
|
|
272
|
+
{ type: "text", text: `${STEER_INTERRUPTED_TEXT}
|
|
273
|
+
Partial output:` },
|
|
274
|
+
...content
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
return { toolCallId: record.toolCallId, content: next, isError: true };
|
|
278
|
+
}
|
|
256
279
|
var _toolRedaction;
|
|
257
280
|
function toolRedactionOptions() {
|
|
258
281
|
_toolRedaction ??= { secrets: (0, import_gg_ai.environmentSecrets)(process.env) };
|
|
@@ -727,6 +750,7 @@ async function* agentLoop(messages, options) {
|
|
|
727
750
|
transportSessionId: options.transportSessionId,
|
|
728
751
|
projectId: liveProjectId,
|
|
729
752
|
cacheRetention: options.cacheRetention,
|
|
753
|
+
onContextPrepared: options.onContextPrepared,
|
|
730
754
|
promptCacheKey: options.promptCacheKey,
|
|
731
755
|
serviceTier: options.serviceTier,
|
|
732
756
|
supportsImages: options.supportsImages,
|
|
@@ -1402,10 +1426,14 @@ async function* agentLoop(messages, options) {
|
|
|
1402
1426
|
fatalToolArgumentRecoverable = recoverable;
|
|
1403
1427
|
fatalToolArgumentToolName = toolName;
|
|
1404
1428
|
};
|
|
1429
|
+
const preempt = new AbortController();
|
|
1430
|
+
const unsubscribeSteering = options.onSteeringAvailable?.(() => preempt.abort());
|
|
1405
1431
|
const executionOptions = {
|
|
1406
1432
|
signal: options.signal,
|
|
1433
|
+
preemptSignal: options.onSteeringAvailable ? preempt.signal : void 0,
|
|
1407
1434
|
maxToolResultChars: options.maxToolResultChars,
|
|
1408
1435
|
maxTurnToolResultChars: options.maxTurnToolResultChars,
|
|
1436
|
+
transformToolResult: options.transformToolResult,
|
|
1409
1437
|
toolMap,
|
|
1410
1438
|
invalidToolArgumentCounts,
|
|
1411
1439
|
markFatalToolArgumentError,
|
|
@@ -1414,7 +1442,15 @@ async function* agentLoop(messages, options) {
|
|
|
1414
1442
|
const hasSequentialToolCall = toolCalls.some(
|
|
1415
1443
|
(toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
|
|
1416
1444
|
);
|
|
1417
|
-
|
|
1445
|
+
let executionResult;
|
|
1446
|
+
try {
|
|
1447
|
+
executionResult = hasSequentialToolCall ? yield* executeToolCallsMixed(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
|
|
1448
|
+
} finally {
|
|
1449
|
+
unsubscribeSteering?.();
|
|
1450
|
+
}
|
|
1451
|
+
if (executionResult.preempted) {
|
|
1452
|
+
diag("steer_preempted_tools", { turn, provider: options.provider, model: options.model });
|
|
1453
|
+
}
|
|
1418
1454
|
messages.push({ role: "tool", content: executionResult.toolResults });
|
|
1419
1455
|
yield { type: "checkpoint", turn };
|
|
1420
1456
|
const toolsAborted = executionResult.aborted;
|
|
@@ -1588,8 +1624,12 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1588
1624
|
}
|
|
1589
1625
|
const callerSignal = options.signal;
|
|
1590
1626
|
const toolTimeout = AbortSignal.timeout(tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS);
|
|
1627
|
+
const preemptSignal = isInterruptibleTool(tool, toolCall.name) ? options.preemptSignal : void 0;
|
|
1628
|
+
const toolSignals = [callerSignal, toolTimeout, preemptSignal].filter(
|
|
1629
|
+
(s) => s !== void 0
|
|
1630
|
+
);
|
|
1591
1631
|
const ctx = {
|
|
1592
|
-
signal:
|
|
1632
|
+
signal: toolSignals.length === 1 ? toolTimeout : AbortSignal.any(toolSignals),
|
|
1593
1633
|
toolCallId: toolCall.id,
|
|
1594
1634
|
onUpdate: (update) => {
|
|
1595
1635
|
pushEvent({
|
|
@@ -1603,6 +1643,15 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1603
1643
|
const normalized = normalizeToolResult(raw);
|
|
1604
1644
|
resultContent = (0, import_gg_ai.redactValue)(normalized.content, toolRedactionOptions());
|
|
1605
1645
|
details = (0, import_gg_ai.redactValue)(normalized.details, toolRedactionOptions());
|
|
1646
|
+
if (options.transformToolResult) {
|
|
1647
|
+
try {
|
|
1648
|
+
resultContent = options.transformToolResult(
|
|
1649
|
+
{ name: toolCall.name, args: toolCall.args },
|
|
1650
|
+
resultContent
|
|
1651
|
+
);
|
|
1652
|
+
} catch {
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1606
1655
|
for (const key of options.invalidToolArgumentCounts.keys()) {
|
|
1607
1656
|
if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
|
|
1608
1657
|
}
|
|
@@ -1640,6 +1689,15 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1640
1689
|
}
|
|
1641
1690
|
resultContent = (0, import_gg_ai.redactValue)(resultContent, toolRedactionOptions());
|
|
1642
1691
|
details = (0, import_gg_ai.redactValue)(details, toolRedactionOptions());
|
|
1692
|
+
if (options.preemptSignal?.aborted && isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name)) {
|
|
1693
|
+
const marked = markSteerInterrupted({
|
|
1694
|
+
toolCallId: toolCall.id,
|
|
1695
|
+
content: resultContent,
|
|
1696
|
+
isError
|
|
1697
|
+
});
|
|
1698
|
+
resultContent = marked.content;
|
|
1699
|
+
isError = true;
|
|
1700
|
+
}
|
|
1643
1701
|
const durationMs = Date.now() - startTime;
|
|
1644
1702
|
pushEvent({
|
|
1645
1703
|
type: "tool_call_end",
|
|
@@ -1652,6 +1710,59 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1652
1710
|
});
|
|
1653
1711
|
return { toolCallId: toolCall.id, content: resultContent, isError };
|
|
1654
1712
|
}
|
|
1713
|
+
function createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds) {
|
|
1714
|
+
const running = /* @__PURE__ */ new Map();
|
|
1715
|
+
let graceTimer;
|
|
1716
|
+
const preemptSignal = options.preemptSignal;
|
|
1717
|
+
const abandon = () => {
|
|
1718
|
+
graceTimer = void 0;
|
|
1719
|
+
for (const [id, entry] of running) {
|
|
1720
|
+
if (!entry.interruptible || resultsById.has(id)) continue;
|
|
1721
|
+
resultsById.set(id, { toolCallId: id, content: STEER_INTERRUPTED_TEXT, isError: true });
|
|
1722
|
+
pushToolEvent(eventStream, state, {
|
|
1723
|
+
type: "tool_call_end",
|
|
1724
|
+
toolCallId: id,
|
|
1725
|
+
result: STEER_INTERRUPTED_TEXT,
|
|
1726
|
+
isError: true,
|
|
1727
|
+
durationMs: Date.now() - entry.startedAt
|
|
1728
|
+
});
|
|
1729
|
+
running.delete(id);
|
|
1730
|
+
}
|
|
1731
|
+
if (running.size === 0) eventStream.close();
|
|
1732
|
+
};
|
|
1733
|
+
const onPreempt = () => {
|
|
1734
|
+
if (graceTimer === void 0) graceTimer = setTimeout(abandon, PREEMPT_GRACE_MS);
|
|
1735
|
+
};
|
|
1736
|
+
preemptSignal?.addEventListener("abort", onPreempt, { once: true });
|
|
1737
|
+
return {
|
|
1738
|
+
async run(toolCall) {
|
|
1739
|
+
if (options.preemptSignal?.aborted) return;
|
|
1740
|
+
dispatchedIds.add(toolCall.id);
|
|
1741
|
+
const interruptible = isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name);
|
|
1742
|
+
running.set(toolCall.id, { toolCall, interruptible, startedAt: Date.now() });
|
|
1743
|
+
const record = await executeSingleToolCall(
|
|
1744
|
+
toolCall,
|
|
1745
|
+
options,
|
|
1746
|
+
(event) => pushToolEvent(eventStream, state, event)
|
|
1747
|
+
);
|
|
1748
|
+
running.delete(toolCall.id);
|
|
1749
|
+
if (resultsById.has(toolCall.id)) return;
|
|
1750
|
+
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);
|
|
1751
|
+
resultsById.set(
|
|
1752
|
+
toolCall.id,
|
|
1753
|
+
interruptible && preemptSignal?.aborted && !alreadyMarked ? markSteerInterrupted(record) : record
|
|
1754
|
+
);
|
|
1755
|
+
if (preemptSignal?.aborted && graceTimer === void 0 && running.size === 0) {
|
|
1756
|
+
eventStream.close();
|
|
1757
|
+
}
|
|
1758
|
+
},
|
|
1759
|
+
preempted: () => preemptSignal?.aborted === true,
|
|
1760
|
+
dispose: () => {
|
|
1761
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
1762
|
+
preemptSignal?.removeEventListener("abort", onPreempt);
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1655
1766
|
async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
1656
1767
|
const eventStream = new import_gg_ai.EventStream();
|
|
1657
1768
|
const state = { finalized: false };
|
|
@@ -1659,6 +1770,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1659
1770
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1660
1771
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1661
1772
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1773
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1662
1774
|
const phases = [];
|
|
1663
1775
|
let currentParallel = [];
|
|
1664
1776
|
for (const toolCall of toolCalls) {
|
|
@@ -1679,40 +1791,18 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1679
1791
|
void (async () => {
|
|
1680
1792
|
try {
|
|
1681
1793
|
for (const phase of phases) {
|
|
1682
|
-
if (options.signal?.aborted) break;
|
|
1794
|
+
if (options.signal?.aborted || options.preemptSignal?.aborted) break;
|
|
1683
1795
|
if (phase.sequential) {
|
|
1684
1796
|
const signature = JSON.stringify([
|
|
1685
1797
|
phase.sequential.name,
|
|
1686
1798
|
canonicalToolArgs(phase.sequential.args)
|
|
1687
1799
|
]);
|
|
1688
1800
|
if (!options.seenToolCalls.has(signature)) options.seenToolCalls.clear();
|
|
1689
|
-
|
|
1690
|
-
const record = await executeSingleToolCall(
|
|
1691
|
-
phase.sequential,
|
|
1692
|
-
options,
|
|
1693
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1694
|
-
);
|
|
1695
|
-
resultsById.set(record.toolCallId, record);
|
|
1801
|
+
await tracker.run(phase.sequential);
|
|
1696
1802
|
} else if (phase.parallel.length === 1) {
|
|
1697
|
-
|
|
1698
|
-
const record = await executeSingleToolCall(
|
|
1699
|
-
phase.parallel[0],
|
|
1700
|
-
options,
|
|
1701
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1702
|
-
);
|
|
1703
|
-
resultsById.set(record.toolCallId, record);
|
|
1803
|
+
await tracker.run(phase.parallel[0]);
|
|
1704
1804
|
} else {
|
|
1705
|
-
await Promise.all(
|
|
1706
|
-
phase.parallel.map(async (toolCall) => {
|
|
1707
|
-
dispatchedIds.add(toolCall.id);
|
|
1708
|
-
const record = await executeSingleToolCall(
|
|
1709
|
-
toolCall,
|
|
1710
|
-
options,
|
|
1711
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1712
|
-
);
|
|
1713
|
-
resultsById.set(record.toolCallId, record);
|
|
1714
|
-
})
|
|
1715
|
-
);
|
|
1805
|
+
await Promise.all(phase.parallel.map((toolCall) => tracker.run(toolCall)));
|
|
1716
1806
|
}
|
|
1717
1807
|
}
|
|
1718
1808
|
if (!state.finalized) eventStream.close();
|
|
@@ -1733,12 +1823,13 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1733
1823
|
}
|
|
1734
1824
|
} finally {
|
|
1735
1825
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1826
|
+
tracker.dispose();
|
|
1736
1827
|
state.finalized = true;
|
|
1737
1828
|
}
|
|
1738
1829
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1739
1830
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1740
1831
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1741
|
-
return { toolResults, aborted };
|
|
1832
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1742
1833
|
}
|
|
1743
1834
|
async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
|
|
1744
1835
|
const eventStream = new import_gg_ai.EventStream();
|
|
@@ -1747,17 +1838,8 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1747
1838
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1748
1839
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1749
1840
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
dispatchedIds.add(toolCall.id);
|
|
1753
|
-
const record = await executeSingleToolCall(
|
|
1754
|
-
toolCall,
|
|
1755
|
-
options,
|
|
1756
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1757
|
-
);
|
|
1758
|
-
resultsById.set(record.toolCallId, record);
|
|
1759
|
-
})
|
|
1760
|
-
).then(() => {
|
|
1841
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1842
|
+
Promise.all(toolCalls.map((toolCall) => tracker.run(toolCall))).then(() => {
|
|
1761
1843
|
if (!state.finalized) eventStream.close();
|
|
1762
1844
|
}).catch((err) => {
|
|
1763
1845
|
if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1775,12 +1857,13 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1775
1857
|
}
|
|
1776
1858
|
} finally {
|
|
1777
1859
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1860
|
+
tracker.dispose();
|
|
1778
1861
|
state.finalized = true;
|
|
1779
1862
|
}
|
|
1780
1863
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1781
1864
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1782
1865
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1783
|
-
return { toolResults, aborted };
|
|
1866
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1784
1867
|
}
|
|
1785
1868
|
function cancelledBeforeStartText(name) {
|
|
1786
1869
|
return `\`${name}\` was cancelled before it started, so it had no effect. Safe to retry.`;
|
|
@@ -2076,6 +2159,7 @@ var Agent = class {
|
|
|
2076
2159
|
options;
|
|
2077
2160
|
steeringQueue = [];
|
|
2078
2161
|
followUpQueue = [];
|
|
2162
|
+
steeringListeners = /* @__PURE__ */ new Set();
|
|
2079
2163
|
constructor(options) {
|
|
2080
2164
|
this.options = options;
|
|
2081
2165
|
if (options.system) {
|
|
@@ -2100,9 +2184,14 @@ var Agent = class {
|
|
|
2100
2184
|
get running() {
|
|
2101
2185
|
return this._running;
|
|
2102
2186
|
}
|
|
2103
|
-
/**
|
|
2187
|
+
/**
|
|
2188
|
+
* Queue a steering message. Instant interrupt: if tools are running, the
|
|
2189
|
+
* interruptible ones are preempted right away and the message is injected
|
|
2190
|
+
* before the next model call (the turn continues — unlike abort/Stop).
|
|
2191
|
+
*/
|
|
2104
2192
|
steer(msg) {
|
|
2105
2193
|
this.steeringQueue.push(msg);
|
|
2194
|
+
for (const listener of [...this.steeringListeners]) listener();
|
|
2106
2195
|
}
|
|
2107
2196
|
/** Queue a follow-up message for injection when the agent would otherwise stop. */
|
|
2108
2197
|
followUp(msg) {
|
|
@@ -2122,6 +2211,14 @@ var Agent = class {
|
|
|
2122
2211
|
const all = [...callerResult ?? [], ...queued];
|
|
2123
2212
|
return all.length > 0 ? all : null;
|
|
2124
2213
|
},
|
|
2214
|
+
onSteeringAvailable: (listener) => {
|
|
2215
|
+
this.steeringListeners.add(listener);
|
|
2216
|
+
const unsubscribeCaller = this.options.onSteeringAvailable?.(listener);
|
|
2217
|
+
return () => {
|
|
2218
|
+
this.steeringListeners.delete(listener);
|
|
2219
|
+
unsubscribeCaller?.();
|
|
2220
|
+
};
|
|
2221
|
+
},
|
|
2125
2222
|
getFollowUpMessages: async () => {
|
|
2126
2223
|
const callerResult = await this.options.getFollowUpMessages?.() ?? [];
|
|
2127
2224
|
const queued = this.followUpQueue.splice(0);
|