@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.
- package/README.md +4 -2
- package/dist/agents/Agent.d.ts +9 -2
- package/dist/agents/Agent.js +4 -0
- package/dist/agents/AgentConfig.d.ts +76 -2
- package/dist/agents/BaseAgent.d.ts +24 -2
- package/dist/agents/BaseAgent.js +17 -0
- package/dist/agents/anthropic/ClaudeAgent.d.ts +4 -3
- package/dist/agents/anthropic/ClaudeAgent.js +47 -17
- package/dist/agents/cancellation.d.ts +55 -0
- package/dist/agents/cancellation.js +72 -0
- package/dist/agents/errors/AgentError.d.ts +50 -2
- package/dist/agents/errors/AgentError.js +57 -1
- package/dist/agents/google/GeminiAgent.d.ts +3 -2
- package/dist/agents/google/GeminiAgent.js +34 -11
- package/dist/agents/mistral/MistralAgent.d.ts +3 -2
- package/dist/agents/mistral/MistralAgent.js +33 -13
- package/dist/agents/ollama/OllamaAgent.d.ts +17 -3
- package/dist/agents/ollama/OllamaAgent.js +69 -19
- package/dist/agents/openai/OpenAiAgent.d.ts +4 -3
- package/dist/agents/openai/OpenAiAgent.js +52 -17
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.d.ts +4 -3
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.js +48 -19
- package/dist/agents/openrouter/OpenRouterAgent.d.ts +234 -0
- package/dist/agents/openrouter/OpenRouterAgent.js +711 -0
- package/dist/agents/openrouter/types.d.ts +164 -0
- package/dist/agents/openrouter/types.js +15 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +1 -0
- package/dist/history/transformers.d.ts +80 -0
- package/dist/history/transformers.js +156 -1
- package/dist/history/types.d.ts +22 -2
- package/dist/history/types.js +13 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/mcp/MCPClient.js +4 -2
- package/dist/openrouter.d.ts +6 -0
- package/dist/openrouter.js +24 -0
- package/dist/tools/Tool.d.ts +13 -3
- package/dist/tools/Tool.js +18 -4
- package/dist/viz/types.d.ts +1 -1
- 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.
|
package/dist/agents/Agent.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/dist/agents/Agent.js
CHANGED
|
@@ -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
|
-
|
|
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()` /
|
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 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
|
|
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
|
|
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
|