@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.
- package/README.md +652 -643
- package/aeye-openrouter.md +179 -175
- 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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
},
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
OpenRouter
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
| `
|
|
401
|
-
| `
|
|
402
|
-
| `
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
|
410
|
-
|
|
411
|
-
| `
|
|
412
|
-
| `
|
|
413
|
-
| `
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
|
418
|
-
|
|
419
|
-
| `
|
|
420
|
-
| `
|
|
421
|
-
| `
|
|
422
|
-
| `
|
|
423
|
-
| `
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
```typescript
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
```typescript
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
**
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
-
|
|
574
|
-
- `
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
- `
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
-
|
|
600
|
-
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
- ✅
|
|
605
|
-
- ✅
|
|
606
|
-
- ✅
|
|
607
|
-
- ✅
|
|
608
|
-
- ✅
|
|
609
|
-
- ✅
|
|
610
|
-
- ✅
|
|
611
|
-
- ✅
|
|
612
|
-
- ✅
|
|
613
|
-
- ✅
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
-
|
|
618
|
-
-
|
|
619
|
-
-
|
|
620
|
-
-
|
|
621
|
-
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
- [
|
|
627
|
-
- [
|
|
628
|
-
- [
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|