mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 02:34:15 -05:00
feat(gateway): add Concentrate AI provider with dynamic model discovery (#2140)
* feat(gateway): add Concentrate AI provider with dynamic model discovery * fix(concentrate): prompt for Concentrate API key in /provider instead of pre-filling OPENAI_API_KEY * docs(concentrate): remove standalone setup guide to match other gateways * fix(concentrate): dedicated-credential-only routing, env-only identity, and credential isolation - Make Concentrate dedicatedCredentialsOnly so ambient OPENAI_API_KEY is never forwarded. Only CONCENTRATE_API_KEY authenticates the route. - Resolve Concentrate env-only route identity from CONCENTRATE_API_KEY, CONCENTRATE_BASE_URL, CONCENTRATE_MODEL, or a Concentrate-shaped OPENAI_BASE_URL. - Mirror the dedicated credential into OPENAI_API_KEY only after the route identity is established and only for the canonical /v1 inference endpoint. - Add Concentrate support to --provider concentrate, saved profiles, startup env rebuild, and .env allowlist. - Add regression tests for env-only, flag, saved-profile, and client routing. - Fix adjacent ApiSmart keyless profile leaking string 'undefined' into OPENAI_API_KEY and extend the first providerProfiles test timeout for the now-slower fresh module import. * fix(concentrate): protect credentials on noncanonical urls * fix(concentrate): drop legacy keys on retargeted profiles * fix(concentrate): remove legacy generic keys on proxies * fix(concentrate): reject noncanonical credential routes * fix(concentrate): validate env-only credentials * fix(concentrate): align env-only route validation * fix(concentrate): clear headers before client setup * test(validation): isolate Concentrate model env * fix(concentrate): honor provider precedence and model defaults * test(concentrate): cover model resolution precedence * fix(concentrate): preserve legacy model fallback * fix(concentrate): normalize early model selection * fix(concentrate): validate and select rejected models safely * fix(concentrate): reset stale route state * fix(concentrate): preserve proxy profile capabilities
This commit is contained in:
@@ -141,6 +141,7 @@ const PRESET_ORDER = [
|
||||
'Bankr',
|
||||
'ClinePass',
|
||||
'Cloudflare Workers AI',
|
||||
'Concentrate',
|
||||
'DeepSeek',
|
||||
'Codex OAuth',
|
||||
'xAI OAuth (Grok)',
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import concentrate from './concentrate.js'
|
||||
|
||||
const mapModel = concentrate.catalog?.discovery?.mapModel
|
||||
|
||||
describe('concentrate gateway', () => {
|
||||
test('uses dynamic discovery and supports fallback auth', () => {
|
||||
expect(concentrate.id).toBe('concentrate')
|
||||
expect(concentrate.label).toBe('Concentrate')
|
||||
expect(concentrate.category).toBe('aggregating')
|
||||
expect(concentrate.defaultBaseUrl).toBe('https://api.concentrate.ai/v1')
|
||||
expect(concentrate.defaultModel).toBe('deepseek-v4-flash')
|
||||
expect(concentrate.supportsModelRouting).toBe(true)
|
||||
|
||||
expect(concentrate.setup.requiresAuth).toBe(true)
|
||||
expect(concentrate.setup.authMode).toBe('api-key')
|
||||
expect(concentrate.setup.credentialEnvVars).toEqual(['CONCENTRATE_API_KEY'])
|
||||
expect(concentrate.setup.dedicatedCredentialsOnly).toBeUndefined()
|
||||
|
||||
expect(concentrate.catalog?.source).toBe('dynamic')
|
||||
expect(concentrate.catalog?.discovery?.kind).toBe('openai-compatible')
|
||||
expect(concentrate.catalog?.discovery?.requiresAuth).toBe(false)
|
||||
|
||||
expect(concentrate.validation?.kind).toBe('credential-env')
|
||||
expect(
|
||||
(concentrate.validation as { credentialEnvVars: string[] }).credentialEnvVars,
|
||||
).toEqual([
|
||||
'CONCENTRATE_API_KEY',
|
||||
'OPENAI_API_KEYS',
|
||||
'OPENAI_API_KEY',
|
||||
])
|
||||
|
||||
expect(mapModel).toBeDefined()
|
||||
})
|
||||
|
||||
test('mapModel keeps chat models and drops non-chat ids', () => {
|
||||
if (!mapModel) throw new Error('mapModel missing')
|
||||
|
||||
expect(
|
||||
mapModel({
|
||||
id: 'deepseek-v4-flash',
|
||||
display_name: 'DeepSeek V4 Flash',
|
||||
owned_by: 'deepseek',
|
||||
max_input_tokens: 1_048_576,
|
||||
max_tokens: 393_216,
|
||||
}),
|
||||
).toEqual({
|
||||
id: 'deepseek-v4-flash',
|
||||
apiName: 'deepseek-v4-flash',
|
||||
label: 'DeepSeek V4 Flash',
|
||||
contextWindow: 1_048_576,
|
||||
maxOutputTokens: 393_216,
|
||||
})
|
||||
|
||||
expect(
|
||||
mapModel({
|
||||
id: 'claude-sonnet-5',
|
||||
display_name: 'Claude Sonnet 5',
|
||||
owned_by: 'anthropic',
|
||||
max_input_tokens: 200_000,
|
||||
max_tokens: 64_000,
|
||||
}),
|
||||
).toEqual({
|
||||
id: 'claude-sonnet-5',
|
||||
apiName: 'claude-sonnet-5',
|
||||
label: 'Claude Sonnet 5',
|
||||
contextWindow: 200_000,
|
||||
maxOutputTokens: 64_000,
|
||||
})
|
||||
|
||||
expect(mapModel({ id: 'redact-v1' })).toBeNull()
|
||||
expect(mapModel({ id: 'gpt-oss-safeguard-120b' })).toBeNull()
|
||||
expect(mapModel({ id: 'text-embedding-3-small' })).toBeNull()
|
||||
expect(mapModel({ id: 'whisper-1' })).toBeNull()
|
||||
expect(mapModel({})).toBeNull()
|
||||
expect(mapModel(null)).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,111 @@
|
||||
import { defineGateway } from '../define.js'
|
||||
|
||||
const NON_CHAT_MODEL_PATTERN =
|
||||
/redact|safeguard|embed|embedding|whisper|tts|dall-e|rerank|moderation|image-generation|video-generation|audio-generation/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
|
||||
}
|
||||
|
||||
function mapConcentrateModel(raw: unknown) {
|
||||
if (!isRecord(raw)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const id = getTrimmedString(raw, 'id')
|
||||
if (!id || NON_CHAT_MODEL_PATTERN.test(id)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const displayName = getTrimmedString(raw, 'display_name')
|
||||
const label = displayName || id
|
||||
const contextWindow = getPositiveInteger(raw.max_input_tokens)
|
||||
const maxOutputTokens = getPositiveInteger(raw.max_tokens)
|
||||
|
||||
return {
|
||||
id,
|
||||
apiName: id,
|
||||
label,
|
||||
...(contextWindow !== undefined ? { contextWindow } : {}),
|
||||
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
export default defineGateway({
|
||||
id: 'concentrate',
|
||||
label: 'Concentrate',
|
||||
category: 'aggregating',
|
||||
defaultBaseUrl: 'https://api.concentrate.ai/v1',
|
||||
defaultModel: 'deepseek-v4-flash',
|
||||
supportsModelRouting: true,
|
||||
setup: {
|
||||
requiresAuth: true,
|
||||
authMode: 'api-key',
|
||||
credentialEnvVars: ['CONCENTRATE_API_KEY'],
|
||||
},
|
||||
startup: {
|
||||
probeReadiness: 'openai-compatible-models',
|
||||
},
|
||||
transportConfig: {
|
||||
kind: 'openai-compatible',
|
||||
openaiShim: {
|
||||
supportsApiFormatSelection: false,
|
||||
supportsAuthHeaders: false,
|
||||
maxTokensField: 'max_tokens',
|
||||
},
|
||||
},
|
||||
preset: {
|
||||
id: 'concentrate',
|
||||
description: 'Concentrate AI — 150+ models via OpenAI-compatible API',
|
||||
vendorId: 'openai',
|
||||
apiKeyEnvVars: ['CONCENTRATE_API_KEY'],
|
||||
baseUrlEnvVars: ['CONCENTRATE_BASE_URL'],
|
||||
modelEnvVars: ['CONCENTRATE_MODEL', 'OPENAI_MODEL'],
|
||||
},
|
||||
validation: {
|
||||
kind: 'credential-env',
|
||||
routing: {
|
||||
matchDefaultBaseUrl: true,
|
||||
matchBaseUrlHosts: ['api.concentrate.ai'],
|
||||
},
|
||||
credentialEnvVars: [
|
||||
'CONCENTRATE_API_KEY',
|
||||
'OPENAI_API_KEYS',
|
||||
'OPENAI_API_KEY',
|
||||
],
|
||||
missingCredentialMessage:
|
||||
'Concentrate auth is required. Set CONCENTRATE_API_KEY or OPENAI_API_KEY.',
|
||||
},
|
||||
catalog: {
|
||||
source: 'dynamic',
|
||||
discovery: {
|
||||
kind: 'openai-compatible',
|
||||
requiresAuth: false,
|
||||
mapModel: mapConcentrateModel,
|
||||
},
|
||||
discoveryCacheTtl: '1d',
|
||||
discoveryRefreshMode: 'startup',
|
||||
allowManualRefresh: true,
|
||||
},
|
||||
usage: { supported: false },
|
||||
})
|
||||
@@ -27,6 +27,7 @@ import gatewayAzureOpenai from '../gateways/azure-openai.js'
|
||||
import gatewayBedrock from '../gateways/bedrock.js'
|
||||
import gatewayClinepass from '../gateways/clinepass.js'
|
||||
import gatewayCloudflare from '../gateways/cloudflare.js'
|
||||
import gatewayConcentrate from '../gateways/concentrate.js'
|
||||
import gatewayCustom from '../gateways/custom.js'
|
||||
import gatewayDashscopeCn from '../gateways/dashscope-cn.js'
|
||||
import gatewayDashscopeIntl from '../gateways/dashscope-intl.js'
|
||||
@@ -90,7 +91,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, 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, 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[])[]
|
||||
|
||||
@@ -176,6 +176,24 @@ export const PROVIDER_PRESET_MANIFEST = [
|
||||
"OPENAI_MODEL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"preset": "concentrate",
|
||||
"routeKind": "gateway",
|
||||
"routeId": "concentrate",
|
||||
"vendorId": "openai",
|
||||
"gatewayId": "concentrate",
|
||||
"description": "Concentrate AI — 150+ models via OpenAI-compatible API",
|
||||
"apiKeyEnvVars": [
|
||||
"CONCENTRATE_API_KEY"
|
||||
],
|
||||
"baseUrlEnvVars": [
|
||||
"CONCENTRATE_BASE_URL"
|
||||
],
|
||||
"modelEnvVars": [
|
||||
"CONCENTRATE_MODEL",
|
||||
"OPENAI_MODEL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"preset": "deepseek",
|
||||
"routeKind": "vendor",
|
||||
@@ -548,6 +566,7 @@ export const ORDERED_PROVIDER_PRESETS = [
|
||||
"bankr",
|
||||
"clinepass",
|
||||
"cloudflare",
|
||||
"concentrate",
|
||||
"deepseek",
|
||||
"fireworks",
|
||||
"gemini",
|
||||
|
||||
@@ -88,6 +88,7 @@ describe('loaded registry validation', () => {
|
||||
test('gateway defaultModel values are present unless provided outside curated catalog metadata', () => {
|
||||
const routesWithExternalDefaultModelSources = new Set([
|
||||
'atomic-chat',
|
||||
'concentrate',
|
||||
'custom',
|
||||
'lmstudio',
|
||||
'ollama',
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
isApismartBaseUrl,
|
||||
isCanonicalApismartInferenceBaseUrl,
|
||||
isCloudflareBaseUrl,
|
||||
isConcentrateBaseUrl,
|
||||
isLongcatBaseUrl,
|
||||
resolveActiveRouteIdFromEnv,
|
||||
resolveRouteCredentialValue,
|
||||
@@ -1009,3 +1010,136 @@ test('getRouteDefaultModel skips hidden and expired catalog entries in the fallb
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('isConcentrateBaseUrl matches the Concentrate API host', () => {
|
||||
expect(isConcentrateBaseUrl('https://api.concentrate.ai/v1')).toBe(true)
|
||||
expect(isConcentrateBaseUrl('https://api.concentrate.ai/v1/chat/completions')).toBe(true)
|
||||
expect(isConcentrateBaseUrl('http://api.concentrate.ai/v1')).toBe(false)
|
||||
expect(isConcentrateBaseUrl('https://api.concentrate.ai:8443/v1')).toBe(false)
|
||||
expect(isConcentrateBaseUrl('https://api.concentrate.ai.evil.test/v1')).toBe(false)
|
||||
expect(isConcentrateBaseUrl(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv treats Concentrate credential-only env as Concentrate', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'concentrate-key',
|
||||
}),
|
||||
).toBe('concentrate')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv uses CONCENTRATE_BASE_URL with a dedicated credential', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'concentrate-key',
|
||||
CONCENTRATE_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
}),
|
||||
).toBe('concentrate')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv uses CONCENTRATE_MODEL with a dedicated credential', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'concentrate-key',
|
||||
CONCENTRATE_MODEL: 'claude-sonnet-5',
|
||||
}),
|
||||
).toBe('concentrate')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv ignores placeholder Concentrate credentials', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'SUA_CHAVE',
|
||||
}),
|
||||
).not.toBe('concentrate')
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'null',
|
||||
}),
|
||||
).not.toBe('concentrate')
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'undefined',
|
||||
}),
|
||||
).not.toBe('concentrate')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv prefers Concentrate dedicated key over ambient OpenAI credentials', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'concentrate-key',
|
||||
OPENAI_API_KEY: 'ambient-openai-key',
|
||||
OPENAI_API_KEYS: 'ambient-openai-key-a,ambient-openai-key-b',
|
||||
}),
|
||||
).toBe('concentrate')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv does not infer Concentrate with a conflicting OpenAI base URL', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'concentrate-key',
|
||||
OPENAI_BASE_URL: 'https://api.openai.com/v1',
|
||||
}),
|
||||
).toBe('anthropic')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv does not infer Concentrate from a same-host noncanonical base URL', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CLAUDE_CODE_USE_OPENAI: '1',
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/staging/v1',
|
||||
}),
|
||||
).toBe('custom')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv keeps an explicit non-OpenAI provider over Concentrate key-only setup', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CONCENTRATE_API_KEY: 'concentrate-key',
|
||||
CLAUDE_CODE_USE_GEMINI: '1',
|
||||
}),
|
||||
).toBe('gemini')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv honors an explicit OpenAI opt-out over Concentrate', () => {
|
||||
expect(resolveActiveRouteIdFromEnv({ CLAUDE_CODE_USE_OPENAI: '0', CONCENTRATE_API_KEY: 'concentrate-key' })).not.toBe('concentrate')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv refines generic OpenAI profile by Concentrate base URL', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CLAUDE_CODE_USE_OPENAI: '1',
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
}),
|
||||
).toBe('concentrate')
|
||||
})
|
||||
|
||||
test('getRouteCredentialEnvVars supports documented generic OpenAI Concentrate setup', () => {
|
||||
expect(getRouteCredentialEnvVars('concentrate')).toEqual([
|
||||
'CONCENTRATE_API_KEY',
|
||||
'OPENAI_API_KEYS',
|
||||
'OPENAI_API_KEY',
|
||||
])
|
||||
expect(
|
||||
getRouteCredentialValue('concentrate', {
|
||||
OPENAI_API_KEY: 'generic-openai-key',
|
||||
}),
|
||||
).toBe('generic-openai-key')
|
||||
expect(
|
||||
getRouteCredentialValue('concentrate', {
|
||||
OPENAI_API_KEY: 'generic-openai-key',
|
||||
CONCENTRATE_API_KEY: 'concentrate-key',
|
||||
}),
|
||||
).toBe('concentrate-key')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv does not let a stale Concentrate model override generic OpenAI', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
CLAUDE_CODE_USE_OPENAI: '1',
|
||||
OPENAI_BASE_URL: 'https://api.openai.com/v1',
|
||||
OPENAI_API_KEY: 'generic-openai-key',
|
||||
CONCENTRATE_MODEL: 'deepseek-v4-flash-0731',
|
||||
}),
|
||||
).toBe('openai')
|
||||
})
|
||||
|
||||
@@ -230,7 +230,8 @@ function hasUsableEnvCredentialValue(
|
||||
envVar === 'OPENAI_API_KEYS' ||
|
||||
envVar === 'OPENAI_API_KEY' ||
|
||||
envVar === 'AIMLAPI_API_KEY' ||
|
||||
envVar === 'APISMART_API_KEY'
|
||||
envVar === 'APISMART_API_KEY' ||
|
||||
envVar === 'CONCENTRATE_API_KEY'
|
||||
) {
|
||||
return hasUsableOpenAICredential(value)
|
||||
}
|
||||
@@ -418,6 +419,70 @@ export function isClinePassBaseUrl(value: string | undefined): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function isConcentrateBaseUrl(value: string | undefined): boolean {
|
||||
const trimmed = value?.trim()
|
||||
if (!trimmed) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(trimmed)
|
||||
return (
|
||||
url.protocol === 'https:' &&
|
||||
!url.port &&
|
||||
!url.search &&
|
||||
!url.hash &&
|
||||
url.hostname.toLowerCase() === 'api.concentrate.ai'
|
||||
)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const CONCENTRATE_CANONICAL_INFERENCE_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
|
||||
export function isCanonicalConcentrateInferenceBaseUrl(
|
||||
value: string | undefined,
|
||||
): boolean {
|
||||
const trimmed = value?.trim()
|
||||
if (!trimmed) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const canonical = new URL(CONCENTRATE_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 {
|
||||
const openAIBaseUrl = processEnv.OPENAI_BASE_URL?.trim()
|
||||
if (isConcentrateBaseUrl(openAIBaseUrl)) {
|
||||
return openAIBaseUrl
|
||||
}
|
||||
|
||||
const openAIApiBase = processEnv.OPENAI_API_BASE?.trim()
|
||||
if (isConcentrateBaseUrl(openAIApiBase)) {
|
||||
return openAIApiBase
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Host-scoped ApiSmart route match. Used for env-only conflict detection and
|
||||
* base-URL route identity (including `/v1/chat/completions` path suffixes that
|
||||
@@ -886,6 +951,22 @@ export function hasApismartEnvOnlyProviderIntent(
|
||||
)
|
||||
}
|
||||
|
||||
export function hasConcentrateEnvOnlyProviderIntent(
|
||||
processEnv: NodeJS.ProcessEnv = process.env,
|
||||
): boolean {
|
||||
// The dedicated credential explicitly selects Concentrate. Base/model
|
||||
// overrides alone are configuration details, not route identity: treating
|
||||
// them as identity would let a stale optional setting override a valid
|
||||
// generic OpenAI configuration.
|
||||
return (
|
||||
hasUsableOpenAICredential(processEnv.CONCENTRATE_API_KEY) &&
|
||||
!hasConflictingOpenAIBaseUrlForRoute(processEnv, isConcentrateBaseUrl) &&
|
||||
!(processEnv.CLAUDE_CODE_USE_OPENAI !== undefined &&
|
||||
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_OPENAI)) &&
|
||||
hasNoExplicitNonOpenAIProvider(processEnv)
|
||||
)
|
||||
}
|
||||
|
||||
export function resolveEnvOnlyProviderRouteId(
|
||||
processEnv: NodeJS.ProcessEnv = process.env,
|
||||
):
|
||||
@@ -899,6 +980,7 @@ export function resolveEnvOnlyProviderRouteId(
|
||||
| 'longcat'
|
||||
| 'clinepass'
|
||||
| 'apismart'
|
||||
| 'concentrate'
|
||||
| null {
|
||||
if (
|
||||
hasMiniMaxRouteIntent(processEnv) &&
|
||||
@@ -947,6 +1029,10 @@ export function resolveEnvOnlyProviderRouteId(
|
||||
return 'apismart'
|
||||
}
|
||||
|
||||
if (hasConcentrateEnvOnlyProviderIntent(processEnv)) {
|
||||
return 'concentrate'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1022,6 +1108,15 @@ export function resolveRouteCredentialValue(
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
// Concentrate is the same: route identity is host-scoped, but the dedicated
|
||||
// credential is only valid for the documented /v1 inference endpoint.
|
||||
if (
|
||||
routeId === 'concentrate' &&
|
||||
options?.baseUrl !== undefined &&
|
||||
!isCanonicalConcentrateInferenceBaseUrl(options.baseUrl)
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return getRouteCredentialValue(routeId, processEnv)
|
||||
}
|
||||
@@ -1144,7 +1239,9 @@ export function resolveRouteIdFromBaseUrl(
|
||||
if (
|
||||
(route.id === 'cloudflare' && !isCloudflareBaseUrl(baseUrl)) ||
|
||||
(route.id === 'longcat' && !isLongcatBaseUrl(baseUrl)) ||
|
||||
(route.id === 'apismart' && !isApismartBaseUrl(baseUrl))
|
||||
(route.id === 'apismart' && !isApismartBaseUrl(baseUrl)) ||
|
||||
(route.id === 'concentrate' &&
|
||||
!isCanonicalConcentrateInferenceBaseUrl(baseUrl))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ const originalEnv = {
|
||||
LONGCAT_API_KEY: process.env.LONGCAT_API_KEY,
|
||||
AIMLAPI_API_KEY: process.env.AIMLAPI_API_KEY,
|
||||
APISMART_API_KEY: process.env.APISMART_API_KEY,
|
||||
CONCENTRATE_API_KEY: process.env.CONCENTRATE_API_KEY,
|
||||
CONCENTRATE_BASE_URL: process.env.CONCENTRATE_BASE_URL,
|
||||
CONCENTRATE_MODEL: process.env.CONCENTRATE_MODEL,
|
||||
NVIDIA_NIM: process.env.NVIDIA_NIM,
|
||||
NVIDIA_API_KEY: process.env.NVIDIA_API_KEY,
|
||||
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
||||
@@ -165,6 +168,9 @@ beforeEach(async () => {
|
||||
delete process.env.LONGCAT_API_KEY
|
||||
delete process.env.AIMLAPI_API_KEY
|
||||
delete process.env.APISMART_API_KEY
|
||||
delete process.env.CONCENTRATE_API_KEY
|
||||
delete process.env.CONCENTRATE_BASE_URL
|
||||
delete process.env.CONCENTRATE_MODEL
|
||||
delete process.env.OPENAI_AUTH_HEADER
|
||||
delete process.env.OPENAI_AUTH_SCHEME
|
||||
delete process.env.OPENAI_AUTH_HEADER_VALUE
|
||||
@@ -215,6 +221,9 @@ afterEach(() => {
|
||||
restoreEnv('LONGCAT_API_KEY', originalEnv.LONGCAT_API_KEY)
|
||||
restoreEnv('AIMLAPI_API_KEY', originalEnv.AIMLAPI_API_KEY)
|
||||
restoreEnv('APISMART_API_KEY', originalEnv.APISMART_API_KEY)
|
||||
restoreEnv('CONCENTRATE_API_KEY', originalEnv.CONCENTRATE_API_KEY)
|
||||
restoreEnv('CONCENTRATE_BASE_URL', originalEnv.CONCENTRATE_BASE_URL)
|
||||
restoreEnv('CONCENTRATE_MODEL', originalEnv.CONCENTRATE_MODEL)
|
||||
restoreEnv('NVIDIA_NIM', originalEnv.NVIDIA_NIM)
|
||||
restoreEnv('NVIDIA_API_KEY', originalEnv.NVIDIA_API_KEY)
|
||||
restoreEnv('ANTHROPIC_API_KEY', originalEnv.ANTHROPIC_API_KEY)
|
||||
@@ -771,6 +780,119 @@ test('env-only ApiSmart setup withholds its key from a noncanonical same-host UR
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
})
|
||||
|
||||
test('routes env-only Concentrate 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.CONCENTRATE_API_KEY = 'concentrate-test-key'
|
||||
process.env.CONCENTRATE_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
process.env.CONCENTRATE_MODEL = 'claude-sonnet-5'
|
||||
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-concentrate',
|
||||
model: 'claude-sonnet-5',
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'concentrate ok',
|
||||
},
|
||||
finish_reason: 'stop',
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 8,
|
||||
completion_tokens: 3,
|
||||
total_tokens: 11,
|
||||
},
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
)
|
||||
}) as FetchType
|
||||
|
||||
const client = (await getAnthropicClient({
|
||||
maxRetries: 0,
|
||||
model: 'claude-sonnet-5',
|
||||
})) as unknown as ShimClient
|
||||
|
||||
const response = await client.beta.messages.create({
|
||||
model: 'claude-sonnet-5',
|
||||
system: 'test system',
|
||||
messages: [{ role: 'user', content: 'hello' }],
|
||||
max_tokens: 64,
|
||||
stream: false,
|
||||
})
|
||||
|
||||
expect(capturedUrl).toBe('https://api.concentrate.ai/v1/chat/completions')
|
||||
expect(capturedHeaders?.get('authorization')).toBe('Bearer concentrate-test-key')
|
||||
expect(capturedHeaders?.get('x-proxy-auth')).toBeNull()
|
||||
expect(capturedBody?.model).toBe('claude-sonnet-5')
|
||||
expect(process.env.OPENAI_BASE_URL).toBe('https://api.concentrate.ai/v1')
|
||||
expect(process.env.OPENAI_API_KEY).toBe('concentrate-test-key')
|
||||
expect(response).toMatchObject({
|
||||
role: 'assistant',
|
||||
model: 'claude-sonnet-5',
|
||||
})
|
||||
})
|
||||
|
||||
test('env-only Concentrate setup withholds its key from a noncanonical same-host URL', async () => {
|
||||
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.CONCENTRATE_API_KEY = 'concentrate-test-key'
|
||||
process.env.OPENAI_BASE_URL = 'https://api.concentrate.ai/v1/models'
|
||||
|
||||
await getAnthropicClient({ maxRetries: 0, model: 'claude-sonnet-5' })
|
||||
|
||||
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
||||
expect(process.env.OPENAI_BASE_URL).toBe(
|
||||
'https://api.concentrate.ai/v1/models',
|
||||
)
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
})
|
||||
|
||||
test('generic OpenAI configuration for the canonical Concentrate endpoint retains its key', async () => {
|
||||
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.OPENAI_API_KEY = 'generic-openai-key'
|
||||
process.env.OPENAI_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
process.env.OPENAI_MODEL = 'claude-sonnet-5'
|
||||
|
||||
await getAnthropicClient({ maxRetries: 0, model: 'claude-sonnet-5' })
|
||||
|
||||
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBeUndefined()
|
||||
expect(process.env.OPENAI_BASE_URL).toBe('https://api.concentrate.ai/v1')
|
||||
expect(process.env.OPENAI_MODEL).toBe('claude-sonnet-5')
|
||||
expect(process.env.OPENAI_API_KEY).toBe('generic-openai-key')
|
||||
})
|
||||
|
||||
test('routes env-only AI/ML API requests through the OpenAI-compatible shim despite an ambient OpenAI key', async () => {
|
||||
let capturedUrl: string | undefined
|
||||
let capturedHeaders: Headers | undefined
|
||||
|
||||
+74
-44
@@ -44,6 +44,7 @@ import {
|
||||
getMiniMaxBaseUrlOverride,
|
||||
getNearaiBaseUrlOverride,
|
||||
isCanonicalApismartInferenceBaseUrl,
|
||||
isCanonicalConcentrateInferenceBaseUrl,
|
||||
getRouteDefaultBaseUrl,
|
||||
getRouteDefaultModel,
|
||||
getXaiBaseUrlOverride,
|
||||
@@ -411,6 +412,43 @@ function applyApismartEnvOnlyDefaults(): void {
|
||||
delete process.env.ANTHROPIC_CUSTOM_HEADERS
|
||||
}
|
||||
|
||||
function applyConcentrateEnvOnlyDefaults(): void {
|
||||
const baseUrlOverride =
|
||||
usableProviderConfigEnvValue(process.env.CONCENTRATE_BASE_URL) ||
|
||||
usableProviderConfigEnvValue(process.env.OPENAI_BASE_URL) ||
|
||||
usableProviderConfigEnvValue(process.env.OPENAI_API_BASE) ||
|
||||
undefined
|
||||
const modelOverride =
|
||||
usableProviderConfigEnvValue(process.env.CONCENTRATE_MODEL) ||
|
||||
usableProviderConfigEnvValue(process.env.OPENAI_MODEL) ||
|
||||
undefined
|
||||
const apiKey = process.env.CONCENTRATE_API_KEY
|
||||
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL =
|
||||
baseUrlOverride ?? getRouteDefaultBaseUrl('concentrate')
|
||||
process.env.OPENAI_MODEL = modelOverride ?? getRouteDefaultModel('concentrate')
|
||||
// A dedicated key explicitly selects Concentrate. Mirror it into
|
||||
// OPENAI_API_KEY for the shared transport and avoid forwarding a stale
|
||||
// generic credential. Generic OpenAI credentials remain supported when the
|
||||
// user explicitly configures the canonical Concentrate base URL without the
|
||||
// dedicated selection key.
|
||||
if (
|
||||
hasUsableOpenAICredential(apiKey) &&
|
||||
isCanonicalConcentrateInferenceBaseUrl(process.env.OPENAI_BASE_URL)
|
||||
) {
|
||||
process.env.OPENAI_API_KEY = apiKey
|
||||
} else {
|
||||
delete process.env.OPENAI_API_KEY
|
||||
}
|
||||
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(
|
||||
value: string | undefined,
|
||||
): string | undefined {
|
||||
@@ -512,6 +550,41 @@ export async function getAnthropicClient({
|
||||
? 'max'
|
||||
: standardEffortToOpenAI(appliedEffortLevel))
|
||||
: undefined
|
||||
// Normalize env-only routes before snapshotting custom headers. Dedicated
|
||||
// routes such as Concentrate deliberately clear inherited proxy headers;
|
||||
// doing that after getCustomHeaders() would leave a copied secret in the
|
||||
// request defaults.
|
||||
const envOnlyProviderRouteId = resolveEnvOnlyProviderRouteId(process.env)
|
||||
const useMiniMaxEnvOnlyProvider = shouldUseMiniMaxEnvOnlyProvider(
|
||||
model,
|
||||
envOnlyProviderRouteId,
|
||||
)
|
||||
const useXiaomiMimoEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'xiaomi-mimo' && !useMiniMaxEnvOnlyProvider
|
||||
const useXaiEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'xai' && !useMiniMaxEnvOnlyProvider
|
||||
const useNearaiEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'nearai' && !useMiniMaxEnvOnlyProvider
|
||||
const useFireworksEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'fireworks' && !useMiniMaxEnvOnlyProvider
|
||||
const useLongcatEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'longcat' && !useMiniMaxEnvOnlyProvider
|
||||
const useAimlapiEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'aimlapi' && !useMiniMaxEnvOnlyProvider
|
||||
const useApismartEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'apismart' && !useMiniMaxEnvOnlyProvider
|
||||
const useConcentrateEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'concentrate' && !useMiniMaxEnvOnlyProvider
|
||||
if (useMiniMaxEnvOnlyProvider) applyMiniMaxEnvOnlyDefaults(model)
|
||||
if (useXiaomiMimoEnvOnlyProvider) applyXiaomiMimoEnvOnlyDefaults()
|
||||
if (useXaiEnvOnlyProvider) applyXaiEnvOnlyDefaults()
|
||||
if (useNearaiEnvOnlyProvider) applyNearaiEnvOnlyDefaults()
|
||||
if (useFireworksEnvOnlyProvider) applyFireworksEnvOnlyDefaults()
|
||||
if (useLongcatEnvOnlyProvider) applyLongcatEnvOnlyDefaults()
|
||||
if (useAimlapiEnvOnlyProvider) applyAimlapiEnvOnlyDefaults()
|
||||
if (useApismartEnvOnlyProvider) applyApismartEnvOnlyDefaults()
|
||||
if (useConcentrateEnvOnlyProvider) applyConcentrateEnvOnlyDefaults()
|
||||
|
||||
const containerId = process.env.CLAUDE_CODE_CONTAINER_ID
|
||||
const remoteSessionId = process.env.CLAUDE_CODE_REMOTE_SESSION_ID
|
||||
const clientApp = process.env.CLAUDE_AGENT_SDK_CLIENT_APP
|
||||
@@ -542,50 +615,6 @@ export async function getAnthropicClient({
|
||||
defaultHeaders['x-anthropic-additional-protection'] = 'true'
|
||||
}
|
||||
|
||||
const envOnlyProviderRouteId = resolveEnvOnlyProviderRouteId(process.env)
|
||||
const useMiniMaxEnvOnlyProvider = shouldUseMiniMaxEnvOnlyProvider(
|
||||
model,
|
||||
envOnlyProviderRouteId,
|
||||
)
|
||||
const useXiaomiMimoEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'xiaomi-mimo' && !useMiniMaxEnvOnlyProvider
|
||||
const useXaiEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'xai' && !useMiniMaxEnvOnlyProvider
|
||||
const useNearaiEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'nearai' && !useMiniMaxEnvOnlyProvider
|
||||
const useFireworksEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'fireworks' && !useMiniMaxEnvOnlyProvider
|
||||
const useLongcatEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'longcat' && !useMiniMaxEnvOnlyProvider
|
||||
const useAimlapiEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'aimlapi' && !useMiniMaxEnvOnlyProvider
|
||||
const useApismartEnvOnlyProvider =
|
||||
envOnlyProviderRouteId === 'apismart' && !useMiniMaxEnvOnlyProvider
|
||||
if (useMiniMaxEnvOnlyProvider) {
|
||||
applyMiniMaxEnvOnlyDefaults(model)
|
||||
}
|
||||
if (useXiaomiMimoEnvOnlyProvider) {
|
||||
applyXiaomiMimoEnvOnlyDefaults()
|
||||
}
|
||||
if (useXaiEnvOnlyProvider) {
|
||||
applyXaiEnvOnlyDefaults()
|
||||
}
|
||||
if (useNearaiEnvOnlyProvider) {
|
||||
applyNearaiEnvOnlyDefaults()
|
||||
}
|
||||
if (useFireworksEnvOnlyProvider) {
|
||||
applyFireworksEnvOnlyDefaults()
|
||||
}
|
||||
if (useLongcatEnvOnlyProvider) {
|
||||
applyLongcatEnvOnlyDefaults()
|
||||
}
|
||||
if (useAimlapiEnvOnlyProvider) {
|
||||
applyAimlapiEnvOnlyDefaults()
|
||||
}
|
||||
if (useApismartEnvOnlyProvider) {
|
||||
applyApismartEnvOnlyDefaults()
|
||||
}
|
||||
|
||||
const apiProvider = getAPIProvider()
|
||||
const isFirstPartyBaseUrl = isFirstPartyAnthropicBaseUrl()
|
||||
const shouldUseFirstPartyAuth = shouldUseFirstPartyAnthropicAuthForProvider({
|
||||
@@ -690,6 +719,7 @@ export async function getAnthropicClient({
|
||||
useFireworksEnvOnlyProvider ||
|
||||
useAimlapiEnvOnlyProvider ||
|
||||
useApismartEnvOnlyProvider ||
|
||||
useConcentrateEnvOnlyProvider ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI) ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB) ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI) ||
|
||||
|
||||
@@ -50,6 +50,9 @@ const originalEnv = {
|
||||
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,
|
||||
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
|
||||
MIMO_API_KEY: process.env.MIMO_API_KEY,
|
||||
CONCENTRATE_API_KEY: process.env.CONCENTRATE_API_KEY,
|
||||
CONCENTRATE_BASE_URL: process.env.CONCENTRATE_BASE_URL,
|
||||
CONCENTRATE_MODEL: process.env.CONCENTRATE_MODEL,
|
||||
OPENGATEWAY_API_KEY: process.env.OPENGATEWAY_API_KEY,
|
||||
OPENGATEWAY_BASE_URL: process.env.OPENGATEWAY_BASE_URL,
|
||||
OPENCODE_API_KEY: process.env.OPENCODE_API_KEY,
|
||||
@@ -471,6 +474,9 @@ beforeEach(async () => {
|
||||
delete process.env.OPENROUTER_API_KEY
|
||||
delete process.env.DEEPSEEK_API_KEY
|
||||
delete process.env.MIMO_API_KEY
|
||||
delete process.env.CONCENTRATE_API_KEY
|
||||
delete process.env.CONCENTRATE_BASE_URL
|
||||
delete process.env.CONCENTRATE_MODEL
|
||||
delete process.env.OPENGATEWAY_API_KEY
|
||||
delete process.env.OPENGATEWAY_BASE_URL
|
||||
delete process.env.OPENCODE_API_KEY
|
||||
@@ -517,6 +523,9 @@ afterEach(() => {
|
||||
restoreEnv('OPENROUTER_API_KEY', originalEnv.OPENROUTER_API_KEY)
|
||||
restoreEnv('DEEPSEEK_API_KEY', originalEnv.DEEPSEEK_API_KEY)
|
||||
restoreEnv('MIMO_API_KEY', originalEnv.MIMO_API_KEY)
|
||||
restoreEnv('CONCENTRATE_API_KEY', originalEnv.CONCENTRATE_API_KEY)
|
||||
restoreEnv('CONCENTRATE_BASE_URL', originalEnv.CONCENTRATE_BASE_URL)
|
||||
restoreEnv('CONCENTRATE_MODEL', originalEnv.CONCENTRATE_MODEL)
|
||||
restoreEnv('OPENGATEWAY_API_KEY', originalEnv.OPENGATEWAY_API_KEY)
|
||||
restoreEnv('OPENGATEWAY_BASE_URL', originalEnv.OPENGATEWAY_BASE_URL)
|
||||
restoreEnv('OPENCODE_API_KEY', originalEnv.OPENCODE_API_KEY)
|
||||
@@ -547,6 +556,20 @@ test('gitlawb opengateway provider flag prefers OPENGATEWAY_API_KEY over generic
|
||||
expect(captured.authorization).toBe('Bearer fake-ogw-key')
|
||||
})
|
||||
|
||||
test('Concentrate selection prefers its dedicated key over a generic OPENAI_API_KEYS pool', async () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-key'
|
||||
process.env.OPENAI_API_KEYS = 'generic-openai-key-a,generic-openai-key-b'
|
||||
delete process.env.OPENAI_API_KEY
|
||||
|
||||
const result = applyProviderFlag('concentrate', [])
|
||||
expect(result.error).toBeUndefined()
|
||||
|
||||
const captured = await captureChatCompletionRequest()
|
||||
|
||||
expect(captured.url).toBe('https://api.concentrate.ai/v1/chat/completions')
|
||||
expect(captured.authorization).toBe('Bearer concentrate-key')
|
||||
})
|
||||
|
||||
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'
|
||||
|
||||
@@ -288,6 +288,7 @@ export async function executeOpenAIRequest(
|
||||
requestProcessEnv.MINIMAX_API_KEY,
|
||||
requestProcessEnv.ATLAS_CLOUD_API_KEY,
|
||||
requestProcessEnv.APISMART_API_KEY,
|
||||
requestProcessEnv.CONCENTRATE_API_KEY,
|
||||
requestProcessEnv.NEARAI_API_KEY,
|
||||
requestProcessEnv.FIREWORKS_API_KEY,
|
||||
requestProcessEnv.LONGCAT_API_KEY,
|
||||
|
||||
@@ -292,6 +292,25 @@ describe('loadEnvFile', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('loads documented Concentrate env-only provider setup values', () => {
|
||||
const filePath = writeTempEnvFile([
|
||||
'CONCENTRATE_API_KEY=concentrate-key',
|
||||
'CONCENTRATE_BASE_URL=https://api.concentrate.ai/v1',
|
||||
'CONCENTRATE_MODEL=claude-sonnet-5',
|
||||
].join('\n'))
|
||||
|
||||
const loaded = loadEnvFile(filePath)
|
||||
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBe('concentrate-key')
|
||||
expect(process.env.CONCENTRATE_BASE_URL).toBe('https://api.concentrate.ai/v1')
|
||||
expect(process.env.CONCENTRATE_MODEL).toBe('claude-sonnet-5')
|
||||
expect(loaded).toEqual({
|
||||
CONCENTRATE_API_KEY: 'concentrate-key',
|
||||
CONCENTRATE_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
CONCENTRATE_MODEL: 'claude-sonnet-5',
|
||||
})
|
||||
})
|
||||
|
||||
it('loads documented Azure OpenAI API version values', () => {
|
||||
const filePath = writeTempEnvFile(
|
||||
'AZURE_OPENAI_API_VERSION=2024-12-01-preview',
|
||||
|
||||
@@ -49,6 +49,9 @@ const ALLOWED_ENV_FILE_KEYS = new Set([
|
||||
'CODEX_AUTH_JSON_PATH',
|
||||
'CODEX_CREDENTIAL_SOURCE',
|
||||
'CODEX_HOME',
|
||||
'CONCENTRATE_API_KEY',
|
||||
'CONCENTRATE_BASE_URL',
|
||||
'CONCENTRATE_MODEL',
|
||||
'DASHSCOPE_API_KEY',
|
||||
'DEEPSEEK_API_KEY',
|
||||
'EXA_API_KEY',
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
clearPluginSettingsBase,
|
||||
resetSettingsCache,
|
||||
} from '../settings/settingsCache.js'
|
||||
let allowedModels: Set<string> | undefined
|
||||
|
||||
async function importFreshModelModule() {
|
||||
mock.restore()
|
||||
const getAPIProvider = () => {
|
||||
@@ -44,7 +46,7 @@ async function importFreshModelModule() {
|
||||
getAPIProvider() === 'firstParty' && !!process.env.ANTHROPIC_BASE_URL,
|
||||
}))
|
||||
mock.module('./modelAllowlist.js', () => ({
|
||||
isModelAllowed: () => true,
|
||||
isModelAllowed: (model: string) => allowedModels?.has(model) ?? true,
|
||||
}))
|
||||
const nonce = `${Date.now()}-${Math.random()}`
|
||||
return import(`./model.js?ts=${nonce}`)
|
||||
@@ -75,6 +77,9 @@ const SAVED_ENV = {
|
||||
ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL,
|
||||
ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL,
|
||||
MIMO_API_KEY: process.env.MIMO_API_KEY,
|
||||
CONCENTRATE_API_KEY: process.env.CONCENTRATE_API_KEY,
|
||||
CONCENTRATE_BASE_URL: process.env.CONCENTRATE_BASE_URL,
|
||||
CONCENTRATE_MODEL: process.env.CONCENTRATE_MODEL,
|
||||
OPENAI_MODEL: process.env.OPENAI_MODEL,
|
||||
OPENAI_BASE_URL: process.env.OPENAI_BASE_URL,
|
||||
CODEX_API_KEY: process.env.CODEX_API_KEY,
|
||||
@@ -122,6 +127,7 @@ beforeEach(async () => {
|
||||
resetStateForTests()
|
||||
resetSettingsCache()
|
||||
clearPluginSettingsBase()
|
||||
allowedModels = undefined
|
||||
delete process.env.CLAUDE_CODE_USE_OPENAI
|
||||
delete process.env.CLAUDE_CODE_USE_GEMINI
|
||||
delete process.env.CLAUDE_CODE_USE_GITHUB
|
||||
@@ -133,6 +139,9 @@ beforeEach(async () => {
|
||||
delete process.env.MINIMAX_API_KEY
|
||||
delete process.env.ANTHROPIC_MODEL
|
||||
delete process.env.MIMO_API_KEY
|
||||
delete process.env.CONCENTRATE_API_KEY
|
||||
delete process.env.CONCENTRATE_BASE_URL
|
||||
delete process.env.CONCENTRATE_MODEL
|
||||
delete process.env.OPENAI_MODEL
|
||||
delete process.env.OPENAI_BASE_URL
|
||||
delete process.env.CODEX_API_KEY
|
||||
@@ -340,6 +349,91 @@ test('getDefaultMainLoopModelSetting defaults MiniMax to M3', async () => {
|
||||
expect(getDefaultMainLoopModel()).toBe('MiniMax-M3')
|
||||
})
|
||||
|
||||
test('Concentrate selects its dedicated model before client normalization', async () => {
|
||||
// getMainLoopModel runs before getAnthropicClient mirrors Concentrate into
|
||||
// OPENAI_MODEL. A saved model must not win during that interval.
|
||||
saveGlobalConfig(current => ({ ...current, model: 'stale-other-provider-model' }))
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-test'
|
||||
process.env.CONCENTRATE_MODEL = 'claude-sonnet-5'
|
||||
|
||||
const {
|
||||
getDefaultMainLoopModelSetting,
|
||||
getMainLoopModel,
|
||||
getUserSpecifiedModelSetting,
|
||||
} = await importFreshModelModule()
|
||||
expect(getUserSpecifiedModelSetting()).toBe('claude-sonnet-5')
|
||||
expect(getDefaultMainLoopModelSetting()).toBe('claude-sonnet-5')
|
||||
expect(getMainLoopModel()).toBe('claude-sonnet-5')
|
||||
})
|
||||
|
||||
test('Concentrate honors its legacy OpenAI model fallback before client normalization', async () => {
|
||||
saveGlobalConfig(current => ({ ...current, model: 'stale-other-provider-model' }))
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-test'
|
||||
process.env.OPENAI_MODEL = 'legacy-concentrate-model'
|
||||
|
||||
const { getMainLoopModel, getUserSpecifiedModelSetting } =
|
||||
await importFreshModelModule()
|
||||
expect(getUserSpecifiedModelSetting()).toBe('legacy-concentrate-model')
|
||||
expect(getMainLoopModel()).toBe('legacy-concentrate-model')
|
||||
})
|
||||
|
||||
test('Concentrate skips a discovered-model rejection to its OpenAI fallback', async () => {
|
||||
allowedModels = new Set(['legacy-concentrate-model'])
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-test'
|
||||
process.env.CONCENTRATE_MODEL = 'rejected-concentrate-model'
|
||||
process.env.OPENAI_MODEL = 'legacy-concentrate-model'
|
||||
|
||||
const {
|
||||
getDefaultMainLoopModelSetting,
|
||||
getMainLoopModel,
|
||||
getUserSpecifiedModelSetting,
|
||||
} = await importFreshModelModule()
|
||||
expect(getUserSpecifiedModelSetting()).toBe('legacy-concentrate-model')
|
||||
expect(getDefaultMainLoopModelSetting()).toBe('legacy-concentrate-model')
|
||||
expect(getMainLoopModel()).toBe('legacy-concentrate-model')
|
||||
})
|
||||
|
||||
test('Concentrate falls back to its route default when configured models are rejected', async () => {
|
||||
allowedModels = new Set()
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-test'
|
||||
process.env.CONCENTRATE_MODEL = 'rejected-concentrate-model'
|
||||
process.env.OPENAI_MODEL = 'also-rejected-model'
|
||||
|
||||
const {
|
||||
getDefaultMainLoopModelSetting,
|
||||
getMainLoopModel,
|
||||
getUserSpecifiedModelSetting,
|
||||
} = await importFreshModelModule()
|
||||
expect(getUserSpecifiedModelSetting()).toBeUndefined()
|
||||
expect(getDefaultMainLoopModelSetting()).toBe('deepseek-v4-flash')
|
||||
expect(getMainLoopModel()).toBe('deepseek-v4-flash')
|
||||
})
|
||||
|
||||
test('Concentrate uses its descriptor default before client normalization', async () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-test'
|
||||
|
||||
const { getDefaultMainLoopModelSetting } = await importFreshModelModule()
|
||||
expect(getDefaultMainLoopModelSetting()).toBe('deepseek-v4-flash')
|
||||
})
|
||||
|
||||
test.each(['null', 'undefined', ' '])(
|
||||
'Concentrate ignores unusable dedicated model value %p before client normalization',
|
||||
async value => {
|
||||
saveGlobalConfig(current => ({ ...current, model: 'stale-other-provider-model' }))
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-test'
|
||||
process.env.CONCENTRATE_MODEL = value
|
||||
|
||||
const {
|
||||
getDefaultMainLoopModelSetting,
|
||||
getMainLoopModel,
|
||||
getUserSpecifiedModelSetting,
|
||||
} = await importFreshModelModule()
|
||||
expect(getUserSpecifiedModelSetting()).toBeUndefined()
|
||||
expect(getDefaultMainLoopModelSetting()).toBe('deepseek-v4-flash')
|
||||
expect(getMainLoopModel()).toBe('deepseek-v4-flash')
|
||||
},
|
||||
)
|
||||
|
||||
test('getDefaultMainLoopModelSetting uses the NVIDIA NIM route model', async () => {
|
||||
process.env.NVIDIA_NIM = '1'
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
|
||||
@@ -35,7 +35,7 @@ import { type ModelAlias, isModelAlias } from './aliases.js'
|
||||
import { capitalize } from '../stringUtils.js'
|
||||
import { DEFAULT_GEMINI_MODEL } from '../providerProfile.js'
|
||||
import { getAntModelOverrideConfig, resolveAntModel } from './antModels.js'
|
||||
import { getRouteDefaultModel } from '../../integrations/routeMetadata.js'
|
||||
import { getRouteDefaultModel, resolveActiveRouteIdFromEnv } from '../../integrations/routeMetadata.js'
|
||||
|
||||
export type ModelShortName = string
|
||||
export type ModelName = string
|
||||
@@ -51,6 +51,24 @@ function normalizeModelSetting(value: unknown): ModelName | ModelAlias | undefin
|
||||
return trimmed.length > 0 ? trimmed : undefined
|
||||
}
|
||||
|
||||
function getUsableProviderConfigModel(value: string | undefined): string | undefined {
|
||||
const normalized = normalizeModelSetting(value)
|
||||
if (!normalized) return undefined
|
||||
const lowercase = normalized.toLowerCase()
|
||||
return lowercase === 'undefined' || lowercase === 'null' ? undefined : normalized
|
||||
}
|
||||
|
||||
function getAllowedConcentrateConfigModel(): string | undefined {
|
||||
for (const value of [
|
||||
process.env.CONCENTRATE_MODEL,
|
||||
process.env.OPENAI_MODEL,
|
||||
]) {
|
||||
const model = getUsableProviderConfigModel(value)
|
||||
if (model && isModelAllowed(model)) return model
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function getSmallFastModel(): ModelName {
|
||||
if (process.env.ANTHROPIC_SMALL_FAST_MODEL) return process.env.ANTHROPIC_SMALL_FAST_MODEL
|
||||
if (isCustomAnthropicProvider()) {
|
||||
@@ -140,6 +158,7 @@ export function getUserSpecifiedModelSetting(): ModelSetting | undefined {
|
||||
// settings.model, so switching from (say) Moonshot to Codex kept firing
|
||||
// `kimi-k2.6` at the Codex endpoint and getting 400s.
|
||||
const provider = getAPIProvider()
|
||||
const activeRouteId = resolveActiveRouteIdFromEnv(process.env)
|
||||
const isOpenAIShimProvider =
|
||||
provider === 'openai' ||
|
||||
provider === 'codex' ||
|
||||
@@ -148,14 +167,19 @@ export function getUserSpecifiedModelSetting(): ModelSetting | undefined {
|
||||
provider === 'minimax' ||
|
||||
provider === 'xiaomi-mimo' ||
|
||||
provider === 'xai'
|
||||
specifiedModel =
|
||||
(provider === 'gemini' ? process.env.GEMINI_MODEL : undefined) ||
|
||||
(provider === 'mistral' ? process.env.MISTRAL_MODEL : undefined) ||
|
||||
(provider === 'minimax' ? getMiniMaxModelEnv() : undefined) ||
|
||||
(isOpenAIShimProvider ? process.env.OPENAI_MODEL : undefined) ||
|
||||
(provider === 'firstParty' ? process.env.ANTHROPIC_MODEL : undefined) ||
|
||||
setting ||
|
||||
undefined
|
||||
specifiedModel = activeRouteId === 'concentrate'
|
||||
// Concentrate is an env-only OpenAI-compatible route. Model selection
|
||||
// happens before the client applies its OpenAI-compatible defaults, so
|
||||
// consume its dedicated setting here rather than falling through to a
|
||||
// saved model from an unrelated provider.
|
||||
? getAllowedConcentrateConfigModel()
|
||||
: (provider === 'gemini' ? process.env.GEMINI_MODEL : undefined) ||
|
||||
(provider === 'mistral' ? process.env.MISTRAL_MODEL : undefined) ||
|
||||
(provider === 'minimax' ? getMiniMaxModelEnv() : undefined) ||
|
||||
(isOpenAIShimProvider ? process.env.OPENAI_MODEL : undefined) ||
|
||||
(provider === 'firstParty' ? process.env.ANTHROPIC_MODEL : undefined) ||
|
||||
setting ||
|
||||
undefined
|
||||
}
|
||||
|
||||
// Ignore the user-specified model if it's not in the availableModels allowlist.
|
||||
@@ -373,6 +397,13 @@ export function getRuntimeMainLoopModel(params: {
|
||||
* @returns The default model setting to use
|
||||
*/
|
||||
export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
|
||||
if (resolveActiveRouteIdFromEnv(process.env) === 'concentrate') {
|
||||
return (
|
||||
getAllowedConcentrateConfigModel() ||
|
||||
getRouteDefaultModel('concentrate') ||
|
||||
'deepseek-v4-flash'
|
||||
)
|
||||
}
|
||||
// Custom Anthropic-compatible endpoints intentionally retain the legacy
|
||||
// firstParty provider category, so prefer their explicitly configured model
|
||||
// before the subscription and PAYG defaults below.
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
applyModelFlagFromArgs,
|
||||
VALID_PROVIDERS,
|
||||
} from './providerFlag.js'
|
||||
import { resolveActiveRouteIdFromEnv } from '../integrations/routeMetadata.js'
|
||||
|
||||
const ENV_KEYS = [
|
||||
'CLAUDE_CODE_USE_OPENAI',
|
||||
@@ -42,6 +43,9 @@ const ENV_KEYS = [
|
||||
'ATLAS_CLOUD_API_KEY',
|
||||
'APISMART_API_KEY',
|
||||
'APISMART_MODEL',
|
||||
'CONCENTRATE_API_KEY',
|
||||
'CONCENTRATE_BASE_URL',
|
||||
'CONCENTRATE_MODEL',
|
||||
'LONGCAT_API_KEY',
|
||||
'OPENGATEWAY_API_KEY',
|
||||
'OPENGATEWAY_BASE_URL',
|
||||
@@ -93,6 +97,9 @@ const RESET_KEYS = [
|
||||
'ATLAS_CLOUD_API_KEY',
|
||||
'APISMART_API_KEY',
|
||||
'APISMART_MODEL',
|
||||
'CONCENTRATE_API_KEY',
|
||||
'CONCENTRATE_BASE_URL',
|
||||
'CONCENTRATE_MODEL',
|
||||
'LONGCAT_API_KEY',
|
||||
'OPENGATEWAY_API_KEY',
|
||||
'OPENGATEWAY_BASE_URL',
|
||||
@@ -201,6 +208,16 @@ describe('applyProviderFlag - anthropic', () => {
|
||||
expect(process.env.APISMART_API_KEY).toBeUndefined()
|
||||
expect(process.env.APISMART_MODEL).toBeUndefined()
|
||||
})
|
||||
|
||||
test('does not leave Concentrate env-only selection active', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-key'
|
||||
process.env.CONCENTRATE_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
process.env.CONCENTRATE_MODEL = 'claude-sonnet-5'
|
||||
applyProviderFlag('anthropic', [])
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_BASE_URL).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_MODEL).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyProviderFlag - custom Anthropic-compatible', () => {
|
||||
@@ -261,6 +278,21 @@ describe('applyProviderFlag - custom Anthropic-compatible', () => {
|
||||
expect(process.env.ANTHROPIC_MODEL).toBe('proxy-model')
|
||||
})
|
||||
|
||||
test('does not leave Concentrate env-only selection active', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://proxy.example/v1'
|
||||
process.env.ANTHROPIC_AUTH_TOKEN = 'proxy-token'
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-key'
|
||||
process.env.CONCENTRATE_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
process.env.CONCENTRATE_MODEL = 'claude-sonnet-5'
|
||||
|
||||
const result = applyProviderFlag('custom-anthropic', [])
|
||||
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_BASE_URL).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_MODEL).toBeUndefined()
|
||||
})
|
||||
|
||||
test('accepts native x-api-key authentication', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://proxy.example/v1'
|
||||
process.env.ANTHROPIC_API_KEY = 'stale-first-party-key'
|
||||
@@ -309,6 +341,16 @@ describe('applyProviderFlag - openai', () => {
|
||||
expect(process.env.APISMART_API_KEY).toBeUndefined()
|
||||
expect(process.env.APISMART_MODEL).toBeUndefined()
|
||||
})
|
||||
|
||||
test('does not leave Concentrate env-only selection active', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-key'
|
||||
process.env.CONCENTRATE_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
process.env.CONCENTRATE_MODEL = 'claude-sonnet-5'
|
||||
applyProviderFlag('openai', [])
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_BASE_URL).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_MODEL).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyProviderFlag - cloudflare', () => {
|
||||
@@ -1309,3 +1351,128 @@ describe('applyModelFlagFromArgs', () => {
|
||||
expect(process.env.OPENAI_MODEL).toBe('qwen2.5-coder:14b')
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyProviderFlag - concentrate', () => {
|
||||
test('sets Concentrate OpenAI-compatible defaults and mirrors CONCENTRATE_API_KEY', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-secret-key'
|
||||
|
||||
const result = applyProviderFlag('concentrate', [])
|
||||
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
||||
expect(process.env.OPENAI_BASE_URL).toBe('https://api.concentrate.ai/v1')
|
||||
expect(process.env.OPENAI_MODEL).toBe('deepseek-v4-flash')
|
||||
expect(process.env.OPENAI_API_KEY).toBe('concentrate-secret-key')
|
||||
})
|
||||
|
||||
test('uses CONCENTRATE_BASE_URL and CONCENTRATE_MODEL from env', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-secret-key'
|
||||
process.env.CONCENTRATE_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
process.env.CONCENTRATE_MODEL = 'claude-sonnet-5'
|
||||
|
||||
applyProviderFlag('concentrate', [])
|
||||
|
||||
expect(process.env.OPENAI_BASE_URL).toBe('https://api.concentrate.ai/v1')
|
||||
expect(process.env.OPENAI_MODEL).toBe('claude-sonnet-5')
|
||||
})
|
||||
|
||||
test('does not mirror the dedicated key to a noncanonical Concentrate URL', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-secret-key'
|
||||
process.env.CONCENTRATE_BASE_URL = 'https://api.concentrate.ai/staging/v1'
|
||||
|
||||
applyProviderFlag('concentrate', [])
|
||||
|
||||
expect(process.env.OPENAI_BASE_URL).toBe('https://api.concentrate.ai/staging/v1')
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
})
|
||||
|
||||
test('explicit --model overrides CONCENTRATE_MODEL and OPENAI_MODEL', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-secret-key'
|
||||
process.env.CONCENTRATE_MODEL = 'claude-sonnet-5'
|
||||
|
||||
applyProviderFlag('concentrate', ['--model', 'deepseek-v4-pro-0731'])
|
||||
|
||||
expect(process.env.OPENAI_MODEL).toBe('deepseek-v4-pro-0731')
|
||||
expect(process.env.CONCENTRATE_MODEL).toBeUndefined()
|
||||
})
|
||||
|
||||
test('dedicated key overrides a lingering OPENAI_API_KEY from another provider', () => {
|
||||
process.env.OPENAI_API_KEY = 'existing-openai-key'
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-secret-key'
|
||||
|
||||
applyProviderFlag('concentrate', [])
|
||||
|
||||
expect(process.env.OPENAI_API_KEY).toBe('concentrate-secret-key')
|
||||
})
|
||||
|
||||
test('clears a stale OPENAI_API_KEY when no Concentrate key is set', () => {
|
||||
delete process.env.CONCENTRATE_API_KEY
|
||||
process.env.OPENAI_API_KEY = 'existing-openai-key'
|
||||
|
||||
applyProviderFlag('concentrate', [])
|
||||
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
})
|
||||
|
||||
test.each(['SUA_CHAVE', 'sua_chave', 'null', 'undefined', ' NULL '])(
|
||||
'does not mirror placeholder Concentrate credential %s',
|
||||
placeholder => {
|
||||
process.env.CONCENTRATE_API_KEY = placeholder
|
||||
|
||||
applyProviderFlag('concentrate', [])
|
||||
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
},
|
||||
)
|
||||
|
||||
test('clears a copied Concentrate key from OPENAI_API_KEY when switching to another provider', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-secret-key'
|
||||
process.env.OPENAI_API_KEY = 'concentrate-secret-key'
|
||||
|
||||
applyProviderFlag('openai', [])
|
||||
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
})
|
||||
|
||||
test('switching from Concentrate to Ollama replaces the route identity and credentials', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-secret-key'
|
||||
|
||||
applyProviderFlag('concentrate', [])
|
||||
applyProviderFlag('ollama', [])
|
||||
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_BASE_URL).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_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 Concentrate to OpenAI replaces the known gateway endpoint', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-secret-key'
|
||||
|
||||
applyProviderFlag('concentrate', [])
|
||||
applyProviderFlag('openai', [])
|
||||
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(process.env.OPENAI_BASE_URL).toBe('https://api.openai.com/v1')
|
||||
expect(resolveActiveRouteIdFromEnv(process.env)).toBe('openai')
|
||||
})
|
||||
|
||||
test('clears unsupported OpenAI shim settings from a previous route', () => {
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-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('concentrate', [])
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
+115
-20
@@ -29,7 +29,10 @@ import {
|
||||
resolveRouteIdFromBaseUrl,
|
||||
} from '../integrations/index.js'
|
||||
import { PRESET_VENDOR_MAP } from '../integrations/compatibility.js'
|
||||
import { isCanonicalApismartInferenceBaseUrl } from '../integrations/routeMetadata.js'
|
||||
import {
|
||||
isCanonicalApismartInferenceBaseUrl,
|
||||
isCanonicalConcentrateInferenceBaseUrl,
|
||||
} from '../integrations/routeMetadata.js'
|
||||
import { hasUsableOpenAICredential } from '../services/api/credentialPool.js'
|
||||
import { isFirstPartyAnthropicBaseUrlForEnv } from './anthropicBaseUrl.js'
|
||||
|
||||
@@ -203,7 +206,6 @@ function shouldReplaceStaleKnownBaseUrl(provider: string): boolean {
|
||||
|
||||
const targetRouteId = resolveProfileRoute(provider).routeId
|
||||
return (
|
||||
targetRouteId !== 'openai' &&
|
||||
targetRouteId !== 'custom' &&
|
||||
targetRouteId !== 'unknown-fallback' &&
|
||||
currentRouteId !== targetRouteId
|
||||
@@ -252,6 +254,12 @@ function clearUnsupportedOpenAIShimSettings(routeId: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function clearConcentrateProviderState(): void {
|
||||
delete process.env.CONCENTRATE_API_KEY
|
||||
delete process.env.CONCENTRATE_BASE_URL
|
||||
delete process.env.CONCENTRATE_MODEL
|
||||
}
|
||||
|
||||
function usableProviderModelEnvValue(
|
||||
value: string | undefined,
|
||||
): string | undefined {
|
||||
@@ -350,18 +358,21 @@ export function applyProviderFlag(
|
||||
: process.env.OPENAI_API_KEY !== undefined &&
|
||||
process.env.OPENAI_API_KEY === process.env.ATLAS_CLOUD_API_KEY
|
||||
? 'atlas-cloud'
|
||||
: process.env.OPENAI_API_KEY !== undefined &&
|
||||
process.env.OPENAI_API_KEY === process.env.APISMART_API_KEY
|
||||
? 'apismart'
|
||||
: process.env.OPENAI_API_KEY !== undefined &&
|
||||
process.env.OPENAI_API_KEY === process.env.NEARAI_API_KEY
|
||||
? 'nearai'
|
||||
: process.env.OPENAI_API_KEY !== undefined &&
|
||||
process.env.OPENAI_API_KEY === process.env.FIREWORKS_API_KEY
|
||||
? 'fireworks'
|
||||
: process.env.OPENAI_API_KEY !== undefined &&
|
||||
process.env.OPENAI_API_KEY === process.env.LONGCAT_API_KEY
|
||||
? 'longcat'
|
||||
process.env.OPENAI_API_KEY === process.env.APISMART_API_KEY
|
||||
? 'apismart'
|
||||
: 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.NEARAI_API_KEY
|
||||
? 'nearai'
|
||||
: process.env.OPENAI_API_KEY !== undefined &&
|
||||
process.env.OPENAI_API_KEY === process.env.FIREWORKS_API_KEY
|
||||
? 'fireworks'
|
||||
: process.env.OPENAI_API_KEY !== undefined &&
|
||||
process.env.OPENAI_API_KEY === process.env.LONGCAT_API_KEY
|
||||
? 'longcat'
|
||||
: process.env.OPENAI_API_KEY !== undefined &&
|
||||
opengatewayApiKey !== undefined &&
|
||||
opengatewayApiKey.length > 0 &&
|
||||
@@ -455,9 +466,11 @@ export function applyProviderFlag(
|
||||
case 'openai':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
// An explicit generic OpenAI selection must not be reclassified as a
|
||||
// dedicated env-only gateway during client startup.
|
||||
// dedicated env-only gateway during client startup. Replace a previous
|
||||
// known gateway endpoint, but preserve a user-supplied custom endpoint.
|
||||
delete process.env.APISMART_API_KEY
|
||||
delete process.env.APISMART_MODEL
|
||||
applyOpenAIBaseUrlDefault(provider, defaultBaseUrl)
|
||||
if (model) process.env.OPENAI_MODEL = model
|
||||
break
|
||||
|
||||
@@ -486,7 +499,10 @@ export function applyProviderFlag(
|
||||
|
||||
case 'ollama':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL ??= defaultBaseUrl ?? 'http://localhost:11434/v1'
|
||||
applyOpenAIBaseUrlDefault(
|
||||
provider,
|
||||
defaultBaseUrl ?? 'http://localhost:11434/v1',
|
||||
)
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
process.env.OPENAI_API_KEY = 'ollama'
|
||||
}
|
||||
@@ -495,7 +511,10 @@ export function applyProviderFlag(
|
||||
|
||||
case 'nvidia-nim':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL ??= defaultBaseUrl ?? 'https://integrate.api.nvidia.com/v1'
|
||||
applyOpenAIBaseUrlDefault(
|
||||
provider,
|
||||
defaultBaseUrl ?? 'https://integrate.api.nvidia.com/v1',
|
||||
)
|
||||
process.env.NVIDIA_NIM = '1'
|
||||
if (process.env.NVIDIA_API_KEY && !process.env.OPENAI_API_KEY) {
|
||||
process.env.OPENAI_API_KEY = process.env.NVIDIA_API_KEY
|
||||
@@ -506,7 +525,10 @@ export function applyProviderFlag(
|
||||
|
||||
case 'bankr':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL ??= defaultBaseUrl ?? 'https://llm.bankr.bot/v1'
|
||||
applyOpenAIBaseUrlDefault(
|
||||
provider,
|
||||
defaultBaseUrl ?? 'https://llm.bankr.bot/v1',
|
||||
)
|
||||
process.env.OPENAI_MODEL ??= 'claude-opus-4.6'
|
||||
if (model) process.env.OPENAI_MODEL = model
|
||||
if (process.env.BNKR_API_KEY && !process.env.OPENAI_API_KEY) {
|
||||
@@ -567,7 +589,7 @@ export function applyProviderFlag(
|
||||
|
||||
case 'xai':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL ??= 'https://api.x.ai/v1'
|
||||
applyOpenAIBaseUrlDefault(provider, defaultBaseUrl ?? 'https://api.x.ai/v1')
|
||||
process.env.OPENAI_MODEL ??= defaultModel ?? 'grok-4.6'
|
||||
if (model) process.env.OPENAI_MODEL = model
|
||||
if (process.env.XAI_API_KEY && !process.env.OPENAI_API_KEY) {
|
||||
@@ -577,7 +599,10 @@ export function applyProviderFlag(
|
||||
|
||||
case 'xiaomi-mimo':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL ??= defaultBaseUrl ?? 'https://api.xiaomimimo.com/v1'
|
||||
applyOpenAIBaseUrlDefault(
|
||||
provider,
|
||||
defaultBaseUrl ?? 'https://api.xiaomimimo.com/v1',
|
||||
)
|
||||
process.env.OPENAI_MODEL ??= defaultModel ?? 'mimo-v2.5-pro'
|
||||
if (model) process.env.OPENAI_MODEL = model
|
||||
if (process.env.MIMO_API_KEY && !process.env.OPENAI_API_KEY) {
|
||||
@@ -600,7 +625,10 @@ export function applyProviderFlag(
|
||||
|
||||
case 'venice':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL ??= defaultBaseUrl ?? 'https://api.venice.ai/api/v1'
|
||||
applyOpenAIBaseUrlDefault(
|
||||
provider,
|
||||
defaultBaseUrl ?? 'https://api.venice.ai/api/v1',
|
||||
)
|
||||
process.env.OPENAI_MODEL ??= defaultModel ?? 'venice-uncensored'
|
||||
if (model) process.env.OPENAI_MODEL = model
|
||||
if (process.env.VENICE_API_KEY && !process.env.OPENAI_API_KEY) {
|
||||
@@ -670,6 +698,62 @@ export function applyProviderFlag(
|
||||
}
|
||||
break
|
||||
|
||||
case 'concentrate':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
// Concentrate uses the standard OpenAI-compatible wire contract with no
|
||||
// alternate API formats or custom auth headers.
|
||||
clearUnsupportedOpenAIShimSettings('concentrate')
|
||||
delete process.env.ANTHROPIC_CUSTOM_HEADERS
|
||||
{
|
||||
const baseUrlOverride = usableProviderModelEnvValue(
|
||||
process.env.CONCENTRATE_BASE_URL,
|
||||
)
|
||||
if (baseUrlOverride) {
|
||||
process.env.OPENAI_BASE_URL = baseUrlOverride
|
||||
} else {
|
||||
// An explicit Concentrate selection must not retain an unrelated
|
||||
// custom OpenAI endpoint from the preceding provider. Users can
|
||||
// deliberately select a different Concentrate endpoint through
|
||||
// CONCENTRATE_BASE_URL above.
|
||||
process.env.OPENAI_BASE_URL =
|
||||
defaultBaseUrl ?? 'https://api.concentrate.ai/v1'
|
||||
}
|
||||
}
|
||||
{
|
||||
const concentrateModel = usableProviderModelEnvValue(
|
||||
process.env.CONCENTRATE_MODEL,
|
||||
)
|
||||
if (concentrateModel) {
|
||||
process.env.OPENAI_MODEL = concentrateModel
|
||||
} else {
|
||||
process.env.OPENAI_MODEL ??=
|
||||
usableProviderModelEnvValue(process.env.OPENAI_MODEL) ||
|
||||
defaultModel ||
|
||||
'deepseek-v4-flash'
|
||||
}
|
||||
}
|
||||
if (model) {
|
||||
// Runtime model resolution gives CONCENTRATE_MODEL priority over the
|
||||
// shared shim setting. An explicit CLI selection must supersede that
|
||||
// ambient provider default all the way through client normalization.
|
||||
delete process.env.CONCENTRATE_MODEL
|
||||
process.env.OPENAI_MODEL = model
|
||||
}
|
||||
// This explicit dedicated selection mirrors CONCENTRATE_API_KEY into the
|
||||
// shared shim transport and clears a stale generic key. A generic
|
||||
// OpenAI-compatible Concentrate setup remains supported when selected
|
||||
// through OPENAI_BASE_URL instead. Do not mirror the dedicated key to a
|
||||
// same-host proxy or alternate path supplied through CONCENTRATE_BASE_URL.
|
||||
if (
|
||||
hasUsableOpenAICredential(process.env.CONCENTRATE_API_KEY) &&
|
||||
isCanonicalConcentrateInferenceBaseUrl(getConfiguredOpenAIBaseUrl())
|
||||
) {
|
||||
process.env.OPENAI_API_KEY = process.env.CONCENTRATE_API_KEY
|
||||
} else {
|
||||
delete process.env.OPENAI_API_KEY
|
||||
}
|
||||
break
|
||||
|
||||
case 'fireworks':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
applyOpenAIBaseUrlDefault(provider, defaultBaseUrl)
|
||||
@@ -760,5 +844,16 @@ export function applyProviderFlag(
|
||||
break
|
||||
}
|
||||
|
||||
// A provider flag selects a complete route for this process. Concentrate's
|
||||
// dedicated variables are another source of route identity, so leaving them
|
||||
// behind can re-select Concentrate after a later OpenAI-compatible provider
|
||||
// has applied its defaults. Keep their lifecycle at the selection boundary,
|
||||
// rather than relying on individual switch branches to remember cleanup.
|
||||
// This runs only after the selected branch succeeds, so an invalid
|
||||
// custom-anthropic request remains non-mutating.
|
||||
if (provider !== 'concentrate') {
|
||||
clearConcentrateProviderState()
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
buildAtomicChatProfileEnv,
|
||||
buildApismartProfileEnv,
|
||||
buildCompatibilityProcessEnv,
|
||||
buildConcentrateProfileEnv,
|
||||
buildCodexProfileEnv,
|
||||
buildGeminiProfileEnv,
|
||||
buildLaunchEnv,
|
||||
@@ -3012,3 +3013,250 @@ test('atomic-chat launch ignores mismatched persisted openai env', async () => {
|
||||
assert.equal(env.CODEX_API_KEY, undefined)
|
||||
assert.equal(env.CHATGPT_ACCOUNT_ID, undefined)
|
||||
})
|
||||
|
||||
test('openai launch preserves persisted Concentrate dedicated credentials across restart', async () => {
|
||||
const env = await buildLaunchEnv({
|
||||
profile: 'openai',
|
||||
persisted: profile('openai', {
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
OPENAI_API_KEY: 'concentrate-secret-key',
|
||||
CONCENTRATE_API_KEY: 'concentrate-secret-key',
|
||||
}),
|
||||
goal: 'coding',
|
||||
processEnv: {},
|
||||
})
|
||||
|
||||
assert.equal(env.OPENAI_BASE_URL, 'https://api.concentrate.ai/v1')
|
||||
assert.equal(env.OPENAI_MODEL, 'deepseek-v4-flash-0731')
|
||||
assert.equal(env.OPENAI_API_KEY, 'concentrate-secret-key')
|
||||
assert.equal(env.CONCENTRATE_API_KEY, 'concentrate-secret-key')
|
||||
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'concentrate')
|
||||
})
|
||||
|
||||
test('legacy generic OpenAI Concentrate profiles keep their credential generic', async () => {
|
||||
const env = await buildLaunchEnv({
|
||||
profile: 'openai',
|
||||
persisted: profile('openai', {
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
OPENAI_API_KEY: 'concentrate-secret-key',
|
||||
}),
|
||||
goal: 'coding',
|
||||
processEnv: {},
|
||||
})
|
||||
|
||||
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'concentrate')
|
||||
assert.equal(env.OPENAI_API_KEY, 'concentrate-secret-key')
|
||||
assert.equal(env.CONCENTRATE_API_KEY, undefined)
|
||||
assert.equal(
|
||||
resolveRouteCredentialValue({
|
||||
routeId: 'concentrate',
|
||||
processEnv: env,
|
||||
baseUrl: env.OPENAI_BASE_URL,
|
||||
}),
|
||||
'concentrate-secret-key',
|
||||
)
|
||||
})
|
||||
|
||||
test('openai launch keeps a generic Concentrate credential generic', async () => {
|
||||
const env = await buildLaunchEnv({
|
||||
profile: 'openai',
|
||||
persisted: profile('openai', {
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'concentrate',
|
||||
}),
|
||||
goal: 'coding',
|
||||
processEnv: {
|
||||
OPENAI_API_KEY: 'generic-openai-key',
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(env.CONCENTRATE_API_KEY, undefined)
|
||||
assert.equal(
|
||||
resolveRouteCredentialValue({
|
||||
routeId: 'concentrate',
|
||||
processEnv: env,
|
||||
baseUrl: env.OPENAI_BASE_URL,
|
||||
}),
|
||||
'generic-openai-key',
|
||||
)
|
||||
})
|
||||
|
||||
test('generic Concentrate profile lets a rotated OpenAI key override its saved key', async () => {
|
||||
const env = await buildLaunchEnv({
|
||||
profile: 'openai',
|
||||
persisted: profile('openai', {
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
OPENAI_API_KEY: 'saved-generic-key',
|
||||
}),
|
||||
goal: 'coding',
|
||||
processEnv: {
|
||||
OPENAI_API_KEY: 'rotated-generic-key',
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(env.OPENAI_API_KEY, 'rotated-generic-key')
|
||||
assert.equal(env.CONCENTRATE_API_KEY, undefined)
|
||||
assert.equal(
|
||||
resolveRouteCredentialValue({
|
||||
routeId: 'concentrate',
|
||||
processEnv: env,
|
||||
baseUrl: env.OPENAI_BASE_URL,
|
||||
}),
|
||||
'rotated-generic-key',
|
||||
)
|
||||
})
|
||||
|
||||
test('openai launch withholds ambient Concentrate credentials from a keyless proxy profile on restart', async () => {
|
||||
const env = await buildLaunchEnv({
|
||||
profile: 'openai',
|
||||
persisted: profile('openai', {
|
||||
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'concentrate',
|
||||
OPENAI_BASE_URL: 'https://proxy.example.com/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
}),
|
||||
goal: 'coding',
|
||||
processEnv: {
|
||||
OPENAI_BASE_URL: 'https://proxy.example.com/v1',
|
||||
OPENAI_API_KEY: 'ambient-concentrate-key',
|
||||
CONCENTRATE_API_KEY: 'ambient-concentrate-key',
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'concentrate')
|
||||
assert.equal(env.OPENAI_API_KEY, undefined)
|
||||
assert.equal(env.CONCENTRATE_API_KEY, undefined)
|
||||
|
||||
const canonical = await buildLaunchEnv({
|
||||
profile: 'openai',
|
||||
persisted: profile('openai', {
|
||||
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'concentrate',
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
}),
|
||||
goal: 'coding',
|
||||
processEnv: {
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
OPENAI_API_KEY: 'ambient-concentrate-key',
|
||||
CONCENTRATE_API_KEY: 'ambient-concentrate-key',
|
||||
},
|
||||
})
|
||||
assert.equal(canonical.OPENAI_API_KEY, 'ambient-concentrate-key')
|
||||
assert.equal(canonical.CONCENTRATE_API_KEY, 'ambient-concentrate-key')
|
||||
})
|
||||
|
||||
test('openai launch removes a legacy persisted Concentrate key from a noncanonical URL', async () => {
|
||||
const env = await buildLaunchEnv({
|
||||
profile: 'openai',
|
||||
persisted: profile('openai', {
|
||||
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'concentrate',
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/staging/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
CONCENTRATE_API_KEY: 'legacy-concentrate-key',
|
||||
}),
|
||||
goal: 'coding',
|
||||
processEnv: {},
|
||||
})
|
||||
|
||||
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'concentrate')
|
||||
assert.equal(env.OPENAI_API_KEY, undefined)
|
||||
assert.equal(env.CONCENTRATE_API_KEY, undefined)
|
||||
})
|
||||
|
||||
test('openai launch removes legacy generic credentials from a noncanonical Concentrate URL', async () => {
|
||||
for (const [credentialEnvVar, credential] of [
|
||||
['OPENAI_API_KEY', 'legacy-concentrate-key'],
|
||||
['OPENAI_API_KEYS', 'legacy-concentrate-key-1,legacy-concentrate-key-2'],
|
||||
] as const) {
|
||||
const env = await buildLaunchEnv({
|
||||
profile: 'openai',
|
||||
persisted: profile('openai', {
|
||||
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'concentrate',
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/staging/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
[credentialEnvVar]: credential,
|
||||
}),
|
||||
goal: 'coding',
|
||||
processEnv: {},
|
||||
})
|
||||
|
||||
assert.equal(env.OPENAI_API_KEY, undefined)
|
||||
assert.equal(env.OPENAI_API_KEYS, undefined)
|
||||
assert.equal(env.CONCENTRATE_API_KEY, undefined)
|
||||
}
|
||||
})
|
||||
|
||||
test('buildStartupEnvFromProfile preserves Concentrate env-only setup over a saved profile', async () => {
|
||||
const env = await buildStartupEnvFromProfile({
|
||||
persisted: profile('openai', {
|
||||
OPENAI_BASE_URL: 'https://api.openai.com/v1',
|
||||
OPENAI_MODEL: 'gpt-4o',
|
||||
OPENAI_API_KEY: 'sk-persisted',
|
||||
}),
|
||||
goal: 'balanced',
|
||||
processEnv: {
|
||||
CONCENTRATE_API_KEY: 'concentrate-env-only-key',
|
||||
CONCENTRATE_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
CONCENTRATE_MODEL: 'claude-sonnet-5',
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(env.CONCENTRATE_API_KEY, 'concentrate-env-only-key')
|
||||
assert.equal(env.CONCENTRATE_BASE_URL, 'https://api.concentrate.ai/v1')
|
||||
assert.equal(env.CONCENTRATE_MODEL, 'claude-sonnet-5')
|
||||
assert.equal(env.OPENAI_API_KEY, undefined)
|
||||
assert.equal(resolveActiveRouteIdFromEnv(env), 'concentrate')
|
||||
})
|
||||
|
||||
test('buildConcentrateProfileEnv prefers CONCENTRATE_MODEL over OPENAI_MODEL', () => {
|
||||
const env = buildConcentrateProfileEnv({
|
||||
apiKey: 'concentrate-secret-key',
|
||||
processEnv: {
|
||||
CONCENTRATE_MODEL: 'claude-sonnet-5',
|
||||
OPENAI_MODEL: 'gpt-4o',
|
||||
},
|
||||
})
|
||||
|
||||
assert.ok(env)
|
||||
assert.equal(env?.OPENAI_MODEL, 'claude-sonnet-5')
|
||||
assert.equal(env?.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'concentrate')
|
||||
})
|
||||
|
||||
test('buildConcentrateProfileEnv reads CONCENTRATE_BASE_URL override', () => {
|
||||
const env = buildConcentrateProfileEnv({
|
||||
apiKey: 'concentrate-secret-key',
|
||||
processEnv: {
|
||||
CONCENTRATE_BASE_URL: 'https://api.concentrate.ai/v1',
|
||||
},
|
||||
})
|
||||
|
||||
assert.ok(env)
|
||||
assert.equal(env?.OPENAI_BASE_URL, 'https://api.concentrate.ai/v1')
|
||||
})
|
||||
|
||||
test('buildConcentrateProfileEnv refuses to serialize its credential for a noncanonical URL', () => {
|
||||
const env = buildConcentrateProfileEnv({
|
||||
apiKey: 'concentrate-secret-key',
|
||||
processEnv: {
|
||||
CONCENTRATE_BASE_URL: 'https://api.concentrate.ai/staging/v1',
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(env, null)
|
||||
})
|
||||
|
||||
test('buildOpenAIProfileEnv does not stamp a generic key as Concentrate credentials on a noncanonical URL', () => {
|
||||
const env = buildOpenAIProfileEnv({
|
||||
goal: 'coding',
|
||||
baseUrl: 'https://api.concentrate.ai/staging/v1',
|
||||
apiKey: 'generic-proxy-key',
|
||||
processEnv: {},
|
||||
})
|
||||
|
||||
assert.ok(env)
|
||||
assert.equal(env?.OPENAI_API_KEY, 'generic-proxy-key')
|
||||
assert.equal(env?.CONCENTRATE_API_KEY, undefined)
|
||||
})
|
||||
|
||||
+121
-12
@@ -26,6 +26,7 @@ import {
|
||||
getRouteDefaultBaseUrl,
|
||||
getRouteDefaultModel,
|
||||
isCanonicalApismartInferenceBaseUrl,
|
||||
isCanonicalConcentrateInferenceBaseUrl,
|
||||
isLongcatBaseUrl,
|
||||
normalizeXiaomiMimoBaseUrl,
|
||||
resolveRouteCredentialValue,
|
||||
@@ -117,6 +118,9 @@ const PROFILE_ENV_KEYS = [
|
||||
'NEARAI_API_KEY',
|
||||
'FIREWORKS_API_KEY',
|
||||
'LONGCAT_API_KEY',
|
||||
'CONCENTRATE_API_KEY',
|
||||
'CONCENTRATE_BASE_URL',
|
||||
'CONCENTRATE_MODEL',
|
||||
'CLINE_API_KEY',
|
||||
'OPENCODE_API_KEY',
|
||||
'CLAUDE_CODE_PROVIDER_ROUTE_ID',
|
||||
@@ -204,6 +208,9 @@ export type ProfileEnv = {
|
||||
NEARAI_API_KEY?: string
|
||||
FIREWORKS_API_KEY?: string
|
||||
LONGCAT_API_KEY?: string
|
||||
CONCENTRATE_API_KEY?: string
|
||||
CONCENTRATE_BASE_URL?: string
|
||||
CONCENTRATE_MODEL?: string
|
||||
OPENCODE_API_KEY?: string
|
||||
CLOUDFLARE_API_TOKEN?: string
|
||||
CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS?: string
|
||||
@@ -689,6 +696,61 @@ export function buildApismartProfileEnv(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildConcentrateProfileEnv(options: {
|
||||
model?: string | null
|
||||
baseUrl?: string | null
|
||||
apiKey?: string | null
|
||||
processEnv?: NodeJS.ProcessEnv
|
||||
}): ProfileEnv | null {
|
||||
const processEnv = options.processEnv ?? process.env
|
||||
const key = sanitizeApiKey(options.apiKey ?? processEnv.CONCENTRATE_API_KEY)
|
||||
if (!key) {
|
||||
return null
|
||||
}
|
||||
|
||||
const defaultBaseUrl = getRouteDefaultBaseUrl('concentrate')
|
||||
const defaultModel = getRouteDefaultModel('concentrate')
|
||||
if (!defaultBaseUrl || !defaultModel) {
|
||||
throw new Error('Concentrate route defaults are missing from integration metadata.')
|
||||
}
|
||||
const secretSource: SecretValueSource = {
|
||||
OPENAI_API_KEY: key,
|
||||
CONCENTRATE_API_KEY: key,
|
||||
}
|
||||
const configuredBaseUrl =
|
||||
sanitizeProviderConfigValue(options.baseUrl, secretSource) ||
|
||||
sanitizeProviderConfigValue(processEnv.CONCENTRATE_BASE_URL, secretSource) ||
|
||||
sanitizeProviderConfigValue(processEnv.OPENAI_BASE_URL, secretSource)
|
||||
// The dedicated credential belongs only to Concentrate's documented
|
||||
// inference endpoint. Returning null lets the caller use its generic
|
||||
// profile path without serializing this secret for a proxy or alternate
|
||||
// same-host path.
|
||||
if (
|
||||
configuredBaseUrl &&
|
||||
!isCanonicalConcentrateInferenceBaseUrl(configuredBaseUrl)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
OPENAI_BASE_URL: configuredBaseUrl || defaultBaseUrl,
|
||||
OPENAI_MODEL:
|
||||
normalizeProfileModel(
|
||||
sanitizeProviderConfigValue(options.model, secretSource),
|
||||
) ||
|
||||
normalizeProfileModel(
|
||||
sanitizeProviderConfigValue(processEnv.CONCENTRATE_MODEL, secretSource),
|
||||
) ||
|
||||
normalizeProfileModel(
|
||||
sanitizeProviderConfigValue(processEnv.OPENAI_MODEL, secretSource),
|
||||
) ||
|
||||
defaultModel,
|
||||
OPENAI_API_KEY: key,
|
||||
CONCENTRATE_API_KEY: key,
|
||||
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'concentrate',
|
||||
}
|
||||
}
|
||||
|
||||
export function buildGeminiProfileEnv(options: {
|
||||
model?: string | null
|
||||
baseUrl?: string | null
|
||||
@@ -1411,7 +1473,8 @@ function hasConcreteProviderSelection(
|
||||
sanitizeApiKey(processEnv.APISMART_API_KEY) !== undefined ||
|
||||
sanitizeApiKey(processEnv.FIREWORKS_API_KEY) !== undefined ||
|
||||
sanitizeApiKey(processEnv.NEARAI_API_KEY) !== undefined ||
|
||||
sanitizeApiKey(processEnv.LONGCAT_API_KEY) !== undefined
|
||||
sanitizeApiKey(processEnv.LONGCAT_API_KEY) !== undefined ||
|
||||
sanitizeApiKey(processEnv.CONCENTRATE_API_KEY) !== undefined
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2080,14 +2143,25 @@ export async function buildLaunchEnv(options: {
|
||||
effectiveOpenAIRouteId === 'apismart' &&
|
||||
!!env.OPENAI_BASE_URL?.trim() &&
|
||||
!isCanonicalApismartInferenceBaseUrl(env.OPENAI_BASE_URL)
|
||||
const isNoncanonicalConcentrateLaunch =
|
||||
effectiveOpenAIRouteId === 'concentrate' &&
|
||||
!!env.OPENAI_BASE_URL?.trim() &&
|
||||
!isCanonicalConcentrateInferenceBaseUrl(env.OPENAI_BASE_URL)
|
||||
const isNoncanonicalDedicatedOpenAILaunch =
|
||||
isNoncanonicalAimlapiLaunch || isNoncanonicalApismartLaunch
|
||||
isNoncanonicalAimlapiLaunch || isNoncanonicalApismartLaunch || isNoncanonicalConcentrateLaunch
|
||||
if (isNoncanonicalDedicatedOpenAILaunch) {
|
||||
delete env.OPENAI_API_KEY
|
||||
delete env.OPENAI_API_KEYS
|
||||
const persistedCredential = resolveOpenAICredentialEnvSelection(persistedEnv)
|
||||
if (persistedCredential) {
|
||||
env[persistedCredential.envVar] = persistedCredential.value
|
||||
// AIMLAPI and ApiSmart may retain a user-configured proxy credential in
|
||||
// their persisted generic OpenAI field. Concentrate is different: its
|
||||
// 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) {
|
||||
const persistedCredential = resolveOpenAICredentialEnvSelection(persistedEnv)
|
||||
if (persistedCredential) {
|
||||
env[persistedCredential.envVar] = persistedCredential.value
|
||||
}
|
||||
}
|
||||
// Custom authentication is a second credential channel, not just transport
|
||||
// metadata: the OpenAI shim sends OPENAI_AUTH_HEADER_VALUE as the request
|
||||
@@ -2115,6 +2189,7 @@ export async function buildLaunchEnv(options: {
|
||||
for (const dedicatedKey of [
|
||||
'ATLAS_CLOUD_API_KEY',
|
||||
'APISMART_API_KEY',
|
||||
'CONCENTRATE_API_KEY',
|
||||
'NEARAI_API_KEY',
|
||||
'FIREWORKS_API_KEY',
|
||||
'LONGCAT_API_KEY',
|
||||
@@ -2133,6 +2208,9 @@ export async function buildLaunchEnv(options: {
|
||||
if (dedicatedKey === 'APISMART_API_KEY' && effectiveOpenAIRouteId !== 'apismart') {
|
||||
continue
|
||||
}
|
||||
if (dedicatedKey === 'CONCENTRATE_API_KEY' && effectiveOpenAIRouteId !== 'concentrate') {
|
||||
continue
|
||||
}
|
||||
if (dedicatedKey === 'NVIDIA_API_KEY' && effectiveOpenAIRouteId !== 'nvidia-nim') {
|
||||
continue
|
||||
}
|
||||
@@ -2156,8 +2234,20 @@ export async function buildLaunchEnv(options: {
|
||||
dedicatedKey === 'APISMART_API_KEY' &&
|
||||
!!dedicatedBaseUrl &&
|
||||
!isCanonicalApismartInferenceBaseUrl(dedicatedBaseUrl)
|
||||
const withholdAmbientConcentrateKey =
|
||||
dedicatedKey === 'CONCENTRATE_API_KEY' &&
|
||||
!!dedicatedBaseUrl &&
|
||||
!isCanonicalConcentrateInferenceBaseUrl(dedicatedBaseUrl)
|
||||
const withholdAmbientDedicatedKey =
|
||||
withholdAmbientAimlapiKey || withholdAmbientApismartKey
|
||||
withholdAmbientAimlapiKey || withholdAmbientApismartKey || withholdAmbientConcentrateKey
|
||||
// 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) {
|
||||
continue
|
||||
}
|
||||
// AIMLAPI accepts generic OpenAI credentials, but ApiSmart is
|
||||
// dedicatedCredentialsOnly. Never promote a shell OPENAI_API_KEY into the
|
||||
// dedicated credential on relaunch.
|
||||
@@ -2179,12 +2269,22 @@ export async function buildLaunchEnv(options: {
|
||||
persistedOpenAICredential?.kind === 'usable'
|
||||
? sanitizeApiKey(persistedOpenAICredential.value)
|
||||
: undefined
|
||||
const backfillLegacyConcentrateProfileKey =
|
||||
dedicatedKey === 'CONCENTRATE_API_KEY' &&
|
||||
effectiveOpenAIRouteId === 'concentrate' &&
|
||||
persistedOpenAIRouteId === 'concentrate' &&
|
||||
!!dedicatedBaseUrl &&
|
||||
isCanonicalConcentrateInferenceBaseUrl(dedicatedBaseUrl) &&
|
||||
persistedOpenAICredential?.kind === 'usable'
|
||||
? sanitizeApiKey(persistedOpenAICredential.value)
|
||||
: undefined
|
||||
const dedicatedValue = withholdAmbientDedicatedKey
|
||||
? sanitizeApiKey(persistedEnv[dedicatedKey])
|
||||
: backfillDedicatedFromOpenAI ||
|
||||
sanitizeApiKey(processEnv[dedicatedKey]) ||
|
||||
sanitizeApiKey(persistedEnv[dedicatedKey]) ||
|
||||
backfillLegacyApismartProfileKey
|
||||
backfillLegacyApismartProfileKey ||
|
||||
backfillLegacyConcentrateProfileKey
|
||||
if (dedicatedValue) {
|
||||
env[dedicatedKey] = dedicatedValue
|
||||
}
|
||||
@@ -2276,16 +2376,25 @@ export async function buildStartupEnvFromProfile(options?: {
|
||||
// If startup already has a concrete provider selection, keep trusting it.
|
||||
// This prevents legacy profiles or the fresh-install default from becoming
|
||||
// a silent third precedence layer over explicit env/flags.
|
||||
// A retained ApiSmart proxy profile carries route identity specifically to
|
||||
// withhold ambient dedicated credentials from its noncanonical endpoint.
|
||||
// Do not let an env-only key skip that guard; the persisted profile must be
|
||||
// applied first so buildLaunchEnv can preserve the proxy boundary.
|
||||
// Retained dedicated-provider proxy profiles carry route identity specifically
|
||||
// to withhold ambient credentials from their noncanonical endpoint. Do not
|
||||
// let an env-only key skip that guard; the persisted profile must be applied
|
||||
// first so buildLaunchEnv can preserve the credential boundary.
|
||||
const persistedApismartProxy =
|
||||
persisted?.profile === 'openai' &&
|
||||
persisted.env.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'apismart' &&
|
||||
!!persisted.env.OPENAI_BASE_URL?.trim() &&
|
||||
!isCanonicalApismartInferenceBaseUrl(persisted.env.OPENAI_BASE_URL)
|
||||
if (hasConcreteProviderSelection(processEnv) && !persistedApismartProxy) {
|
||||
const persistedConcentrateProxy =
|
||||
persisted?.profile === 'openai' &&
|
||||
persisted.env.CLAUDE_CODE_PROVIDER_ROUTE_ID === 'concentrate' &&
|
||||
!!persisted.env.OPENAI_BASE_URL?.trim() &&
|
||||
!isCanonicalConcentrateInferenceBaseUrl(persisted.env.OPENAI_BASE_URL)
|
||||
if (
|
||||
hasConcreteProviderSelection(processEnv) &&
|
||||
!persistedApismartProxy &&
|
||||
!persistedConcentrateProxy
|
||||
) {
|
||||
return processEnv
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,9 @@ const RESTORED_KEYS = [
|
||||
'ATLAS_CLOUD_API_KEY',
|
||||
'APISMART_API_KEY',
|
||||
'APISMART_MODEL',
|
||||
'CONCENTRATE_API_KEY',
|
||||
'CONCENTRATE_BASE_URL',
|
||||
'CONCENTRATE_MODEL',
|
||||
'CLINE_API_KEY',
|
||||
'HICAP_API_KEY',
|
||||
'CLOUDFLARE_API_TOKEN',
|
||||
@@ -278,6 +281,17 @@ function buildApismartProfile(overrides: Partial<ProviderProfile> = {}): Provide
|
||||
})
|
||||
}
|
||||
|
||||
function buildConcentrateProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile {
|
||||
return buildProfile({
|
||||
provider: 'concentrate',
|
||||
name: 'Concentrate',
|
||||
baseUrl: 'https://api.concentrate.ai/v1',
|
||||
model: 'deepseek-v4-flash-0731',
|
||||
apiKey: 'concentrate-test-key',
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
function buildClinePassProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile {
|
||||
return buildProfile({
|
||||
provider: 'clinepass',
|
||||
@@ -317,7 +331,7 @@ describe('applyProviderProfileToProcessEnv', () => {
|
||||
)
|
||||
|
||||
expect(process.env.OPENAI_AZURE_STYLE).toBe('1')
|
||||
})
|
||||
}, 20_000)
|
||||
|
||||
test('openai profile clears competing gemini/github flags', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
@@ -952,6 +966,200 @@ describe('applyProviderProfileToProcessEnv', () => {
|
||||
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('apismart')
|
||||
})
|
||||
|
||||
test('concentrate profile applies OpenAI-compatible env with CONCENTRATE_API_KEY mirror', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
process.env.CLAUDE_CODE_USE_GEMINI = '1'
|
||||
|
||||
applyProviderProfileToProcessEnv(buildConcentrateProfile())
|
||||
const { getAPIProvider: getFreshAPIProvider } =
|
||||
await importFreshProvidersModule()
|
||||
|
||||
expect(process.env.CLAUDE_CODE_USE_GEMINI).toBeUndefined()
|
||||
expect(String(process.env.CLAUDE_CODE_USE_OPENAI)).toBe('1')
|
||||
expect(process.env.OPENAI_BASE_URL).toBe('https://api.concentrate.ai/v1')
|
||||
expect(process.env.OPENAI_MODEL).toBe('deepseek-v4-flash-0731')
|
||||
expect(process.env.OPENAI_API_KEY).toBe('concentrate-test-key')
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBe('concentrate-test-key')
|
||||
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('concentrate')
|
||||
expect(getFreshAPIProvider()).toBe('openai')
|
||||
})
|
||||
|
||||
test('generic OpenAI profile at the canonical Concentrate endpoint keeps its credential generic', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
const { getProviderValidationError } = await import(
|
||||
`./providerValidation.js?ts=${Date.now()}-${Math.random()}`
|
||||
)
|
||||
|
||||
applyProviderProfileToProcessEnv(
|
||||
buildProfile({
|
||||
provider: 'openai',
|
||||
baseUrl: 'https://api.concentrate.ai/v1',
|
||||
model: 'deepseek-v4-flash-0731',
|
||||
apiKey: 'generic-concentrate-key',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(process.env.OPENAI_API_KEY).toBe('generic-concentrate-key')
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(await getProviderValidationError(process.env)).toBeNull()
|
||||
})
|
||||
|
||||
test('concentrate profile clears a stale route-specific model before applying its saved model', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
process.env.CONCENTRATE_MODEL = 'claude-sonnet-5'
|
||||
|
||||
applyProviderProfileToProcessEnv(buildConcentrateProfile())
|
||||
|
||||
expect(process.env.CONCENTRATE_MODEL).toBeUndefined()
|
||||
expect(process.env.OPENAI_MODEL).toBe('deepseek-v4-flash-0731')
|
||||
})
|
||||
|
||||
test('concentrate profile without a base URL retains its dedicated credential for the default route', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
|
||||
applyProviderProfileToProcessEnv(
|
||||
buildConcentrateProfile({ baseUrl: undefined }),
|
||||
)
|
||||
|
||||
expect(process.env.OPENAI_BASE_URL).toBe('https://api.concentrate.ai/v1')
|
||||
expect(process.env.OPENAI_API_KEY).toBe('concentrate-test-key')
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBe('concentrate-test-key')
|
||||
})
|
||||
|
||||
test('retargeted Concentrate profile withholds its dedicated credential', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
|
||||
applyProviderProfileToProcessEnv(
|
||||
buildConcentrateProfile({ 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.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('concentrate')
|
||||
})
|
||||
|
||||
test('retargeted Concentrate profiles retain generic proxy capabilities', async () => {
|
||||
const { addProviderProfile, applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
|
||||
const saved = addProviderProfile({
|
||||
provider: 'concentrate',
|
||||
name: 'Concentrate proxy',
|
||||
baseUrl: 'https://proxy.example/v1',
|
||||
model: 'proxy-model',
|
||||
apiKey: 'concentrate-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_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-auth-value')
|
||||
expect(process.env.ANTHROPIC_CUSTOM_HEADERS).toBe(
|
||||
'X-Proxy-Trace: enabled',
|
||||
)
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
})
|
||||
|
||||
test('keyless Concentrate profile resolves CONCENTRATE_API_KEY without persisting it', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
process.env.CONCENTRATE_API_KEY = 'ambient-concentrate-key'
|
||||
|
||||
applyProviderProfileToProcessEnv(
|
||||
buildConcentrateProfile({
|
||||
apiKey: undefined,
|
||||
baseUrl: 'https://api.concentrate.ai/v1',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(process.env.OPENAI_API_KEY).toBe('ambient-concentrate-key')
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBe('ambient-concentrate-key')
|
||||
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('concentrate')
|
||||
}, 20_000)
|
||||
|
||||
test('keyless canonical Concentrate profile never promotes a generic OpenAI key', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
process.env.OPENAI_API_KEY = 'generic-openai-key'
|
||||
|
||||
applyProviderProfileToProcessEnv(
|
||||
buildConcentrateProfile({
|
||||
apiKey: undefined,
|
||||
baseUrl: 'https://api.concentrate.ai/v1',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('concentrate')
|
||||
}, 20_000)
|
||||
|
||||
test('non-canonical Concentrate host path withholds the dedicated credential', async () => {
|
||||
const { applyProviderProfileToProcessEnv } =
|
||||
await importFreshProviderProfileModules()
|
||||
|
||||
applyProviderProfileToProcessEnv(
|
||||
buildConcentrateProfile({
|
||||
baseUrl: 'https://api.concentrate.ai/staging/v1',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(process.env.OPENAI_BASE_URL).toBe(
|
||||
'https://api.concentrate.ai/staging/v1',
|
||||
)
|
||||
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||
expect(process.env.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('concentrate')
|
||||
})
|
||||
|
||||
test.each(['SUA_CHAVE', 'sua_chave', 'null', 'undefined', ' NULL '])(
|
||||
'addProviderProfile drops placeholder Concentrate credential %s',
|
||||
async placeholder => {
|
||||
const { addProviderProfile, getProviderProfiles } =
|
||||
await importFreshProviderProfileModules()
|
||||
|
||||
saveMockGlobalConfig(current => ({
|
||||
...current,
|
||||
providerProfiles: [],
|
||||
activeProviderProfileId: undefined,
|
||||
}))
|
||||
|
||||
const saved = addProviderProfile({
|
||||
provider: 'concentrate',
|
||||
name: 'Concentrate',
|
||||
baseUrl: 'https://api.concentrate.ai/v1',
|
||||
model: 'deepseek-v4-flash-0731',
|
||||
apiKey: placeholder,
|
||||
})
|
||||
|
||||
expect(saved?.apiKey).toBeUndefined()
|
||||
expect(getProviderProfiles()[0]?.apiKey).toBeUndefined()
|
||||
},
|
||||
)
|
||||
|
||||
test.each(['SUA_CHAVE', 'sua_chave', 'null', 'undefined', ' NULL '])(
|
||||
'addProviderProfile drops placeholder ApiSmart credential %s',
|
||||
async placeholder => {
|
||||
@@ -3354,6 +3562,59 @@ describe('setActiveProviderProfile', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('retargeted Concentrate profiles keep route identity but persist without their dedicated credential', 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 concentrateProfile = buildConcentrateProfile({
|
||||
id: 'concentrate_proxy',
|
||||
baseUrl: 'https://api.concentrate.ai/staging/v1',
|
||||
})
|
||||
|
||||
saveMockGlobalConfig(current => ({
|
||||
...current,
|
||||
providerProfiles: [concentrateProfile],
|
||||
}))
|
||||
|
||||
const result = setActiveProviderProfile('concentrate_proxy', { configDir })
|
||||
const persisted = JSON.parse(
|
||||
readFileSync(join(configDir, '.openclaude-profile.json'), 'utf8'),
|
||||
)
|
||||
|
||||
expect(result?.id).toBe('concentrate_proxy')
|
||||
expect(persisted.profile).toBe('openai')
|
||||
expect(persisted.env).toEqual({
|
||||
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'concentrate',
|
||||
OPENAI_BASE_URL: 'https://api.concentrate.ai/staging/v1',
|
||||
OPENAI_MODEL: 'deepseek-v4-flash-0731',
|
||||
})
|
||||
|
||||
const { buildStartupEnvFromProfile } = await import(
|
||||
`./providerProfile.js?ts=${Date.now()}-${Math.random()}`
|
||||
)
|
||||
const startupEnv = await buildStartupEnvFromProfile({
|
||||
persisted,
|
||||
processEnv: {
|
||||
CONCENTRATE_API_KEY: 'ambient-concentrate-key',
|
||||
OPENAI_API_KEY: 'ambient-concentrate-key',
|
||||
},
|
||||
})
|
||||
|
||||
expect(startupEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('concentrate')
|
||||
expect(startupEnv.CONCENTRATE_API_KEY).toBeUndefined()
|
||||
expect(startupEnv.OPENAI_API_KEY).toBeUndefined()
|
||||
} 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-'))
|
||||
|
||||
+100
-15
@@ -18,6 +18,7 @@ import {
|
||||
createProfileFile,
|
||||
saveProfileFile,
|
||||
buildBedrockProfileEnv,
|
||||
buildConcentrateProfileEnv,
|
||||
buildGeminiProfileEnv,
|
||||
buildGithubProfileEnv,
|
||||
buildMiniMaxProfileEnv,
|
||||
@@ -55,6 +56,7 @@ import {
|
||||
isCloudflareBaseUrl,
|
||||
isClinePassBaseUrl,
|
||||
isCanonicalApismartInferenceBaseUrl,
|
||||
isCanonicalConcentrateInferenceBaseUrl,
|
||||
isFireworksBaseUrl,
|
||||
isLongcatBaseUrl,
|
||||
isNearaiBaseUrl,
|
||||
@@ -161,6 +163,18 @@ function isApismartProfile(profile: ProviderProfile): boolean {
|
||||
return !baseUrl || isCanonicalApismartInferenceBaseUrl(baseUrl)
|
||||
}
|
||||
|
||||
function isConcentrateProfile(profile: ProviderProfile): boolean {
|
||||
const { route } = resolveProfileCompatibility(profile.provider)
|
||||
if (route.routeId !== 'concentrate') {
|
||||
return false
|
||||
}
|
||||
const baseUrl = profile.baseUrl?.trim()
|
||||
// Missing base URL resolves to the Concentrate default, which is canonical.
|
||||
// Only the documented `/v1` inference URL may carry the dedicated key —
|
||||
// host-only or path-suffixed Concentrate URLs are treated as retargeted.
|
||||
return !baseUrl || isCanonicalConcentrateInferenceBaseUrl(baseUrl)
|
||||
}
|
||||
|
||||
function deriveGithubEnterpriseUrl(baseUrl: string | undefined): string | undefined {
|
||||
if (!baseUrl?.trim()) return undefined
|
||||
try {
|
||||
@@ -239,15 +253,19 @@ function resolveProfileCapabilityRouteId(
|
||||
return routeIdFromBaseUrl
|
||||
}
|
||||
|
||||
// Cloudflare and LongCat profiles retargeted away from their dedicated
|
||||
// endpoints run generically at runtime. Mirror that boundary here so
|
||||
// capability-driven surfaces are not stripped based on stale route ids.
|
||||
// Dedicated-route profiles retargeted away from their documented endpoints
|
||||
// run generically at runtime. Mirror that boundary here so capability-driven
|
||||
// surfaces are not stripped based on stale route ids.
|
||||
if (
|
||||
(providerRouteId === 'cloudflare' || providerRouteId === 'longcat') &&
|
||||
(providerRouteId === 'cloudflare' ||
|
||||
providerRouteId === 'longcat' ||
|
||||
providerRouteId === 'concentrate') &&
|
||||
baseUrl &&
|
||||
!(providerRouteId === 'cloudflare'
|
||||
? isCloudflareBaseUrl(baseUrl)
|
||||
: isLongcatBaseUrl(baseUrl))
|
||||
: providerRouteId === 'longcat'
|
||||
? isLongcatBaseUrl(baseUrl)
|
||||
: isCanonicalConcentrateInferenceBaseUrl(baseUrl))
|
||||
) {
|
||||
return 'custom'
|
||||
}
|
||||
@@ -972,7 +990,9 @@ export function applyProviderProfileToProcessEnv(
|
||||
? normalizeXiaomiMimoBaseUrl(profile.baseUrl) ?? profile.baseUrl
|
||||
: route.routeId === 'apismart' && !profile.baseUrl?.trim()
|
||||
? getRouteDefaultBaseUrl('apismart') ?? profile.baseUrl
|
||||
: profile.baseUrl
|
||||
: route.routeId === 'concentrate' && !profile.baseUrl?.trim()
|
||||
? getRouteDefaultBaseUrl('concentrate') ?? profile.baseUrl
|
||||
: profile.baseUrl
|
||||
const openAIProfileEnv: ProfileEnv = {
|
||||
OPENAI_BASE_URL: normalizedProfileBaseUrl,
|
||||
OPENAI_MODEL: primaryModel,
|
||||
@@ -1001,7 +1021,13 @@ export function applyProviderProfileToProcessEnv(
|
||||
|
||||
const withholdRetargetedApismartCredential =
|
||||
route.routeId === 'apismart' && !isApismartProfile(profile)
|
||||
if (profile.apiKey && !withholdRetargetedApismartCredential) {
|
||||
const withholdRetargetedConcentrateCredential =
|
||||
route.routeId === 'concentrate' && !isConcentrateProfile(profile)
|
||||
if (
|
||||
profile.apiKey &&
|
||||
!withholdRetargetedApismartCredential &&
|
||||
!withholdRetargetedConcentrateCredential
|
||||
) {
|
||||
openAIProfileEnv.OPENAI_API_KEY = profile.apiKey
|
||||
if (route.vendorId === 'minimax' || normalizedProfileBaseUrl.toLowerCase().includes('minimax')) {
|
||||
openAIProfileEnv.MINIMAX_API_KEY = profile.apiKey
|
||||
@@ -1038,6 +1064,9 @@ export function applyProviderProfileToProcessEnv(
|
||||
if (isApismartProfile(profile)) {
|
||||
openAIProfileEnv.APISMART_API_KEY = profile.apiKey
|
||||
}
|
||||
if (isConcentrateProfile(profile)) {
|
||||
openAIProfileEnv.CONCENTRATE_API_KEY = profile.apiKey
|
||||
}
|
||||
if (isClinePassProfile(profile)) {
|
||||
openAIProfileEnv.CLINE_API_KEY = profile.apiKey
|
||||
}
|
||||
@@ -1094,10 +1123,29 @@ export function applyProviderProfileToProcessEnv(
|
||||
// must not receive the ambient key.
|
||||
if (isApismartProfile(profile)) {
|
||||
const ambientApismartKey = sanitizeApiKey(process.env.APISMART_API_KEY)
|
||||
openAIProfileEnv.OPENAI_API_KEY =
|
||||
openAIProfileEnv.OPENAI_API_KEY ?? ambientApismartKey
|
||||
openAIProfileEnv.APISMART_API_KEY =
|
||||
openAIProfileEnv.APISMART_API_KEY ?? ambientApismartKey
|
||||
if (ambientApismartKey) {
|
||||
openAIProfileEnv.OPENAI_API_KEY =
|
||||
openAIProfileEnv.OPENAI_API_KEY ?? ambientApismartKey
|
||||
openAIProfileEnv.APISMART_API_KEY =
|
||||
openAIProfileEnv.APISMART_API_KEY ?? ambientApismartKey
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stamp dedicated Concentrate profile identity and mirror its credential so
|
||||
// the saved profile relaunches authenticated.
|
||||
if (route.routeId === 'concentrate') {
|
||||
openAIProfileEnv.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'concentrate'
|
||||
// Keyless canonical Concentrate profiles may resolve the ambient dedicated
|
||||
// credential the same way ApiSmart does.
|
||||
if (isConcentrateProfile(profile) && !profile.apiKey) {
|
||||
const ambientConcentrateKey = sanitizeApiKey(
|
||||
process.env.CONCENTRATE_API_KEY,
|
||||
)
|
||||
if (ambientConcentrateKey) {
|
||||
openAIProfileEnv.OPENAI_API_KEY =
|
||||
openAIProfileEnv.OPENAI_API_KEY ?? ambientConcentrateKey
|
||||
openAIProfileEnv.CONCENTRATE_API_KEY = ambientConcentrateKey
|
||||
}
|
||||
}
|
||||
}
|
||||
if (route.gatewayId === 'nvidia-nim') {
|
||||
@@ -1381,14 +1429,23 @@ function buildOpenAICompatibleStartupEnv(
|
||||
if (isCodexBaseUrl(activeProfile.baseUrl)) {
|
||||
return null
|
||||
}
|
||||
const activeProfileRouteId = resolveProfileRoute(activeProfile.provider).routeId
|
||||
const withholdRetargetedApismartCredential =
|
||||
resolveProfileRoute(activeProfile.provider).routeId === 'apismart' &&
|
||||
activeProfileRouteId === 'apismart' &&
|
||||
!isApismartProfile(activeProfile)
|
||||
const withholdRetargetedConcentrateCredential =
|
||||
activeProfileRouteId === 'concentrate' &&
|
||||
!isConcentrateProfile(activeProfile)
|
||||
const isAimlapiProfile =
|
||||
activeProfile.provider === 'aimlapi' ||
|
||||
resolveRouteIdFromBaseUrl(activeProfile.baseUrl) === 'aimlapi'
|
||||
const isConcentrateProfileFlag = isConcentrateProfile(activeProfile)
|
||||
|
||||
if (activeProfile.apiKey && !withholdRetargetedApismartCredential) {
|
||||
if (
|
||||
activeProfile.apiKey &&
|
||||
!withholdRetargetedApismartCredential &&
|
||||
!withholdRetargetedConcentrateCredential
|
||||
) {
|
||||
const strictEnv = buildOpenAIProfileEnv({
|
||||
goal: 'balanced',
|
||||
model: activeProfile.model,
|
||||
@@ -1416,6 +1473,9 @@ function buildOpenAICompatibleStartupEnv(
|
||||
if (isApismartProfile(activeProfile)) {
|
||||
strictEnv.APISMART_API_KEY = activeProfile.apiKey
|
||||
}
|
||||
if (isConcentrateProfileFlag) {
|
||||
strictEnv.CONCENTRATE_API_KEY = activeProfile.apiKey
|
||||
}
|
||||
if (isClinePassProfile(activeProfile)) {
|
||||
strictEnv.CLINE_API_KEY = activeProfile.apiKey
|
||||
}
|
||||
@@ -1465,10 +1525,19 @@ function buildOpenAICompatibleStartupEnv(
|
||||
// Preserve ApiSmart identity on retargeted/proxy startup envs so relaunch
|
||||
// withholding can refuse ambient dedicated credentials. Canonical profiles
|
||||
// already stamp this via buildApismartProfileEnv.
|
||||
if (resolveProfileRoute(activeProfile.provider).routeId === 'apismart') {
|
||||
if (activeProfileRouteId === 'apismart') {
|
||||
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'apismart'
|
||||
}
|
||||
if (activeProfile.apiKey && !withholdRetargetedApismartCredential) {
|
||||
// Preserve Concentrate identity for retargeted profiles too, so a later
|
||||
// launch can apply the same dedicated-credential boundary.
|
||||
if (activeProfileRouteId === 'concentrate') {
|
||||
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'concentrate'
|
||||
}
|
||||
if (
|
||||
activeProfile.apiKey &&
|
||||
!withholdRetargetedApismartCredential &&
|
||||
!withholdRetargetedConcentrateCredential
|
||||
) {
|
||||
env.OPENAI_API_KEY = activeProfile.apiKey
|
||||
if (activeProfile.baseUrl?.toLowerCase().includes('bankr')) {
|
||||
env.BNKR_API_KEY = activeProfile.apiKey
|
||||
@@ -1494,6 +1563,9 @@ function buildOpenAICompatibleStartupEnv(
|
||||
if (isApismartProfile(activeProfile)) {
|
||||
env.APISMART_API_KEY = activeProfile.apiKey
|
||||
}
|
||||
if (isConcentrateProfileFlag) {
|
||||
env.CONCENTRATE_API_KEY = activeProfile.apiKey
|
||||
}
|
||||
if (isClinePassProfile(activeProfile)) {
|
||||
env.CLINE_API_KEY = activeProfile.apiKey
|
||||
}
|
||||
@@ -1696,6 +1768,19 @@ function buildStartupProfileFromActiveProfile(
|
||||
: null
|
||||
}
|
||||
|
||||
if (route.routeId === 'concentrate' && isConcentrateProfile(activeProfile)) {
|
||||
const env =
|
||||
buildConcentrateProfileEnv({
|
||||
model: getPrimaryModel(activeProfile.model),
|
||||
baseUrl: activeProfile.baseUrl,
|
||||
apiKey: activeProfile.apiKey,
|
||||
processEnv: process.env,
|
||||
}) ?? null
|
||||
return env
|
||||
? { profile: 'openai', env: applySupportedProfileCustomHeaders(activeProfile, env) }
|
||||
: null
|
||||
}
|
||||
|
||||
if (route.vendorId === 'nearai') {
|
||||
const env = buildOpenAICompatibleStartupEnv(activeProfile)
|
||||
return env ? { profile: 'openai', env } : null
|
||||
|
||||
@@ -33,6 +33,9 @@ const ENV_KEYS = [
|
||||
'MINIMAX_API_KEY',
|
||||
'LONGCAT_API_KEY',
|
||||
'APISMART_API_KEY',
|
||||
'CONCENTRATE_API_KEY',
|
||||
'CONCENTRATE_BASE_URL',
|
||||
'CONCENTRATE_MODEL',
|
||||
'NVIDIA_API_KEY',
|
||||
'NVIDIA_NIM',
|
||||
'BNKR_API_KEY',
|
||||
@@ -211,6 +214,80 @@ test('noncanonical ApiSmart paths do not validate a dedicated credential', async
|
||||
)
|
||||
})
|
||||
|
||||
test('noncanonical Concentrate host paths fall back to generic OpenAI validation', async () => {
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL = 'https://api.concentrate.ai/staging/v1'
|
||||
process.env.OPENAI_API_KEY = 'generic-proxy-key'
|
||||
delete process.env.CONCENTRATE_API_KEY
|
||||
delete process.env.OPENAI_API_KEYS
|
||||
|
||||
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
|
||||
})
|
||||
|
||||
test('canonical Concentrate base supports the documented generic OpenAI credential', async () => {
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
process.env.OPENAI_API_KEY = 'generic-concentrate-key'
|
||||
delete process.env.CONCENTRATE_API_KEY
|
||||
|
||||
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
|
||||
})
|
||||
|
||||
test('noncanonical same-host base rejects a selected CONCENTRATE_API_KEY route', async () => {
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL = 'https://api.concentrate.ai/staging/v1'
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-key'
|
||||
delete process.env.OPENAI_API_KEY
|
||||
delete process.env.OPENAI_API_KEYS
|
||||
|
||||
await expect(getProviderValidationError(process.env)).resolves.toBe(
|
||||
'Concentrate credentials require the canonical https://api.concentrate.ai/v1 endpoint.',
|
||||
)
|
||||
})
|
||||
|
||||
test('noncanonical CONCENTRATE_BASE_URL is rejected instead of silently withholding its key', async () => {
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.CONCENTRATE_BASE_URL = 'https://api.concentrate.ai/staging/v1'
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-key'
|
||||
delete process.env.OPENAI_API_KEY
|
||||
delete process.env.OPENAI_API_KEYS
|
||||
|
||||
await expect(getProviderValidationError(process.env)).resolves.toBe(
|
||||
'Concentrate credentials require the canonical https://api.concentrate.ai/v1 endpoint.',
|
||||
)
|
||||
})
|
||||
|
||||
test('key-only noncanonical Concentrate setup validates before OpenAI mode is enabled', async () => {
|
||||
process.env.CONCENTRATE_BASE_URL = 'https://api.concentrate.ai/staging/v1'
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-key'
|
||||
delete process.env.OPENAI_API_KEY
|
||||
delete process.env.OPENAI_API_KEYS
|
||||
|
||||
await expect(getProviderValidationError(process.env)).resolves.toBe(
|
||||
'Concentrate credentials require the canonical https://api.concentrate.ai/v1 endpoint.',
|
||||
)
|
||||
})
|
||||
|
||||
test('Concentrate key-only setup validates before client defaults are applied', async () => {
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.CONCENTRATE_API_KEY = 'concentrate-key'
|
||||
|
||||
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
|
||||
})
|
||||
|
||||
test.each(['SUA_CHAVE', 'sua_chave', 'null', 'undefined', ' NULL '])(
|
||||
'Concentrate validation rejects placeholder CONCENTRATE_API_KEY %s',
|
||||
async placeholder => {
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL = 'https://api.concentrate.ai/v1'
|
||||
process.env.CONCENTRATE_API_KEY = placeholder
|
||||
|
||||
await expect(getProviderValidationError(process.env)).resolves.toBe(
|
||||
'Concentrate auth is required. Set CONCENTRATE_API_KEY or OPENAI_API_KEY.',
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
test.each(['SUA_CHAVE', 'sua_chave', 'null', 'undefined', ' NULL '])(
|
||||
'ApiSmart validation rejects placeholder APISMART_API_KEY %s',
|
||||
async placeholder => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
getRouteDescriptor,
|
||||
getRouteDefaultModel,
|
||||
isCanonicalApismartInferenceBaseUrl,
|
||||
isCanonicalConcentrateInferenceBaseUrl,
|
||||
isCloudflareBaseUrl,
|
||||
isLongcatBaseUrl,
|
||||
matchHostnameAgainstRouteHosts,
|
||||
@@ -139,7 +140,8 @@ function hasUsableCredentialEnvValue(
|
||||
envVar === 'OPENAI_API_KEYS' ||
|
||||
envVar === 'OPENAI_API_KEY' ||
|
||||
envVar === 'AIMLAPI_API_KEY' ||
|
||||
envVar === 'APISMART_API_KEY'
|
||||
envVar === 'APISMART_API_KEY' ||
|
||||
envVar === 'CONCENTRATE_API_KEY'
|
||||
) {
|
||||
return hasUsableOpenAICredential(value)
|
||||
}
|
||||
@@ -261,6 +263,14 @@ function getRuntimeValidationTarget(
|
||||
return enabledTarget
|
||||
}
|
||||
|
||||
// The documented CONCENTRATE_API_KEY-only setup is routed before the client
|
||||
// applies its default base URL. Select its descriptor directly so startup
|
||||
// validates the dedicated credential, including a noncanonical dedicated
|
||||
// base URL, instead of returning early for an unset OpenAI mode.
|
||||
if (resolveActiveRouteIdFromEnv(env) === 'concentrate') {
|
||||
return validationTargets.find(target => target.descriptor.id === 'concentrate')
|
||||
}
|
||||
|
||||
if (!useOpenAI) {
|
||||
return undefined
|
||||
}
|
||||
@@ -286,7 +296,9 @@ function getRuntimeValidationTarget(
|
||||
(target.descriptor.id === 'longcat' &&
|
||||
!isLongcatBaseUrl(request.baseUrl)) ||
|
||||
(target.descriptor.id === 'apismart' &&
|
||||
!isCanonicalApismartInferenceBaseUrl(request.baseUrl)))
|
||||
!isCanonicalApismartInferenceBaseUrl(request.baseUrl)) ||
|
||||
(target.descriptor.id === 'concentrate' &&
|
||||
!isCanonicalConcentrateInferenceBaseUrl(request.baseUrl)))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
@@ -389,6 +401,21 @@ async function getDescriptorValidationError(
|
||||
return null
|
||||
}
|
||||
|
||||
// An explicit Concentrate credential carries the dedicated contract. A
|
||||
// same-host proxy with only generic OpenAI variables is not selected as this
|
||||
// route, but once Concentrate is selected its base must be canonical.
|
||||
const concentrateBaseUrl =
|
||||
env.CONCENTRATE_BASE_URL?.trim() ||
|
||||
env.OPENAI_BASE_URL?.trim() ||
|
||||
env.OPENAI_API_BASE?.trim()
|
||||
if (
|
||||
target.descriptor.id === 'concentrate' &&
|
||||
concentrateBaseUrl &&
|
||||
!isCanonicalConcentrateInferenceBaseUrl(concentrateBaseUrl)
|
||||
) {
|
||||
return 'Concentrate credentials require the canonical https://api.concentrate.ai/v1 endpoint.'
|
||||
}
|
||||
|
||||
switch (validation.kind) {
|
||||
case 'credential-env':
|
||||
return getCredentialEnvValidationError(validation, env, options.request)
|
||||
|
||||
Reference in New Issue
Block a user