@x12i/ai-providers-router 4.8.2 → 4.8.4
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/dist/adapters/openrouter/OpenRouterAdapter.js +4 -4
- package/dist/factory.js +11 -5
- package/dist/openrouter-catalog.js +37 -28
- package/dist/router/Router.js +7 -8
- package/dist/router/RouterWrapper.js +32 -13
- package/dist/utils/openrouterEnv.d.ts +8 -0
- package/dist/utils/openrouterEnv.js +18 -0
- package/dist/utils/openrouterModelVendor.d.ts +10 -0
- package/dist/utils/openrouterModelVendor.js +40 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { openRouterCatalog } from '../../openrouter-catalog.js';
|
|
2
|
+
import { resolveModelVendorSlug } from '../../utils/openrouterModelVendor.js';
|
|
2
3
|
import { extractCostUsdFromProviderUsage } from '../../normalization/cost.js';
|
|
3
4
|
import { getReasoningCapabilitiesFromRegistry, hasReasoningParamInCatalog } from './reasoning-capabilities.js';
|
|
4
5
|
/**
|
|
@@ -258,13 +259,12 @@ export class OpenRouterAdapter {
|
|
|
258
259
|
const { requestId, mode, request, exec } = input;
|
|
259
260
|
// Extract model from request (could be in request.model or request.config.model)
|
|
260
261
|
let model = request.model || request.config?.model || 'openai/gpt-4o-mini';
|
|
261
|
-
// Determine the
|
|
262
|
-
// When OpenRouter mode is enabled, the interceptor sets request.config.provider
|
|
263
|
-
// to preserve the original provider name (e.g., "openai", "grok") for model mapping
|
|
262
|
+
// Determine the model vendor slug for normalization (not the OpenRouter transport id).
|
|
264
263
|
const originalProvider = request.config?.provider;
|
|
264
|
+
const modelVendor = resolveModelVendorSlug(model, originalProvider);
|
|
265
265
|
// Normalize model name for OpenRouter using catalog data
|
|
266
266
|
// (e.g., "gpt-4o" + provider="openai" → "openai/gpt-4o")
|
|
267
|
-
model = await this.normalizeModelName(model,
|
|
267
|
+
model = await this.normalizeModelName(model, modelVendor);
|
|
268
268
|
// Validate model is available in OpenRouter catalog
|
|
269
269
|
try {
|
|
270
270
|
const isAvailable = await openRouterCatalog.isModelAvailable(model);
|
package/dist/factory.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LLMProviderRouter } from './router/RouterWrapper.js';
|
|
2
2
|
import dns from 'node:dns';
|
|
3
|
+
import { resolveOpenRouterApiKey, OPENROUTER_API_KEY_ERC } from './utils/openrouterEnv.js';
|
|
3
4
|
import dotenv from 'dotenv';
|
|
4
5
|
// Fix IPv6-first DNS resolution issue on Windows (forces IPv4-first to avoid connect timeouts)
|
|
5
6
|
// This prevents undici from trying IPv6 first on networks that silently drop IPv6 traffic
|
|
@@ -114,9 +115,9 @@ export async function createRouter(config) {
|
|
|
114
115
|
apiKey: 'ENV.GOOGLE_API_KEY',
|
|
115
116
|
},
|
|
116
117
|
openrouter: {
|
|
117
|
-
apiKey:
|
|
118
|
-
httpReferer: 'ENV.OPEN_ROUTER_HTTP_REFERER',
|
|
119
|
-
xTitle: 'ENV.OPEN_ROUTER_X_TITLE',
|
|
118
|
+
apiKey: OPENROUTER_API_KEY_ERC,
|
|
119
|
+
httpReferer: 'ENV.OPENROUTER_HTTP_REFERER||ENV.OPEN_ROUTER_HTTP_REFERER',
|
|
120
|
+
xTitle: 'ENV.OPENROUTER_X_TITLE||ENV.OPEN_ROUTER_X_TITLE',
|
|
120
121
|
useOpenRouter: 'ENV.USE_OPENROUTER',
|
|
121
122
|
},
|
|
122
123
|
},
|
|
@@ -125,6 +126,10 @@ export async function createRouter(config) {
|
|
|
125
126
|
const ercResult = {
|
|
126
127
|
config: resolveEnvPlaceholders(ercConfig)
|
|
127
128
|
};
|
|
129
|
+
const resolvedOpenRouterKey = resolveOpenRouterApiKey();
|
|
130
|
+
if (resolvedOpenRouterKey && ercResult.config.providers?.openrouter) {
|
|
131
|
+
ercResult.config.providers.openrouter.apiKey = resolvedOpenRouterKey;
|
|
132
|
+
}
|
|
128
133
|
// Use explicit config if provided (Advanced Mode), otherwise use ERC auto-discovered config (Zero-Config Mode)
|
|
129
134
|
const finalConfig = Object.keys(config || {}).length > 0 ? config : {
|
|
130
135
|
logLevel: ercResult.config.router.logLevel,
|
|
@@ -136,13 +141,14 @@ export async function createRouter(config) {
|
|
|
136
141
|
// Configure providers from ERC auto-detected config OR explicit config
|
|
137
142
|
const providerConfigs = config?.providerConfigs || ercResult.config.providers;
|
|
138
143
|
// Check if OpenRouter mode should be enabled
|
|
139
|
-
// Default: true if OPEN_ROUTER_KEY is present, else false
|
|
144
|
+
// Default: true if OPENROUTER_API_KEY (or legacy OPEN_ROUTER_KEY) is present, else false
|
|
140
145
|
// Can be explicitly disabled with USE_OPENROUTER=false
|
|
141
|
-
const openRouterKey = providerConfigs.openrouter?.apiKey;
|
|
146
|
+
const openRouterKey = providerConfigs.openrouter?.apiKey ?? resolvedOpenRouterKey;
|
|
142
147
|
const useOpenRouterEnv = providerConfigs.openrouter?.useOpenRouter;
|
|
143
148
|
// Check if API key is valid (not placeholder, not empty)
|
|
144
149
|
const hasValidKey = openRouterKey &&
|
|
145
150
|
openRouterKey !== 'ENV.OPEN_ROUTER_KEY' &&
|
|
151
|
+
openRouterKey !== OPENROUTER_API_KEY_ERC &&
|
|
146
152
|
typeof openRouterKey === 'string' &&
|
|
147
153
|
openRouterKey.trim() !== '';
|
|
148
154
|
// Determine if OpenRouter should be enabled
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { getDirname } from './utils/esm-compat.js';
|
|
4
|
+
import { inferVendorSlugFromBareModel, isOpenRouterTransportProvider, resolveModelVendorSlug, } from './utils/openrouterModelVendor.js';
|
|
4
5
|
// Get __dirname equivalent for ESM
|
|
5
6
|
const __dirname = getDirname(import.meta.url);
|
|
6
7
|
/**
|
|
@@ -184,43 +185,51 @@ export class OpenRouterCatalogLoader {
|
|
|
184
185
|
*/
|
|
185
186
|
async normalizeModelName(modelName, providerHint) {
|
|
186
187
|
const models = await this.getModels();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
const trimmed = modelName.trim();
|
|
189
|
+
const vendorHint = isOpenRouterTransportProvider(providerHint) ? undefined : providerHint;
|
|
190
|
+
// Already vendor-qualified (`openai/gpt-5.4`, etc.)
|
|
191
|
+
if (trimmed.includes('/')) {
|
|
192
|
+
const catalogMatch = models.find((m) => m.openrouterId === trimmed);
|
|
193
|
+
if (catalogMatch)
|
|
194
|
+
return catalogMatch.openrouterId;
|
|
195
|
+
const aliasMatch = models.find((m) => m.aliases.includes(trimmed));
|
|
196
|
+
if (aliasMatch)
|
|
197
|
+
return aliasMatch.openrouterId;
|
|
198
|
+
const [prefix] = trimmed.split('/');
|
|
199
|
+
if (prefix && !isOpenRouterTransportProvider(prefix)) {
|
|
200
|
+
const isKnownVendor = (await this.findProviderBySlug(prefix)) != null ||
|
|
201
|
+
(await this.findProviderByVendorId(prefix)) != null;
|
|
202
|
+
if (isKnownVendor)
|
|
203
|
+
return trimmed;
|
|
192
204
|
}
|
|
193
|
-
//
|
|
205
|
+
// Never double-prefix vendor/model strings (e.g. openrouter + openai/gpt-5.4)
|
|
206
|
+
return trimmed;
|
|
194
207
|
}
|
|
195
|
-
//
|
|
196
|
-
const aliasMatch = models.find(m => m.aliases.includes(
|
|
197
|
-
if (aliasMatch)
|
|
208
|
+
// Bare alias (`gpt-4o`, tier keys resolved upstream, etc.)
|
|
209
|
+
const aliasMatch = models.find((m) => m.aliases.includes(trimmed));
|
|
210
|
+
if (aliasMatch)
|
|
198
211
|
return aliasMatch.openrouterId;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
if (model) {
|
|
212
|
+
const resolvedVendor = resolveModelVendorSlug(trimmed, providerHint) ?? vendorHint;
|
|
213
|
+
if (resolvedVendor) {
|
|
214
|
+
const candidateId = `${resolvedVendor}/${trimmed}`;
|
|
215
|
+
const model = models.find((m) => m.openrouterId === candidateId);
|
|
216
|
+
if (model)
|
|
205
217
|
return model.openrouterId;
|
|
206
|
-
|
|
218
|
+
return candidateId;
|
|
207
219
|
}
|
|
208
|
-
|
|
209
|
-
const inferredProvider = await this.inferProviderFromModel(modelName);
|
|
220
|
+
const inferredProvider = await this.inferProviderFromModel(trimmed);
|
|
210
221
|
if (inferredProvider) {
|
|
211
|
-
const candidateId = `${inferredProvider}/${
|
|
212
|
-
const model = models.find(m => m.openrouterId === candidateId);
|
|
213
|
-
if (model)
|
|
222
|
+
const candidateId = `${inferredProvider}/${trimmed}`;
|
|
223
|
+
const model = models.find((m) => m.openrouterId === candidateId);
|
|
224
|
+
if (model)
|
|
214
225
|
return model.openrouterId;
|
|
215
|
-
|
|
226
|
+
return candidateId;
|
|
216
227
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return `${providerHint}/${modelName}`;
|
|
228
|
+
const patternVendor = inferVendorSlugFromBareModel(trimmed);
|
|
229
|
+
if (patternVendor) {
|
|
230
|
+
return `${patternVendor}/${trimmed}`;
|
|
221
231
|
}
|
|
222
|
-
|
|
223
|
-
return modelName;
|
|
232
|
+
return trimmed;
|
|
224
233
|
}
|
|
225
234
|
}
|
|
226
235
|
// Export singleton instance
|
package/dist/router/Router.js
CHANGED
|
@@ -3,6 +3,7 @@ import { applyResponseNormalization } from '../normalization/applyResponseNormal
|
|
|
3
3
|
import { extractCostUsdFromRouterResponse } from '../normalization/cost.js';
|
|
4
4
|
import { FallbackExhaustedError } from '../errors.js';
|
|
5
5
|
import { buildCandidateRequest, buildFallbackCandidates, isRetryableError, summarizeError, toError, } from './fallbackUtils.js';
|
|
6
|
+
import { hasOpenRouterApiKey } from '../utils/openrouterEnv.js';
|
|
6
7
|
import { attachPartialRouterPayload, buildPartialRouterPayload } from './partialErrorPayload.js';
|
|
7
8
|
/**
|
|
8
9
|
* Main router class
|
|
@@ -36,10 +37,8 @@ export class AIRouter {
|
|
|
36
37
|
resolveProviderName(input) {
|
|
37
38
|
const hasOpenRouterAdapter = this.adapters.has('openrouter');
|
|
38
39
|
const hasOpenRouterProvider = this.providers.has('openrouter');
|
|
39
|
-
// Check if
|
|
40
|
-
const hasOpenRouterKey =
|
|
41
|
-
process.env.OPEN_ROUTER_KEY.trim() !== '' &&
|
|
42
|
-
!process.env.OPEN_ROUTER_KEY.startsWith('ENV.'));
|
|
40
|
+
// Check if OpenRouter API key is set (OPENROUTER_API_KEY canonical; OPEN_ROUTER_KEY legacy)
|
|
41
|
+
const hasOpenRouterKey = hasOpenRouterApiKey();
|
|
43
42
|
// Normalize config.provider (handle string, trim whitespace)
|
|
44
43
|
const cfgProviderRaw = input.request?.config?.provider;
|
|
45
44
|
const cfgProvider = typeof cfgProviderRaw === 'string' ? cfgProviderRaw.trim() : undefined;
|
|
@@ -71,7 +70,7 @@ export class AIRouter {
|
|
|
71
70
|
// Check if OpenRouter mode could work automatically
|
|
72
71
|
if (hasOpenRouterAdapter && !hasOpenRouterKey) {
|
|
73
72
|
throw new Error(`Provider '${cfgProvider}' specified in request.config.provider but not registered. ` +
|
|
74
|
-
`OpenRouter adapter is available - set
|
|
73
|
+
`OpenRouter adapter is available - set OPENROUTER_API_KEY environment variable to enable automatic OpenRouter mode. ` +
|
|
75
74
|
`Available providers: ${available || '(none)'}`);
|
|
76
75
|
}
|
|
77
76
|
throw new Error(`Provider '${cfgProvider}' specified in request.config.provider but not registered. Available providers: ${available || '(none)'}`);
|
|
@@ -90,10 +89,10 @@ export class AIRouter {
|
|
|
90
89
|
if (hasProviderInConfig && hasOpenRouterAdapter && !hasRegisteredProviders) {
|
|
91
90
|
if (!hasOpenRouterKey) {
|
|
92
91
|
throw new Error('OpenRouter adapter available and config.provider specified, but OpenRouter provider module not registered. ' +
|
|
93
|
-
'Set
|
|
92
|
+
'Set OPENROUTER_API_KEY environment variable to enable automatic OpenRouter mode (works with both createRouter() and manual initialization).');
|
|
94
93
|
}
|
|
95
94
|
throw new Error('OpenRouter adapter available and config.provider specified, but OpenRouter provider module not registered. ' +
|
|
96
|
-
'
|
|
95
|
+
'OPENROUTER_API_KEY is set but provider module initialization failed. Check that @x12i/ai-provider-openai is installed.');
|
|
97
96
|
}
|
|
98
97
|
if (hasProviderInConfig && !hasOpenRouterAdapter) {
|
|
99
98
|
throw new Error(`Provider '${input.request?.config?.provider}' specified in config but no providers registered and OpenRouter adapter not available.`);
|
|
@@ -101,7 +100,7 @@ export class AIRouter {
|
|
|
101
100
|
// Final fallback error with OpenRouter suggestion
|
|
102
101
|
if (hasOpenRouterAdapter && !hasOpenRouterKey) {
|
|
103
102
|
throw new Error('No provider specified and no providers registered. ' +
|
|
104
|
-
'OpenRouter adapter is available - set
|
|
103
|
+
'OpenRouter adapter is available - set OPENROUTER_API_KEY environment variable to enable automatic OpenRouter mode.');
|
|
105
104
|
}
|
|
106
105
|
throw new Error('No provider specified and no providers registered');
|
|
107
106
|
}
|
|
@@ -5,6 +5,8 @@ import { OpenAIAdapter } from '../adapters/openai/OpenAIAdapter.js';
|
|
|
5
5
|
import { GrokAdapter } from '../adapters/grok/GrokAdapter.js';
|
|
6
6
|
import { OpenRouterAdapter } from '../adapters/openrouter/OpenRouterAdapter.js';
|
|
7
7
|
import { getLogger } from '../logger.js';
|
|
8
|
+
import { resolveOpenRouterApiKey, OPENROUTER_API_KEY_ERC } from '../utils/openrouterEnv.js';
|
|
9
|
+
import { resolveModelVendorSlug } from '../utils/openrouterModelVendor.js';
|
|
8
10
|
/**
|
|
9
11
|
* Resolve ENV. placeholders to actual environment variable values
|
|
10
12
|
* Replaces nx-config2 functionality to avoid ESM/CommonJS compatibility issues
|
|
@@ -373,9 +375,18 @@ export class LLMProviderRouter {
|
|
|
373
375
|
* Used by ensureProvidersRegistered() and by the OpenRouter retry path when the first run skipped registration.
|
|
374
376
|
*/
|
|
375
377
|
async tryRegisterOpenRouter(config) {
|
|
376
|
-
const
|
|
378
|
+
const configuredKey = this.config?.openrouter?.apiKey &&
|
|
379
|
+
typeof this.config.openrouter.apiKey === 'string' &&
|
|
380
|
+
!this.config.openrouter.apiKey.startsWith('ENV.')
|
|
377
381
|
? this.config.openrouter.apiKey
|
|
378
|
-
:
|
|
382
|
+
: undefined;
|
|
383
|
+
const resolvedConfigKey = config.openrouter?.apiKey &&
|
|
384
|
+
typeof config.openrouter.apiKey === 'string' &&
|
|
385
|
+
!config.openrouter.apiKey.startsWith('ENV.') &&
|
|
386
|
+
!config.openrouter.apiKey.includes('||')
|
|
387
|
+
? config.openrouter.apiKey
|
|
388
|
+
: undefined;
|
|
389
|
+
const openRouterKey = configuredKey ?? resolvedConfigKey ?? resolveOpenRouterApiKey();
|
|
379
390
|
const hasOpenRouterKey = openRouterKey &&
|
|
380
391
|
typeof openRouterKey === 'string' &&
|
|
381
392
|
openRouterKey.trim() !== '' &&
|
|
@@ -409,27 +420,29 @@ export class LLMProviderRouter {
|
|
|
409
420
|
newRequest.request.config.provider = callerProvider;
|
|
410
421
|
return newRequest;
|
|
411
422
|
}
|
|
412
|
-
let effectiveProvider = originalProvider;
|
|
413
|
-
|
|
414
|
-
|
|
423
|
+
let effectiveProvider = originalProvider ?? callerProvider;
|
|
424
|
+
const model = newRequest.request.config?.model || newRequest.request?.model;
|
|
425
|
+
const inferredFromModel = typeof model === 'string' ? resolveModelVendorSlug(model, effectiveProvider) : undefined;
|
|
426
|
+
if (inferredFromModel) {
|
|
427
|
+
effectiveProvider = inferredFromModel;
|
|
428
|
+
}
|
|
429
|
+
else if (!effectiveProvider || effectiveProvider === 'openrouter') {
|
|
415
430
|
if (model && typeof model === 'string') {
|
|
416
431
|
if (model.startsWith('gpt-') || model.startsWith('openai/'))
|
|
417
432
|
effectiveProvider = 'openai';
|
|
418
433
|
else if (model.startsWith('claude-') || model.startsWith('anthropic/'))
|
|
419
434
|
effectiveProvider = 'anthropic';
|
|
420
435
|
else if (model.startsWith('grok-') || model.startsWith('xai/'))
|
|
421
|
-
effectiveProvider = '
|
|
436
|
+
effectiveProvider = 'x-ai';
|
|
422
437
|
else if (model.startsWith('gemini-') || model.startsWith('google/'))
|
|
423
438
|
effectiveProvider = 'google';
|
|
424
439
|
}
|
|
425
|
-
if (!effectiveProvider)
|
|
440
|
+
if (!effectiveProvider || effectiveProvider === 'openrouter') {
|
|
426
441
|
effectiveProvider = 'openai';
|
|
442
|
+
}
|
|
427
443
|
}
|
|
428
444
|
newRequest.request.config.provider = effectiveProvider;
|
|
429
|
-
|
|
430
|
-
this.logger?.warn?.(`[OpenRouterProxy] Forcing provider=openrouter (effectiveProvider=${effectiveProvider}, originalProvider=${originalProvider ?? newRequest.provider}, model=${newRequest.request?.config?.model ?? newRequest.request?.model ?? 'unknown'})`);
|
|
431
|
-
newRequest.provider = 'openrouter';
|
|
432
|
-
}
|
|
445
|
+
newRequest.provider = 'openrouter';
|
|
433
446
|
return newRequest;
|
|
434
447
|
});
|
|
435
448
|
this.logger.info('Auto-registered OpenRouter provider and enabled OpenRouter mode');
|
|
@@ -450,8 +463,11 @@ export class LLMProviderRouter {
|
|
|
450
463
|
if (this.providerRegistry.has('openrouter'))
|
|
451
464
|
return;
|
|
452
465
|
// Retry OpenRouter only: key may be available now (e.g. from this.config passed by gateway)
|
|
453
|
-
const detectionConfig = { providers: { openrouter: { apiKey:
|
|
466
|
+
const detectionConfig = { providers: { openrouter: { apiKey: OPENROUTER_API_KEY_ERC } } };
|
|
454
467
|
const resolved = resolveEnvPlaceholders(detectionConfig);
|
|
468
|
+
if (resolved.providers?.openrouter && resolveOpenRouterApiKey()) {
|
|
469
|
+
resolved.providers.openrouter.apiKey = resolveOpenRouterApiKey();
|
|
470
|
+
}
|
|
455
471
|
await this.tryRegisterOpenRouter(resolved.providers);
|
|
456
472
|
return;
|
|
457
473
|
}
|
|
@@ -468,12 +484,15 @@ export class LLMProviderRouter {
|
|
|
468
484
|
baseURL: 'ENV.XAI_API_BASE',
|
|
469
485
|
},
|
|
470
486
|
openrouter: {
|
|
471
|
-
apiKey:
|
|
487
|
+
apiKey: OPENROUTER_API_KEY_ERC,
|
|
472
488
|
},
|
|
473
489
|
}
|
|
474
490
|
};
|
|
475
491
|
const resolvedConfig = resolveEnvPlaceholders(detectionConfig);
|
|
476
492
|
const config = resolvedConfig.providers;
|
|
493
|
+
if (config.openrouter && resolveOpenRouterApiKey()) {
|
|
494
|
+
config.openrouter.apiKey = resolveOpenRouterApiKey();
|
|
495
|
+
}
|
|
477
496
|
await this.tryRegisterOpenRouter(config);
|
|
478
497
|
// If OpenRouter is being used, we SKIP auto-registering other providers
|
|
479
498
|
// to avoid global state conflicts (e.g. ai-provider-openai singleton config).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical OpenRouter API key env var is OPENROUTER_API_KEY (ai-tools / gateway).
|
|
3
|
+
* OPEN_ROUTER_KEY is accepted for backward compatibility only.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveOpenRouterApiKey(): string | undefined;
|
|
6
|
+
export declare function hasOpenRouterApiKey(): boolean;
|
|
7
|
+
/** ERC placeholder resolution: prefer canonical name, fall back to legacy. */
|
|
8
|
+
export declare const OPENROUTER_API_KEY_ERC = "ENV.OPENROUTER_API_KEY||ENV.OPEN_ROUTER_KEY";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical OpenRouter API key env var is OPENROUTER_API_KEY (ai-tools / gateway).
|
|
3
|
+
* OPEN_ROUTER_KEY is accepted for backward compatibility only.
|
|
4
|
+
*/
|
|
5
|
+
export function resolveOpenRouterApiKey() {
|
|
6
|
+
for (const envVar of ['OPENROUTER_API_KEY', 'OPEN_ROUTER_KEY']) {
|
|
7
|
+
const value = process.env[envVar];
|
|
8
|
+
if (typeof value === 'string' && value.trim() !== '' && !value.startsWith('ENV.')) {
|
|
9
|
+
return value.trim();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function hasOpenRouterApiKey() {
|
|
15
|
+
return resolveOpenRouterApiKey() !== undefined;
|
|
16
|
+
}
|
|
17
|
+
/** ERC placeholder resolution: prefer canonical name, fall back to legacy. */
|
|
18
|
+
export const OPENROUTER_API_KEY_ERC = 'ENV.OPENROUTER_API_KEY||ENV.OPEN_ROUTER_KEY';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** OpenRouter module id — transport, not a model vendor slug. */
|
|
2
|
+
export declare const OPENROUTER_TRANSPORT_PROVIDER = "openrouter";
|
|
3
|
+
export declare function isOpenRouterTransportProvider(provider?: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the model vendor slug used in OpenRouter ids (`vendor/model`).
|
|
6
|
+
* When config.provider is `openrouter`, infer from the model string instead.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveModelVendorSlug(model: string, providerHint?: string): string | undefined;
|
|
9
|
+
/** Pattern fallback for bare model ids not yet in the bundled catalog. */
|
|
10
|
+
export declare function inferVendorSlugFromBareModel(modelName: string): string | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** OpenRouter module id — transport, not a model vendor slug. */
|
|
2
|
+
export const OPENROUTER_TRANSPORT_PROVIDER = 'openrouter';
|
|
3
|
+
export function isOpenRouterTransportProvider(provider) {
|
|
4
|
+
return provider === OPENROUTER_TRANSPORT_PROVIDER;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the model vendor slug used in OpenRouter ids (`vendor/model`).
|
|
8
|
+
* When config.provider is `openrouter`, infer from the model string instead.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveModelVendorSlug(model, providerHint) {
|
|
11
|
+
const trimmed = typeof model === 'string' ? model.trim() : '';
|
|
12
|
+
if (!trimmed)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (trimmed.includes('/')) {
|
|
15
|
+
const prefix = trimmed.split('/')[0]?.trim();
|
|
16
|
+
if (prefix && !isOpenRouterTransportProvider(prefix)) {
|
|
17
|
+
return prefix;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (providerHint && !isOpenRouterTransportProvider(providerHint)) {
|
|
22
|
+
return providerHint;
|
|
23
|
+
}
|
|
24
|
+
return inferVendorSlugFromBareModel(trimmed);
|
|
25
|
+
}
|
|
26
|
+
/** Pattern fallback for bare model ids not yet in the bundled catalog. */
|
|
27
|
+
export function inferVendorSlugFromBareModel(modelName) {
|
|
28
|
+
if (modelName.startsWith('gpt-') || modelName.startsWith('o1-') || modelName.startsWith('o3-') || modelName.startsWith('o4-')) {
|
|
29
|
+
return 'openai';
|
|
30
|
+
}
|
|
31
|
+
if (modelName.startsWith('claude-'))
|
|
32
|
+
return 'anthropic';
|
|
33
|
+
if (modelName.startsWith('grok-'))
|
|
34
|
+
return 'x-ai';
|
|
35
|
+
if (modelName.startsWith('gemini-'))
|
|
36
|
+
return 'google';
|
|
37
|
+
if (modelName.startsWith('llama-'))
|
|
38
|
+
return 'meta-llama';
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|