@agentionai/agents 1.7.0-beta.0 → 1.8.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 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
@@ -217,7 +217,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
217
217
  // Store token usage for metrics tracking
218
218
  const usage = this.accumulateUsage(this.parseUsage(response.usage));
219
219
  if (response.stop_reason === "max_tokens") {
220
- 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);
221
221
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
222
222
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
223
223
  // Report error to viz
@@ -510,7 +510,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
510
510
  // Checked after accumulateUsage() so the tokens already spent are reported.
511
511
  (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
512
512
  if (stopReason === "max_tokens") {
513
- 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);
514
514
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
515
515
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
516
516
  if (this.vizEventId) {
@@ -22,12 +22,44 @@ export declare class ApiError extends AgentError {
22
22
  response?: any | undefined;
23
23
  constructor(message: string, statusCode?: number | undefined, response?: any | undefined);
24
24
  }
25
+ /**
26
+ * Error thrown when the provider rate limits a request and the agent's retry
27
+ * policy did not manage to get past it.
28
+ *
29
+ * Extends {@link ApiError}, so existing `catch (e) { if (e instanceof ApiError) }`
30
+ * handling keeps working; the extra fields carry what the provider said about
31
+ * when to come back.
32
+ */
33
+ export declare class RateLimitError extends ApiError {
34
+ retryAfterMs?: number | undefined;
35
+ limit?: number | undefined;
36
+ remaining?: number | undefined;
37
+ resetAt?: Date | undefined;
38
+ /**
39
+ * @param message Error message
40
+ * @param retryAfterMs How long the provider asked you to wait, in
41
+ * milliseconds, from its `Retry-After` header. Undefined
42
+ * when it sent no hint.
43
+ * @param limit Requests permitted in the window (`X-RateLimit-Limit`).
44
+ * @param remaining Requests left in the window (`X-RateLimit-Remaining`).
45
+ * @param resetAt When the window resets (`X-RateLimit-Reset`).
46
+ * @param response The underlying provider error.
47
+ */
48
+ constructor(message: string, retryAfterMs?: number | undefined, limit?: number | undefined, remaining?: number | undefined, resetAt?: Date | undefined, response?: unknown);
49
+ }
25
50
  /**
26
51
  * Error thrown when maximum token limit is exceeded
27
52
  */
28
53
  export declare class MaxTokensExceededError extends AgentError {
29
- tokenLimit: number;
30
- constructor(message: string, tokenLimit: number);
54
+ tokenLimit?: number | undefined;
55
+ /**
56
+ * @param message Error message
57
+ * @param tokenLimit The configured `maxTokens` that was hit. Undefined when
58
+ * no limit was configured and the model's own output cap
59
+ * is what ended the response — reporting a number the
60
+ * caller never set would just be misleading.
61
+ */
62
+ constructor(message: string, tokenLimit?: number | undefined);
31
63
  }
32
64
  /**
33
65
  * Error thrown when maximum retries are exceeded
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ToolExecutionError = exports.AbortError = exports.MaxRetriesExceededError = exports.MaxTokensExceededError = exports.ApiError = exports.ExecutionError = exports.AgentError = void 0;
3
+ exports.ToolExecutionError = exports.AbortError = exports.MaxRetriesExceededError = exports.MaxTokensExceededError = exports.RateLimitError = exports.ApiError = exports.ExecutionError = exports.AgentError = void 0;
4
4
  /**
5
5
  * Base error class for all agent-related errors
6
6
  */
@@ -37,10 +37,46 @@ class ApiError extends AgentError {
37
37
  }
38
38
  }
39
39
  exports.ApiError = ApiError;
40
+ /**
41
+ * Error thrown when the provider rate limits a request and the agent's retry
42
+ * policy did not manage to get past it.
43
+ *
44
+ * Extends {@link ApiError}, so existing `catch (e) { if (e instanceof ApiError) }`
45
+ * handling keeps working; the extra fields carry what the provider said about
46
+ * when to come back.
47
+ */
48
+ class RateLimitError extends ApiError {
49
+ /**
50
+ * @param message Error message
51
+ * @param retryAfterMs How long the provider asked you to wait, in
52
+ * milliseconds, from its `Retry-After` header. Undefined
53
+ * when it sent no hint.
54
+ * @param limit Requests permitted in the window (`X-RateLimit-Limit`).
55
+ * @param remaining Requests left in the window (`X-RateLimit-Remaining`).
56
+ * @param resetAt When the window resets (`X-RateLimit-Reset`).
57
+ * @param response The underlying provider error.
58
+ */
59
+ constructor(message, retryAfterMs, limit, remaining, resetAt, response) {
60
+ super(message, 429, response);
61
+ this.retryAfterMs = retryAfterMs;
62
+ this.limit = limit;
63
+ this.remaining = remaining;
64
+ this.resetAt = resetAt;
65
+ this.name = "RateLimitError";
66
+ }
67
+ }
68
+ exports.RateLimitError = RateLimitError;
40
69
  /**
41
70
  * Error thrown when maximum token limit is exceeded
42
71
  */
43
72
  class MaxTokensExceededError extends AgentError {
73
+ /**
74
+ * @param message Error message
75
+ * @param tokenLimit The configured `maxTokens` that was hit. Undefined when
76
+ * no limit was configured and the model's own output cap
77
+ * is what ended the response — reporting a number the
78
+ * caller never set would just be misleading.
79
+ */
44
80
  constructor(message, tokenLimit) {
45
81
  super(message);
46
82
  this.tokenLimit = tokenLimit;
@@ -67,7 +67,12 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
67
67
  const responseSchema = config.responseSchema ?? vendorConfig.responseSchema;
68
68
  this.config = {
69
69
  model: config.model || "gemini-flash-latest",
70
- maxTokens: config.maxTokens || 1024,
70
+ // No default: `maxOutputTokens` is optional on Gemini, and omitting it
71
+ // lets the model use its full output budget. A default here silently
72
+ // truncated every response — and on thinking models it was worse, since
73
+ // thoughts count against the same budget and a small cap could be spent
74
+ // entirely on them.
75
+ maxTokens: config.maxTokens,
71
76
  apiKey: config.apiKey,
72
77
  temperature: config.temperature,
73
78
  topP: config.topP,
@@ -355,7 +360,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
355
360
  throw error;
356
361
  }
357
362
  if (candidate.finishReason === "MAX_TOKENS") {
358
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
363
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
359
364
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
360
365
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
361
366
  // Report error to viz
@@ -68,7 +68,10 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
68
68
  const rateLimitDelay = config.rateLimitDelay ?? vendorConfig.rateLimitDelay ?? 1500;
69
69
  this.config = {
70
70
  model: config.model || "mistral-small-latest",
71
- maxTokens: config.maxTokens || 1024,
71
+ // No default: `maxTokens` is optional on Mistral's API, and omitting it
72
+ // lets the model use its full output budget. A default here silently
73
+ // truncated every response.
74
+ maxTokens: config.maxTokens,
72
75
  disableParallelToolUse,
73
76
  safePrompt,
74
77
  randomSeed,
@@ -219,7 +222,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
219
222
  // Track token usage
220
223
  const usage = this.accumulateUsage(this.parseUsage(response.usage));
221
224
  if (choice.finishReason === "length") {
222
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
225
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
223
226
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
224
227
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
225
228
  // Report error to viz
@@ -73,7 +73,13 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
73
73
  const user = config.user ?? vendorConfig.user;
74
74
  this.config = {
75
75
  model: config.model || "gpt-4.1-mini",
76
- maxTokens: config.maxTokens || 1024,
76
+ // No default. `max_output_tokens` is optional on the Responses API, and
77
+ // omitting it lets the model use its full output budget. A default here
78
+ // silently truncated every response — and on reasoning models it was
79
+ // worse than a truncation, since reasoning tokens count against the same
80
+ // budget: a small cap could be spent entirely on thinking, returning
81
+ // `status: "incomplete"` with no text at all.
82
+ maxTokens: config.maxTokens,
77
83
  disableParallelToolUse,
78
84
  disableReasoning,
79
85
  reasoningEffort,
@@ -532,7 +538,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
532
538
  }
533
539
  }
534
540
  if (event.type === "response.incomplete") {
535
- throw new AgentError_1.MaxTokensExceededError("Response incomplete: max tokens reached", this.config.maxTokens || 1024);
541
+ throw new AgentError_1.MaxTokensExceededError("Response incomplete: max tokens reached", this.config.maxTokens);
536
542
  }
537
543
  }
538
544
  // The SDK's stream iterator swallows the abort and simply stops yielding.
@@ -172,7 +172,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
172
172
  const choice = response.choices[0];
173
173
  const message = choice.message;
174
174
  if (choice.finish_reason === "length") {
175
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
175
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
176
176
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
177
177
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
178
178
  if (this.vizEventId) {
@@ -410,7 +410,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
410
410
  // complete turn — writing partial text to history and emitting DONE.
411
411
  (0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
412
412
  if (finishReason === "length") {
413
- const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
413
+ const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
414
414
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
415
415
  this.emit(AgentEvent_1.AgentEvent.ERROR, error);
416
416
  if (this.vizEventId) {
@@ -0,0 +1,234 @@
1
+ import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
2
+ import { OpenRouterSpecificConfig } from "../AgentConfig";
3
+ import { ExecuteOptions } from "../cancellation";
4
+ import { History, MessageContent } from "../../history/History";
5
+ import type { OpenRouterGenerationInfo } from "./types";
6
+ /**
7
+ * A single chunk yielded by `executeStream()`.
8
+ * - `"text"` — visible output token
9
+ * - `"reasoning"` — internal reasoning token
10
+ */
11
+ export type StreamChunk = {
12
+ type: "text" | "reasoning";
13
+ content: string;
14
+ };
15
+ /**
16
+ * Build a `beforeRequest` hook that adds custom headers to every request.
17
+ *
18
+ * `@openrouter/sdk` has no `defaultHeaders` option like the Anthropic and
19
+ * OpenAI clients, so headers are injected at the HTTP layer instead. They
20
+ * overwrite headers the SDK already set, so that `defaultHeaders` means the
21
+ * same thing on every provider — see `CommonAgentConfig.defaultHeaders`.
22
+ *
23
+ * `httpReferer` / `appTitle` are a separate OpenRouter attribution path
24
+ * (`HTTP-Referer` / `X-Title`). They are not a substitute for tracing or
25
+ * gateway headers.
26
+ */
27
+ export declare function defaultHeadersHook(headers: Record<string, string>): (request: Request) => void;
28
+ export type OpenRouterConfig = BaseAgentConfig & OpenRouterSpecificConfig & {
29
+ model?: string;
30
+ maxTokens?: number;
31
+ /** Override the API base URL — for an OpenRouter-compatible gateway. */
32
+ baseURL?: string;
33
+ /** Vendor-nested form of the same options, for `AgentConfig` compatibility. */
34
+ vendorConfig?: {
35
+ openrouter?: OpenRouterSpecificConfig;
36
+ };
37
+ };
38
+ /**
39
+ * A model as OpenRouter's `/models` endpoint reports it.
40
+ *
41
+ * Richer than any single upstream provider's listing: OpenRouter publishes
42
+ * per-token pricing, the context window, and the exact parameter names each
43
+ * model accepts.
44
+ */
45
+ export type OpenRouterModelCard = {
46
+ id: string;
47
+ canonicalSlug?: string | null;
48
+ name?: string;
49
+ created?: number;
50
+ description?: string;
51
+ contextLength?: number | null;
52
+ architecture?: {
53
+ inputModalities?: string[];
54
+ outputModalities?: string[];
55
+ tokenizer?: string;
56
+ instructType?: string | null;
57
+ };
58
+ pricing?: {
59
+ prompt?: string;
60
+ completion?: string;
61
+ request?: string;
62
+ image?: string;
63
+ webSearch?: string;
64
+ internalReasoning?: string;
65
+ };
66
+ topProvider?: {
67
+ contextLength?: number | null;
68
+ maxCompletionTokens?: number | null;
69
+ isModerated?: boolean;
70
+ };
71
+ /** Parameter names the model accepts, e.g. `"tools"`, `"reasoning"`, `"seed"`. */
72
+ supportedParameters?: string[] | null;
73
+ [key: string]: unknown;
74
+ };
75
+ /**
76
+ * Agent backed by [OpenRouter](https://openrouter.ai) via the official
77
+ * `@openrouter/sdk`, giving one API key access to models from every provider it
78
+ * fronts.
79
+ *
80
+ * Beyond what an OpenAI-compatible endpoint offers, this agent exposes
81
+ * OpenRouter's routing controls — `models` fallbacks, `provider` preferences —
82
+ * reports the credit cost of each run on {@link lastGeneration}, and round-trips
83
+ * `reasoning_details` so multi-turn tool calls work on reasoning models whose
84
+ * thinking blocks are signed.
85
+ *
86
+ * @requires @openrouter/sdk - Install as a peer dependency:
87
+ * ```bash
88
+ * npm install @openrouter/sdk
89
+ * ```
90
+ * The SDK is ESM-only, so it is loaded through a dynamic import. On CommonJS
91
+ * that needs Node 20.19+ or 22.12+, where `require()` of an ES module works.
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * const agent = new OpenRouterAgent({
96
+ * id: "router",
97
+ * name: "Router",
98
+ * description: "Answers questions",
99
+ * apiKey: process.env.OPENROUTER_API_KEY!,
100
+ * model: "anthropic/claude-opus-4-20250514",
101
+ * models: ["openai/gpt-5.6"], // used if the primary is rate limited
102
+ * provider: { sort: "throughput" },
103
+ * });
104
+ *
105
+ * const answer = await agent.execute("Explain recursion");
106
+ * console.log(agent.lastGeneration?.cost, "credits");
107
+ * ```
108
+ */
109
+ export declare class OpenRouterAgent extends BaseAgent {
110
+ protected config: OpenRouterConfig;
111
+ /**
112
+ * Cost and routing facts for the most recent `execute()` / `executeStream()`.
113
+ * Reset at the start of each run, alongside `lastTokenUsage`.
114
+ */
115
+ lastGeneration?: OpenRouterGenerationInfo;
116
+ private clientPromise?;
117
+ private vizEventId?;
118
+ private currentToolCallCount;
119
+ constructor(config: OpenRouterConfig, history?: History);
120
+ /**
121
+ * Load `@openrouter/sdk` and construct the client, once per agent.
122
+ *
123
+ * The specifier goes through a variable so TypeScript does not resolve it at
124
+ * build time, which keeps the dependency genuinely optional — the same
125
+ * approach `MCPClient` uses. The promise is memoized including its rejection,
126
+ * so a missing package reports the install hint on every call rather than
127
+ * retrying the import.
128
+ */
129
+ private getClient;
130
+ private createClient;
131
+ protected getToolDefinitions(): Array<Record<string, unknown>>;
132
+ protected process(_input: string): Promise<string>;
133
+ /**
134
+ * List the models OpenRouter offers, following pagination to the end.
135
+ *
136
+ * Fills `contextLength`, `maxOutputTokens` and `capabilities` from
137
+ * OpenRouter's own metadata: `supported_parameters` says whether a model takes
138
+ * `tools` and `reasoning`, and `architecture.input_modalities` whether it
139
+ * accepts images. Per-token pricing is on `raw.pricing`.
140
+ */
141
+ listModels(): Promise<ModelInfo<OpenRouterModelCard>[]>;
142
+ execute(input: string | MessageContent[], options?: ExecuteOptions): Promise<string>;
143
+ /**
144
+ * Stream a response as an async generator of {@link StreamChunk} objects.
145
+ *
146
+ * Tool calls are executed transparently — the generator keeps streaming after
147
+ * each tool-call round trip.
148
+ */
149
+ executeStream(input: string | MessageContent[], options?: ExecuteOptions): AsyncGenerator<StreamChunk>;
150
+ /** Shared setup for `execute()` and `executeStream()`. */
151
+ private beginRun;
152
+ /**
153
+ * Map whatever a run threw onto this library's error types, emit it, and close
154
+ * any open visualization event. Returns the error for the caller to throw.
155
+ */
156
+ private failRun;
157
+ /**
158
+ * Turn an `@openrouter/sdk` error into an {@link AgentError}.
159
+ *
160
+ * The SDK throws one class per status code, all extending `OpenRouterError`
161
+ * with `statusCode`, `headers` and `body`. Rather than importing those classes
162
+ * — which would make the optional peer dependency mandatory — this reads the
163
+ * shape structurally.
164
+ */
165
+ private mapProviderError;
166
+ /**
167
+ * Build a {@link RateLimitError} from a 429, lifting OpenRouter's rate-limit
168
+ * headers onto it. They are only present on OpenRouter's own platform limits —
169
+ * a 429 passed through from an upstream provider carries neither, which is why
170
+ * every field is optional.
171
+ */
172
+ private rateLimitError;
173
+ private closeViz;
174
+ /**
175
+ * Wrap a `ChatRequest` in the envelope `@openrouter/sdk` `chat.send()` expects.
176
+ * Passing the body bare fails Speakeasy validation (`Input validation failed`).
177
+ */
178
+ private sendRequest;
179
+ /** The `ChatRequest` body, identical for the streaming and buffered paths. */
180
+ private buildRequest;
181
+ /**
182
+ * Per-request options: the cancellation signal plus the retry policy.
183
+ *
184
+ * `retryCodes` has to be passed on every call — the SDK reads it only from the
185
+ * call options, never from the client's, so setting it once at construction
186
+ * would silently do nothing.
187
+ */
188
+ private requestOptions;
189
+ private callProvider;
190
+ protected handleResponse(response: any, options?: ExecuteOptions): Promise<string>;
191
+ private streamTurn;
192
+ private handleToolCalls;
193
+ /**
194
+ * Fold one API call's cost and routing facts into {@link lastGeneration}.
195
+ * Cost is summed — a tool loop bills once per hop — while the id and model
196
+ * describe the most recent call.
197
+ */
198
+ private recordGeneration;
199
+ protected parseUsage(response: unknown): TokenUsage;
200
+ private parseUsageObject;
201
+ private completeViz;
202
+ }
203
+ /**
204
+ * Milliseconds to wait from a `Retry-After` header.
205
+ *
206
+ * RFC 9110 allows two forms — delay-seconds (`120`) and an HTTP-date
207
+ * (`Wed, 21 Oct 2026 07:28:00 GMT`). OpenRouter sends the first; the second is
208
+ * accepted because it is legal and cheap to support. A date already in the past
209
+ * yields `0` rather than a negative wait.
210
+ *
211
+ * @returns The delay in milliseconds, or `undefined` when the header is absent
212
+ * or unparseable.
213
+ */
214
+ export declare function parseRetryAfter(raw: string | null | undefined): number | undefined;
215
+ /**
216
+ * The instant an `X-RateLimit-Reset` header points at.
217
+ *
218
+ * OpenRouter documents that the header exists but not what is in it, and the
219
+ * three encodings in common use across APIs are indistinguishable by type — so
220
+ * they are told apart by magnitude, taking "the answer is somewhere near now" as
221
+ * the tiebreaker:
222
+ *
223
+ * - below `10^9` — a duration in seconds from now (a literal epoch would be
224
+ * before 2001, which no live API means)
225
+ * - below `10^11` — Unix **seconds** (`10^11` seconds is the year 5138, so
226
+ * anything under it is a plausible timestamp and anything over it is not)
227
+ * - otherwise — Unix **milliseconds**
228
+ *
229
+ * Returns `undefined` for a missing or non-finite value, so callers see "not
230
+ * reported" rather than a date in 1970. Prefer
231
+ * {@link RateLimitError.retryAfterMs} when both are present: it is unambiguous.
232
+ */
233
+ export declare function parseResetAt(value: number | undefined): Date | undefined;
234
+ //# sourceMappingURL=OpenRouterAgent.d.ts.map