feat(gateway): add API Route hybrid gateway (#2225)

* feat(gateway): add API Route hybrid gateway

Add API Route as a descriptor-first OpenAI-compatible aggregating gateway with hybrid catalog discovery, dedicated API_ROUTE_API_KEY credentials, and route boundary protections.

* fix(gateway): address review feedback for API Route gateway

* test(api-route): verify auth header withheld on noncanonical URL

* fix(api-route): honor generic saved profile credentials

* test(api-route): cover generic profile credential fallback

* fix(api-route): protect route vars from settings overrides

* fix(api-route): allow dedicated vars in provider env files

* test(api-route): isolate provider flag environment

* fix(api-route): preserve env-only intent across startup profiles

* test(api-route): cover provider env file loading

* test(api-route): cover host-managed settings boundary

* test(api-route): cover saved profile ownership and switch cleanup

* test(api-route): preserve env-only startup selection

* test(api-route): verify saved profile credential boundary

* test(api-route): accept canonical saved profile credentials

* test(api-route): cover saved profile relaunch

* sync: apply upstream xAI OAuth state validation

* sync: apply upstream xAI OAuth state validation

* sync: apply upstream xAI OAuth state validation

* test(api-route): preserve env-file precedence and atomicity

* test(api-route): cover saved-only profile contract

* fix(gateway): register api-route for proxy credential withholding and sanitize model flag

- Add api-route to isNoncanonicalDedicatedOpenAILaunch to strip ambient credentials
- Register persistedApiRouteProxy in applyStartupEnvFromProfile
- Delete OPENAI_API_KEYS on noncanonical base URLs in applyApiRouteEnvOnlyDefaults
- Sanitize API_ROUTE_MODEL with usableProviderModelEnvValue in applyProviderFlag
- Add regression tests covering credential withholding and model placeholder handling

* fix(gateway): clear OPENAI_API_KEYS in canonical API Route selection

- Delete OPENAI_API_KEYS when dedicated API_ROUTE_API_KEY is mirrored in applyApiRouteEnvOnlyDefaults
- Delete OPENAI_API_KEYS in applyProviderFlag for canonical api-route selection
- Add request-level regression test verifying API_ROUTE_API_KEY precedence over OPENAI_API_KEYS
- Add providerFlag test confirming credential pool cleanup

* fix(api-route): complete saved profile credential parity
This commit is contained in:
DennyHo0917
2026-09-29 12:59:42 +08:00
committed by GitHub
parent 5cd11336ca
commit 9a2910da23
29 changed files with 1530 additions and 23 deletions
+5
View File
@@ -216,6 +216,11 @@ ANTHROPIC_API_KEY=sk-ant-your-key-here
# OPENAI_BASE_URL=https://api.aimlapi.com/v1 # OPENAI_BASE_URL=https://api.aimlapi.com/v1
# OPENAI_MODEL=gpt-4o # OPENAI_MODEL=gpt-4o
# For API Route, the dedicated key enables the OpenAI-compatible route:
# API_ROUTE_API_KEY=your-api-route-key-here
# OPENAI_BASE_URL=https://global.api-route.com/v1
# OPENAI_MODEL=claude-sonnet-4-6
# For LLMTR, prefer its dedicated key. Raw env setup must also set # For LLMTR, prefer its dedicated key. Raw env setup must also set
# OPENAI_BASE_URL and OPENAI_MODEL. OPENAI_API_KEY remains supported: # OPENAI_BASE_URL and OPENAI_MODEL. OPENAI_API_KEY remains supported:
# LLMTR_API_KEY=your-llmtr-key-here # LLMTR_API_KEY=your-llmtr-key-here
+1
View File
@@ -313,6 +313,7 @@ Advanced and source-build guides:
| LLMTR | `/provider` or OpenAI-compatible env vars | Multi-model gateway at `https://llmtr.com/v1`; `/provider` and `--provider llmtr` default to `deepseek/deepseek-v4-flash`, while raw env setup must set `OPENAI_BASE_URL=https://llmtr.com/v1` and `OPENAI_MODEL`; accepts `LLMTR_API_KEY` or `OPENAI_API_KEY` after the route is selected and discovers tool-capable Chat Completions models from the public catalog | | LLMTR | `/provider` or OpenAI-compatible env vars | Multi-model gateway at `https://llmtr.com/v1`; `/provider` and `--provider llmtr` default to `deepseek/deepseek-v4-flash`, while raw env setup must set `OPENAI_BASE_URL=https://llmtr.com/v1` and `OPENAI_MODEL`; accepts `LLMTR_API_KEY` or `OPENAI_API_KEY` after the route is selected and discovers tool-capable Chat Completions models from the public catalog |
| Command Code | `/provider` or OpenAI-compatible env vars | Hybrid OpenAI-compatible gateway at `https://api.commandcode.ai/provider/v1`; `/provider` and `--provider commandcode` default to `deepseek/deepseek-v4-flash`, while raw env setup must set `OPENAI_BASE_URL=https://api.commandcode.ai/provider/v1` and `OPENAI_MODEL`; requires `CMD_API_KEY`, `COMMANDCODE_API_KEY`, or the official `COMMAND_CODE_API_KEY` after the route is selected and discovers Chat Completions models from the public catalog. Claude models are unsupported by this Chat Completions integration | | Command Code | `/provider` or OpenAI-compatible env vars | Hybrid OpenAI-compatible gateway at `https://api.commandcode.ai/provider/v1`; `/provider` and `--provider commandcode` default to `deepseek/deepseek-v4-flash`, while raw env setup must set `OPENAI_BASE_URL=https://api.commandcode.ai/provider/v1` and `OPENAI_MODEL`; requires `CMD_API_KEY`, `COMMANDCODE_API_KEY`, or the official `COMMAND_CODE_API_KEY` after the route is selected and discovers Chat Completions models from the public catalog. Claude models are unsupported by this Chat Completions integration |
| ApiSmart | `/provider` or `APISMART_API_KEY` | Uses `https://gw.apismart.ai/v1`, defaults to `DEEPSEEK_V4_FLASH`, and supports optional `APISMART_MODEL` plus authenticated model discovery | | ApiSmart | `/provider` or `APISMART_API_KEY` | Uses `https://gw.apismart.ai/v1`, defaults to `DEEPSEEK_V4_FLASH`, and supports optional `APISMART_MODEL` plus authenticated model discovery |
| API Route | `/provider` or `API_ROUTE_API_KEY` | Uses `https://global.api-route.com/v1`, defaults to `claude-sonnet-4-6`, and supports optional `API_ROUTE_MODEL` plus authenticated model discovery |
| Hicap | `/provider` or OpenAI-compatible env vars | Uses `api-key` auth, discovers models from unauthenticated `/models`, and supports Responses mode for `gpt-` models | | Hicap | `/provider` or OpenAI-compatible env vars | Uses `api-key` auth, discovers models from unauthenticated `/models`, and supports Responses mode for `gpt-` models |
| Fireworks AI | `/provider` or env vars | First-class provider with 276 curated models (DeepSeek, Qwen, Llama, Gemma, and more); uses `FIREWORKS_API_KEY` | | Fireworks AI | `/provider` or env vars | First-class provider with 276 curated models (DeepSeek, Qwen, Llama, Gemma, and more); uses `FIREWORKS_API_KEY` |
| LongCat | `/provider` or env vars | Meituan LongCat OpenAI-compatible API at `https://api.longcat.chat/openai/v1`; uses `LONGCAT_API_KEY` and defaults to `LongCat-2.0` | | LongCat | `/provider` or env vars | Meituan LongCat OpenAI-compatible API at `https://api.longcat.chat/openai/v1`; uses `LONGCAT_API_KEY` and defaults to `LongCat-2.0` |
+1
View File
@@ -58,6 +58,7 @@ const EXPECTED_PRESETS = [
'opencode', 'opencode',
'opencode-go', 'opencode-go',
'clinepass', 'clinepass',
'api-route',
] as const satisfies readonly ProviderPreset[] ] as const satisfies readonly ProviderPreset[]
describe('compatibility mappings', () => { describe('compatibility mappings', () => {
+174
View File
@@ -0,0 +1,174 @@
import { expect, test } from 'bun:test'
import { routeForPreset } from '../compatibility.js'
import { getProviderPresetUiMetadata } from '../providerUiMetadata.js'
import {
resolveActiveRouteIdFromEnv,
resolveRouteCredentialValue,
resolveRouteIdFromBaseUrl,
} from '../routeMetadata.js'
import gateway, { mapApiRouteModel } from './api-route.js'
test('API Route uses a dedicated hybrid OpenAI-compatible gateway contract', () => {
expect(gateway.defaultBaseUrl).toBe('https://global.api-route.com/v1')
expect(gateway.defaultModel).toBe('claude-sonnet-4-6')
expect(gateway.setup.credentialEnvVars).toEqual(['API_ROUTE_API_KEY'])
expect(gateway.setup.dedicatedCredentialsOnly).toBe(false)
expect(gateway.preset?.apiKeyEnvVars).toEqual(['API_ROUTE_API_KEY'])
expect(gateway.validation).toMatchObject({
kind: 'credential-env',
routing: { matchDefaultBaseUrl: true },
credentialEnvVars: ['API_ROUTE_API_KEY', 'OPENAI_API_KEYS', 'OPENAI_API_KEY'],
})
expect(gateway.transportConfig).toEqual({
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
maxTokensField: 'max_tokens',
},
})
expect(gateway.catalog?.source).toBe('hybrid')
expect(gateway.catalog?.discovery?.requiresAuth).toBe(true)
expect(gateway.catalog?.models?.map(model => model.apiName)).toEqual([
'claude-sonnet-4-6',
])
})
test('API Route preset uses the existing generic profile path', () => {
expect(routeForPreset('api-route')).toEqual({
vendorId: 'openai',
gatewayId: 'api-route',
routeId: 'api-route',
})
expect(
getProviderPresetUiMetadata('api-route', {
API_ROUTE_API_KEY: 'test-api-route-key',
}),
).toMatchObject({
apiKey: 'test-api-route-key',
baseUrl: 'https://global.api-route.com/v1',
model: 'claude-sonnet-4-6',
provider: 'api-route',
routeId: 'api-route',
})
expect(
getProviderPresetUiMetadata('api-route', {
API_ROUTE_API_KEY: 'test-api-route-key',
API_ROUTE_MODEL: 'custom-api-route-model',
OPENAI_MODEL: 'custom-openai-model',
}),
).toMatchObject({
model: 'custom-api-route-model',
})
expect(
getProviderPresetUiMetadata('api-route', {
API_ROUTE_API_KEY: 'test-api-route-key',
OPENAI_MODEL: 'custom-openai-model',
}),
).toMatchObject({
model: 'custom-openai-model',
})
})
test('API Route profile credentials use the generic fallback only on the canonical inference URL', () => {
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: 'https://global.api-route.com/v1',
processEnv: { OPENAI_API_KEY: 'saved-profile-key' },
}),
).toBe('saved-profile-key')
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: 'https://proxy.example.com/v1',
processEnv: { OPENAI_API_KEY: 'saved-profile-key' },
}),
).toBeUndefined()
const env = { API_ROUTE_API_KEY: 'secret-key' }
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: 'https://global.api-route.com/v1',
processEnv: env,
}),
).toBe('secret-key')
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: 'http://global.api-route.com/v1',
processEnv: env,
}),
).toBeUndefined()
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: 'https://global.api-route.com/v1?tenant=proxy',
processEnv: env,
}),
).toBeUndefined()
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: 'https://proxy.example.com/v1',
processEnv: env,
}),
).toBeUndefined()
})
test('API Route discovery maps chat models and drops non-chat IDs', () => {
expect(
mapApiRouteModel({
id: 'claude-sonnet-4-6',
name: 'Claude Sonnet 4.6',
context_length: 200000,
}),
).toEqual({
id: 'claude-sonnet-4-6',
apiName: 'claude-sonnet-4-6',
label: 'Claude Sonnet 4.6',
contextWindow: 200000,
})
expect(
mapApiRouteModel({
id: 'gpt-4o-mini',
owned_by: 'openai',
}),
).toEqual({
id: 'gpt-4o-mini',
apiName: 'gpt-4o-mini',
label: 'gpt-4o-mini (openai)',
})
expect(mapApiRouteModel({ id: 'text-embedding-3-small' })).toBeNull()
expect(mapApiRouteModel({ id: 'whisper-1' })).toBeNull()
expect(mapApiRouteModel({ id: 'dall-e-3' })).toBeNull()
expect(mapApiRouteModel({ id: 'gpt-image-1' })).toBeNull()
expect(mapApiRouteModel({ id: 'sora-turbo' })).toBeNull()
expect(mapApiRouteModel({ id: 'veo-2' })).toBeNull()
expect(mapApiRouteModel(null)).toBeNull()
expect(mapApiRouteModel({})).toBeNull()
})
test('API Route route resolution detects endpoints and environment intent', () => {
expect(resolveRouteIdFromBaseUrl('https://global.api-route.com/v1')).toBe(
'api-route',
)
expect(
resolveRouteIdFromBaseUrl('https://global.api-route.com/v1/chat/completions'),
).toBe('api-route')
expect(
resolveActiveRouteIdFromEnv({
API_ROUTE_API_KEY: 'test-key',
}),
).toBe('api-route')
})
+125
View File
@@ -0,0 +1,125 @@
import { defineCatalog, defineGateway } from '../define.js'
const NON_CHAT_MODEL_PATTERN =
/(embedding|embed|dall-e|whisper|tts|rerank|moderation|omni-moderation|audio|^gpt-image-|^sora-|^veo-)/i
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
function getTrimmedString(
record: Record<string, unknown>,
key: string,
): string | undefined {
const value = record[key]
return typeof value === 'string' ? value.trim() : undefined
}
function getPositiveInteger(value: unknown): number | undefined {
if (
typeof value === 'number' &&
Number.isFinite(value) &&
Number.isInteger(value) &&
value > 0
) {
return value
}
return undefined
}
export function mapApiRouteModel(raw: unknown) {
if (!isRecord(raw)) {
return null
}
const id = getTrimmedString(raw, 'id')
if (!id || NON_CHAT_MODEL_PATTERN.test(id)) {
return null
}
const name = getTrimmedString(raw, 'name')
const ownedBy =
getTrimmedString(raw, 'owned_by') || getTrimmedString(raw, 'ownedBy')
const label = name || (ownedBy ? `${id} (${ownedBy})` : id)
const contextWindow =
getPositiveInteger(raw.context_length) ??
getPositiveInteger(raw.context_window)
return {
id,
apiName: id,
label,
...(contextWindow !== undefined ? { contextWindow } : {}),
}
}
const curatedModels = [
{
id: 'claude-sonnet-4-6',
apiName: 'claude-sonnet-4-6',
aliases: ['sonnet-4.6', 'claude-sonnet'],
modelDescriptorId: 'claude-sonnet-4-6',
label: 'Claude Sonnet 4.6',
contextWindow: 200_000,
maxOutputTokens: 8_192,
},
]
const catalog = defineCatalog({
source: 'hybrid',
discovery: {
kind: 'openai-compatible',
requiresAuth: true,
mapModel: mapApiRouteModel,
},
discoveryCacheTtl: '1d',
discoveryRefreshMode: 'background-if-stale',
allowManualRefresh: true,
models: [...curatedModels],
})
export default defineGateway({
id: 'api-route',
label: 'API Route',
category: 'aggregating',
defaultBaseUrl: 'https://global.api-route.com/v1',
defaultModel: 'claude-sonnet-4-6',
supportsModelRouting: true,
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['API_ROUTE_API_KEY'],
dedicatedCredentialsOnly: false,
},
startup: {
probeReadiness: 'openai-compatible-models',
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
maxTokensField: 'max_tokens',
},
},
preset: {
id: 'api-route',
description: 'API Route OpenAI-compatible multi-model gateway',
vendorId: 'openai',
apiKeyEnvVars: ['API_ROUTE_API_KEY'],
modelEnvVars: ['API_ROUTE_MODEL', 'OPENAI_MODEL'],
},
validation: {
kind: 'credential-env',
routing: {
matchDefaultBaseUrl: true,
matchBaseUrlHosts: ['global.api-route.com'],
},
credentialEnvVars: ['API_ROUTE_API_KEY', 'OPENAI_API_KEYS', 'OPENAI_API_KEY'],
missingCredentialMessage:
'API Route auth is required. Set API_ROUTE_API_KEY.',
},
catalog,
usage: { supported: false },
})
@@ -20,6 +20,7 @@ import vendorXai from '../vendors/xai.js'
import vendorXiaomiMimo from '../vendors/xiaomi-mimo.js' import vendorXiaomiMimo from '../vendors/xiaomi-mimo.js'
import vendorZai from '../vendors/zai.js' import vendorZai from '../vendors/zai.js'
import gatewayAimlapi from '../gateways/aimlapi.js' import gatewayAimlapi from '../gateways/aimlapi.js'
import gatewayApiRoute from '../gateways/api-route.js'
import gatewayApismart from '../gateways/apismart.js' import gatewayApismart from '../gateways/apismart.js'
import gatewayAtlasCloud from '../gateways/atlas-cloud.js' import gatewayAtlasCloud from '../gateways/atlas-cloud.js'
import gatewayAtomicChat from '../gateways/atomic-chat.js' import gatewayAtomicChat from '../gateways/atomic-chat.js'
@@ -93,7 +94,7 @@ import modelXai from '../models/xai.js'
import modelXiaomiMimo from '../models/xiaomi-mimo.js' import modelXiaomiMimo from '../models/xiaomi-mimo.js'
export const VENDOR_DESCRIPTORS = [vendorAnthropic, vendorBankr, vendorDeepseek, vendorFireworks, vendorGemini, vendorLongcat, vendorMinimax, vendorMoonshot, vendorNearai, vendorOpenai, vendorVenice, vendorXai, vendorXiaomiMimo, vendorZai] as const satisfies readonly VendorDescriptor[] export const VENDOR_DESCRIPTORS = [vendorAnthropic, vendorBankr, vendorDeepseek, vendorFireworks, vendorGemini, vendorLongcat, vendorMinimax, vendorMoonshot, vendorNearai, vendorOpenai, vendorVenice, vendorXai, vendorXiaomiMimo, vendorZai] as const satisfies readonly VendorDescriptor[]
export const GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, gatewayCommandcode, gatewayConcentrate, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLlmtr, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[] export const GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApiRoute, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, gatewayCommandcode, gatewayConcentrate, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLlmtr, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[]
export const ANTHROPIC_PROXY_DESCRIPTORS = [anthropicproxyCustom] as const satisfies readonly AnthropicProxyDescriptor[] export const ANTHROPIC_PROXY_DESCRIPTORS = [anthropicproxyCustom] as const satisfies readonly AnthropicProxyDescriptor[]
export const BRAND_DESCRIPTORS = [brandClaude, brandDeepseek, brandFireworks, brandGemini, brandGlm, brandGpt, brandKimi, brandLing, brandLlama, brandLongcat, brandMacaron, brandMinimax, brandMistral, brandNearai, brandNemotron, brandOpenaiCompatibleAlias, brandQwen, brandTencent, brandXai, brandXiaomiMimo] as const satisfies readonly BrandDescriptor[] export const BRAND_DESCRIPTORS = [brandClaude, brandDeepseek, brandFireworks, brandGemini, brandGlm, brandGpt, brandKimi, brandLing, brandLlama, brandLongcat, brandMacaron, brandMinimax, brandMistral, brandNearai, brandNemotron, brandOpenaiCompatibleAlias, brandQwen, brandTencent, brandXai, brandXiaomiMimo] as const satisfies readonly BrandDescriptor[]
export const MODEL_DESCRIPTOR_GROUPS = [modelClaude, modelDeepseek, modelFireworksMerged, modelGemini, modelGlm, modelGpt, modelKimi, modelLing, modelLlama, modelLongcat, modelMacaron, modelMinimax, modelMistral, modelNearai, modelNemotron, modelOpenaiCompatibleAlias, modelOpencode, modelQwen, modelTencent, modelXai, modelXiaomiMimo] as const satisfies readonly (readonly ModelDescriptor[])[] export const MODEL_DESCRIPTOR_GROUPS = [modelClaude, modelDeepseek, modelFireworksMerged, modelGemini, modelGlm, modelGpt, modelKimi, modelLing, modelLlama, modelLongcat, modelMacaron, modelMinimax, modelMistral, modelNearai, modelNemotron, modelOpenaiCompatibleAlias, modelOpencode, modelQwen, modelTencent, modelXai, modelXiaomiMimo] as const satisfies readonly (readonly ModelDescriptor[])[]
@@ -90,6 +90,21 @@ export const PROVIDER_PRESET_MANIFEST = [
"DASHSCOPE_API_KEY" "DASHSCOPE_API_KEY"
] ]
}, },
{
"preset": "api-route",
"routeKind": "gateway",
"routeId": "api-route",
"vendorId": "openai",
"gatewayId": "api-route",
"description": "API Route OpenAI-compatible multi-model gateway",
"apiKeyEnvVars": [
"API_ROUTE_API_KEY"
],
"modelEnvVars": [
"API_ROUTE_MODEL",
"OPENAI_MODEL"
]
},
{ {
"preset": "apismart", "preset": "apismart",
"routeKind": "gateway", "routeKind": "gateway",
@@ -591,6 +606,7 @@ export const ORDERED_PROVIDER_PRESETS = [
"anthropic", "anthropic",
"dashscope-cn", "dashscope-cn",
"dashscope-intl", "dashscope-intl",
"api-route",
"apismart", "apismart",
"atlas-cloud", "atlas-cloud",
"azure-openai", "azure-openai",
+2 -1
View File
@@ -47,7 +47,8 @@ function hasUsableEnvValue(envVar: string, value: string | undefined): boolean {
envVar === 'LLMTR_API_KEY' || envVar === 'LLMTR_API_KEY' ||
envVar === 'CMD_API_KEY' || envVar === 'CMD_API_KEY' ||
envVar === 'COMMANDCODE_API_KEY' || envVar === 'COMMANDCODE_API_KEY' ||
envVar === 'COMMAND_CODE_API_KEY' envVar === 'COMMAND_CODE_API_KEY' ||
envVar === 'API_ROUTE_API_KEY'
) { ) {
return hasUsableOpenAICredential(value) return hasUsableOpenAICredential(value)
} }
+9
View File
@@ -1223,6 +1223,15 @@ test('resolveActiveRouteIdFromEnv honors an explicit OpenAI opt-out over Concent
expect(resolveActiveRouteIdFromEnv({ CLAUDE_CODE_USE_OPENAI: '0', CONCENTRATE_API_KEY: 'concentrate-key' })).not.toBe('concentrate') expect(resolveActiveRouteIdFromEnv({ CLAUDE_CODE_USE_OPENAI: '0', CONCENTRATE_API_KEY: 'concentrate-key' })).not.toBe('concentrate')
}) })
test('resolveActiveRouteIdFromEnv honors an explicit OpenAI opt-out over API Route', () => {
expect(
resolveActiveRouteIdFromEnv({
CLAUDE_CODE_USE_OPENAI: '0',
API_ROUTE_API_KEY: 'api-route-key',
}),
).not.toBe('api-route')
})
test('resolveActiveRouteIdFromEnv refines generic OpenAI profile by Concentrate base URL', () => { test('resolveActiveRouteIdFromEnv refines generic OpenAI profile by Concentrate base URL', () => {
expect( expect(
resolveActiveRouteIdFromEnv({ resolveActiveRouteIdFromEnv({
+86 -2
View File
@@ -246,7 +246,8 @@ function hasUsableEnvCredentialValue(
envVar === 'LLMTR_API_KEY' || envVar === 'LLMTR_API_KEY' ||
envVar === 'CMD_API_KEY' || envVar === 'CMD_API_KEY' ||
envVar === 'COMMANDCODE_API_KEY' || envVar === 'COMMANDCODE_API_KEY' ||
envVar === 'COMMAND_CODE_API_KEY' envVar === 'COMMAND_CODE_API_KEY' ||
envVar === 'API_ROUTE_API_KEY'
) { ) {
return hasUsableOpenAICredential(value) return hasUsableOpenAICredential(value)
} }
@@ -539,6 +540,54 @@ export function isCanonicalCommandcodeInferenceBaseUrl(
} }
} }
const API_ROUTE_CANONICAL_INFERENCE_BASE_URL =
'https://global.api-route.com/v1'
export function isCanonicalApiRouteInferenceBaseUrl(
value: string | undefined,
): boolean {
const trimmed = value?.trim()
if (!trimmed) {
return false
}
try {
const canonical = new URL(API_ROUTE_CANONICAL_INFERENCE_BASE_URL)
const candidate = new URL(trimmed)
const normalizePath = (pathname: string): string =>
pathname.replace(/\/+$/, '') || '/'
return (
candidate.protocol === 'https:' &&
!candidate.port &&
!candidate.search &&
!candidate.hash &&
candidate.hostname.toLowerCase() === canonical.hostname.toLowerCase() &&
normalizePath(candidate.pathname) === normalizePath(canonical.pathname)
)
} catch {
return false
}
}
export function isApiRouteBaseUrl(value: string | undefined): boolean {
const trimmed = value?.trim()
if (!trimmed) {
return false
}
try {
const parsed = new URL(trimmed)
return (
parsed.protocol === 'https:' &&
!parsed.port &&
(parsed.hostname.toLowerCase() === 'global.api-route.com' ||
parsed.hostname.toLowerCase() === 'api-route.com')
)
} catch {
return false
}
}
export function getConcentrateBaseUrlOverride( export function getConcentrateBaseUrlOverride(
processEnv: NodeJS.ProcessEnv = process.env, processEnv: NodeJS.ProcessEnv = process.env,
): string | undefined { ): string | undefined {
@@ -1039,6 +1088,18 @@ export function hasConcentrateEnvOnlyProviderIntent(
) )
} }
export function hasApiRouteEnvOnlyProviderIntent(
processEnv: NodeJS.ProcessEnv = process.env,
): boolean {
return (
hasUsableOpenAICredential(processEnv.API_ROUTE_API_KEY) &&
!hasConflictingOpenAIBaseUrlForRoute(processEnv, isApiRouteBaseUrl) &&
!(processEnv.CLAUDE_CODE_USE_OPENAI !== undefined &&
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_OPENAI)) &&
hasNoExplicitNonOpenAIProvider(processEnv)
)
}
export function resolveEnvOnlyProviderRouteId( export function resolveEnvOnlyProviderRouteId(
processEnv: NodeJS.ProcessEnv = process.env, processEnv: NodeJS.ProcessEnv = process.env,
): ):
@@ -1053,6 +1114,7 @@ export function resolveEnvOnlyProviderRouteId(
| 'clinepass' | 'clinepass'
| 'apismart' | 'apismart'
| 'concentrate' | 'concentrate'
| 'api-route'
| null { | null {
if ( if (
hasMiniMaxRouteIntent(processEnv) && hasMiniMaxRouteIntent(processEnv) &&
@@ -1105,6 +1167,10 @@ export function resolveEnvOnlyProviderRouteId(
return 'concentrate' return 'concentrate'
} }
if (hasApiRouteEnvOnlyProviderIntent(processEnv)) {
return 'api-route'
}
return null return null
} }
@@ -1203,6 +1269,13 @@ export function resolveRouteCredentialValue(
) { ) {
return undefined return undefined
} }
if (
routeId === 'api-route' &&
options?.baseUrl !== undefined &&
!isCanonicalApiRouteInferenceBaseUrl(options.baseUrl)
) {
return undefined
}
return getRouteCredentialValue(routeId, processEnv) return getRouteCredentialValue(routeId, processEnv)
} }
@@ -1327,6 +1400,12 @@ export function resolveRouteIdFromBaseUrl(
) { ) {
continue continue
} }
if (
route.id === 'api-route' &&
!isCanonicalApiRouteInferenceBaseUrl(baseUrl)
) {
continue
}
return route.id return route.id
} }
} }
@@ -1347,7 +1426,9 @@ export function resolveRouteIdFromBaseUrl(
(route.id === 'llmtr' && (route.id === 'llmtr' &&
!isCanonicalLlmtrInferenceBaseUrl(baseUrl)) || !isCanonicalLlmtrInferenceBaseUrl(baseUrl)) ||
(route.id === 'commandcode' && (route.id === 'commandcode' &&
!isCanonicalCommandcodeInferenceBaseUrl(baseUrl)) !isCanonicalCommandcodeInferenceBaseUrl(baseUrl)) ||
(route.id === 'api-route' &&
!isApiRouteBaseUrl(baseUrl))
) { ) {
continue continue
} }
@@ -1393,6 +1474,9 @@ function profileRouteHonorsBaseUrlBoundary(
if (routeId === 'commandcode') { if (routeId === 'commandcode') {
return isCanonicalCommandcodeInferenceBaseUrl(baseUrl) return isCanonicalCommandcodeInferenceBaseUrl(baseUrl)
} }
if (routeId === 'api-route') {
return isCanonicalApiRouteInferenceBaseUrl(baseUrl)
}
if (routeId === 'zai') { if (routeId === 'zai') {
return !baseUrl || isCanonicalZaiCodingPlanBaseUrl(baseUrl) return !baseUrl || isCanonicalZaiCodingPlanBaseUrl(baseUrl)
} }
+206
View File
@@ -89,6 +89,8 @@ const originalEnv = {
LONGCAT_API_KEY: process.env.LONGCAT_API_KEY, LONGCAT_API_KEY: process.env.LONGCAT_API_KEY,
AIMLAPI_API_KEY: process.env.AIMLAPI_API_KEY, AIMLAPI_API_KEY: process.env.AIMLAPI_API_KEY,
APISMART_API_KEY: process.env.APISMART_API_KEY, APISMART_API_KEY: process.env.APISMART_API_KEY,
API_ROUTE_API_KEY: process.env.API_ROUTE_API_KEY,
API_ROUTE_MODEL: process.env.API_ROUTE_MODEL,
CONCENTRATE_API_KEY: process.env.CONCENTRATE_API_KEY, CONCENTRATE_API_KEY: process.env.CONCENTRATE_API_KEY,
CONCENTRATE_BASE_URL: process.env.CONCENTRATE_BASE_URL, CONCENTRATE_BASE_URL: process.env.CONCENTRATE_BASE_URL,
CONCENTRATE_MODEL: process.env.CONCENTRATE_MODEL, CONCENTRATE_MODEL: process.env.CONCENTRATE_MODEL,
@@ -209,6 +211,8 @@ beforeEach(async () => {
delete process.env.LONGCAT_API_KEY delete process.env.LONGCAT_API_KEY
delete process.env.AIMLAPI_API_KEY delete process.env.AIMLAPI_API_KEY
delete process.env.APISMART_API_KEY delete process.env.APISMART_API_KEY
delete process.env.API_ROUTE_API_KEY
delete process.env.API_ROUTE_MODEL
delete process.env.CONCENTRATE_API_KEY delete process.env.CONCENTRATE_API_KEY
delete process.env.CONCENTRATE_BASE_URL delete process.env.CONCENTRATE_BASE_URL
delete process.env.CONCENTRATE_MODEL delete process.env.CONCENTRATE_MODEL
@@ -274,6 +278,8 @@ afterEach(() => {
restoreEnv('LONGCAT_API_KEY', originalEnv.LONGCAT_API_KEY) restoreEnv('LONGCAT_API_KEY', originalEnv.LONGCAT_API_KEY)
restoreEnv('AIMLAPI_API_KEY', originalEnv.AIMLAPI_API_KEY) restoreEnv('AIMLAPI_API_KEY', originalEnv.AIMLAPI_API_KEY)
restoreEnv('APISMART_API_KEY', originalEnv.APISMART_API_KEY) restoreEnv('APISMART_API_KEY', originalEnv.APISMART_API_KEY)
restoreEnv('API_ROUTE_API_KEY', originalEnv.API_ROUTE_API_KEY)
restoreEnv('API_ROUTE_MODEL', originalEnv.API_ROUTE_MODEL)
restoreEnv('CONCENTRATE_API_KEY', originalEnv.CONCENTRATE_API_KEY) restoreEnv('CONCENTRATE_API_KEY', originalEnv.CONCENTRATE_API_KEY)
restoreEnv('CONCENTRATE_BASE_URL', originalEnv.CONCENTRATE_BASE_URL) restoreEnv('CONCENTRATE_BASE_URL', originalEnv.CONCENTRATE_BASE_URL)
restoreEnv('CONCENTRATE_MODEL', originalEnv.CONCENTRATE_MODEL) restoreEnv('CONCENTRATE_MODEL', originalEnv.CONCENTRATE_MODEL)
@@ -930,6 +936,206 @@ test('env-only ApiSmart setup withholds its key from a noncanonical same-host UR
expect(process.env.OPENAI_API_KEY).toBeUndefined() expect(process.env.OPENAI_API_KEY).toBeUndefined()
}) })
test('routes env-only API Route requests through the OpenAI-compatible shim', async () => {
let capturedUrl: string | undefined
let capturedHeaders: Headers | undefined
let capturedBody: Record<string, unknown> | undefined
delete process.env.CLAUDE_CODE_USE_GEMINI
delete process.env.GEMINI_API_KEY
delete process.env.GEMINI_MODEL
delete process.env.GEMINI_BASE_URL
delete process.env.GEMINI_AUTH_MODE
process.env.API_ROUTE_API_KEY = 'api-route-test-key'
process.env.API_ROUTE_MODEL = 'claude-sonnet-4-6'
process.env.ANTHROPIC_CUSTOM_HEADERS = 'X-Proxy-Auth: ambient-proxy-secret'
globalThis.fetch = (async (input, init) => {
capturedUrl =
typeof input === 'string'
? input
: input instanceof URL
? input.toString()
: input.url
capturedHeaders = new Headers(init?.headers)
capturedBody = JSON.parse(String(init?.body)) as Record<string, unknown>
return new Response(
JSON.stringify({
id: 'chatcmpl-api-route',
model: 'claude-sonnet-4-6',
choices: [
{
message: {
role: 'assistant',
content: 'api-route ok',
},
finish_reason: 'stop',
},
],
usage: {
prompt_tokens: 8,
completion_tokens: 3,
total_tokens: 11,
},
}),
{
headers: {
'Content-Type': 'application/json',
},
},
)
}) as typeof fetch
const client = await getAnthropicClient({
maxRetries: 0,
model: 'claude-sonnet-4-6',
})
const response = await client.messages.create({
max_tokens: 64,
messages: [{ role: 'user', content: 'hello' }],
model: 'claude-sonnet-4-6',
})
expect(response.content[0]).toEqual({
type: 'text',
text: 'api-route ok',
})
expect(capturedUrl).toBe(
'https://global.api-route.com/v1/chat/completions',
)
expect(capturedHeaders?.get('authorization')).toBe(
'Bearer api-route-test-key',
)
expect(capturedHeaders?.get('x-proxy-auth')).toBeNull()
expect(capturedBody?.model).toBe('claude-sonnet-4-6')
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
expect(process.env.OPENAI_BASE_URL).toBe('https://global.api-route.com/v1')
expect(process.env.OPENAI_API_KEY).toBe('api-route-test-key')
expect(process.env.OPENAI_MODEL).toBe('claude-sonnet-4-6')
})
test('env-only API Route setup prefers API_ROUTE_API_KEY over stale OPENAI_API_KEYS pool', async () => {
let capturedHeaders: Headers | undefined
delete process.env.CLAUDE_CODE_USE_GEMINI
delete process.env.GEMINI_API_KEY
delete process.env.GEMINI_MODEL
delete process.env.GEMINI_BASE_URL
delete process.env.GEMINI_AUTH_MODE
process.env.API_ROUTE_API_KEY = 'api-route-correct-key'
process.env.OPENAI_API_KEYS = 'stale-key-1,stale-key-2'
process.env.OPENAI_BASE_URL = 'https://global.api-route.com/v1'
globalThis.fetch = (async (_input, init) => {
capturedHeaders = new Headers(init?.headers)
return new Response(
JSON.stringify({
id: 'chatcmpl-api-route-precedence',
model: 'claude-sonnet-4-6',
choices: [
{
message: {
role: 'assistant',
content: 'api-route ok',
},
finish_reason: 'stop',
},
],
usage: {
prompt_tokens: 8,
completion_tokens: 3,
total_tokens: 11,
},
}),
{
headers: {
'Content-Type': 'application/json',
},
},
)
}) as typeof fetch
const client = await getAnthropicClient({
maxRetries: 0,
model: 'claude-sonnet-4-6',
})
await client.messages.create({
max_tokens: 64,
messages: [{ role: 'user', content: 'hello' }],
model: 'claude-sonnet-4-6',
})
expect(capturedHeaders?.get('authorization')).toBe(
'Bearer api-route-correct-key',
)
expect(capturedHeaders?.get('authorization')).not.toContain('stale-key')
expect(process.env.OPENAI_API_KEYS).toBeUndefined()
expect(process.env.OPENAI_API_KEY).toBe('api-route-correct-key')
})
test('env-only API Route setup withholds its key from a noncanonical URL', async () => {
let capturedHeaders: Headers | undefined
delete process.env.CLAUDE_CODE_USE_GEMINI
delete process.env.GEMINI_API_KEY
delete process.env.GEMINI_MODEL
delete process.env.GEMINI_BASE_URL
delete process.env.GEMINI_AUTH_MODE
process.env.API_ROUTE_API_KEY = 'api-route-test-key'
process.env.OPENAI_BASE_URL = 'https://global.api-route.com/v1/models'
process.env.OPENAI_API_KEYS = 'ambient-key-pool-1,ambient-key-pool-2'
globalThis.fetch = (async (_input, init) => {
capturedHeaders = new Headers(init?.headers)
return new Response(
JSON.stringify({
id: 'chatcmpl-api-route-noncanonical',
model: 'claude-sonnet-4-6',
choices: [
{
message: {
role: 'assistant',
content: 'api-route noncanonical ok',
},
finish_reason: 'stop',
},
],
usage: {
prompt_tokens: 8,
completion_tokens: 3,
total_tokens: 11,
},
}),
{
headers: {
'Content-Type': 'application/json',
},
},
)
}) as typeof fetch
const client = await getAnthropicClient({
maxRetries: 0,
model: 'claude-sonnet-4-6',
})
await client.messages.create({
max_tokens: 64,
messages: [{ role: 'user', content: 'hello' }],
model: 'claude-sonnet-4-6',
})
expect(capturedHeaders?.get('authorization')).toBeNull()
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
expect(process.env.OPENAI_BASE_URL).toBe(
'https://global.api-route.com/v1/models',
)
expect(process.env.OPENAI_API_KEY).toBeUndefined()
expect(process.env.OPENAI_API_KEYS).toBeUndefined()
})
test('routes env-only Concentrate requests through the OpenAI-compatible shim', async () => { test('routes env-only Concentrate requests through the OpenAI-compatible shim', async () => {
let capturedUrl: string | undefined let capturedUrl: string | undefined
let capturedHeaders: Headers | undefined let capturedHeaders: Headers | undefined
+37
View File
@@ -43,6 +43,7 @@ import {
getLongcatBaseUrlOverride, getLongcatBaseUrlOverride,
getMiniMaxBaseUrlOverride, getMiniMaxBaseUrlOverride,
getNearaiBaseUrlOverride, getNearaiBaseUrlOverride,
isCanonicalApiRouteInferenceBaseUrl,
isCanonicalApismartInferenceBaseUrl, isCanonicalApismartInferenceBaseUrl,
isCanonicalConcentrateInferenceBaseUrl, isCanonicalConcentrateInferenceBaseUrl,
getRouteDefaultBaseUrl, getRouteDefaultBaseUrl,
@@ -450,6 +451,39 @@ function applyConcentrateEnvOnlyDefaults(): void {
delete process.env.ANTHROPIC_CUSTOM_HEADERS delete process.env.ANTHROPIC_CUSTOM_HEADERS
} }
function applyApiRouteEnvOnlyDefaults(): void {
const baseUrlOverride =
usableProviderConfigEnvValue(process.env.OPENAI_BASE_URL) ||
usableProviderConfigEnvValue(process.env.OPENAI_API_BASE) ||
undefined
const modelOverride =
usableProviderConfigEnvValue(process.env.API_ROUTE_MODEL) ||
usableProviderConfigEnvValue(process.env.OPENAI_MODEL) ||
undefined
const apiKey = process.env.API_ROUTE_API_KEY
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL =
baseUrlOverride ?? getRouteDefaultBaseUrl('api-route')
process.env.OPENAI_MODEL = modelOverride ?? getRouteDefaultModel('api-route')
if (
hasUsableOpenAICredential(apiKey) &&
isCanonicalApiRouteInferenceBaseUrl(process.env.OPENAI_BASE_URL)
) {
process.env.OPENAI_API_KEY = apiKey
delete process.env.OPENAI_API_KEYS
} else {
delete process.env.OPENAI_API_KEY
delete process.env.OPENAI_API_KEYS
}
delete process.env.OPENAI_API_FORMAT
delete process.env.OPENAI_AZURE_STYLE
delete process.env.OPENAI_AUTH_HEADER
delete process.env.OPENAI_AUTH_SCHEME
delete process.env.OPENAI_AUTH_HEADER_VALUE
delete process.env.ANTHROPIC_CUSTOM_HEADERS
}
function usableProviderConfigEnvValue( function usableProviderConfigEnvValue(
value: string | undefined, value: string | undefined,
): string | undefined { ): string | undefined {
@@ -574,6 +608,8 @@ export async function getAnthropicClient({
envOnlyProviderRouteId === 'apismart' && !useMiniMaxEnvOnlyProvider envOnlyProviderRouteId === 'apismart' && !useMiniMaxEnvOnlyProvider
const useConcentrateEnvOnlyProvider = const useConcentrateEnvOnlyProvider =
envOnlyProviderRouteId === 'concentrate' && !useMiniMaxEnvOnlyProvider envOnlyProviderRouteId === 'concentrate' && !useMiniMaxEnvOnlyProvider
const useApiRouteEnvOnlyProvider =
envOnlyProviderRouteId === 'api-route' && !useMiniMaxEnvOnlyProvider
if (useMiniMaxEnvOnlyProvider) applyMiniMaxEnvOnlyDefaults(model) if (useMiniMaxEnvOnlyProvider) applyMiniMaxEnvOnlyDefaults(model)
if (useXiaomiMimoEnvOnlyProvider) applyXiaomiMimoEnvOnlyDefaults() if (useXiaomiMimoEnvOnlyProvider) applyXiaomiMimoEnvOnlyDefaults()
if (useXaiEnvOnlyProvider) applyXaiEnvOnlyDefaults() if (useXaiEnvOnlyProvider) applyXaiEnvOnlyDefaults()
@@ -583,6 +619,7 @@ export async function getAnthropicClient({
if (useAimlapiEnvOnlyProvider) applyAimlapiEnvOnlyDefaults() if (useAimlapiEnvOnlyProvider) applyAimlapiEnvOnlyDefaults()
if (useApismartEnvOnlyProvider) applyApismartEnvOnlyDefaults() if (useApismartEnvOnlyProvider) applyApismartEnvOnlyDefaults()
if (useConcentrateEnvOnlyProvider) applyConcentrateEnvOnlyDefaults() if (useConcentrateEnvOnlyProvider) applyConcentrateEnvOnlyDefaults()
if (useApiRouteEnvOnlyProvider) applyApiRouteEnvOnlyDefaults()
const containerId = process.env.CLAUDE_CODE_CONTAINER_ID const containerId = process.env.CLAUDE_CODE_CONTAINER_ID
const remoteSessionId = process.env.CLAUDE_CODE_REMOTE_SESSION_ID const remoteSessionId = process.env.CLAUDE_CODE_REMOTE_SESSION_ID
@@ -21,6 +21,7 @@ const originalEnv = {
OPENAI_API_KEY: process.env.OPENAI_API_KEY, OPENAI_API_KEY: process.env.OPENAI_API_KEY,
OPENAI_API_KEYS: process.env.OPENAI_API_KEYS, OPENAI_API_KEYS: process.env.OPENAI_API_KEYS,
LLMTR_API_KEY: process.env.LLMTR_API_KEY, LLMTR_API_KEY: process.env.LLMTR_API_KEY,
API_ROUTE_API_KEY: process.env.API_ROUTE_API_KEY,
CMD_API_KEY: process.env.CMD_API_KEY, CMD_API_KEY: process.env.CMD_API_KEY,
COMMANDCODE_API_KEY: process.env.COMMANDCODE_API_KEY, COMMANDCODE_API_KEY: process.env.COMMANDCODE_API_KEY,
COMMAND_CODE_API_KEY: process.env.COMMAND_CODE_API_KEY, COMMAND_CODE_API_KEY: process.env.COMMAND_CODE_API_KEY,
@@ -455,6 +456,7 @@ beforeEach(async () => {
process.env.OPENAI_API_KEY = 'test-key' process.env.OPENAI_API_KEY = 'test-key'
delete process.env.OPENAI_API_KEYS delete process.env.OPENAI_API_KEYS
delete process.env.LLMTR_API_KEY delete process.env.LLMTR_API_KEY
delete process.env.API_ROUTE_API_KEY
delete process.env.CMD_API_KEY delete process.env.CMD_API_KEY
delete process.env.COMMANDCODE_API_KEY delete process.env.COMMANDCODE_API_KEY
delete process.env.COMMAND_CODE_API_KEY delete process.env.COMMAND_CODE_API_KEY
@@ -508,6 +510,7 @@ afterEach(() => {
restoreEnv('OPENAI_API_KEY', originalEnv.OPENAI_API_KEY) restoreEnv('OPENAI_API_KEY', originalEnv.OPENAI_API_KEY)
restoreEnv('OPENAI_API_KEYS', originalEnv.OPENAI_API_KEYS) restoreEnv('OPENAI_API_KEYS', originalEnv.OPENAI_API_KEYS)
restoreEnv('LLMTR_API_KEY', originalEnv.LLMTR_API_KEY) restoreEnv('LLMTR_API_KEY', originalEnv.LLMTR_API_KEY)
restoreEnv('API_ROUTE_API_KEY', originalEnv.API_ROUTE_API_KEY)
restoreEnv('CMD_API_KEY', originalEnv.CMD_API_KEY) restoreEnv('CMD_API_KEY', originalEnv.CMD_API_KEY)
restoreEnv('COMMANDCODE_API_KEY', originalEnv.COMMANDCODE_API_KEY) restoreEnv('COMMANDCODE_API_KEY', originalEnv.COMMANDCODE_API_KEY)
restoreEnv('COMMAND_CODE_API_KEY', originalEnv.COMMAND_CODE_API_KEY) restoreEnv('COMMAND_CODE_API_KEY', originalEnv.COMMAND_CODE_API_KEY)
@@ -588,6 +591,22 @@ test('Concentrate selection prefers its dedicated key over a generic OPENAI_API_
expect(captured.authorization).toBe('Bearer concentrate-key') expect(captured.authorization).toBe('Bearer concentrate-key')
}) })
test('API Route selection prefers its dedicated key over a generic OPENAI_API_KEYS pool', async () => {
process.env.API_ROUTE_API_KEY = 'api-route-key'
process.env.OPENAI_API_KEYS = 'generic-openai-key-a,generic-openai-key-b'
delete process.env.OPENAI_API_KEY
delete process.env.OPENAI_BASE_URL
delete process.env.OPENAI_MODEL
const result = applyProviderFlag('api-route', [])
expect(result.error).toBeUndefined()
const captured = await captureChatCompletionRequest('claude-sonnet-4-6')
expect(captured.url).toBe('https://global.api-route.com/v1/chat/completions')
expect(captured.authorization).toBe('Bearer api-route-key')
})
test('selected LLMTR route sends LLMTR_API_KEY through the generic route credential resolver', async () => { test('selected LLMTR route sends LLMTR_API_KEY through the generic route credential resolver', async () => {
process.env.LLMTR_API_KEY = 'llmtr-key' process.env.LLMTR_API_KEY = 'llmtr-key'
delete process.env.OPENAI_API_KEYS delete process.env.OPENAI_API_KEYS
@@ -327,6 +327,7 @@ export async function executeOpenAIRequest(
requestProcessEnv.ATLAS_CLOUD_API_KEY, requestProcessEnv.ATLAS_CLOUD_API_KEY,
requestProcessEnv.APISMART_API_KEY, requestProcessEnv.APISMART_API_KEY,
requestProcessEnv.CONCENTRATE_API_KEY, requestProcessEnv.CONCENTRATE_API_KEY,
requestProcessEnv.API_ROUTE_API_KEY,
requestProcessEnv.NEARAI_API_KEY, requestProcessEnv.NEARAI_API_KEY,
requestProcessEnv.FIREWORKS_API_KEY, requestProcessEnv.FIREWORKS_API_KEY,
requestProcessEnv.LONGCAT_API_KEY, requestProcessEnv.LONGCAT_API_KEY,
+45
View File
@@ -24,6 +24,8 @@ const TEST_ENV_KEYS = [
'COMMAND_CODE_API_KEY', 'COMMAND_CODE_API_KEY',
'APISMART_API_KEY', 'APISMART_API_KEY',
'APISMART_MODEL', 'APISMART_MODEL',
'API_ROUTE_API_KEY',
'API_ROUTE_MODEL',
'OPENAI_API_KEYS', 'OPENAI_API_KEYS',
'OPENAI_API_KEY', 'OPENAI_API_KEY',
'OPENAI_AZURE_STYLE', 'OPENAI_AZURE_STYLE',
@@ -298,6 +300,49 @@ describe('loadEnvFile', () => {
}) })
}) })
it('loads documented API Route env-only provider setup values', () => {
const filePath = writeTempEnvFile([
'API_ROUTE_API_KEY=api-route-key',
'API_ROUTE_MODEL=claude-sonnet-4-6',
].join('\n'))
const loaded = loadEnvFile(filePath)
expect(process.env.API_ROUTE_API_KEY).toBe('api-route-key')
expect(process.env.API_ROUTE_MODEL).toBe('claude-sonnet-4-6')
expect(loaded).toEqual({
API_ROUTE_API_KEY: 'api-route-key',
API_ROUTE_MODEL: 'claude-sonnet-4-6',
})
})
it('preserves existing API Route env values over provider env-file values', () => {
process.env.API_ROUTE_API_KEY = 'shell-api-route-key'
process.env.API_ROUTE_MODEL = 'shell-api-route-model'
const filePath = writeTempEnvFile([
'API_ROUTE_API_KEY=file-api-route-key',
'API_ROUTE_MODEL=file-api-route-model',
].join('\n'))
const loaded = loadEnvFile(filePath)
expect(process.env.API_ROUTE_API_KEY).toBe('shell-api-route-key')
expect(process.env.API_ROUTE_MODEL).toBe('shell-api-route-model')
expect(loaded).toEqual({})
})
it('rejects API Route env files atomically when any variable is unsupported', () => {
const filePath = writeTempEnvFile([
'API_ROUTE_API_KEY=api-route-key',
'UNSAFE_API_ROUTE_SETTING=blocked',
].join('\n'))
expect(() => loadEnvFile(filePath)).toThrow(
'Unsupported variable UNSAFE_API_ROUTE_SETTING in --provider-env-file',
)
expect(process.env.API_ROUTE_API_KEY).toBeUndefined()
})
it('loads the dedicated LLMTR credential without selecting a route', () => { it('loads the dedicated LLMTR credential without selecting a route', () => {
const filePath = writeTempEnvFile('LLMTR_API_KEY=llmtr-key') const filePath = writeTempEnvFile('LLMTR_API_KEY=llmtr-key')
+2
View File
@@ -17,6 +17,8 @@ const ALLOWED_ENV_FILE_KEYS = new Set([
'ATLAS_CLOUD_API_KEY', 'ATLAS_CLOUD_API_KEY',
'APISMART_API_KEY', 'APISMART_API_KEY',
'APISMART_MODEL', 'APISMART_MODEL',
'API_ROUTE_API_KEY',
'API_ROUTE_MODEL',
'AWS_BEARER_TOKEN_BEDROCK', 'AWS_BEARER_TOKEN_BEDROCK',
'AWS_DEFAULT_REGION', 'AWS_DEFAULT_REGION',
'AWS_PROFILE', 'AWS_PROFILE',
+36 -1
View File
@@ -18,7 +18,10 @@ import {
loadEnvFile, loadEnvFile,
rememberLoadedEnvFileValues, rememberLoadedEnvFileValues,
} from './envFile.js' } from './envFile.js'
import { applyConfigEnvironmentVariables } from './managedEnv.js' import {
applyConfigEnvironmentVariables,
applySafeConfigEnvironmentVariables,
} from './managedEnv.js'
const ENV_KEYS = [ const ENV_KEYS = [
'CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST', 'CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST',
@@ -28,6 +31,8 @@ const ENV_KEYS = [
'CMD_API_KEY', 'CMD_API_KEY',
'COMMANDCODE_API_KEY', 'COMMANDCODE_API_KEY',
'COMMAND_CODE_API_KEY', 'COMMAND_CODE_API_KEY',
'API_ROUTE_API_KEY',
'API_ROUTE_MODEL',
'OPENAI_API_KEY', 'OPENAI_API_KEY',
'OPENAI_AZURE_STYLE', 'OPENAI_AZURE_STYLE',
'OPENAI_BASE_URL', 'OPENAI_BASE_URL',
@@ -95,6 +100,36 @@ function writeTempEnvFile(content: string): string {
return filePath return filePath
} }
describe('host-managed API Route settings boundary', () => {
for (const [name, applySettings] of [
['pre-trust', applySafeConfigEnvironmentVariables],
['post-trust', applyConfigEnvironmentVariables],
] as const) {
it(`preserves host routing against API Route settings during ${name} merge`, () => {
process.env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST = '1'
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
process.env.OPENAI_MODEL = 'host-model'
process.env.OPENAI_API_KEY = 'host-key'
saveGlobalConfig(current => ({
...current,
env: {
API_ROUTE_API_KEY: 'settings-api-route-key',
API_ROUTE_MODEL: 'settings-api-route-model',
},
}))
applySettings()
expect(process.env.API_ROUTE_API_KEY).toBeUndefined()
expect(process.env.API_ROUTE_MODEL).toBeUndefined()
expect(process.env.OPENAI_BASE_URL).toBe('https://api.openai.com/v1')
expect(process.env.OPENAI_MODEL).toBe('host-model')
expect(process.env.OPENAI_API_KEY).toBe('host-key')
})
}
})
describe('applyConfigEnvironmentVariables', () => { describe('applyConfigEnvironmentVariables', () => {
it('preserves the complete host-managed Command Code route against settings env', () => { it('preserves the complete host-managed Command Code route against settings env', () => {
process.env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST = '1' process.env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST = '1'
+2
View File
@@ -43,6 +43,7 @@ const PROVIDER_MANAGED_ENV_VARS = new Set([
'CMD_API_KEY', 'CMD_API_KEY',
'COMMANDCODE_API_KEY', 'COMMANDCODE_API_KEY',
'COMMAND_CODE_API_KEY', 'COMMAND_CODE_API_KEY',
'API_ROUTE_API_KEY',
'OPENAI_API_KEY', 'OPENAI_API_KEY',
'OPENAI_API_KEYS', 'OPENAI_API_KEYS',
'CLAUDE_CODE_SKIP_BEDROCK_AUTH', 'CLAUDE_CODE_SKIP_BEDROCK_AUTH',
@@ -65,6 +66,7 @@ const PROVIDER_MANAGED_ENV_VARS = new Set([
'ANTHROPIC_SMALL_FAST_MODEL', 'ANTHROPIC_SMALL_FAST_MODEL',
'ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION', 'ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION',
'CLAUDE_CODE_SUBAGENT_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL',
'API_ROUTE_MODEL',
'OPENAI_MODEL', 'OPENAI_MODEL',
]) ])
@@ -80,6 +80,8 @@ const SAVED_ENV = {
CONCENTRATE_API_KEY: process.env.CONCENTRATE_API_KEY, CONCENTRATE_API_KEY: process.env.CONCENTRATE_API_KEY,
CONCENTRATE_BASE_URL: process.env.CONCENTRATE_BASE_URL, CONCENTRATE_BASE_URL: process.env.CONCENTRATE_BASE_URL,
CONCENTRATE_MODEL: process.env.CONCENTRATE_MODEL, CONCENTRATE_MODEL: process.env.CONCENTRATE_MODEL,
API_ROUTE_API_KEY: process.env.API_ROUTE_API_KEY,
API_ROUTE_MODEL: process.env.API_ROUTE_MODEL,
OPENAI_MODEL: process.env.OPENAI_MODEL, OPENAI_MODEL: process.env.OPENAI_MODEL,
OPENAI_BASE_URL: process.env.OPENAI_BASE_URL, OPENAI_BASE_URL: process.env.OPENAI_BASE_URL,
CODEX_API_KEY: process.env.CODEX_API_KEY, CODEX_API_KEY: process.env.CODEX_API_KEY,
@@ -142,6 +144,8 @@ beforeEach(async () => {
delete process.env.CONCENTRATE_API_KEY delete process.env.CONCENTRATE_API_KEY
delete process.env.CONCENTRATE_BASE_URL delete process.env.CONCENTRATE_BASE_URL
delete process.env.CONCENTRATE_MODEL delete process.env.CONCENTRATE_MODEL
delete process.env.API_ROUTE_API_KEY
delete process.env.API_ROUTE_MODEL
delete process.env.OPENAI_MODEL delete process.env.OPENAI_MODEL
delete process.env.OPENAI_BASE_URL delete process.env.OPENAI_BASE_URL
delete process.env.CODEX_API_KEY delete process.env.CODEX_API_KEY
@@ -434,6 +438,39 @@ test.each(['null', 'undefined', ' '])(
}, },
) )
test('API Route selects its dedicated model before client normalization', async () => {
saveGlobalConfig(current => ({ ...current, model: 'stale-other-provider-model' }))
process.env.API_ROUTE_API_KEY = 'api-route-test'
process.env.API_ROUTE_MODEL = 'custom-api-route-model'
const {
getDefaultMainLoopModelSetting,
getMainLoopModel,
getUserSpecifiedModelSetting,
} = await importFreshModelModule()
expect(getUserSpecifiedModelSetting()).toBe('custom-api-route-model')
expect(getDefaultMainLoopModelSetting()).toBe('custom-api-route-model')
expect(getMainLoopModel()).toBe('custom-api-route-model')
})
test('API Route honors its legacy OpenAI model fallback before client normalization', async () => {
saveGlobalConfig(current => ({ ...current, model: 'stale-other-provider-model' }))
process.env.API_ROUTE_API_KEY = 'api-route-test'
process.env.OPENAI_MODEL = 'legacy-api-route-model'
const { getMainLoopModel, getUserSpecifiedModelSetting } =
await importFreshModelModule()
expect(getUserSpecifiedModelSetting()).toBe('legacy-api-route-model')
expect(getMainLoopModel()).toBe('legacy-api-route-model')
})
test('API Route uses its descriptor default before client normalization', async () => {
process.env.API_ROUTE_API_KEY = 'api-route-test'
const { getDefaultMainLoopModelSetting } = await importFreshModelModule()
expect(getDefaultMainLoopModelSetting()).toBe('claude-sonnet-4-6')
})
test('getDefaultMainLoopModelSetting uses the NVIDIA NIM route model', async () => { test('getDefaultMainLoopModelSetting uses the NVIDIA NIM route model', async () => {
process.env.NVIDIA_NIM = '1' process.env.NVIDIA_NIM = '1'
process.env.CLAUDE_CODE_USE_OPENAI = '1' process.env.CLAUDE_CODE_USE_OPENAI = '1'
+21 -1
View File
@@ -69,6 +69,17 @@ function getAllowedConcentrateConfigModel(): string | undefined {
return undefined return undefined
} }
function getAllowedApiRouteConfigModel(): string | undefined {
for (const value of [
process.env.API_ROUTE_MODEL,
process.env.OPENAI_MODEL,
]) {
const model = getUsableProviderConfigModel(value)
if (model && isModelAllowed(model)) return model
}
return undefined
}
export function getSmallFastModel(): ModelName { export function getSmallFastModel(): ModelName {
if (process.env.ANTHROPIC_SMALL_FAST_MODEL) return process.env.ANTHROPIC_SMALL_FAST_MODEL if (process.env.ANTHROPIC_SMALL_FAST_MODEL) return process.env.ANTHROPIC_SMALL_FAST_MODEL
if (isCustomAnthropicProvider()) { if (isCustomAnthropicProvider()) {
@@ -173,7 +184,9 @@ export function getUserSpecifiedModelSetting(): ModelSetting | undefined {
// consume its dedicated setting here rather than falling through to a // consume its dedicated setting here rather than falling through to a
// saved model from an unrelated provider. // saved model from an unrelated provider.
? getAllowedConcentrateConfigModel() ? getAllowedConcentrateConfigModel()
: (provider === 'gemini' ? process.env.GEMINI_MODEL : undefined) || : activeRouteId === 'api-route'
? getAllowedApiRouteConfigModel()
: (provider === 'gemini' ? process.env.GEMINI_MODEL : undefined) ||
(provider === 'mistral' ? process.env.MISTRAL_MODEL : undefined) || (provider === 'mistral' ? process.env.MISTRAL_MODEL : undefined) ||
(provider === 'minimax' ? getMiniMaxModelEnv() : undefined) || (provider === 'minimax' ? getMiniMaxModelEnv() : undefined) ||
(isOpenAIShimProvider ? process.env.OPENAI_MODEL : undefined) || (isOpenAIShimProvider ? process.env.OPENAI_MODEL : undefined) ||
@@ -404,6 +417,13 @@ export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
'deepseek-v4-flash' 'deepseek-v4-flash'
) )
} }
if (resolveActiveRouteIdFromEnv(process.env) === 'api-route') {
return (
getAllowedApiRouteConfigModel() ||
getRouteDefaultModel('api-route') ||
'claude-sonnet-4-6'
)
}
// Custom Anthropic-compatible endpoints intentionally retain the legacy // Custom Anthropic-compatible endpoints intentionally retain the legacy
// firstParty provider category, so prefer their explicitly configured model // firstParty provider category, so prefer their explicitly configured model
// before the subscription and PAYG defaults below. // before the subscription and PAYG defaults below.
+151
View File
@@ -47,6 +47,8 @@ const ENV_KEYS = [
'ATLAS_CLOUD_API_KEY', 'ATLAS_CLOUD_API_KEY',
'APISMART_API_KEY', 'APISMART_API_KEY',
'APISMART_MODEL', 'APISMART_MODEL',
'API_ROUTE_API_KEY',
'API_ROUTE_MODEL',
'CONCENTRATE_API_KEY', 'CONCENTRATE_API_KEY',
'CONCENTRATE_BASE_URL', 'CONCENTRATE_BASE_URL',
'CONCENTRATE_MODEL', 'CONCENTRATE_MODEL',
@@ -101,6 +103,8 @@ const RESET_KEYS = [
'ATLAS_CLOUD_API_KEY', 'ATLAS_CLOUD_API_KEY',
'APISMART_API_KEY', 'APISMART_API_KEY',
'APISMART_MODEL', 'APISMART_MODEL',
'API_ROUTE_API_KEY',
'API_ROUTE_MODEL',
'CONCENTRATE_API_KEY', 'CONCENTRATE_API_KEY',
'CONCENTRATE_BASE_URL', 'CONCENTRATE_BASE_URL',
'CONCENTRATE_MODEL', 'CONCENTRATE_MODEL',
@@ -1848,3 +1852,150 @@ describe('applyProviderFlag - concentrate', () => {
expect(process.env.ANTHROPIC_CUSTOM_HEADERS).toBeUndefined() expect(process.env.ANTHROPIC_CUSTOM_HEADERS).toBeUndefined()
}) })
}) })
describe('applyProviderFlag - api-route', () => {
test('sets API Route OpenAI-compatible defaults and mirrors API_ROUTE_API_KEY', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
const result = applyProviderFlag('api-route', [])
expect(result.error).toBeUndefined()
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
expect(process.env.OPENAI_BASE_URL).toBe('https://global.api-route.com/v1')
expect(process.env.OPENAI_API_KEY).toBe('api-route-secret-key')
expect(process.env.OPENAI_MODEL).toBe('claude-sonnet-4-6')
})
test('uses API_ROUTE_MODEL from env when --model is not provided', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
process.env.API_ROUTE_MODEL = 'custom-api-route-model'
applyProviderFlag('api-route', [])
expect(process.env.OPENAI_MODEL).toBe('custom-api-route-model')
})
test('explicit --model overrides API_ROUTE_MODEL', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
process.env.API_ROUTE_MODEL = 'custom-api-route-model'
applyProviderFlag('api-route', ['--model', 'flag-model'])
expect(process.env.OPENAI_MODEL).toBe('flag-model')
})
test.each(['', ' ', 'null', 'undefined', ' NULL '])(
'does not contaminate OPENAI_MODEL with unusable API_ROUTE_MODEL %j',
placeholder => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
process.env.API_ROUTE_MODEL = placeholder
applyProviderFlag('api-route', [])
expect(process.env.OPENAI_MODEL).toBe('claude-sonnet-4-6')
},
)
test('does not forward the dedicated key to a preserved custom base URL', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
process.env.OPENAI_BASE_URL = 'https://custom-proxy.example.com/v1'
applyProviderFlag('api-route', [])
expect(process.env.OPENAI_BASE_URL).toBe(
'https://custom-proxy.example.com/v1',
)
expect(process.env.OPENAI_API_KEY).toBeUndefined()
})
test('dedicated key overrides a lingering OPENAI_API_KEY from another provider', () => {
process.env.OPENAI_API_KEY = 'existing-openai-key'
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
applyProviderFlag('api-route', [])
expect(process.env.OPENAI_API_KEY).toBe('api-route-secret-key')
})
test('clears OPENAI_API_KEYS when dedicated API Route key is set on canonical endpoint', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
process.env.OPENAI_API_KEYS = 'stale-key-1,stale-key-2'
applyProviderFlag('api-route', [])
expect(process.env.OPENAI_API_KEY).toBe('api-route-secret-key')
expect(process.env.OPENAI_API_KEYS).toBeUndefined()
})
test('clears a stale OPENAI_API_KEY when no API Route key is set', () => {
delete process.env.API_ROUTE_API_KEY
process.env.OPENAI_API_KEY = 'existing-openai-key'
applyProviderFlag('api-route', [])
expect(process.env.OPENAI_API_KEY).toBeUndefined()
})
test.each(['SUA_CHAVE', 'sua_chave', 'null', 'undefined', ' NULL '])(
'does not mirror placeholder API Route credential %s',
placeholder => {
process.env.API_ROUTE_API_KEY = placeholder
applyProviderFlag('api-route', [])
expect(process.env.OPENAI_API_KEY).toBeUndefined()
},
)
test('clears unsupported OpenAI shim settings from a previous route', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
process.env.OPENAI_API_FORMAT = 'responses'
process.env.OPENAI_AUTH_HEADER = 'x-api-key'
process.env.OPENAI_AUTH_SCHEME = 'raw'
process.env.OPENAI_AUTH_HEADER_VALUE = 'stale-value'
process.env.ANTHROPIC_CUSTOM_HEADERS = 'X-Proxy-Auth: proxy-secret'
applyProviderFlag('api-route', [])
expect(process.env.OPENAI_API_FORMAT).toBeUndefined()
expect(process.env.OPENAI_AUTH_HEADER).toBeUndefined()
expect(process.env.OPENAI_AUTH_SCHEME).toBeUndefined()
expect(process.env.OPENAI_AUTH_HEADER_VALUE).toBeUndefined()
expect(process.env.ANTHROPIC_CUSTOM_HEADERS).toBeUndefined()
})
test('clears a copied API Route key from OPENAI_API_KEY when switching to another provider', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
process.env.OPENAI_API_KEY = 'api-route-secret-key'
applyProviderFlag('openai', [])
expect(process.env.OPENAI_API_KEY).toBeUndefined()
})
test('switching from API Route to Ollama replaces the route identity and credentials', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
applyProviderFlag('api-route', [])
applyProviderFlag('ollama', [])
expect(process.env.API_ROUTE_API_KEY).toBeUndefined()
expect(process.env.API_ROUTE_MODEL).toBeUndefined()
expect(process.env.OPENAI_BASE_URL).toBe('http://localhost:11434/v1')
expect(process.env.OPENAI_API_KEY).toBe('ollama')
expect(resolveActiveRouteIdFromEnv(process.env)).toBe('ollama')
})
test('switching from API Route to OpenAI replaces the known gateway endpoint', () => {
process.env.API_ROUTE_API_KEY = 'api-route-secret-key'
applyProviderFlag('api-route', [])
applyProviderFlag('openai', [])
expect(process.env.API_ROUTE_API_KEY).toBeUndefined()
expect(process.env.OPENAI_BASE_URL).toBe('https://api.openai.com/v1')
expect(resolveActiveRouteIdFromEnv(process.env)).toBe('openai')
})
})
+47 -4
View File
@@ -31,6 +31,7 @@ import {
import { PRESET_VENDOR_MAP } from '../integrations/compatibility.js' import { PRESET_VENDOR_MAP } from '../integrations/compatibility.js'
import { getCommandcodeChatCompletionsModelError } from '../integrations/gateways/commandcode.js' import { getCommandcodeChatCompletionsModelError } from '../integrations/gateways/commandcode.js'
import { import {
isCanonicalApiRouteInferenceBaseUrl,
isCanonicalApismartInferenceBaseUrl, isCanonicalApismartInferenceBaseUrl,
isCanonicalConcentrateInferenceBaseUrl, isCanonicalConcentrateInferenceBaseUrl,
isCanonicalLlmtrInferenceBaseUrl, isCanonicalLlmtrInferenceBaseUrl,
@@ -401,6 +402,9 @@ export function applyProviderFlag(
: process.env.OPENAI_API_KEY !== undefined && : process.env.OPENAI_API_KEY !== undefined &&
process.env.OPENAI_API_KEY === process.env.APISMART_API_KEY process.env.OPENAI_API_KEY === process.env.APISMART_API_KEY
? 'apismart' ? 'apismart'
: process.env.OPENAI_API_KEY !== undefined &&
process.env.OPENAI_API_KEY === process.env.API_ROUTE_API_KEY
? 'api-route'
: process.env.OPENAI_API_KEY !== undefined && : process.env.OPENAI_API_KEY !== undefined &&
process.env.OPENAI_API_KEY === process.env.CONCENTRATE_API_KEY process.env.OPENAI_API_KEY === process.env.CONCENTRATE_API_KEY
? 'concentrate' ? 'concentrate'
@@ -470,6 +474,8 @@ export function applyProviderFlag(
// OpenAI-compatible env-only route override it later in startup. // OpenAI-compatible env-only route override it later in startup.
delete process.env.APISMART_API_KEY delete process.env.APISMART_API_KEY
delete process.env.APISMART_MODEL delete process.env.APISMART_MODEL
delete process.env.API_ROUTE_API_KEY
delete process.env.API_ROUTE_MODEL
delete process.env.ANTHROPIC_AUTH_TOKEN delete process.env.ANTHROPIC_AUTH_TOKEN
delete process.env.ANTHROPIC_CUSTOM_HEADERS delete process.env.ANTHROPIC_CUSTOM_HEADERS
break break
@@ -517,6 +523,8 @@ export function applyProviderFlag(
// known gateway endpoint, but preserve a user-supplied custom endpoint. // known gateway endpoint, but preserve a user-supplied custom endpoint.
delete process.env.APISMART_API_KEY delete process.env.APISMART_API_KEY
delete process.env.APISMART_MODEL delete process.env.APISMART_MODEL
delete process.env.API_ROUTE_API_KEY
delete process.env.API_ROUTE_MODEL
applyOpenAIBaseUrlDefault(provider, defaultBaseUrl) applyOpenAIBaseUrlDefault(provider, defaultBaseUrl)
if (effectiveModel) process.env.OPENAI_MODEL = effectiveModel if (effectiveModel) process.env.OPENAI_MODEL = effectiveModel
break break
@@ -888,7 +896,9 @@ export function applyProviderFlag(
(provider === 'llmtr' && (provider === 'llmtr' &&
isCanonicalLlmtrInferenceBaseUrl(getConfiguredOpenAIBaseUrl())) || isCanonicalLlmtrInferenceBaseUrl(getConfiguredOpenAIBaseUrl())) ||
(provider === 'commandcode' && (provider === 'commandcode' &&
isCanonicalCommandcodeInferenceBaseUrl(getConfiguredOpenAIBaseUrl())) isCanonicalCommandcodeInferenceBaseUrl(getConfiguredOpenAIBaseUrl())) ||
(provider === 'api-route' &&
isCanonicalApiRouteInferenceBaseUrl(getConfiguredOpenAIBaseUrl()))
) { ) {
clearUnsupportedOpenAIShimSettings(provider) clearUnsupportedOpenAIShimSettings(provider)
delete process.env.ANTHROPIC_CUSTOM_HEADERS delete process.env.ANTHROPIC_CUSTOM_HEADERS
@@ -918,6 +928,35 @@ export function applyProviderFlag(
delete process.env.OPENAI_API_KEY delete process.env.OPENAI_API_KEY
} }
} }
if (
provider === 'api-route' &&
isCanonicalApiRouteInferenceBaseUrl(getConfiguredOpenAIBaseUrl())
) {
const dedicatedApiRouteKey = hasUsableOpenAICredential(
process.env.API_ROUTE_API_KEY,
)
? process.env.API_ROUTE_API_KEY
: undefined
if (dedicatedApiRouteKey) {
process.env.OPENAI_API_KEY = dedicatedApiRouteKey
delete process.env.OPENAI_API_KEYS
} else {
delete process.env.OPENAI_API_KEY
}
}
if (provider === 'api-route') {
const apiRouteModel = usableProviderModelEnvValue(
process.env.API_ROUTE_MODEL,
)
if (apiRouteModel) {
process.env.OPENAI_MODEL = apiRouteModel
} else {
process.env.OPENAI_MODEL ??=
usableProviderModelEnvValue(process.env.OPENAI_MODEL) ||
defaultModel ||
'claude-sonnet-4-6'
}
}
if (defaultModel) { if (defaultModel) {
process.env.OPENAI_MODEL ??= defaultModel process.env.OPENAI_MODEL ??= defaultModel
} }
@@ -925,9 +964,9 @@ export function applyProviderFlag(
break break
} }
// A provider flag selects a complete route for this process. Concentrate's // A provider flag selects a complete route for this process. Dedicated
// dedicated variables are another source of route identity, so leaving them // variables are another source of route identity, so leaving them
// behind can re-select Concentrate after a later OpenAI-compatible provider // behind can re-select a provider after a later OpenAI-compatible provider
// has applied its defaults. Keep their lifecycle at the selection boundary, // has applied its defaults. Keep their lifecycle at the selection boundary,
// rather than relying on individual switch branches to remember cleanup. // rather than relying on individual switch branches to remember cleanup.
// This runs only after the selected branch succeeds, so an invalid // This runs only after the selected branch succeeds, so an invalid
@@ -935,6 +974,10 @@ export function applyProviderFlag(
if (provider !== 'concentrate') { if (provider !== 'concentrate') {
clearConcentrateProviderState() clearConcentrateProviderState()
} }
if (provider !== 'api-route') {
delete process.env.API_ROUTE_API_KEY
delete process.env.API_ROUTE_MODEL
}
return {} return {}
} }
+136
View File
@@ -3405,6 +3405,37 @@ test('openai launch removes legacy generic credentials from a noncanonical Conce
} }
}) })
test('API Route saved profile relaunch uses persisted generic key/model over ambient dedicated state', async () => {
const env = await buildLaunchEnv({
profile: 'openai',
persisted: profile('openai', {
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'api-route',
OPENAI_BASE_URL: 'https://global.api-route.com/v1',
OPENAI_MODEL: 'saved-api-route-model',
OPENAI_API_KEY: 'saved-api-route-key',
}),
goal: 'balanced',
processEnv: {
API_ROUTE_API_KEY: 'ambient-api-route-key',
API_ROUTE_MODEL: 'ambient-api-route-model',
},
})
assert.equal(env.API_ROUTE_API_KEY, undefined)
assert.equal(env.API_ROUTE_MODEL, undefined)
assert.equal(env.OPENAI_BASE_URL, 'https://global.api-route.com/v1')
assert.equal(env.OPENAI_MODEL, 'saved-api-route-model')
assert.equal(env.OPENAI_API_KEY, 'saved-api-route-key')
assert.equal(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: env.OPENAI_BASE_URL,
processEnv: env,
}),
'saved-api-route-key',
)
})
test('buildStartupEnvFromProfile preserves Concentrate env-only setup over a saved profile', async () => { test('buildStartupEnvFromProfile preserves Concentrate env-only setup over a saved profile', async () => {
const env = await buildStartupEnvFromProfile({ const env = await buildStartupEnvFromProfile({
persisted: profile('openai', { persisted: profile('openai', {
@@ -3427,6 +3458,50 @@ test('buildStartupEnvFromProfile preserves Concentrate env-only setup over a sav
assert.equal(resolveActiveRouteIdFromEnv(env), 'concentrate') assert.equal(resolveActiveRouteIdFromEnv(env), 'concentrate')
}) })
test('applyStartupEnvFromProfile preserves API Route env-only setup over a saved profile', async () => {
const processEnv: NodeJS.ProcessEnv = {
API_ROUTE_API_KEY: 'api-route-env-only-key',
API_ROUTE_MODEL: 'api-route-env-only-model',
}
const error = await applyStartupEnvFromProfile({
persisted: profile('openai', {
OPENAI_BASE_URL: 'https://api.openai.com/v1',
OPENAI_MODEL: 'gpt-4o',
OPENAI_API_KEY: 'sk-persisted',
}),
processEnv,
})
assert.equal(error, null)
assert.equal(processEnv.API_ROUTE_API_KEY, 'api-route-env-only-key')
assert.equal(processEnv.API_ROUTE_MODEL, 'api-route-env-only-model')
assert.equal(processEnv.OPENAI_BASE_URL, undefined)
assert.equal(processEnv.OPENAI_API_KEY, undefined)
assert.equal(resolveActiveRouteIdFromEnv(processEnv), 'api-route')
})
test('applyStartupEnvFromProfile preserves API Route key-only descriptor-default intent', async () => {
const processEnv: NodeJS.ProcessEnv = {
API_ROUTE_API_KEY: 'api-route-env-only-key',
}
const error = await applyStartupEnvFromProfile({
persisted: profile('openai', {
OPENAI_BASE_URL: 'https://api.openai.com/v1',
OPENAI_MODEL: 'gpt-4o',
OPENAI_API_KEY: 'sk-persisted',
}),
processEnv,
})
assert.equal(error, null)
assert.equal(processEnv.API_ROUTE_API_KEY, 'api-route-env-only-key')
assert.equal(processEnv.OPENAI_MODEL, undefined)
assert.equal(resolveActiveRouteIdFromEnv(processEnv), 'api-route')
assert.equal(await getProviderValidationError(processEnv), null)
})
test('buildConcentrateProfileEnv prefers CONCENTRATE_MODEL over OPENAI_MODEL', () => { test('buildConcentrateProfileEnv prefers CONCENTRATE_MODEL over OPENAI_MODEL', () => {
const env = buildConcentrateProfileEnv({ const env = buildConcentrateProfileEnv({
apiKey: 'concentrate-secret-key', apiKey: 'concentrate-secret-key',
@@ -3476,3 +3551,64 @@ test('buildOpenAIProfileEnv does not stamp a generic key as Concentrate credenti
assert.equal(env?.OPENAI_API_KEY, 'generic-proxy-key') assert.equal(env?.OPENAI_API_KEY, 'generic-proxy-key')
assert.equal(env?.CONCENTRATE_API_KEY, undefined) assert.equal(env?.CONCENTRATE_API_KEY, undefined)
}) })
test('openai launch withholds ambient API Route credentials from a keyless proxy profile on restart', async () => {
for (const ambient of [
{ OPENAI_API_KEY: 'ambient-api-route-key' },
{ OPENAI_API_KEYS: 'ambient-key-a,ambient-key-b' },
]) {
const env = await buildLaunchEnv({
profile: 'openai',
persisted: profile('openai', {
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'api-route',
OPENAI_BASE_URL: 'https://proxy.example.com/v1',
OPENAI_MODEL: 'claude-sonnet-4-6',
}),
goal: 'coding',
processEnv: {
OPENAI_BASE_URL: 'https://proxy.example.com/v1',
...ambient,
},
})
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'api-route')
assert.equal(env.OPENAI_API_KEY, undefined)
assert.equal(env.OPENAI_API_KEYS, undefined)
}
const canonical = await buildLaunchEnv({
profile: 'openai',
persisted: profile('openai', {
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'api-route',
OPENAI_BASE_URL: 'https://global.api-route.com/v1',
OPENAI_MODEL: 'claude-sonnet-4-6',
}),
goal: 'coding',
processEnv: {
OPENAI_BASE_URL: 'https://global.api-route.com/v1',
OPENAI_API_KEY: 'ambient-api-route-key',
},
})
assert.equal(canonical.OPENAI_API_KEY, 'ambient-api-route-key')
})
test('buildStartupEnvFromProfile applies saved API Route proxy profile even when ambient credentials are present', async () => {
const processEnv: NodeJS.ProcessEnv = {
OPENAI_API_KEY: 'ambient-key',
OPENAI_API_KEYS: 'ambient-key-1,ambient-key-2',
}
const env = await buildStartupEnvFromProfile({
persisted: profile('openai', {
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'api-route',
OPENAI_BASE_URL: 'https://proxy.example.com/v1',
OPENAI_MODEL: 'claude-sonnet-4-6',
}),
processEnv,
})
assert.equal(env.OPENAI_BASE_URL, 'https://proxy.example.com/v1')
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'api-route')
assert.equal(env.OPENAI_API_KEY, undefined)
assert.equal(env.OPENAI_API_KEYS, undefined)
})
+18 -2
View File
@@ -25,6 +25,7 @@ import { getErrnoCode } from './errors.js'
import { import {
getRouteDefaultBaseUrl, getRouteDefaultBaseUrl,
getRouteDefaultModel, getRouteDefaultModel,
isCanonicalApiRouteInferenceBaseUrl,
isCanonicalApismartInferenceBaseUrl, isCanonicalApismartInferenceBaseUrl,
isCanonicalConcentrateInferenceBaseUrl, isCanonicalConcentrateInferenceBaseUrl,
isCanonicalLlmtrInferenceBaseUrl, isCanonicalLlmtrInferenceBaseUrl,
@@ -118,6 +119,8 @@ const PROFILE_ENV_KEYS = [
'ATLAS_CLOUD_API_KEY', 'ATLAS_CLOUD_API_KEY',
'APISMART_API_KEY', 'APISMART_API_KEY',
'APISMART_MODEL', 'APISMART_MODEL',
'API_ROUTE_API_KEY',
'API_ROUTE_MODEL',
'NEARAI_API_KEY', 'NEARAI_API_KEY',
'FIREWORKS_API_KEY', 'FIREWORKS_API_KEY',
'LONGCAT_API_KEY', 'LONGCAT_API_KEY',
@@ -219,6 +222,7 @@ export type ProfileEnv = {
MIMO_API_KEY?: string MIMO_API_KEY?: string
ATLAS_CLOUD_API_KEY?: string ATLAS_CLOUD_API_KEY?: string
APISMART_API_KEY?: string APISMART_API_KEY?: string
API_ROUTE_API_KEY?: string
CLINE_API_KEY?: string CLINE_API_KEY?: string
NEARAI_API_KEY?: string NEARAI_API_KEY?: string
FIREWORKS_API_KEY?: string FIREWORKS_API_KEY?: string
@@ -1491,6 +1495,7 @@ function hasConcreteProviderSelection(
// before the env-only resolver has a chance to see it. // before the env-only resolver has a chance to see it.
return ( return (
sanitizeApiKey(processEnv.APISMART_API_KEY) !== undefined || sanitizeApiKey(processEnv.APISMART_API_KEY) !== undefined ||
sanitizeApiKey(processEnv.API_ROUTE_API_KEY) !== undefined ||
sanitizeApiKey(processEnv.FIREWORKS_API_KEY) !== undefined || sanitizeApiKey(processEnv.FIREWORKS_API_KEY) !== undefined ||
sanitizeApiKey(processEnv.NEARAI_API_KEY) !== undefined || sanitizeApiKey(processEnv.NEARAI_API_KEY) !== undefined ||
sanitizeApiKey(processEnv.LONGCAT_API_KEY) !== undefined || sanitizeApiKey(processEnv.LONGCAT_API_KEY) !== undefined ||
@@ -2181,12 +2186,17 @@ export async function buildLaunchEnv(options: {
effectiveOpenAIRouteId === 'commandcode' && effectiveOpenAIRouteId === 'commandcode' &&
!!env.OPENAI_BASE_URL?.trim() && !!env.OPENAI_BASE_URL?.trim() &&
!isCanonicalCommandcodeInferenceBaseUrl(env.OPENAI_BASE_URL) !isCanonicalCommandcodeInferenceBaseUrl(env.OPENAI_BASE_URL)
const isNoncanonicalApiRouteLaunch =
effectiveOpenAIRouteId === 'api-route' &&
!!env.OPENAI_BASE_URL?.trim() &&
!isCanonicalApiRouteInferenceBaseUrl(env.OPENAI_BASE_URL)
const isNoncanonicalDedicatedOpenAILaunch = const isNoncanonicalDedicatedOpenAILaunch =
isNoncanonicalAimlapiLaunch || isNoncanonicalAimlapiLaunch ||
isNoncanonicalApismartLaunch || isNoncanonicalApismartLaunch ||
isNoncanonicalConcentrateLaunch || isNoncanonicalConcentrateLaunch ||
isNoncanonicalLlmtrLaunch || isNoncanonicalLlmtrLaunch ||
isNoncanonicalCommandcodeLaunch isNoncanonicalCommandcodeLaunch ||
isNoncanonicalApiRouteLaunch
if (isNoncanonicalDedicatedOpenAILaunch) { if (isNoncanonicalDedicatedOpenAILaunch) {
delete env.OPENAI_API_KEY delete env.OPENAI_API_KEY
delete env.OPENAI_API_KEYS delete env.OPENAI_API_KEYS
@@ -2510,12 +2520,18 @@ export async function buildStartupEnvFromProfile(options?: {
persisted.env.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'commandcode' && persisted.env.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'commandcode' &&
!!persisted.env.OPENAI_BASE_URL?.trim() && !!persisted.env.OPENAI_BASE_URL?.trim() &&
!isCanonicalCommandcodeInferenceBaseUrl(persisted.env.OPENAI_BASE_URL) !isCanonicalCommandcodeInferenceBaseUrl(persisted.env.OPENAI_BASE_URL)
const persistedApiRouteProxy =
persisted?.profile === 'openai' &&
persisted.env.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'api-route' &&
!!persisted.env.OPENAI_BASE_URL?.trim() &&
!isCanonicalApiRouteInferenceBaseUrl(persisted.env.OPENAI_BASE_URL)
if ( if (
hasConcreteProviderSelection(processEnv) && hasConcreteProviderSelection(processEnv) &&
!persistedApismartProxy && !persistedApismartProxy &&
!persistedConcentrateProxy && !persistedConcentrateProxy &&
!persistedLlmtrProxy && !persistedLlmtrProxy &&
!persistedCommandcodeProxy !persistedCommandcodeProxy &&
!persistedApiRouteProxy
) { ) {
return processEnv return processEnv
} }
+210
View File
@@ -14,6 +14,7 @@ async function importFreshProvidersModule() {
const originalEnv = { ...process.env } const originalEnv = { ...process.env }
const originalCwd = process.cwd() const originalCwd = process.cwd()
const originalFetch = globalThis.fetch
const RESTORED_KEYS = [ const RESTORED_KEYS = [
'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED', 'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED',
@@ -73,6 +74,8 @@ const RESTORED_KEYS = [
'ATLAS_CLOUD_API_KEY', 'ATLAS_CLOUD_API_KEY',
'APISMART_API_KEY', 'APISMART_API_KEY',
'APISMART_MODEL', 'APISMART_MODEL',
'API_ROUTE_API_KEY',
'API_ROUTE_MODEL',
'LLMTR_API_KEY', 'LLMTR_API_KEY',
'CMD_API_KEY', 'CMD_API_KEY',
'COMMANDCODE_API_KEY', 'COMMANDCODE_API_KEY',
@@ -136,6 +139,7 @@ afterEach(() => {
} }
mock.restore() mock.restore()
globalThis.fetch = originalFetch
mockConfigState = createMockConfigState() mockConfigState = createMockConfigState()
process.chdir(originalCwd) process.chdir(originalCwd)
if (testConfigDir) { if (testConfigDir) {
@@ -297,6 +301,17 @@ function buildConcentrateProfile(overrides: Partial<ProviderProfile> = {}): Prov
}) })
} }
function buildApiRouteProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile {
return buildProfile({
provider: 'api-route',
name: 'API Route',
baseUrl: 'https://global.api-route.com/v1',
model: 'claude-sonnet-4-6',
apiKey: 'api-route-test-key',
...overrides,
})
}
function buildLlmtrProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile { function buildLlmtrProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile {
return buildProfile({ return buildProfile({
provider: 'llmtr', provider: 'llmtr',
@@ -394,6 +409,147 @@ describe('applyProviderProfileToProcessEnv', () => {
).toBe('selected-new') ).toBe('selected-new')
}, 20_000) }, 20_000)
test('API Route saved profile mirrors its dedicated credential and route identity', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
applyProviderProfileToProcessEnv(
buildProfile({
provider: 'api-route',
name: 'API Route',
baseUrl: 'https://global.api-route.com/v1',
model: 'saved-api-route-model',
apiKey: 'saved-api-route-key',
}),
)
expect(process.env.API_ROUTE_API_KEY).toBe('saved-api-route-key')
expect(process.env.API_ROUTE_MODEL).toBeUndefined()
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('api-route')
expect(process.env.OPENAI_BASE_URL).toBe('https://global.api-route.com/v1')
expect(process.env.OPENAI_API_KEY).toBe('saved-api-route-key')
expect(process.env.OPENAI_MODEL).toBe('saved-api-route-model')
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: process.env.OPENAI_BASE_URL,
processEnv: process.env,
}),
).toBe('saved-api-route-key')
}, 20_000)
test('API Route saved profile clears competing dedicated env and uses its saved key/model', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
process.env.API_ROUTE_API_KEY = 'ambient-api-route-key'
process.env.API_ROUTE_MODEL = 'ambient-api-route-model'
applyProviderProfileToProcessEnv(
buildProfile({
provider: 'api-route',
name: 'API Route',
baseUrl: 'https://global.api-route.com/v1',
model: 'saved-api-route-model',
apiKey: 'saved-api-route-key',
}),
)
expect(process.env.API_ROUTE_API_KEY).toBe('saved-api-route-key')
expect(process.env.API_ROUTE_MODEL).toBeUndefined()
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('api-route')
expect(process.env.OPENAI_API_KEY).toBe('saved-api-route-key')
expect(process.env.OPENAI_MODEL).toBe('saved-api-route-model')
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: process.env.OPENAI_BASE_URL,
processEnv: process.env,
}),
).toBe('saved-api-route-key')
}, 20_000)
test('switching away from API Route clears dedicated route state', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
process.env.API_ROUTE_API_KEY = 'ambient-api-route-key'
process.env.API_ROUTE_MODEL = 'ambient-api-route-model'
applyProviderProfileToProcessEnv(
buildProfile({
provider: 'anthropic',
name: 'Anthropic',
baseUrl: 'https://api.anthropic.com',
model: 'claude-sonnet-4-6',
apiKey: 'anthropic-key',
}),
)
expect(process.env.API_ROUTE_API_KEY).toBeUndefined()
expect(process.env.API_ROUTE_MODEL).toBeUndefined()
expect(process.env.OPENAI_API_KEY).toBeUndefined()
expect(process.env.ANTHROPIC_API_KEY).toBe('anthropic-key')
}, 20_000)
test('API Route profile without a base URL uses the canonical endpoint and ambient key', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
process.env.API_ROUTE_API_KEY = 'ambient-api-route-key'
applyProviderProfileToProcessEnv(
buildApiRouteProfile({ baseUrl: undefined, apiKey: undefined }),
)
expect(process.env.OPENAI_BASE_URL).toBe('https://global.api-route.com/v1')
expect(process.env.OPENAI_API_KEY).toBe('ambient-api-route-key')
expect(process.env.API_ROUTE_API_KEY).toBe('ambient-api-route-key')
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('api-route')
}, 20_000)
test('retargeted API Route profile withholds its dedicated credential and skips discovery', async () => {
const fetchMock = mock(() => Promise.resolve(new Response('{}')))
globalThis.fetch = fetchMock as unknown as typeof fetch
const { setActiveProviderProfile } = await importFreshProviderProfileModules()
const profile = buildApiRouteProfile({
id: 'api_route_proxy',
baseUrl: 'https://proxy.example/v1',
})
saveMockGlobalConfig(current => ({
...current,
providerProfiles: [profile],
}))
setActiveProviderProfile(profile.id, { configDir: testConfigDir ?? undefined })
await Promise.resolve()
expect(process.env.OPENAI_API_KEY).toBeUndefined()
expect(process.env.API_ROUTE_API_KEY).toBeUndefined()
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('api-route')
expect(fetchMock).not.toHaveBeenCalled()
const persisted = JSON.parse(
readFileSync(join(testConfigDir!, '.openclaude-profile.json'), 'utf8'),
)
expect(persisted.env).toEqual({
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'api-route',
OPENAI_BASE_URL: 'https://proxy.example/v1',
OPENAI_MODEL: 'claude-sonnet-4-6',
})
const { buildStartupEnvFromProfile } = await import(
`./providerProfile.js?ts=${Date.now()}-${Math.random()}`
)
const startupEnv = await buildStartupEnvFromProfile({
persisted,
processEnv: {
API_ROUTE_API_KEY: 'ambient-api-route-key',
OPENAI_API_KEY: 'ambient-api-route-key',
},
})
expect(startupEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('api-route')
expect(startupEnv.OPENAI_API_KEY).toBeUndefined()
expect(startupEnv.API_ROUTE_API_KEY).toBeUndefined()
}, 20_000)
test('keyless canonical LLMTR profile adopts its ambient dedicated key', async () => { test('keyless canonical LLMTR profile adopts its ambient dedicated key', async () => {
const { applyProviderProfileToProcessEnv } = const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules() await importFreshProviderProfileModules()
@@ -4315,6 +4471,60 @@ describe('setActiveProviderProfile', () => {
} }
}) })
test('keyed canonical API Route profiles persist and restart with route identity', async () => {
const tempDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-'))
const configDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-config-'))
process.chdir(tempDir)
process.env.CLAUDE_CONFIG_DIR = configDir
try {
const { setActiveProviderProfile } =
await importFreshProviderProfileModules()
const apiRouteProfile = buildApiRouteProfile({
id: 'api_route_profile',
})
saveMockGlobalConfig(current => ({
...current,
providerProfiles: [apiRouteProfile],
}))
setActiveProviderProfile(apiRouteProfile.id, { configDir })
const persisted = JSON.parse(
readFileSync(join(configDir, '.openclaude-profile.json'), 'utf8'),
)
expect(persisted.env).toMatchObject({
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'api-route',
OPENAI_BASE_URL: 'https://global.api-route.com/v1',
OPENAI_API_KEY: 'api-route-test-key',
API_ROUTE_API_KEY: 'api-route-test-key',
})
const { buildStartupEnvFromProfile } = await import(
`./providerProfile.js?ts=${Date.now()}-${Math.random()}`
)
const startupEnv = await buildStartupEnvFromProfile({
persisted,
processEnv: {},
})
expect(startupEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('api-route')
expect(startupEnv.OPENAI_BASE_URL).toBe('https://global.api-route.com/v1')
expect(startupEnv.OPENAI_API_KEY).toBe('api-route-test-key')
expect(
resolveRouteCredentialValue({
routeId: 'api-route',
baseUrl: startupEnv.OPENAI_BASE_URL,
processEnv: startupEnv,
}),
).toBe('api-route-test-key')
} finally {
process.chdir(originalCwd)
rmSync(tempDir, { recursive: true, force: true })
rmSync(configDir, { recursive: true, force: true })
}
})
test('keyed canonical LLMTR profiles persist their saved dedicated credential across restart', async () => { test('keyed canonical LLMTR profiles persist their saved dedicated credential across restart', async () => {
const tempDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-')) const tempDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-'))
const configDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-config-')) const configDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-config-'))
+69 -5
View File
@@ -55,6 +55,7 @@ import {
getRouteDefaultBaseUrl, getRouteDefaultBaseUrl,
isCloudflareBaseUrl, isCloudflareBaseUrl,
isClinePassBaseUrl, isClinePassBaseUrl,
isCanonicalApiRouteInferenceBaseUrl,
isCanonicalApismartInferenceBaseUrl, isCanonicalApismartInferenceBaseUrl,
isCanonicalConcentrateInferenceBaseUrl, isCanonicalConcentrateInferenceBaseUrl,
isCanonicalLlmtrInferenceBaseUrl, isCanonicalLlmtrInferenceBaseUrl,
@@ -184,6 +185,15 @@ function isConcentrateProfile(profile: ProviderProfile): boolean {
return !baseUrl || isCanonicalConcentrateInferenceBaseUrl(baseUrl) return !baseUrl || isCanonicalConcentrateInferenceBaseUrl(baseUrl)
} }
function isApiRouteProfile(profile: ProviderProfile): boolean {
const { route } = resolveProfileCompatibility(profile.provider)
if (route.routeId !== 'api-route') {
return false
}
const baseUrl = profile.baseUrl?.trim()
return !baseUrl || isCanonicalApiRouteInferenceBaseUrl(baseUrl)
}
function isLlmtrProfile(profile: ProviderProfile): boolean { function isLlmtrProfile(profile: ProviderProfile): boolean {
const { route } = resolveProfileCompatibility(profile.provider) const { route } = resolveProfileCompatibility(profile.provider)
if (route.routeId !== 'llmtr') { if (route.routeId !== 'llmtr') {
@@ -297,6 +307,7 @@ export function resolveProfileCapabilityRouteId(
(providerRouteId === 'cloudflare' || (providerRouteId === 'cloudflare' ||
providerRouteId === 'longcat' || providerRouteId === 'longcat' ||
providerRouteId === 'concentrate' || providerRouteId === 'concentrate' ||
providerRouteId === 'api-route' ||
providerRouteId === 'llmtr' || providerRouteId === 'llmtr' ||
providerRouteId === 'commandcode') && providerRouteId === 'commandcode') &&
baseUrl && baseUrl &&
@@ -306,6 +317,8 @@ export function resolveProfileCapabilityRouteId(
? isLongcatBaseUrl(baseUrl) ? isLongcatBaseUrl(baseUrl)
: providerRouteId === 'concentrate' : providerRouteId === 'concentrate'
? isCanonicalConcentrateInferenceBaseUrl(baseUrl) ? isCanonicalConcentrateInferenceBaseUrl(baseUrl)
: providerRouteId === 'api-route'
? isCanonicalApiRouteInferenceBaseUrl(baseUrl)
: providerRouteId === 'commandcode' : providerRouteId === 'commandcode'
? isCanonicalCommandcodeInferenceBaseUrl(baseUrl) ? isCanonicalCommandcodeInferenceBaseUrl(baseUrl)
: isCanonicalLlmtrInferenceBaseUrl(baseUrl)) : isCanonicalLlmtrInferenceBaseUrl(baseUrl))
@@ -941,6 +954,10 @@ function isProcessEnvAlignedWithProfile(
? !includeApiKey || ? !includeApiKey ||
sameOptionalEnvValue(processEnv.APISMART_API_KEY, profile.apiKey) sameOptionalEnvValue(processEnv.APISMART_API_KEY, profile.apiKey)
: true) && : true) &&
(isApiRouteProfile(profile)
? !includeApiKey ||
sameOptionalEnvValue(processEnv.API_ROUTE_API_KEY, profile.apiKey)
: true) &&
(isCommandcodeProfile(profile) (isCommandcodeProfile(profile)
? !includeApiKey || ? !includeApiKey ||
sameOptionalEnvValue( sameOptionalEnvValue(
@@ -1110,9 +1127,11 @@ export function applyProviderProfileToProcessEnv(
? getRouteDefaultBaseUrl('apismart') ?? profile.baseUrl ? getRouteDefaultBaseUrl('apismart') ?? profile.baseUrl
: route.routeId === 'concentrate' && !profile.baseUrl?.trim() : route.routeId === 'concentrate' && !profile.baseUrl?.trim()
? getRouteDefaultBaseUrl('concentrate') ?? profile.baseUrl ? getRouteDefaultBaseUrl('concentrate') ?? profile.baseUrl
: route.routeId === 'commandcode' && !profile.baseUrl?.trim() : route.routeId === 'api-route' && !profile.baseUrl?.trim()
? getRouteDefaultBaseUrl('commandcode') ?? profile.baseUrl ? getRouteDefaultBaseUrl('api-route') ?? profile.baseUrl
: profile.baseUrl : route.routeId === 'commandcode' && !profile.baseUrl?.trim()
? getRouteDefaultBaseUrl('commandcode') ?? profile.baseUrl
: profile.baseUrl
const openAIProfileEnv: ProfileEnv = { const openAIProfileEnv: ProfileEnv = {
OPENAI_BASE_URL: normalizedProfileBaseUrl, OPENAI_BASE_URL: normalizedProfileBaseUrl,
OPENAI_MODEL: primaryModel, OPENAI_MODEL: primaryModel,
@@ -1143,6 +1162,8 @@ export function applyProviderProfileToProcessEnv(
route.routeId === 'apismart' && !isApismartProfile(profile) route.routeId === 'apismart' && !isApismartProfile(profile)
const withholdRetargetedConcentrateCredential = const withholdRetargetedConcentrateCredential =
route.routeId === 'concentrate' && !isConcentrateProfile(profile) route.routeId === 'concentrate' && !isConcentrateProfile(profile)
const withholdRetargetedApiRouteCredential =
route.routeId === 'api-route' && !isApiRouteProfile(profile)
const withholdRetargetedLlmtrCredential = const withholdRetargetedLlmtrCredential =
route.routeId === 'llmtr' && !isLlmtrProfile(profile) route.routeId === 'llmtr' && !isLlmtrProfile(profile)
const withholdRetargetedCommandcodeCredential = const withholdRetargetedCommandcodeCredential =
@@ -1151,6 +1172,7 @@ export function applyProviderProfileToProcessEnv(
profile.apiKey && profile.apiKey &&
!withholdRetargetedApismartCredential && !withholdRetargetedApismartCredential &&
!withholdRetargetedConcentrateCredential && !withholdRetargetedConcentrateCredential &&
!withholdRetargetedApiRouteCredential &&
!withholdRetargetedLlmtrCredential && !withholdRetargetedLlmtrCredential &&
!withholdRetargetedCommandcodeCredential !withholdRetargetedCommandcodeCredential
) { ) {
@@ -1193,6 +1215,9 @@ export function applyProviderProfileToProcessEnv(
if (isConcentrateProfile(profile)) { if (isConcentrateProfile(profile)) {
openAIProfileEnv.CONCENTRATE_API_KEY = profile.apiKey openAIProfileEnv.CONCENTRATE_API_KEY = profile.apiKey
} }
if (isApiRouteProfile(profile)) {
openAIProfileEnv.API_ROUTE_API_KEY = profile.apiKey
}
if (isCommandcodeProfile(profile)) { if (isCommandcodeProfile(profile)) {
openAIProfileEnv.CMD_API_KEY = profile.apiKey openAIProfileEnv.CMD_API_KEY = profile.apiKey
} }
@@ -1284,6 +1309,17 @@ export function applyProviderProfileToProcessEnv(
} }
} }
} }
if (route.routeId === 'api-route') {
openAIProfileEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'api-route'
if (isApiRouteProfile(profile) && !profile.apiKey) {
const ambientApiRouteKey = sanitizeApiKey(process.env.API_ROUTE_API_KEY)
if (ambientApiRouteKey) {
openAIProfileEnv.OPENAI_API_KEY =
openAIProfileEnv.OPENAI_API_KEY ?? ambientApiRouteKey
openAIProfileEnv.API_ROUTE_API_KEY = ambientApiRouteKey
}
}
}
// A keyless canonical LLMTR profile may use the dedicated credential from // A keyless canonical LLMTR profile may use the dedicated credential from
// the ambient environment. Profile application clears all managed provider // the ambient environment. Profile application clears all managed provider
// variables before installing this object, so adopt it here while the // variables before installing this object, so adopt it here while the
@@ -1600,12 +1636,18 @@ function buildOpenAICompatibleStartupEnv(
return null return null
} }
const activeProfileRouteId = resolveProfileRoute(activeProfile.provider).routeId const activeProfileRouteId = resolveProfileRoute(activeProfile.provider).routeId
const normalizedBaseUrl =
activeProfileRouteId === 'api-route' && !activeProfile.baseUrl?.trim()
? getRouteDefaultBaseUrl('api-route') ?? activeProfile.baseUrl
: activeProfile.baseUrl
const withholdRetargetedApismartCredential = const withholdRetargetedApismartCredential =
activeProfileRouteId === 'apismart' && activeProfileRouteId === 'apismart' &&
!isApismartProfile(activeProfile) !isApismartProfile(activeProfile)
const withholdRetargetedConcentrateCredential = const withholdRetargetedConcentrateCredential =
activeProfileRouteId === 'concentrate' && activeProfileRouteId === 'concentrate' &&
!isConcentrateProfile(activeProfile) !isConcentrateProfile(activeProfile)
const withholdRetargetedApiRouteCredential =
activeProfileRouteId === 'api-route' && !isApiRouteProfile(activeProfile)
const withholdRetargetedLlmtrCredential = const withholdRetargetedLlmtrCredential =
activeProfileRouteId === 'llmtr' && !isLlmtrProfile(activeProfile) activeProfileRouteId === 'llmtr' && !isLlmtrProfile(activeProfile)
const withholdRetargetedCommandcodeCredential = const withholdRetargetedCommandcodeCredential =
@@ -1615,6 +1657,7 @@ function buildOpenAICompatibleStartupEnv(
activeProfile.provider === 'aimlapi' || activeProfile.provider === 'aimlapi' ||
resolveRouteIdFromBaseUrl(activeProfile.baseUrl) === 'aimlapi' resolveRouteIdFromBaseUrl(activeProfile.baseUrl) === 'aimlapi'
const isConcentrateProfileFlag = isConcentrateProfile(activeProfile) const isConcentrateProfileFlag = isConcentrateProfile(activeProfile)
const isApiRouteProfileFlag = isApiRouteProfile(activeProfile)
const isLlmtrProfileFlag = isLlmtrProfile(activeProfile) const isLlmtrProfileFlag = isLlmtrProfile(activeProfile)
const isCommandcodeProfileFlag = isCommandcodeProfile(activeProfile) const isCommandcodeProfileFlag = isCommandcodeProfile(activeProfile)
@@ -1622,13 +1665,14 @@ function buildOpenAICompatibleStartupEnv(
activeProfile.apiKey && activeProfile.apiKey &&
!withholdRetargetedApismartCredential && !withholdRetargetedApismartCredential &&
!withholdRetargetedConcentrateCredential && !withholdRetargetedConcentrateCredential &&
!withholdRetargetedApiRouteCredential &&
!withholdRetargetedLlmtrCredential && !withholdRetargetedLlmtrCredential &&
!withholdRetargetedCommandcodeCredential !withholdRetargetedCommandcodeCredential
) { ) {
const strictEnv = buildOpenAIProfileEnv({ const strictEnv = buildOpenAIProfileEnv({
goal: 'balanced', goal: 'balanced',
model: activeProfile.model, model: activeProfile.model,
baseUrl: activeProfile.baseUrl, baseUrl: normalizedBaseUrl,
apiKey: activeProfile.apiKey, apiKey: activeProfile.apiKey,
apiFormat: activeProfile.apiFormat, apiFormat: activeProfile.apiFormat,
azureStyle: activeProfile.azureStyle ? '1' : undefined, azureStyle: activeProfile.azureStyle ? '1' : undefined,
@@ -1665,6 +1709,10 @@ function buildOpenAICompatibleStartupEnv(
if (isConcentrateProfileFlag) { if (isConcentrateProfileFlag) {
strictEnv.CONCENTRATE_API_KEY = activeProfile.apiKey strictEnv.CONCENTRATE_API_KEY = activeProfile.apiKey
} }
if (isApiRouteProfileFlag) {
strictEnv.API_ROUTE_API_KEY = activeProfile.apiKey
strictEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'api-route'
}
if (isLlmtrProfileFlag) { if (isLlmtrProfileFlag) {
strictEnv.LLMTR_API_KEY = activeProfile.apiKey strictEnv.LLMTR_API_KEY = activeProfile.apiKey
} }
@@ -1698,7 +1746,7 @@ function buildOpenAICompatibleStartupEnv(
} }
const env: ProfileEnv = { const env: ProfileEnv = {
OPENAI_BASE_URL: activeProfile.baseUrl, OPENAI_BASE_URL: normalizedBaseUrl,
OPENAI_MODEL: getPrimaryModel(activeProfile.model), OPENAI_MODEL: getPrimaryModel(activeProfile.model),
...(activeProfile.apiFormat ? { OPENAI_API_FORMAT: activeProfile.apiFormat } : {}), ...(activeProfile.apiFormat ? { OPENAI_API_FORMAT: activeProfile.apiFormat } : {}),
...(activeProfile.azureStyle ? { OPENAI_AZURE_STYLE: '1' } : {}), ...(activeProfile.azureStyle ? { OPENAI_AZURE_STYLE: '1' } : {}),
@@ -1732,6 +1780,9 @@ function buildOpenAICompatibleStartupEnv(
if (activeProfileRouteId === 'concentrate') { if (activeProfileRouteId === 'concentrate') {
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'concentrate' env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'concentrate'
} }
if (activeProfileRouteId === 'api-route') {
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'api-route'
}
if (activeProfileRouteId === 'llmtr') { if (activeProfileRouteId === 'llmtr') {
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'llmtr' env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'llmtr'
} }
@@ -1742,6 +1793,7 @@ function buildOpenAICompatibleStartupEnv(
activeProfile.apiKey && activeProfile.apiKey &&
!withholdRetargetedApismartCredential && !withholdRetargetedApismartCredential &&
!withholdRetargetedConcentrateCredential && !withholdRetargetedConcentrateCredential &&
!withholdRetargetedApiRouteCredential &&
!withholdRetargetedLlmtrCredential && !withholdRetargetedLlmtrCredential &&
!withholdRetargetedCommandcodeCredential !withholdRetargetedCommandcodeCredential
) { ) {
@@ -1773,6 +1825,9 @@ function buildOpenAICompatibleStartupEnv(
if (isConcentrateProfileFlag) { if (isConcentrateProfileFlag) {
env.CONCENTRATE_API_KEY = activeProfile.apiKey env.CONCENTRATE_API_KEY = activeProfile.apiKey
} }
if (isApiRouteProfileFlag) {
env.API_ROUTE_API_KEY = activeProfile.apiKey
}
if (isLlmtrProfileFlag) { if (isLlmtrProfileFlag) {
env.LLMTR_API_KEY = activeProfile.apiKey env.LLMTR_API_KEY = activeProfile.apiKey
} }
@@ -1801,6 +1856,12 @@ function buildOpenAICompatibleStartupEnv(
if (isCloudflareBaseUrl(activeProfile.baseUrl)) { if (isCloudflareBaseUrl(activeProfile.baseUrl)) {
env.CLOUDFLARE_API_TOKEN = activeProfile.apiKey env.CLOUDFLARE_API_TOKEN = activeProfile.apiKey
} }
} else if (isApiRouteProfileFlag) {
const ambientApiRouteKey = sanitizeApiKey(process.env.API_ROUTE_API_KEY)
if (ambientApiRouteKey) {
env.OPENAI_API_KEY = ambientApiRouteKey
env.API_ROUTE_API_KEY = ambientApiRouteKey
}
} else { } else {
delete env.OPENAI_API_KEY delete env.OPENAI_API_KEY
} }
@@ -2033,6 +2094,9 @@ function triggerStartupDiscoveryRefreshForProfile(
if (route.routeId === 'apismart' && !isApismartProfile(profile)) { if (route.routeId === 'apismart' && !isApismartProfile(profile)) {
return return
} }
if (route.routeId === 'api-route' && !isApiRouteProfile(profile)) {
return
}
if (route.routeId === 'llmtr' && !isLlmtrProfile(profile)) { if (route.routeId === 'llmtr' && !isLlmtrProfile(profile)) {
return return
} }
+42
View File
@@ -38,6 +38,8 @@ const ENV_KEYS = [
'COMMANDCODE_API_KEY', 'COMMANDCODE_API_KEY',
'COMMAND_CODE_API_KEY', 'COMMAND_CODE_API_KEY',
'APISMART_API_KEY', 'APISMART_API_KEY',
'API_ROUTE_API_KEY',
'API_ROUTE_MODEL',
'CONCENTRATE_API_KEY', 'CONCENTRATE_API_KEY',
'CONCENTRATE_BASE_URL', 'CONCENTRATE_BASE_URL',
'CONCENTRATE_MODEL', 'CONCENTRATE_MODEL',
@@ -479,6 +481,46 @@ test.each(['SUA_CHAVE', 'sua_chave', 'null', 'undefined', ' NULL '])(
}, },
) )
test('API Route saved generic profile credential validates on the canonical endpoint', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://global.api-route.com/v1'
process.env.OPENAI_MODEL = 'saved-model'
process.env.OPENAI_API_KEY = 'saved-profile-key'
delete process.env.API_ROUTE_API_KEY
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
})
test('API Route key-only setup validates before client defaults are applied', async () => {
process.env.API_ROUTE_API_KEY = 'api-route-key'
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
})
test('noncanonical same-host base rejects a selected API_ROUTE_API_KEY route', async () => {
process.env.OPENAI_BASE_URL = 'https://global.api-route.com/staging/v1'
process.env.API_ROUTE_API_KEY = 'api-route-key'
await expect(getProviderValidationError(process.env)).resolves.toBe(
'API Route credentials require the canonical https://global.api-route.com/v1 endpoint.',
)
})
test.each(['SUA_CHAVE', 'sua_chave', 'null', 'undefined', ' NULL '])(
'API Route validation rejects placeholder API_ROUTE_API_KEY %s',
async placeholder => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://global.api-route.com/v1'
process.env.API_ROUTE_API_KEY = placeholder
delete process.env.OPENAI_API_KEY
delete process.env.OPENAI_API_KEYS
await expect(getProviderValidationError(process.env)).resolves.toBe(
'API Route auth is required. Set API_ROUTE_API_KEY.',
)
},
)
test('codex auth error redacts descriptor-declared provider secret values used as model text', async () => { test('codex auth error redacts descriptor-declared provider secret values used as model text', async () => {
const providerSecret = 'ogw-provider-secret' const providerSecret = 'ogw-provider-secret'
process.env.CLAUDE_CODE_USE_OPENAI = '1' process.env.CLAUDE_CODE_USE_OPENAI = '1'
+22 -6
View File
@@ -16,6 +16,7 @@ import {
getRouteCredentialValue, getRouteCredentialValue,
getRouteDescriptor, getRouteDescriptor,
getRouteDefaultModel, getRouteDefaultModel,
isCanonicalApiRouteInferenceBaseUrl,
isCanonicalApismartInferenceBaseUrl, isCanonicalApismartInferenceBaseUrl,
isCanonicalConcentrateInferenceBaseUrl, isCanonicalConcentrateInferenceBaseUrl,
isCanonicalCommandcodeInferenceBaseUrl, isCanonicalCommandcodeInferenceBaseUrl,
@@ -144,6 +145,7 @@ function hasUsableCredentialEnvValue(
envVar === 'OPENAI_API_KEY' || envVar === 'OPENAI_API_KEY' ||
envVar === 'AIMLAPI_API_KEY' || envVar === 'AIMLAPI_API_KEY' ||
envVar === 'APISMART_API_KEY' || envVar === 'APISMART_API_KEY' ||
envVar === 'API_ROUTE_API_KEY' ||
envVar === 'CONCENTRATE_API_KEY' || envVar === 'CONCENTRATE_API_KEY' ||
envVar === 'LLMTR_API_KEY' || envVar === 'LLMTR_API_KEY' ||
envVar === 'CMD_API_KEY' || envVar === 'CMD_API_KEY' ||
@@ -270,12 +272,13 @@ function getRuntimeValidationTarget(
return enabledTarget return enabledTarget
} }
// The documented CONCENTRATE_API_KEY-only setup is routed before the client // The documented CONCENTRATE_API_KEY-only and API_ROUTE_API_KEY-only setup
// applies its default base URL. Select its descriptor directly so startup // is routed before the client applies its default base URL. Select its descriptor
// validates the dedicated credential, including a noncanonical dedicated // directly so startup validates the dedicated credential, including a noncanonical
// base URL, instead of returning early for an unset OpenAI mode. // dedicated base URL, instead of returning early for an unset OpenAI mode.
if (resolveActiveRouteIdFromEnv(env) === 'concentrate') { const activeRouteId = resolveActiveRouteIdFromEnv(env)
return validationTargets.find(target => target.descriptor.id === 'concentrate') if (activeRouteId === 'concentrate' || activeRouteId === 'api-route') {
return validationTargets.find(target => target.descriptor.id === activeRouteId)
} }
if (!useOpenAI) { if (!useOpenAI) {
@@ -306,6 +309,8 @@ function getRuntimeValidationTarget(
!isCanonicalApismartInferenceBaseUrl(request.baseUrl)) || !isCanonicalApismartInferenceBaseUrl(request.baseUrl)) ||
(target.descriptor.id === 'concentrate' && (target.descriptor.id === 'concentrate' &&
!isCanonicalConcentrateInferenceBaseUrl(request.baseUrl)) || !isCanonicalConcentrateInferenceBaseUrl(request.baseUrl)) ||
(target.descriptor.id === 'api-route' &&
!isCanonicalApiRouteInferenceBaseUrl(request.baseUrl)) ||
(target.descriptor.id === 'commandcode' && (target.descriptor.id === 'commandcode' &&
!isCanonicalCommandcodeInferenceBaseUrl(request.baseUrl))) !isCanonicalCommandcodeInferenceBaseUrl(request.baseUrl)))
) { ) {
@@ -435,6 +440,17 @@ async function getDescriptorValidationError(
return 'Concentrate credentials require the canonical https://api.concentrate.ai/v1 endpoint.' return 'Concentrate credentials require the canonical https://api.concentrate.ai/v1 endpoint.'
} }
const apiRouteBaseUrl =
env.OPENAI_BASE_URL?.trim() ||
env.OPENAI_API_BASE?.trim()
if (
target.descriptor.id === 'api-route' &&
apiRouteBaseUrl &&
!isCanonicalApiRouteInferenceBaseUrl(apiRouteBaseUrl)
) {
return 'API Route credentials require the canonical https://global.api-route.com/v1 endpoint.'
}
switch (validation.kind) { switch (validation.kind) {
case 'credential-env': case 'credential-env':
return getCredentialEnvValidationError(validation, env, options.request) return getCredentialEnvValidationError(validation, env, options.request)
+8
View File
@@ -204,6 +204,14 @@ export const providers: Provider[] = [
envVars: ['APISMART_API_KEY', 'APISMART_MODEL'], envVars: ['APISMART_API_KEY', 'APISMART_MODEL'],
notes: 'Unified OpenAI-compatible gateway at https://gw.apismart.ai/v1; defaults to DEEPSEEK_V4_FLASH with hybrid /v1/models discovery.', notes: 'Unified OpenAI-compatible gateway at https://gw.apismart.ai/v1; defaults to DEEPSEEK_V4_FLASH with hybrid /v1/models discovery.',
}, },
{
id: 'api-route',
name: 'API Route',
group: 'gateways',
setup: '/provider or env vars',
envVars: ['API_ROUTE_API_KEY', 'API_ROUTE_MODEL'],
notes: 'OpenAI-compatible multi-model gateway at https://global.api-route.com/v1; defaults to claude-sonnet-4-6 with hybrid /v1/models discovery.',
},
{ {
id: 'cloudflare', id: 'cloudflare',
name: 'Cloudflare Workers AI', name: 'Cloudflare Workers AI',