@x12i/ai-providers-router 4.6.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 (58) hide show
  1. package/.metadata/anthropic.response-map.json +1 -0
  2. package/.metadata/google.response-map.json +1 -0
  3. package/.metadata/groq.response-map.json +1 -0
  4. package/.metadata/llm-request-config-registry.json +111 -0
  5. package/.metadata/llm-response-config-registry.json +1 -0
  6. package/.metadata/model-aliases.json +1 -0
  7. package/.metadata/model-normalization.json +1 -0
  8. package/.metadata/moonshot.response-map.json +1 -0
  9. package/.metadata/openai.response-map.json +1 -0
  10. package/.metadata/openrouter_catalog_with_vendor_mapping.json +15781 -0
  11. package/.metadata/reasoning-support.json +159 -0
  12. package/.metadata/xai.response-map.json +1 -0
  13. package/README.md +480 -0
  14. package/dist/adapters/grok/GrokAdapter.d.ts +50 -0
  15. package/dist/adapters/grok/GrokAdapter.js +342 -0
  16. package/dist/adapters/openai/OpenAIAdapter.d.ts +50 -0
  17. package/dist/adapters/openai/OpenAIAdapter.js +401 -0
  18. package/dist/adapters/openrouter/OpenRouterAdapter.d.ts +87 -0
  19. package/dist/adapters/openrouter/OpenRouterAdapter.js +1449 -0
  20. package/dist/adapters/openrouter/reasoning-capabilities.d.ts +26 -0
  21. package/dist/adapters/openrouter/reasoning-capabilities.js +79 -0
  22. package/dist/discovery.d.ts +6 -0
  23. package/dist/discovery.js +114 -0
  24. package/dist/errors.d.ts +27 -0
  25. package/dist/errors.js +33 -0
  26. package/dist/factory.d.ts +15 -0
  27. package/dist/factory.js +206 -0
  28. package/dist/gateway.d.ts +22 -0
  29. package/dist/gateway.js +154 -0
  30. package/dist/index.d.ts +9 -0
  31. package/dist/index.js +42 -0
  32. package/dist/interceptors.d.ts +10 -0
  33. package/dist/interceptors.js +1 -0
  34. package/dist/logger.d.ts +70 -0
  35. package/dist/logger.js +222 -0
  36. package/dist/openrouter-catalog.d.ts +119 -0
  37. package/dist/openrouter-catalog.js +222 -0
  38. package/dist/providers/OpenRouterProvider.d.ts +16 -0
  39. package/dist/providers/OpenRouterProvider.js +171 -0
  40. package/dist/registry/AdapterRegistry.d.ts +86 -0
  41. package/dist/registry/AdapterRegistry.js +36 -0
  42. package/dist/registry/ProviderRegistry.d.ts +24 -0
  43. package/dist/registry/ProviderRegistry.js +46 -0
  44. package/dist/router/Router.d.ts +33 -0
  45. package/dist/router/Router.js +258 -0
  46. package/dist/router/RouterTypes.d.ts +138 -0
  47. package/dist/router/RouterTypes.js +5 -0
  48. package/dist/router/RouterWrapper.d.ts +83 -0
  49. package/dist/router/RouterWrapper.js +744 -0
  50. package/dist/router.d.ts +13 -0
  51. package/dist/router.js +8 -0
  52. package/dist/types.d.ts +33 -0
  53. package/dist/types.js +1 -0
  54. package/dist/utils/esm-compat.d.ts +9 -0
  55. package/dist/utils/esm-compat.js +13 -0
  56. package/dist/utils/ids.d.ts +4 -0
  57. package/dist/utils/ids.js +6 -0
  58. package/package.json +66 -0
@@ -0,0 +1,222 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { getDirname } from './utils/esm-compat.js';
4
+ // Get __dirname equivalent for ESM
5
+ const __dirname = getDirname(import.meta.url);
6
+ /**
7
+ * OpenRouter Catalog Loader
8
+ * Loads and caches the OpenRouter catalog data from JSON file
9
+ */
10
+ export class OpenRouterCatalogLoader {
11
+ constructor() {
12
+ this.catalog = null;
13
+ // Default path relative to this file
14
+ this.catalogPath = path.join(__dirname, '..', '.metadata', 'openrouter_catalog_with_vendor_mapping.json');
15
+ }
16
+ static getInstance() {
17
+ if (!OpenRouterCatalogLoader.instance) {
18
+ OpenRouterCatalogLoader.instance = new OpenRouterCatalogLoader();
19
+ }
20
+ return OpenRouterCatalogLoader.instance;
21
+ }
22
+ /**
23
+ * Load the catalog from the JSON file
24
+ */
25
+ async load() {
26
+ if (this.catalog) {
27
+ return this.catalog;
28
+ }
29
+ try {
30
+ const data = fs.readFileSync(this.catalogPath, 'utf8');
31
+ this.catalog = JSON.parse(data);
32
+ return this.catalog;
33
+ }
34
+ catch (error) {
35
+ throw new Error(`Failed to load OpenRouter catalog from ${this.catalogPath}: ${error}`);
36
+ }
37
+ }
38
+ /**
39
+ * Get the loaded catalog (throws if not loaded)
40
+ */
41
+ getCatalog() {
42
+ if (!this.catalog) {
43
+ throw new Error('OpenRouter catalog not loaded. Call load() first.');
44
+ }
45
+ return this.catalog;
46
+ }
47
+ /**
48
+ * Get all providers
49
+ */
50
+ async getProviders() {
51
+ const catalog = await this.load();
52
+ return catalog.providers;
53
+ }
54
+ /**
55
+ * Get all models
56
+ */
57
+ async getModels() {
58
+ const catalog = await this.load();
59
+ return catalog.models;
60
+ }
61
+ /**
62
+ * Find a model by OpenRouter ID
63
+ */
64
+ async findModelById(openrouterId) {
65
+ const models = await this.getModels();
66
+ return models.find(m => m.openrouterId === openrouterId) || null;
67
+ }
68
+ /**
69
+ * Find models by canonical slug
70
+ */
71
+ async findModelByCanonicalSlug(canonicalSlug) {
72
+ const models = await this.getModels();
73
+ return models.find(m => m.canonicalSlug === canonicalSlug) || null;
74
+ }
75
+ /**
76
+ * Find models by aliases (returns all matches)
77
+ */
78
+ async findModelsByAlias(alias) {
79
+ const models = await this.getModels();
80
+ return models.filter(m => m.aliases.includes(alias));
81
+ }
82
+ /**
83
+ * Find provider by slug
84
+ */
85
+ async findProviderBySlug(slug) {
86
+ const providers = await this.getProviders();
87
+ return providers.find(p => p.slug === slug) || null;
88
+ }
89
+ /**
90
+ * Get all provider slugs
91
+ */
92
+ async getProviderSlugs() {
93
+ const providers = await this.getProviders();
94
+ return providers.map(p => p.slug);
95
+ }
96
+ /**
97
+ * Find provider by vendor ID (from model.vendor.direct.vendorId)
98
+ * This maps vendor IDs to provider slugs for alias support
99
+ */
100
+ async findProviderByVendorId(vendorId) {
101
+ const providers = await this.getProviders();
102
+ // Direct match with slug
103
+ const directMatch = providers.find(p => p.slug === vendorId);
104
+ if (directMatch) {
105
+ return directMatch;
106
+ }
107
+ // Some vendor IDs might be different from slugs, check common mappings
108
+ const vendorMappings = {
109
+ 'xai': 'grok', // xAI is Grok's vendor
110
+ 'meta': 'meta-llama', // Meta vendor maps to meta-llama provider
111
+ // Add more mappings as needed based on catalog data
112
+ };
113
+ const mappedSlug = vendorMappings[vendorId];
114
+ if (mappedSlug) {
115
+ return providers.find(p => p.slug === mappedSlug) || null;
116
+ }
117
+ return null;
118
+ }
119
+ /**
120
+ * Get all vendor IDs that map to providers
121
+ */
122
+ async getAllVendorIds() {
123
+ const models = await this.getModels();
124
+ const vendorIds = new Set();
125
+ for (const model of models) {
126
+ vendorIds.add(model.vendor.direct.vendorId);
127
+ }
128
+ return Array.from(vendorIds);
129
+ }
130
+ /**
131
+ * Check if a model is available
132
+ */
133
+ async isModelAvailable(modelId) {
134
+ const model = await this.findModelById(modelId);
135
+ return model !== null;
136
+ }
137
+ /**
138
+ * Get model capabilities
139
+ */
140
+ async getModelCapabilities(modelId) {
141
+ const model = await this.findModelById(modelId);
142
+ return model?.capabilities || null;
143
+ }
144
+ /**
145
+ * Validate model supports a specific parameter
146
+ */
147
+ async modelSupportsParameter(modelId, parameter) {
148
+ const capabilities = await this.getModelCapabilities(modelId);
149
+ return capabilities?.supportedParameters?.includes(parameter) || false;
150
+ }
151
+ /**
152
+ * Infer provider from model name using catalog data
153
+ */
154
+ async inferProviderFromModel(modelName) {
155
+ const models = await this.getModels();
156
+ // First try exact match with openrouterId
157
+ const exactMatch = models.find(m => m.openrouterId === modelName);
158
+ if (exactMatch) {
159
+ return exactMatch.vendor.author;
160
+ }
161
+ // Try alias match
162
+ const aliasMatch = models.find(m => m.aliases.includes(modelName));
163
+ if (aliasMatch) {
164
+ return aliasMatch.vendor.author;
165
+ }
166
+ // Try prefix match (e.g., "openai/gpt-4o" -> "openai")
167
+ if (modelName.includes('/')) {
168
+ const [prefix] = modelName.split('/');
169
+ const provider = await this.findProviderBySlug(prefix);
170
+ if (provider) {
171
+ return provider.slug;
172
+ }
173
+ // Also try vendor ID mapping for the prefix
174
+ const vendorProvider = await this.findProviderByVendorId(prefix);
175
+ if (vendorProvider) {
176
+ return vendorProvider.slug;
177
+ }
178
+ }
179
+ return null;
180
+ }
181
+ /**
182
+ * Normalize model name to OpenRouter format
183
+ * Returns the canonical OpenRouter ID for the model
184
+ */
185
+ async normalizeModelName(modelName, providerHint) {
186
+ const models = await this.getModels();
187
+ // If already in OpenRouter format (contains '/'), validate it
188
+ if (modelName.includes('/')) {
189
+ const model = models.find(m => m.openrouterId === modelName);
190
+ if (model) {
191
+ return model.openrouterId;
192
+ }
193
+ // If not found but contains '/', it might be an alias, continue to check
194
+ }
195
+ // Try to find by exact alias match
196
+ const aliasMatch = models.find(m => m.aliases.includes(modelName));
197
+ if (aliasMatch) {
198
+ return aliasMatch.openrouterId;
199
+ }
200
+ // If provider hint provided, try to construct OpenRouter ID
201
+ if (providerHint) {
202
+ const candidateId = `${providerHint}/${modelName}`;
203
+ const model = models.find(m => m.openrouterId === candidateId);
204
+ if (model) {
205
+ return model.openrouterId;
206
+ }
207
+ }
208
+ // Fallback: try to infer provider and construct ID
209
+ const inferredProvider = await this.inferProviderFromModel(modelName);
210
+ if (inferredProvider) {
211
+ const candidateId = `${inferredProvider}/${modelName}`;
212
+ const model = models.find(m => m.openrouterId === candidateId);
213
+ if (model) {
214
+ return model.openrouterId;
215
+ }
216
+ }
217
+ // Last resort: return as-is (might be a direct model name)
218
+ return modelName;
219
+ }
220
+ }
221
+ // Export singleton instance
222
+ export const openRouterCatalog = OpenRouterCatalogLoader.getInstance();
@@ -0,0 +1,16 @@
1
+ /**
2
+ * OpenRouter ProviderModule wrapper using @openrouter/sdk directly
3
+ * This properly implements the ProviderModule interface for OpenRouter
4
+ */
5
+ import type { ProviderModule } from '@x12i/ai-provider-interface';
6
+ export interface OpenRouterProviderConfig {
7
+ apiKey: string;
8
+ baseURL?: string;
9
+ timeoutMs?: number;
10
+ httpReferer?: string;
11
+ xTitle?: string;
12
+ }
13
+ /**
14
+ * Create OpenRouter ProviderModule using @openrouter/sdk directly
15
+ */
16
+ export declare function createOpenRouterProvider(config: OpenRouterProviderConfig): ProviderModule;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * OpenRouter ProviderModule wrapper using @openrouter/sdk directly
3
+ * This properly implements the ProviderModule interface for OpenRouter
4
+ */
5
+ import { OpenRouter } from '@openrouter/sdk';
6
+ /**
7
+ * Create OpenRouter ProviderModule using @openrouter/sdk directly
8
+ */
9
+ export function createOpenRouterProvider(config) {
10
+ const client = new OpenRouter({
11
+ apiKey: config.apiKey,
12
+ serverURL: config.baseURL || 'https://openrouter.ai/api/v1',
13
+ httpReferer: config.httpReferer,
14
+ xTitle: config.xTitle,
15
+ timeoutMs: config.timeoutMs,
16
+ });
17
+ return {
18
+ name: 'openrouter',
19
+ capabilities: {
20
+ modes: {
21
+ sync: true,
22
+ stream: true,
23
+ batch: false,
24
+ },
25
+ operations: [
26
+ 'openai.chat.completions.create',
27
+ 'openai.chat.completions.stream',
28
+ 'openai.responses.create',
29
+ 'openai.responses.stream',
30
+ ],
31
+ },
32
+ async execute(spec) {
33
+ const { operation, args, exec } = spec;
34
+ // Create AbortController for timeout
35
+ const controller = new AbortController();
36
+ let timeoutId = null;
37
+ if (exec?.timeoutMs) {
38
+ timeoutId = setTimeout(() => {
39
+ controller.abort();
40
+ }, exec.timeoutMs);
41
+ }
42
+ // Use exec.signal if provided, otherwise use our timeout controller
43
+ const signal = exec?.signal || controller.signal;
44
+ try {
45
+ let rawResponse;
46
+ if (operation === 'openai.responses.create' || operation === 'openai.chat.completions.create') {
47
+ // Convert args to OpenRouter SDK format
48
+ // OpenRouter SDK only supports Chat Completions format (messages array)
49
+ // If adapter passed 'input' (Responses API format), convert it to messages
50
+ const openRouterArgs = { ...args };
51
+ // If args has 'input' field (Responses API), convert to messages format
52
+ if (openRouterArgs.input && !openRouterArgs.messages) {
53
+ if (typeof openRouterArgs.input === 'string') {
54
+ openRouterArgs.messages = [{ role: 'user', content: openRouterArgs.input }];
55
+ }
56
+ else if (openRouterArgs.input.input_text) {
57
+ openRouterArgs.messages = [{ role: 'user', content: openRouterArgs.input.input_text }];
58
+ }
59
+ else {
60
+ openRouterArgs.messages = [{ role: 'user', content: JSON.stringify(openRouterArgs.input) }];
61
+ }
62
+ // Remove input field as OpenRouter doesn't support it
63
+ delete openRouterArgs.input;
64
+ }
65
+ // Ensure messages array exists
66
+ if (!openRouterArgs.messages || !Array.isArray(openRouterArgs.messages)) {
67
+ throw new Error('OpenRouter SDK requires messages array');
68
+ }
69
+ // Convert max_output_tokens to max_tokens if present (Responses API -> Chat Completions)
70
+ if (openRouterArgs.max_output_tokens !== undefined && openRouterArgs.max_tokens === undefined) {
71
+ openRouterArgs.max_tokens = openRouterArgs.max_output_tokens;
72
+ delete openRouterArgs.max_output_tokens;
73
+ }
74
+ // Use OpenRouter's chat API (OpenRouter uses OpenAI-compatible API)
75
+ const chatResponse = await client.chat.send({
76
+ ...openRouterArgs,
77
+ stream: false,
78
+ }, {
79
+ signal,
80
+ });
81
+ rawResponse = chatResponse;
82
+ }
83
+ else {
84
+ throw new Error(`Unsupported operation: ${operation}`);
85
+ }
86
+ return {
87
+ rawResponse,
88
+ rawMeta: {
89
+ operation,
90
+ provider: 'openrouter',
91
+ },
92
+ };
93
+ }
94
+ catch (error) {
95
+ if (timeoutId) {
96
+ clearTimeout(timeoutId);
97
+ }
98
+ throw error;
99
+ }
100
+ },
101
+ async *stream(spec) {
102
+ const { operation, args, exec } = spec;
103
+ // Create AbortController for timeout
104
+ const controller = new AbortController();
105
+ let timeoutId = null;
106
+ if (exec?.timeoutMs) {
107
+ timeoutId = setTimeout(() => {
108
+ controller.abort();
109
+ }, exec.timeoutMs);
110
+ }
111
+ // Use exec.signal if provided, otherwise use our timeout controller
112
+ const signal = exec?.signal || controller.signal;
113
+ try {
114
+ if (operation === 'openai.responses.stream' || operation === 'openai.chat.completions.stream') {
115
+ // Convert args to OpenRouter SDK format
116
+ // OpenRouter SDK only supports Chat Completions format (messages array)
117
+ // If adapter passed 'input' (Responses API format), convert it to messages
118
+ const openRouterArgs = { ...args };
119
+ // If args has 'input' field (Responses API), convert to messages format
120
+ if (openRouterArgs.input && !openRouterArgs.messages) {
121
+ if (typeof openRouterArgs.input === 'string') {
122
+ openRouterArgs.messages = [{ role: 'user', content: openRouterArgs.input }];
123
+ }
124
+ else if (openRouterArgs.input.input_text) {
125
+ openRouterArgs.messages = [{ role: 'user', content: openRouterArgs.input.input_text }];
126
+ }
127
+ else {
128
+ openRouterArgs.messages = [{ role: 'user', content: JSON.stringify(openRouterArgs.input) }];
129
+ }
130
+ // Remove input field as OpenRouter doesn't support it
131
+ delete openRouterArgs.input;
132
+ }
133
+ // Ensure messages array exists
134
+ if (!openRouterArgs.messages || !Array.isArray(openRouterArgs.messages)) {
135
+ throw new Error('OpenRouter SDK requires messages array');
136
+ }
137
+ // Convert max_output_tokens to max_tokens if present (Responses API -> Chat Completions)
138
+ if (openRouterArgs.max_output_tokens !== undefined && openRouterArgs.max_tokens === undefined) {
139
+ openRouterArgs.max_tokens = openRouterArgs.max_output_tokens;
140
+ delete openRouterArgs.max_output_tokens;
141
+ }
142
+ // Use OpenRouter's streaming chat API
143
+ const stream = await client.chat.send({
144
+ ...openRouterArgs,
145
+ stream: true,
146
+ }, {
147
+ signal,
148
+ });
149
+ for await (const chunk of stream) {
150
+ yield {
151
+ raw: chunk,
152
+ rawMeta: {
153
+ operation,
154
+ provider: 'openrouter',
155
+ },
156
+ };
157
+ }
158
+ }
159
+ else {
160
+ throw new Error(`Unsupported streaming operation: ${operation}`);
161
+ }
162
+ }
163
+ catch (error) {
164
+ if (timeoutId) {
165
+ clearTimeout(timeoutId);
166
+ }
167
+ throw error;
168
+ }
169
+ },
170
+ };
171
+ }
@@ -0,0 +1,86 @@
1
+ import type { ProviderSDKCallSpec, ProviderSDKExecResult, ProviderSDKStreamChunk, ProviderBatchResults } from '@x12i/ai-provider-interface';
2
+ import type { AIResponse, AIStreamEvent } from '../router/RouterTypes.js';
3
+ /**
4
+ * Router-side adapter interface
5
+ * Adapters convert router requests to ProviderSDKCallSpec and parse raw responses
6
+ */
7
+ export interface RouterAdapter {
8
+ provider: string;
9
+ /**
10
+ * Build ProviderSDKCallSpec from router request
11
+ */
12
+ buildCallSpec(input: {
13
+ requestId: string;
14
+ mode: 'sync' | 'stream';
15
+ request: any;
16
+ exec?: {
17
+ timeoutMs?: number;
18
+ retries?: number;
19
+ idempotencyKey?: string;
20
+ signal?: AbortSignal;
21
+ };
22
+ }): ProviderSDKCallSpec | Promise<ProviderSDKCallSpec>;
23
+ /**
24
+ * Parse ProviderSDKExecResult to AIResponse
25
+ */
26
+ parseResponse(input: {
27
+ requestId: string;
28
+ request: any;
29
+ execResult: ProviderSDKExecResult;
30
+ }): AIResponse;
31
+ /**
32
+ * Parse stream chunk to router events (optional)
33
+ */
34
+ parseStreamChunk?(input: {
35
+ requestId: string;
36
+ request: any;
37
+ chunk: ProviderSDKStreamChunk;
38
+ }): AIStreamEvent[];
39
+ /**
40
+ * Finalize stream and return final response (optional)
41
+ */
42
+ finalizeStream?(input: {
43
+ requestId: string;
44
+ request: any;
45
+ collected: {
46
+ rawEvents: unknown[];
47
+ outputText?: string;
48
+ };
49
+ finalRaw?: unknown;
50
+ }): AIResponse;
51
+ /**
52
+ * Parse batch item result (optional)
53
+ */
54
+ parseBatchItem?(input: {
55
+ requestId: string;
56
+ request: any;
57
+ item: ProviderBatchResults['items'][number];
58
+ }): {
59
+ requestId: string;
60
+ rawResponse?: unknown;
61
+ outputText?: string;
62
+ error?: any;
63
+ };
64
+ }
65
+ /**
66
+ * Registry for router-side adapters
67
+ */
68
+ export declare class AdapterRegistry {
69
+ private map;
70
+ /**
71
+ * Register an adapter
72
+ */
73
+ register(adapter: RouterAdapter): void;
74
+ /**
75
+ * Get adapter for a provider
76
+ */
77
+ get(provider: string): RouterAdapter;
78
+ /**
79
+ * Check if adapter exists for provider
80
+ */
81
+ has(provider: string): boolean;
82
+ /**
83
+ * List all registered adapter provider names
84
+ */
85
+ list(): string[];
86
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Registry for router-side adapters
3
+ */
4
+ export class AdapterRegistry {
5
+ constructor() {
6
+ this.map = new Map();
7
+ }
8
+ /**
9
+ * Register an adapter
10
+ */
11
+ register(adapter) {
12
+ this.map.set(adapter.provider, adapter);
13
+ }
14
+ /**
15
+ * Get adapter for a provider
16
+ */
17
+ get(provider) {
18
+ const a = this.map.get(provider);
19
+ if (!a) {
20
+ throw new Error(`Adapter not registered for provider: ${provider}. Available: ${Array.from(this.map.keys()).join(', ')}`);
21
+ }
22
+ return a;
23
+ }
24
+ /**
25
+ * Check if adapter exists for provider
26
+ */
27
+ has(provider) {
28
+ return this.map.has(provider);
29
+ }
30
+ /**
31
+ * List all registered adapter provider names
32
+ */
33
+ list() {
34
+ return [...this.map.keys()];
35
+ }
36
+ }
@@ -0,0 +1,24 @@
1
+ import type { ProviderModule } from '@x12i/ai-provider-interface';
2
+ /**
3
+ * Registry for provider modules
4
+ * Loads and manages ProviderModule instances
5
+ */
6
+ export declare class ProviderRegistry {
7
+ private map;
8
+ /**
9
+ * Register a provider module
10
+ */
11
+ register(provider: ProviderModule): void;
12
+ /**
13
+ * Get a provider module by name
14
+ */
15
+ get(name: string): ProviderModule;
16
+ /**
17
+ * List all registered provider names
18
+ */
19
+ list(): string[];
20
+ /**
21
+ * Check if a provider is registered
22
+ */
23
+ has(name: string): boolean;
24
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Registry for provider modules
3
+ * Loads and manages ProviderModule instances
4
+ */
5
+ export class ProviderRegistry {
6
+ constructor() {
7
+ this.map = new Map();
8
+ }
9
+ /**
10
+ * Register a provider module
11
+ */
12
+ register(provider) {
13
+ if (!provider.name) {
14
+ throw new Error('Provider module must have a name');
15
+ }
16
+ if (!provider.capabilities) {
17
+ throw new Error(`Provider '${provider.name}' must declare capabilities`);
18
+ }
19
+ if (!provider.execute) {
20
+ throw new Error(`Provider '${provider.name}' must implement execute()`);
21
+ }
22
+ this.map.set(provider.name, provider);
23
+ }
24
+ /**
25
+ * Get a provider module by name
26
+ */
27
+ get(name) {
28
+ const p = this.map.get(name);
29
+ if (!p) {
30
+ throw new Error(`Provider not registered: ${name}. Available: ${Array.from(this.map.keys()).join(', ')}`);
31
+ }
32
+ return p;
33
+ }
34
+ /**
35
+ * List all registered provider names
36
+ */
37
+ list() {
38
+ return [...this.map.keys()];
39
+ }
40
+ /**
41
+ * Check if a provider is registered
42
+ */
43
+ has(name) {
44
+ return this.map.has(name);
45
+ }
46
+ }
@@ -0,0 +1,33 @@
1
+ import type { ProviderRegistry } from '../registry/ProviderRegistry.js';
2
+ import type { AdapterRegistry } from '../registry/AdapterRegistry.js';
3
+ import type { AIRouterRequest, AIResponse, AIStreamEvent, AIBatchResponse, AIBatchRequestItem } from './RouterTypes.js';
4
+ /**
5
+ * Main router class
6
+ * Orchestrates provider execution using ProviderModules and router-side adapters
7
+ */
8
+ export declare class AIRouter {
9
+ private providers;
10
+ private adapters;
11
+ constructor(providers: ProviderRegistry, adapters: AdapterRegistry);
12
+ /**
13
+ * Resolve provider name from request, checking OpenRouter mode first
14
+ */
15
+ private resolveProviderName;
16
+ /**
17
+ * Execute a sync request
18
+ */
19
+ runSync(input: AIRouterRequest): Promise<AIResponse>;
20
+ /**
21
+ * Execute a streaming request
22
+ */
23
+ runStream(input: AIRouterRequest): AsyncIterable<AIStreamEvent>;
24
+ /**
25
+ * Execute a batch request
26
+ */
27
+ runBatch(providerName: string, items: AIBatchRequestItem[], exec?: {
28
+ timeoutMs?: number;
29
+ retries?: number;
30
+ idempotencyKey?: string;
31
+ signal?: AbortSignal;
32
+ }): Promise<AIBatchResponse>;
33
+ }