@relayplane/proxy 1.8.12 → 1.8.16

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.
@@ -62,6 +62,7 @@ exports.extractResponseText = extractResponseText;
62
62
  exports.parseModelSuffix = parseModelSuffix;
63
63
  exports.classifyComplexity = classifyComplexity;
64
64
  exports.shouldEscalate = shouldEscalate;
65
+ exports.resolveExplicitModel = resolveExplicitModel;
65
66
  exports.startProxy = startProxy;
66
67
  const http = __importStar(require("node:http"));
67
68
  const fs = __importStar(require("node:fs"));
@@ -1891,10 +1892,48 @@ function parsePreferredModel(preferredModel) {
1891
1892
  return { provider: provider, model };
1892
1893
  }
1893
1894
  /**
1894
- * Resolve explicit model name to provider and model
1895
- * Handles direct model names like "claude-3-5-sonnet-latest" or "gpt-4o"
1895
+ * Resolve explicit model name to provider and model.
1896
+ /**
1897
+ * Add provider prefix to bare model names for aggregator routing (e.g., OpenRouter).
1898
+ * Complexity routing produces bare names like 'claude-sonnet-4-6' — aggregators need
1899
+ * the full 'anthropic/claude-sonnet-4-6' format to identify the upstream provider.
1900
+ * If the model already has a prefix (contains '/'), it's returned unchanged.
1896
1901
  */
1897
- function resolveExplicitModel(modelName) {
1902
+ function addProviderPrefix(model, detectedProvider) {
1903
+ // Already has a prefix (e.g., 'anthropic/claude-sonnet-4-6')
1904
+ if (model.includes('/'))
1905
+ return model;
1906
+ // Map detected provider to OpenRouter-style prefix
1907
+ const prefixMap = {
1908
+ anthropic: 'anthropic',
1909
+ openai: 'openai',
1910
+ google: 'google',
1911
+ deepseek: 'deepseek',
1912
+ mistral: 'mistralai',
1913
+ together: 'together',
1914
+ groq: 'groq',
1915
+ };
1916
+ const prefix = prefixMap[detectedProvider];
1917
+ if (prefix)
1918
+ return `${prefix}/${model}`;
1919
+ // Fallback: infer from model name patterns
1920
+ if (model.startsWith('claude') || model.startsWith('claude-'))
1921
+ return `anthropic/${model}`;
1922
+ if (model.startsWith('gpt') || model.startsWith('o1') || model.startsWith('o3'))
1923
+ return `openai/${model}`;
1924
+ if (model.startsWith('gemini'))
1925
+ return `google/${model}`;
1926
+ return model;
1927
+ }
1928
+ /**
1929
+ * When `defaultProvider` is set, ALL models are routed to that provider
1930
+ * regardless of model name prefix — the model name is preserved as-is
1931
+ * so OpenRouter receives the full `anthropic/claude-sonnet-4-6` format.
1932
+ */
1933
+ function resolveExplicitModel(modelName, defaultProvider) {
1934
+ if (defaultProvider) {
1935
+ return { provider: defaultProvider, model: modelName };
1936
+ }
1898
1937
  // Resolve aliases first (e.g., relayplane:auto → rp:balanced)
1899
1938
  const resolvedAlias = resolveModelAlias(modelName);
1900
1939
  // Check SMART_ALIASES (rp:best, rp:fast, etc.)
@@ -1954,7 +1993,10 @@ function resolveExplicitModel(modelName) {
1954
1993
  }
1955
1994
  return null;
1956
1995
  }
1957
- function resolveConfigModel(modelName) {
1996
+ function resolveConfigModel(modelName, defaultProvider) {
1997
+ if (defaultProvider) {
1998
+ return { provider: defaultProvider, model: modelName };
1999
+ }
1958
2000
  return resolveExplicitModel(modelName) ?? parsePreferredModel(modelName);
1959
2001
  }
1960
2002
  function extractResponseTextAuto(responseData) {
@@ -2147,18 +2189,27 @@ function resolveProviderApiKey(provider, ctx, envApiKey) {
2147
2189
  }
2148
2190
  const apiKeyEnv = exports.DEFAULT_ENDPOINTS[provider]?.apiKeyEnv ?? `${provider.toUpperCase()}_API_KEY`;
2149
2191
  const apiKey = process.env[apiKeyEnv];
2150
- if (!apiKey) {
2151
- return {
2152
- error: {
2153
- status: 500,
2154
- payload: {
2155
- error: `Missing ${apiKeyEnv} environment variable`,
2156
- hint: `Cross-provider routing requires API keys for each provider. Set ${apiKeyEnv} to enable ${provider} models.`,
2157
- },
2158
- },
2159
- };
2192
+ if (apiKey) {
2193
+ return { apiKey };
2160
2194
  }
2161
- return { apiKey };
2195
+ // Auth passthrough: use incoming Authorization: Bearer token when no env key is configured.
2196
+ // This supports defaultProvider scenarios where the caller passes the API key in the request
2197
+ // (e.g., OpenRouter users who pass their key via Authorization header).
2198
+ if (ctx.authHeader) {
2199
+ const bearerMatch = ctx.authHeader.match(/^Bearer\s+(.+)$/i);
2200
+ if (bearerMatch) {
2201
+ return { apiKey: bearerMatch[1] };
2202
+ }
2203
+ }
2204
+ return {
2205
+ error: {
2206
+ status: 500,
2207
+ payload: {
2208
+ error: `Missing ${apiKeyEnv} environment variable`,
2209
+ hint: `Cross-provider routing requires API keys for each provider. Set ${apiKeyEnv} to enable ${provider} models.`,
2210
+ },
2211
+ },
2212
+ };
2162
2213
  }
2163
2214
  function getCascadeModels(config) {
2164
2215
  return config.routing?.cascade?.models ?? [];
@@ -4279,6 +4330,25 @@ async function startProxy(config = {}) {
4279
4330
  }
4280
4331
  }
4281
4332
  }
4333
+ // ── defaultProvider: override provider for all non-cascade cloud routing ──
4334
+ // When set, ALL models route to this provider regardless of model prefix.
4335
+ // Ollama is excluded — local routing takes priority over defaultProvider.
4336
+ if (proxyConfig.defaultProvider && !useCascade && targetProvider !== 'ollama') {
4337
+ const originalProvider = targetProvider;
4338
+ targetProvider = proxyConfig.defaultProvider;
4339
+ // When routing to OpenRouter (or any aggregator), model names need provider prefixes.
4340
+ // Complexity routing produces bare names like 'claude-sonnet-4-6' — OpenRouter needs
4341
+ // 'anthropic/claude-sonnet-4-6'. Passthrough mode preserves the original request model.
4342
+ if (routingMode === 'passthrough') {
4343
+ targetModel = requestedModel;
4344
+ }
4345
+ else {
4346
+ // Add provider prefix for bare model names when routing to an aggregator
4347
+ targetModel = addProviderPrefix(targetModel, originalProvider);
4348
+ }
4349
+ log(`defaultProvider override: ${originalProvider} → ${targetProvider} (model: ${targetModel})`);
4350
+ }
4351
+ // ── End defaultProvider override ──
4282
4352
  if (!useCascade) {
4283
4353
  log(`Routing to: ${targetProvider}/${targetModel}`);
4284
4354
  }
@@ -4529,6 +4599,10 @@ async function startProxy(config = {}) {
4529
4599
  console.log(` POST /v1/messages/count_tokens - Token counting`);
4530
4600
  console.log(` GET /v1/models - Model list`);
4531
4601
  console.log(` Models: relayplane:auto, relayplane:cost, relayplane:fast, relayplane:quality`);
4602
+ if (proxyConfig.defaultProvider) {
4603
+ console.log(` Providers:`);
4604
+ console.log(` ✓ ${proxyConfig.defaultProvider.charAt(0).toUpperCase() + proxyConfig.defaultProvider.slice(1)} (default provider — all models route here)`);
4605
+ }
4532
4606
  console.log(` Auth: Passthrough for Anthropic, env vars for other providers`);
4533
4607
  console.log(` Streaming: ✅ Enabled`);
4534
4608
  startWatchdog();