@agentionai/agents 1.6.0 → 1.8.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.
Files changed (41) hide show
  1. package/README.md +4 -2
  2. package/dist/agents/Agent.d.ts +9 -2
  3. package/dist/agents/Agent.js +4 -0
  4. package/dist/agents/AgentConfig.d.ts +76 -2
  5. package/dist/agents/BaseAgent.d.ts +24 -2
  6. package/dist/agents/BaseAgent.js +17 -0
  7. package/dist/agents/anthropic/ClaudeAgent.d.ts +4 -3
  8. package/dist/agents/anthropic/ClaudeAgent.js +47 -17
  9. package/dist/agents/cancellation.d.ts +55 -0
  10. package/dist/agents/cancellation.js +72 -0
  11. package/dist/agents/errors/AgentError.d.ts +50 -2
  12. package/dist/agents/errors/AgentError.js +57 -1
  13. package/dist/agents/google/GeminiAgent.d.ts +3 -2
  14. package/dist/agents/google/GeminiAgent.js +34 -11
  15. package/dist/agents/mistral/MistralAgent.d.ts +3 -2
  16. package/dist/agents/mistral/MistralAgent.js +33 -13
  17. package/dist/agents/ollama/OllamaAgent.d.ts +17 -3
  18. package/dist/agents/ollama/OllamaAgent.js +69 -19
  19. package/dist/agents/openai/OpenAiAgent.d.ts +4 -3
  20. package/dist/agents/openai/OpenAiAgent.js +52 -17
  21. package/dist/agents/openai-compatible/OpenAICompatibleAgent.d.ts +4 -3
  22. package/dist/agents/openai-compatible/OpenAICompatibleAgent.js +48 -19
  23. package/dist/agents/openrouter/OpenRouterAgent.d.ts +234 -0
  24. package/dist/agents/openrouter/OpenRouterAgent.js +711 -0
  25. package/dist/agents/openrouter/types.d.ts +164 -0
  26. package/dist/agents/openrouter/types.js +15 -0
  27. package/dist/core.d.ts +1 -0
  28. package/dist/core.js +1 -0
  29. package/dist/history/transformers.d.ts +80 -0
  30. package/dist/history/transformers.js +156 -1
  31. package/dist/history/types.d.ts +22 -2
  32. package/dist/history/types.js +13 -2
  33. package/dist/index.d.ts +5 -1
  34. package/dist/index.js +5 -1
  35. package/dist/mcp/MCPClient.js +4 -2
  36. package/dist/openrouter.d.ts +6 -0
  37. package/dist/openrouter.js +24 -0
  38. package/dist/tools/Tool.d.ts +13 -3
  39. package/dist/tools/Tool.js +18 -4
  40. package/dist/viz/types.d.ts +1 -1
  41. package/package.json +10 -1
package/README.md CHANGED
@@ -27,6 +27,7 @@ Get an API key from your chosen provider:
27
27
  - **OpenAI**: [platform.openai.com](https://platform.openai.com/api-keys)
28
28
  - **Gemini**: [aistudio.google.com](https://aistudio.google.com/app/apikey)
29
29
  - **Mistral**: [console.mistral.ai](https://console.mistral.ai/)
30
+ - **OpenRouter**: [openrouter.ai/keys](https://openrouter.ai/keys) — one key, dozens of upstream providers
30
31
  - **Ollama** / **llama.cpp**: no API key needed — run models locally (see [Agents guide](https://docs.agention.ai/guide/agents))
31
32
 
32
33
  Set it as an environment variable:
@@ -62,6 +63,7 @@ import { ClaudeAgent } from '@agentionai/agents/claude'; // Requires @anthro
62
63
  import { OpenAiAgent } from '@agentionai/agents/openai'; // Requires openai
63
64
  import { GeminiAgent } from '@agentionai/agents/gemini'; // Requires @google/generative-ai
64
65
  import { MistralAgent } from '@agentionai/agents/mistral'; // Requires @mistralai/mistralai
66
+ import { OpenRouterAgent } from '@agentionai/agents/openrouter'; // Requires @openrouter/sdk
65
67
  import { OllamaAgent } from '@agentionai/agents/ollama'; // Requires ollama (local, no API key)
66
68
  import { LlamaCppAgent } from '@agentionai/agents/llamacpp'; // Requires openai (local, no API key)
67
69
  ```
@@ -75,9 +77,9 @@ import { ClaudeAgent, OpenAiAgent } from '@agentionai/agents';
75
77
 
76
78
  ## Features
77
79
 
78
- - **Multi-Provider, No Lock-in** - Claude, OpenAI, Gemini, Mistral, plus local models via Ollama and llama.cpp—same interface. Switch models with one line. `listModels()` asks any provider what it currently offers, in one shared shape.
80
+ - **Multi-Provider, No Lock-in** - Claude, OpenAI, Gemini, Mistral, OpenRouter (one key, dozens of upstream providers), plus local models via Ollama and llama.cpp—same interface. Switch models with one line. `listModels()` asks any provider what it currently offers, in one shared shape.
79
81
  - **Composable Context Management** - Tool result masking (lossless, free) + rolling summarization (auto-firing) + sub-agent delegation (token isolation by architecture).
80
- - **Streaming** - `executeStream()` on Claude, OpenAI, and all OpenAI-compatible agents. Yields `{ type: "text" | "reasoning" }` chunks; tool calls handled transparently.
82
+ - **Streaming** - `executeStream()` on Claude, OpenAI, OpenRouter, and all OpenAI-compatible agents. Yields `{ type: "text" | "reasoning" }` chunks; tool calls handled transparently.
81
83
  - **Built-In Tools** - Use provider-defined server-side tools (e.g. Anthropic's web search, bash, text editor) alongside your own.
82
84
  - **Composable, Not Magical** - Agents are objects. Pipelines are arrays. No hidden state, no surprises.
83
85
  - **Multimodal / Vision** - Send images alongside text with a unified `MessageContent[]` API across all providers.
@@ -6,6 +6,8 @@ import { GeminiAgent } from "./google/GeminiAgent";
6
6
  import { MistralAgent } from "./mistral/MistralAgent";
7
7
  import { OllamaAgent } from "./ollama/OllamaAgent";
8
8
  import { LlamaCppAgent } from "./llamacpp/LlamaCppAgent";
9
+ import { OpenRouterAgent } from "./openrouter/OpenRouterAgent";
10
+ import type { OpenRouterSpecificConfig } from "./AgentConfig";
9
11
  import { ClaudeModel, OpenAIModel, GeminiModel, MistralModel, OllamaModel, LlamaCppModel } from "./model-types";
10
12
  type ClaudeAgentConfig = Omit<BaseAgentConfig, "vendor" | "model"> & {
11
13
  vendor: "anthropic";
@@ -33,9 +35,14 @@ type LlamaCppAgentConfig = Omit<BaseAgentConfig, "vendor" | "model"> & {
33
35
  model?: LlamaCppModel;
34
36
  baseURL?: string;
35
37
  };
36
- type AgentConfig = ClaudeAgentConfig | OpenAIAgentConfig | GeminiAgentConfig | MistralAgentConfig | OllamaAgentConfig | LlamaCppAgentConfig;
38
+ type OpenRouterAgentConfig = Omit<BaseAgentConfig, "vendor" | "model"> & OpenRouterSpecificConfig & {
39
+ vendor: "openrouter";
40
+ model?: string;
41
+ baseURL?: string;
42
+ };
43
+ type AgentConfig = ClaudeAgentConfig | OpenAIAgentConfig | GeminiAgentConfig | MistralAgentConfig | OllamaAgentConfig | LlamaCppAgentConfig | OpenRouterAgentConfig;
37
44
  export declare class Agent {
38
- static create(config: AgentConfig, history?: History): ClaudeAgent | GeminiAgent | MistralAgent | OllamaAgent | LlamaCppAgent | 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">;
39
46
  }
40
47
  export {};
41
48
  //# sourceMappingURL=Agent.d.ts.map
@@ -7,6 +7,7 @@ const GeminiAgent_1 = require("./google/GeminiAgent");
7
7
  const MistralAgent_1 = require("./mistral/MistralAgent");
8
8
  const OllamaAgent_1 = require("./ollama/OllamaAgent");
9
9
  const LlamaCppAgent_1 = require("./llamacpp/LlamaCppAgent");
10
+ const OpenRouterAgent_1 = require("./openrouter/OpenRouterAgent");
10
11
  class Agent {
11
12
  static create(config, history) {
12
13
  if (config.vendor === "anthropic") {
@@ -27,6 +28,9 @@ class Agent {
27
28
  else if (config.vendor === "llamacpp") {
28
29
  return new LlamaCppAgent_1.LlamaCppAgent(config, history);
29
30
  }
31
+ else if (config.vendor === "openrouter") {
32
+ return new OpenRouterAgent_1.OpenRouterAgent(config, history);
33
+ }
30
34
  else {
31
35
  throw new Error("No vendor defined");
32
36
  }
@@ -2,8 +2,9 @@ import { Tool } from "../tools/Tool";
2
2
  import { BuiltInTool } from "../tools/BuiltInTool";
3
3
  import { BaseAgent } from "./BaseAgent";
4
4
  import type { ReasoningEffort } from "./model-types";
5
+ import type { OpenRouterProviderPreferences, OpenRouterReasoningConfig, OpenRouterRetryConfig } from "./openrouter/types";
5
6
  /** Supported LLM vendors */
6
- export type AgentVendor = "openai" | "anthropic" | "mistral" | "gemini" | "ollama" | "llamacpp";
7
+ export type AgentVendor = "openai" | "anthropic" | "mistral" | "gemini" | "ollama" | "llamacpp" | "openrouter";
7
8
  /**
8
9
  * Common configuration shared by all agents
9
10
  */
@@ -163,6 +164,76 @@ export interface LlamaCppSpecificConfig {
163
164
  /** Base URL of the llama.cpp server's OpenAI-compatible API (default: `http://localhost:8080/v1`) */
164
165
  baseURL?: string;
165
166
  }
167
+ /** Vendor-specific configuration for Cerebras */
168
+ export interface CerebrasSpecificConfig {
169
+ /** Cerebras OpenAI-compatible API base URL. */
170
+ baseURL?: string;
171
+ }
172
+ /**
173
+ * Vendor-specific configuration for OpenRouter
174
+ *
175
+ * @see https://openrouter.ai/docs/api-reference/chat/send-a-chat-completion-request
176
+ */
177
+ export interface OpenRouterSpecificConfig {
178
+ /**
179
+ * Fallback models, tried in order when the primary `model` cannot serve the
180
+ * request — including when it is rate limited.
181
+ *
182
+ * This is the one throttling mitigation that works against a `:free` model's
183
+ * daily quota, which no amount of client-side backoff will wait out. Put a
184
+ * paid model last if you want the run to finish regardless.
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * model: "deepseek/deepseek-chat-v3:free",
189
+ * vendorConfig: { openrouter: { models: ["qwen/qwen3-235b-a22b", "openai/gpt-5.6"] } }
190
+ * ```
191
+ */
192
+ models?: string[];
193
+ /** Where OpenRouter may route the request. */
194
+ provider?: OpenRouterProviderPreferences;
195
+ /**
196
+ * Retry policy for the chat request.
197
+ *
198
+ * Overriding the SDK's own default is deliberate. `@openrouter/sdk` retries
199
+ * only `5XX` on this endpoint, so a 429 fails on the first response even
200
+ * though its backoff already knows how to honour `Retry-After`; and its
201
+ * default `maxElapsedTime` is an hour. The agent instead retries
202
+ * `429`/`408`/`409`/`5XX` with a two-minute ceiling. Pass
203
+ * `{ strategy: "none" }` to opt out entirely and handle 429s yourself.
204
+ */
205
+ retry?: OpenRouterRetryConfig;
206
+ /**
207
+ * HTTP status codes the retry policy applies to. Accepts exact codes
208
+ * (`"429"`) and families (`"5XX"`). Defaults to
209
+ * `["408", "409", "429", "5XX"]`.
210
+ */
211
+ retryCodes?: string[];
212
+ /** Reasoning configuration for models that support it. */
213
+ reasoning?: OpenRouterReasoningConfig;
214
+ /**
215
+ * OpenRouter plugins to enable — web search, file parsing, context
216
+ * compression, moderation. Passed through untouched; see
217
+ * https://openrouter.ai/docs/guides/features/plugins for the shapes.
218
+ */
219
+ plugins?: unknown[];
220
+ /**
221
+ * Sticky routing key. Requests sharing one are pinned to the same upstream
222
+ * provider, which is what makes prompt caching hit across a conversation.
223
+ * Also groups the requests in OpenRouter's observability views.
224
+ */
225
+ sessionId?: string;
226
+ /** Stable per-end-user identifier used for abuse isolation. Never forwarded raw. */
227
+ user?: string;
228
+ /** Processing tier; `"fast"` is an accepted alias for `"priority"`. */
229
+ serviceTier?: string;
230
+ /** Sent as `HTTP-Referer` — how OpenRouter attributes traffic to your app. */
231
+ httpReferer?: string;
232
+ /** Sent as `X-Title` — the app name shown on OpenRouter's leaderboards. */
233
+ appTitle?: string;
234
+ /** Disable parallel tool calling (sends `parallel_tool_calls: false`). */
235
+ disableParallelToolUse?: boolean;
236
+ }
166
237
  /**
167
238
  * Generic vendor-specific configuration container
168
239
  * This allows any vendor to add custom config without modifying base types
@@ -174,6 +245,7 @@ export interface VendorSpecificConfig {
174
245
  gemini?: GeminiSpecificConfig;
175
246
  ollama?: OllamaSpecificConfig;
176
247
  llamacpp?: LlamaCppSpecificConfig;
248
+ openrouter?: OpenRouterSpecificConfig;
177
249
  }
178
250
  /**
179
251
  * Complete agent configuration with vendor-specific extensions
@@ -217,10 +289,12 @@ export type TypedAgentConfig<V extends AgentVendor> = CommonAgentConfig & {
217
289
  ollama?: OllamaSpecificConfig;
218
290
  } : V extends "llamacpp" ? {
219
291
  llamacpp?: LlamaCppSpecificConfig;
292
+ } : V extends "openrouter" ? {
293
+ openrouter?: OpenRouterSpecificConfig;
220
294
  } : never;
221
295
  };
222
296
  /**
223
297
  * Helper type to extract vendor-specific config for a given vendor
224
298
  */
225
- export type VendorConfigFor<V extends AgentVendor> = V extends "anthropic" ? ClaudeSpecificConfig : V extends "openai" ? OpenAISpecificConfig : V extends "mistral" ? MistralSpecificConfig : V extends "gemini" ? GeminiSpecificConfig : V extends "ollama" ? OllamaSpecificConfig : V extends "llamacpp" ? LlamaCppSpecificConfig : never;
299
+ export type VendorConfigFor<V extends AgentVendor> = V extends "anthropic" ? ClaudeSpecificConfig : V extends "openai" ? OpenAISpecificConfig : V extends "mistral" ? MistralSpecificConfig : V extends "gemini" ? GeminiSpecificConfig : V extends "ollama" ? OllamaSpecificConfig : V extends "llamacpp" ? LlamaCppSpecificConfig : V extends "openrouter" ? OpenRouterSpecificConfig : never;
226
300
  //# sourceMappingURL=AgentConfig.d.ts.map
@@ -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,11 +213,11 @@ 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") {
211
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
220
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
212
221
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
213
222
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
214
223
  // Report error to viz
@@ -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,8 +504,13 @@ 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
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
513
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
487
514
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
488
515
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
489
516
  if (this.vizEventId) {
@@ -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