@aeye/openrouter 0.1.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 ADDED
@@ -0,0 +1,556 @@
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, 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**: Support for advanced reasoning with o1, Claude, and others
14
+ - **Streaming**: Full streaming support across compatible models
15
+ - **Extends OpenAI**: Built on the OpenAI provider for compatibility
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @aeye/openrouter @aeye/openai @aeye/ai @aeye/core openai zod
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { OpenRouterProvider } from '@aeye/openrouter';
27
+ import { AI } from '@aeye/ai';
28
+
29
+ // Create provider instance
30
+ const openrouter = new OpenRouterProvider({
31
+ apiKey: process.env.OPENROUTER_API_KEY!,
32
+ defaultParams: {
33
+ siteUrl: 'https://yourapp.com',
34
+ appName: 'Your App Name',
35
+ },
36
+ });
37
+
38
+ // Use with AI instance
39
+ const ai = AI.with()
40
+ .providers({ openrouter })
41
+ .create();
42
+
43
+ // Make a request (OpenRouter picks the best provider)
44
+ const response = await ai.chat.get([
45
+ { role: 'user', content: 'Explain TypeScript generics' }
46
+ ], {
47
+ metadata: { model: 'openai/gpt-4-turbo' }
48
+ });
49
+
50
+ console.log(response.content);
51
+ console.log('Cost:', response.usage.cost); // Actual cost from OpenRouter
52
+ ```
53
+
54
+ ## Configuration
55
+
56
+ ### Basic Configuration
57
+
58
+ ```typescript
59
+ import { OpenRouterProvider, OpenRouterConfig } from '@aeye/openrouter';
60
+
61
+ const config: OpenRouterConfig = {
62
+ apiKey: process.env.OPENROUTER_API_KEY!,
63
+ defaultParams: {
64
+ siteUrl: 'https://yourapp.com', // Your app URL (helps with rankings)
65
+ appName: 'My AI App', // Your app name (shown in OpenRouter dashboard)
66
+ },
67
+ };
68
+
69
+ const provider = new OpenRouterProvider(config);
70
+ ```
71
+
72
+ ### Advanced Configuration
73
+
74
+ ```typescript
75
+ const provider = new OpenRouterProvider({
76
+ apiKey: process.env.OPENROUTER_API_KEY!,
77
+ defaultParams: {
78
+ siteUrl: 'https://yourapp.com',
79
+ appName: 'My AI App',
80
+
81
+ // Automatic fallbacks
82
+ allowFallbacks: true, // Enable fallback to alternative providers
83
+
84
+ // Provider preferences
85
+ providers: {
86
+ prefer: ['openai', 'anthropic'], // Prefer these providers
87
+ allow: ['openai', 'anthropic', 'google'], // Only use these providers
88
+ deny: ['provider-x'], // Never use these providers
89
+ ignore: ['low-quality-provider'], // Exclude from consideration
90
+ order: ['openai', 'anthropic'], // Preferred order
91
+ quantizations: ['int8', 'fp16'], // Preferred quantization levels
92
+ dataCollection: 'deny', // or 'allow' - control data collection
93
+ },
94
+
95
+ // Privacy & security
96
+ requireParameters: true, // Require all providers to support parameters
97
+ dataCollection: 'deny', // Global data collection setting
98
+
99
+ // Model transformations
100
+ transforms: ['middle-out'], // Apply transformations to requests
101
+ },
102
+ });
103
+ ```
104
+
105
+ ## Usage Examples
106
+
107
+ ### Basic Chat with Cost Tracking
108
+
109
+ ```typescript
110
+ const response = await ai.chat.get([
111
+ { role: 'user', content: 'What is the capital of France?' }
112
+ ], {
113
+ metadata: { model: 'openai/gpt-3.5-turbo' }
114
+ });
115
+
116
+ console.log(response.content);
117
+ console.log('Tokens:', response.usage.totalTokens);
118
+ console.log('Cost: $', response.usage.cost); // Actual cost from OpenRouter
119
+ ```
120
+
121
+ ### Using Multiple Models
122
+
123
+ OpenRouter allows you to access models from different providers using the format `provider/model`:
124
+
125
+ ```typescript
126
+ // OpenAI GPT-4
127
+ const gpt4 = await ai.chat.get(messages, {
128
+ metadata: { model: 'openai/gpt-4-turbo' }
129
+ });
130
+
131
+ // Anthropic Claude
132
+ const claude = await ai.chat.get(messages, {
133
+ metadata: { model: 'anthropic/claude-3-opus' }
134
+ });
135
+
136
+ // Google Gemini
137
+ const gemini = await ai.chat.get(messages, {
138
+ metadata: { model: 'google/gemini-pro' }
139
+ });
140
+
141
+ // Meta Llama
142
+ const llama = await ai.chat.get(messages, {
143
+ metadata: { model: 'meta-llama/llama-3-70b' }
144
+ });
145
+ ```
146
+
147
+ ### Automatic Model Selection
148
+
149
+ Let OpenRouter choose the best model based on your criteria:
150
+
151
+ ```typescript
152
+ const ai = AI.with()
153
+ .providers({ openrouter })
154
+ .create({
155
+ defaultMetadata: {
156
+ required: ['chat', 'streaming'],
157
+ weights: {
158
+ speed: 0.3,
159
+ cost: 0.4,
160
+ quality: 0.3,
161
+ },
162
+ },
163
+ });
164
+
165
+ // OpenRouter will select the best model matching criteria
166
+ const response = await ai.chat.get([
167
+ { role: 'user', content: 'Explain quantum computing' }
168
+ ]);
169
+ ```
170
+
171
+ ### Provider Fallbacks
172
+
173
+ ```typescript
174
+ // Primary provider fails? OpenRouter automatically falls back
175
+ const provider = new OpenRouterProvider({
176
+ apiKey: process.env.OPENROUTER_API_KEY!,
177
+ defaultParams: {
178
+ allowFallbacks: true,
179
+ providers: {
180
+ order: ['openai', 'anthropic', 'google'], // Try in this order
181
+ },
182
+ },
183
+ });
184
+
185
+ // If OpenAI is down, automatically tries Anthropic, then Google
186
+ const response = await ai.chat.get(messages, {
187
+ metadata: { model: 'openai/gpt-4-turbo' }
188
+ });
189
+ ```
190
+
191
+ ### Zero Data Retention (ZDR)
192
+
193
+ ```typescript
194
+ // Only use ZDR-compliant models
195
+ const ai = AI.with()
196
+ .providers({ openrouter })
197
+ .create({
198
+ defaultMetadata: {
199
+ required: ['chat', 'zdr'], // Require ZDR support
200
+ },
201
+ });
202
+
203
+ // Also configure at provider level
204
+ const provider = new OpenRouterProvider({
205
+ apiKey: process.env.OPENROUTER_API_KEY!,
206
+ defaultParams: {
207
+ dataCollection: 'deny', // Ensure no data collection
208
+ },
209
+ });
210
+ ```
211
+
212
+ ### Streaming with Cost Tracking
213
+
214
+ ```typescript
215
+ let totalCost = 0;
216
+
217
+ for await (const chunk of ai.chat.stream([
218
+ { role: 'user', content: 'Write a story about a robot' }
219
+ ], {
220
+ metadata: { model: 'anthropic/claude-3-sonnet' }
221
+ })) {
222
+ if (chunk.content) {
223
+ process.stdout.write(chunk.content);
224
+ }
225
+
226
+ // Cost is sent in final chunk
227
+ if (chunk.usage?.cost) {
228
+ totalCost = chunk.usage.cost;
229
+ }
230
+ }
231
+
232
+ console.log(`\n\nTotal cost: $${totalCost}`);
233
+ ```
234
+
235
+ ### Reasoning Models
236
+
237
+ ```typescript
238
+ // Use OpenAI o1 through OpenRouter
239
+ const response = await ai.chat.get([
240
+ {
241
+ role: 'user',
242
+ content: 'Solve: If a train leaves NYC at 3pm going 60mph, and another leaves Boston at 4pm going 80mph, when do they meet?'
243
+ }
244
+ ], {
245
+ metadata: { model: 'openai/o1-preview' }
246
+ });
247
+
248
+ console.log('Reasoning:', response.reasoning);
249
+ console.log('Answer:', response.content);
250
+ console.log('Cost:', response.usage.cost);
251
+ ```
252
+
253
+ ### Function Calling
254
+
255
+ ```typescript
256
+ import z from 'zod';
257
+
258
+ const response = await ai.chat.get([
259
+ { role: 'user', content: 'What is the weather in Paris?' }
260
+ ], {
261
+ metadata: { model: 'openai/gpt-4-turbo' },
262
+ tools: [
263
+ {
264
+ name: 'get_weather',
265
+ description: 'Get current weather for a location',
266
+ parameters: z.object({
267
+ location: z.string(),
268
+ unit: z.enum(['celsius', 'fahrenheit']).optional(),
269
+ }),
270
+ }
271
+ ],
272
+ toolChoice: 'auto',
273
+ });
274
+ ```
275
+
276
+ ### Fetching Available Models
277
+
278
+ OpenRouter provides a model source that can fetch all available models:
279
+
280
+ ```typescript
281
+ import { OpenRouterModelSource } from '@aeye/openrouter';
282
+
283
+ const source = new OpenRouterModelSource({
284
+ apiKey: process.env.OPENROUTER_API_KEY,
285
+ includeZDR: true, // Include ZDR information
286
+ });
287
+
288
+ const models = await source.fetchModels();
289
+
290
+ models.forEach(model => {
291
+ console.log(`${model.id}`);
292
+ console.log(` Pricing: $${model.pricing.inputTokensPer1M}/1M input tokens`);
293
+ console.log(` Context: ${model.contextWindow} tokens`);
294
+ console.log(` ZDR: ${model.capabilities.has('zdr')}`);
295
+ });
296
+ ```
297
+
298
+ ### Automatic Model Registration
299
+
300
+ ```typescript
301
+ // Automatically fetch and register OpenRouter models
302
+ const ai = AI.with()
303
+ .providers({ openrouter })
304
+ .create({
305
+ fetchOpenRouterModels: true, // Auto-fetch all available models
306
+ });
307
+
308
+ // Now all OpenRouter models are available for automatic selection
309
+ const response = await ai.chat.get(messages);
310
+ ```
311
+
312
+ ## Configuration Options
313
+
314
+ ### OpenRouterConfig
315
+
316
+ Extends `OpenAIConfig` from `@aeye/openai`.
317
+
318
+ | Property | Type | Required | Description |
319
+ |----------|------|----------|-------------|
320
+ | `apiKey` | `string` | Yes | OpenRouter API key |
321
+ | `baseURL` | `string` | No | Custom base URL (defaults to https://openrouter.ai/api/v1) |
322
+ | `defaultParams` | `object` | No | Default parameters for all requests |
323
+
324
+ ### defaultParams
325
+
326
+ | Property | Type | Description |
327
+ |----------|------|-------------|
328
+ | `siteUrl` | `string` | Your app URL (helps with rankings on OpenRouter) |
329
+ | `appName` | `string` | Your app name (shown in OpenRouter dashboard) |
330
+ | `allowFallbacks` | `boolean` | Enable automatic provider fallbacks |
331
+ | `requireParameters` | `boolean` | Require all providers support parameters |
332
+ | `dataCollection` | `'deny' \| 'allow'` | Control data collection |
333
+ | `order` | `string[]` | Preferred provider order |
334
+ | `providers` | `object` | Provider preferences (see below) |
335
+ | `transforms` | `string[]` | Request transformations to apply |
336
+
337
+ ### providers Options
338
+
339
+ | Property | Type | Description |
340
+ |----------|------|-------------|
341
+ | `allow` | `string[]` | Whitelist of allowed providers |
342
+ | `deny` | `string[]` | Blacklist of denied providers |
343
+ | `prefer` | `string[]` | Preferred providers (given priority) |
344
+ | `ignore` | `string[]` | Providers to ignore |
345
+ | `order` | `string[]` | Explicit provider order |
346
+ | `quantizations` | `string[]` | Preferred quantization levels |
347
+ | `dataCollection` | `'deny' \| 'allow'` | Provider-level data collection |
348
+
349
+ ## Cost Information
350
+
351
+ OpenRouter includes actual cost information in every response:
352
+
353
+ ```typescript
354
+ const response = await ai.chat.get(messages, {
355
+ metadata: { model: 'openai/gpt-4-turbo' }
356
+ });
357
+
358
+ // Cost is automatically included
359
+ console.log('Input tokens:', response.usage.inputTokens);
360
+ console.log('Output tokens:', response.usage.outputTokens);
361
+ console.log('Cost: $', response.usage.cost); // Actual USD cost
362
+
363
+ // Cost calculation is skipped when provider returns cost
364
+ // No need to manually calculate costs!
365
+ ```
366
+
367
+ The cost is provided by OpenRouter's API in the `total_cost` field and automatically extracted into the `usage.cost` field.
368
+
369
+ ## Model Format
370
+
371
+ OpenRouter uses the format `provider/model-name`:
372
+
373
+ - `openai/gpt-4-turbo`
374
+ - `anthropic/claude-3-opus`
375
+ - `google/gemini-pro`
376
+ - `meta-llama/llama-3-70b-instruct`
377
+ - `mistralai/mistral-large`
378
+ - `cohere/command-r-plus`
379
+
380
+ See the [OpenRouter Models](https://openrouter.ai/models) page for the full list.
381
+
382
+ ## Privacy & Data Retention
383
+
384
+ ### Zero Data Retention (ZDR)
385
+
386
+ OpenRouter supports ZDR mode where your data is not stored:
387
+
388
+ ```typescript
389
+ // Require ZDR-compliant models
390
+ const ai = AI.with()
391
+ .providers({ openrouter })
392
+ .create({
393
+ defaultMetadata: {
394
+ required: ['chat', 'zdr'],
395
+ },
396
+ });
397
+
398
+ // Or configure at provider level
399
+ const provider = new OpenRouterProvider({
400
+ apiKey: process.env.OPENROUTER_API_KEY!,
401
+ defaultParams: {
402
+ dataCollection: 'deny',
403
+ providers: {
404
+ dataCollection: 'deny',
405
+ },
406
+ },
407
+ });
408
+ ```
409
+
410
+ Models with ZDR support have the `zdr` capability tag.
411
+
412
+ ## Error Handling
413
+
414
+ OpenRouter uses the same error types as the OpenAI provider:
415
+
416
+ ```typescript
417
+ import { ProviderError, RateLimitError } from '@aeye/openrouter';
418
+
419
+ try {
420
+ const response = await ai.chat.get(messages, {
421
+ metadata: { model: 'openai/gpt-4-turbo' }
422
+ });
423
+ } catch (error) {
424
+ if (error instanceof RateLimitError) {
425
+ console.error('Rate limit exceeded');
426
+ console.log(`Retry after ${error.retryAfter} seconds`);
427
+ } else if (error instanceof ProviderError) {
428
+ console.error(`Provider error: ${error.message}`);
429
+ }
430
+ }
431
+ ```
432
+
433
+ ## Best Practices
434
+
435
+ 1. **Set Site URL and App Name**: This helps with OpenRouter rankings and analytics
436
+ ```typescript
437
+ defaultParams: {
438
+ siteUrl: 'https://yourapp.com',
439
+ appName: 'Your App',
440
+ }
441
+ ```
442
+
443
+ 2. **Use Cost Tracking**: Monitor costs with the built-in `usage.cost` field
444
+ ```typescript
445
+ console.log('Cost: $', response.usage.cost);
446
+ ```
447
+
448
+ 3. **Enable Fallbacks**: Use automatic fallbacks for reliability
449
+ ```typescript
450
+ defaultParams: {
451
+ allowFallbacks: true,
452
+ providers: {
453
+ order: ['openai', 'anthropic', 'google'],
454
+ },
455
+ }
456
+ ```
457
+
458
+ 4. **Specify Provider Preferences**: Control which providers are used
459
+ ```typescript
460
+ providers: {
461
+ prefer: ['openai', 'anthropic'],
462
+ deny: ['low-quality-provider'],
463
+ }
464
+ ```
465
+
466
+ 5. **Use ZDR for Sensitive Data**: Enable Zero Data Retention for privacy
467
+ ```typescript
468
+ defaultParams: {
469
+ dataCollection: 'deny',
470
+ }
471
+ ```
472
+
473
+ 6. **Monitor Model Availability**: OpenRouter's model list changes frequently
474
+ ```typescript
475
+ fetchOpenRouterModels: true // Auto-fetch latest models
476
+ ```
477
+
478
+ ## API Reference
479
+
480
+ ### OpenRouterProvider
481
+
482
+ Extends `OpenAIProvider` from `@aeye/openai`.
483
+
484
+ **Constructor**: `new OpenRouterProvider(config: OpenRouterConfig)`
485
+
486
+ **Methods**:
487
+ - Inherits all methods from `OpenAIProvider`
488
+ - `createExecutor<TContext, TMetadata>(config?)` - Returns executor with cost tracking
489
+ - `createStreamer<TContext, TMetadata>(config?)` - Returns streamer with cost tracking
490
+ - `listModels(config?)` - Fetches models from OpenRouter API with ZDR info
491
+
492
+ **Overridden Methods**:
493
+ - `createClient(config)` - Creates OpenAI client with OpenRouter endpoint
494
+ - `customizeChatParams(params, config, request)` - Adds OpenRouter-specific params
495
+
496
+ ### OpenRouterModelSource
497
+
498
+ Model source for fetching OpenRouter models.
499
+
500
+ **Constructor**: `new OpenRouterModelSource(config?: OpenRouterSourceConfig)`
501
+
502
+ **Methods**:
503
+ - `fetchModels(config?): Promise<ModelInfo[]>` - Fetch all available models
504
+
505
+ **Config Options**:
506
+ - `apiKey?: string` - OpenRouter API key (optional for public models)
507
+ - `includeZDR?: boolean` - Include ZDR information (default: true)
508
+
509
+ ## Supported Features
510
+
511
+ - ✅ Chat completions
512
+ - ✅ Streaming
513
+ - ✅ Function calling
514
+ - ✅ Vision (for supported models)
515
+ - ✅ Reasoning models
516
+ - ✅ Cost tracking
517
+ - ✅ Automatic fallbacks
518
+ - ✅ Provider routing
519
+ - ✅ ZDR mode
520
+ - ✅ Multi-modal (for supported models)
521
+ - ❌ Image generation (not supported by OpenRouter)
522
+ - ❌ Speech synthesis (not supported by OpenRouter)
523
+ - ❌ Transcription (not supported by OpenRouter)
524
+ - ❌ Embeddings (not supported by OpenRouter)
525
+
526
+ For image generation, speech, transcription, and embeddings, use provider-specific packages like `@aeye/openai`, `@aeye/anthropic`, etc.
527
+
528
+ ## Related Packages
529
+
530
+ - **[@aeye/core](../core)**: Core @aeye framework types and interfaces
531
+ - **[@aeye/ai](../ai)**: AI abstractions and utilities
532
+ - **[@aeye/openai](../openai)**: OpenAI provider (base class)
533
+ - **[@aeye/anthropic](../anthropic)**: Anthropic Claude provider
534
+ - **[@aeye/google](../google)**: Google AI provider
535
+
536
+ ## Links
537
+
538
+ - [OpenRouter Website](https://openrouter.ai)
539
+ - [OpenRouter Models](https://openrouter.ai/models)
540
+ - [OpenRouter Documentation](https://openrouter.ai/docs)
541
+ - [OpenRouter API Keys](https://openrouter.ai/keys)
542
+
543
+ ## License
544
+
545
+ MIT
546
+
547
+ ## Contributing
548
+
549
+ Contributions are welcome! Please see the main [@aeye repository](https://github.com/ClickerMonkey/aeye) for contribution guidelines.
550
+
551
+ ## Support
552
+
553
+ For issues and questions:
554
+ - GitHub Issues: https://github.com/ClickerMonkey/aeye/issues
555
+ - Documentation: https://github.com/ClickerMonkey/aeye
556
+ - OpenRouter Discord: https://discord.gg/openrouter
@@ -0,0 +1,8 @@
1
+ /**
2
+ * OpenRouter Provider Integration Tests
3
+ *
4
+ * Tests that make REAL API calls to OpenRouter.
5
+ * Requires OPENROUTER_API_KEY environment variable.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=openrouter.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter.integration.test.d.ts","sourceRoot":"","sources":["../../src/__integration__/openrouter.integration.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}