@agentionai/agents 1.6.0 → 1.7.0-beta.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.
@@ -2,8 +2,11 @@ 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";
6
+ import { ExecuteOptions } from "./cancellation";
5
7
  export type { HistoryEntry, MessageRole, MessageContent, ImageMimeType };
6
8
  export type { AgentVendor };
9
+ export type { ExecuteOptions };
7
10
  /**
8
11
  * Agent config as used across all agents
9
12
  * @deprecated Use CommonAgentConfig with vendorConfig instead
@@ -173,9 +176,16 @@ export declare abstract class BaseAgent<TInput = unknown, TOutput = unknown> ext
173
176
  *
174
177
  */
175
178
  constructor(config: BaseAgentConfig, history?: History);
176
- abstract execute(input: TInput): Promise<TOutput>;
179
+ /**
180
+ * Run the agent on `input`.
181
+ *
182
+ * @param options Per-run options. `options.signal` cancels the run: the
183
+ * in-flight provider request is aborted and the promise
184
+ * rejects with an {@link AbortError}.
185
+ */
186
+ abstract execute(input: TInput, options?: ExecuteOptions): Promise<TOutput>;
177
187
  protected abstract process(input: TInput): Promise<TOutput>;
178
- protected abstract handleResponse(response: unknown): Promise<unknown>;
188
+ protected abstract handleResponse(response: unknown, options?: ExecuteOptions): Promise<unknown>;
179
189
  protected getToolDefinitions(): unknown[];
180
190
  /**
181
191
  * List the models the provider currently offers, straight from its models
@@ -220,6 +230,18 @@ export declare abstract class BaseAgent<TInput = unknown, TOutput = unknown> ext
220
230
  getHistoryEntries(): HistoryEntry[];
221
231
  getTools(): Tool<unknown>[];
222
232
  clearHistory(): void;
233
+ /**
234
+ * Build the {@link AbortError} for a cancelled run and emit it as an
235
+ * `AgentEvent.ERROR`, so a cancellation reaches error listeners the same way
236
+ * every other failure does.
237
+ *
238
+ * Returns the error rather than throwing it, leaving the caller to report it
239
+ * to whatever visualization event is open before rethrowing.
240
+ *
241
+ * @param error What the provider threw once the signal fired.
242
+ * @param signal The signal supplied to this run, if any.
243
+ */
244
+ protected abortError(error: unknown, signal?: AbortSignal): AbortError;
223
245
  protected abstract parseUsage(input: unknown): TokenUsage;
224
246
  /**
225
247
  * Clear accumulated usage. Called at the start of every `execute()` /
@@ -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 AgentEvent_1 = require("./AgentEvent");
11
12
  /**
12
13
  * The base agent is what the other agents are inheriting from
13
14
  * Handles the BaseConfig
@@ -136,6 +137,22 @@ class BaseAgent extends events_1.default {
136
137
  clearHistory() {
137
138
  this.history.clear();
138
139
  }
140
+ /**
141
+ * Build the {@link AbortError} for a cancelled run and emit it as an
142
+ * `AgentEvent.ERROR`, so a cancellation reaches error listeners the same way
143
+ * every other failure does.
144
+ *
145
+ * Returns the error rather than throwing it, leaving the caller to report it
146
+ * to whatever visualization event is open before rethrowing.
147
+ *
148
+ * @param error What the provider threw once the signal fired.
149
+ * @param signal The signal supplied to this run, if any.
150
+ */
151
+ abortError(error, signal) {
152
+ const abortError = new AgentError_1.AbortError(`Execution of agent ${this.getName()} was aborted`, signal?.reason ?? error);
153
+ this.emit(AgentEvent_1.AgentEvent.ERROR, abortError);
154
+ return abortError;
155
+ }
139
156
  /**
140
157
  * Clear accumulated usage. Called at the start of every `execute()` /
141
158
  * `executeStream()` so `lastTokenUsage` describes a single execution.
@@ -3,6 +3,7 @@ import { Message, type ModelInfo as AnthropicModelInfo, ToolUnion, Usage } from
3
3
  import { type ToolDefinition } from "../../tools/Tool";
4
4
  import { type BuiltInTool } from "../../tools/BuiltInTool";
5
5
  import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
6
+ import { ExecuteOptions } from "../cancellation";
6
7
  import { History, MessageContent } from "../../history/History";
7
8
  import { StreamChunk } from "../openai-compatible/OpenAICompatibleAgent";
8
9
  import { ClaudeModel } from "../model-types";
@@ -151,8 +152,8 @@ export declare class ClaudeAgent extends BaseAgent {
151
152
  metadata: Record<string, string> | undefined;
152
153
  };
153
154
  protected process(_input: string): Promise<string>;
154
- execute(input: string | MessageContent[]): Promise<string>;
155
- protected handleResponse(response: Message): Promise<string>;
155
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
156
+ protected handleResponse(response: Message, options?: ExecuteOptions): Promise<string>;
156
157
  private handleToolUse;
157
158
  /**
158
159
  * Stream a response as an async generator of `StreamChunk` objects.
@@ -168,7 +169,7 @@ export declare class ClaudeAgent extends BaseAgent {
168
169
  * }
169
170
  * ```
170
171
  */
171
- executeStream(input: string | MessageContent[]): AsyncGenerator<StreamChunk>;
172
+ executeStream(input: string | MessageContent[], options?: ExecuteOptions): AsyncGenerator<StreamChunk>;
172
173
  private streamTurn;
173
174
  protected parseUsage(input: Usage): TokenUsage;
174
175
  }
@@ -4,6 +4,7 @@ exports.ClaudeAgent = void 0;
4
4
  const sdk_1 = require("@anthropic-ai/sdk");
5
5
  const BaseAgent_1 = require("../BaseAgent");
6
6
  const AgentEvent_1 = require("../AgentEvent");
7
+ const cancellation_1 = require("../cancellation");
7
8
  const AgentError_1 = require("../errors/AgentError");
8
9
  const History_1 = require("../../history/History");
9
10
  const transformers_1 = require("../../history/transformers");
@@ -143,7 +144,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
143
144
  async process(_input) {
144
145
  return "";
145
146
  }
146
- async execute(input) {
147
+ async execute(input, options) {
147
148
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
148
149
  // Reset token usage for this execution
149
150
  this.resetTokenUsage();
@@ -173,11 +174,19 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
173
174
  this.history.beginExecution();
174
175
  try {
175
176
  this.startTurnTimer();
176
- const response = (await this.client.messages.create(this.buildMessageParams()));
177
+ const response = (await this.client.messages.create(this.buildMessageParams(), { signal: options?.signal }));
177
178
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
178
- return await this.handleResponse(response);
179
+ return await this.handleResponse(response, options);
179
180
  }
180
181
  catch (error) {
182
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
183
+ const abortError = this.abortError(error, options?.signal);
184
+ if (this.vizEventId) {
185
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
186
+ this.vizEventId = undefined;
187
+ }
188
+ throw abortError;
189
+ }
181
190
  if (error instanceof sdk_1.APIError) {
182
191
  const apiError = new AgentError_1.ApiError(`Anthropic API error: ${error.message}`, error.status, error);
183
192
  this.emit(AgentEvent_1.AgentEvent.ERROR, apiError);
@@ -204,7 +213,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
204
213
  this.history.endExecution();
205
214
  }
206
215
  }
207
- async handleResponse(response) {
216
+ async handleResponse(response, options) {
208
217
  // Store token usage for metrics tracking
209
218
  const usage = this.accumulateUsage(this.parseUsage(response.usage));
210
219
  if (response.stop_reason === "max_tokens") {
@@ -253,19 +262,24 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
253
262
  }
254
263
  else if (response.stop_reason === "tool_use") {
255
264
  try {
265
+ // Stop before the assistant turn is written: nothing else would notice
266
+ // a cancellation until the next provider call, and bailing out here
267
+ // avoids both running the tools' side effects and leaving a tool_use
268
+ // in history with no tool_result to answer it.
269
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
256
270
  this.emit(AgentEvent_1.AgentEvent.TOOL_USE, response.content);
257
271
  // Add assistant response to history (normalized format)
258
272
  const assistantEntry = transformers_1.anthropicTransformer.fromProviderContent("assistant", response.content);
259
273
  this.addToHistory(assistantEntry);
260
- const toolResults = await this.handleToolUse(response.content);
274
+ const toolResults = await this.handleToolUse(response.content, options);
261
275
  // Add tool results to history (normalized format)
262
276
  this.addMessageToHistory("user", toolResults);
263
277
  // Continue conversation with tool results
264
278
  try {
265
279
  this.startTurnTimer();
266
- const newResponse = (await this.client.messages.create(this.buildMessageParams()));
280
+ const newResponse = (await this.client.messages.create(this.buildMessageParams(), { signal: options?.signal }));
267
281
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
268
- return this.handleResponse(newResponse);
282
+ return this.handleResponse(newResponse, options);
269
283
  }
270
284
  catch (error) {
271
285
  if (error instanceof sdk_1.APIError) {
@@ -292,7 +306,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
292
306
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
293
307
  throw error;
294
308
  }
295
- async handleToolUse(content) {
309
+ async handleToolUse(content, options) {
296
310
  const toolUseBlocks = content.filter((block) => block.type === "tool_use");
297
311
  if (!toolUseBlocks.length) {
298
312
  throw new AgentError_1.ExecutionError("No tool use blocks found in content");
@@ -323,7 +337,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
323
337
  ? VizReporter_1.vizReporter.toolStart(block.name, block.id, block.input, agentSource)
324
338
  : undefined;
325
339
  try {
326
- const result = await tool.execute(this.getId(), this.getName(), block.input, block.id, this.config.model, "anthropic");
340
+ const result = await tool.execute(this.getId(), this.getName(), block.input, block.id, this.config.model, "anthropic", { signal: options?.signal });
327
341
  if (vizEventId) {
328
342
  VizReporter_1.vizReporter.toolComplete(vizEventId, block.name, block.id, true, result);
329
343
  }
@@ -358,7 +372,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
358
372
  * }
359
373
  * ```
360
374
  */
361
- async *executeStream(input) {
375
+ async *executeStream(input, options) {
362
376
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
363
377
  this.resetTokenUsage();
364
378
  this.currentToolCallCount = 0;
@@ -379,9 +393,17 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
379
393
  this.history.setSessionAnchor();
380
394
  this.history.beginExecution();
381
395
  try {
382
- yield* this.streamTurn();
396
+ yield* this.streamTurn(options);
383
397
  }
384
398
  catch (error) {
399
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
400
+ const abortError = this.abortError(error, options?.signal);
401
+ if (this.vizEventId) {
402
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
403
+ this.vizEventId = undefined;
404
+ }
405
+ throw abortError;
406
+ }
385
407
  if (error instanceof sdk_1.APIError) {
386
408
  const apiError = new AgentError_1.ApiError(`Anthropic API error: ${error.message}`, error.status, error);
387
409
  this.emit(AgentEvent_1.AgentEvent.ERROR, apiError);
@@ -409,12 +431,12 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
409
431
  this.history.endExecution();
410
432
  }
411
433
  }
412
- async *streamTurn() {
434
+ async *streamTurn(options) {
413
435
  this.startTurnTimer();
414
436
  const stream = await this.client.messages.create({
415
437
  ...this.buildMessageParams(),
416
438
  stream: true,
417
- });
439
+ }, { signal: options?.signal });
418
440
  const blocks = new Map();
419
441
  let textContent = "";
420
442
  let stopReason = null;
@@ -482,6 +504,11 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
482
504
  output_tokens: outputTokens,
483
505
  total_tokens: inputTokens + outputTokens,
484
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()}`);
485
512
  if (stopReason === "max_tokens") {
486
513
  const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
487
514
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
@@ -518,13 +545,16 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
518
545
  }
519
546
  const toolUseBlocks = orderedBlocks.filter((b) => b.type === "tool_use");
520
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()}`);
521
551
  this.emit(AgentEvent_1.AgentEvent.TOOL_USE, orderedBlocks);
522
552
  this.currentToolCallCount += toolUseBlocks.length;
523
553
  const assistantEntry = transformers_1.anthropicTransformer.fromProviderContent("assistant", orderedBlocks);
524
554
  this.addToHistory(assistantEntry);
525
- const toolResults = await this.handleToolUse(orderedBlocks);
555
+ const toolResults = await this.handleToolUse(orderedBlocks, options);
526
556
  this.addMessageToHistory("user", toolResults);
527
- yield* this.streamTurn();
557
+ yield* this.streamTurn(options);
528
558
  }
529
559
  else {
530
560
  const assistantEntry = transformers_1.anthropicTransformer.fromProviderContent("assistant", orderedBlocks);
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Options accepted by every agent's `execute()` and `executeStream()`.
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * const controller = new AbortController();
7
+ * setTimeout(() => controller.abort(), 5_000);
8
+ *
9
+ * try {
10
+ * await agent.execute("Write an essay", { signal: controller.signal });
11
+ * } catch (error) {
12
+ * if (error instanceof AbortError) console.log("cancelled");
13
+ * }
14
+ * ```
15
+ */
16
+ export interface ExecuteOptions {
17
+ /**
18
+ * Cancels the run. The in-flight provider request is aborted, no further
19
+ * requests or tools are started, and the call rejects with an
20
+ * {@link AbortError}. History already written by the run is left in place, so
21
+ * a non-transient agent keeps whatever turns completed before the abort.
22
+ */
23
+ signal?: AbortSignal;
24
+ }
25
+ /** Options passed to a tool's `execute`, carrying the current run's signal. */
26
+ export interface ToolExecuteOptions {
27
+ /** The `AbortSignal` of the agent run this tool call belongs to. */
28
+ signal?: AbortSignal;
29
+ }
30
+ /**
31
+ * Whether an error represents a cancellation rather than a genuine failure.
32
+ *
33
+ * The signal is authoritative: once it has fired, whatever the provider threw
34
+ * on the way out is a consequence of the abort — including errors an agent
35
+ * wrapped in `ExecutionError` before it reached the top-level handler. The
36
+ * error's own `name` is only consulted when no aborted signal was supplied.
37
+ */
38
+ export declare function isAbortError(error: unknown, signal?: AbortSignal): boolean;
39
+ /**
40
+ * Throw an {@link AbortError} if the signal has already fired.
41
+ *
42
+ * Used at the points between provider calls where nothing would otherwise
43
+ * notice the cancellation — before starting tool execution in particular, so a
44
+ * cancelled run does not fire off side effects it will never use.
45
+ */
46
+ export declare function throwIfAborted(signal: AbortSignal | undefined, context: string): void;
47
+ /**
48
+ * Combine two optional signals into one that fires as soon as either does.
49
+ *
50
+ * Returns the other signal unchanged when only one is present, and `undefined`
51
+ * when neither is. Written by hand rather than with `AbortSignal.any`, which
52
+ * needs Node 20.
53
+ */
54
+ export declare function combineSignals(a?: AbortSignal, b?: AbortSignal): AbortSignal | undefined;
55
+ //# sourceMappingURL=cancellation.d.ts.map
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAbortError = isAbortError;
4
+ exports.throwIfAborted = throwIfAborted;
5
+ exports.combineSignals = combineSignals;
6
+ const AgentError_1 = require("./errors/AgentError");
7
+ /**
8
+ * The `name` each provider SDK gives its cancellation error. Checked as a
9
+ * fallback for the case where the caller's own signal is not the one that
10
+ * fired — a client-level timeout, for instance.
11
+ */
12
+ const ABORT_ERROR_NAMES = new Set([
13
+ "AbortError", // fetch / DOMException, ollama, and our own AbortError
14
+ "APIUserAbortError", // @anthropic-ai/sdk, openai
15
+ "RequestAbortedError", // @mistralai/mistralai
16
+ "GoogleGenerativeAIAbortError", // @google/generative-ai
17
+ ]);
18
+ /**
19
+ * Whether an error represents a cancellation rather than a genuine failure.
20
+ *
21
+ * The signal is authoritative: once it has fired, whatever the provider threw
22
+ * on the way out is a consequence of the abort — including errors an agent
23
+ * wrapped in `ExecutionError` before it reached the top-level handler. The
24
+ * error's own `name` is only consulted when no aborted signal was supplied.
25
+ */
26
+ function isAbortError(error, signal) {
27
+ if (signal?.aborted)
28
+ return true;
29
+ const name = error?.name;
30
+ return typeof name === "string" && ABORT_ERROR_NAMES.has(name);
31
+ }
32
+ /**
33
+ * Throw an {@link AbortError} if the signal has already fired.
34
+ *
35
+ * Used at the points between provider calls where nothing would otherwise
36
+ * notice the cancellation — before starting tool execution in particular, so a
37
+ * cancelled run does not fire off side effects it will never use.
38
+ */
39
+ function throwIfAborted(signal, context) {
40
+ if (!signal?.aborted)
41
+ return;
42
+ throw new AgentError_1.AbortError(`${context} was aborted`, signal.reason);
43
+ }
44
+ /**
45
+ * Combine two optional signals into one that fires as soon as either does.
46
+ *
47
+ * Returns the other signal unchanged when only one is present, and `undefined`
48
+ * when neither is. Written by hand rather than with `AbortSignal.any`, which
49
+ * needs Node 20.
50
+ */
51
+ function combineSignals(a, b) {
52
+ if (!a)
53
+ return b;
54
+ if (!b)
55
+ return a;
56
+ if (a.aborted)
57
+ return a;
58
+ if (b.aborted)
59
+ return b;
60
+ const controller = new AbortController();
61
+ const abort = (source) => () => controller.abort(source.reason);
62
+ const onA = abort(a);
63
+ const onB = abort(b);
64
+ a.addEventListener("abort", onA, { once: true });
65
+ b.addEventListener("abort", onB, { once: true });
66
+ controller.signal.addEventListener("abort", () => {
67
+ a.removeEventListener("abort", onA);
68
+ b.removeEventListener("abort", onB);
69
+ }, { once: true });
70
+ return controller.signal;
71
+ }
72
+ //# sourceMappingURL=cancellation.js.map
@@ -36,6 +36,22 @@ export declare class MaxRetriesExceededError extends AgentError {
36
36
  maxRetries: number;
37
37
  constructor(message: string, maxRetries: number);
38
38
  }
39
+ /**
40
+ * Error thrown when an execution is cancelled through the `AbortSignal` passed
41
+ * to `execute()` / `executeStream()`.
42
+ *
43
+ * `name` is `"AbortError"`, matching the platform convention, so callers that
44
+ * already check `err.name === "AbortError"` keep working.
45
+ */
46
+ export declare class AbortError extends AgentError {
47
+ reason?: unknown | undefined;
48
+ /**
49
+ * @param message Error message
50
+ * @param reason The signal's `reason`, or the underlying provider error when
51
+ * the signal did not carry one.
52
+ */
53
+ constructor(message: string, reason?: unknown | undefined);
54
+ }
39
55
  /**
40
56
  * Error thrown when tool execution fails
41
57
  */
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ToolExecutionError = exports.MaxRetriesExceededError = exports.MaxTokensExceededError = exports.ApiError = exports.ExecutionError = exports.AgentError = void 0;
3
+ exports.ToolExecutionError = exports.AbortError = exports.MaxRetriesExceededError = exports.MaxTokensExceededError = exports.ApiError = exports.ExecutionError = exports.AgentError = void 0;
4
4
  /**
5
5
  * Base error class for all agent-related errors
6
6
  */
@@ -59,6 +59,26 @@ class MaxRetriesExceededError extends AgentError {
59
59
  }
60
60
  }
61
61
  exports.MaxRetriesExceededError = MaxRetriesExceededError;
62
+ /**
63
+ * Error thrown when an execution is cancelled through the `AbortSignal` passed
64
+ * to `execute()` / `executeStream()`.
65
+ *
66
+ * `name` is `"AbortError"`, matching the platform convention, so callers that
67
+ * already check `err.name === "AbortError"` keep working.
68
+ */
69
+ class AbortError extends AgentError {
70
+ /**
71
+ * @param message Error message
72
+ * @param reason The signal's `reason`, or the underlying provider error when
73
+ * the signal did not carry one.
74
+ */
75
+ constructor(message, reason) {
76
+ super(message);
77
+ this.reason = reason;
78
+ this.name = "AbortError";
79
+ }
80
+ }
81
+ exports.AbortError = AbortError;
62
82
  /**
63
83
  * Error thrown when tool execution fails
64
84
  */
@@ -1,5 +1,6 @@
1
1
  import { FunctionDeclarationsTool, GenerateContentResult, Schema } from "@google/generative-ai";
2
2
  import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
3
+ import { ExecuteOptions } from "../cancellation";
3
4
  import { History, MessageContent } from "../../history/History";
4
5
  import { GeminiModel } from "../model-types";
5
6
  /**
@@ -123,8 +124,8 @@ export declare class GeminiAgent extends BaseAgent {
123
124
  */
124
125
  private mapJsonSchemaTypeToGemini;
125
126
  protected process(_input: string): Promise<string>;
126
- execute(input: string | MessageContent[]): Promise<string>;
127
- protected handleResponse(response: GenerateContentResult): Promise<string>;
127
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
128
+ protected handleResponse(response: GenerateContentResult, options?: ExecuteOptions): Promise<string>;
128
129
  private handleFunctionCalls;
129
130
  protected parseUsage(input: {
130
131
  promptTokenCount?: number;
@@ -4,6 +4,7 @@ exports.GeminiAgent = exports.GEMINI_RETIRED_MODELS = void 0;
4
4
  const generative_ai_1 = require("@google/generative-ai");
5
5
  const BaseAgent_1 = require("../BaseAgent");
6
6
  const AgentEvent_1 = require("../AgentEvent");
7
+ const cancellation_1 = require("../cancellation");
7
8
  const AgentError_1 = require("../errors/AgentError");
8
9
  const transformers_1 = require("../../history/transformers");
9
10
  const VizReporter_1 = require("../../viz/VizReporter");
@@ -251,7 +252,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
251
252
  async process(_input) {
252
253
  return "";
253
254
  }
254
- async execute(input) {
255
+ async execute(input, options) {
255
256
  this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
256
257
  // Reset token usage for this execution
257
258
  this.resetTokenUsage();
@@ -297,11 +298,23 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
297
298
  responseMimeType: this.config.responseMimeType,
298
299
  responseSchema: this.config.responseSchema,
299
300
  },
300
- });
301
+ },
302
+ // Note: an abort only stops the client from waiting — Google still
303
+ // runs and bills the request. Merged over the model's own request
304
+ // options, so `customHeaders` set at construction survive.
305
+ { signal: options?.signal });
301
306
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
302
- return await this.handleResponse(response);
307
+ return await this.handleResponse(response, options);
303
308
  }
304
309
  catch (error) {
310
+ if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
311
+ const abortError = this.abortError(error, options?.signal);
312
+ if (this.vizEventId) {
313
+ VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
314
+ this.vizEventId = undefined;
315
+ }
316
+ throw abortError;
317
+ }
305
318
  const err = error;
306
319
  if (err.status) {
307
320
  const apiError = new AgentError_1.ApiError(`Gemini API error: ${err.message || "Unknown error"}`, err.status, error);
@@ -328,7 +341,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
328
341
  this.history.endExecution();
329
342
  }
330
343
  }
331
- async handleResponse(response) {
344
+ async handleResponse(response, options) {
332
345
  const result = response.response;
333
346
  // Parse and track usage
334
347
  if (result.usageMetadata) {
@@ -377,11 +390,16 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
377
390
  }
378
391
  // Handle function calls
379
392
  try {
393
+ // Stop before the assistant turn is written: nothing else would notice a
394
+ // cancellation until the next provider call, and bailing out here avoids
395
+ // both running the tools' side effects and leaving a functionCall in
396
+ // history with no functionResponse to answer it.
397
+ (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
380
398
  this.emit(AgentEvent_1.AgentEvent.TOOL_USE, functionCalls);
381
399
  // Add assistant response with function calls to history (normalized)
382
400
  const assistantEntry = transformers_1.geminiTransformer.fromProviderContent("assistant", parts);
383
401
  this.addToHistory(assistantEntry);
384
- const toolResults = await this.handleFunctionCalls(functionCalls);
402
+ const toolResults = await this.handleFunctionCalls(functionCalls, options);
385
403
  // Add tool results to history (normalized)
386
404
  for (const tr of toolResults) {
387
405
  const resultEntry = transformers_1.geminiTransformer.toolResultEntry(tr.name, tr.response);
@@ -407,9 +425,9 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
407
425
  responseMimeType: this.config.responseMimeType,
408
426
  responseSchema: this.config.responseSchema,
409
427
  },
410
- });
428
+ }, { signal: options?.signal });
411
429
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
412
- return this.handleResponse(newResponse);
430
+ return this.handleResponse(newResponse, options);
413
431
  }
414
432
  catch (error) {
415
433
  const err = error;
@@ -438,7 +456,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
438
456
  throw toolError;
439
457
  }
440
458
  }
441
- async handleFunctionCalls(functionCalls) {
459
+ async handleFunctionCalls(functionCalls, options) {
442
460
  if (!functionCalls.length) {
443
461
  throw new AgentError_1.ExecutionError("No function calls found in response");
444
462
  }
@@ -462,7 +480,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
462
480
  try {
463
481
  const args = (fc.args || {});
464
482
  const result = await tool.execute(this.getId(), this.getName(), args, toolName, // Gemini uses function name as ID
465
- this.config.model, "gemini");
483
+ this.config.model, "gemini", { signal: options?.signal });
466
484
  return {
467
485
  name: toolName,
468
486
  response: JSON.stringify(result),
@@ -1,4 +1,5 @@
1
1
  import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
2
+ import { ExecuteOptions } from "../cancellation";
2
3
  import { History, MessageContent } from "../../history/History";
3
4
  import { ChatCompletionResponse, ModelList, Tool, UsageInfo } from "@mistralai/mistralai/models/components";
4
5
  import { MistralModel } from "../model-types";
@@ -66,8 +67,8 @@ export declare class MistralAgent extends BaseAgent {
66
67
  listModels(): Promise<ModelInfo<MistralModelCard>[]>;
67
68
  protected getToolDefinitions(): Tool[];
68
69
  protected process(_input: string): Promise<string>;
69
- execute(input: string | MessageContent[]): Promise<string>;
70
- protected handleResponse(response: ChatCompletionResponse): Promise<string>;
70
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
71
+ protected handleResponse(response: ChatCompletionResponse, options?: ExecuteOptions): Promise<string>;
71
72
  private handleToolCalls;
72
73
  protected parseUsage(input: UsageInfo): TokenUsage;
73
74
  }