@relayplane/proxy 1.8.23 → 1.8.26
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone-proxy.d.ts","sourceRoot":"","sources":["../src/standalone-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,OAAO,KAAK,EAAE,QAAQ,IAAI,YAAY,EAAY,MAAM,kBAAkB,CAAC;AAE3E,KAAK,QAAQ,GAAG,YAAY,GACxB,YAAY,GACZ,UAAU,GACV,MAAM,GACN,SAAS,GACT,UAAU,GACV,WAAW,GACX,YAAY,GACZ,QAAQ,CAAC;AAMb,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA8E5C,2DAA2D;AAC3D,eAAO,MAAM,mBAAmB,gBAAuB,CAAC;AAuBxD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAiD9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"standalone-proxy.d.ts","sourceRoot":"","sources":["../src/standalone-proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/standalone-proxy.js
CHANGED
|
@@ -219,6 +219,41 @@ exports.MODEL_MAPPING = {
|
|
|
219
219
|
'gpt-4o': { provider: 'openai', model: 'gpt-4o' },
|
|
220
220
|
'gpt-4o-mini': { provider: 'openai', model: 'gpt-4o-mini' },
|
|
221
221
|
'gpt-4.1': { provider: 'openai', model: 'gpt-4.1' },
|
|
222
|
+
// OpenAI GPT-5 family
|
|
223
|
+
'gpt-5.4': { provider: 'openai', model: 'gpt-5.4' },
|
|
224
|
+
'gpt-5.4-pro': { provider: 'openai', model: 'gpt-5.4-pro' },
|
|
225
|
+
'gpt-5.3': { provider: 'openai', model: 'gpt-5.3-chat' },
|
|
226
|
+
'gpt-5.2': { provider: 'openai', model: 'gpt-5.2' },
|
|
227
|
+
'gpt-5.1': { provider: 'openai', model: 'gpt-5.1' },
|
|
228
|
+
'gpt-5': { provider: 'openai', model: 'gpt-5.4' },
|
|
229
|
+
'gpt-5-mini': { provider: 'openai', model: 'gpt-5-mini' },
|
|
230
|
+
'gpt-5-nano': { provider: 'openai', model: 'gpt-5-nano' },
|
|
231
|
+
// OpenAI GPT-4.1
|
|
232
|
+
'gpt-4.1-mini': { provider: 'openai', model: 'gpt-4.1-mini' },
|
|
233
|
+
'gpt-4.1-nano': { provider: 'openai', model: 'gpt-4.1-nano' },
|
|
234
|
+
// OpenAI O-series reasoning
|
|
235
|
+
'o3': { provider: 'openai', model: 'o3' },
|
|
236
|
+
'o3-pro': { provider: 'openai', model: 'o3-pro' },
|
|
237
|
+
'o3-mini': { provider: 'openai', model: 'o3-mini' },
|
|
238
|
+
'o4-mini': { provider: 'openai', model: 'o4-mini' },
|
|
239
|
+
// Google Gemini
|
|
240
|
+
'gemini-3.1-pro': { provider: 'google', model: 'gemini-3.1-pro-preview' },
|
|
241
|
+
'gemini-3-pro': { provider: 'google', model: 'gemini-3-pro-preview' },
|
|
242
|
+
'gemini-3-flash': { provider: 'google', model: 'gemini-3-flash-preview' },
|
|
243
|
+
'gemini-2.5-pro': { provider: 'google', model: 'gemini-2.5-pro' },
|
|
244
|
+
'gemini-2.5-flash': { provider: 'google', model: 'gemini-2.5-flash' },
|
|
245
|
+
'gemini-2.5-flash-lite': { provider: 'google', model: 'gemini-2.5-flash-lite' },
|
|
246
|
+
'gemini-2.0-flash': { provider: 'google', model: 'gemini-2.0-flash' },
|
|
247
|
+
// xAI Grok
|
|
248
|
+
'grok-4.20': { provider: 'xai', model: 'grok-4.20-beta' },
|
|
249
|
+
'grok-4': { provider: 'xai', model: 'grok-4' },
|
|
250
|
+
'grok-4-fast': { provider: 'xai', model: 'grok-4-fast' },
|
|
251
|
+
'grok-4.1-fast': { provider: 'xai', model: 'grok-4.1-fast' },
|
|
252
|
+
'grok-3': { provider: 'xai', model: 'grok-3' },
|
|
253
|
+
'grok-3-mini': { provider: 'xai', model: 'grok-3-mini' },
|
|
254
|
+
// DeepSeek
|
|
255
|
+
'deepseek': { provider: 'deepseek', model: 'deepseek-chat' },
|
|
256
|
+
'deepseek-r1': { provider: 'deepseek', model: 'deepseek-reasoner' },
|
|
222
257
|
};
|
|
223
258
|
/**
|
|
224
259
|
* RelayPlane model aliases - resolve before routing
|
|
@@ -236,9 +271,9 @@ exports.RELAYPLANE_ALIASES = {
|
|
|
236
271
|
*/
|
|
237
272
|
exports.SMART_ALIASES = {
|
|
238
273
|
// Defaults: OpenRouter (used when no env vars are available)
|
|
239
|
-
'rp:best': { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-
|
|
274
|
+
'rp:best': { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-6' },
|
|
240
275
|
'rp:fast': { provider: 'openrouter', model: 'anthropic/claude-3-5-haiku' },
|
|
241
|
-
'rp:cheap': { provider: 'openrouter', model: 'google/gemini-2.
|
|
276
|
+
'rp:cheap': { provider: 'openrouter', model: 'google/gemini-2.5-flash-lite' },
|
|
242
277
|
'rp:balanced': { provider: 'openrouter', model: 'anthropic/claude-3-5-haiku' },
|
|
243
278
|
};
|
|
244
279
|
/**
|
|
@@ -251,9 +286,9 @@ function buildSmartAliases() {
|
|
|
251
286
|
return {
|
|
252
287
|
via: 'openrouter',
|
|
253
288
|
aliases: {
|
|
254
|
-
'rp:best': { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-
|
|
289
|
+
'rp:best': { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-6' },
|
|
255
290
|
'rp:fast': { provider: 'openrouter', model: 'anthropic/claude-3-5-haiku' },
|
|
256
|
-
'rp:cheap': { provider: 'openrouter', model: 'google/gemini-2.
|
|
291
|
+
'rp:cheap': { provider: 'openrouter', model: 'google/gemini-2.5-flash-lite' },
|
|
257
292
|
'rp:balanced': { provider: 'openrouter', model: 'anthropic/claude-3-5-haiku' },
|
|
258
293
|
},
|
|
259
294
|
};
|
|
@@ -284,9 +319,9 @@ function buildSmartAliases() {
|
|
|
284
319
|
return {
|
|
285
320
|
via: 'openrouter (fallback — no API keys detected)',
|
|
286
321
|
aliases: {
|
|
287
|
-
'rp:best': { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-
|
|
322
|
+
'rp:best': { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-6' },
|
|
288
323
|
'rp:fast': { provider: 'openrouter', model: 'anthropic/claude-3-5-haiku' },
|
|
289
|
-
'rp:cheap': { provider: 'openrouter', model: 'google/gemini-2.
|
|
324
|
+
'rp:cheap': { provider: 'openrouter', model: 'google/gemini-2.5-flash-lite' },
|
|
290
325
|
'rp:balanced': { provider: 'openrouter', model: 'anthropic/claude-3-5-haiku' },
|
|
291
326
|
},
|
|
292
327
|
};
|
|
@@ -365,10 +400,10 @@ function resolveModelAlias(model) {
|
|
|
365
400
|
return model;
|
|
366
401
|
}
|
|
367
402
|
/**
|
|
368
|
-
* Default routing based on task type
|
|
369
|
-
*
|
|
403
|
+
* Default routing based on task type.
|
|
404
|
+
* Updated at proxy startup by provider auto-detection via detectAvailableProviders().
|
|
370
405
|
*/
|
|
371
|
-
|
|
406
|
+
let DEFAULT_ROUTING = {
|
|
372
407
|
code_generation: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
|
|
373
408
|
code_review: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
|
|
374
409
|
summarization: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
|
|
@@ -379,6 +414,119 @@ const DEFAULT_ROUTING = {
|
|
|
379
414
|
question_answering: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
|
|
380
415
|
general: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
|
|
381
416
|
};
|
|
417
|
+
/**
|
|
418
|
+
* Parse a complexity routing config value into a provider/model pair.
|
|
419
|
+
* Accepts:
|
|
420
|
+
* - plain model name string: "claude-sonnet-4-6"
|
|
421
|
+
* - provider/model slash notation: "google/gemini-2.5-flash-lite"
|
|
422
|
+
* - openrouter prefix: "openrouter/anthropic/claude-sonnet-4-6"
|
|
423
|
+
* - object: { provider: "google", model: "gemini-2.5-flash-lite" }
|
|
424
|
+
*/
|
|
425
|
+
function parseComplexityModel(val) {
|
|
426
|
+
if (typeof val === 'object' && val !== null) {
|
|
427
|
+
return val;
|
|
428
|
+
}
|
|
429
|
+
if (typeof val === 'string') {
|
|
430
|
+
if (val.includes('/')) {
|
|
431
|
+
const idx = val.indexOf('/');
|
|
432
|
+
const rawProvider = val.slice(0, idx);
|
|
433
|
+
const model = val.slice(idx + 1); // preserves openrouter/anthropic/claude-... style
|
|
434
|
+
const knownProviders = ['openai', 'anthropic', 'google', 'xai', 'openrouter', 'deepseek', 'groq', 'local', 'ollama'];
|
|
435
|
+
if (!knownProviders.includes(rawProvider)) {
|
|
436
|
+
console.warn(`[parseComplexityModel] Unknown provider "${rawProvider}" in config, falling back to anthropic`);
|
|
437
|
+
return { provider: 'anthropic', model };
|
|
438
|
+
}
|
|
439
|
+
const provider = rawProvider;
|
|
440
|
+
return { provider, model };
|
|
441
|
+
}
|
|
442
|
+
// Plain model name — look up in MODEL_MAPPING, fallback to anthropic
|
|
443
|
+
return exports.MODEL_MAPPING[val] ?? { provider: 'anthropic', model: val };
|
|
444
|
+
}
|
|
445
|
+
return { provider: 'anthropic', model: 'claude-sonnet-4-6' };
|
|
446
|
+
}
|
|
447
|
+
/** Per-provider default complexity tier models */
|
|
448
|
+
const PROVIDER_COMPLEXITY_TIERS = {
|
|
449
|
+
anthropic: {
|
|
450
|
+
simple: { provider: 'anthropic', model: 'claude-haiku-4-5' },
|
|
451
|
+
moderate: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
|
|
452
|
+
complex: { provider: 'anthropic', model: 'claude-opus-4-6' },
|
|
453
|
+
},
|
|
454
|
+
openai: {
|
|
455
|
+
simple: { provider: 'openai', model: 'gpt-4.1-mini' },
|
|
456
|
+
moderate: { provider: 'openai', model: 'gpt-5.4' },
|
|
457
|
+
complex: { provider: 'openai', model: 'gpt-5.4' },
|
|
458
|
+
},
|
|
459
|
+
google: {
|
|
460
|
+
simple: { provider: 'google', model: 'gemini-2.5-flash-lite' },
|
|
461
|
+
moderate: { provider: 'google', model: 'gemini-2.5-flash' },
|
|
462
|
+
complex: { provider: 'google', model: 'gemini-2.5-pro' },
|
|
463
|
+
},
|
|
464
|
+
xai: {
|
|
465
|
+
simple: { provider: 'xai', model: 'grok-4.1-fast' },
|
|
466
|
+
moderate: { provider: 'xai', model: 'grok-4.20-beta' },
|
|
467
|
+
complex: { provider: 'xai', model: 'grok-4' },
|
|
468
|
+
},
|
|
469
|
+
deepseek: {
|
|
470
|
+
simple: { provider: 'deepseek', model: 'deepseek-chat' },
|
|
471
|
+
moderate: { provider: 'deepseek', model: 'deepseek-chat' },
|
|
472
|
+
complex: { provider: 'deepseek', model: 'deepseek-reasoner' },
|
|
473
|
+
},
|
|
474
|
+
openrouter: {
|
|
475
|
+
simple: { provider: 'openrouter', model: 'google/gemini-2.5-flash-lite' },
|
|
476
|
+
moderate: { provider: 'openrouter', model: 'google/gemini-2.5-flash' },
|
|
477
|
+
complex: { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-6' },
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* Detect which AI providers are available based on env vars and user config.
|
|
482
|
+
* Returns providers in priority order: anthropic > openai > google > xai > deepseek > openrouter > groq
|
|
483
|
+
*/
|
|
484
|
+
function detectAvailableProviders(userConfig) {
|
|
485
|
+
const cfg = (userConfig ?? {});
|
|
486
|
+
const auth = (cfg['auth'] ?? {});
|
|
487
|
+
const available = [];
|
|
488
|
+
if (process.env['ANTHROPIC_API_KEY'] || auth['anthropicApiKey'] || auth['anthropicMaxToken']) {
|
|
489
|
+
available.push('anthropic');
|
|
490
|
+
}
|
|
491
|
+
if (process.env['OPENAI_API_KEY'] || auth['openaiApiKey']) {
|
|
492
|
+
available.push('openai');
|
|
493
|
+
}
|
|
494
|
+
if (process.env['GOOGLE_API_KEY'] || process.env['GEMINI_API_KEY'] || auth['googleApiKey']) {
|
|
495
|
+
available.push('google');
|
|
496
|
+
}
|
|
497
|
+
if (process.env['XAI_API_KEY'] || auth['xaiApiKey']) {
|
|
498
|
+
available.push('xai');
|
|
499
|
+
}
|
|
500
|
+
if (process.env['DEEPSEEK_API_KEY'] || auth['deepseekApiKey']) {
|
|
501
|
+
available.push('deepseek');
|
|
502
|
+
}
|
|
503
|
+
if (process.env['OPENROUTER_API_KEY'] || auth['openrouterApiKey']) {
|
|
504
|
+
available.push('openrouter');
|
|
505
|
+
}
|
|
506
|
+
if (process.env['GROQ_API_KEY'] || auth['groqApiKey']) {
|
|
507
|
+
available.push('groq');
|
|
508
|
+
}
|
|
509
|
+
return available;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Build default complexity tiers based on first detected provider.
|
|
513
|
+
* Config overrides win — only fills in tiers not explicitly set.
|
|
514
|
+
*/
|
|
515
|
+
function buildDefaultComplexityTiers(providers, existing) {
|
|
516
|
+
// Find first provider that has a known tier mapping
|
|
517
|
+
const primaryProvider = providers.find((p) => PROVIDER_COMPLEXITY_TIERS[p]) ?? 'anthropic';
|
|
518
|
+
const defaults = PROVIDER_COMPLEXITY_TIERS[primaryProvider] ?? PROVIDER_COMPLEXITY_TIERS['anthropic'];
|
|
519
|
+
const simple = existing?.simple != null
|
|
520
|
+
? parseComplexityModel(existing.simple)
|
|
521
|
+
: defaults.simple;
|
|
522
|
+
const moderate = existing?.moderate != null
|
|
523
|
+
? parseComplexityModel(existing.moderate)
|
|
524
|
+
: defaults.moderate;
|
|
525
|
+
const complex = existing?.complex != null
|
|
526
|
+
? parseComplexityModel(existing.complex)
|
|
527
|
+
: defaults.complex;
|
|
528
|
+
return { simple, moderate, complex };
|
|
529
|
+
}
|
|
382
530
|
const UNCERTAINTY_PATTERNS = [
|
|
383
531
|
/i'?m not (entirely |completely |really )?sure/i,
|
|
384
532
|
/i don'?t (really |actually )?know/i,
|
|
@@ -1411,6 +1559,44 @@ function convertMessagesToGemini(messages) {
|
|
|
1411
1559
|
}
|
|
1412
1560
|
return geminiContents;
|
|
1413
1561
|
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Recursively strip JSON Schema properties that Gemini rejects but OpenAI/Anthropic accept.
|
|
1564
|
+
* Gemini rejects: patternProperties, additionalProperties (boolean), $schema, definitions, $defs, unevaluatedProperties
|
|
1565
|
+
*/
|
|
1566
|
+
function sanitizeSchemaForGemini(schema, _depth = 0, _nodeCount = { count: 0 }) {
|
|
1567
|
+
// Guard against deeply nested or extremely wide schemas (DoS prevention)
|
|
1568
|
+
if (_depth > 20)
|
|
1569
|
+
return schema;
|
|
1570
|
+
_nodeCount.count++;
|
|
1571
|
+
if (_nodeCount.count > 10000)
|
|
1572
|
+
return schema;
|
|
1573
|
+
if (Array.isArray(schema)) {
|
|
1574
|
+
return schema.map(item => sanitizeSchemaForGemini(item, _depth + 1, _nodeCount));
|
|
1575
|
+
}
|
|
1576
|
+
if (schema !== null && typeof schema === 'object') {
|
|
1577
|
+
const obj = schema;
|
|
1578
|
+
const result = {};
|
|
1579
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1580
|
+
// Strip fields Gemini doesn't support
|
|
1581
|
+
if (key === 'patternProperties')
|
|
1582
|
+
continue;
|
|
1583
|
+
if (key === '$schema')
|
|
1584
|
+
continue;
|
|
1585
|
+
if (key === 'definitions')
|
|
1586
|
+
continue;
|
|
1587
|
+
if (key === '$defs')
|
|
1588
|
+
continue;
|
|
1589
|
+
if (key === 'unevaluatedProperties')
|
|
1590
|
+
continue;
|
|
1591
|
+
// additionalProperties: Gemini only accepts object form, not boolean
|
|
1592
|
+
if (key === 'additionalProperties' && typeof value === 'boolean')
|
|
1593
|
+
continue;
|
|
1594
|
+
result[key] = sanitizeSchemaForGemini(value, _depth + 1, _nodeCount);
|
|
1595
|
+
}
|
|
1596
|
+
return result;
|
|
1597
|
+
}
|
|
1598
|
+
return schema;
|
|
1599
|
+
}
|
|
1414
1600
|
/**
|
|
1415
1601
|
* Forward non-streaming request to Gemini API
|
|
1416
1602
|
*/
|
|
@@ -1436,7 +1622,7 @@ async function forwardToGemini(request, targetModel, apiKey) {
|
|
|
1436
1622
|
functionDeclarations: request.tools.map((t) => ({
|
|
1437
1623
|
name: t.function.name,
|
|
1438
1624
|
description: t.function.description || "",
|
|
1439
|
-
parameters: t.function.parameters || {}
|
|
1625
|
+
parameters: sanitizeSchemaForGemini(t.function.parameters || {})
|
|
1440
1626
|
}))
|
|
1441
1627
|
}];
|
|
1442
1628
|
}
|
|
@@ -1474,7 +1660,7 @@ async function forwardToGeminiStream(request, targetModel, apiKey) {
|
|
|
1474
1660
|
functionDeclarations: request.tools.map((t) => ({
|
|
1475
1661
|
name: t.function.name,
|
|
1476
1662
|
description: t.function.description || "",
|
|
1477
|
-
parameters: t.function.parameters || {}
|
|
1663
|
+
parameters: sanitizeSchemaForGemini(t.function.parameters || {})
|
|
1478
1664
|
}))
|
|
1479
1665
|
}];
|
|
1480
1666
|
}
|
|
@@ -2235,18 +2421,25 @@ function getCooldownConfig(config) {
|
|
|
2235
2421
|
};
|
|
2236
2422
|
return { ...defaults, ...config.reliability?.cooldowns };
|
|
2237
2423
|
}
|
|
2424
|
+
function complexityValToString(val) {
|
|
2425
|
+
if (val == null)
|
|
2426
|
+
return undefined;
|
|
2427
|
+
if (typeof val === 'string')
|
|
2428
|
+
return val;
|
|
2429
|
+
return `${val.provider}/${val.model}`;
|
|
2430
|
+
}
|
|
2238
2431
|
function getCostModel(config) {
|
|
2239
|
-
return (config.routing?.complexity?.simple ||
|
|
2432
|
+
return (complexityValToString(config.routing?.complexity?.simple) ||
|
|
2240
2433
|
config.routing?.cascade?.models?.[0] ||
|
|
2241
2434
|
'claude-haiku-4-5');
|
|
2242
2435
|
}
|
|
2243
2436
|
function getFastModel(config) {
|
|
2244
|
-
return (config.routing?.complexity?.simple ||
|
|
2437
|
+
return (complexityValToString(config.routing?.complexity?.simple) ||
|
|
2245
2438
|
config.routing?.cascade?.models?.[0] ||
|
|
2246
2439
|
'claude-haiku-4-5');
|
|
2247
2440
|
}
|
|
2248
2441
|
function getQualityModel(config) {
|
|
2249
|
-
return (config.routing?.complexity?.complex ||
|
|
2442
|
+
return (complexityValToString(config.routing?.complexity?.complex) ||
|
|
2250
2443
|
config.routing?.cascade?.models?.[config.routing?.cascade?.models?.length ? config.routing.cascade.models.length - 1 : 0] ||
|
|
2251
2444
|
process.env['RELAYPLANE_QUALITY_MODEL'] ||
|
|
2252
2445
|
'claude-sonnet-4-6');
|
|
@@ -2604,61 +2797,72 @@ async function startProxy(config = {}) {
|
|
|
2604
2797
|
log(`[CROSS-CASCADE] Enabled. Provider order: ${proxyConfig.crossProviderCascade.providers.join(' → ')}`);
|
|
2605
2798
|
}
|
|
2606
2799
|
const isFirstRun = !rawFileHasRouting || !userConfig.first_run_complete;
|
|
2800
|
+
// Always detect available providers and update DEFAULT_ROUTING at startup
|
|
2801
|
+
const availableProviders = detectAvailableProviders(userConfig);
|
|
2802
|
+
{
|
|
2803
|
+
// Build human-readable provider labels for startup log
|
|
2804
|
+
const providerLabels = availableProviders.map((p) => {
|
|
2805
|
+
if (p === 'anthropic') {
|
|
2806
|
+
const key = process.env['ANTHROPIC_API_KEY'] || '';
|
|
2807
|
+
return key.startsWith('sk-ant-api') ? '✓ Anthropic' : '✓ Anthropic (Max)';
|
|
2808
|
+
}
|
|
2809
|
+
return `✓ ${p.charAt(0).toUpperCase() + p.slice(1)}`;
|
|
2810
|
+
});
|
|
2811
|
+
if (providerLabels.length > 0) {
|
|
2812
|
+
console.log(`[RelayPlane] ${providerLabels.join(', ')}`);
|
|
2813
|
+
}
|
|
2814
|
+
// Build default tiers, respecting any existing user config overrides
|
|
2815
|
+
const existingComplexity = proxyConfig.routing?.complexity;
|
|
2816
|
+
const defaultProviders = availableProviders.length > 0 ? availableProviders : ['openrouter'];
|
|
2817
|
+
const tiers = buildDefaultComplexityTiers(defaultProviders, existingComplexity);
|
|
2818
|
+
// Update DEFAULT_ROUTING with detected provider's moderate tier
|
|
2819
|
+
const moderateRoute = tiers.moderate;
|
|
2820
|
+
const allTaskTypes = ['code_generation', 'code_review', 'summarization', 'analysis', 'creative_writing', 'data_extraction', 'translation', 'question_answering', 'general'];
|
|
2821
|
+
for (const tt of allTaskTypes) {
|
|
2822
|
+
DEFAULT_ROUTING[tt] = moderateRoute;
|
|
2823
|
+
}
|
|
2824
|
+
console.log(`[RelayPlane] Auto-routing: simple=${tiers.simple.model}, moderate=${tiers.moderate.model}, complex=${tiers.complex.model}`);
|
|
2825
|
+
}
|
|
2607
2826
|
if (isFirstRun || proxyConfig.routing?.mode === 'auto') {
|
|
2608
2827
|
const envAnthropicKey = process.env['ANTHROPIC_API_KEY'];
|
|
2609
2828
|
const hasRegularApiKey = !!envAnthropicKey && envAnthropicKey.startsWith('sk-ant-api');
|
|
2610
|
-
if (
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
}
|
|
2829
|
+
if (isFirstRun) {
|
|
2830
|
+
let existingRaw = {};
|
|
2831
|
+
try {
|
|
2832
|
+
existingRaw = JSON.parse(await fs.promises.readFile(configPath, 'utf8'));
|
|
2833
|
+
}
|
|
2834
|
+
catch { /* fresh start, no existing config */ }
|
|
2835
|
+
let autoComplexity;
|
|
2836
|
+
if (availableProviders.includes('anthropic') && hasRegularApiKey) {
|
|
2837
|
+
// Full Anthropic API key — enable haiku 3-tier routing
|
|
2838
|
+
console.log('[RelayPlane] Auto-config: ANTHROPIC_API_KEY detected — enabling 3-tier routing (haiku/sonnet/opus)');
|
|
2839
|
+
autoComplexity = { simple: 'claude-haiku-4-5', moderate: 'claude-sonnet-4-6', complex: 'claude-opus-4-6' };
|
|
2840
|
+
}
|
|
2841
|
+
else if (availableProviders.length > 0 && !availableProviders.includes('anthropic')) {
|
|
2842
|
+
// Non-Anthropic provider — use detected provider's tiers
|
|
2843
|
+
const providerTiers = buildDefaultComplexityTiers(availableProviders);
|
|
2844
|
+
console.log(`[RelayPlane] Auto-config: ${availableProviders[0]} detected — enabling provider-aware 3-tier routing`);
|
|
2845
|
+
autoComplexity = {
|
|
2846
|
+
simple: `${providerTiers.simple.provider}/${providerTiers.simple.model}`,
|
|
2847
|
+
moderate: `${providerTiers.moderate.provider}/${providerTiers.moderate.model}`,
|
|
2848
|
+
complex: `${providerTiers.complex.provider}/${providerTiers.complex.model}`,
|
|
2629
2849
|
};
|
|
2630
|
-
const updatedConfig = { ...existingRaw, routing: autoRouting, first_run_complete: true };
|
|
2631
|
-
await fs.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
2632
|
-
await fs.promises.writeFile(configPath, JSON.stringify(updatedConfig, null, 2), 'utf8');
|
|
2633
|
-
proxyConfig = await loadProxyConfig(configPath, log);
|
|
2634
|
-
console.log(`[RelayPlane] Auto-config: wrote 3-tier routing config to ${configPath}`);
|
|
2635
2850
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
// No regular API key — OAuth only or no Anthropic key; skip Haiku (OAuth not supported for Haiku)
|
|
2639
|
-
if (isFirstRun) {
|
|
2851
|
+
else {
|
|
2852
|
+
// OAuth only or no API key — skip Haiku (OAuth not supported for Haiku)
|
|
2640
2853
|
console.warn('[RelayPlane] ⚠️ No ANTHROPIC_API_KEY (sk-ant-api*) — Haiku disabled. Set ANTHROPIC_API_KEY to enable 3-tier routing.');
|
|
2641
|
-
|
|
2642
|
-
try {
|
|
2643
|
-
existingRaw = JSON.parse(await fs.promises.readFile(configPath, 'utf8'));
|
|
2644
|
-
}
|
|
2645
|
-
catch { /* fresh start, no existing config */ }
|
|
2646
|
-
const autoRouting = {
|
|
2647
|
-
mode: 'complexity',
|
|
2648
|
-
cascade: { enabled: false, models: [], escalateOn: 'uncertainty', maxEscalations: 1 },
|
|
2649
|
-
complexity: {
|
|
2650
|
-
enabled: true,
|
|
2651
|
-
simple: 'claude-sonnet-4-6',
|
|
2652
|
-
moderate: 'claude-sonnet-4-6',
|
|
2653
|
-
complex: 'claude-opus-4-6',
|
|
2654
|
-
},
|
|
2655
|
-
};
|
|
2656
|
-
const updatedConfig = { ...existingRaw, routing: autoRouting, first_run_complete: true };
|
|
2657
|
-
await fs.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
2658
|
-
await fs.promises.writeFile(configPath, JSON.stringify(updatedConfig, null, 2), 'utf8');
|
|
2659
|
-
proxyConfig = await loadProxyConfig(configPath, log);
|
|
2660
|
-
console.log(`[RelayPlane] Auto-config: wrote OAuth-safe config to ${configPath} (no Haiku)`);
|
|
2854
|
+
autoComplexity = { simple: 'claude-sonnet-4-6', moderate: 'claude-sonnet-4-6', complex: 'claude-opus-4-6' };
|
|
2661
2855
|
}
|
|
2856
|
+
const autoRouting = {
|
|
2857
|
+
mode: 'complexity',
|
|
2858
|
+
cascade: { enabled: false, models: [], escalateOn: 'uncertainty', maxEscalations: 1 },
|
|
2859
|
+
complexity: { enabled: true, ...autoComplexity },
|
|
2860
|
+
};
|
|
2861
|
+
const updatedConfig = { ...existingRaw, routing: autoRouting, first_run_complete: true };
|
|
2862
|
+
await fs.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
2863
|
+
await fs.promises.writeFile(configPath, JSON.stringify(updatedConfig, null, 2), 'utf8');
|
|
2864
|
+
proxyConfig = await loadProxyConfig(configPath, log);
|
|
2865
|
+
console.log(`[RelayPlane] Auto-config: wrote routing config to ${configPath}`);
|
|
2662
2866
|
}
|
|
2663
2867
|
}
|
|
2664
2868
|
}
|
|
@@ -3193,8 +3397,6 @@ async function startProxy(config = {}) {
|
|
|
3193
3397
|
providerStats[provider].success++;
|
|
3194
3398
|
}
|
|
3195
3399
|
}
|
|
3196
|
-
// Debug: log provider stats
|
|
3197
|
-
console.log('[RelayPlane Health] Provider stats:', JSON.stringify(providerStats));
|
|
3198
3400
|
const providers = [];
|
|
3199
3401
|
for (const [name, ep] of Object.entries(exports.DEFAULT_ENDPOINTS)) {
|
|
3200
3402
|
// Skip Ollama from normal key-based health check — it's handled separately
|
|
@@ -3558,7 +3760,11 @@ async function startProxy(config = {}) {
|
|
|
3558
3760
|
useCascade = false; // Disable full cascade, use complexity routing instead
|
|
3559
3761
|
let selectedModel = null;
|
|
3560
3762
|
if (proxyConfig.routing?.complexity?.enabled) {
|
|
3561
|
-
|
|
3763
|
+
const complexityVal = proxyConfig.routing?.complexity?.[complexity];
|
|
3764
|
+
if (complexityVal != null) {
|
|
3765
|
+
const parsed = parseComplexityModel(complexityVal);
|
|
3766
|
+
selectedModel = `${parsed.provider}/${parsed.model}`;
|
|
3767
|
+
}
|
|
3562
3768
|
}
|
|
3563
3769
|
else {
|
|
3564
3770
|
selectedModel = getCascadeModels(proxyConfig)[0] || getCostModel(proxyConfig);
|
|
@@ -3600,9 +3806,12 @@ async function startProxy(config = {}) {
|
|
|
3600
3806
|
else {
|
|
3601
3807
|
// Complexity-based routing takes priority when enabled
|
|
3602
3808
|
if (proxyConfig.routing?.complexity?.enabled) {
|
|
3603
|
-
const
|
|
3604
|
-
|
|
3605
|
-
|
|
3809
|
+
const complexityVal = proxyConfig.routing?.complexity?.[complexity];
|
|
3810
|
+
if (complexityVal != null) {
|
|
3811
|
+
const parsed = parseComplexityModel(complexityVal);
|
|
3812
|
+
selectedModel = `${parsed.provider}/${parsed.model}`;
|
|
3813
|
+
log(`Complexity routing: ${complexity} → ${parsed.provider}/${parsed.model}`);
|
|
3814
|
+
}
|
|
3606
3815
|
}
|
|
3607
3816
|
// Fall back to learned routing rules (non-default only)
|
|
3608
3817
|
if (!selectedModel) {
|
|
@@ -4284,9 +4493,12 @@ async function startProxy(config = {}) {
|
|
|
4284
4493
|
else {
|
|
4285
4494
|
// Complexity-based routing takes priority when enabled
|
|
4286
4495
|
if (proxyConfig.routing?.complexity?.enabled) {
|
|
4287
|
-
const
|
|
4288
|
-
|
|
4289
|
-
|
|
4496
|
+
const complexityVal = proxyConfig.routing?.complexity?.[complexity];
|
|
4497
|
+
if (complexityVal != null) {
|
|
4498
|
+
const parsed = parseComplexityModel(complexityVal);
|
|
4499
|
+
selectedModel = `${parsed.provider}/${parsed.model}`;
|
|
4500
|
+
log(`Complexity routing: ${complexity} → ${parsed.provider}/${parsed.model}`);
|
|
4501
|
+
}
|
|
4290
4502
|
}
|
|
4291
4503
|
// Fall back to learned routing rules (non-default only)
|
|
4292
4504
|
if (!selectedModel && !targetModel) {
|