@agentionai/agents 1.11.0 → 1.13.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.
@@ -42,7 +42,7 @@ type OpenRouterAgentConfig = Omit<BaseAgentConfig, "vendor" | "model"> & OpenRou
42
42
  };
43
43
  type AgentConfig = ClaudeAgentConfig | OpenAIAgentConfig | GeminiAgentConfig | MistralAgentConfig | OllamaAgentConfig | LlamaCppAgentConfig | OpenRouterAgentConfig;
44
44
  export declare class Agent {
45
- static create(config: AgentConfig, history?: History): ClaudeAgent | GeminiAgent | MistralAgent | OllamaAgent | LlamaCppAgent | OpenRouterAgent | OpenAiAgent<(string & {}) | "gpt-5-pro" | "gpt-5.2-pro" | "gpt-5.4-pro" | "gpt-5.5-pro" | "o1" | "o1-pro" | "o3" | "o3-mini" | "o4-mini" | "gpt-5" | "gpt-5-mini" | "gpt-5-nano" | "gpt-5.1" | "gpt-5.2" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-nano" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-5.6-luna" | "gpt-4.1" | "gpt-4.1-mini" | "gpt-4.1-nano" | "gpt-4o" | "gpt-4o-mini" | "gpt-4o-2024-11-20" | "gpt-4o-2024-08-06" | "gpt-4o-2024-05-13" | "gpt-4o-mini-2024-07-18" | "gpt-4-turbo" | "gpt-4-turbo-2024-04-09" | "gpt-4-turbo-preview" | "gpt-4-0125-preview" | "gpt-4-1106-preview" | "gpt-4" | "gpt-4-0613" | "gpt-3.5-turbo" | "gpt-3.5-turbo-0125" | "gpt-3.5-turbo-1106" | "o1-preview" | "o1-mini">;
45
+ static create(config: AgentConfig, history?: History): ClaudeAgent | GeminiAgent | MistralAgent | OllamaAgent | LlamaCppAgent | OpenRouterAgent | OpenAiAgent<(string & {}) | "gpt-5-pro" | "gpt-5.2-pro" | "gpt-5.4-pro" | "gpt-5.5-pro" | "o1" | "o1-pro" | "o3" | "o3-mini" | "o4-mini" | "gpt-5" | "gpt-5-mini" | "gpt-5-nano" | "gpt-5.1" | "gpt-5.2" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-nano" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-5.6-luna" | "gpt-4.1" | "gpt-4.1-mini" | "gpt-4.1-nano" | "gpt-4o" | "gpt-4o-mini" | "gpt-4o-2024-11-20" | "gpt-4o-2024-08-06" | "gpt-4o-2024-05-13" | "gpt-4o-mini-2024-07-18" | "gpt-4-turbo" | "gpt-4-turbo-2024-04-09" | "gpt-4-turbo-preview" | "gpt-4-0125-preview" | "gpt-4-1106-preview" | "gpt-4" | "gpt-4-0613" | "gpt-3.5-turbo" | "gpt-3.5-turbo-0125" | "gpt-3.5-turbo-1106" | "o1-preview" | "o1-mini", import("openai/resources/models").Model>;
46
46
  }
47
47
  export {};
48
48
  //# sourceMappingURL=Agent.d.ts.map
@@ -137,6 +137,29 @@ export interface OpenAISpecificConfig {
137
137
  * see `lib/tools/BuiltInTool.ts`.
138
138
  */
139
139
  builtInTools?: BuiltInTool[];
140
+ /**
141
+ * Override the API base URL. Defaults to `api.openai.com/v1`; `CodexAgent`
142
+ * defaults it to `https://chatgpt.com/backend-api/codex`, and setting it
143
+ * there points at a Codex proxy instead.
144
+ */
145
+ baseURL?: string;
146
+ /**
147
+ * `CodexAgent` only: ChatGPT workspace to bill, sent as the
148
+ * `chatgpt-account-id` header.
149
+ */
150
+ accountId?: string;
151
+ /**
152
+ * `CodexAgent` only: client identifier sent as the `originator` header.
153
+ * OpenAI varies the model catalog by originator.
154
+ *
155
+ * @default "codex_cli_rs"
156
+ */
157
+ originator?: string;
158
+ /**
159
+ * `client_version` sent to the Codex models endpoint, which requires one and
160
+ * hides models newer than the version claimed.
161
+ */
162
+ clientVersion?: string;
140
163
  }
141
164
  /**
142
165
  * Vendor-specific configuration for Mistral
@@ -12,6 +12,11 @@ export declare class AgentEvent {
12
12
  static TOOL_ERROR: string;
13
13
  static CHUNK: string;
14
14
  static REASONING_CHUNK: string;
15
+ /**
16
+ * A streamed turn was cut short before its assistant message could be written
17
+ * to history. The listener receives the `PartialTurn` that was salvaged.
18
+ */
19
+ static PARTIAL_TURN: string;
15
20
  private defaultPrevented;
16
21
  constructor(target: BaseAgent<any>);
17
22
  preventDefault(): void;
@@ -25,4 +25,9 @@ AgentEvent.MAX_TOKENS_EXCEEDED = "max_tokens_exceeded";
25
25
  AgentEvent.TOOL_ERROR = "tool_error";
26
26
  AgentEvent.CHUNK = "chunk";
27
27
  AgentEvent.REASONING_CHUNK = "reasoning_chunk";
28
+ /**
29
+ * A streamed turn was cut short before its assistant message could be written
30
+ * to history. The listener receives the `PartialTurn` that was salvaged.
31
+ */
32
+ AgentEvent.PARTIAL_TURN = "partial_turn";
28
33
  //# sourceMappingURL=AgentEvent.js.map
@@ -2,7 +2,7 @@ import EventEmitter from "events";
2
2
  import { Tool } from "../tools/Tool";
3
3
  import { History, HistoryEntry, MessageRole, MessageContent, ImageMimeType } from "../history/History";
4
4
  import { AgentVendor, CommonAgentConfig, VendorSpecificConfig } from "./AgentConfig";
5
- import { AbortError } from "./errors/AgentError";
5
+ import { AbortError, AgentError } from "./errors/AgentError";
6
6
  import { ExecuteOptions } from "./cancellation";
7
7
  export type { HistoryEntry, MessageRole, MessageContent, ImageMimeType };
8
8
  export type { AgentVendor };
@@ -146,6 +146,76 @@ export type ModelInfo<TRaw = unknown> = {
146
146
  /** The provider's unmodified entry for this model. */
147
147
  raw: TRaw;
148
148
  };
149
+ /**
150
+ * Why a streamed turn stopped before its assistant message could be written to
151
+ * history. See {@link PartialTurn}.
152
+ */
153
+ export type PartialTurnReason =
154
+ /** The stream or the code around it threw. */
155
+ "error"
156
+ /** The caller's `AbortSignal` fired. */
157
+ | "aborted"
158
+ /** The provider stopped on its token limit. */
159
+ | "max_tokens"
160
+ /** The consumer stopped iterating the generator (a `break`, or a `return`). */
161
+ | "abandoned";
162
+ /** One tool call as it stood when a streamed turn was cut short. */
163
+ export type PartialToolCall = {
164
+ /** Provider-assigned call id. Empty if the stream ended before it arrived. */
165
+ id: string;
166
+ /** Tool name, which streams in before the arguments do. */
167
+ name: string;
168
+ /**
169
+ * Raw JSON argument text as received. Very likely truncated mid-token, so
170
+ * this is for inspection and recovery, not for `JSON.parse`.
171
+ */
172
+ arguments: string;
173
+ };
174
+ /**
175
+ * What a streamed turn had generated when it was cut short — the text,
176
+ * the reasoning trail, and any tool calls that had started to arrive.
177
+ *
178
+ * Streaming agents accumulate a turn locally and only write it to history once
179
+ * the stream ends cleanly, so a connection drop, a provider-side error or a
180
+ * token-limit stop would otherwise discard everything generated up to that
181
+ * point. That is cheap for a short answer and expensive for a local reasoning
182
+ * model, where the trail can represent twenty minutes of compute.
183
+ *
184
+ * The partial turn is therefore always captured, on {@link BaseAgent.lastPartialTurn}
185
+ * and on the thrown {@link AgentError.partial}, but never written to history:
186
+ * a half-finished turn is frequently not replayable (Anthropic thinking blocks
187
+ * need the `signature` that arrives last, truncated tool-call JSON does not
188
+ * parse), so what to do with it is the caller's decision.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * try {
193
+ * for await (const chunk of agent.executeStream("...")) process.stdout.write(chunk.content);
194
+ * } catch (err) {
195
+ * const salvaged = agent.lastPartialTurn;
196
+ * if (salvaged) fs.writeFileSync("trail.md", salvaged.reasoning);
197
+ * }
198
+ * ```
199
+ */
200
+ export type PartialTurn = {
201
+ /** Assistant text generated so far. Empty if none arrived. */
202
+ text: string;
203
+ /** Reasoning/thinking text generated so far. Empty if none arrived. */
204
+ reasoning: string;
205
+ /** Tool calls that had begun streaming, in index order. */
206
+ toolCalls: PartialToolCall[];
207
+ /** Why the turn stopped. */
208
+ reason: PartialTurnReason;
209
+ /** The error that ended it, where one did. */
210
+ error?: unknown;
211
+ /**
212
+ * Provider-specific extras worth keeping — currently Anthropic's thinking
213
+ * block `signature`, which is what decides whether the trail can be replayed.
214
+ */
215
+ meta?: Record<string, unknown>;
216
+ /** When the turn was cut short. */
217
+ at: Date;
218
+ };
149
219
  /**
150
220
  * The base agent is what the other agents are inheriting from
151
221
  * Handles the BaseConfig
@@ -168,6 +238,12 @@ export declare abstract class BaseAgent<TInput = unknown, TOutput = unknown> ext
168
238
  * Reset at the start of each execution.
169
239
  */
170
240
  lastTokenUsage?: TokenUsage;
241
+ /**
242
+ * What the most recent streamed turn had generated when it was cut short,
243
+ * or `undefined` if the last execution completed and its turn reached
244
+ * history. Reset at the start of every execution. See {@link PartialTurn}.
245
+ */
246
+ lastPartialTurn?: PartialTurn;
171
247
  /** Start of the API call currently in flight, set by `startTurnTimer()`. */
172
248
  private turnStartedAt?;
173
249
  /** First-token timestamp of the call in flight, set by `markFirstToken()`. */
@@ -265,6 +341,36 @@ export declare abstract class BaseAgent<TInput = unknown, TOutput = unknown> ext
265
341
  * chunk.
266
342
  */
267
343
  protected markFirstToken(): void;
344
+ /**
345
+ * Clear any captured partial turn. Called at the start of every `execute()` /
346
+ * `executeStream()`, so a set `lastPartialTurn` always refers to the current
347
+ * execution and never to an earlier one.
348
+ */
349
+ protected resetPartialTurn(): void;
350
+ /**
351
+ * Record what a streamed turn had generated when it was cut short, and
352
+ * announce it on {@link AgentEvent.PARTIAL_TURN}.
353
+ *
354
+ * Called from the `finally` of every streaming turn that did not commit its
355
+ * assistant message to history. A turn with nothing in it is ignored, so
356
+ * `lastPartialTurn` being set always means there is unsaved work.
357
+ *
358
+ * @returns the recorded partial turn, or `undefined` if there was nothing to
359
+ * record.
360
+ */
361
+ protected capturePartialTurn(partial: Omit<PartialTurn, "at">): PartialTurn | undefined;
362
+ /**
363
+ * Classify why a streamed turn stopped, from the error that ended it.
364
+ *
365
+ * @param error the error propagating out of the turn, or `undefined` when the
366
+ * consumer simply stopped iterating.
367
+ */
368
+ protected partialTurnReason(error: unknown, signal?: AbortSignal): PartialTurnReason;
369
+ /**
370
+ * Attach the captured partial turn to an error on its way out, so a caller
371
+ * holding only the error can still recover the trail.
372
+ */
373
+ protected withPartialTurn<E extends AgentError>(error: E): E;
268
374
  /**
269
375
  * Fold one API call's usage into `lastTokenUsage`, filling in any timings
270
376
  * the provider did not report from the local turn timer.
@@ -8,6 +8,7 @@ const events_1 = __importDefault(require("events"));
8
8
  const Tool_1 = require("../tools/Tool");
9
9
  const History_1 = require("../history/History");
10
10
  const AgentError_1 = require("./errors/AgentError");
11
+ const cancellation_1 = require("./cancellation");
11
12
  const AgentEvent_1 = require("./AgentEvent");
12
13
  /**
13
14
  * The base agent is what the other agents are inheriting from
@@ -181,6 +182,61 @@ class BaseAgent extends events_1.default {
181
182
  this.turnFirstTokenAt = Date.now();
182
183
  }
183
184
  }
185
+ /**
186
+ * Clear any captured partial turn. Called at the start of every `execute()` /
187
+ * `executeStream()`, so a set `lastPartialTurn` always refers to the current
188
+ * execution and never to an earlier one.
189
+ */
190
+ resetPartialTurn() {
191
+ this.lastPartialTurn = undefined;
192
+ }
193
+ /**
194
+ * Record what a streamed turn had generated when it was cut short, and
195
+ * announce it on {@link AgentEvent.PARTIAL_TURN}.
196
+ *
197
+ * Called from the `finally` of every streaming turn that did not commit its
198
+ * assistant message to history. A turn with nothing in it is ignored, so
199
+ * `lastPartialTurn` being set always means there is unsaved work.
200
+ *
201
+ * @returns the recorded partial turn, or `undefined` if there was nothing to
202
+ * record.
203
+ */
204
+ capturePartialTurn(partial) {
205
+ if (!partial.text &&
206
+ !partial.reasoning &&
207
+ partial.toolCalls.length === 0) {
208
+ return undefined;
209
+ }
210
+ const captured = { ...partial, at: new Date() };
211
+ this.lastPartialTurn = captured;
212
+ this.emit(AgentEvent_1.AgentEvent.PARTIAL_TURN, captured);
213
+ return captured;
214
+ }
215
+ /**
216
+ * Classify why a streamed turn stopped, from the error that ended it.
217
+ *
218
+ * @param error the error propagating out of the turn, or `undefined` when the
219
+ * consumer simply stopped iterating.
220
+ */
221
+ partialTurnReason(error, signal) {
222
+ if (error === undefined)
223
+ return "abandoned";
224
+ if (signal?.aborted || (0, cancellation_1.isAbortError)(error, signal))
225
+ return "aborted";
226
+ if (error instanceof AgentError_1.MaxTokensExceededError)
227
+ return "max_tokens";
228
+ return "error";
229
+ }
230
+ /**
231
+ * Attach the captured partial turn to an error on its way out, so a caller
232
+ * holding only the error can still recover the trail.
233
+ */
234
+ withPartialTurn(error) {
235
+ if (this.lastPartialTurn && error.partial === undefined) {
236
+ error.partial = this.lastPartialTurn;
237
+ }
238
+ return error;
239
+ }
184
240
  /**
185
241
  * Fold one API call's usage into `lastTokenUsage`, filling in any timings
186
242
  * the provider did not report from the local turn timer.
@@ -148,6 +148,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
148
148
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
149
149
  // Reset token usage for this execution
150
150
  this.resetTokenUsage();
151
+ this.resetPartialTurn();
151
152
  this.currentToolCallCount = 0;
152
153
  // Normalise input to a display string for viz reporting
153
154
  const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
@@ -375,6 +376,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
375
376
  async *executeStream(input, options) {
376
377
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
377
378
  this.resetTokenUsage();
379
+ this.resetPartialTurn();
378
380
  this.currentToolCallCount = 0;
379
381
  const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
380
382
  if (VizConfig_1.vizConfig.isEnabled()) {
@@ -402,7 +404,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
402
404
  VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
403
405
  this.vizEventId = undefined;
404
406
  }
405
- throw abortError;
407
+ throw this.withPartialTurn(abortError);
406
408
  }
407
409
  if (error instanceof sdk_1.APIError) {
408
410
  const apiError = new AgentError_1.ApiError(`Anthropic API error: ${error.message}`, error.status, error);
@@ -411,13 +413,13 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
411
413
  VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, error.status === 429);
412
414
  this.vizEventId = undefined;
413
415
  }
414
- throw apiError;
416
+ throw this.withPartialTurn(apiError);
415
417
  }
416
418
  // Errors raised inside streamTurn() (e.g. MaxTokensExceededError) are
417
419
  // already emitted and viz-reported at the throw site — preserve their
418
420
  // type rather than re-wrapping them in a generic ExecutionError.
419
421
  if (error instanceof AgentError_1.AgentError) {
420
- throw error;
422
+ throw this.withPartialTurn(error);
421
423
  }
422
424
  const executionError = new AgentError_1.ExecutionError(`Anthropic error: ${error instanceof Error ? error.message : "Unknown error"}`);
423
425
  this.emit(AgentEvent_1.AgentEvent.ERROR, executionError);
@@ -425,7 +427,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
425
427
  VizReporter_1.vizReporter.agentError(this.vizEventId, "ExecutionError", executionError.message, false);
426
428
  this.vizEventId = undefined;
427
429
  }
428
- throw executionError;
430
+ throw this.withPartialTurn(executionError);
429
431
  }
430
432
  finally {
431
433
  this.history.endExecution();
@@ -442,131 +444,169 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
442
444
  let stopReason = null;
443
445
  let inputTokens = 0;
444
446
  let outputTokens = 0;
445
- for await (const event of stream) {
446
- if (event.type === "message_start") {
447
- const e = event;
448
- inputTokens = e.message.usage.input_tokens;
449
- outputTokens = e.message.usage.output_tokens;
450
- }
451
- if (event.type === "message_delta") {
452
- const e = event;
453
- stopReason = e.delta.stop_reason ?? stopReason;
454
- outputTokens += e.usage?.output_tokens ?? 0;
455
- }
456
- if (event.type === "content_block_start") {
457
- const e = event;
458
- const block = e.content_block;
459
- if (block.type === "tool_use") {
460
- blocks.set(e.index, { kind: "tool_use", id: block.id, name: block.name, inputJson: "" });
447
+ // Set once this frame's assistant message reaches history. Until then the
448
+ // turn exists only in `blocks`, and the `finally` salvages it — extended
449
+ // thinking can run for minutes, and the stream throwing (or the consumer
450
+ // walking away) would otherwise drop the whole trail.
451
+ let committed = false;
452
+ let failure;
453
+ try {
454
+ for await (const event of stream) {
455
+ if (event.type === "message_start") {
456
+ const e = event;
457
+ inputTokens = e.message.usage.input_tokens;
458
+ outputTokens = e.message.usage.output_tokens;
461
459
  }
462
- else if (block.type === "text") {
463
- blocks.set(e.index, { kind: "text", text: "" });
460
+ if (event.type === "message_delta") {
461
+ const e = event;
462
+ stopReason = e.delta.stop_reason ?? stopReason;
463
+ outputTokens += e.usage?.output_tokens ?? 0;
464
464
  }
465
- else if (block.type === "thinking") {
466
- blocks.set(e.index, { kind: "thinking", thinking: "", signature: "" });
465
+ if (event.type === "content_block_start") {
466
+ const e = event;
467
+ const block = e.content_block;
468
+ if (block.type === "tool_use") {
469
+ blocks.set(e.index, { kind: "tool_use", id: block.id, name: block.name, inputJson: "" });
470
+ }
471
+ else if (block.type === "text") {
472
+ blocks.set(e.index, { kind: "text", text: "" });
473
+ }
474
+ else if (block.type === "thinking") {
475
+ blocks.set(e.index, { kind: "thinking", thinking: "", signature: "" });
476
+ }
477
+ else if (block.type === "redacted_thinking") {
478
+ blocks.set(e.index, { kind: "redacted_thinking", data: block.data });
479
+ }
467
480
  }
468
- else if (block.type === "redacted_thinking") {
469
- blocks.set(e.index, { kind: "redacted_thinking", data: block.data });
481
+ if (event.type === "content_block_delta") {
482
+ // First generated content of the turn — thinking counts, since it is
483
+ // generation time either way.
484
+ this.markFirstToken();
485
+ const e = event;
486
+ const delta = e.delta;
487
+ const acc = blocks.get(e.index);
488
+ if (delta.type === "text_delta") {
489
+ textContent += delta.text;
490
+ if (acc?.kind === "text")
491
+ acc.text += delta.text;
492
+ this.emit(AgentEvent_1.AgentEvent.CHUNK, delta.text);
493
+ yield { type: "text", content: delta.text };
494
+ }
495
+ else if (delta.type === "thinking_delta") {
496
+ if (acc?.kind === "thinking")
497
+ acc.thinking += delta.thinking;
498
+ this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, delta.thinking);
499
+ yield { type: "reasoning", content: delta.thinking };
500
+ }
501
+ else if (delta.type === "signature_delta") {
502
+ if (acc?.kind === "thinking")
503
+ acc.signature += delta.signature;
504
+ }
505
+ else if (delta.type === "input_json_delta") {
506
+ if (acc?.kind === "tool_use")
507
+ acc.inputJson += delta.partial_json;
508
+ }
470
509
  }
471
510
  }
472
- if (event.type === "content_block_delta") {
473
- // First generated content of the turn — thinking counts, since it is
474
- // generation time either way.
475
- this.markFirstToken();
476
- const e = event;
477
- const delta = e.delta;
478
- const acc = blocks.get(e.index);
479
- if (delta.type === "text_delta") {
480
- textContent += delta.text;
481
- if (acc?.kind === "text")
482
- acc.text += delta.text;
483
- this.emit(AgentEvent_1.AgentEvent.CHUNK, delta.text);
484
- yield { type: "text", content: delta.text };
485
- }
486
- else if (delta.type === "thinking_delta") {
487
- if (acc?.kind === "thinking")
488
- acc.thinking += delta.thinking;
489
- this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, delta.thinking);
490
- yield { type: "reasoning", content: delta.thinking };
491
- }
492
- else if (delta.type === "signature_delta") {
493
- if (acc?.kind === "thinking")
494
- acc.signature += delta.signature;
511
+ this.accumulateUsage({
512
+ input_tokens: inputTokens,
513
+ output_tokens: outputTokens,
514
+ total_tokens: inputTokens + outputTokens,
515
+ });
516
+ // The SDK's stream iterator swallows the abort and simply stops yielding,
517
+ // so without this an interrupted stream would look like a short but
518
+ // complete turn writing partial text to history and emitting DONE.
519
+ // Checked after accumulateUsage() so the tokens already spent are reported.
520
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
521
+ if (stopReason === "max_tokens") {
522
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
523
+ this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
524
+ this.emit(AgentEvent_1.AgentEvent.ERROR, error);
525
+ if (this.vizEventId) {
526
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "MaxTokensExceededError", error.message, false);
527
+ this.vizEventId = undefined;
495
528
  }
496
- else if (delta.type === "input_json_delta") {
497
- if (acc?.kind === "tool_use")
498
- acc.inputJson += delta.partial_json;
529
+ throw error;
530
+ }
531
+ // Rebuild the assistant turn in stream order (thinking → text → tool_use).
532
+ const orderedBlocks = Array.from(blocks.entries())
533
+ .sort(([a], [b]) => a - b)
534
+ .map(([, b]) => {
535
+ switch (b.kind) {
536
+ case "thinking":
537
+ return { type: "thinking", thinking: b.thinking, signature: b.signature };
538
+ case "redacted_thinking":
539
+ return { type: "redacted_thinking", data: b.data };
540
+ case "tool_use":
541
+ return {
542
+ type: "tool_use",
543
+ id: b.id,
544
+ name: b.name,
545
+ input: JSON.parse(b.inputJson || "{}"),
546
+ };
547
+ case "text":
548
+ return { type: "text", text: b.text, citations: [] };
499
549
  }
550
+ });
551
+ // Fallback: preserve streamed text even if no text block start was observed.
552
+ if (textContent && !orderedBlocks.some((b) => b.type === "text")) {
553
+ orderedBlocks.push({ type: "text", text: textContent, citations: [] });
500
554
  }
501
- }
502
- this.accumulateUsage({
503
- input_tokens: inputTokens,
504
- output_tokens: outputTokens,
505
- total_tokens: inputTokens + outputTokens,
506
- });
507
- // The SDK's stream iterator swallows the abort and simply stops yielding,
508
- // so without this an interrupted stream would look like a short but
509
- // complete turn — writing partial text to history and emitting DONE.
510
- // Checked after accumulateUsage() so the tokens already spent are reported.
511
- (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
512
- if (stopReason === "max_tokens") {
513
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
514
- this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
515
- this.emit(AgentEvent_1.AgentEvent.ERROR, error);
516
- if (this.vizEventId) {
517
- VizReporter_1.vizReporter.agentError(this.vizEventId, "MaxTokensExceededError", error.message, false);
518
- this.vizEventId = undefined;
555
+ const toolUseBlocks = orderedBlocks.filter((b) => b.type === "tool_use");
556
+ if (stopReason === "tool_use" && toolUseBlocks.length > 0) {
557
+ // As in handleResponse(): bail out before the assistant turn is written,
558
+ // so a cancelled run leaves no unanswered tool_use in history.
559
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
560
+ this.emit(AgentEvent_1.AgentEvent.TOOL_USE, orderedBlocks);
561
+ this.currentToolCallCount += toolUseBlocks.length;
562
+ const assistantEntry = transformers_1.anthropicTransformer.fromProviderContent("assistant", orderedBlocks);
563
+ this.addToHistory(assistantEntry);
564
+ committed = true;
565
+ const toolResults = await this.handleToolUse(orderedBlocks, options);
566
+ this.addMessageToHistory("user", toolResults);
567
+ yield* this.streamTurn(options);
519
568
  }
520
- throw error;
521
- }
522
- // Rebuild the assistant turn in stream order (thinking → text → tool_use).
523
- const orderedBlocks = Array.from(blocks.entries())
524
- .sort(([a], [b]) => a - b)
525
- .map(([, b]) => {
526
- switch (b.kind) {
527
- case "thinking":
528
- return { type: "thinking", thinking: b.thinking, signature: b.signature };
529
- case "redacted_thinking":
530
- return { type: "redacted_thinking", data: b.data };
531
- case "tool_use":
532
- return {
533
- type: "tool_use",
534
- id: b.id,
535
- name: b.name,
536
- input: JSON.parse(b.inputJson || "{}"),
537
- };
538
- case "text":
539
- return { type: "text", text: b.text, citations: [] };
569
+ else {
570
+ const assistantEntry = transformers_1.anthropicTransformer.fromProviderContent("assistant", orderedBlocks);
571
+ this.addToHistory(assistantEntry);
572
+ committed = true;
573
+ this.emit(AgentEvent_1.AgentEvent.DONE, { content: textContent }, this.lastTokenUsage);
574
+ if (this.vizEventId) {
575
+ VizReporter_1.vizReporter.agentComplete(this.vizEventId, {
576
+ input: this.lastTokenUsage?.input_tokens || 0,
577
+ output: this.lastTokenUsage?.output_tokens || 0,
578
+ total: this.lastTokenUsage?.total_tokens || 0,
579
+ }, "end_turn", this.currentToolCallCount > 0, this.currentToolCallCount, textContent);
580
+ this.vizEventId = undefined;
581
+ }
540
582
  }
541
- });
542
- // Fallback: preserve streamed text even if no text block start was observed.
543
- if (textContent && !orderedBlocks.some((b) => b.type === "text")) {
544
- orderedBlocks.push({ type: "text", text: textContent, citations: [] });
545
583
  }
546
- const toolUseBlocks = orderedBlocks.filter((b) => b.type === "tool_use");
547
- if (stopReason === "tool_use" && toolUseBlocks.length > 0) {
548
- // As in handleResponse(): bail out before the assistant turn is written,
549
- // so a cancelled run leaves no unanswered tool_use in history.
550
- (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
551
- this.emit(AgentEvent_1.AgentEvent.TOOL_USE, orderedBlocks);
552
- this.currentToolCallCount += toolUseBlocks.length;
553
- const assistantEntry = transformers_1.anthropicTransformer.fromProviderContent("assistant", orderedBlocks);
554
- this.addToHistory(assistantEntry);
555
- const toolResults = await this.handleToolUse(orderedBlocks, options);
556
- this.addMessageToHistory("user", toolResults);
557
- yield* this.streamTurn(options);
584
+ catch (error) {
585
+ failure = error;
586
+ throw error;
558
587
  }
559
- else {
560
- const assistantEntry = transformers_1.anthropicTransformer.fromProviderContent("assistant", orderedBlocks);
561
- this.addToHistory(assistantEntry);
562
- this.emit(AgentEvent_1.AgentEvent.DONE, { content: textContent }, this.lastTokenUsage);
563
- if (this.vizEventId) {
564
- VizReporter_1.vizReporter.agentComplete(this.vizEventId, {
565
- input: this.lastTokenUsage?.input_tokens || 0,
566
- output: this.lastTokenUsage?.output_tokens || 0,
567
- total: this.lastTokenUsage?.total_tokens || 0,
568
- }, "end_turn", this.currentToolCallCount > 0, this.currentToolCallCount, textContent);
569
- this.vizEventId = undefined;
588
+ finally {
589
+ if (!committed) {
590
+ // `signature` is what decides whether the trail can ever be replayed to
591
+ // Anthropic: it arrives in a `signature_delta` after the thinking text,
592
+ // so an interrupted block usually has none. Reported rather than
593
+ // guessed at, since a signatureless thinking block is rejected on
594
+ // replay while the text itself is still worth keeping.
595
+ const thinking = Array.from(blocks.values()).filter((b) => b.kind === "thinking");
596
+ this.capturePartialTurn({
597
+ text: textContent,
598
+ reasoning: thinking.map((b) => b.thinking).join(""),
599
+ toolCalls: Array.from(blocks.entries())
600
+ .sort(([a], [b]) => a - b)
601
+ .flatMap(([, b]) => b.kind === "tool_use"
602
+ ? [{ id: b.id, name: b.name, arguments: b.inputJson }]
603
+ : []),
604
+ reason: this.partialTurnReason(failure, options?.signal),
605
+ error: failure,
606
+ meta: thinking.length
607
+ ? { signatures: thinking.map((b) => b.signature) }
608
+ : undefined,
609
+ });
570
610
  }
571
611
  }
572
612
  }
@@ -1,7 +1,19 @@
1
+ import type { PartialTurn } from "../BaseAgent";
1
2
  /**
2
3
  * Base error class for all agent-related errors
3
4
  */
4
5
  export declare class AgentError extends Error {
6
+ /**
7
+ * What a streamed turn had generated when this error cut it short, where the
8
+ * error came out of `executeStream()`. Streaming agents only write an
9
+ * assistant turn to history once the stream ends cleanly, so without this the
10
+ * text and the reasoning trail generated up to the failure would be lost —
11
+ * cheap for a short answer, expensive for a local reasoning model.
12
+ *
13
+ * Also available as `agent.lastPartialTurn`. See {@link PartialTurn} for why
14
+ * it is handed back rather than written to history.
15
+ */
16
+ partial?: PartialTurn;
5
17
  /**
6
18
  * @param message Error message
7
19
  * @param options Additional error options