@aeye/openrouter 0.5.0 → 0.7.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 (3) hide show
  1. package/README.md +652 -643
  2. package/aeye-openrouter.md +179 -175
  3. package/package.json +71 -71
package/README.md CHANGED
@@ -1,643 +1,652 @@
1
- # @aeye/openrouter
2
-
3
- OpenRouter provider for the @aeye (AI TypeScript) framework. OpenRouter provides unified access to multiple AI providers through a single API, with automatic fallbacks, routing optimization, and competitive pricing.
4
-
5
- ## Features
6
-
7
- - **Multi-Provider Access**: Access models from OpenAI, Anthropic, Google, xAI, Meta, and more through one API
8
- - **Automatic Fallbacks**: Seamless failover to alternative providers if primary fails
9
- - **Intelligent Routing**: Automatic selection of best provider for each request
10
- - **Cost Tracking**: Built-in cost information in responses
11
- - **ZDR Support**: Zero Data Retention mode for privacy-sensitive applications
12
- - **Provider Preferences**: Control which providers to use, prefer, or avoid
13
- - **Reasoning Models**: Effort or token-budget reasoning for GPT, Claude, Gemini, Grok, and others
14
- - **Server Tools**: Web search, web fetch, and image generation run by OpenRouter
15
- - **Media**: Image generation/editing, speech, transcription, and embeddings
16
- - **Reranking, Decisions, Batches**: `/rerank` (Cohere, Voyage, Qwen, Nemotron), the alpha decisions endpoint (System One models such as Jev and Kev), and inline batches
17
- - **Plugins & Fallback Models**: PDF parsing, response healing, context compression, and model fallbacks
18
- - **Streaming**: Full streaming support across compatible models
19
- - **Extends OpenAI**: Built on the OpenAI provider for compatibility
20
-
21
- ## Installation
22
-
23
- ```bash
24
- npm install @aeye/openrouter @aeye/openai @aeye/ai @aeye/core openai zod
25
- ```
26
-
27
- Requires Node >= 22 and the `openai` SDK v7.
28
-
29
- ## Quick Start
30
-
31
- ```typescript
32
- import { OpenRouterProvider } from '@aeye/openrouter';
33
- import { AI } from '@aeye/ai';
34
-
35
- // Create provider instance
36
- const openrouter = new OpenRouterProvider({
37
- apiKey: process.env.OPENROUTER_API_KEY!,
38
- defaultParams: {
39
- siteUrl: 'https://yourapp.com',
40
- appName: 'Your App Name',
41
- },
42
- });
43
-
44
- // Use with AI instance
45
- const ai = AI.with()
46
- .providers({ openrouter })
47
- .create();
48
-
49
- // Make a request (OpenRouter picks the best provider)
50
- const response = await ai.chat.get([
51
- { role: 'user', content: 'Explain TypeScript generics' }
52
- ], {
53
- metadata: { model: 'openai/gpt-5.4-mini' }
54
- });
55
-
56
- console.log(response.content);
57
- console.log('Cost:', response.usage.cost); // Actual cost from OpenRouter
58
- ```
59
-
60
- ## Configuration
61
-
62
- ### Basic Configuration
63
-
64
- ```typescript
65
- import { OpenRouterProvider, OpenRouterConfig } from '@aeye/openrouter';
66
-
67
- const config: OpenRouterConfig = {
68
- apiKey: process.env.OPENROUTER_API_KEY!,
69
- defaultParams: {
70
- siteUrl: 'https://yourapp.com', // Your app URL (helps with rankings)
71
- appName: 'My AI App', // Your app name (shown in OpenRouter dashboard)
72
- },
73
- };
74
-
75
- const provider = new OpenRouterProvider(config);
76
- ```
77
-
78
- ### Advanced Configuration
79
-
80
- ```typescript
81
- const provider = new OpenRouterProvider({
82
- apiKey: process.env.OPENROUTER_API_KEY!,
83
- defaultParams: {
84
- siteUrl: 'https://yourapp.com',
85
- appName: 'My AI App',
86
-
87
- // Provider routing (sent as OpenRouter's `provider` object)
88
- providers: {
89
- order: ['openai', 'anthropic'], // Preferred order
90
- allowFallbacks: true, // Fall back to other providers
91
- only: ['openai', 'anthropic', 'google'], // Only use these providers
92
- ignore: ['low-quality-provider'], // Never use these providers
93
- quantizations: ['int8', 'fp16'], // Allowed quantization levels
94
- sort: { by: 'throughput', partition: 'none' }, // or 'price' | 'latency' | 'exacto'
95
- preferredMinThroughput: 50, // tokens/s, median
96
- preferredMaxLatency: 2, // seconds, median
97
- dataCollection: 'deny', // or 'allow'
98
- zdr: true, // Zero data retention endpoints only
99
- enforceDistillableText: false, // Only providers that allow training on outputs
100
- requireParameters: true, // Only providers that support every parameter
101
- maxPrice: { prompt: 10, completion: 30 },
102
- },
103
-
104
- // Models to try, in order, when the requested one fails
105
- fallbackModels: ['openai/gpt-5.4-mini', 'anthropic/claude-sonnet-4.5'],
106
-
107
- // Plugins
108
- plugins: [
109
- { id: 'file-parser', pdf: { engine: 'cloudflare-ai' } },
110
- { id: 'response-healing' },
111
- { id: 'context-compression', engine: 'middle-out' },
112
- ],
113
- },
114
- });
115
- ```
116
-
117
- `transforms` is deprecated: OpenRouter replaced transforms with plugins, and `transforms: ['middle-out']` is sent as the `context-compression` plugin. `OpenRouterConfig` has no `api` option; requests always go through Chat Completions.
118
-
119
- ## Usage Examples
120
-
121
- ### Basic Chat with Cost Tracking
122
-
123
- ```typescript
124
- const response = await ai.chat.get([
125
- { role: 'user', content: 'What is the capital of France?' }
126
- ], {
127
- metadata: { model: 'openai/gpt-5.4-nano' }
128
- });
129
-
130
- console.log(response.content);
131
- console.log('Tokens:', response.usage.totalTokens);
132
- console.log('Cost: $', response.usage.cost); // Actual cost from OpenRouter
133
- ```
134
-
135
- ### Using Multiple Models
136
-
137
- OpenRouter allows you to access models from different providers using the format `provider/model`:
138
-
139
- ```typescript
140
- // OpenAI GPT-4
141
- const gpt4 = await ai.chat.get(messages, {
142
- metadata: { model: 'openai/gpt-5.4-mini' }
143
- });
144
-
145
- // Anthropic Claude
146
- const claude = await ai.chat.get(messages, {
147
- metadata: { model: 'anthropic/claude-opus-4.5' }
148
- });
149
-
150
- // Google Gemini
151
- const gemini = await ai.chat.get(messages, {
152
- metadata: { model: 'google/gemini-pro' }
153
- });
154
-
155
- // Meta Llama
156
- const llama = await ai.chat.get(messages, {
157
- metadata: { model: 'meta-llama/llama-3-70b' }
158
- });
159
- ```
160
-
161
- ### Automatic Model Selection
162
-
163
- Let OpenRouter choose the best model based on your criteria:
164
-
165
- ```typescript
166
- const ai = AI.with()
167
- .providers({ openrouter })
168
- .create({
169
- defaultMetadata: {
170
- required: ['chat', 'streaming'],
171
- weights: {
172
- speed: 0.3,
173
- cost: 0.4,
174
- quality: 0.3,
175
- },
176
- },
177
- });
178
-
179
- // OpenRouter will select the best model matching criteria
180
- const response = await ai.chat.get([
181
- { role: 'user', content: 'Explain quantum computing' }
182
- ]);
183
- ```
184
-
185
- ### Provider Fallbacks
186
-
187
- ```typescript
188
- // Primary provider fails? OpenRouter automatically falls back
189
- const provider = new OpenRouterProvider({
190
- apiKey: process.env.OPENROUTER_API_KEY!,
191
- defaultParams: {
192
- allowFallbacks: true,
193
- providers: {
194
- order: ['openai', 'anthropic', 'google'], // Try in this order
195
- },
196
- },
197
- });
198
-
199
- // If OpenAI is down, automatically tries Anthropic, then Google
200
- const response = await ai.chat.get(messages, {
201
- metadata: { model: 'openai/gpt-5.4-mini' }
202
- });
203
- ```
204
-
205
- ### Zero Data Retention (ZDR)
206
-
207
- ```typescript
208
- // Only use ZDR-compliant models
209
- const ai = AI.with()
210
- .providers({ openrouter })
211
- .create({
212
- defaultMetadata: {
213
- required: ['chat', 'zdr'], // Require ZDR support
214
- },
215
- });
216
-
217
- // Also configure at provider level
218
- const provider = new OpenRouterProvider({
219
- apiKey: process.env.OPENROUTER_API_KEY!,
220
- defaultParams: {
221
- dataCollection: 'deny', // Ensure no data collection
222
- },
223
- });
224
- ```
225
-
226
- ### Streaming with Cost Tracking
227
-
228
- ```typescript
229
- let totalCost = 0;
230
-
231
- for await (const chunk of ai.chat.stream([
232
- { role: 'user', content: 'Write a story about a robot' }
233
- ], {
234
- metadata: { model: 'anthropic/claude-sonnet-4.5' }
235
- })) {
236
- if (chunk.content) {
237
- process.stdout.write(chunk.content);
238
- }
239
-
240
- // Cost is sent in final chunk
241
- if (chunk.usage?.cost) {
242
- totalCost = chunk.usage.cost;
243
- }
244
- }
245
-
246
- console.log(`\n\nTotal cost: $${totalCost}`);
247
- ```
248
-
249
- ### Reasoning Models
250
-
251
- ```typescript
252
- // Reason with an effort, or a token budget via reason.maxTokens (never both)
253
- const response = await ai.chat.get({
254
- messages: [{
255
- role: 'user',
256
- content: 'Solve: If a train leaves NYC at 3pm going 60mph, and another leaves Boston at 4pm going 80mph, when do they meet?'
257
- }],
258
- reason: { effort: 'high', exclude: false },
259
- }, {
260
- metadata: { model: 'openai/gpt-5.5' }
261
- });
262
-
263
- console.log('Reasoning:', response.reasoning);
264
- console.log('Answer:', response.content);
265
- console.log('Cost:', response.usage.cost);
266
- ```
267
-
268
- ### Server Tools
269
-
270
- ```typescript
271
- const response = await ai.chat.get({
272
- messages: [{ role: 'user', content: 'What did the TypeScript team ship this month?' }],
273
- hostedTools: [
274
- { type: 'web_search', maxResults: 5, allowedDomains: ['devblogs.microsoft.com'], blockedDomains: ['example.com'] },
275
- { type: 'web_fetch' },
276
- ],
277
- }, {
278
- metadata: { model: 'openai/gpt-5.4-mini' }
279
- });
280
-
281
- console.log(response.citations);
282
- console.log(response.usage?.tools); // { web_search: 1 }
283
- ```
284
-
285
- `web_search`, `web_fetch`, and `image_generation` run as `openrouter:*` server tools; `file_search`, `code_interpreter`, and `mcp` are rejected.
286
-
287
- ### Images, Speech, Transcription, Embeddings
288
-
289
- ```typescript
290
- // Images go through chat with the image modality; size becomes an aspect ratio
291
- const image = await ai.image.generate.get({ prompt: 'A lighthouse at dusk', size: '1536x1024' });
292
-
293
- // Embeddings, speech, and transcription use OpenRouter's OpenAI-compatible endpoints
294
- const { embeddings } = await ai.embed.get({ texts: ['hello'] });
295
- const transcript = await ai.transcribe.get({ audio: fs.readFileSync('note.mp3') });
296
- ```
297
-
298
- Defaults: `google/gemini-2.5-flash-image` for images, `openai/gpt-transcribe`, `openai/text-embedding-3-small`, and `openai/gpt-5.4-mini` for image analysis; speech has no default model. Image and transcription streaming fall back to the non-streaming methods.
299
-
300
- ### Reranking, Decisions, Batches
301
-
302
- ```typescript
303
- // Order documents by relevance (default cohere/rerank-v3.5)
304
- const { results } = await ai.rerank.get({ query: 'refund policy', documents, topN: 3 });
305
-
306
- // Typed answers from a decision model (default typesafe/jev-1.14)
307
- const { answers } = await ai.decide.get({
308
- input: 'I was charged twice for my order.',
309
- questions: {
310
- billing: { type: 'predicate', instructions: 'Is this a billing problem?' },
311
- team: { type: 'choice', instructions: 'Which team handles it?', choices: { payments: 'Charges and refunds', web: 'Website bugs' } },
312
- },
313
- });
314
-
315
- // Chat requests as one batch, posted inline to /batches
316
- const job = await ai.batch.submit(requests, { metadata: { model: 'openai/gpt-5.4-mini' } });
317
- const done = await ai.batch.wait(job, {});
318
- ```
319
-
320
- Rerank and decision costs come back as `usage.cost`. Decisions use OpenRouter's alpha endpoint, outside the versioned API. Change the defaults with `defaultModels.rerank` and `defaultModels.decision`.
321
-
322
- ### Function Calling
323
-
324
- ```typescript
325
- import z from 'zod';
326
-
327
- const response = await ai.chat.get({
328
- messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
329
- tools: [
330
- {
331
- name: 'get_weather',
332
- description: 'Get current weather for a location',
333
- parameters: z.object({
334
- location: z.string(),
335
- unit: z.enum(['celsius', 'fahrenheit']).optional(),
336
- }),
337
- }
338
- ],
339
- toolChoice: 'auto',
340
- }, {
341
- metadata: { model: 'openai/gpt-5.4-mini' },
342
- });
343
- ```
344
-
345
- `toolChoice: { allowed: [...], required? }` is emulated by sending only the allowed tools, because OpenRouter ignores `allowed_tools`. Free-text tools (`format`) go as one-field functions with any grammar in the description; `defer` and `async` are not sent.
346
-
347
- ### Fetching Available Models
348
-
349
- OpenRouter provides a model source that can fetch all available models:
350
-
351
- ```typescript
352
- import { OpenRouterModelSource } from '@aeye/openrouter';
353
-
354
- const source = new OpenRouterModelSource({
355
- apiKey: process.env.OPENROUTER_API_KEY,
356
- includeZDR: true, // Include ZDR information
357
- });
358
-
359
- const models = await source.fetchModels();
360
-
361
- models.forEach(model => {
362
- console.log(`${model.id}`);
363
- console.log(` Pricing: $${model.pricing.text.input}/1M input tokens`);
364
- console.log(` Context: ${model.contextWindow} tokens`);
365
- console.log(` ZDR: ${model.capabilities.has('zdr')}`);
366
- });
367
- ```
368
-
369
- ### Automatic Model Registration
370
-
371
- ```typescript
372
- // Automatically fetch and register OpenRouter models
373
- const ai = AI.with()
374
- .providers({ openrouter })
375
- .create({
376
- fetchOpenRouterModels: true, // Auto-fetch all available models
377
- });
378
-
379
- // Now all OpenRouter models are available for automatic selection
380
- const response = await ai.chat.get(messages);
381
- ```
382
-
383
- ## Configuration Options
384
-
385
- ### OpenRouterConfig
386
-
387
- Extends `OpenAIConfig` from `@aeye/openai`, minus its `api` option.
388
-
389
- | Property | Type | Required | Description |
390
- |----------|------|----------|-------------|
391
- | `apiKey` | `string` | Yes | OpenRouter API key |
392
- | `baseURL` | `string` | No | Custom base URL (defaults to https://openrouter.ai/api/v1) |
393
- | `defaultParams` | `object` | No | Default parameters for all requests |
394
- | `defaultModels` | `object` | No | Per-operation defaults; built in: `cohere/rerank-v3.5` (rerank), `typesafe/jev-1.14` (decision), plus the image, transcription, and embedding defaults above |
395
-
396
- ### defaultParams
397
-
398
- | Property | Type | Description |
399
- |----------|------|-------------|
400
- | `siteUrl` | `string` | Your app URL, sent as `HTTP-Referer` |
401
- | `appName` | `string` | Your app name, sent as `X-Title` |
402
- | `providers` | `object` | Provider routing (see below) |
403
- | `fallbackModels` | `string[]` | Models to fall back to, in order (sent as `models`) |
404
- | `plugins` | `OpenRouterPlugin[]` | `file-parser`, `response-healing`, `context-compression` |
405
- | `transforms` | `string[]` | Deprecated; `middle-out` is sent as the `context-compression` plugin |
406
-
407
- ### providers Options
408
-
409
- | Property | Type | Description |
410
- |----------|------|-------------|
411
- | `order` | `string[]` | Explicit provider order |
412
- | `allowFallbacks` | `boolean` | Fall back to other providers |
413
- | `requireParameters` | `boolean` | Only providers that support every parameter; auto-on for strict `json_schema` when unset |
414
- | `dataCollection` | `'deny' \| 'allow'` | Provider data collection |
415
- | `zdr` | `boolean` | Zero data retention endpoints only |
416
- | `enforceDistillableText` | `boolean` | Only providers that allow training on outputs |
417
- | `only` | `string[]` | Whitelist of providers |
418
- | `ignore` | `string[]` | Providers to skip |
419
- | `quantizations` | `OpenRouterQuantization[]` | Allowed quantization levels |
420
- | `sort` | `'price' \| 'throughput' \| 'latency' \| 'exacto' \| { by, partition? }` | Sort providers; `exacto` prefers the best tool-calling accuracy, `partition: 'none'` sorts across fallback models together |
421
- | `preferredMinThroughput` | `number` | Preferred median throughput floor (tokens/s) |
422
- | `preferredMaxLatency` | `number` | Preferred median latency ceiling (seconds) |
423
- | `maxPrice` | `{ prompt?, completion?, image?, audio?, request? }` | Price ceilings |
424
-
425
- ## Cost Information
426
-
427
- OpenRouter includes actual cost information in every response:
428
-
429
- ```typescript
430
- const response = await ai.chat.get(messages, {
431
- metadata: { model: 'openai/gpt-5.4-mini' }
432
- });
433
-
434
- // Cost is automatically included
435
- console.log('Input tokens:', response.usage.text.input);
436
- console.log('Output tokens:', response.usage.text.output);
437
- console.log('Cost: $', response.usage.cost); // Actual USD cost
438
-
439
- // Cost calculation is skipped when provider returns cost
440
- // No need to manually calculate costs!
441
- ```
442
-
443
- The cost is provided by OpenRouter's API in the `total_cost` field and automatically extracted into the `usage.cost` field.
444
-
445
- ## Model Format
446
-
447
- OpenRouter uses the format `provider/model-name`:
448
-
449
- - `openai/gpt-5.4-mini`
450
- - `anthropic/claude-opus-4.5`
451
- - `google/gemini-2.5-flash`
452
- - `x-ai/grok-4.3`
453
- - `meta-llama/llama-3-70b-instruct`
454
- - `mistralai/mistral-large`
455
- - `cohere/command-r-plus`
456
-
457
- See the [OpenRouter Models](https://openrouter.ai/models) page for the full list.
458
-
459
- ## Privacy & Data Retention
460
-
461
- ### Zero Data Retention (ZDR)
462
-
463
- OpenRouter supports ZDR mode where your data is not stored:
464
-
465
- ```typescript
466
- // Require ZDR-compliant models
467
- const ai = AI.with()
468
- .providers({ openrouter })
469
- .create({
470
- defaultMetadata: {
471
- required: ['chat', 'zdr'],
472
- },
473
- });
474
-
475
- // Or configure at provider level
476
- const provider = new OpenRouterProvider({
477
- apiKey: process.env.OPENROUTER_API_KEY!,
478
- defaultParams: {
479
- dataCollection: 'deny',
480
- providers: {
481
- dataCollection: 'deny',
482
- },
483
- },
484
- });
485
- ```
486
-
487
- Models with ZDR support have the `zdr` capability tag.
488
-
489
- ## Error Handling
490
-
491
- OpenRouter uses the same error types as the OpenAI provider:
492
-
493
- ```typescript
494
- import { ProviderError, RateLimitError } from '@aeye/openrouter';
495
-
496
- try {
497
- const response = await ai.chat.get(messages, {
498
- metadata: { model: 'openai/gpt-5.4-mini' }
499
- });
500
- } catch (error) {
501
- if (error instanceof RateLimitError) {
502
- console.error('Rate limit exceeded');
503
- console.log(`Retry after ${error.retryAfter} seconds`);
504
- } else if (error instanceof ProviderError) {
505
- console.error(`Provider error: ${error.message}`);
506
- }
507
- }
508
- ```
509
-
510
- ## Best Practices
511
-
512
- 1. **Set Site URL and App Name**: This helps with OpenRouter rankings and analytics
513
- ```typescript
514
- defaultParams: {
515
- siteUrl: 'https://yourapp.com',
516
- appName: 'Your App',
517
- }
518
- ```
519
-
520
- 2. **Use Cost Tracking**: Monitor costs with the built-in `usage.cost` field
521
- ```typescript
522
- console.log('Cost: $', response.usage.cost);
523
- ```
524
-
525
- 3. **Enable Fallbacks**: Use automatic fallbacks for reliability
526
- ```typescript
527
- defaultParams: {
528
- providers: {
529
- allowFallbacks: true,
530
- order: ['openai', 'anthropic', 'google'],
531
- },
532
- fallbackModels: ['openai/gpt-5.4-mini'],
533
- }
534
- ```
535
-
536
- 4. **Specify Provider Preferences**: Control which providers are used
537
- ```typescript
538
- providers: {
539
- only: ['openai', 'anthropic'],
540
- ignore: ['low-quality-provider'],
541
- }
542
- ```
543
-
544
- 5. **Use ZDR for Sensitive Data**: Enable Zero Data Retention for privacy
545
- ```typescript
546
- defaultParams: {
547
- providers: { dataCollection: 'deny', zdr: true },
548
- }
549
- ```
550
-
551
- 6. **Monitor Model Availability**: OpenRouter's model list changes frequently
552
- ```typescript
553
- fetchOpenRouterModels: true // Auto-fetch latest models
554
- ```
555
-
556
- ## API Reference
557
-
558
- ### OpenRouterProvider
559
-
560
- Extends `OpenAIProvider` from `@aeye/openai`.
561
-
562
- **Constructor**: `new OpenRouterProvider(config: OpenRouterConfig)`
563
-
564
- **Methods**:
565
- - Inherits all methods from `OpenAIProvider`
566
- - `createExecutor<TContext, TMetadata>(config?)` - Returns executor with cost tracking
567
- - `createStreamer<TContext, TMetadata>(config?)` - Returns streamer with cost tracking
568
- - `listModels(config?)` - Fetches models from OpenRouter API with ZDR info
569
-
570
- **Overridden Methods**:
571
- - `createClient(config)` - Creates OpenAI client with OpenRouter endpoint
572
- - `augmentChatRequest(params, request, config)` - Adds OpenRouter-specific params
573
- - `buildChatParams(request, run, streaming)` - Sends only the allowed tools for `{ allowed }` tool choices
574
- - `createBatch(batch, run)` / `retrieveBatch(job, run)` - Inline batches through `/batches`
575
-
576
- **Added Methods**:
577
- - `rerank(request, ctx, config?)` - `/rerank`
578
- - `decide(request, ctx, config?)` - alpha decisions endpoint
579
-
580
- ### OpenRouterModelSource
581
-
582
- Model source for fetching OpenRouter models.
583
-
584
- **Constructor**: `new OpenRouterModelSource(config?: OpenRouterSourceConfig)`
585
-
586
- **Methods**:
587
- - `fetchModels(config?): Promise<ModelInfo[]>` - Fetch all available models
588
-
589
- **Config Options**:
590
- - `apiKey?: string` - OpenRouter API key (optional for public models)
591
- - `includeZDR?: boolean` - Include ZDR information (default: true)
592
-
593
- ## Supported Features
594
-
595
- - ✅ Chat completions
596
- - ✅ Streaming
597
- - ✅ Function calling
598
- - ✅ Vision (for supported models)
599
- - ✅ Reasoning models
600
- - ✅ Cost tracking
601
- - ✅ Automatic fallbacks
602
- - ✅ Provider routing
603
- - ✅ ZDR mode
604
- - ✅ Multi-modal (for supported models)
605
- - ✅ Server tools (web search, web fetch, image generation)
606
- - ✅ Prompt caching (`cacheBreakpoint` → `cache_control`)
607
- - ✅ Image generation and editing (through chat; no streaming)
608
- - ✅ Speech synthesis
609
- - ✅ Transcription (no streaming)
610
- - ✅ Embeddings
611
- - ✅ Reranking
612
- - ✅ Decisions (alpha endpoint)
613
- - ✅ Batches
614
-
615
- ## Related Packages
616
-
617
- - **[@aeye/core](../core)**: Core @aeye framework types and interfaces
618
- - **[@aeye/ai](../ai)**: AI abstractions and utilities
619
- - **[@aeye/openai](../openai)**: OpenAI provider (base class)
620
- - **[@aeye/anthropic](../anthropic)**: Anthropic Claude provider
621
- - **[@aeye/google](../google)**: Google AI provider
622
-
623
- ## Links
624
-
625
- - [OpenRouter Website](https://openrouter.ai)
626
- - [OpenRouter Models](https://openrouter.ai/models)
627
- - [OpenRouter Documentation](https://openrouter.ai/docs)
628
- - [OpenRouter API Keys](https://openrouter.ai/keys)
629
-
630
- ## License
631
-
632
- GPL-3.0
633
-
634
- ## Contributing
635
-
636
- Contributions are welcome! Please see the main [@aeye repository](https://github.com/ClickerMonkey/aeye) for contribution guidelines.
637
-
638
- ## Support
639
-
640
- For issues and questions:
641
- - GitHub Issues: https://github.com/ClickerMonkey/aeye/issues
642
- - Documentation: https://github.com/ClickerMonkey/aeye
643
- - OpenRouter Discord: https://discord.gg/openrouter
1
+ # @aeye/openrouter
2
+
3
+ OpenRouter provider for the @aeye (AI TypeScript) framework. OpenRouter provides unified access to multiple AI providers through a single API, with automatic fallbacks, routing optimization, and competitive pricing.
4
+
5
+ ## Features
6
+
7
+ - **Multi-Provider Access**: Access models from OpenAI, Anthropic, Google, xAI, Meta, and more through one API
8
+ - **Automatic Fallbacks**: Seamless failover to alternative providers if primary fails
9
+ - **Intelligent Routing**: Automatic selection of best provider for each request
10
+ - **Cost Tracking**: Built-in cost information in responses
11
+ - **ZDR Support**: Zero Data Retention mode for privacy-sensitive applications
12
+ - **Provider Preferences**: Control which providers to use, prefer, or avoid
13
+ - **Reasoning Models**: Effort or token-budget reasoning for GPT, Claude, Gemini, Grok, and others
14
+ - **Server Tools**: Web search, web fetch, and image generation run by OpenRouter
15
+ - **Media**: Image generation/editing, speech, transcription, and embeddings
16
+ - **Reranking, Decisions, Batches**: `/rerank` (Cohere, Voyage, Qwen, Nemotron), the alpha decisions endpoint (System One models such as Jev and Kev), and inline batches
17
+ - **Plugins & Fallback Models**: PDF parsing, response healing, context compression, and model fallbacks
18
+ - **Streaming**: Full streaming support across compatible models
19
+ - **Extends OpenAI**: Built on the OpenAI provider for compatibility
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install @aeye/openrouter @aeye/openai @aeye/ai @aeye/core openai zod
25
+ ```
26
+
27
+ Requires Node >= 22 and the `openai` SDK v7.
28
+
29
+ ## Quick Start
30
+
31
+ ```typescript
32
+ import { OpenRouterProvider } from '@aeye/openrouter';
33
+ import { AI } from '@aeye/ai';
34
+
35
+ // Create provider instance
36
+ const openrouter = new OpenRouterProvider({
37
+ apiKey: process.env.OPENROUTER_API_KEY!,
38
+ defaultParams: {
39
+ siteUrl: 'https://yourapp.com',
40
+ appName: 'Your App Name',
41
+ },
42
+ });
43
+
44
+ // Use with AI instance
45
+ const ai = AI.with()
46
+ .providers({ openrouter })
47
+ .create();
48
+
49
+ // Make a request (OpenRouter picks the best provider)
50
+ const response = await ai.chat.get([
51
+ { role: 'user', content: 'Explain TypeScript generics' }
52
+ ], {
53
+ metadata: { model: 'openai/gpt-5.4-mini' }
54
+ });
55
+
56
+ console.log(response.content);
57
+ console.log('Cost:', response.usage.cost); // Actual cost from OpenRouter
58
+ ```
59
+
60
+ ## Configuration
61
+
62
+ ### Basic Configuration
63
+
64
+ ```typescript
65
+ import { OpenRouterProvider, OpenRouterConfig } from '@aeye/openrouter';
66
+
67
+ const config: OpenRouterConfig = {
68
+ apiKey: process.env.OPENROUTER_API_KEY!,
69
+ defaultParams: {
70
+ siteUrl: 'https://yourapp.com', // Your app URL (helps with rankings)
71
+ appName: 'My AI App', // Your app name (shown in OpenRouter dashboard)
72
+ },
73
+ };
74
+
75
+ const provider = new OpenRouterProvider(config);
76
+ ```
77
+
78
+ ### Advanced Configuration
79
+
80
+ ```typescript
81
+ const provider = new OpenRouterProvider({
82
+ apiKey: process.env.OPENROUTER_API_KEY!,
83
+ defaultParams: {
84
+ siteUrl: 'https://yourapp.com',
85
+ appName: 'My AI App',
86
+
87
+ // Provider routing (sent as OpenRouter's `provider` object)
88
+ providers: {
89
+ order: ['openai', 'anthropic'], // Preferred order
90
+ allowFallbacks: true, // Fall back to other providers
91
+ only: ['openai', 'anthropic', 'google'], // Only use these providers
92
+ ignore: ['low-quality-provider'], // Never use these providers
93
+ quantizations: ['int8', 'fp16'], // Allowed quantization levels
94
+ sort: { by: 'throughput', partition: 'none' }, // or 'price' | 'latency' | 'exacto'
95
+ preferredMinThroughput: 50, // tokens/s, median
96
+ preferredMaxLatency: 2, // seconds, median
97
+ dataCollection: 'deny', // or 'allow'
98
+ zdr: true, // Zero data retention endpoints only
99
+ enforceDistillableText: false, // Only providers that allow training on outputs
100
+ requireParameters: true, // Only providers that support every parameter
101
+ maxPrice: { prompt: 10, completion: 30 },
102
+ },
103
+
104
+ // Models to try, in order, when the requested one fails
105
+ fallbackModels: ['openai/gpt-5.4-mini', 'anthropic/claude-sonnet-4.5'],
106
+
107
+ // Plugins
108
+ plugins: [
109
+ { id: 'file-parser', pdf: { engine: 'cloudflare-ai' } },
110
+ { id: 'response-healing' },
111
+ { id: 'context-compression', engine: 'middle-out' },
112
+ ],
113
+ },
114
+ });
115
+ ```
116
+
117
+ `transforms` is deprecated: OpenRouter replaced transforms with plugins, and `transforms: ['middle-out']` is sent as the `context-compression` plugin. `OpenRouterConfig` has no `api` option; requests always go through Chat Completions.
118
+
119
+ ## Usage Examples
120
+
121
+ ### Basic Chat with Cost Tracking
122
+
123
+ ```typescript
124
+ const response = await ai.chat.get([
125
+ { role: 'user', content: 'What is the capital of France?' }
126
+ ], {
127
+ metadata: { model: 'openai/gpt-5.4-nano' }
128
+ });
129
+
130
+ console.log(response.content);
131
+ console.log('Tokens:', response.usage.totalTokens);
132
+ console.log('Cost: $', response.usage.cost); // Actual cost from OpenRouter
133
+ ```
134
+
135
+ ### Using Multiple Models
136
+
137
+ OpenRouter allows you to access models from different providers using the format `provider/model`:
138
+
139
+ ```typescript
140
+ // OpenAI GPT-4
141
+ const gpt4 = await ai.chat.get(messages, {
142
+ metadata: { model: 'openai/gpt-5.4-mini' }
143
+ });
144
+
145
+ // Anthropic Claude
146
+ const claude = await ai.chat.get(messages, {
147
+ metadata: { model: 'anthropic/claude-opus-4.5' }
148
+ });
149
+
150
+ // Google Gemini
151
+ const gemini = await ai.chat.get(messages, {
152
+ metadata: { model: 'google/gemini-pro' }
153
+ });
154
+
155
+ // Meta Llama
156
+ const llama = await ai.chat.get(messages, {
157
+ metadata: { model: 'meta-llama/llama-3-70b' }
158
+ });
159
+ ```
160
+
161
+ ### Automatic Model Selection
162
+
163
+ Let OpenRouter choose the best model based on your criteria:
164
+
165
+ ```typescript
166
+ const ai = AI.with()
167
+ .providers({ openrouter })
168
+ .create({
169
+ defaultMetadata: {
170
+ required: ['chat', 'streaming'],
171
+ weights: {
172
+ speed: 0.3,
173
+ cost: 0.4,
174
+ quality: 0.3,
175
+ },
176
+ },
177
+ });
178
+
179
+ // OpenRouter will select the best model matching criteria
180
+ const response = await ai.chat.get([
181
+ { role: 'user', content: 'Explain quantum computing' }
182
+ ]);
183
+ ```
184
+
185
+ ### Provider Fallbacks
186
+
187
+ ```typescript
188
+ // Primary provider fails? OpenRouter automatically falls back
189
+ const provider = new OpenRouterProvider({
190
+ apiKey: process.env.OPENROUTER_API_KEY!,
191
+ defaultParams: {
192
+ allowFallbacks: true,
193
+ providers: {
194
+ order: ['openai', 'anthropic', 'google'], // Try in this order
195
+ },
196
+ },
197
+ });
198
+
199
+ // If OpenAI is down, automatically tries Anthropic, then Google
200
+ const response = await ai.chat.get(messages, {
201
+ metadata: { model: 'openai/gpt-5.4-mini' }
202
+ });
203
+ ```
204
+
205
+ ### Zero Data Retention (ZDR)
206
+
207
+ ```typescript
208
+ // Only use ZDR-compliant models
209
+ const ai = AI.with()
210
+ .providers({ openrouter })
211
+ .create({
212
+ defaultMetadata: {
213
+ required: ['chat', 'zdr'], // Require ZDR support
214
+ },
215
+ });
216
+
217
+ // Also configure at provider level
218
+ const provider = new OpenRouterProvider({
219
+ apiKey: process.env.OPENROUTER_API_KEY!,
220
+ defaultParams: {
221
+ dataCollection: 'deny', // Ensure no data collection
222
+ },
223
+ });
224
+ ```
225
+
226
+ ### Streaming with Cost Tracking
227
+
228
+ ```typescript
229
+ let totalCost = 0;
230
+
231
+ for await (const chunk of ai.chat.stream([
232
+ { role: 'user', content: 'Write a story about a robot' }
233
+ ], {
234
+ metadata: { model: 'anthropic/claude-sonnet-4.5' }
235
+ })) {
236
+ if (chunk.content) {
237
+ process.stdout.write(chunk.content);
238
+ }
239
+
240
+ // Cost is sent in final chunk
241
+ if (chunk.usage?.cost) {
242
+ totalCost = chunk.usage.cost;
243
+ }
244
+ }
245
+
246
+ console.log(`\n\nTotal cost: $${totalCost}`);
247
+ ```
248
+
249
+ ### Reasoning Models
250
+
251
+ ```typescript
252
+ // Reason with an effort, or a token budget via reason.maxTokens (never both)
253
+ const response = await ai.chat.get({
254
+ messages: [{
255
+ role: 'user',
256
+ content: 'Solve: If a train leaves NYC at 3pm going 60mph, and another leaves Boston at 4pm going 80mph, when do they meet?'
257
+ }],
258
+ reason: { effort: 'high', exclude: false },
259
+ }, {
260
+ metadata: { model: 'openai/gpt-5.5' }
261
+ });
262
+
263
+ console.log('Reasoning:', response.reasoning);
264
+ console.log('Answer:', response.content);
265
+ console.log('Cost:', response.usage.cost);
266
+ ```
267
+
268
+ Reasoning comes back with `reasoning.provider` set to `'openrouter'`. Keep it on the assistant message and it is replayed on that same message (with its text and tool calls) on the next turn; reasoning made by another provider, such as OpenAI directly or Bedrock, isn't replayed.
269
+
270
+ ### Server Tools
271
+
272
+ ```typescript
273
+ const response = await ai.chat.get({
274
+ messages: [{ role: 'user', content: 'What did the TypeScript team ship this month?' }],
275
+ hostedTools: [
276
+ { type: 'web_search', maxResults: 5, allowedDomains: ['devblogs.microsoft.com'], blockedDomains: ['example.com'] },
277
+ { type: 'web_fetch' },
278
+ ],
279
+ }, {
280
+ metadata: { model: 'openai/gpt-5.4-mini' }
281
+ });
282
+
283
+ console.log(response.citations);
284
+ console.log(response.usage?.tools); // { web_search: 1 }
285
+ ```
286
+
287
+ `web_search`, `web_fetch`, and `image_generation` run as `openrouter:*` server tools; `file_search`, `code_interpreter`, and `mcp` are rejected.
288
+
289
+ ### Images, Speech, Transcription, Embeddings
290
+
291
+ ```typescript
292
+ // Images go through chat with the image modality; size becomes an aspect ratio
293
+ const image = await ai.image.generate.get({ prompt: 'A lighthouse at dusk', size: '1536x1024' });
294
+
295
+ // Embeddings, speech, and transcription use OpenRouter's OpenAI-compatible endpoints
296
+ const { embeddings } = await ai.embed.get({ texts: ['hello'] });
297
+ const transcript = await ai.transcribe.get({ audio: fs.readFileSync('note.mp3') });
298
+ ```
299
+
300
+ Defaults: `google/gemini-2.5-flash-image` for images, `openai/gpt-transcribe`, `openai/text-embedding-3-small`, and `openai/gpt-5.4-mini` for image analysis; speech has no default model. Image and transcription streaming fall back to the non-streaming methods.
301
+
302
+ ### Reranking, Decisions, Batches
303
+
304
+ ```typescript
305
+ // Order documents by relevance (default cohere/rerank-v3.5)
306
+ const { results } = await ai.rerank.get({ query: 'refund policy', documents, topN: 3 });
307
+
308
+ // Typed answers from a decision model (default typesafe/jev-1.14)
309
+ const { answers } = await ai.decide.get({
310
+ input: 'I was charged twice for my order.',
311
+ questions: {
312
+ billing: { type: 'predicate', instructions: 'Is this a billing problem?' },
313
+ team: { type: 'choice', instructions: 'Which team handles it?', choices: { payments: 'Charges and refunds', web: 'Website bugs' } },
314
+ },
315
+ });
316
+
317
+ // Chat requests as one batch, posted inline to /batches
318
+ const job = await ai.batch.submit(requests, { metadata: { model: 'openai/gpt-5.4-mini' } });
319
+ const done = await ai.batch.wait(job, {});
320
+ ```
321
+
322
+ Rerank and decision costs come back as `usage.cost`, and both run the provider's `hooks.rerank` / `hooks.decide`. Decisions use OpenRouter's alpha endpoint, outside the versioned API. Change the defaults with `defaultModels.rerank` and `defaultModels.decision`.
323
+
324
+ OpenRouter reports a batch's cost only for the whole batch: `job.cost` (with BYOK, its fee plus the provider's bill). Batch results carry token counts but no cost.
325
+
326
+ ### Function Calling
327
+
328
+ ```typescript
329
+ import z from 'zod';
330
+
331
+ const response = await ai.chat.get({
332
+ messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
333
+ tools: [
334
+ {
335
+ name: 'get_weather',
336
+ description: 'Get current weather for a location',
337
+ parameters: z.object({
338
+ location: z.string(),
339
+ unit: z.enum(['celsius', 'fahrenheit']).optional(),
340
+ }),
341
+ }
342
+ ],
343
+ toolChoice: 'auto',
344
+ }, {
345
+ metadata: { model: 'openai/gpt-5.4-mini' },
346
+ });
347
+ ```
348
+
349
+ `toolChoice: { allowed: [...], required? }` is emulated by sending only the allowed tools, because OpenRouter ignores `allowed_tools`. Free-text tools (`format`) go as one-field functions with any grammar in the description; `defer` and `async` are not sent.
350
+
351
+ When any tool is strict, the request carries `x-anthropic-beta: structured-outputs-2025-11-13`: without it OpenRouter strips `strict` from tools bound for Anthropic. Other upstreams ignore the header.
352
+
353
+ When a request sets a JSON or schema `responseFormat` and no message mentions JSON, a `Respond in JSON.` system message is put first, because some upstreams serve every response format as JSON mode and reject a conversation that never says "json" (Alibaba for Qwen).
354
+
355
+ ### Fetching Available Models
356
+
357
+ OpenRouter provides a model source that can fetch all available models:
358
+
359
+ ```typescript
360
+ import { OpenRouterModelSource } from '@aeye/openrouter';
361
+
362
+ const source = new OpenRouterModelSource({
363
+ apiKey: process.env.OPENROUTER_API_KEY,
364
+ includeZDR: true, // Include ZDR information
365
+ });
366
+
367
+ const models = await source.fetchModels();
368
+
369
+ models.forEach(model => {
370
+ console.log(`${model.id}`);
371
+ console.log(` Pricing: $${model.pricing.text.input}/1M input tokens`);
372
+ console.log(` Context: ${model.contextWindow} tokens`);
373
+ console.log(` ZDR: ${model.capabilities.has('zdr')}`);
374
+ });
375
+ ```
376
+
377
+ ### Automatic Model Registration
378
+
379
+ ```typescript
380
+ // Automatically fetch and register OpenRouter models
381
+ const ai = AI.with()
382
+ .providers({ openrouter })
383
+ .create({
384
+ fetchOpenRouterModels: true, // Auto-fetch all available models
385
+ });
386
+
387
+ // Now all OpenRouter models are available for automatic selection
388
+ const response = await ai.chat.get(messages);
389
+ ```
390
+
391
+ ## Configuration Options
392
+
393
+ ### OpenRouterConfig
394
+
395
+ Extends `OpenAIConfig` from `@aeye/openai`, minus its `api` option.
396
+
397
+ | Property | Type | Required | Description |
398
+ |----------|------|----------|-------------|
399
+ | `apiKey` | `string` | Yes | OpenRouter API key |
400
+ | `baseURL` | `string` | No | Custom base URL (defaults to https://openrouter.ai/api/v1) |
401
+ | `defaultParams` | `object` | No | Default parameters for all requests |
402
+ | `defaultModels` | `object` | No | Per-operation defaults; built in: `cohere/rerank-v3.5` (rerank), `typesafe/jev-1.14` (decision), plus the image, transcription, and embedding defaults above |
403
+
404
+ ### defaultParams
405
+
406
+ | Property | Type | Description |
407
+ |----------|------|-------------|
408
+ | `siteUrl` | `string` | Your app URL, sent as `HTTP-Referer` |
409
+ | `appName` | `string` | Your app name, sent as `X-Title` |
410
+ | `providers` | `object` | Provider routing (see below) |
411
+ | `fallbackModels` | `string[]` | Models to fall back to, in order (sent as `models`) |
412
+ | `plugins` | `OpenRouterPlugin[]` | `file-parser`, `response-healing`, `context-compression` |
413
+ | `transforms` | `string[]` | Deprecated; `middle-out` is sent as the `context-compression` plugin |
414
+
415
+ ### providers Options
416
+
417
+ | Property | Type | Description |
418
+ |----------|------|-------------|
419
+ | `order` | `string[]` | Explicit provider order |
420
+ | `allowFallbacks` | `boolean` | Fall back to other providers |
421
+ | `requireParameters` | `boolean` | Only providers that support every parameter; auto-on for strict `json_schema` when unset |
422
+ | `dataCollection` | `'deny' \| 'allow'` | Provider data collection |
423
+ | `zdr` | `boolean` | Zero data retention endpoints only |
424
+ | `enforceDistillableText` | `boolean` | Only providers that allow training on outputs |
425
+ | `only` | `string[]` | Whitelist of providers |
426
+ | `ignore` | `string[]` | Providers to skip |
427
+ | `quantizations` | `OpenRouterQuantization[]` | Allowed quantization levels |
428
+ | `sort` | `'price' \| 'throughput' \| 'latency' \| 'exacto' \| { by, partition? }` | Sort providers; `exacto` prefers the best tool-calling accuracy, `partition: 'none'` sorts across fallback models together |
429
+ | `preferredMinThroughput` | `number` | Preferred median throughput floor (tokens/s) |
430
+ | `preferredMaxLatency` | `number` | Preferred median latency ceiling (seconds) |
431
+ | `maxPrice` | `{ prompt?, completion?, image?, audio?, request? }` | Price ceilings |
432
+
433
+ ## Cost Information
434
+
435
+ OpenRouter includes actual cost information in every response:
436
+
437
+ ```typescript
438
+ const response = await ai.chat.get(messages, {
439
+ metadata: { model: 'openai/gpt-5.4-mini' }
440
+ });
441
+
442
+ // Cost is automatically included
443
+ console.log('Input tokens:', response.usage.text.input);
444
+ console.log('Output tokens:', response.usage.text.output);
445
+ console.log('Cost: $', response.usage.cost); // Actual USD cost
446
+
447
+ // Cost calculation is skipped when provider returns cost
448
+ // No need to manually calculate costs!
449
+ ```
450
+
451
+ The cost is provided by OpenRouter's API in the `total_cost` field and automatically extracted into the `usage.cost` field.
452
+
453
+ ## Model Format
454
+
455
+ OpenRouter uses the format `provider/model-name`:
456
+
457
+ - `openai/gpt-5.4-mini`
458
+ - `anthropic/claude-opus-4.5`
459
+ - `google/gemini-2.5-flash`
460
+ - `x-ai/grok-4.3`
461
+ - `meta-llama/llama-3-70b-instruct`
462
+ - `mistralai/mistral-large`
463
+ - `cohere/command-r-plus`
464
+
465
+ See the [OpenRouter Models](https://openrouter.ai/models) page for the full list.
466
+
467
+ ## Privacy & Data Retention
468
+
469
+ ### Zero Data Retention (ZDR)
470
+
471
+ OpenRouter supports ZDR mode where your data is not stored:
472
+
473
+ ```typescript
474
+ // Require ZDR-compliant models
475
+ const ai = AI.with()
476
+ .providers({ openrouter })
477
+ .create({
478
+ defaultMetadata: {
479
+ required: ['chat', 'zdr'],
480
+ },
481
+ });
482
+
483
+ // Or configure at provider level
484
+ const provider = new OpenRouterProvider({
485
+ apiKey: process.env.OPENROUTER_API_KEY!,
486
+ defaultParams: {
487
+ dataCollection: 'deny',
488
+ providers: {
489
+ dataCollection: 'deny',
490
+ },
491
+ },
492
+ });
493
+ ```
494
+
495
+ Models with ZDR support have the `zdr` capability tag.
496
+
497
+ ## Error Handling
498
+
499
+ OpenRouter uses the same error types as the OpenAI provider:
500
+
501
+ ```typescript
502
+ import { ProviderError, RateLimitError } from '@aeye/openrouter';
503
+
504
+ try {
505
+ const response = await ai.chat.get(messages, {
506
+ metadata: { model: 'openai/gpt-5.4-mini' }
507
+ });
508
+ } catch (error) {
509
+ if (error instanceof RateLimitError) {
510
+ console.error('Rate limit exceeded');
511
+ console.log(`Retry after ${error.retryAfter} seconds`);
512
+ } else if (error instanceof ProviderError) {
513
+ console.error(`Provider error: ${error.message}`);
514
+ }
515
+ }
516
+ ```
517
+
518
+ ## Best Practices
519
+
520
+ 1. **Set Site URL and App Name**: This helps with OpenRouter rankings and analytics
521
+ ```typescript
522
+ defaultParams: {
523
+ siteUrl: 'https://yourapp.com',
524
+ appName: 'Your App',
525
+ }
526
+ ```
527
+
528
+ 2. **Use Cost Tracking**: Monitor costs with the built-in `usage.cost` field
529
+ ```typescript
530
+ console.log('Cost: $', response.usage.cost);
531
+ ```
532
+
533
+ 3. **Enable Fallbacks**: Use automatic fallbacks for reliability
534
+ ```typescript
535
+ defaultParams: {
536
+ providers: {
537
+ allowFallbacks: true,
538
+ order: ['openai', 'anthropic', 'google'],
539
+ },
540
+ fallbackModels: ['openai/gpt-5.4-mini'],
541
+ }
542
+ ```
543
+
544
+ 4. **Specify Provider Preferences**: Control which providers are used
545
+ ```typescript
546
+ providers: {
547
+ only: ['openai', 'anthropic'],
548
+ ignore: ['low-quality-provider'],
549
+ }
550
+ ```
551
+
552
+ 5. **Use ZDR for Sensitive Data**: Enable Zero Data Retention for privacy
553
+ ```typescript
554
+ defaultParams: {
555
+ providers: { dataCollection: 'deny', zdr: true },
556
+ }
557
+ ```
558
+
559
+ 6. **Monitor Model Availability**: OpenRouter's model list changes frequently
560
+ ```typescript
561
+ fetchOpenRouterModels: true // Auto-fetch latest models
562
+ ```
563
+
564
+ ## API Reference
565
+
566
+ ### OpenRouterProvider
567
+
568
+ Extends `OpenAIProvider` from `@aeye/openai`.
569
+
570
+ **Constructor**: `new OpenRouterProvider(config: OpenRouterConfig)`
571
+
572
+ **Methods**:
573
+ - Inherits all methods from `OpenAIProvider`
574
+ - `createExecutor<TContext, TMetadata>(config?)` - Returns executor with cost tracking
575
+ - `createStreamer<TContext, TMetadata>(config?)` - Returns streamer with cost tracking
576
+ - `listModels(config?)` - Fetches models from OpenRouter API with ZDR info
577
+
578
+ **Overridden Methods**:
579
+ - `createClient(config)` - Creates OpenAI client with OpenRouter endpoint
580
+ - `augmentChatRequest(params, request, config)` - Adds OpenRouter-specific params
581
+ - `buildChatParams(request, run, streaming)` - Sends only the allowed tools for `{ allowed }` tool choices
582
+ - `chatHeaders(params)` - Adds `x-anthropic-beta: structured-outputs-2025-11-13` when a tool is strict
583
+ - `createBatch(batch, run)` / `retrieveBatch(job, run)` - Inline batches through `/batches`
584
+
585
+ **Added Methods**:
586
+ - `rerank(request, ctx, config?)` - `/rerank`
587
+ - `decide(request, ctx, config?)` - alpha decisions endpoint
588
+
589
+ ### OpenRouterModelSource
590
+
591
+ Model source for fetching OpenRouter models.
592
+
593
+ **Constructor**: `new OpenRouterModelSource(config?: OpenRouterSourceConfig)`
594
+
595
+ **Methods**:
596
+ - `fetchModels(config?): Promise<ModelInfo[]>` - Fetch all available models
597
+
598
+ **Config Options**:
599
+ - `apiKey?: string` - OpenRouter API key (optional for public models)
600
+ - `includeZDR?: boolean` - Include ZDR information (default: true)
601
+
602
+ ## Supported Features
603
+
604
+ - ✅ Chat completions
605
+ - ✅ Streaming
606
+ - ✅ Function calling
607
+ - ✅ Vision (for supported models)
608
+ - ✅ Reasoning models
609
+ - ✅ Cost tracking
610
+ - ✅ Automatic fallbacks
611
+ - ✅ Provider routing
612
+ - ✅ ZDR mode
613
+ - ✅ Multi-modal (for supported models)
614
+ - ✅ Server tools (web search, web fetch, image generation)
615
+ - ✅ Prompt caching (`cacheBreakpoint` → `cache_control`)
616
+ - ✅ Image generation and editing (through chat; no streaming)
617
+ - ✅ Speech synthesis
618
+ - ✅ Transcription (no streaming)
619
+ - ✅ Embeddings
620
+ - ✅ Reranking
621
+ - ✅ Decisions (alpha endpoint)
622
+ - ✅ Batches
623
+
624
+ ## Related Packages
625
+
626
+ - **[@aeye/core](../core)**: Core @aeye framework types and interfaces
627
+ - **[@aeye/ai](../ai)**: AI abstractions and utilities
628
+ - **[@aeye/openai](../openai)**: OpenAI provider (base class)
629
+ - **[@aeye/anthropic](../anthropic)**: Anthropic Claude provider
630
+ - **[@aeye/google](../google)**: Google AI provider
631
+
632
+ ## Links
633
+
634
+ - [OpenRouter Website](https://openrouter.ai)
635
+ - [OpenRouter Models](https://openrouter.ai/models)
636
+ - [OpenRouter Documentation](https://openrouter.ai/docs)
637
+ - [OpenRouter API Keys](https://openrouter.ai/keys)
638
+
639
+ ## License
640
+
641
+ GPL-3.0
642
+
643
+ ## Contributing
644
+
645
+ Contributions are welcome! Please see the main [@aeye repository](https://github.com/ClickerMonkey/aeye) for contribution guidelines.
646
+
647
+ ## Support
648
+
649
+ For issues and questions:
650
+ - GitHub Issues: https://github.com/ClickerMonkey/aeye/issues
651
+ - Documentation: https://github.com/ClickerMonkey/aeye
652
+ - OpenRouter Discord: https://discord.gg/openrouter