@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.d.cts
CHANGED
|
@@ -103,6 +103,12 @@ interface AgentTool<T extends z.ZodType = z.ZodType> extends Tool {
|
|
|
103
103
|
* message — becomes unreachable.
|
|
104
104
|
*/
|
|
105
105
|
timeoutMs?: number;
|
|
106
|
+
/**
|
|
107
|
+
* Whether a mid-run steering message may preempt this tool. Defaults to
|
|
108
|
+
* true, except for atomic file mutators (`edit`, `write`, …) which always
|
|
109
|
+
* run to completion so they are never left half-applied.
|
|
110
|
+
*/
|
|
111
|
+
interruptible?: boolean;
|
|
106
112
|
execute: (args: z.infer<T>, context: ToolContext) => ToolExecuteResult | Promise<ToolExecuteResult>;
|
|
107
113
|
}
|
|
108
114
|
interface AgentTextDeltaEvent {
|
|
@@ -343,6 +349,7 @@ interface AgentOptions {
|
|
|
343
349
|
transportSessionId?: StreamOptions["transportSessionId"];
|
|
344
350
|
projectId?: StreamOptions["projectId"];
|
|
345
351
|
cacheRetention?: StreamOptions["cacheRetention"];
|
|
352
|
+
onContextPrepared?: StreamOptions["onContextPrepared"];
|
|
346
353
|
/** Stable per-session cache routing key for providers that support it. */
|
|
347
354
|
promptCacheKey?: StreamOptions["promptCacheKey"];
|
|
348
355
|
/** Override the User-Agent sent with OAuth-authenticated Anthropic requests. */
|
|
@@ -370,6 +377,14 @@ interface AgentOptions {
|
|
|
370
377
|
* against parallel fan-outs injecting huge uncached context in one turn;
|
|
371
378
|
* the largest results are trimmed (water-filling) with a re-run notice. */
|
|
372
379
|
maxTurnToolResultChars?: number;
|
|
380
|
+
/** Optional post-processing of a SUCCESSFUL tool result (after redaction,
|
|
381
|
+
* before the tool_call_end event and the provider context). Return the
|
|
382
|
+
* content unchanged to leave it alone. A throw is ignored (original kept).
|
|
383
|
+
* Used e.g. to append a warning to untrusted-content results. */
|
|
384
|
+
transformToolResult?: (call: {
|
|
385
|
+
name: string;
|
|
386
|
+
args: Record<string, unknown>;
|
|
387
|
+
}, content: ToolResultContent) => ToolResultContent;
|
|
373
388
|
/** Max consecutive pause_turn continuations before stopping (default: 5).
|
|
374
389
|
* Prevents infinite loops when server-side tools keep pausing. */
|
|
375
390
|
maxContinuations?: number;
|
|
@@ -391,6 +406,15 @@ interface AgentOptions {
|
|
|
391
406
|
* on read.
|
|
392
407
|
*/
|
|
393
408
|
getSteeringMessages?: () => Promise<Message[] | null> | Message[] | null;
|
|
409
|
+
/**
|
|
410
|
+
* Instant interrupt (codex `instant_interrupt`): subscribe to "a steering
|
|
411
|
+
* message just arrived". While tools are running, the listener preempts
|
|
412
|
+
* interruptible tools (their AbortSignal fires; unfinished calls get an
|
|
413
|
+
* "Interrupted" error result) and the loop drains steering and continues.
|
|
414
|
+
* Distinct from `signal` (the Stop button), which ends the run. Returns an
|
|
415
|
+
* unsubscribe function.
|
|
416
|
+
*/
|
|
417
|
+
onSteeringAvailable?: (listener: () => void) => () => void;
|
|
394
418
|
/**
|
|
395
419
|
* Polled when the agent would otherwise stop (no tool calls, no steering).
|
|
396
420
|
* Returns messages to inject and continue the loop. Lower priority than
|
|
@@ -455,6 +479,7 @@ declare class Agent {
|
|
|
455
479
|
private options;
|
|
456
480
|
private steeringQueue;
|
|
457
481
|
private followUpQueue;
|
|
482
|
+
private steeringListeners;
|
|
458
483
|
constructor(options: AgentOptions);
|
|
459
484
|
/** Snapshot of the current message history. Used for session persistence. */
|
|
460
485
|
getMessages(): Message[];
|
|
@@ -465,7 +490,11 @@ declare class Agent {
|
|
|
465
490
|
*/
|
|
466
491
|
setSignal(signal: AbortSignal | undefined): void;
|
|
467
492
|
get running(): boolean;
|
|
468
|
-
/**
|
|
493
|
+
/**
|
|
494
|
+
* Queue a steering message. Instant interrupt: if tools are running, the
|
|
495
|
+
* interruptible ones are preempted right away and the message is injected
|
|
496
|
+
* before the next model call (the turn continues — unlike abort/Stop).
|
|
497
|
+
*/
|
|
469
498
|
steer(msg: Message): void;
|
|
470
499
|
/** Queue a follow-up message for injection when the agent would otherwise stop. */
|
|
471
500
|
followUp(msg: Message): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -103,6 +103,12 @@ interface AgentTool<T extends z.ZodType = z.ZodType> extends Tool {
|
|
|
103
103
|
* message — becomes unreachable.
|
|
104
104
|
*/
|
|
105
105
|
timeoutMs?: number;
|
|
106
|
+
/**
|
|
107
|
+
* Whether a mid-run steering message may preempt this tool. Defaults to
|
|
108
|
+
* true, except for atomic file mutators (`edit`, `write`, …) which always
|
|
109
|
+
* run to completion so they are never left half-applied.
|
|
110
|
+
*/
|
|
111
|
+
interruptible?: boolean;
|
|
106
112
|
execute: (args: z.infer<T>, context: ToolContext) => ToolExecuteResult | Promise<ToolExecuteResult>;
|
|
107
113
|
}
|
|
108
114
|
interface AgentTextDeltaEvent {
|
|
@@ -343,6 +349,7 @@ interface AgentOptions {
|
|
|
343
349
|
transportSessionId?: StreamOptions["transportSessionId"];
|
|
344
350
|
projectId?: StreamOptions["projectId"];
|
|
345
351
|
cacheRetention?: StreamOptions["cacheRetention"];
|
|
352
|
+
onContextPrepared?: StreamOptions["onContextPrepared"];
|
|
346
353
|
/** Stable per-session cache routing key for providers that support it. */
|
|
347
354
|
promptCacheKey?: StreamOptions["promptCacheKey"];
|
|
348
355
|
/** Override the User-Agent sent with OAuth-authenticated Anthropic requests. */
|
|
@@ -370,6 +377,14 @@ interface AgentOptions {
|
|
|
370
377
|
* against parallel fan-outs injecting huge uncached context in one turn;
|
|
371
378
|
* the largest results are trimmed (water-filling) with a re-run notice. */
|
|
372
379
|
maxTurnToolResultChars?: number;
|
|
380
|
+
/** Optional post-processing of a SUCCESSFUL tool result (after redaction,
|
|
381
|
+
* before the tool_call_end event and the provider context). Return the
|
|
382
|
+
* content unchanged to leave it alone. A throw is ignored (original kept).
|
|
383
|
+
* Used e.g. to append a warning to untrusted-content results. */
|
|
384
|
+
transformToolResult?: (call: {
|
|
385
|
+
name: string;
|
|
386
|
+
args: Record<string, unknown>;
|
|
387
|
+
}, content: ToolResultContent) => ToolResultContent;
|
|
373
388
|
/** Max consecutive pause_turn continuations before stopping (default: 5).
|
|
374
389
|
* Prevents infinite loops when server-side tools keep pausing. */
|
|
375
390
|
maxContinuations?: number;
|
|
@@ -391,6 +406,15 @@ interface AgentOptions {
|
|
|
391
406
|
* on read.
|
|
392
407
|
*/
|
|
393
408
|
getSteeringMessages?: () => Promise<Message[] | null> | Message[] | null;
|
|
409
|
+
/**
|
|
410
|
+
* Instant interrupt (codex `instant_interrupt`): subscribe to "a steering
|
|
411
|
+
* message just arrived". While tools are running, the listener preempts
|
|
412
|
+
* interruptible tools (their AbortSignal fires; unfinished calls get an
|
|
413
|
+
* "Interrupted" error result) and the loop drains steering and continues.
|
|
414
|
+
* Distinct from `signal` (the Stop button), which ends the run. Returns an
|
|
415
|
+
* unsubscribe function.
|
|
416
|
+
*/
|
|
417
|
+
onSteeringAvailable?: (listener: () => void) => () => void;
|
|
394
418
|
/**
|
|
395
419
|
* Polled when the agent would otherwise stop (no tool calls, no steering).
|
|
396
420
|
* Returns messages to inject and continue the loop. Lower priority than
|
|
@@ -455,6 +479,7 @@ declare class Agent {
|
|
|
455
479
|
private options;
|
|
456
480
|
private steeringQueue;
|
|
457
481
|
private followUpQueue;
|
|
482
|
+
private steeringListeners;
|
|
458
483
|
constructor(options: AgentOptions);
|
|
459
484
|
/** Snapshot of the current message history. Used for session persistence. */
|
|
460
485
|
getMessages(): Message[];
|
|
@@ -465,7 +490,11 @@ declare class Agent {
|
|
|
465
490
|
*/
|
|
466
491
|
setSignal(signal: AbortSignal | undefined): void;
|
|
467
492
|
get running(): boolean;
|
|
468
|
-
/**
|
|
493
|
+
/**
|
|
494
|
+
* Queue a steering message. Instant interrupt: if tools are running, the
|
|
495
|
+
* interruptible ones are preempted right away and the message is injected
|
|
496
|
+
* before the next model call (the turn continues — unlike abort/Stop).
|
|
497
|
+
*/
|
|
469
498
|
steer(msg: Message): void;
|
|
470
499
|
/** Queue a follow-up message for injection when the agent would otherwise stop. */
|
|
471
500
|
followUp(msg: Message): void;
|
package/dist/index.js
CHANGED
|
@@ -220,6 +220,29 @@ ${rule.reminder.trim()}
|
|
|
220
220
|
// src/agent-loop.ts
|
|
221
221
|
var DEFAULT_MAX_TURNS = 300;
|
|
222
222
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
|
|
223
|
+
var PREEMPT_GRACE_MS = 500;
|
|
224
|
+
var STEER_INTERRUPTED_TEXT = "Interrupted: the user sent a new message.";
|
|
225
|
+
var ATOMIC_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "multi_edit", "apply_patch", "notebook_edit"]);
|
|
226
|
+
function isInterruptibleTool(tool, name) {
|
|
227
|
+
if (tool?.interruptible !== void 0) return tool.interruptible;
|
|
228
|
+
return !ATOMIC_TOOL_NAMES.has(name);
|
|
229
|
+
}
|
|
230
|
+
function markSteerInterrupted(record) {
|
|
231
|
+
const content = record.content;
|
|
232
|
+
let next;
|
|
233
|
+
if (typeof content === "string") {
|
|
234
|
+
next = content.trim() ? `${STEER_INTERRUPTED_TEXT}
|
|
235
|
+
Partial output:
|
|
236
|
+
${content}` : STEER_INTERRUPTED_TEXT;
|
|
237
|
+
} else {
|
|
238
|
+
next = [
|
|
239
|
+
{ type: "text", text: `${STEER_INTERRUPTED_TEXT}
|
|
240
|
+
Partial output:` },
|
|
241
|
+
...content
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
return { toolCallId: record.toolCallId, content: next, isError: true };
|
|
245
|
+
}
|
|
223
246
|
var _toolRedaction;
|
|
224
247
|
function toolRedactionOptions() {
|
|
225
248
|
_toolRedaction ??= { secrets: environmentSecrets(process.env) };
|
|
@@ -694,6 +717,7 @@ async function* agentLoop(messages, options) {
|
|
|
694
717
|
transportSessionId: options.transportSessionId,
|
|
695
718
|
projectId: liveProjectId,
|
|
696
719
|
cacheRetention: options.cacheRetention,
|
|
720
|
+
onContextPrepared: options.onContextPrepared,
|
|
697
721
|
promptCacheKey: options.promptCacheKey,
|
|
698
722
|
serviceTier: options.serviceTier,
|
|
699
723
|
supportsImages: options.supportsImages,
|
|
@@ -1369,10 +1393,14 @@ async function* agentLoop(messages, options) {
|
|
|
1369
1393
|
fatalToolArgumentRecoverable = recoverable;
|
|
1370
1394
|
fatalToolArgumentToolName = toolName;
|
|
1371
1395
|
};
|
|
1396
|
+
const preempt = new AbortController();
|
|
1397
|
+
const unsubscribeSteering = options.onSteeringAvailable?.(() => preempt.abort());
|
|
1372
1398
|
const executionOptions = {
|
|
1373
1399
|
signal: options.signal,
|
|
1400
|
+
preemptSignal: options.onSteeringAvailable ? preempt.signal : void 0,
|
|
1374
1401
|
maxToolResultChars: options.maxToolResultChars,
|
|
1375
1402
|
maxTurnToolResultChars: options.maxTurnToolResultChars,
|
|
1403
|
+
transformToolResult: options.transformToolResult,
|
|
1376
1404
|
toolMap,
|
|
1377
1405
|
invalidToolArgumentCounts,
|
|
1378
1406
|
markFatalToolArgumentError,
|
|
@@ -1381,7 +1409,15 @@ async function* agentLoop(messages, options) {
|
|
|
1381
1409
|
const hasSequentialToolCall = toolCalls.some(
|
|
1382
1410
|
(toolCall) => toolMap.get(toolCall.name)?.executionMode === "sequential"
|
|
1383
1411
|
);
|
|
1384
|
-
|
|
1412
|
+
let executionResult;
|
|
1413
|
+
try {
|
|
1414
|
+
executionResult = hasSequentialToolCall ? yield* executeToolCallsMixed(toolCalls, toolResults, executionOptions) : yield* executeToolCallsParallel(toolCalls, toolResults, executionOptions);
|
|
1415
|
+
} finally {
|
|
1416
|
+
unsubscribeSteering?.();
|
|
1417
|
+
}
|
|
1418
|
+
if (executionResult.preempted) {
|
|
1419
|
+
diag("steer_preempted_tools", { turn, provider: options.provider, model: options.model });
|
|
1420
|
+
}
|
|
1385
1421
|
messages.push({ role: "tool", content: executionResult.toolResults });
|
|
1386
1422
|
yield { type: "checkpoint", turn };
|
|
1387
1423
|
const toolsAborted = executionResult.aborted;
|
|
@@ -1555,8 +1591,12 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1555
1591
|
}
|
|
1556
1592
|
const callerSignal = options.signal;
|
|
1557
1593
|
const toolTimeout = AbortSignal.timeout(tool.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS);
|
|
1594
|
+
const preemptSignal = isInterruptibleTool(tool, toolCall.name) ? options.preemptSignal : void 0;
|
|
1595
|
+
const toolSignals = [callerSignal, toolTimeout, preemptSignal].filter(
|
|
1596
|
+
(s) => s !== void 0
|
|
1597
|
+
);
|
|
1558
1598
|
const ctx = {
|
|
1559
|
-
signal:
|
|
1599
|
+
signal: toolSignals.length === 1 ? toolTimeout : AbortSignal.any(toolSignals),
|
|
1560
1600
|
toolCallId: toolCall.id,
|
|
1561
1601
|
onUpdate: (update) => {
|
|
1562
1602
|
pushEvent({
|
|
@@ -1570,6 +1610,15 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1570
1610
|
const normalized = normalizeToolResult(raw);
|
|
1571
1611
|
resultContent = redactValue(normalized.content, toolRedactionOptions());
|
|
1572
1612
|
details = redactValue(normalized.details, toolRedactionOptions());
|
|
1613
|
+
if (options.transformToolResult) {
|
|
1614
|
+
try {
|
|
1615
|
+
resultContent = options.transformToolResult(
|
|
1616
|
+
{ name: toolCall.name, args: toolCall.args },
|
|
1617
|
+
resultContent
|
|
1618
|
+
);
|
|
1619
|
+
} catch {
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1573
1622
|
for (const key of options.invalidToolArgumentCounts.keys()) {
|
|
1574
1623
|
if (key.startsWith(`${toolCall.name}:`)) options.invalidToolArgumentCounts.delete(key);
|
|
1575
1624
|
}
|
|
@@ -1607,6 +1656,15 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1607
1656
|
}
|
|
1608
1657
|
resultContent = redactValue(resultContent, toolRedactionOptions());
|
|
1609
1658
|
details = redactValue(details, toolRedactionOptions());
|
|
1659
|
+
if (options.preemptSignal?.aborted && isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name)) {
|
|
1660
|
+
const marked = markSteerInterrupted({
|
|
1661
|
+
toolCallId: toolCall.id,
|
|
1662
|
+
content: resultContent,
|
|
1663
|
+
isError
|
|
1664
|
+
});
|
|
1665
|
+
resultContent = marked.content;
|
|
1666
|
+
isError = true;
|
|
1667
|
+
}
|
|
1610
1668
|
const durationMs = Date.now() - startTime;
|
|
1611
1669
|
pushEvent({
|
|
1612
1670
|
type: "tool_call_end",
|
|
@@ -1619,6 +1677,59 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1619
1677
|
});
|
|
1620
1678
|
return { toolCallId: toolCall.id, content: resultContent, isError };
|
|
1621
1679
|
}
|
|
1680
|
+
function createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds) {
|
|
1681
|
+
const running = /* @__PURE__ */ new Map();
|
|
1682
|
+
let graceTimer;
|
|
1683
|
+
const preemptSignal = options.preemptSignal;
|
|
1684
|
+
const abandon = () => {
|
|
1685
|
+
graceTimer = void 0;
|
|
1686
|
+
for (const [id, entry] of running) {
|
|
1687
|
+
if (!entry.interruptible || resultsById.has(id)) continue;
|
|
1688
|
+
resultsById.set(id, { toolCallId: id, content: STEER_INTERRUPTED_TEXT, isError: true });
|
|
1689
|
+
pushToolEvent(eventStream, state, {
|
|
1690
|
+
type: "tool_call_end",
|
|
1691
|
+
toolCallId: id,
|
|
1692
|
+
result: STEER_INTERRUPTED_TEXT,
|
|
1693
|
+
isError: true,
|
|
1694
|
+
durationMs: Date.now() - entry.startedAt
|
|
1695
|
+
});
|
|
1696
|
+
running.delete(id);
|
|
1697
|
+
}
|
|
1698
|
+
if (running.size === 0) eventStream.close();
|
|
1699
|
+
};
|
|
1700
|
+
const onPreempt = () => {
|
|
1701
|
+
if (graceTimer === void 0) graceTimer = setTimeout(abandon, PREEMPT_GRACE_MS);
|
|
1702
|
+
};
|
|
1703
|
+
preemptSignal?.addEventListener("abort", onPreempt, { once: true });
|
|
1704
|
+
return {
|
|
1705
|
+
async run(toolCall) {
|
|
1706
|
+
if (options.preemptSignal?.aborted) return;
|
|
1707
|
+
dispatchedIds.add(toolCall.id);
|
|
1708
|
+
const interruptible = isInterruptibleTool(options.toolMap.get(toolCall.name), toolCall.name);
|
|
1709
|
+
running.set(toolCall.id, { toolCall, interruptible, startedAt: Date.now() });
|
|
1710
|
+
const record = await executeSingleToolCall(
|
|
1711
|
+
toolCall,
|
|
1712
|
+
options,
|
|
1713
|
+
(event) => pushToolEvent(eventStream, state, event)
|
|
1714
|
+
);
|
|
1715
|
+
running.delete(toolCall.id);
|
|
1716
|
+
if (resultsById.has(toolCall.id)) return;
|
|
1717
|
+
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);
|
|
1718
|
+
resultsById.set(
|
|
1719
|
+
toolCall.id,
|
|
1720
|
+
interruptible && preemptSignal?.aborted && !alreadyMarked ? markSteerInterrupted(record) : record
|
|
1721
|
+
);
|
|
1722
|
+
if (preemptSignal?.aborted && graceTimer === void 0 && running.size === 0) {
|
|
1723
|
+
eventStream.close();
|
|
1724
|
+
}
|
|
1725
|
+
},
|
|
1726
|
+
preempted: () => preemptSignal?.aborted === true,
|
|
1727
|
+
dispose: () => {
|
|
1728
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
1729
|
+
preemptSignal?.removeEventListener("abort", onPreempt);
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1622
1733
|
async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
1623
1734
|
const eventStream = new EventStream();
|
|
1624
1735
|
const state = { finalized: false };
|
|
@@ -1626,6 +1737,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1626
1737
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1627
1738
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1628
1739
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1740
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1629
1741
|
const phases = [];
|
|
1630
1742
|
let currentParallel = [];
|
|
1631
1743
|
for (const toolCall of toolCalls) {
|
|
@@ -1646,40 +1758,18 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1646
1758
|
void (async () => {
|
|
1647
1759
|
try {
|
|
1648
1760
|
for (const phase of phases) {
|
|
1649
|
-
if (options.signal?.aborted) break;
|
|
1761
|
+
if (options.signal?.aborted || options.preemptSignal?.aborted) break;
|
|
1650
1762
|
if (phase.sequential) {
|
|
1651
1763
|
const signature = JSON.stringify([
|
|
1652
1764
|
phase.sequential.name,
|
|
1653
1765
|
canonicalToolArgs(phase.sequential.args)
|
|
1654
1766
|
]);
|
|
1655
1767
|
if (!options.seenToolCalls.has(signature)) options.seenToolCalls.clear();
|
|
1656
|
-
|
|
1657
|
-
const record = await executeSingleToolCall(
|
|
1658
|
-
phase.sequential,
|
|
1659
|
-
options,
|
|
1660
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1661
|
-
);
|
|
1662
|
-
resultsById.set(record.toolCallId, record);
|
|
1768
|
+
await tracker.run(phase.sequential);
|
|
1663
1769
|
} else if (phase.parallel.length === 1) {
|
|
1664
|
-
|
|
1665
|
-
const record = await executeSingleToolCall(
|
|
1666
|
-
phase.parallel[0],
|
|
1667
|
-
options,
|
|
1668
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1669
|
-
);
|
|
1670
|
-
resultsById.set(record.toolCallId, record);
|
|
1770
|
+
await tracker.run(phase.parallel[0]);
|
|
1671
1771
|
} else {
|
|
1672
|
-
await Promise.all(
|
|
1673
|
-
phase.parallel.map(async (toolCall) => {
|
|
1674
|
-
dispatchedIds.add(toolCall.id);
|
|
1675
|
-
const record = await executeSingleToolCall(
|
|
1676
|
-
toolCall,
|
|
1677
|
-
options,
|
|
1678
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1679
|
-
);
|
|
1680
|
-
resultsById.set(record.toolCallId, record);
|
|
1681
|
-
})
|
|
1682
|
-
);
|
|
1772
|
+
await Promise.all(phase.parallel.map((toolCall) => tracker.run(toolCall)));
|
|
1683
1773
|
}
|
|
1684
1774
|
}
|
|
1685
1775
|
if (!state.finalized) eventStream.close();
|
|
@@ -1700,12 +1790,13 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1700
1790
|
}
|
|
1701
1791
|
} finally {
|
|
1702
1792
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1793
|
+
tracker.dispose();
|
|
1703
1794
|
state.finalized = true;
|
|
1704
1795
|
}
|
|
1705
1796
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1706
1797
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1707
1798
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1708
|
-
return { toolResults, aborted };
|
|
1799
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1709
1800
|
}
|
|
1710
1801
|
async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
|
|
1711
1802
|
const eventStream = new EventStream();
|
|
@@ -1714,17 +1805,8 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1714
1805
|
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1715
1806
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1716
1807
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
dispatchedIds.add(toolCall.id);
|
|
1720
|
-
const record = await executeSingleToolCall(
|
|
1721
|
-
toolCall,
|
|
1722
|
-
options,
|
|
1723
|
-
(event) => pushToolEvent(eventStream, state, event)
|
|
1724
|
-
);
|
|
1725
|
-
resultsById.set(record.toolCallId, record);
|
|
1726
|
-
})
|
|
1727
|
-
).then(() => {
|
|
1808
|
+
const tracker = createPreemptTracker(options, eventStream, state, resultsById, dispatchedIds);
|
|
1809
|
+
Promise.all(toolCalls.map((toolCall) => tracker.run(toolCall))).then(() => {
|
|
1728
1810
|
if (!state.finalized) eventStream.close();
|
|
1729
1811
|
}).catch((err) => {
|
|
1730
1812
|
if (!state.finalized) eventStream.abort(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1742,12 +1824,13 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1742
1824
|
}
|
|
1743
1825
|
} finally {
|
|
1744
1826
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1827
|
+
tracker.dispose();
|
|
1745
1828
|
state.finalized = true;
|
|
1746
1829
|
}
|
|
1747
1830
|
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1748
1831
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1749
1832
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1750
|
-
return { toolResults, aborted };
|
|
1833
|
+
return { toolResults, aborted, preempted: !aborted && tracker.preempted() };
|
|
1751
1834
|
}
|
|
1752
1835
|
function cancelledBeforeStartText(name) {
|
|
1753
1836
|
return `\`${name}\` was cancelled before it started, so it had no effect. Safe to retry.`;
|
|
@@ -2043,6 +2126,7 @@ var Agent = class {
|
|
|
2043
2126
|
options;
|
|
2044
2127
|
steeringQueue = [];
|
|
2045
2128
|
followUpQueue = [];
|
|
2129
|
+
steeringListeners = /* @__PURE__ */ new Set();
|
|
2046
2130
|
constructor(options) {
|
|
2047
2131
|
this.options = options;
|
|
2048
2132
|
if (options.system) {
|
|
@@ -2067,9 +2151,14 @@ var Agent = class {
|
|
|
2067
2151
|
get running() {
|
|
2068
2152
|
return this._running;
|
|
2069
2153
|
}
|
|
2070
|
-
/**
|
|
2154
|
+
/**
|
|
2155
|
+
* Queue a steering message. Instant interrupt: if tools are running, the
|
|
2156
|
+
* interruptible ones are preempted right away and the message is injected
|
|
2157
|
+
* before the next model call (the turn continues — unlike abort/Stop).
|
|
2158
|
+
*/
|
|
2071
2159
|
steer(msg) {
|
|
2072
2160
|
this.steeringQueue.push(msg);
|
|
2161
|
+
for (const listener of [...this.steeringListeners]) listener();
|
|
2073
2162
|
}
|
|
2074
2163
|
/** Queue a follow-up message for injection when the agent would otherwise stop. */
|
|
2075
2164
|
followUp(msg) {
|
|
@@ -2089,6 +2178,14 @@ var Agent = class {
|
|
|
2089
2178
|
const all = [...callerResult ?? [], ...queued];
|
|
2090
2179
|
return all.length > 0 ? all : null;
|
|
2091
2180
|
},
|
|
2181
|
+
onSteeringAvailable: (listener) => {
|
|
2182
|
+
this.steeringListeners.add(listener);
|
|
2183
|
+
const unsubscribeCaller = this.options.onSteeringAvailable?.(listener);
|
|
2184
|
+
return () => {
|
|
2185
|
+
this.steeringListeners.delete(listener);
|
|
2186
|
+
unsubscribeCaller?.();
|
|
2187
|
+
};
|
|
2188
|
+
},
|
|
2092
2189
|
getFollowUpMessages: async () => {
|
|
2093
2190
|
const callerResult = await this.options.getFollowUpMessages?.() ?? [];
|
|
2094
2191
|
const queued = this.followUpQueue.splice(0);
|