agent-accelerator 0.6.0 → 0.6.6
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/README.md +62 -18
- package/package.json +1 -1
- package/src/agent/agent.ts +192 -695
- package/src/agent/architecture.md +19 -13
- package/src/agent/context.ts +1 -0
- package/src/agent/defaults.ts +88 -0
- package/src/agent/delegation.ts +8 -3
- package/src/agent/loop.ts +242 -226
- package/src/agent/named-sessions.ts +93 -0
- package/src/agent/options.ts +166 -0
- package/src/agent/run-control.ts +248 -0
- package/src/agent/subagents-tool.ts +14 -21
- package/src/agent/usage-limit.ts +92 -0
- package/src/agent/worker.ts +17 -3
- package/src/agent/workers.ts +13 -1
- package/src/architecture.md +1 -0
- package/src/index.ts +11 -11
- package/src/prompts/additional-context.md +2 -0
- package/src/prompts/architecture.md +19 -0
- package/src/prompts/prompts.ts +81 -0
- package/src/prompts/reasoning-directive.md +5 -0
- package/src/prompts/structured-output.md +2 -0
- package/src/prompts/tool-bridge.md +1 -0
- package/src/prompts/tool-interrupted.md +1 -0
- package/src/prompts/tool-not-found.md +1 -0
- package/src/prompts/tool-repeat-blocked.md +1 -0
- package/src/prompts/tool-schema-error.md +1 -0
- package/src/prompts/tool-truncated-arguments.md +1 -0
- package/src/prompts/worker-failed.md +1 -0
- package/src/prompts/worker-replied.md +2 -0
- package/src/providers/architecture.md +8 -8
- package/src/providers/google.ts +83 -13
- package/src/providers/openai-compat.ts +17 -7
- package/src/providers/openai.ts +10 -4
- package/src/providers/openrouter.ts +185 -18
- package/src/providers.ts +59 -9
- package/src/scheduler/architecture.md +7 -7
- package/src/scheduler/scheduler.ts +40 -0
- package/src/scheduler/tool.ts +5 -9
- package/src/session/journal.ts +1 -0
- package/src/session/store.ts +4 -0
- package/src/streaming/event-stream.ts +1 -1
- package/src/tools/architecture.md +7 -6
- package/src/tools/executor.ts +60 -15
- package/src/tools/tool-content.ts +32 -0
- package/src/types/agent.ts +40 -8
- package/src/types/core.ts +4 -0
- package/src/types/message.ts +7 -0
- package/src/types/model.ts +4 -1
- package/src/types/scheduler.ts +4 -4
- package/src/types/tool.ts +2 -0
- package/src/utils/errors.ts +3 -3
- package/src/utils/headers.ts +1 -1
- /package/src/{scheduler/scheduled-actions.md → prompts/scheduled-actions-tool.md} +0 -0
- /package/src/{agent/subagents.md → prompts/subagents-tool.md} +0 -0
package/src/agent/loop.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Provider, ProviderRequestOptions, ModelSpec, ProviderRawData } from "../types/model.ts";
|
|
2
|
-
import type { ToolDefinition, ToolCallRecord, ToolResultRecord } from "../types/tool.ts";
|
|
2
|
+
import type { ToolDefinition, ToolCallRecord, ToolResultRecord, StandardToolDeclaration } from "../types/tool.ts";
|
|
3
3
|
import type { TokenUsage } from "../types/core.ts";
|
|
4
4
|
import type { AgentRunOptions } from "../types/agent.ts";
|
|
5
5
|
import type { ContentPart } from "../types/message.ts";
|
|
@@ -32,6 +32,7 @@ import { isSubagentResult, normalizeSubagentMetadata } from "../tools/subagent-r
|
|
|
32
32
|
import { BudgetExceededError, TimeoutError } from "../types/errors.ts";
|
|
33
33
|
import { applyCaptureRaw, type CaptureRawMode } from "../utils/raw-capture.ts";
|
|
34
34
|
import { previewPrompt } from "../utils/session.ts";
|
|
35
|
+
import { DEFAULT_PROMPTS, fillPrompt, type Prompts } from "../prompts/prompts.ts";
|
|
35
36
|
import { noteProviderTurn, normalizeStructuredOutput, parseStructuredOutput } from "../providers.ts";
|
|
36
37
|
|
|
37
38
|
/** One mid-session steer request buffered while a run is active. */
|
|
@@ -98,6 +99,8 @@ export interface AgentLoopConfig {
|
|
|
98
99
|
steerInbox?: SteerEntry[];
|
|
99
100
|
/** Called after steer entries are injected (streaming loops also push `steer_injected` events). */
|
|
100
101
|
onSteerInjected?: (injectedPrompts: string[]) => void;
|
|
102
|
+
/** The SDK prompts for this run (`AgentConfig.prompts` resolved); the shipped ones when unset. */
|
|
103
|
+
prompts?: Prompts;
|
|
101
104
|
/**
|
|
102
105
|
* Canonical structured-output request (raw `AgentConfig.output`).
|
|
103
106
|
* Normalized per turn for the provider wire; validated once at the end
|
|
@@ -111,7 +114,7 @@ export interface AgentLoopConfig {
|
|
|
111
114
|
* Returns display previews for events/telemetry. Never throws: invalid
|
|
112
115
|
* entries are skipped so one bad steer cannot fail the active turn.
|
|
113
116
|
*/
|
|
114
|
-
function drainSteerInbox(context: AgentContext, inbox?: SteerEntry[]): string[] {
|
|
117
|
+
function drainSteerInbox(context: AgentContext, inbox?: SteerEntry[], prompts: Prompts = DEFAULT_PROMPTS): string[] {
|
|
115
118
|
if (!inbox || inbox.length === 0) return [];
|
|
116
119
|
const injected: string[] = [];
|
|
117
120
|
while (inbox.length > 0) {
|
|
@@ -121,7 +124,7 @@ function drainSteerInbox(context: AgentContext, inbox?: SteerEntry[]): string[]
|
|
|
121
124
|
const opts = entry.options;
|
|
122
125
|
if (Array.isArray(prompt) && prompt.length === 0) continue;
|
|
123
126
|
if (opts?.additionalContext) {
|
|
124
|
-
const prefix =
|
|
127
|
+
const prefix = `${fillPrompt(prompts.additionalContext, { context: opts.additionalContext })}\n\n`;
|
|
125
128
|
if (typeof prompt === "string") prompt = prefix + prompt;
|
|
126
129
|
else prompt = [{ type: "text", text: prefix } as ContentPart, ...prompt];
|
|
127
130
|
}
|
|
@@ -157,7 +160,7 @@ function emitTurn(
|
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
/**
|
|
160
|
-
* Per-turn `toolChoice`. `"none"`
|
|
163
|
+
* Per-turn `toolChoice`. `"none"` and `"validated"` (which forces nothing) hold for the whole run; a forced choice
|
|
161
164
|
* (`"required"` or a pinned function) applies to the FIRST model turn only:
|
|
162
165
|
* forcing a tool call on every turn would keep the model calling tools until
|
|
163
166
|
* `maxTurns`, so the run could never produce a final answer.
|
|
@@ -167,7 +170,7 @@ function toolChoiceForTurn(
|
|
|
167
170
|
turn: number
|
|
168
171
|
): ProviderRequestOptions["toolChoice"] {
|
|
169
172
|
if (choice === undefined || choice === "auto") return undefined;
|
|
170
|
-
if (choice === "none") return
|
|
173
|
+
if (choice === "none" || choice === "validated") return choice;
|
|
171
174
|
return turn === 1 ? choice : undefined;
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -275,7 +278,7 @@ function warnMaxTurnsHit(agentName: string | undefined, maxTurns: number, pendin
|
|
|
275
278
|
* - drops results whose id the model never issued.
|
|
276
279
|
* Open-weight models that reuse one id for several calls keep one result per call.
|
|
277
280
|
*/
|
|
278
|
-
export function ensureToolPairing(context: AgentContext): void {
|
|
281
|
+
export function ensureToolPairing(context: AgentContext, prompts: Prompts = DEFAULT_PROMPTS): void {
|
|
279
282
|
try {
|
|
280
283
|
type ToolResultPart = Extract<ContentPart, { type: "tool_result" }>;
|
|
281
284
|
const messages = context.messages;
|
|
@@ -316,7 +319,7 @@ export function ensureToolPairing(context: AgentContext): void {
|
|
|
316
319
|
type: "tool_result",
|
|
317
320
|
id: p.id,
|
|
318
321
|
name,
|
|
319
|
-
result:
|
|
322
|
+
result: fillPrompt(prompts.toolInterrupted, { tool: name, id: p.id }),
|
|
320
323
|
isError: true,
|
|
321
324
|
},
|
|
322
325
|
],
|
|
@@ -472,6 +475,14 @@ export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec, serv
|
|
|
472
475
|
}
|
|
473
476
|
}
|
|
474
477
|
|
|
478
|
+
// Adds the media token details of `source` into `target` (kept only when something was reported); in: accumulateUsage, out: target.inputDetails / outputDetails.
|
|
479
|
+
function sumDetails(target: TokenUsage, source: TokenUsage, key: "inputDetails" | "outputDetails"): void {
|
|
480
|
+
const from = source[key] as Record<string, number | undefined> | undefined;
|
|
481
|
+
if (!from) return;
|
|
482
|
+
const into = ((target[key] as Record<string, number> | undefined) ??= {});
|
|
483
|
+
for (const [k, v] of Object.entries(from)) if (typeof v === "number") into[k] = (into[k] ?? 0) + v;
|
|
484
|
+
}
|
|
485
|
+
|
|
475
486
|
// ---------------------------------------------------------------------------
|
|
476
487
|
// Canonical usage accumulation (pure w.r.t. source).
|
|
477
488
|
// Invariants (see assertUsageInvariants in session/store.ts):
|
|
@@ -484,6 +495,7 @@ export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec, serv
|
|
|
484
495
|
// This function never mutates `source`: the priced cost is cloned
|
|
485
496
|
// into a local before folding into the target.
|
|
486
497
|
// ---------------------------------------------------------------------------
|
|
498
|
+
// Adds one turn's usage (and its priced cost) into a running total without touching the source; in: both loops per model turn and finished worker, out: the run's accumulatedUsage.
|
|
487
499
|
export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: ModelSpec, serviceTier?: string): void {
|
|
488
500
|
target.inputTokens += source.inputTokens || 0;
|
|
489
501
|
target.outputTokens += source.outputTokens || 0;
|
|
@@ -492,6 +504,8 @@ export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: M
|
|
|
492
504
|
target.cacheReadTokens = (target.cacheReadTokens ?? 0) + (source.cacheReadTokens ?? 0);
|
|
493
505
|
target.cacheWriteTokens = (target.cacheWriteTokens ?? 0) + (source.cacheWriteTokens ?? 0);
|
|
494
506
|
target.thinkingTokens = (target.thinkingTokens ?? 0) + (source.thinkingTokens ?? 0);
|
|
507
|
+
sumDetails(target, source, "inputDetails");
|
|
508
|
+
sumDetails(target, source, "outputDetails");
|
|
495
509
|
|
|
496
510
|
// Canonical invariant (provider-agnostic): cache hits are a subset of
|
|
497
511
|
// input. Some providers occasionally report cached > input on long chained
|
|
@@ -542,6 +556,163 @@ function sanitizeToolResult(r: ToolResultRecord): { sanitized: ToolResultRecord;
|
|
|
542
556
|
return { sanitized: r, metas: [] };
|
|
543
557
|
}
|
|
544
558
|
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
// Turn helpers shared by runAgentLoop and streamAgentLoop (one copy of the logic that both loops need).
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
562
|
+
|
|
563
|
+
// A zeroed usage total for a new run; in: both loops, out: the run's accumulatedUsage.
|
|
564
|
+
function newTurnUsage(): TokenUsage {
|
|
565
|
+
return {
|
|
566
|
+
inputTokens: 0,
|
|
567
|
+
outputTokens: 0,
|
|
568
|
+
totalTokens: 0,
|
|
569
|
+
cachedTokens: 0,
|
|
570
|
+
cacheReadTokens: 0,
|
|
571
|
+
cacheWriteTokens: 0,
|
|
572
|
+
thinkingTokens: 0,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// What a failed run had done so far (the `partial` attached to its error); in: both loops' catch blocks, out: err.partial.
|
|
577
|
+
function partialProgress(
|
|
578
|
+
usage: TokenUsage,
|
|
579
|
+
turns: number,
|
|
580
|
+
toolCalls: ToolCallRecord[],
|
|
581
|
+
toolResults: ToolResultRecord[],
|
|
582
|
+
subagents: SubAgentExecutionMetadata[],
|
|
583
|
+
text: string | undefined
|
|
584
|
+
): {
|
|
585
|
+
usage: TokenUsage;
|
|
586
|
+
cost: TokenUsage["cost"];
|
|
587
|
+
turns: number;
|
|
588
|
+
toolCalls: ToolCallRecord[];
|
|
589
|
+
toolResults: ToolResultRecord[];
|
|
590
|
+
subagents: SubAgentExecutionMetadata[];
|
|
591
|
+
text: string | undefined;
|
|
592
|
+
} {
|
|
593
|
+
return { usage, cost: usage.cost, turns, toolCalls, toolResults, subagents, text };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// The provider options of one turn: the run's options plus this turn's tools, signal, forced tool choice, structured output and request timeout; in: both loops before each model call, out: provider.generate/stream options.
|
|
597
|
+
function buildTurnOptions(
|
|
598
|
+
config: AgentLoopConfig,
|
|
599
|
+
standardTools: StandardToolDeclaration[],
|
|
600
|
+
signal: AbortSignal | undefined,
|
|
601
|
+
turn: number,
|
|
602
|
+
outputSpec: import("../types/model.ts").StructuredOutputSpec | undefined,
|
|
603
|
+
requestTimeoutMs: number
|
|
604
|
+
): ProviderRequestOptions {
|
|
605
|
+
const { options, runOptions } = config;
|
|
606
|
+
const providerOptions: ProviderRequestOptions = {
|
|
607
|
+
...options,
|
|
608
|
+
sessionId: runOptions?.sessionId || options?.sessionId,
|
|
609
|
+
cache: options?.cache,
|
|
610
|
+
tools: standardTools.length > 0 ? standardTools : undefined,
|
|
611
|
+
signal,
|
|
612
|
+
toolChoice: toolChoiceForTurn(options?.toolChoice, turn),
|
|
613
|
+
...(outputSpec ? { output: outputSpec } : {}),
|
|
614
|
+
};
|
|
615
|
+
// Per-request timeout pass-through (providers honor timeoutMs).
|
|
616
|
+
try {
|
|
617
|
+
if (Number.isFinite(requestTimeoutMs) && requestTimeoutMs !== Infinity) {
|
|
618
|
+
(providerOptions as unknown as { timeoutMs?: number }).timeoutMs = requestTimeoutMs;
|
|
619
|
+
}
|
|
620
|
+
} catch {}
|
|
621
|
+
return providerOptions;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Moves an empty-text turn's reasoning into its answer, only when the caller opted in; in: a finished model turn, out: that turn's text and thinking.
|
|
625
|
+
function promoteThinkingIfAllowed(
|
|
626
|
+
turn: { text: string; thinking?: string; toolCalls?: unknown[] },
|
|
627
|
+
config: AgentLoopConfig,
|
|
628
|
+
runOptions: AgentRunOptions | undefined
|
|
629
|
+
): void {
|
|
630
|
+
if ((turn.text && turn.text.trim() !== "") || (turn.toolCalls && turn.toolCalls.length > 0) || !turn.thinking) return;
|
|
631
|
+
// Thinking is exposed via events/traces and onTurn. Promoting it to answer text is gated (default OFF): without the
|
|
632
|
+
// opt-in the run returns the provider's empty text and finishReason.
|
|
633
|
+
if (!shouldPromoteThinkingToAnswer(config, runOptions)) return;
|
|
634
|
+
const mutable = turn as { text: string; thinking?: string };
|
|
635
|
+
if (turn.thinking.includes("</think>")) {
|
|
636
|
+
const parts = turn.thinking.split(/<\/(?:think|thought)>/i);
|
|
637
|
+
mutable.thinking = parts[0]!.replace(/<(?:think|thought)>/i, "").trim() || undefined;
|
|
638
|
+
mutable.text = parts.slice(1).join("").trim();
|
|
639
|
+
} else {
|
|
640
|
+
mutable.text = turn.thinking;
|
|
641
|
+
mutable.thinking = undefined;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Counts one finished model turn: adds it to the run's usage, reports it to the agent's own spending, then checks the budgets and the token limit; in: both loops after each provider call, out: accumulatedUsage and a thrown BudgetExceededError when a cap is passed.
|
|
646
|
+
function recordModelTurn(
|
|
647
|
+
config: AgentLoopConfig,
|
|
648
|
+
accumulated: TokenUsage,
|
|
649
|
+
usage: TokenUsage,
|
|
650
|
+
spec: ModelSpec | undefined,
|
|
651
|
+
maxTotalTokens: number
|
|
652
|
+
): void {
|
|
653
|
+
const serviceTier = config.options?.serviceTier;
|
|
654
|
+
accumulateUsage(accumulated, usage, spec, serviceTier);
|
|
655
|
+
try {
|
|
656
|
+
config.onTurnUsage?.(usage, spec);
|
|
657
|
+
} catch {}
|
|
658
|
+
config.enforceBudget?.({ ...usage, cost: computeCostFromPricing(usage, spec, serviceTier) });
|
|
659
|
+
checkTokenLimit(accumulated, maxTotalTokens);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Emits the full reasoning text of a turn on the telemetry bus (observability only: never in wire bodies, history or files); in: both loops after each model turn, out: a `turn.thinking` event.
|
|
663
|
+
function emitThinkingTrace(
|
|
664
|
+
turn: { thinking?: string; thoughtSignature?: string },
|
|
665
|
+
turns: number,
|
|
666
|
+
providerId: string,
|
|
667
|
+
modelId: string,
|
|
668
|
+
sessionId: string | undefined
|
|
669
|
+
): void {
|
|
670
|
+
if (!turn.thinking) return;
|
|
671
|
+
try {
|
|
672
|
+
telemetryBus.emit({
|
|
673
|
+
code: "turn.thinking",
|
|
674
|
+
message: `[Agent Accelerator] turn ${turns} thinking (${providerId}/${modelId})`,
|
|
675
|
+
data: {
|
|
676
|
+
turns,
|
|
677
|
+
model: modelId,
|
|
678
|
+
provider: providerId,
|
|
679
|
+
sessionId,
|
|
680
|
+
thinking: turn.thinking,
|
|
681
|
+
...(turn.thoughtSignature ? { thoughtSignature: turn.thoughtSignature } : {}),
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
} catch {}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Turns executed tool results into what goes into history: unwraps delegation envelopes, rolls each worker's usage into the run's total and budget (calling `onWorker` for each), and returns the sanitized results; in: both loops after a tool batch, out: the results for the context and `allSubagents`.
|
|
688
|
+
function rollUpToolResults(
|
|
689
|
+
results: ToolResultRecord[],
|
|
690
|
+
config: AgentLoopConfig,
|
|
691
|
+
accumulated: TokenUsage,
|
|
692
|
+
allSubagents: SubAgentExecutionMetadata[],
|
|
693
|
+
onWorker?: (worker: SubAgentExecutionMetadata) => void
|
|
694
|
+
): ToolResultRecord[] {
|
|
695
|
+
const sanitizedResults: ToolResultRecord[] = [];
|
|
696
|
+
for (const r of results) {
|
|
697
|
+
const { sanitized, metas } = sanitizeToolResult(r);
|
|
698
|
+
if (metas.length > 0) {
|
|
699
|
+
allSubagents.push(...metas);
|
|
700
|
+
for (const s of metas) {
|
|
701
|
+
if (s.raw) s.raw = applyCaptureRaw(s.raw, config.captureRaw);
|
|
702
|
+
const subagentSpec = getModelFromCatalog(s.provider, s.model);
|
|
703
|
+
if (subagentSpec && (!s.usage?.cost || !s.usage.cost.totalCost)) {
|
|
704
|
+
s.usage.cost = computeCostFromPricing(s.usage, subagentSpec);
|
|
705
|
+
}
|
|
706
|
+
accumulateUsage(accumulated, s.usage, subagentSpec);
|
|
707
|
+
config.enforceBudget?.(s.usage);
|
|
708
|
+
onWorker?.(s);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
sanitizedResults.push(sanitized);
|
|
712
|
+
}
|
|
713
|
+
return sanitizedResults;
|
|
714
|
+
}
|
|
715
|
+
|
|
545
716
|
/**
|
|
546
717
|
* Stops a model from hammering the same call. Per fingerprint (name + stable
|
|
547
718
|
* args) the guard counts CONSECUTIVE turns: the first two identical calls run,
|
|
@@ -557,6 +728,7 @@ async function executeToolCallsWithRepeatGuard(options: {
|
|
|
557
728
|
openToolLog?: ExecuteToolsOptions["openToolLog"];
|
|
558
729
|
previousFingerprints: Set<string>;
|
|
559
730
|
repeatCounts: Map<string, number>;
|
|
731
|
+
prompts?: Prompts;
|
|
560
732
|
agentName?: string;
|
|
561
733
|
signal?: AbortSignal;
|
|
562
734
|
sessionId?: string;
|
|
@@ -590,7 +762,7 @@ async function executeToolCallsWithRepeatGuard(options: {
|
|
|
590
762
|
{ agentName: options.agentName, sessionId: options.sessionId },
|
|
591
763
|
{ reason: "repeat_blocked" }
|
|
592
764
|
);
|
|
593
|
-
blocked.set(call.id, makeRepeatBlockedResult(call));
|
|
765
|
+
blocked.set(call.id, makeRepeatBlockedResult(call, options.prompts?.toolRepeatBlocked));
|
|
594
766
|
} else {
|
|
595
767
|
executable.push(call);
|
|
596
768
|
}
|
|
@@ -610,6 +782,7 @@ async function executeToolCallsWithRepeatGuard(options: {
|
|
|
610
782
|
onSubagentEvent: options.onSubagentEvent,
|
|
611
783
|
onResult: options.onResult,
|
|
612
784
|
openToolLog: options.openToolLog,
|
|
785
|
+
prompts: options.prompts,
|
|
613
786
|
})
|
|
614
787
|
: [];
|
|
615
788
|
const byId = new Map<string, ToolResultRecord[]>();
|
|
@@ -693,6 +866,7 @@ async function finishInterruptedTools(
|
|
|
693
866
|
sessionId: config.runOptions?.sessionId || config.options?.sessionId,
|
|
694
867
|
onResult: config.onToolResult,
|
|
695
868
|
openToolLog: config.openToolLog,
|
|
869
|
+
prompts: config.prompts,
|
|
696
870
|
})
|
|
697
871
|
: [];
|
|
698
872
|
const ran = new Map(executed.map((r) => [r.id, r]));
|
|
@@ -734,15 +908,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
734
908
|
// Partial-run state hoisted outside try so the catch block can attach
|
|
735
909
|
// {usage,cost,turns,toolCalls,toolResults,text} to ANY thrown error.
|
|
736
910
|
const startTime = Date.now();
|
|
737
|
-
const accumulatedUsage: TokenUsage =
|
|
738
|
-
inputTokens: 0,
|
|
739
|
-
outputTokens: 0,
|
|
740
|
-
totalTokens: 0,
|
|
741
|
-
cachedTokens: 0,
|
|
742
|
-
cacheReadTokens: 0,
|
|
743
|
-
cacheWriteTokens: 0,
|
|
744
|
-
thinkingTokens: 0,
|
|
745
|
-
};
|
|
911
|
+
const accumulatedUsage: TokenUsage = newTurnUsage();
|
|
746
912
|
const allToolCalls: ToolCallRecord[] = [];
|
|
747
913
|
const allToolResults: ToolResultRecord[] = [];
|
|
748
914
|
const allSubagents: SubAgentExecutionMetadata[] = [];
|
|
@@ -776,27 +942,20 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
776
942
|
checkDeadline(startTime, limits.deadlineMs);
|
|
777
943
|
config.enforceBudget?.();
|
|
778
944
|
// Repair orphan tool pairs before every request.
|
|
779
|
-
ensureToolPairing(context);
|
|
945
|
+
ensureToolPairing(context, config.prompts);
|
|
780
946
|
if (config.fileAccess?.roots) await enforceFileAccess(context.messages, config.fileAccess.roots);
|
|
781
947
|
turns++;
|
|
782
948
|
|
|
783
949
|
const spec = getModelFromCatalog(provider.id, modelId);
|
|
784
950
|
|
|
785
|
-
const providerOptions
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
};
|
|
794
|
-
// Per-request timeout pass-through (providers honor timeoutMs).
|
|
795
|
-
try {
|
|
796
|
-
if (Number.isFinite(limits.requestTimeoutMs) && limits.requestTimeoutMs !== Infinity) {
|
|
797
|
-
(providerOptions as unknown as { timeoutMs?: number }).timeoutMs = limits.requestTimeoutMs;
|
|
798
|
-
}
|
|
799
|
-
} catch {}
|
|
951
|
+
const providerOptions = buildTurnOptions(
|
|
952
|
+
config,
|
|
953
|
+
standardTools,
|
|
954
|
+
runOptions?.signal,
|
|
955
|
+
turns,
|
|
956
|
+
loopOutput.spec,
|
|
957
|
+
limits.requestTimeoutMs
|
|
958
|
+
);
|
|
800
959
|
|
|
801
960
|
// A long-running call (e.g. Google long decode) times each HTTP request itself: no whole-call race.
|
|
802
961
|
const longRunning = provider.isLongRunning?.(modelId, providerOptions) === true;
|
|
@@ -818,34 +977,9 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
818
977
|
// Canonical session routing: lets adapters detect provider switches.
|
|
819
978
|
noteProviderTurn(runOptions?.sessionId || options?.sessionId, provider.id);
|
|
820
979
|
|
|
821
|
-
|
|
822
|
-
// OFF): without the opt-in the run returns the provider's empty text and finishReason.
|
|
823
|
-
if (
|
|
824
|
-
(!genResult.text || genResult.text.trim() === "") &&
|
|
825
|
-
(!genResult.toolCalls || genResult.toolCalls.length === 0) &&
|
|
826
|
-
genResult.thinking
|
|
827
|
-
) {
|
|
828
|
-
if (shouldPromoteThinkingToAnswer(config, runOptions)) {
|
|
829
|
-
if (genResult.thinking.includes("</think>")) {
|
|
830
|
-
const parts = genResult.thinking.split(/<\/(?:think|thought)>/i);
|
|
831
|
-
genResult.thinking = parts[0]!.replace(/<(?:think|thought)>/i, "").trim() || undefined;
|
|
832
|
-
genResult.text = parts.slice(1).join("").trim();
|
|
833
|
-
} else {
|
|
834
|
-
genResult.text = genResult.thinking;
|
|
835
|
-
genResult.thinking = undefined;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
980
|
+
promoteThinkingIfAllowed(genResult, config, runOptions);
|
|
839
981
|
|
|
840
|
-
|
|
841
|
-
try {
|
|
842
|
-
config.onTurnUsage?.(genResult.usage, spec);
|
|
843
|
-
} catch {}
|
|
844
|
-
config.enforceBudget?.({
|
|
845
|
-
...genResult.usage,
|
|
846
|
-
cost: computeCostFromPricing(genResult.usage, spec, options?.serviceTier),
|
|
847
|
-
});
|
|
848
|
-
checkTokenLimit(accumulatedUsage, limits.maxTotalTokens);
|
|
982
|
+
recordModelTurn(config, accumulatedUsage, genResult.usage, spec, limits.maxTotalTokens);
|
|
849
983
|
|
|
850
984
|
// Record assistant turn in context
|
|
851
985
|
context.addAssistantMessage(
|
|
@@ -856,31 +990,15 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
856
990
|
genResult.providerSteps
|
|
857
991
|
);
|
|
858
992
|
|
|
859
|
-
|
|
860
|
-
// one bus event per thinking turn carrying the text + thought signature.
|
|
861
|
-
// Never touches provider payloads, history, or persistence formats.
|
|
862
|
-
if (genResult.thinking) {
|
|
863
|
-
try {
|
|
864
|
-
telemetryBus.emit({
|
|
865
|
-
code: "turn.thinking",
|
|
866
|
-
message: `[Agent Accelerator] turn ${turns} thinking (${provider.id}/${modelId})`,
|
|
867
|
-
data: {
|
|
868
|
-
turns,
|
|
869
|
-
model: modelId,
|
|
870
|
-
provider: provider.id,
|
|
871
|
-
sessionId: runOptions?.sessionId || options?.sessionId,
|
|
872
|
-
thinking: genResult.thinking,
|
|
873
|
-
...(genResult.thoughtSignature ? { thoughtSignature: genResult.thoughtSignature } : {}),
|
|
874
|
-
},
|
|
875
|
-
});
|
|
876
|
-
} catch {}
|
|
877
|
-
}
|
|
993
|
+
emitThinkingTrace(genResult, turns, provider.id, modelId, runOptions?.sessionId || options?.sessionId);
|
|
878
994
|
|
|
879
995
|
// Mid-session steer: if new queries arrived during this provider turn,
|
|
880
996
|
// inject them now. Tool turns still execute first (see below) so the
|
|
881
997
|
// current action finishes before redirection.
|
|
882
998
|
const noToolSteered =
|
|
883
|
-
!genResult.toolCalls || genResult.toolCalls.length === 0
|
|
999
|
+
!genResult.toolCalls || genResult.toolCalls.length === 0
|
|
1000
|
+
? drainSteerInbox(context, config.steerInbox, config.prompts)
|
|
1001
|
+
: [];
|
|
884
1002
|
|
|
885
1003
|
// If no tool calls, generation is complete — unless steered, in which
|
|
886
1004
|
// case the injected message extends the SAME run (no new response).
|
|
@@ -918,29 +1036,13 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
918
1036
|
sessionId: runOptions?.sessionId || options?.sessionId,
|
|
919
1037
|
onResult: config.onToolResult,
|
|
920
1038
|
openToolLog: config.openToolLog,
|
|
1039
|
+
prompts: config.prompts,
|
|
921
1040
|
});
|
|
922
1041
|
const results = guarded.results;
|
|
923
1042
|
previousToolCallFingerprints = guarded.fingerprints;
|
|
924
1043
|
|
|
925
1044
|
// Process results and extract any sub-agent execution metadata
|
|
926
|
-
const sanitizedResults
|
|
927
|
-
|
|
928
|
-
for (const r of results) {
|
|
929
|
-
const { sanitized, metas } = sanitizeToolResult(r);
|
|
930
|
-
if (metas.length > 0) {
|
|
931
|
-
allSubagents.push(...metas);
|
|
932
|
-
for (const s of metas) {
|
|
933
|
-
if (s.raw) s.raw = applyCaptureRaw(s.raw, config.captureRaw);
|
|
934
|
-
const subagentSpec = getModelFromCatalog(s.provider, s.model);
|
|
935
|
-
if (subagentSpec && (!s.usage?.cost || !s.usage.cost.totalCost)) {
|
|
936
|
-
s.usage.cost = computeCostFromPricing(s.usage, subagentSpec);
|
|
937
|
-
}
|
|
938
|
-
accumulateUsage(accumulatedUsage, s.usage, subagentSpec);
|
|
939
|
-
config.enforceBudget?.(s.usage);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
sanitizedResults.push(sanitized);
|
|
943
|
-
}
|
|
1045
|
+
const sanitizedResults = rollUpToolResults(results, config, accumulatedUsage, allSubagents);
|
|
944
1046
|
|
|
945
1047
|
allToolResults.push(...sanitizedResults);
|
|
946
1048
|
context.addToolResults(sanitizedResults);
|
|
@@ -964,7 +1066,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
964
1066
|
// Steer drain AFTER tool results so call/result pairs stay adjacent.
|
|
965
1067
|
// The next loop iteration sends the injected user message to the model
|
|
966
1068
|
// within the same run (same usage/turn budget).
|
|
967
|
-
const toolSteered = drainSteerInbox(context, config.steerInbox);
|
|
1069
|
+
const toolSteered = drainSteerInbox(context, config.steerInbox, config.prompts);
|
|
968
1070
|
if (toolSteered.length > 0) {
|
|
969
1071
|
try {
|
|
970
1072
|
config.onSteerInjected?.(toolSteered);
|
|
@@ -1038,15 +1140,14 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
1038
1140
|
}
|
|
1039
1141
|
// Every failure carries its partial progress for observability.
|
|
1040
1142
|
try {
|
|
1041
|
-
(err as unknown as { partial?: unknown }).partial ??=
|
|
1042
|
-
|
|
1043
|
-
cost: accumulatedUsage.cost,
|
|
1143
|
+
(err as unknown as { partial?: unknown }).partial ??= partialProgress(
|
|
1144
|
+
accumulatedUsage,
|
|
1044
1145
|
turns,
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1146
|
+
allToolCalls,
|
|
1147
|
+
allToolResults,
|
|
1148
|
+
allSubagents,
|
|
1149
|
+
finalResult?.text
|
|
1150
|
+
);
|
|
1050
1151
|
} catch {
|
|
1051
1152
|
// why: partial attachment is observability-only and must never mask the error.
|
|
1052
1153
|
}
|
|
@@ -1110,15 +1211,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1110
1211
|
|
|
1111
1212
|
(async () => {
|
|
1112
1213
|
// Hoisted for catch-block partial reporting.
|
|
1113
|
-
let accumulatedUsage: TokenUsage =
|
|
1114
|
-
inputTokens: 0,
|
|
1115
|
-
outputTokens: 0,
|
|
1116
|
-
totalTokens: 0,
|
|
1117
|
-
cachedTokens: 0,
|
|
1118
|
-
cacheReadTokens: 0,
|
|
1119
|
-
cacheWriteTokens: 0,
|
|
1120
|
-
thinkingTokens: 0,
|
|
1121
|
-
};
|
|
1214
|
+
let accumulatedUsage: TokenUsage = newTurnUsage();
|
|
1122
1215
|
let lastResponse: AgentResponse | null = null;
|
|
1123
1216
|
let turns = 0;
|
|
1124
1217
|
// Tool progress hoisted so failures carry toolCalls/toolResults.
|
|
@@ -1158,15 +1251,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1158
1251
|
return;
|
|
1159
1252
|
}
|
|
1160
1253
|
|
|
1161
|
-
accumulatedUsage =
|
|
1162
|
-
inputTokens: 0,
|
|
1163
|
-
outputTokens: 0,
|
|
1164
|
-
totalTokens: 0,
|
|
1165
|
-
cachedTokens: 0,
|
|
1166
|
-
cacheReadTokens: 0,
|
|
1167
|
-
cacheWriteTokens: 0,
|
|
1168
|
-
thinkingTokens: 0,
|
|
1169
|
-
};
|
|
1254
|
+
accumulatedUsage = newTurnUsage();
|
|
1170
1255
|
|
|
1171
1256
|
// allToolCalls/allToolResults/allSubagents are hoisted above (fresh
|
|
1172
1257
|
// per stream run); just reset turn counters here.
|
|
@@ -1180,26 +1265,20 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1180
1265
|
throwIfCancelled({ usage: accumulatedUsage, turns, text: lastResponse?.text });
|
|
1181
1266
|
checkDeadline(streamStartTime, streamLimits.deadlineMs);
|
|
1182
1267
|
config.enforceBudget?.();
|
|
1183
|
-
ensureToolPairing(context);
|
|
1268
|
+
ensureToolPairing(context, config.prompts);
|
|
1184
1269
|
if (config.fileAccess?.roots) await enforceFileAccess(context.messages, config.fileAccess.roots);
|
|
1185
1270
|
turns++;
|
|
1186
1271
|
|
|
1187
1272
|
const spec2 = getModelFromCatalog(provider.id, modelId);
|
|
1188
1273
|
|
|
1189
|
-
const providerOptions
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
};
|
|
1198
|
-
try {
|
|
1199
|
-
if (Number.isFinite(streamLimits.requestTimeoutMs) && streamLimits.requestTimeoutMs !== Infinity) {
|
|
1200
|
-
(providerOptions as unknown as { timeoutMs?: number }).timeoutMs = streamLimits.requestTimeoutMs;
|
|
1201
|
-
}
|
|
1202
|
-
} catch {}
|
|
1274
|
+
const providerOptions = buildTurnOptions(
|
|
1275
|
+
config,
|
|
1276
|
+
standardTools,
|
|
1277
|
+
linked.signal,
|
|
1278
|
+
turns,
|
|
1279
|
+
streamOutputSpec,
|
|
1280
|
+
streamLimits.requestTimeoutMs
|
|
1281
|
+
);
|
|
1203
1282
|
|
|
1204
1283
|
const longRunning = provider.isLongRunning?.(modelId, providerOptions) === true;
|
|
1205
1284
|
const innerStream = provider.stream(
|
|
@@ -1281,35 +1360,9 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1281
1360
|
// Canonical session routing: lets adapters detect provider switches.
|
|
1282
1361
|
noteProviderTurn(runOptions?.sessionId || options?.sessionId, provider.id);
|
|
1283
1362
|
|
|
1284
|
-
|
|
1285
|
-
// it to answer text is gated (default OFF): without the opt-in
|
|
1286
|
-
// the turn keeps the provider's empty text and finishReason.
|
|
1287
|
-
if (
|
|
1288
|
-
(!turnResponse.text || turnResponse.text.trim() === "") &&
|
|
1289
|
-
(!turnResponse.toolCalls || turnResponse.toolCalls.length === 0) &&
|
|
1290
|
-
turnResponse.thinking
|
|
1291
|
-
) {
|
|
1292
|
-
if (shouldPromoteThinkingToAnswer(config, runOptions)) {
|
|
1293
|
-
if (turnResponse.thinking.includes("</think>")) {
|
|
1294
|
-
const parts = turnResponse.thinking.split(/<\/(?:think|thought)>/i);
|
|
1295
|
-
(turnResponse as any).thinking = parts[0]!.replace(/<(?:think|thought)>/i, "").trim() || undefined;
|
|
1296
|
-
(turnResponse as any).text = parts.slice(1).join("").trim();
|
|
1297
|
-
} else {
|
|
1298
|
-
(turnResponse as any).text = turnResponse.thinking;
|
|
1299
|
-
(turnResponse as any).thinking = undefined;
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1363
|
+
promoteThinkingIfAllowed(turnResponse, config, runOptions);
|
|
1303
1364
|
|
|
1304
|
-
|
|
1305
|
-
try {
|
|
1306
|
-
config.onTurnUsage?.(turnResponse.usage, spec2);
|
|
1307
|
-
} catch {}
|
|
1308
|
-
config.enforceBudget?.({
|
|
1309
|
-
...turnResponse.usage,
|
|
1310
|
-
cost: computeCostFromPricing(turnResponse.usage, spec2, options?.serviceTier),
|
|
1311
|
-
});
|
|
1312
|
-
checkTokenLimit(accumulatedUsage, streamLimits.maxTotalTokens);
|
|
1365
|
+
recordModelTurn(config, accumulatedUsage, turnResponse.usage, spec2, streamLimits.maxTotalTokens);
|
|
1313
1366
|
|
|
1314
1367
|
context.addAssistantMessage(
|
|
1315
1368
|
turnResponse.text,
|
|
@@ -1319,28 +1372,11 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1319
1372
|
turnResponse.providerSteps
|
|
1320
1373
|
);
|
|
1321
1374
|
|
|
1322
|
-
|
|
1323
|
-
// mirrors the non-streaming loop above; bus-only, never wire/history.
|
|
1324
|
-
if (turnResponse.thinking) {
|
|
1325
|
-
try {
|
|
1326
|
-
telemetryBus.emit({
|
|
1327
|
-
code: "turn.thinking",
|
|
1328
|
-
message: `[Agent Accelerator] turn ${turns} thinking (${provider.id}/${modelId})`,
|
|
1329
|
-
data: {
|
|
1330
|
-
turns,
|
|
1331
|
-
model: modelId,
|
|
1332
|
-
provider: provider.id,
|
|
1333
|
-
sessionId: runOptions?.sessionId || options?.sessionId,
|
|
1334
|
-
thinking: turnResponse.thinking,
|
|
1335
|
-
...(turnResponse.thoughtSignature ? { thoughtSignature: turnResponse.thoughtSignature } : {}),
|
|
1336
|
-
},
|
|
1337
|
-
});
|
|
1338
|
-
} catch {}
|
|
1339
|
-
}
|
|
1375
|
+
emitThinkingTrace(turnResponse, turns, provider.id, modelId, runOptions?.sessionId || options?.sessionId);
|
|
1340
1376
|
|
|
1341
1377
|
const noToolSteered =
|
|
1342
1378
|
!turnResponse.toolCalls || turnResponse.toolCalls.length === 0
|
|
1343
|
-
? drainSteerInbox(context, config.steerInbox)
|
|
1379
|
+
? drainSteerInbox(context, config.steerInbox, config.prompts)
|
|
1344
1380
|
: [];
|
|
1345
1381
|
if (!turnResponse.toolCalls || turnResponse.toolCalls.length === 0) {
|
|
1346
1382
|
emitTurn(runOptions, { turns, text: turnResponse.text, thinking: turnResponse.thinking });
|
|
@@ -1388,6 +1424,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1388
1424
|
sessionId: runOptions?.sessionId || options?.sessionId,
|
|
1389
1425
|
onResult: config.onToolResult,
|
|
1390
1426
|
openToolLog: config.openToolLog,
|
|
1427
|
+
prompts: config.prompts,
|
|
1391
1428
|
onSubagentEvent: (ev) => {
|
|
1392
1429
|
try {
|
|
1393
1430
|
outerStream.push({
|
|
@@ -1406,28 +1443,9 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1406
1443
|
const results = guarded.results;
|
|
1407
1444
|
previousToolCallFingerprints = guarded.fingerprints;
|
|
1408
1445
|
|
|
1409
|
-
const sanitizedResults
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
const { sanitized, metas } = sanitizeToolResult(r);
|
|
1413
|
-
if (metas.length > 0) {
|
|
1414
|
-
allSubagents.push(...metas);
|
|
1415
|
-
for (const s of metas) {
|
|
1416
|
-
if (s.raw) s.raw = applyCaptureRaw(s.raw, config.captureRaw);
|
|
1417
|
-
const subagentSpec = getModelFromCatalog(s.provider, s.model);
|
|
1418
|
-
if (subagentSpec && (!s.usage?.cost || !s.usage.cost.totalCost)) {
|
|
1419
|
-
s.usage.cost = computeCostFromPricing(s.usage, subagentSpec);
|
|
1420
|
-
}
|
|
1421
|
-
accumulateUsage(accumulatedUsage, s.usage, subagentSpec);
|
|
1422
|
-
config.enforceBudget?.(s.usage);
|
|
1423
|
-
outerStream.push({
|
|
1424
|
-
type: "subagent_complete",
|
|
1425
|
-
subagent: s,
|
|
1426
|
-
});
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
sanitizedResults.push(sanitized);
|
|
1430
|
-
}
|
|
1446
|
+
const sanitizedResults = rollUpToolResults(results, config, accumulatedUsage, allSubagents, (s) =>
|
|
1447
|
+
outerStream.push({ type: "subagent_complete", subagent: s })
|
|
1448
|
+
);
|
|
1431
1449
|
|
|
1432
1450
|
for (const res of sanitizedResults) {
|
|
1433
1451
|
outerStream.push({
|
|
@@ -1459,7 +1477,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1459
1477
|
emitProgress(config);
|
|
1460
1478
|
break;
|
|
1461
1479
|
}
|
|
1462
|
-
const toolSteered = drainSteerInbox(context, config.steerInbox);
|
|
1480
|
+
const toolSteered = drainSteerInbox(context, config.steerInbox, config.prompts);
|
|
1463
1481
|
if (toolSteered.length > 0) {
|
|
1464
1482
|
for (const preview of toolSteered) {
|
|
1465
1483
|
try {
|
|
@@ -1500,15 +1518,14 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1500
1518
|
} catch (err) {
|
|
1501
1519
|
const raw = err instanceof Error ? err : new Error(String(err));
|
|
1502
1520
|
try {
|
|
1503
|
-
(raw as unknown as { partial?: unknown }).partial ??=
|
|
1504
|
-
|
|
1505
|
-
cost: accumulatedUsage.cost,
|
|
1521
|
+
(raw as unknown as { partial?: unknown }).partial ??= partialProgress(
|
|
1522
|
+
accumulatedUsage,
|
|
1506
1523
|
turns,
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1524
|
+
allToolCalls,
|
|
1525
|
+
allToolResults,
|
|
1526
|
+
allSubagents,
|
|
1527
|
+
lastResponse?.text
|
|
1528
|
+
);
|
|
1512
1529
|
} catch {}
|
|
1513
1530
|
outerStream.fail(raw);
|
|
1514
1531
|
return;
|
|
@@ -1561,15 +1578,14 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1561
1578
|
// why: rollback policy itself must never mask the original error.
|
|
1562
1579
|
}
|
|
1563
1580
|
// Every failure carries its partial progress for observability.
|
|
1564
|
-
const partial =
|
|
1565
|
-
|
|
1566
|
-
cost: accumulatedUsage.cost,
|
|
1581
|
+
const partial = partialProgress(
|
|
1582
|
+
accumulatedUsage,
|
|
1567
1583
|
turns,
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1584
|
+
allToolCalls,
|
|
1585
|
+
allToolResults,
|
|
1586
|
+
allSubagents,
|
|
1587
|
+
lastResponse?.text
|
|
1588
|
+
);
|
|
1573
1589
|
// Aborts always surface as AbortError with partial attached.
|
|
1574
1590
|
if (isAbort) {
|
|
1575
1591
|
const abortErr = raw.name === "AbortError" ? raw : makeAbortError(partial, raw);
|