@agentionai/agents 1.11.0 → 1.12.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/agents/AgentEvent.d.ts +5 -0
- package/dist/agents/AgentEvent.js +5 -0
- package/dist/agents/BaseAgent.d.ts +107 -1
- package/dist/agents/BaseAgent.js +56 -0
- package/dist/agents/anthropic/ClaudeAgent.js +157 -117
- package/dist/agents/errors/AgentError.d.ts +12 -0
- package/dist/agents/openai/OpenAiAgent.js +116 -59
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.js +140 -107
- package/dist/agents/openrouter/OpenRouterAgent.js +128 -95
- package/dist/agents/reasoning-text.d.ts +44 -0
- package/dist/agents/reasoning-text.js +43 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +3 -3
|
@@ -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.
|
package/dist/agents/BaseAgent.js
CHANGED
|
@@ -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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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
|
-
|
|
466
|
-
|
|
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
|
-
|
|
469
|
-
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|