feat(providers): add focused LLMTR hybrid gateway (#2150)

* feat: add LLMTR hybrid gateway

* feat: support LLMTR_API_KEY

* fix: allow LLMTR provider env files

* fix: protect LLMTR credential routing

* fix: protect LLMTR profile credentials

* fix: complete LLMTR env lifecycle

* fix: select LLMTR model before client setup

* fix: address LLMTR review findings

* fix: route LLMTR auxiliary models correctly

* fix: complete LLMTR credential boundaries

* fix: clear persisted LLMTR startup keys

* fix: normalize LLMTR generic credentials

* fix: scope LLMTR credential support

* fix: align LLMTR credential boundaries

* fix: close LLMTR credential boundaries

* fix: clear stale LLMTR auth state

* fix: clear persisted LLMTR credentials

* fix: complete LLMTR setup contracts

* fix: close LLMTR lifecycle gaps

* fix(providers): close LLMTR credential boundaries

* fix(providers): preserve saved LLMTR profile keys
This commit is contained in:
JATMN
2026-08-24 10:16:04 +08:00
committed by GitHub
parent 421f45992c
commit 1e56d4e7b2
30 changed files with 1274 additions and 37 deletions
+7
View File
@@ -211,6 +211,13 @@ ANTHROPIC_API_KEY=sk-ant-your-key-here
# OPENAI_BASE_URL=https://api.aimlapi.com/v1
# OPENAI_MODEL=gpt-4o
# For LLMTR, prefer its dedicated key. Raw env setup must also set
# OPENAI_BASE_URL and OPENAI_MODEL. OPENAI_API_KEY remains supported:
# LLMTR_API_KEY=your-llmtr-key-here
# OPENAI_API_KEY=your-llmtr-key-here # supported fallback
# OPENAI_BASE_URL=https://llmtr.com/v1
# OPENAI_MODEL=deepseek/deepseek-v4-flash
# Use a custom OpenAI-compatible endpoint (optional — defaults to api.openai.com)
# OPENAI_BASE_URL=https://api.openai.com/v1
# Choose the OpenAI-compatible API surface (optional).
+1
View File
@@ -309,6 +309,7 @@ Advanced and source-build guides:
| Z.AI GLM Coding Plan | `/provider` or OpenAI-compatible env vars | Uses `OPENAI_API_KEY` at `https://api.z.ai/api/coding/paas/v4` and defaults to `glm-5.2` |
| AI/ML API | `/provider` or `AIMLAPI_API_KEY` ([setup guide](docs/aimlapi-setup.md)) | Uses `https://api.aimlapi.com/v1`, auto-detects the OpenAI-compatible route from `AIMLAPI_API_KEY`, sends OpenClaude attribution headers, and discovers chat-capable models from the public `/models` catalog |
| Concentrate | `/provider` or `CONCENTRATE_API_KEY` | Unified OpenAI-compatible gateway at `https://api.concentrate.ai/v1`; defaults to `deepseek-v4-flash` and auto-discovers the chat model 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 |
| 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 |
| 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` |
+137
View File
@@ -136,6 +136,7 @@ const PRESET_ORDER = [
'Anthropic',
'Alibaba Coding Plan (China)',
'Alibaba Coding Plan',
'ApiSmart',
'Atlas Cloud',
'Azure OpenAI',
'Bankr',
@@ -149,6 +150,7 @@ const PRESET_ORDER = [
'Google AI / Gemini',
'Groq',
'Hicap',
'LLMTR',
'LM Studio',
'Atomic Chat',
'Ollama',
@@ -308,6 +310,17 @@ function mockProviderProfilesModule(options?: {
}
}
if (preset === 'llmtr') {
return {
provider: 'llmtr',
name: 'LLMTR',
baseUrl: 'https://llmtr.com/v1',
model: 'deepseek/deepseek-v4-flash',
apiKey: '',
requiresApiKey: true,
}
}
return {
provider: 'openai',
name: 'Mock provider',
@@ -1126,6 +1139,130 @@ test('ProviderManager asks for model and API key when adding OpenAI preset', asy
}
})
test('ProviderManager adds LLMTR with only model selection and API key', async () => {
const addProviderProfile = mock((payload: any) => ({
id: 'llmtr_profile',
...payload,
}))
mockProviderManagerDependencies(() => undefined, async () => undefined, {
addProviderProfile,
})
const nonce = `${Date.now()}-${Math.random()}`
const { ProviderManager } = await import(`./ProviderManager.js?ts=${nonce}`)
const mounted = await mountProviderManager(ProviderManager)
try {
await waitForFrameOutput(mounted.getOutput, frame =>
frame.includes('Provider manager'),
)
mounted.stdin.write('\r')
await waitForFrameOutput(mounted.getOutput, frame =>
frame.includes('Choose provider preset'),
)
await navigateToPreset(mounted.stdin, 'LLMTR')
mounted.stdin.write('\r')
const modelOutput = await waitForFrameOutput(mounted.getOutput, frame =>
frame.includes('Create provider profile') &&
frame.includes('Step 1 of 2: Default model'),
)
expect(modelOutput).toContain('LLMTR')
expect(modelOutput).toContain('deepseek/deepseek-v4-flash')
expect(modelOutput).not.toContain('Provider name')
expect(modelOutput).not.toContain('Base URL')
expect(modelOutput).not.toContain('API mode')
expect(modelOutput).not.toContain('Custom headers')
mounted.stdin.write('\r')
const keyOutput = await waitForFrameOutput(mounted.getOutput, frame =>
frame.includes('Step 2 of 2: API key'),
)
expect(keyOutput).not.toContain('Provider name')
expect(keyOutput).not.toContain('Base URL')
expect(keyOutput).not.toContain('API mode')
expect(keyOutput).not.toContain('Custom headers')
mounted.stdin.write('llmtr-test-key')
await Bun.sleep(25)
mounted.stdin.write('\r')
await waitForCondition(() => addProviderProfile.mock.calls.length > 0)
expect(addProviderProfile).toHaveBeenCalledWith(
expect.objectContaining({
provider: 'llmtr',
name: 'LLMTR',
baseUrl: 'https://llmtr.com/v1',
model: 'deepseek/deepseek-v4-flash',
apiKey: 'llmtr-test-key',
apiFormat: 'chat_completions',
}),
expect.objectContaining({ makeActive: true }),
)
} finally {
await mounted.dispose()
}
})
test('ProviderManager edits query-bearing LLMTR endpoints with generic proxy controls', async () => {
const llmtrProxyProfile = {
id: 'provider_llmtr_proxy',
provider: 'llmtr',
name: 'LLMTR query proxy',
baseUrl: 'https://llmtr.com/v1?tenant=proxy',
model: 'proxy-model',
apiKey: undefined,
apiFormat: 'responses',
authHeader: 'X-Proxy-Key',
authScheme: 'raw',
authHeaderValue: 'proxy-auth-value',
customHeaders: { 'X-Proxy-Trace': 'enabled' },
}
mockProviderManagerDependencies(
() => undefined,
async () => undefined,
{
getProviderProfiles: () => [llmtrProxyProfile],
getActiveProviderProfile: () => llmtrProxyProfile,
},
)
const nonce = `${Date.now()}-${Math.random()}`
const { ProviderManager } = await import(`./ProviderManager.js?ts=${nonce}`)
const mounted = await mountProviderManager(ProviderManager)
try {
await waitForFrameOutput(mounted.getOutput, frame =>
frame.includes('Provider manager') && frame.includes('Edit provider'),
)
mounted.stdin.write('j')
await Bun.sleep(25)
mounted.stdin.write('j')
await Bun.sleep(25)
mounted.stdin.write('\r')
await waitForFrameOutput(mounted.getOutput, frame =>
frame.includes('Edit provider') &&
frame.includes('LLMTR query proxy') &&
!frame.includes('Provider manager'),
)
mounted.stdin.write('\r')
const editOutput = await waitForFrameOutput(mounted.getOutput, frame =>
frame.includes('Edit provider profile') && frame.includes('Step 1 of 8'),
)
expect(editOutput).toContain('Advanced: this provider supports custom request headers')
} finally {
await mounted.dispose()
}
})
test('ProviderManager saves OpenAI preset GPT-5 models with Responses API', async () => {
const addProviderProfile = mock((payload: any) => ({
id: 'openai_profile',
+8 -8
View File
@@ -90,6 +90,7 @@ import {
getActiveProviderProfile,
getProviderPresetDefaults,
getProviderProfiles,
resolveProfileCapabilityRouteId,
setActiveProviderProfile,
type ProviderPreset,
type ProviderProfileInput,
@@ -368,12 +369,7 @@ function resolveProviderEditorRouteId(
provider: ProviderProfile['provider'],
baseUrl?: string,
): string {
const route = resolveProfileRoute(provider).routeId
if (route !== 'openai') {
return route
}
return resolveRouteIdFromBaseUrl(baseUrl) ?? route
return resolveProfileCapabilityRouteId(provider, baseUrl)
}
function routeSupportsResponsesModel(routeId: string, model: string): boolean {
@@ -2539,6 +2535,10 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode {
}
function renderForm(): React.ReactNode {
const editorRouteId = resolveProviderEditorRouteId(
draftProvider,
draft.baseUrl,
)
return (
<Box flexDirection="column" gap={1}>
<Text color="remember" bold>
@@ -2547,9 +2547,9 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode {
<Text dimColor>{displayStep.helpText}</Text>
<Text dimColor>
Provider type:{' '}
{getRouteProviderTypeLabel(resolveProfileRoute(draftProvider).routeId)}
{getRouteProviderTypeLabel(editorRouteId)}
</Text>
{routeSupportsCustomHeaders(resolveProfileRoute(draftProvider).routeId) ? (
{routeSupportsCustomHeaders(editorRouteId) ? (
<Text dimColor>
Advanced: this provider supports custom request headers when you
need them.
+2
View File
@@ -49,9 +49,11 @@ const EXPECTED_PRESETS = [
'atomic-chat',
'cloudflare',
'gitlawb-opengateway',
'concentrate',
'nearai',
'fireworks',
'longcat',
'llmtr',
'opencode',
'opencode-go',
'clinepass',
+129
View File
@@ -0,0 +1,129 @@
import { defineCatalog } from '../define.js'
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
function hasStringValue(value: unknown, expected: string): boolean {
return (
Array.isArray(value) &&
value.some(item => typeof item === 'string' && item === expected)
)
}
function positiveInteger(value: unknown): number | undefined {
return typeof value === 'number' &&
Number.isInteger(value) &&
value > 0
? value
: undefined
}
export function mapLlmtrModel(raw: unknown) {
if (!isRecord(raw)) {
return null
}
const id = typeof raw.id === 'string' ? raw.id.trim() : ''
if (
!id ||
!hasStringValue(raw.supported_parameters, 'tools') ||
!hasStringValue(raw.supported_operations, 'CHAT_COMPLETIONS') ||
!hasStringValue(raw.supported_endpoints, '/v1/chat/completions')
) {
return null
}
const architecture = isRecord(raw.architecture) ? raw.architecture : null
const topProvider = isRecord(raw.top_provider) ? raw.top_provider : null
const contextWindow =
positiveInteger(raw.context_length) ??
positiveInteger(topProvider?.context_length)
const maxOutputTokens = positiveInteger(
topProvider?.max_completion_tokens,
)
return {
id,
apiName: id,
label:
typeof raw.name === 'string' && raw.name.trim() ? raw.name.trim() : id,
capabilities: {
supportsFunctionCalling: true,
supportsVision: hasStringValue(architecture?.input_modalities, 'image'),
supportsReasoning:
isRecord(raw.reasoning) ||
hasStringValue(raw.supported_parameters, 'reasoning') ||
hasStringValue(raw.supported_parameters, 'reasoning_effort'),
},
...(contextWindow ? { contextWindow } : {}),
...(maxOutputTokens ? { maxOutputTokens } : {}),
}
}
export default defineCatalog({
source: 'hybrid',
discovery: {
kind: 'openai-compatible',
requiresAuth: false,
mapModel: mapLlmtrModel,
},
discoveryCacheTtl: '1d',
discoveryRefreshMode: 'background-if-stale',
allowManualRefresh: true,
models: [
{
id: 'llmtr-deepseek-v4-flash',
apiName: 'deepseek/deepseek-v4-flash',
aliases: ['deepseek-v4-flash'],
label: 'DeepSeek V4 Flash',
modelDescriptorId: 'deepseek-v4-flash',
contextWindow: 1_000_000,
maxOutputTokens: 393_216,
},
{
id: 'llmtr-claude-sonnet-4.6',
apiName: 'anthropic/claude-sonnet-4.6',
aliases: ['claude-sonnet-4-6'],
label: 'Claude Sonnet 4.6',
modelDescriptorId: 'claude-sonnet-4-6',
contextWindow: 1_000_000,
maxOutputTokens: 128_000,
},
{
id: 'llmtr-gpt-5.4',
apiName: 'openai/gpt-5.4',
aliases: ['gpt-5.4'],
label: 'GPT-5.4',
modelDescriptorId: 'gpt-5.4',
contextWindow: 272_000,
maxOutputTokens: 128_000,
},
{
id: 'llmtr-gemini-3.1-pro-preview',
apiName: 'google/gemini-3.1-pro-preview',
label: 'Gemini 3.1 Pro Preview',
modelDescriptorId: 'google/gemini-3.1-pro-preview',
contextWindow: 1_048_576,
maxOutputTokens: 65_536,
},
{
id: 'llmtr-deepseek-v4-pro',
apiName: 'deepseek/deepseek-v4-pro',
aliases: ['deepseek-v4-pro'],
label: 'DeepSeek V4 Pro',
modelDescriptorId: 'deepseek-v4-pro',
contextWindow: 1_000_000,
maxOutputTokens: 393_216,
},
{
id: 'llmtr-glm-5.2',
apiName: 'zai/glm-5.2',
aliases: ['glm-5.2'],
label: 'GLM-5.2',
modelDescriptorId: 'glm-5.2',
contextWindow: 1_000_000,
maxOutputTokens: 131_072,
},
],
})
+187
View File
@@ -0,0 +1,187 @@
import { expect, test } from 'bun:test'
import { routeForPreset } from '../compatibility.js'
import { getProviderPresetUiMetadata } from '../providerUiMetadata.js'
import {
resolveActiveRouteIdFromEnv,
resolveRouteCredentialValue,
resolveRouteIdFromBaseUrl,
} from '../routeMetadata.js'
import catalog, { mapLlmtrModel } from './llmtr.models.js'
import gateway from './llmtr.js'
test('LLMTR uses the standard OpenAI-compatible gateway contract', () => {
expect(gateway.defaultBaseUrl).toBe('https://llmtr.com/v1')
expect(gateway.defaultModel).toBe('deepseek/deepseek-v4-flash')
expect(gateway.setup.credentialEnvVars).toEqual([
'LLMTR_API_KEY',
'OPENAI_API_KEY',
])
expect(gateway.setup.dedicatedCredentialsOnly).not.toBe(true)
expect(gateway.preset?.apiKeyEnvVars).toEqual([
'LLMTR_API_KEY',
'OPENAI_API_KEY',
])
expect(gateway.validation).toMatchObject({
kind: 'credential-env',
routing: { matchDefaultBaseUrl: true },
credentialEnvVars: [
'LLMTR_API_KEY',
'OPENAI_API_KEYS',
'OPENAI_API_KEY',
],
})
expect(gateway.validation?.routing?.matchBaseUrlHosts).toBeUndefined()
expect(gateway.transportConfig).toEqual({
kind: 'openai-compatible',
openaiShim: {
requiredApiFormat: 'chat_completions',
supportsAuthHeaders: false,
maxTokensField: 'max_tokens',
},
})
expect(catalog.source).toBe('hybrid')
expect(catalog.discovery?.requiresAuth).toBe(false)
})
test('LLMTR preset uses the existing generic profile path', () => {
expect(routeForPreset('llmtr')).toEqual({
vendorId: 'openai',
gatewayId: 'llmtr',
routeId: 'llmtr',
})
expect(
getProviderPresetUiMetadata('llmtr', {
LLMTR_API_KEY: 'llmtr-key',
OPENAI_API_KEY: 'fallback-key',
}),
).toMatchObject({
apiKey: 'llmtr-key',
baseUrl: 'https://llmtr.com/v1',
model: 'deepseek/deepseek-v4-flash',
provider: 'llmtr',
routeId: 'llmtr',
})
expect(
getProviderPresetUiMetadata('llmtr', {
OPENAI_API_KEY: 'fallback-key',
}).apiKey,
).toBe('fallback-key')
expect(
getProviderPresetUiMetadata('llmtr', {
LLMTR_API_KEY: 'SUA_CHAVE',
}).apiKey,
).toBe('')
expect(
getProviderPresetUiMetadata('llmtr', {
LLMTR_API_KEY: 'SUA_CHAVE',
OPENAI_API_KEY: 'fallback-key',
}).apiKey,
).toBe('fallback-key')
})
test('LLMTR dedicated credentials require an already selected LLMTR route', () => {
const processEnv = { LLMTR_API_KEY: 'llmtr-key' }
expect(resolveRouteIdFromBaseUrl('https://llmtr.com/v1')).toBe('llmtr')
expect(resolveRouteIdFromBaseUrl('https://llmtr.com/proxy/v1')).toBeNull()
expect(
resolveRouteIdFromBaseUrl('https://llmtr.com/v1?tenant=proxy'),
).toBeNull()
expect(resolveRouteIdFromBaseUrl('https://llmtr.com/v1#proxy')).toBeNull()
expect(
resolveRouteCredentialValue({
routeId: 'llmtr',
baseUrl: 'https://llmtr.com/v1',
processEnv,
}),
).toBe('llmtr-key')
expect(
resolveRouteCredentialValue({
routeId: 'custom',
baseUrl: 'https://proxy.example/v1',
processEnv,
}),
).toBeUndefined()
expect(
resolveRouteCredentialValue({
routeId: 'llmtr',
baseUrl: 'https://proxy.example/v1',
processEnv,
}),
).toBeUndefined()
expect(
resolveActiveRouteIdFromEnv(
{
CLAUDE_CODE_USE_OPENAI: '1',
OPENAI_BASE_URL: 'https://proxy.example/v1',
},
{
activeProfileProvider: 'llmtr',
activeProfileBaseUrl: 'https://proxy.example/v1',
},
),
).toBe('custom')
expect(
resolveActiveRouteIdFromEnv(processEnv),
).not.toBe('llmtr')
})
test('LLMTR discovery keeps tool-capable Chat Completions models', () => {
expect(
mapLlmtrModel({
id: 'example/chat-model',
name: 'Chat Model',
context_length: 131_072,
top_provider: {
max_completion_tokens: 32_768,
},
architecture: {
input_modalities: ['text', 'image'],
},
reasoning: {},
supported_parameters: ['tools', 'reasoning'],
supported_operations: ['CHAT_COMPLETIONS'],
supported_endpoints: ['/v1/chat/completions'],
}),
).toEqual({
id: 'example/chat-model',
apiName: 'example/chat-model',
label: 'Chat Model',
capabilities: {
supportsFunctionCalling: true,
supportsVision: true,
supportsReasoning: true,
},
contextWindow: 131_072,
maxOutputTokens: 32_768,
})
})
test.each([
['Responses-only', ['RESPONSES'], ['/v1/responses'], ['tools']],
['no tools', ['CHAT_COMPLETIONS'], ['/v1/chat/completions'], []],
['wrong endpoint', ['CHAT_COMPLETIONS'], ['/v1/responses'], ['tools']],
])('LLMTR discovery rejects %s models', (_label, operations, endpoints, parameters) => {
expect(
mapLlmtrModel({
id: 'example/incompatible',
supported_operations: operations,
supported_endpoints: endpoints,
supported_parameters: parameters,
}),
).toBeNull()
})
test('LLMTR curated models are a small tool-capable fallback catalog', () => {
expect(catalog.models?.map(model => model.apiName)).toEqual([
'deepseek/deepseek-v4-flash',
'anthropic/claude-sonnet-4.6',
'openai/gpt-5.4',
'google/gemini-3.1-pro-preview',
'deepseek/deepseek-v4-pro',
'zai/glm-5.2',
])
})
+49
View File
@@ -0,0 +1,49 @@
import { defineGateway } from '../define.js'
import catalog from './llmtr.models.js'
export default defineGateway({
id: 'llmtr',
label: 'LLMTR',
category: 'aggregating',
defaultBaseUrl: 'https://llmtr.com/v1',
defaultModel: 'deepseek/deepseek-v4-flash',
supportsModelRouting: true,
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['LLMTR_API_KEY', 'OPENAI_API_KEY'],
},
startup: {
probeReadiness: 'openai-compatible-models',
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
requiredApiFormat: 'chat_completions',
supportsAuthHeaders: false,
maxTokensField: 'max_tokens',
},
},
preset: {
id: 'llmtr',
description: 'LLMTR OpenAI-compatible multi-model gateway',
vendorId: 'openai',
apiKeyEnvVars: ['LLMTR_API_KEY', 'OPENAI_API_KEY'],
modelEnvVars: ['OPENAI_MODEL'],
},
validation: {
kind: 'credential-env',
routing: {
matchDefaultBaseUrl: true,
},
credentialEnvVars: [
'LLMTR_API_KEY',
'OPENAI_API_KEYS',
'OPENAI_API_KEY',
],
missingCredentialMessage:
'LLMTR auth is required. Set LLMTR_API_KEY or OPENAI_API_KEY.',
},
catalog,
usage: { supported: false },
})
@@ -37,6 +37,7 @@ import gatewayGitlawbOpengateway from '../gateways/gitlawb-opengateway.js'
import gatewayGroq from '../gateways/groq.js'
import gatewayHicap from '../gateways/hicap.js'
import gatewayKimiCode from '../gateways/kimi-code.js'
import gatewayLlmtr from '../gateways/llmtr.js'
import gatewayLmstudio from '../gateways/lmstudio.js'
import gatewayMistral from '../gateways/mistral.js'
import gatewayNvidiaNim from '../gateways/nvidia-nim.js'
@@ -91,7 +92,7 @@ import modelXai from '../models/xai.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 GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, gatewayConcentrate, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[]
export const GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, 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 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[])[]
@@ -252,6 +252,21 @@ export const PROVIDER_PRESET_MANIFEST = [
"OPENAI_MODEL"
]
},
{
"preset": "llmtr",
"routeKind": "gateway",
"routeId": "llmtr",
"vendorId": "openai",
"gatewayId": "llmtr",
"description": "LLMTR OpenAI-compatible multi-model gateway",
"apiKeyEnvVars": [
"LLMTR_API_KEY",
"OPENAI_API_KEY"
],
"modelEnvVars": [
"OPENAI_MODEL"
]
},
{
"preset": "lmstudio",
"routeKind": "gateway",
@@ -572,6 +587,7 @@ export const ORDERED_PROVIDER_PRESETS = [
"gemini",
"groq",
"hicap",
"llmtr",
"lmstudio",
"atomic-chat",
"ollama",
+5 -1
View File
@@ -41,7 +41,11 @@ function hasUsableEnvValue(envVar: string, value: string | undefined): boolean {
return false
}
if (envVar === 'OPENAI_API_KEYS' || envVar === 'OPENAI_API_KEY') {
if (
envVar === 'OPENAI_API_KEYS' ||
envVar === 'OPENAI_API_KEY' ||
envVar === 'LLMTR_API_KEY'
) {
return hasUsableOpenAICredential(value)
}
+53 -2
View File
@@ -231,7 +231,8 @@ function hasUsableEnvCredentialValue(
envVar === 'OPENAI_API_KEY' ||
envVar === 'AIMLAPI_API_KEY' ||
envVar === 'APISMART_API_KEY' ||
envVar === 'CONCENTRATE_API_KEY'
envVar === 'CONCENTRATE_API_KEY' ||
envVar === 'LLMTR_API_KEY'
) {
return hasUsableOpenAICredential(value)
}
@@ -467,6 +468,34 @@ export function isCanonicalConcentrateInferenceBaseUrl(
}
}
const LLMTR_CANONICAL_INFERENCE_BASE_URL = 'https://llmtr.com/v1'
export function isCanonicalLlmtrInferenceBaseUrl(
value: string | undefined,
): boolean {
const trimmed = value?.trim()
if (!trimmed) {
return false
}
try {
const canonical = new URL(LLMTR_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 getConcentrateBaseUrlOverride(
processEnv: NodeJS.ProcessEnv = process.env,
): string | undefined {
@@ -1117,6 +1146,13 @@ export function resolveRouteCredentialValue(
) {
return undefined
}
if (
routeId === 'llmtr' &&
options?.baseUrl !== undefined &&
!isCanonicalLlmtrInferenceBaseUrl(options.baseUrl)
) {
return undefined
}
return getRouteCredentialValue(routeId, processEnv)
}
@@ -1225,6 +1261,16 @@ export function resolveRouteIdFromBaseUrl(
normalizedBaseUrl &&
normalizedDefaultBaseUrl === normalizedBaseUrl
) {
// LLMTR's dedicated credential and fixed transport contract are valid
// only for the exact inference URL. The generic comparable-URL helper
// intentionally ignores query/hash components for other providers, but a
// query-bearing LLMTR URL is a retargeted/custom endpoint.
if (
route.id === 'llmtr' &&
!isCanonicalLlmtrInferenceBaseUrl(baseUrl)
) {
continue
}
return route.id
}
}
@@ -1241,7 +1287,9 @@ export function resolveRouteIdFromBaseUrl(
(route.id === 'longcat' && !isLongcatBaseUrl(baseUrl)) ||
(route.id === 'apismart' && !isApismartBaseUrl(baseUrl)) ||
(route.id === 'concentrate' &&
!isCanonicalConcentrateInferenceBaseUrl(baseUrl))
!isCanonicalConcentrateInferenceBaseUrl(baseUrl)) ||
(route.id === 'llmtr' &&
!isCanonicalLlmtrInferenceBaseUrl(baseUrl))
) {
continue
}
@@ -1281,6 +1329,9 @@ function profileRouteHonorsBaseUrlBoundary(
if (routeId === 'apismart') {
return isApismartBaseUrl(baseUrl)
}
if (routeId === 'llmtr') {
return isCanonicalLlmtrInferenceBaseUrl(baseUrl)
}
return true
}
+18
View File
@@ -293,6 +293,24 @@ describe('resolveModelRuntimeLimits', () => {
})
})
describe('LLMTR runtime attribution', () => {
it('keeps query-bearing endpoints on the generic custom transport', () => {
const result = resolveOpenAIShimRuntimeContext({
activeProfileProvider: 'llmtr',
baseUrl: 'https://llmtr.com/v1?tenant=proxy',
model: 'proxy-model',
processEnv: {
CLAUDE_CODE_USE_OPENAI: '1',
OPENAI_API_FORMAT: 'responses',
},
})
expect(result.routeId).not.toBe('llmtr')
expect(result.openaiShimConfig.requiredApiFormat).toBeUndefined()
expect(result.openaiShimConfig.maxTokensField).toBeUndefined()
})
})
describe('AIMLAPI runtime attribution', () => {
it('sends the fixed partner id on the canonical endpoint only', () => {
const previous = process.env.AIMLAPI_PARTNER_ID
@@ -20,6 +20,7 @@ const originalEnv = {
OPENAI_API_BASE: process.env.OPENAI_API_BASE,
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
OPENAI_API_KEYS: process.env.OPENAI_API_KEYS,
LLMTR_API_KEY: process.env.LLMTR_API_KEY,
OPENAI_MODEL: process.env.OPENAI_MODEL,
OPENAI_API_FORMAT: process.env.OPENAI_API_FORMAT,
OPENAI_AZURE_STYLE: process.env.OPENAI_AZURE_STYLE,
@@ -409,19 +410,25 @@ function makeChatCompletionResponse(model: string): Response {
async function captureChatCompletionRequest(
model = 'mimo-v2.5-pro',
): Promise<{ authorization: string | null; url: string | null }> {
defaultHeaders: Record<string, string> = {},
): Promise<{
authorization: string | null
headers: Record<string, string>
url: string | null
}> {
let authorization: string | null = null
let headers: Record<string, string> = {}
let url: string | null = null
globalThis.fetch = (async (input, init) => {
url = String(input)
const headers = init?.headers as Record<string, string> | undefined
authorization = headers?.Authorization ?? headers?.authorization ?? null
headers = (init?.headers as Record<string, string> | undefined) ?? {}
authorization = headers.Authorization ?? headers.authorization ?? null
return makeChatCompletionResponse(model)
}) as unknown as FetchType
const client = createOpenAIShimClient({}) as OpenAIShimClient
const client = createOpenAIShimClient({ defaultHeaders }) as OpenAIShimClient
await client.beta.messages.create({
model,
@@ -430,7 +437,7 @@ async function captureChatCompletionRequest(
stream: false,
})
return { authorization, url }
return { authorization, headers, url }
}
function makeCodexSseResponse(responseData: Record<string, unknown>): Response {
@@ -444,6 +451,7 @@ beforeEach(async () => {
delete process.env.OPENAI_API_BASE
process.env.OPENAI_API_KEY = 'test-key'
delete process.env.OPENAI_API_KEYS
delete process.env.LLMTR_API_KEY
delete process.env.OPENAI_MODEL
delete process.env.OPENAI_API_FORMAT
delete process.env.OPENAI_AZURE_STYLE
@@ -493,6 +501,7 @@ afterEach(() => {
restoreEnv('OPENAI_API_BASE', originalEnv.OPENAI_API_BASE)
restoreEnv('OPENAI_API_KEY', originalEnv.OPENAI_API_KEY)
restoreEnv('OPENAI_API_KEYS', originalEnv.OPENAI_API_KEYS)
restoreEnv('LLMTR_API_KEY', originalEnv.LLMTR_API_KEY)
restoreEnv('OPENAI_MODEL', originalEnv.OPENAI_MODEL)
restoreEnv('OPENAI_API_FORMAT', originalEnv.OPENAI_API_FORMAT)
restoreEnv('OPENAI_AZURE_STYLE', originalEnv.OPENAI_AZURE_STYLE)
@@ -570,6 +579,86 @@ test('Concentrate selection prefers its dedicated key over a generic OPENAI_API_
expect(captured.authorization).toBe('Bearer concentrate-key')
})
test('selected LLMTR route sends LLMTR_API_KEY through the generic route credential resolver', async () => {
process.env.LLMTR_API_KEY = 'llmtr-key'
delete process.env.OPENAI_API_KEYS
delete process.env.OPENAI_API_KEY
delete process.env.OPENAI_BASE_URL
delete process.env.OPENAI_MODEL
const result = applyProviderFlag('llmtr', [])
expect(result.error).toBeUndefined()
const captured = await captureChatCompletionRequest(
'deepseek/deepseek-v4-flash',
)
expect(captured.url).toBe('https://llmtr.com/v1/chat/completions')
expect(captured.authorization).toBe('Bearer llmtr-key')
})
test('selected LLMTR route prefers its dedicated key over a generic OPENAI_API_KEYS pool', async () => {
process.env.LLMTR_API_KEY = 'llmtr-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('llmtr', [])
expect(result.error).toBeUndefined()
const captured = await captureChatCompletionRequest(
'deepseek/deepseek-v4-flash',
)
expect(captured.authorization).toBe('Bearer llmtr-key')
})
test('raw-env LLMTR ignores unsupported custom auth and custom headers', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
process.env.OPENAI_MODEL = 'deepseek/deepseek-v4-flash'
process.env.LLMTR_API_KEY = 'llmtr-key'
process.env.OPENAI_AUTH_HEADER = 'X-Proxy-Key'
process.env.OPENAI_AUTH_SCHEME = 'raw'
process.env.OPENAI_AUTH_HEADER_VALUE = 'proxy-secret'
process.env.ANTHROPIC_CUSTOM_HEADERS = 'X-Tenant-Secret: tenant-secret'
delete process.env.OPENAI_API_KEYS
delete process.env.OPENAI_API_KEY
const captured = await captureChatCompletionRequest(
'deepseek/deepseek-v4-flash',
{ 'X-Tenant-Secret': 'tenant-secret' },
)
expect(captured.url).toBe('https://llmtr.com/v1/chat/completions')
expect(captured.authorization).toBe('Bearer llmtr-key')
expect(captured.headers['X-Proxy-Key']).toBeUndefined()
expect(captured.headers['X-Tenant-Secret']).toBeUndefined()
})
test('custom endpoints preserve configured auth and custom headers', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://proxy.example/v1'
process.env.OPENAI_MODEL = 'proxy-model'
process.env.LLMTR_API_KEY = 'llmtr-key'
process.env.OPENAI_AUTH_HEADER = 'X-Proxy-Key'
process.env.OPENAI_AUTH_SCHEME = 'raw'
process.env.OPENAI_AUTH_HEADER_VALUE = 'proxy-secret'
process.env.ANTHROPIC_CUSTOM_HEADERS = 'X-Tenant-Secret: tenant-secret'
delete process.env.OPENAI_API_KEYS
delete process.env.OPENAI_API_KEY
const captured = await captureChatCompletionRequest('proxy-model', {
'X-Tenant-Secret': 'tenant-secret',
})
expect(captured.url).toBe('https://proxy.example/v1/chat/completions')
expect(captured.authorization).toBeNull()
expect(captured.headers['X-Proxy-Key']).toBe('proxy-secret')
expect(captured.headers['X-Tenant-Secret']).toBe('tenant-secret')
})
test('gitlawb opengateway provider flag uses generic OPENAI_API_KEYS pool before generic OPENAI_API_KEY fallback', async () => {
process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'
process.env.OPENAI_API_KEYS = 'fake-openai-pool-a,fake-openai-pool-b'
+32 -4
View File
@@ -5,6 +5,7 @@ import {
redactEncodedSecretSubstringsForDisplay,
redactSecretSubstringsForDisplay,
} from '../../../utils/providerSecrets.js'
import { parseCustomHeadersEnv } from '../../../utils/providerCustomHeaders.js'
export function formatRetryAfterHint(response: Response): string {
const retryAfter = response.headers.get('retry-after')
@@ -240,11 +241,35 @@ export async function executeOpenAIRequest(
isGithubCopilot,
isGithubModels,
} = context
// Existing routes historically accept process-level custom auth even when
// their profile UI hides those controls. LLMTR is the new fixed-contract
// route: enforce its explicit capability without changing that compatibility
// behavior for unrelated providers or generic custom endpoints.
const supportsConfiguredAuthHeaders =
runtimeShimContext.routeId !== 'llmtr' ||
runtimeShimContext.openaiShimConfig.supportsAuthHeaders === true
const unsupportedCustomHeaderNames = supportsConfiguredAuthHeaders
? null
: new Set(
Object.keys(
parseCustomHeadersEnv(
requestProcessEnv.ANTHROPIC_CUSTOM_HEADERS,
) ?? {},
).map(name => name.toLowerCase()),
)
const filterUnsupportedCustomHeaders = (
headers: Record<string, string> | undefined,
): Record<string, string> =>
Object.fromEntries(
Object.entries(headers ?? {}).filter(
([name]) => !unsupportedCustomHeaderNames?.has(name.toLowerCase()),
),
)
const baseHeaders: Record<string, string> = {
'Content-Type': 'application/json',
...filterAnthropicHeaders(shimConfig.headers),
...defaultHeaders,
...filterAnthropicHeaders(options?.headers),
...filterUnsupportedCustomHeaders(defaultHeaders),
...filterUnsupportedCustomHeaders(filterAnthropicHeaders(options?.headers)),
}
const isGemini = isGeminiMode()
@@ -292,6 +317,7 @@ export async function executeOpenAIRequest(
requestProcessEnv.NEARAI_API_KEY,
requestProcessEnv.FIREWORKS_API_KEY,
requestProcessEnv.LONGCAT_API_KEY,
requestProcessEnv.LLMTR_API_KEY,
].some((value) => value?.trim() === openAIApiKeyRawUsable),
)
const routeCredentialIsCopiedProviderKey = Boolean(
@@ -330,7 +356,8 @@ export async function executeOpenAIRequest(
const catalogAuthHeader =
runtimeShimContext.catalogEntry?.transportOverrides?.openaiShim
?.defaultAuthHeader
const configuredAuthHeaderValue = catalogAuthHeader
const configuredAuthHeaderValue =
catalogAuthHeader || !supportsConfiguredAuthHeaders
? undefined
: requestProcessEnv.OPENAI_AUTH_HEADER_VALUE?.trim()
if (configuredAuthHeaderValue && /[\r\n]/.test(configuredAuthHeaderValue)) {
@@ -338,7 +365,8 @@ export async function executeOpenAIRequest(
'OPENAI_AUTH_HEADER_VALUE must not contain CR/LF characters',
)
}
const customAuthHeader = catalogAuthHeader
const customAuthHeader =
catalogAuthHeader || !supportsConfiguredAuthHeaders
? undefined
: requestProcessEnv.OPENAI_AUTH_HEADER?.trim()
const hasCustomAuthHeader = Boolean(
+12
View File
@@ -18,6 +18,7 @@ const TEST_ENV_KEYS = [
'CLAUDE_CODE_USE_OPENAI',
'CODEX_AUTH_JSON_PATH',
'CODEX_HOME',
'LLMTR_API_KEY',
'APISMART_API_KEY',
'APISMART_MODEL',
'OPENAI_API_KEYS',
@@ -292,6 +293,17 @@ describe('loadEnvFile', () => {
})
})
it('loads the dedicated LLMTR credential without selecting a route', () => {
const filePath = writeTempEnvFile('LLMTR_API_KEY=llmtr-key')
const loaded = loadEnvFile(filePath)
expect(process.env.LLMTR_API_KEY).toBe('llmtr-key')
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBeUndefined()
expect(process.env.OPENAI_BASE_URL).toBeUndefined()
expect(loaded).toEqual({ LLMTR_API_KEY: 'llmtr-key' })
})
it('loads documented Concentrate env-only provider setup values', () => {
const filePath = writeTempEnvFile([
'CONCENTRATE_API_KEY=concentrate-key',
+1
View File
@@ -77,6 +77,7 @@ const ALLOWED_ENV_FILE_KEYS = new Set([
'JINA_API_KEY',
'KIMI_API_KEY',
'LINKUP_API_KEY',
'LLMTR_API_KEY',
'LONGCAT_API_KEY',
'MINIMAX_API_KEY',
'MINIMAX_BASE_URL',
+66
View File
@@ -32,6 +32,7 @@ const ENV_KEYS = [
'OPENAI_AUTH_HEADER',
'OPENAI_AUTH_SCHEME',
'OPENAI_AUTH_HEADER_VALUE',
'LLMTR_API_KEY',
'GEMINI_MODEL',
'NVIDIA_API_KEY',
'NVIDIA_NIM',
@@ -574,6 +575,71 @@ describe('applyProviderFlag - descriptor-backed openai-compatible routes', () =>
expect(process.env.OPENAI_BASE_URL).toBe('http://proxy.local:8080/v1')
})
test('LLMTR clears stale custom auth only on its canonical endpoint', () => {
process.env.LLMTR_API_KEY = 'llmtr-key'
process.env.OPENAI_API_FORMAT = 'responses'
process.env.OPENAI_AUTH_HEADER = 'X-Proxy-Key'
process.env.OPENAI_AUTH_SCHEME = 'raw'
process.env.OPENAI_AUTH_HEADER_VALUE = 'proxy-secret'
process.env.ANTHROPIC_CUSTOM_HEADERS = 'X-Tenant-Secret: tenant-secret'
const result = applyProviderFlag('llmtr', [])
expect(result.error).toBeUndefined()
expect(process.env.OPENAI_BASE_URL).toBe('https://llmtr.com/v1')
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('LLMTR preserves custom endpoint auth settings when the endpoint is explicit', () => {
process.env.OPENAI_BASE_URL = 'https://proxy.example/v1'
process.env.OPENAI_API_FORMAT = 'responses'
process.env.OPENAI_AUTH_HEADER = 'X-Proxy-Key'
process.env.OPENAI_AUTH_SCHEME = 'raw'
process.env.OPENAI_AUTH_HEADER_VALUE = 'proxy-secret'
process.env.ANTHROPIC_CUSTOM_HEADERS = 'X-Tenant-Secret: tenant-secret'
const result = applyProviderFlag('llmtr', [])
expect(result.error).toBeUndefined()
expect(process.env.OPENAI_BASE_URL).toBe('https://proxy.example/v1')
expect(process.env.OPENAI_API_FORMAT).toBe('responses')
expect(process.env.OPENAI_AUTH_HEADER).toBe('X-Proxy-Key')
expect(process.env.OPENAI_AUTH_SCHEME).toBe('raw')
expect(process.env.OPENAI_AUTH_HEADER_VALUE).toBe('proxy-secret')
expect(process.env.ANTHROPIC_CUSTOM_HEADERS).toBe(
'X-Tenant-Secret: tenant-secret',
)
})
test('clears an LLMTR key copied into OPENAI_API_KEY when switching routes', () => {
process.env.LLMTR_API_KEY = 'llmtr-secret'
process.env.OPENAI_API_KEY = 'llmtr-secret'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
const result = applyProviderFlag('openrouter', [])
expect(result.error).toBeUndefined()
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
expect(process.env.OPENAI_API_KEY).toBeUndefined()
expect(process.env.LLMTR_API_KEY).toBe('llmtr-secret')
})
test('preserves an independent generic OPENAI_API_KEY when switching from LLMTR', () => {
process.env.LLMTR_API_KEY = 'llmtr-secret'
process.env.OPENAI_API_KEY = 'generic-openai-secret'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
const result = applyProviderFlag('openrouter', [])
expect(result.error).toBeUndefined()
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
expect(process.env.OPENAI_API_KEY).toBe('generic-openai-secret')
})
test('descriptor-backed provider selection preserves custom OPENAI_API_BASE alias', () => {
process.env.OPENAI_API_BASE = 'http://proxy.local:8080/v1'
process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'
+11
View File
@@ -32,6 +32,7 @@ import { PRESET_VENDOR_MAP } from '../integrations/compatibility.js'
import {
isCanonicalApismartInferenceBaseUrl,
isCanonicalConcentrateInferenceBaseUrl,
isCanonicalLlmtrInferenceBaseUrl,
} from '../integrations/routeMetadata.js'
import { hasUsableOpenAICredential } from '../services/api/credentialPool.js'
import { isFirstPartyAnthropicBaseUrlForEnv } from './anthropicBaseUrl.js'
@@ -364,6 +365,9 @@ export function applyProviderFlag(
: process.env.OPENAI_API_KEY !== undefined &&
process.env.OPENAI_API_KEY === process.env.CONCENTRATE_API_KEY
? 'concentrate'
: process.env.OPENAI_API_KEY !== undefined &&
process.env.OPENAI_API_KEY === process.env.LLMTR_API_KEY
? 'llmtr'
: process.env.OPENAI_API_KEY !== undefined &&
process.env.OPENAI_API_KEY === process.env.NEARAI_API_KEY
? 'nearai'
@@ -837,6 +841,13 @@ export function applyProviderFlag(
default:
process.env.CLAUDE_CODE_USE_OPENAI = '1'
applyOpenAIBaseUrlDefault(provider, defaultBaseUrl)
if (
provider === 'llmtr' &&
isCanonicalLlmtrInferenceBaseUrl(getConfiguredOpenAIBaseUrl())
) {
clearUnsupportedOpenAIShimSettings('llmtr')
delete process.env.ANTHROPIC_CUSTOM_HEADERS
}
if (defaultModel) {
process.env.OPENAI_MODEL ??= defaultModel
}
+38
View File
@@ -3148,6 +3148,44 @@ test('openai launch withholds ambient Concentrate credentials from a keyless pro
assert.equal(canonical.CONCENTRATE_API_KEY, 'ambient-concentrate-key')
})
test('openai launch withholds ambient LLMTR credentials from a keyless proxy profile on restart', async () => {
const env = await buildLaunchEnv({
profile: 'openai',
persisted: profile('openai', {
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'llmtr',
OPENAI_BASE_URL: 'https://proxy.example.com/v1',
OPENAI_MODEL: 'proxy-model',
}),
goal: 'coding',
processEnv: {
OPENAI_BASE_URL: 'https://proxy.example.com/v1',
OPENAI_API_KEY: 'ambient-llmtr-key',
LLMTR_API_KEY: 'ambient-llmtr-key',
},
})
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'llmtr')
assert.equal(env.OPENAI_API_KEY, undefined)
assert.equal(env.LLMTR_API_KEY, undefined)
const canonical = await buildLaunchEnv({
profile: 'openai',
persisted: profile('openai', {
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'llmtr',
OPENAI_BASE_URL: 'https://llmtr.com/v1',
OPENAI_MODEL: 'deepseek/deepseek-v4-flash',
}),
goal: 'coding',
processEnv: {
OPENAI_BASE_URL: 'https://llmtr.com/v1',
LLMTR_API_KEY: 'ambient-llmtr-key',
},
})
assert.equal(canonical.OPENAI_API_KEY, undefined)
assert.equal(canonical.LLMTR_API_KEY, 'ambient-llmtr-key')
})
test('openai launch removes a legacy persisted Concentrate key from a noncanonical URL', async () => {
const env = await buildLaunchEnv({
profile: 'openai',
+46 -5
View File
@@ -27,6 +27,7 @@ import {
getRouteDefaultModel,
isCanonicalApismartInferenceBaseUrl,
isCanonicalConcentrateInferenceBaseUrl,
isCanonicalLlmtrInferenceBaseUrl,
isLongcatBaseUrl,
normalizeXiaomiMimoBaseUrl,
resolveRouteCredentialValue,
@@ -118,6 +119,7 @@ const PROFILE_ENV_KEYS = [
'NEARAI_API_KEY',
'FIREWORKS_API_KEY',
'LONGCAT_API_KEY',
'LLMTR_API_KEY',
'CONCENTRATE_API_KEY',
'CONCENTRATE_BASE_URL',
'CONCENTRATE_MODEL',
@@ -208,6 +210,7 @@ export type ProfileEnv = {
NEARAI_API_KEY?: string
FIREWORKS_API_KEY?: string
LONGCAT_API_KEY?: string
LLMTR_API_KEY?: string
CONCENTRATE_API_KEY?: string
CONCENTRATE_BASE_URL?: string
CONCENTRATE_MODEL?: string
@@ -2147,8 +2150,15 @@ export async function buildLaunchEnv(options: {
effectiveOpenAIRouteId === 'concentrate' &&
!!env.OPENAI_BASE_URL?.trim() &&
!isCanonicalConcentrateInferenceBaseUrl(env.OPENAI_BASE_URL)
const isNoncanonicalLlmtrLaunch =
effectiveOpenAIRouteId === 'llmtr' &&
!!env.OPENAI_BASE_URL?.trim() &&
!isCanonicalLlmtrInferenceBaseUrl(env.OPENAI_BASE_URL)
const isNoncanonicalDedicatedOpenAILaunch =
isNoncanonicalAimlapiLaunch || isNoncanonicalApismartLaunch || isNoncanonicalConcentrateLaunch
isNoncanonicalAimlapiLaunch ||
isNoncanonicalApismartLaunch ||
isNoncanonicalConcentrateLaunch ||
isNoncanonicalLlmtrLaunch
if (isNoncanonicalDedicatedOpenAILaunch) {
delete env.OPENAI_API_KEY
delete env.OPENAI_API_KEYS
@@ -2157,7 +2167,7 @@ export async function buildLaunchEnv(options: {
// dedicated credential is never valid off the canonical endpoint, and
// older profiles could have stored that same secret under either generic
// alias. Do not resurrect it for a noncanonical Concentrate launch.
if (!isNoncanonicalConcentrateLaunch) {
if (!isNoncanonicalConcentrateLaunch && !isNoncanonicalLlmtrLaunch) {
const persistedCredential = resolveOpenAICredentialEnvSelection(persistedEnv)
if (persistedCredential) {
env[persistedCredential.envVar] = persistedCredential.value
@@ -2190,6 +2200,7 @@ export async function buildLaunchEnv(options: {
'ATLAS_CLOUD_API_KEY',
'APISMART_API_KEY',
'CONCENTRATE_API_KEY',
'LLMTR_API_KEY',
'NEARAI_API_KEY',
'FIREWORKS_API_KEY',
'LONGCAT_API_KEY',
@@ -2211,6 +2222,9 @@ export async function buildLaunchEnv(options: {
if (dedicatedKey === 'CONCENTRATE_API_KEY' && effectiveOpenAIRouteId !== 'concentrate') {
continue
}
if (dedicatedKey === 'LLMTR_API_KEY' && effectiveOpenAIRouteId !== 'llmtr') {
continue
}
if (dedicatedKey === 'NVIDIA_API_KEY' && effectiveOpenAIRouteId !== 'nvidia-nim') {
continue
}
@@ -2238,14 +2252,21 @@ export async function buildLaunchEnv(options: {
dedicatedKey === 'CONCENTRATE_API_KEY' &&
!!dedicatedBaseUrl &&
!isCanonicalConcentrateInferenceBaseUrl(dedicatedBaseUrl)
const withholdAmbientLlmtrKey =
dedicatedKey === 'LLMTR_API_KEY' &&
!!dedicatedBaseUrl &&
!isCanonicalLlmtrInferenceBaseUrl(dedicatedBaseUrl)
const withholdAmbientDedicatedKey =
withholdAmbientAimlapiKey || withholdAmbientApismartKey || withholdAmbientConcentrateKey
withholdAmbientAimlapiKey ||
withholdAmbientApismartKey ||
withholdAmbientConcentrateKey ||
withholdAmbientLlmtrKey
// Unlike the generic proxy-compatible routes above, Concentrate's
// dedicated key is never valid outside its canonical inference endpoint.
// Do not preserve a legacy persisted key for a retargeted Concentrate
// profile: older versions could have serialized one before this boundary
// was enforced.
if (withholdAmbientConcentrateKey) {
if (withholdAmbientConcentrateKey || withholdAmbientLlmtrKey) {
continue
}
// AIMLAPI accepts generic OpenAI credentials, but ApiSmart is
@@ -2278,9 +2299,23 @@ export async function buildLaunchEnv(options: {
persistedOpenAICredential?.kind === 'usable'
? sanitizeApiKey(persistedOpenAICredential.value)
: undefined
// A selected canonical LLMTR profile owns its saved credential. Prefer it
// over an unrelated ambient LLMTR_API_KEY on restart, and migrate startup
// files written before LLMTR_API_KEY was persisted explicitly.
const persistedLlmtrProfileKey =
dedicatedKey === 'LLMTR_API_KEY' &&
effectiveOpenAIRouteId === 'llmtr' &&
!!dedicatedBaseUrl &&
isCanonicalLlmtrInferenceBaseUrl(dedicatedBaseUrl)
? sanitizeApiKey(persistedEnv.LLMTR_API_KEY) ||
(persistedOpenAICredential?.kind === 'usable'
? sanitizeApiKey(persistedOpenAICredential.value)
: undefined)
: undefined
const dedicatedValue = withholdAmbientDedicatedKey
? sanitizeApiKey(persistedEnv[dedicatedKey])
: backfillDedicatedFromOpenAI ||
persistedLlmtrProfileKey ||
sanitizeApiKey(processEnv[dedicatedKey]) ||
sanitizeApiKey(persistedEnv[dedicatedKey]) ||
backfillLegacyApismartProfileKey ||
@@ -2390,10 +2425,16 @@ export async function buildStartupEnvFromProfile(options?: {
persisted.env.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'concentrate' &&
!!persisted.env.OPENAI_BASE_URL?.trim() &&
!isCanonicalConcentrateInferenceBaseUrl(persisted.env.OPENAI_BASE_URL)
const persistedLlmtrProxy =
persisted?.profile === 'openai' &&
persisted.env.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'llmtr' &&
!!persisted.env.OPENAI_BASE_URL?.trim() &&
!isCanonicalLlmtrInferenceBaseUrl(persisted.env.OPENAI_BASE_URL)
if (
hasConcreteProviderSelection(processEnv) &&
!persistedApismartProxy &&
!persistedConcentrateProxy
!persistedConcentrateProxy &&
!persistedLlmtrProxy
) {
return processEnv
}
+199
View File
@@ -5,6 +5,7 @@ import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
import { acquireEnvMutex, releaseEnvMutex } from '../entrypoints/sdk/shared.js'
import { resolveRouteCredentialValue } from '../integrations/routeMetadata.js'
import type { ProviderProfile } from './config.js'
async function importFreshProvidersModule() {
@@ -72,6 +73,7 @@ const RESTORED_KEYS = [
'ATLAS_CLOUD_API_KEY',
'APISMART_API_KEY',
'APISMART_MODEL',
'LLMTR_API_KEY',
'CONCENTRATE_API_KEY',
'CONCENTRATE_BASE_URL',
'CONCENTRATE_MODEL',
@@ -292,6 +294,17 @@ function buildConcentrateProfile(overrides: Partial<ProviderProfile> = {}): Prov
})
}
function buildLlmtrProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile {
return buildProfile({
provider: 'llmtr',
name: 'LLMTR',
baseUrl: 'https://llmtr.com/v1',
model: 'deepseek/deepseek-v4-flash',
apiKey: 'llmtr-test-key',
...overrides,
})
}
function buildClinePassProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile {
return buildProfile({
provider: 'clinepass',
@@ -317,6 +330,50 @@ function buildCloudflareProfile(overrides: Partial<ProviderProfile> = {}): Provi
}
describe('applyProviderProfileToProcessEnv', () => {
test('LLMTR profile clears an ambient dedicated key so its saved key wins', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
process.env.LLMTR_API_KEY = 'ambient-old'
applyProviderProfileToProcessEnv(
buildProfile({
provider: 'llmtr',
name: 'LLMTR',
baseUrl: 'https://llmtr.com/v1',
model: 'deepseek/deepseek-v4-flash',
apiKey: 'selected-new',
}),
)
expect(process.env.LLMTR_API_KEY).toBeUndefined()
expect(process.env.OPENAI_API_KEY).toBe('selected-new')
expect(
resolveRouteCredentialValue({
routeId: 'llmtr',
baseUrl: process.env.OPENAI_BASE_URL,
processEnv: process.env,
}),
).toBe('selected-new')
}, 20_000)
test('keyless canonical LLMTR profile adopts its ambient dedicated key', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
process.env.LLMTR_API_KEY = 'ambient-llmtr-key'
applyProviderProfileToProcessEnv(buildLlmtrProfile({ apiKey: undefined }))
expect(process.env.LLMTR_API_KEY).toBe('ambient-llmtr-key')
expect(process.env.OPENAI_API_KEY).toBe('ambient-llmtr-key')
expect(
resolveRouteCredentialValue({
routeId: 'llmtr',
baseUrl: process.env.OPENAI_BASE_URL,
processEnv: process.env,
}),
).toBe('ambient-llmtr-key')
}, 20_000)
test('applies Azure-style routing from a saved OpenAI-compatible profile', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
@@ -1802,6 +1859,87 @@ describe('applyProviderProfileToProcessEnv', () => {
expect(process.env.ANTHROPIC_MODEL).toBe('claude-sonnet-4-6')
expect(process.env.CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS).toBeUndefined()
})
test('retargeted LLMTR profile withholds its dedicated credential', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
applyProviderProfileToProcessEnv(
buildLlmtrProfile({ baseUrl: 'https://proxy.example/v1' }),
)
expect(process.env.OPENAI_BASE_URL).toBe('https://proxy.example/v1')
expect(process.env.OPENAI_API_KEY).toBeUndefined()
expect(process.env.LLMTR_API_KEY).toBeUndefined()
})
test('query-bearing LLMTR profiles retain generic proxy capabilities', async () => {
const { addProviderProfile, applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
process.env.LLMTR_API_KEY = 'ambient-llmtr-key'
const saved = addProviderProfile({
provider: 'llmtr',
name: 'LLMTR query proxy',
baseUrl: 'https://llmtr.com/v1?tenant=proxy',
model: 'proxy-model',
apiKey: 'saved-llmtr-key',
apiFormat: 'responses',
authHeader: 'X-Proxy-Key',
authScheme: 'raw',
authHeaderValue: 'proxy-auth-value',
customHeaders: { 'X-Proxy-Trace': 'enabled' },
})
applyProviderProfileToProcessEnv(saved!)
expect(process.env.OPENAI_API_FORMAT).toBe('responses')
expect(process.env.OPENAI_AUTH_HEADER).toBe('X-Proxy-Key')
expect(process.env.OPENAI_AUTH_HEADER_VALUE).toBe('proxy-auth-value')
expect(process.env.ANTHROPIC_CUSTOM_HEADERS).toBe(
'X-Proxy-Trace: enabled',
)
expect(process.env.OPENAI_API_KEY).toBeUndefined()
expect(process.env.LLMTR_API_KEY).toBeUndefined()
})
test('retargeted LLMTR profiles retain generic proxy capabilities', async () => {
const { addProviderProfile, applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
const saved = addProviderProfile({
provider: 'llmtr',
name: 'LLMTR proxy',
baseUrl: 'https://proxy.example/v1',
model: 'proxy-model',
apiKey: 'llmtr-test-key',
apiFormat: 'responses',
authHeader: 'X-Proxy-Key',
authScheme: 'raw',
authHeaderValue: 'proxy-auth-value',
customHeaders: { 'X-Proxy-Trace': 'enabled' },
})
expect(saved).toMatchObject({
apiFormat: 'responses',
authHeader: 'X-Proxy-Key',
authScheme: 'raw',
authHeaderValue: 'proxy-auth-value',
customHeaders: { 'X-Proxy-Trace': 'enabled' },
})
applyProviderProfileToProcessEnv(saved!)
expect(process.env.OPENAI_API_FORMAT).toBe('responses')
expect(process.env.OPENAI_AUTH_HEADER).toBe('X-Proxy-Key')
expect(process.env.OPENAI_AUTH_SCHEME).toBe('raw')
expect(process.env.OPENAI_AUTH_HEADER_VALUE).toBe('proxy-auth-value')
expect(process.env.ANTHROPIC_CUSTOM_HEADERS).toBe(
'X-Proxy-Trace: enabled',
)
expect(process.env.OPENAI_API_KEY).toBeUndefined()
expect(process.env.LLMTR_API_KEY).toBeUndefined()
})
})
describe('getProviderProfiles', () => {
@@ -1918,6 +2056,7 @@ describe('clearActiveProviderProfile', () => {
expect(process.env.OPENAI_BASE_URL).toBeUndefined()
expect(process.env.OPENAI_API_KEY).toBeUndefined()
})
})
describe('Anthropic sentinel survives profile management (#1426)', () => {
@@ -3615,6 +3754,66 @@ describe('setActiveProviderProfile', () => {
}
})
test('keyed canonical LLMTR profiles persist their saved dedicated credential across restart', 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 llmtrProfile = buildLlmtrProfile({ id: 'llmtr_profile' })
saveMockGlobalConfig(current => ({
...current,
providerProfiles: [llmtrProfile],
}))
const result = setActiveProviderProfile('llmtr_profile', { configDir })
const persisted = JSON.parse(
readFileSync(join(configDir, '.openclaude-profile.json'), 'utf8'),
)
expect(result?.id).toBe('llmtr_profile')
expect(persisted.profile).toBe('openai')
expect(persisted.env).toMatchObject({
OPENAI_BASE_URL: 'https://llmtr.com/v1',
OPENAI_MODEL: 'deepseek/deepseek-v4-flash',
OPENAI_API_KEY: 'llmtr-test-key',
LLMTR_API_KEY: 'llmtr-test-key',
})
const { buildStartupEnvFromProfile } = await import(
`./providerProfile.js?ts=${Date.now()}-${Math.random()}`
)
const startupEnv = await buildStartupEnvFromProfile({
persisted,
processEnv: {
LLMTR_API_KEY: 'ambient-unrelated-key',
},
})
expect(startupEnv.OPENAI_API_KEY).toBe('llmtr-test-key')
expect(startupEnv.LLMTR_API_KEY).toBe('llmtr-test-key')
delete persisted.env.LLMTR_API_KEY
const migratedStartupEnv = await buildStartupEnvFromProfile({
persisted,
processEnv: {
LLMTR_API_KEY: 'ambient-unrelated-key',
},
})
expect(migratedStartupEnv.OPENAI_API_KEY).toBe('llmtr-test-key')
expect(migratedStartupEnv.LLMTR_API_KEY).toBe('llmtr-test-key')
} finally {
process.chdir(originalCwd)
rmSync(tempDir, { recursive: true, force: true })
rmSync(configDir, { recursive: true, force: true })
}
})
test('persists Xiaomi MiMo profiles using a legacy-compatible openai startup profile', async () => {
const tempDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-'))
const configDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-config-'))
+55 -6
View File
@@ -57,6 +57,7 @@ import {
isClinePassBaseUrl,
isCanonicalApismartInferenceBaseUrl,
isCanonicalConcentrateInferenceBaseUrl,
isCanonicalLlmtrInferenceBaseUrl,
isFireworksBaseUrl,
isLongcatBaseUrl,
isNearaiBaseUrl,
@@ -175,6 +176,15 @@ function isConcentrateProfile(profile: ProviderProfile): boolean {
return !baseUrl || isCanonicalConcentrateInferenceBaseUrl(baseUrl)
}
function isLlmtrProfile(profile: ProviderProfile): boolean {
const { route } = resolveProfileCompatibility(profile.provider)
if (route.routeId !== 'llmtr') {
return false
}
const baseUrl = profile.baseUrl?.trim()
return !baseUrl || isCanonicalLlmtrInferenceBaseUrl(baseUrl)
}
function deriveGithubEnterpriseUrl(baseUrl: string | undefined): string | undefined {
if (!baseUrl?.trim()) return undefined
try {
@@ -239,7 +249,7 @@ function normalizeBaseUrl(value: string): string {
return trimValue(value).replace(/\/+$/, '')
}
function resolveProfileCapabilityRouteId(
export function resolveProfileCapabilityRouteId(
provider: string,
baseUrl?: string,
): string {
@@ -259,13 +269,16 @@ function resolveProfileCapabilityRouteId(
if (
(providerRouteId === 'cloudflare' ||
providerRouteId === 'longcat' ||
providerRouteId === 'concentrate') &&
providerRouteId === 'concentrate' ||
providerRouteId === 'llmtr') &&
baseUrl &&
!(providerRouteId === 'cloudflare'
? isCloudflareBaseUrl(baseUrl)
: providerRouteId === 'longcat'
? isLongcatBaseUrl(baseUrl)
: isCanonicalConcentrateInferenceBaseUrl(baseUrl))
: providerRouteId === 'concentrate'
? isCanonicalConcentrateInferenceBaseUrl(baseUrl)
: isCanonicalLlmtrInferenceBaseUrl(baseUrl))
) {
return 'custom'
}
@@ -1023,10 +1036,13 @@ export function applyProviderProfileToProcessEnv(
route.routeId === 'apismart' && !isApismartProfile(profile)
const withholdRetargetedConcentrateCredential =
route.routeId === 'concentrate' && !isConcentrateProfile(profile)
const withholdRetargetedLlmtrCredential =
route.routeId === 'llmtr' && !isLlmtrProfile(profile)
if (
profile.apiKey &&
!withholdRetargetedApismartCredential &&
!withholdRetargetedConcentrateCredential
!withholdRetargetedConcentrateCredential &&
!withholdRetargetedLlmtrCredential
) {
openAIProfileEnv.OPENAI_API_KEY = profile.apiKey
if (route.vendorId === 'minimax' || normalizedProfileBaseUrl.toLowerCase().includes('minimax')) {
@@ -1148,6 +1164,22 @@ export function applyProviderProfileToProcessEnv(
}
}
}
// A keyless canonical LLMTR profile may use the dedicated credential from
// the ambient environment. Profile application clears all managed provider
// variables before installing this object, so adopt it here while the
// canonical endpoint boundary is still known. Never carry it to a
// retargeted profile.
if (route.routeId === 'llmtr') {
openAIProfileEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'llmtr'
if (isLlmtrProfile(profile) && !profile.apiKey) {
const ambientLlmtrKey = sanitizeApiKey(process.env.LLMTR_API_KEY)
if (ambientLlmtrKey) {
openAIProfileEnv.OPENAI_API_KEY =
openAIProfileEnv.OPENAI_API_KEY ?? ambientLlmtrKey
openAIProfileEnv.LLMTR_API_KEY = ambientLlmtrKey
}
}
}
if (route.gatewayId === 'nvidia-nim') {
openAIProfileEnv.NVIDIA_NIM = '1'
}
@@ -1436,15 +1468,19 @@ function buildOpenAICompatibleStartupEnv(
const withholdRetargetedConcentrateCredential =
activeProfileRouteId === 'concentrate' &&
!isConcentrateProfile(activeProfile)
const withholdRetargetedLlmtrCredential =
activeProfileRouteId === 'llmtr' && !isLlmtrProfile(activeProfile)
const isAimlapiProfile =
activeProfile.provider === 'aimlapi' ||
resolveRouteIdFromBaseUrl(activeProfile.baseUrl) === 'aimlapi'
const isConcentrateProfileFlag = isConcentrateProfile(activeProfile)
const isLlmtrProfileFlag = isLlmtrProfile(activeProfile)
if (
activeProfile.apiKey &&
!withholdRetargetedApismartCredential &&
!withholdRetargetedConcentrateCredential
!withholdRetargetedConcentrateCredential &&
!withholdRetargetedLlmtrCredential
) {
const strictEnv = buildOpenAIProfileEnv({
goal: 'balanced',
@@ -1476,6 +1512,9 @@ function buildOpenAICompatibleStartupEnv(
if (isConcentrateProfileFlag) {
strictEnv.CONCENTRATE_API_KEY = activeProfile.apiKey
}
if (isLlmtrProfileFlag) {
strictEnv.LLMTR_API_KEY = activeProfile.apiKey
}
if (isClinePassProfile(activeProfile)) {
strictEnv.CLINE_API_KEY = activeProfile.apiKey
}
@@ -1533,10 +1572,14 @@ function buildOpenAICompatibleStartupEnv(
if (activeProfileRouteId === 'concentrate') {
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'concentrate'
}
if (activeProfileRouteId === 'llmtr') {
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'llmtr'
}
if (
activeProfile.apiKey &&
!withholdRetargetedApismartCredential &&
!withholdRetargetedConcentrateCredential
!withholdRetargetedConcentrateCredential &&
!withholdRetargetedLlmtrCredential
) {
env.OPENAI_API_KEY = activeProfile.apiKey
if (activeProfile.baseUrl?.toLowerCase().includes('bankr')) {
@@ -1566,6 +1609,9 @@ function buildOpenAICompatibleStartupEnv(
if (isConcentrateProfileFlag) {
env.CONCENTRATE_API_KEY = activeProfile.apiKey
}
if (isLlmtrProfileFlag) {
env.LLMTR_API_KEY = activeProfile.apiKey
}
if (isClinePassProfile(activeProfile)) {
env.CLINE_API_KEY = activeProfile.apiKey
}
@@ -1820,6 +1866,9 @@ function triggerStartupDiscoveryRefreshForProfile(
if (route.routeId === 'apismart' && !isApismartProfile(profile)) {
return
}
if (route.routeId === 'llmtr' && !isLlmtrProfile(profile)) {
return
}
void refreshStartupDiscoveryForRoute(route.routeId, {
baseUrl: profile.baseUrl,
@@ -20,6 +20,7 @@ describe('clearStartupProviderOverrides', () => {
OPENAI_MODEL: 'minimax-m2.7',
OPENAI_API_KEYS: 'pool-a,pool-b',
OPENAI_API_KEY: 'single-key',
LLMTR_API_KEY: 'llmtr-key',
MINIMAX_API_KEY: 'sk-minimax',
VENICE_API_KEY: 'sk-venice',
LONGCAT_API_KEY: 'sk-longcat',
@@ -44,6 +45,7 @@ describe('clearStartupProviderOverrides', () => {
OPENAI_MODEL: undefined,
OPENAI_API_KEYS: undefined,
OPENAI_API_KEY: undefined,
LLMTR_API_KEY: undefined,
MINIMAX_API_KEY: undefined,
VENICE_API_KEY: undefined,
LONGCAT_API_KEY: undefined,
+1
View File
@@ -14,6 +14,7 @@ export const STARTUP_PROVIDER_OVERRIDE_ENV_KEYS = [
'OPENAI_MODEL',
'OPENAI_API_KEYS',
'OPENAI_API_KEY',
'LLMTR_API_KEY',
'OPENAI_ORG',
'OPENAI_PROJECT',
'OPENAI_ORGANIZATION',
+68
View File
@@ -32,6 +32,7 @@ const ENV_KEYS = [
'MISTRAL_API_KEY',
'MINIMAX_API_KEY',
'LONGCAT_API_KEY',
'LLMTR_API_KEY',
'APISMART_API_KEY',
'CONCENTRATE_API_KEY',
'CONCENTRATE_BASE_URL',
@@ -145,6 +146,73 @@ test('openai missing key error includes recovery guidance and config locations',
expect(message!).toContain('Saved startup settings can come from')
})
test('LLMTR validation accepts its dedicated credential on the selected route', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
process.env.OPENAI_MODEL = 'deepseek/deepseek-v4-flash'
process.env.LLMTR_API_KEY = 'llmtr-key'
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
})
test('LLMTR validation rejects placeholder dedicated credentials', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
process.env.LLMTR_API_KEY = 'SUA_CHAVE'
await expect(getProviderValidationError(process.env)).resolves.toBe(
'LLMTR auth is required. Set LLMTR_API_KEY or OPENAI_API_KEY.',
)
})
test('LLMTR validation rejects an invalid generic fallback credential', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
process.env.OPENAI_API_KEY = 'SUA_CHAVE'
await expect(getProviderValidationError(process.env)).resolves.toBe(
'LLMTR auth is required. Set LLMTR_API_KEY or OPENAI_API_KEY.',
)
})
test('LLMTR validation accepts a dedicated credential despite an invalid generic fallback', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
process.env.LLMTR_API_KEY = 'llmtr-key'
process.env.OPENAI_API_KEY = 'SUA_CHAVE'
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
})
test('LLMTR validation falls back from a placeholder dedicated key to OPENAI_API_KEY', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
process.env.OPENAI_MODEL = 'deepseek/deepseek-v4-flash'
process.env.LLMTR_API_KEY = 'SUA_CHAVE'
process.env.OPENAI_API_KEY = 'llmtr-fallback-key'
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
})
test('LLMTR validation falls back from a placeholder dedicated key to OPENAI_API_KEYS', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://llmtr.com/v1'
process.env.OPENAI_MODEL = 'deepseek/deepseek-v4-flash'
process.env.LLMTR_API_KEY = 'SUA_CHAVE'
process.env.OPENAI_API_KEYS = 'llmtr-pool-key-a,llmtr-pool-key-b'
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
})
test('LLMTR_API_KEY does not authenticate an unrelated OpenAI-compatible route', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'https://proxy.example/v1'
process.env.LLMTR_API_KEY = 'llmtr-key'
const message = await getProviderValidationError(process.env)
expect(message).toContain('OPENAI_API_KEYS or OPENAI_API_KEY is required')
})
test('cloudflare Workers AI URL selects the Cloudflare validation target', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL =
+13 -2
View File
@@ -142,7 +142,8 @@ function hasUsableCredentialEnvValue(
envVar === 'OPENAI_API_KEY' ||
envVar === 'AIMLAPI_API_KEY' ||
envVar === 'APISMART_API_KEY' ||
envVar === 'CONCENTRATE_API_KEY'
envVar === 'CONCENTRATE_API_KEY' ||
envVar === 'LLMTR_API_KEY'
) {
return hasUsableOpenAICredential(value)
}
@@ -344,13 +345,23 @@ function getCredentialEnvValidationError(
if (usesOpenAIFallback) {
const openAIState = resolveOpenAICredentialEnvState(env)
if (openAIState.invalid) {
const hasUsableDedicatedCredential = credentialEnvVars.some(
envVar =>
envVar !== 'OPENAI_API_KEYS' &&
envVar !== 'OPENAI_API_KEY' &&
hasUsableCredentialEnvValue(env, envVar),
)
if (!hasUsableDedicatedCredential) {
return (
validation.invalidCredentialValues?.find(
invalidValue => invalidValue.envVar === openAIState.envVar,
)?.message ?? null
)?.message ??
validation.missingCredentialMessage ??
null
)
}
}
}
for (const invalidValue of validation.invalidCredentialValues ?? []) {
if (
+10
View File
@@ -42,6 +42,16 @@ test('buildInheritedEnvVars forwards pooled OpenAI credentials', () => {
expect(envVars).toContain('OPENAI_API_KEYS=key-a\\,key-b')
})
test('buildInheritedEnvVars forwards an LLMTR credential without inventing route state', () => {
process.env.LLMTR_API_KEY = 'llmtr-key'
const envVars = buildInheritedEnvVars()
expect(envVars).toContain('LLMTR_API_KEY=llmtr-key')
expect(envVars).not.toContain('CLAUDE_CODE_USE_OPENAI=1')
expect(envVars).not.toContain('OPENAI_BASE_URL=')
})
test('buildInheritedEnvVars forwards PATH for source-built teammate tool lookups', () => {
process.env.PATH = '/custom/bin:/usr/bin'
+1
View File
@@ -109,6 +109,7 @@ const TEAMMATE_ENV_VARS = [
'GH_TOKEN',
'OPENAI_API_KEYS',
'OPENAI_API_KEY',
'LLMTR_API_KEY',
'OPENAI_BASE_URL',
'OPENAI_MODEL',
'GEMINI_API_KEY',
+8
View File
@@ -140,6 +140,14 @@ export const providers: Provider[] = [
envVars: ['OPENROUTER_API_KEY'],
notes: 'OpenAI-compatible aggregation across hundreds of hosted models.',
},
{
id: 'llmtr',
name: 'LLMTR',
group: 'gateways',
setup: '/provider or OpenAI-compatible env vars',
envVars: ['LLMTR_API_KEY', 'OPENAI_API_KEY'],
notes: '/provider and --provider llmtr default to deepseek/deepseek-v4-flash; raw env setup must set OPENAI_BASE_URL=https://llmtr.com/v1 and OPENAI_MODEL. Uses public discovery of tool-capable chat models.',
},
{
id: 'near-ai',
name: 'NEAR AI',