@relayplane/proxy 1.8.23 → 1.8.25
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":"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,CAiD/E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGrD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,IAAI,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAM7E,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA4CnH;AA4DD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAWjD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMvD;AAkBD,KAAK,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEjD,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;CAC9B;AAcD,UAAU,aAAa;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC;IAChD,cAAc,EAAE,MAAM,CAAC;CACxB;AAiJD,KAAK,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AA6EpD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;CAChD;AA0GD,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA4OD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,EAAE,OAAO,GACnB;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CA2CjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,GAAG,MAAM,CAavG;AAiJD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAe3D;AAuDD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,UAAU,CAoDpG;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,OAAO,CAIlG;AAosCD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4E9C;AAymBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CA2zE/E"}
|
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,113 @@ 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 provider = val.slice(0, idx);
|
|
433
|
+
const model = val.slice(idx + 1); // preserves openrouter/anthropic/claude-... style
|
|
434
|
+
return { provider, model };
|
|
435
|
+
}
|
|
436
|
+
// Plain model name — look up in MODEL_MAPPING, fallback to anthropic
|
|
437
|
+
return exports.MODEL_MAPPING[val] ?? { provider: 'anthropic', model: val };
|
|
438
|
+
}
|
|
439
|
+
return { provider: 'anthropic', model: 'claude-sonnet-4-6' };
|
|
440
|
+
}
|
|
441
|
+
/** Per-provider default complexity tier models */
|
|
442
|
+
const PROVIDER_COMPLEXITY_TIERS = {
|
|
443
|
+
anthropic: {
|
|
444
|
+
simple: { provider: 'anthropic', model: 'claude-haiku-4-5' },
|
|
445
|
+
moderate: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
|
|
446
|
+
complex: { provider: 'anthropic', model: 'claude-opus-4-6' },
|
|
447
|
+
},
|
|
448
|
+
openai: {
|
|
449
|
+
simple: { provider: 'openai', model: 'gpt-4.1-mini' },
|
|
450
|
+
moderate: { provider: 'openai', model: 'gpt-5.4' },
|
|
451
|
+
complex: { provider: 'openai', model: 'gpt-5.4' },
|
|
452
|
+
},
|
|
453
|
+
google: {
|
|
454
|
+
simple: { provider: 'google', model: 'gemini-2.5-flash-lite' },
|
|
455
|
+
moderate: { provider: 'google', model: 'gemini-2.5-flash' },
|
|
456
|
+
complex: { provider: 'google', model: 'gemini-2.5-pro' },
|
|
457
|
+
},
|
|
458
|
+
xai: {
|
|
459
|
+
simple: { provider: 'xai', model: 'grok-4.1-fast' },
|
|
460
|
+
moderate: { provider: 'xai', model: 'grok-4.20-beta' },
|
|
461
|
+
complex: { provider: 'xai', model: 'grok-4' },
|
|
462
|
+
},
|
|
463
|
+
deepseek: {
|
|
464
|
+
simple: { provider: 'deepseek', model: 'deepseek-chat' },
|
|
465
|
+
moderate: { provider: 'deepseek', model: 'deepseek-chat' },
|
|
466
|
+
complex: { provider: 'deepseek', model: 'deepseek-reasoner' },
|
|
467
|
+
},
|
|
468
|
+
openrouter: {
|
|
469
|
+
simple: { provider: 'openrouter', model: 'google/gemini-2.5-flash-lite' },
|
|
470
|
+
moderate: { provider: 'openrouter', model: 'google/gemini-2.5-flash' },
|
|
471
|
+
complex: { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-6' },
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
/**
|
|
475
|
+
* Detect which AI providers are available based on env vars and user config.
|
|
476
|
+
* Returns providers in priority order: anthropic > openai > google > xai > deepseek > openrouter > groq
|
|
477
|
+
*/
|
|
478
|
+
function detectAvailableProviders(userConfig) {
|
|
479
|
+
const cfg = (userConfig ?? {});
|
|
480
|
+
const auth = (cfg['auth'] ?? {});
|
|
481
|
+
const available = [];
|
|
482
|
+
if (process.env['ANTHROPIC_API_KEY'] || auth['anthropicApiKey'] || auth['anthropicMaxToken']) {
|
|
483
|
+
available.push('anthropic');
|
|
484
|
+
}
|
|
485
|
+
if (process.env['OPENAI_API_KEY'] || auth['openaiApiKey']) {
|
|
486
|
+
available.push('openai');
|
|
487
|
+
}
|
|
488
|
+
if (process.env['GOOGLE_API_KEY'] || process.env['GEMINI_API_KEY'] || auth['googleApiKey']) {
|
|
489
|
+
available.push('google');
|
|
490
|
+
}
|
|
491
|
+
if (process.env['XAI_API_KEY'] || auth['xaiApiKey']) {
|
|
492
|
+
available.push('xai');
|
|
493
|
+
}
|
|
494
|
+
if (process.env['DEEPSEEK_API_KEY'] || auth['deepseekApiKey']) {
|
|
495
|
+
available.push('deepseek');
|
|
496
|
+
}
|
|
497
|
+
if (process.env['OPENROUTER_API_KEY'] || auth['openrouterApiKey']) {
|
|
498
|
+
available.push('openrouter');
|
|
499
|
+
}
|
|
500
|
+
if (process.env['GROQ_API_KEY'] || auth['groqApiKey']) {
|
|
501
|
+
available.push('groq');
|
|
502
|
+
}
|
|
503
|
+
return available;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Build default complexity tiers based on first detected provider.
|
|
507
|
+
* Config overrides win — only fills in tiers not explicitly set.
|
|
508
|
+
*/
|
|
509
|
+
function buildDefaultComplexityTiers(providers, existing) {
|
|
510
|
+
// Find first provider that has a known tier mapping
|
|
511
|
+
const primaryProvider = providers.find((p) => PROVIDER_COMPLEXITY_TIERS[p]) ?? 'anthropic';
|
|
512
|
+
const defaults = PROVIDER_COMPLEXITY_TIERS[primaryProvider] ?? PROVIDER_COMPLEXITY_TIERS['anthropic'];
|
|
513
|
+
const simple = existing?.simple != null
|
|
514
|
+
? parseComplexityModel(existing.simple)
|
|
515
|
+
: defaults.simple;
|
|
516
|
+
const moderate = existing?.moderate != null
|
|
517
|
+
? parseComplexityModel(existing.moderate)
|
|
518
|
+
: defaults.moderate;
|
|
519
|
+
const complex = existing?.complex != null
|
|
520
|
+
? parseComplexityModel(existing.complex)
|
|
521
|
+
: defaults.complex;
|
|
522
|
+
return { simple, moderate, complex };
|
|
523
|
+
}
|
|
382
524
|
const UNCERTAINTY_PATTERNS = [
|
|
383
525
|
/i'?m not (entirely |completely |really )?sure/i,
|
|
384
526
|
/i don'?t (really |actually )?know/i,
|
|
@@ -1411,6 +1553,38 @@ function convertMessagesToGemini(messages) {
|
|
|
1411
1553
|
}
|
|
1412
1554
|
return geminiContents;
|
|
1413
1555
|
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Recursively strip JSON Schema properties that Gemini rejects but OpenAI/Anthropic accept.
|
|
1558
|
+
* Gemini rejects: patternProperties, additionalProperties (boolean), $schema, definitions, $defs, unevaluatedProperties
|
|
1559
|
+
*/
|
|
1560
|
+
function sanitizeSchemaForGemini(schema) {
|
|
1561
|
+
if (Array.isArray(schema)) {
|
|
1562
|
+
return schema.map(sanitizeSchemaForGemini);
|
|
1563
|
+
}
|
|
1564
|
+
if (schema !== null && typeof schema === 'object') {
|
|
1565
|
+
const obj = schema;
|
|
1566
|
+
const result = {};
|
|
1567
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1568
|
+
// Strip fields Gemini doesn't support
|
|
1569
|
+
if (key === 'patternProperties')
|
|
1570
|
+
continue;
|
|
1571
|
+
if (key === '$schema')
|
|
1572
|
+
continue;
|
|
1573
|
+
if (key === 'definitions')
|
|
1574
|
+
continue;
|
|
1575
|
+
if (key === '$defs')
|
|
1576
|
+
continue;
|
|
1577
|
+
if (key === 'unevaluatedProperties')
|
|
1578
|
+
continue;
|
|
1579
|
+
// additionalProperties: Gemini only accepts object form, not boolean
|
|
1580
|
+
if (key === 'additionalProperties' && typeof value === 'boolean')
|
|
1581
|
+
continue;
|
|
1582
|
+
result[key] = sanitizeSchemaForGemini(value);
|
|
1583
|
+
}
|
|
1584
|
+
return result;
|
|
1585
|
+
}
|
|
1586
|
+
return schema;
|
|
1587
|
+
}
|
|
1414
1588
|
/**
|
|
1415
1589
|
* Forward non-streaming request to Gemini API
|
|
1416
1590
|
*/
|
|
@@ -1436,7 +1610,7 @@ async function forwardToGemini(request, targetModel, apiKey) {
|
|
|
1436
1610
|
functionDeclarations: request.tools.map((t) => ({
|
|
1437
1611
|
name: t.function.name,
|
|
1438
1612
|
description: t.function.description || "",
|
|
1439
|
-
parameters: t.function.parameters || {}
|
|
1613
|
+
parameters: sanitizeSchemaForGemini(t.function.parameters || {})
|
|
1440
1614
|
}))
|
|
1441
1615
|
}];
|
|
1442
1616
|
}
|
|
@@ -1474,7 +1648,7 @@ async function forwardToGeminiStream(request, targetModel, apiKey) {
|
|
|
1474
1648
|
functionDeclarations: request.tools.map((t) => ({
|
|
1475
1649
|
name: t.function.name,
|
|
1476
1650
|
description: t.function.description || "",
|
|
1477
|
-
parameters: t.function.parameters || {}
|
|
1651
|
+
parameters: sanitizeSchemaForGemini(t.function.parameters || {})
|
|
1478
1652
|
}))
|
|
1479
1653
|
}];
|
|
1480
1654
|
}
|
|
@@ -2235,18 +2409,25 @@ function getCooldownConfig(config) {
|
|
|
2235
2409
|
};
|
|
2236
2410
|
return { ...defaults, ...config.reliability?.cooldowns };
|
|
2237
2411
|
}
|
|
2412
|
+
function complexityValToString(val) {
|
|
2413
|
+
if (val == null)
|
|
2414
|
+
return undefined;
|
|
2415
|
+
if (typeof val === 'string')
|
|
2416
|
+
return val;
|
|
2417
|
+
return `${val.provider}/${val.model}`;
|
|
2418
|
+
}
|
|
2238
2419
|
function getCostModel(config) {
|
|
2239
|
-
return (config.routing?.complexity?.simple ||
|
|
2420
|
+
return (complexityValToString(config.routing?.complexity?.simple) ||
|
|
2240
2421
|
config.routing?.cascade?.models?.[0] ||
|
|
2241
2422
|
'claude-haiku-4-5');
|
|
2242
2423
|
}
|
|
2243
2424
|
function getFastModel(config) {
|
|
2244
|
-
return (config.routing?.complexity?.simple ||
|
|
2425
|
+
return (complexityValToString(config.routing?.complexity?.simple) ||
|
|
2245
2426
|
config.routing?.cascade?.models?.[0] ||
|
|
2246
2427
|
'claude-haiku-4-5');
|
|
2247
2428
|
}
|
|
2248
2429
|
function getQualityModel(config) {
|
|
2249
|
-
return (config.routing?.complexity?.complex ||
|
|
2430
|
+
return (complexityValToString(config.routing?.complexity?.complex) ||
|
|
2250
2431
|
config.routing?.cascade?.models?.[config.routing?.cascade?.models?.length ? config.routing.cascade.models.length - 1 : 0] ||
|
|
2251
2432
|
process.env['RELAYPLANE_QUALITY_MODEL'] ||
|
|
2252
2433
|
'claude-sonnet-4-6');
|
|
@@ -2604,61 +2785,72 @@ async function startProxy(config = {}) {
|
|
|
2604
2785
|
log(`[CROSS-CASCADE] Enabled. Provider order: ${proxyConfig.crossProviderCascade.providers.join(' → ')}`);
|
|
2605
2786
|
}
|
|
2606
2787
|
const isFirstRun = !rawFileHasRouting || !userConfig.first_run_complete;
|
|
2788
|
+
// Always detect available providers and update DEFAULT_ROUTING at startup
|
|
2789
|
+
const availableProviders = detectAvailableProviders();
|
|
2790
|
+
{
|
|
2791
|
+
// Build human-readable provider labels for startup log
|
|
2792
|
+
const providerLabels = availableProviders.map((p) => {
|
|
2793
|
+
if (p === 'anthropic') {
|
|
2794
|
+
const key = process.env['ANTHROPIC_API_KEY'] || '';
|
|
2795
|
+
return key.startsWith('sk-ant-api') ? '✓ Anthropic' : '✓ Anthropic (Max)';
|
|
2796
|
+
}
|
|
2797
|
+
return `✓ ${p.charAt(0).toUpperCase() + p.slice(1)}`;
|
|
2798
|
+
});
|
|
2799
|
+
if (providerLabels.length > 0) {
|
|
2800
|
+
console.log(`[RelayPlane] ${providerLabels.join(', ')}`);
|
|
2801
|
+
}
|
|
2802
|
+
// Build default tiers, respecting any existing user config overrides
|
|
2803
|
+
const existingComplexity = proxyConfig.routing?.complexity;
|
|
2804
|
+
const defaultProviders = availableProviders.length > 0 ? availableProviders : ['openrouter'];
|
|
2805
|
+
const tiers = buildDefaultComplexityTiers(defaultProviders, existingComplexity);
|
|
2806
|
+
// Update DEFAULT_ROUTING with detected provider's moderate tier
|
|
2807
|
+
const moderateRoute = tiers.moderate;
|
|
2808
|
+
const allTaskTypes = ['code_generation', 'code_review', 'summarization', 'analysis', 'creative_writing', 'data_extraction', 'translation', 'question_answering', 'general'];
|
|
2809
|
+
for (const tt of allTaskTypes) {
|
|
2810
|
+
DEFAULT_ROUTING[tt] = moderateRoute;
|
|
2811
|
+
}
|
|
2812
|
+
console.log(`[RelayPlane] Auto-routing: simple=${tiers.simple.model}, moderate=${tiers.moderate.model}, complex=${tiers.complex.model}`);
|
|
2813
|
+
}
|
|
2607
2814
|
if (isFirstRun || proxyConfig.routing?.mode === 'auto') {
|
|
2608
2815
|
const envAnthropicKey = process.env['ANTHROPIC_API_KEY'];
|
|
2609
2816
|
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
|
-
}
|
|
2817
|
+
if (isFirstRun) {
|
|
2818
|
+
let existingRaw = {};
|
|
2819
|
+
try {
|
|
2820
|
+
existingRaw = JSON.parse(await fs.promises.readFile(configPath, 'utf8'));
|
|
2821
|
+
}
|
|
2822
|
+
catch { /* fresh start, no existing config */ }
|
|
2823
|
+
let autoComplexity;
|
|
2824
|
+
if (availableProviders.includes('anthropic') && hasRegularApiKey) {
|
|
2825
|
+
// Full Anthropic API key — enable haiku 3-tier routing
|
|
2826
|
+
console.log('[RelayPlane] Auto-config: ANTHROPIC_API_KEY detected — enabling 3-tier routing (haiku/sonnet/opus)');
|
|
2827
|
+
autoComplexity = { simple: 'claude-haiku-4-5', moderate: 'claude-sonnet-4-6', complex: 'claude-opus-4-6' };
|
|
2828
|
+
}
|
|
2829
|
+
else if (availableProviders.length > 0 && !availableProviders.includes('anthropic')) {
|
|
2830
|
+
// Non-Anthropic provider — use detected provider's tiers
|
|
2831
|
+
const providerTiers = buildDefaultComplexityTiers(availableProviders);
|
|
2832
|
+
console.log(`[RelayPlane] Auto-config: ${availableProviders[0]} detected — enabling provider-aware 3-tier routing`);
|
|
2833
|
+
autoComplexity = {
|
|
2834
|
+
simple: `${providerTiers.simple.provider}/${providerTiers.simple.model}`,
|
|
2835
|
+
moderate: `${providerTiers.moderate.provider}/${providerTiers.moderate.model}`,
|
|
2836
|
+
complex: `${providerTiers.complex.provider}/${providerTiers.complex.model}`,
|
|
2629
2837
|
};
|
|
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
2838
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
// No regular API key — OAuth only or no Anthropic key; skip Haiku (OAuth not supported for Haiku)
|
|
2639
|
-
if (isFirstRun) {
|
|
2839
|
+
else {
|
|
2840
|
+
// OAuth only or no API key — skip Haiku (OAuth not supported for Haiku)
|
|
2640
2841
|
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)`);
|
|
2842
|
+
autoComplexity = { simple: 'claude-sonnet-4-6', moderate: 'claude-sonnet-4-6', complex: 'claude-opus-4-6' };
|
|
2661
2843
|
}
|
|
2844
|
+
const autoRouting = {
|
|
2845
|
+
mode: 'complexity',
|
|
2846
|
+
cascade: { enabled: false, models: [], escalateOn: 'uncertainty', maxEscalations: 1 },
|
|
2847
|
+
complexity: { enabled: true, ...autoComplexity },
|
|
2848
|
+
};
|
|
2849
|
+
const updatedConfig = { ...existingRaw, routing: autoRouting, first_run_complete: true };
|
|
2850
|
+
await fs.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
2851
|
+
await fs.promises.writeFile(configPath, JSON.stringify(updatedConfig, null, 2), 'utf8');
|
|
2852
|
+
proxyConfig = await loadProxyConfig(configPath, log);
|
|
2853
|
+
console.log(`[RelayPlane] Auto-config: wrote routing config to ${configPath}`);
|
|
2662
2854
|
}
|
|
2663
2855
|
}
|
|
2664
2856
|
}
|
|
@@ -3193,8 +3385,6 @@ async function startProxy(config = {}) {
|
|
|
3193
3385
|
providerStats[provider].success++;
|
|
3194
3386
|
}
|
|
3195
3387
|
}
|
|
3196
|
-
// Debug: log provider stats
|
|
3197
|
-
console.log('[RelayPlane Health] Provider stats:', JSON.stringify(providerStats));
|
|
3198
3388
|
const providers = [];
|
|
3199
3389
|
for (const [name, ep] of Object.entries(exports.DEFAULT_ENDPOINTS)) {
|
|
3200
3390
|
// Skip Ollama from normal key-based health check — it's handled separately
|
|
@@ -3558,7 +3748,11 @@ async function startProxy(config = {}) {
|
|
|
3558
3748
|
useCascade = false; // Disable full cascade, use complexity routing instead
|
|
3559
3749
|
let selectedModel = null;
|
|
3560
3750
|
if (proxyConfig.routing?.complexity?.enabled) {
|
|
3561
|
-
|
|
3751
|
+
const complexityVal = proxyConfig.routing?.complexity?.[complexity];
|
|
3752
|
+
if (complexityVal != null) {
|
|
3753
|
+
const parsed = parseComplexityModel(complexityVal);
|
|
3754
|
+
selectedModel = `${parsed.provider}/${parsed.model}`;
|
|
3755
|
+
}
|
|
3562
3756
|
}
|
|
3563
3757
|
else {
|
|
3564
3758
|
selectedModel = getCascadeModels(proxyConfig)[0] || getCostModel(proxyConfig);
|
|
@@ -3600,9 +3794,12 @@ async function startProxy(config = {}) {
|
|
|
3600
3794
|
else {
|
|
3601
3795
|
// Complexity-based routing takes priority when enabled
|
|
3602
3796
|
if (proxyConfig.routing?.complexity?.enabled) {
|
|
3603
|
-
const
|
|
3604
|
-
|
|
3605
|
-
|
|
3797
|
+
const complexityVal = proxyConfig.routing?.complexity?.[complexity];
|
|
3798
|
+
if (complexityVal != null) {
|
|
3799
|
+
const parsed = parseComplexityModel(complexityVal);
|
|
3800
|
+
selectedModel = `${parsed.provider}/${parsed.model}`;
|
|
3801
|
+
log(`Complexity routing: ${complexity} → ${parsed.provider}/${parsed.model}`);
|
|
3802
|
+
}
|
|
3606
3803
|
}
|
|
3607
3804
|
// Fall back to learned routing rules (non-default only)
|
|
3608
3805
|
if (!selectedModel) {
|
|
@@ -4284,9 +4481,12 @@ async function startProxy(config = {}) {
|
|
|
4284
4481
|
else {
|
|
4285
4482
|
// Complexity-based routing takes priority when enabled
|
|
4286
4483
|
if (proxyConfig.routing?.complexity?.enabled) {
|
|
4287
|
-
const
|
|
4288
|
-
|
|
4289
|
-
|
|
4484
|
+
const complexityVal = proxyConfig.routing?.complexity?.[complexity];
|
|
4485
|
+
if (complexityVal != null) {
|
|
4486
|
+
const parsed = parseComplexityModel(complexityVal);
|
|
4487
|
+
selectedModel = `${parsed.provider}/${parsed.model}`;
|
|
4488
|
+
log(`Complexity routing: ${complexity} → ${parsed.provider}/${parsed.model}`);
|
|
4489
|
+
}
|
|
4290
4490
|
}
|
|
4291
4491
|
// Fall back to learned routing rules (non-default only)
|
|
4292
4492
|
if (!selectedModel && !targetModel) {
|