mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 02:34:15 -05:00
1357 lines
50 KiB
TypeScript
1357 lines
50 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
import {
|
|
acquireSharedMutationLock,
|
|
releaseSharedMutationLock,
|
|
} from '../test/sharedMutationLock.js'
|
|
import {
|
|
parseProviderFlag,
|
|
parseModelFlag,
|
|
applyProviderFlag,
|
|
applyProviderFlagFromArgs,
|
|
reapplyRememberedProviderFlag,
|
|
clearRememberedProviderFlagForTests,
|
|
applyModelFlagFromArgs,
|
|
VALID_PROVIDERS,
|
|
} from './providerFlag.js'
|
|
import { resolveEnvOnlyProviderRouteId } from '../integrations/routeMetadata.js'
|
|
|
|
const ENV_KEYS = [
|
|
'CLAUDE_CODE_USE_OPENAI',
|
|
'CLAUDE_CODE_USE_GEMINI',
|
|
'CLAUDE_CODE_USE_GITHUB',
|
|
'CLAUDE_CODE_USE_MISTRAL',
|
|
'CLAUDE_CODE_USE_BEDROCK',
|
|
'CLAUDE_CODE_USE_VERTEX',
|
|
'CLAUDE_CODE_USE_FOUNDRY',
|
|
'CLAUDE_CODE_PROVIDER_ROUTE_ID',
|
|
'OPENAI_BASE_URL',
|
|
'OPENAI_API_BASE',
|
|
'OPENAI_API_KEY',
|
|
'OPENAI_MODEL',
|
|
'OPENAI_API_FORMAT',
|
|
'OPENAI_AZURE_STYLE',
|
|
'OPENAI_AUTH_HEADER',
|
|
'OPENAI_AUTH_SCHEME',
|
|
'OPENAI_AUTH_HEADER_VALUE',
|
|
'GEMINI_MODEL',
|
|
'NVIDIA_API_KEY',
|
|
'NVIDIA_NIM',
|
|
'BNKR_API_KEY',
|
|
'XAI_API_KEY',
|
|
'MINIMAX_API_KEY',
|
|
'VENICE_API_KEY',
|
|
'MIMO_API_KEY',
|
|
'ATLAS_CLOUD_API_KEY',
|
|
'APISMART_API_KEY',
|
|
'APISMART_MODEL',
|
|
'LONGCAT_API_KEY',
|
|
'OPENGATEWAY_API_KEY',
|
|
'OPENGATEWAY_BASE_URL',
|
|
'CLOUDFLARE_API_TOKEN',
|
|
'MISTRAL_MODEL',
|
|
'ANTHROPIC_MODEL',
|
|
'ANTHROPIC_BASE_URL',
|
|
'ANTHROPIC_API_KEY',
|
|
'ANTHROPIC_AUTH_TOKEN',
|
|
'ANTHROPIC_CUSTOM_HEADERS',
|
|
'USER_TYPE',
|
|
]
|
|
|
|
const originalEnv: Record<string, string | undefined> = {}
|
|
|
|
beforeEach(async () => {
|
|
await acquireSharedMutationLock('utils/providerFlag.test.ts')
|
|
for (const key of ENV_KEYS) {
|
|
originalEnv[key] = process.env[key]
|
|
delete process.env[key]
|
|
}
|
|
})
|
|
|
|
const RESET_KEYS = [
|
|
'CLAUDE_CODE_USE_OPENAI',
|
|
'CLAUDE_CODE_USE_GEMINI',
|
|
'CLAUDE_CODE_USE_GITHUB',
|
|
'CLAUDE_CODE_USE_MISTRAL',
|
|
'CLAUDE_CODE_USE_BEDROCK',
|
|
'CLAUDE_CODE_USE_VERTEX',
|
|
'CLAUDE_CODE_USE_FOUNDRY',
|
|
'CLAUDE_CODE_PROVIDER_ROUTE_ID',
|
|
'OPENAI_BASE_URL',
|
|
'OPENAI_API_BASE',
|
|
'OPENAI_API_KEY',
|
|
'OPENAI_MODEL',
|
|
'OPENAI_API_FORMAT',
|
|
'OPENAI_AZURE_STYLE',
|
|
'OPENAI_AUTH_HEADER',
|
|
'OPENAI_AUTH_SCHEME',
|
|
'OPENAI_AUTH_HEADER_VALUE',
|
|
'GEMINI_MODEL',
|
|
'NVIDIA_API_KEY',
|
|
'NVIDIA_NIM',
|
|
'BNKR_API_KEY',
|
|
'XAI_API_KEY',
|
|
'MINIMAX_API_KEY',
|
|
'VENICE_API_KEY',
|
|
'MIMO_API_KEY',
|
|
'ATLAS_CLOUD_API_KEY',
|
|
'APISMART_API_KEY',
|
|
'APISMART_MODEL',
|
|
'LONGCAT_API_KEY',
|
|
'OPENGATEWAY_API_KEY',
|
|
'OPENGATEWAY_BASE_URL',
|
|
'CLOUDFLARE_API_TOKEN',
|
|
'MISTRAL_MODEL',
|
|
'ANTHROPIC_MODEL',
|
|
'ANTHROPIC_BASE_URL',
|
|
'ANTHROPIC_API_KEY',
|
|
'ANTHROPIC_AUTH_TOKEN',
|
|
'ANTHROPIC_CUSTOM_HEADERS',
|
|
'USER_TYPE',
|
|
] as const
|
|
|
|
beforeEach(() => {
|
|
clearRememberedProviderFlagForTests()
|
|
for (const key of RESET_KEYS) {
|
|
delete process.env[key]
|
|
}
|
|
})
|
|
|
|
afterEach(() => {
|
|
try {
|
|
clearRememberedProviderFlagForTests()
|
|
for (const key of ENV_KEYS) {
|
|
if (originalEnv[key] === undefined) {
|
|
delete process.env[key]
|
|
} else {
|
|
process.env[key] = originalEnv[key]
|
|
}
|
|
}
|
|
} finally {
|
|
releaseSharedMutationLock()
|
|
}
|
|
})
|
|
|
|
// --- parseProviderFlag ---
|
|
|
|
describe('parseProviderFlag', () => {
|
|
test('returns provider name when --provider flag present', () => {
|
|
expect(parseProviderFlag(['--provider', 'openai'])).toBe('openai')
|
|
})
|
|
|
|
test('returns provider name with --model alongside', () => {
|
|
expect(parseProviderFlag(['--provider', 'gemini', '--model', 'gemini-2.0-flash'])).toBe('gemini')
|
|
})
|
|
|
|
test('returns null when --provider flag absent', () => {
|
|
expect(parseProviderFlag(['--model', 'gpt-4o'])).toBeNull()
|
|
})
|
|
|
|
test('returns null for empty args', () => {
|
|
expect(parseProviderFlag([])).toBeNull()
|
|
})
|
|
|
|
test('returns null when --provider has no value', () => {
|
|
expect(parseProviderFlag(['--provider'])).toBeNull()
|
|
})
|
|
|
|
test('returns null when --provider value starts with --', () => {
|
|
expect(parseProviderFlag(['--provider', '--model'])).toBeNull()
|
|
})
|
|
})
|
|
|
|
// --- applyProviderFlag ---
|
|
|
|
describe('applyProviderFlag - anthropic', () => {
|
|
test('sets no env vars for anthropic (default)', () => {
|
|
const result = applyProviderFlag('anthropic', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_GEMINI).toBeUndefined()
|
|
})
|
|
|
|
test('clears a previously selected custom Anthropic endpoint', () => {
|
|
process.env.ANTHROPIC_BASE_URL = 'https://proxy.example/v1'
|
|
process.env.ANTHROPIC_MODEL = 'proxy-model'
|
|
process.env.ANTHROPIC_API_KEY = 'proxy-api-key'
|
|
process.env.ANTHROPIC_AUTH_TOKEN = 'proxy-token'
|
|
process.env.ANTHROPIC_CUSTOM_HEADERS = 'x-tenant: example'
|
|
|
|
const result = applyProviderFlag('anthropic', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_BASE_URL).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_MODEL).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_API_KEY).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_AUTH_TOKEN).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_CUSTOM_HEADERS).toBeUndefined()
|
|
})
|
|
|
|
test('preserves a first-party Anthropic API key', () => {
|
|
process.env.ANTHROPIC_API_KEY = 'first-party-key'
|
|
|
|
const result = applyProviderFlag('anthropic', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_API_KEY).toBe('first-party-key')
|
|
})
|
|
|
|
test('explicit Anthropic selection suppresses an ambient ApiSmart key', () => {
|
|
process.env.ANTHROPIC_API_KEY = 'first-party-key'
|
|
process.env.APISMART_API_KEY = 'ambient-apismart-key'
|
|
|
|
applyProviderFlag('anthropic', [])
|
|
|
|
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('anthropic')
|
|
expect(resolveEnvOnlyProviderRouteId(process.env)).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - custom Anthropic-compatible', () => {
|
|
test('requires a custom endpoint instead of sending its credential to Anthropic', () => {
|
|
process.env.ANTHROPIC_AUTH_TOKEN = 'proxy-token'
|
|
|
|
const result = applyProviderFlag('custom-anthropic', [])
|
|
|
|
expect(result.error).toContain('ANTHROPIC_BASE_URL')
|
|
})
|
|
|
|
test('rejects the first-party Anthropic endpoint instead of forwarding a custom credential', () => {
|
|
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com'
|
|
process.env.ANTHROPIC_AUTH_TOKEN = 'proxy-token'
|
|
|
|
const result = applyProviderFlag('custom-anthropic', [])
|
|
|
|
expect(result.error).toContain('non-Anthropic ANTHROPIC_BASE_URL')
|
|
})
|
|
|
|
test('rejects the internal first-party Anthropic staging endpoint', () => {
|
|
process.env.USER_TYPE = 'ant'
|
|
process.env.ANTHROPIC_BASE_URL = 'https://api-staging.anthropic.com'
|
|
process.env.ANTHROPIC_AUTH_TOKEN = 'proxy-token'
|
|
|
|
const result = applyProviderFlag('custom-anthropic', [])
|
|
|
|
expect(result.error).toContain('non-Anthropic ANTHROPIC_BASE_URL')
|
|
})
|
|
|
|
test('keeps native Anthropic routing and applies --model', () => {
|
|
process.env.ANTHROPIC_BASE_URL = 'https://proxy.example/v1'
|
|
process.env.ANTHROPIC_AUTH_TOKEN = 'proxy-token'
|
|
process.env.ANTHROPIC_API_KEY = 'stale-anthropic-key'
|
|
process.env.CLAUDE_CODE_USE_FOUNDRY = '1'
|
|
process.env.OPENAI_BASE_URL = 'https://stale.example/v1'
|
|
process.env.OPENAI_API_BASE = 'https://stale.example/v1'
|
|
process.env.OPENAI_API_FORMAT = 'responses'
|
|
process.env.OPENAI_AUTH_HEADER = 'Authorization'
|
|
process.env.OPENAI_AUTH_SCHEME = 'bearer'
|
|
process.env.OPENAI_AUTH_HEADER_VALUE = 'stale-token'
|
|
|
|
const result = applyProviderFlag('custom-anthropic', ['--model', 'proxy-model'])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_FOUNDRY).toBeUndefined()
|
|
expect(process.env.OPENAI_MODEL).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBeUndefined()
|
|
expect(process.env.OPENAI_API_BASE).toBeUndefined()
|
|
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_BASE_URL).toBe('https://proxy.example/v1')
|
|
expect(process.env.ANTHROPIC_AUTH_TOKEN).toBe('proxy-token')
|
|
expect(process.env.ANTHROPIC_API_KEY).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_MODEL).toBe('proxy-model')
|
|
})
|
|
|
|
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'
|
|
|
|
const result = applyProviderFlag('custom-anthropic', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_API_KEY).toBe('stale-first-party-key')
|
|
expect(process.env.ANTHROPIC_AUTH_TOKEN).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('VALID_PROVIDERS', () => {
|
|
test('includes descriptor-backed preset and route ids', () => {
|
|
expect(VALID_PROVIDERS).toContain('deepseek')
|
|
expect(VALID_PROVIDERS).toContain('moonshotai')
|
|
expect(VALID_PROVIDERS).toContain('openrouter')
|
|
expect(VALID_PROVIDERS).toContain('atomic-chat')
|
|
expect(VALID_PROVIDERS).toContain('zai')
|
|
expect(VALID_PROVIDERS).toContain('venice')
|
|
expect(VALID_PROVIDERS).toContain('xiaomi-mimo')
|
|
expect(VALID_PROVIDERS).toContain('xiaomi-mimo-token')
|
|
expect(VALID_PROVIDERS).toContain('longcat')
|
|
expect(VALID_PROVIDERS).toContain('custom-anthropic')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - openai', () => {
|
|
test('sets CLAUDE_CODE_USE_OPENAI=1', () => {
|
|
const result = applyProviderFlag('openai', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
})
|
|
|
|
test('sets OPENAI_MODEL when --model is provided', () => {
|
|
applyProviderFlag('openai', ['--model', 'gpt-4o'])
|
|
expect(process.env.OPENAI_MODEL).toBe('gpt-4o')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - cloudflare', () => {
|
|
test('does not seed the placeholder <ACCOUNT_ID> base URL', () => {
|
|
process.env.CLOUDFLARE_API_TOKEN = 'cf-token'
|
|
const result = applyProviderFlag('cloudflare', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
// The descriptor default contains an unresolved `<ACCOUNT_ID>` placeholder;
|
|
// it must not be installed verbatim as a broken endpoint.
|
|
expect(process.env.OPENAI_BASE_URL).toBeUndefined()
|
|
})
|
|
|
|
test('keeps a real account-scoped base URL the user already configured', () => {
|
|
process.env.CLOUDFLARE_API_TOKEN = 'cf-token'
|
|
process.env.OPENAI_BASE_URL =
|
|
'https://api.cloudflare.com/client/v4/accounts/real123/ai/v1'
|
|
applyProviderFlag('cloudflare', [])
|
|
expect(process.env.OPENAI_BASE_URL).toBe(
|
|
'https://api.cloudflare.com/client/v4/accounts/real123/ai/v1',
|
|
)
|
|
})
|
|
|
|
test('mirrors CLOUDFLARE_API_TOKEN into OPENAI_API_KEY once a real Cloudflare endpoint is configured', () => {
|
|
process.env.CLOUDFLARE_API_TOKEN = 'cf-token'
|
|
process.env.OPENAI_BASE_URL =
|
|
'https://api.cloudflare.com/client/v4/accounts/real123/ai/v1'
|
|
delete process.env.OPENAI_API_KEY
|
|
applyProviderFlag('cloudflare', [])
|
|
// The Cloudflare transport authenticates via the generic OpenAI-compatible
|
|
// header, so the dedicated token is copied into OPENAI_API_KEY — but only
|
|
// once the configured base URL resolves to api.cloudflare.com.
|
|
expect(String(process.env.OPENAI_API_KEY)).toBe('cf-token')
|
|
})
|
|
|
|
test('does NOT mirror the token while no Cloudflare endpoint is configured', () => {
|
|
// The descriptor default is an unresolved `<ACCOUNT_ID>` placeholder that is
|
|
// never seeded, so with OPENAI_BASE_URL unset the endpoint is unknown.
|
|
// Mirroring here would leave the token attached to no real host.
|
|
process.env.CLOUDFLARE_API_TOKEN = 'cf-token'
|
|
delete process.env.OPENAI_BASE_URL
|
|
delete process.env.OPENAI_API_KEY
|
|
applyProviderFlag('cloudflare', [])
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('does NOT leak the token onto a stale non-Cloudflare base URL', () => {
|
|
// Regression: a previous OpenAI-compatible provider left OPENAI_BASE_URL
|
|
// pointing elsewhere. The Cloudflare token must not be copied onto it.
|
|
process.env.CLOUDFLARE_API_TOKEN = 'cf-token'
|
|
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
|
|
delete process.env.OPENAI_API_KEY
|
|
applyProviderFlag('cloudflare', [])
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('does NOT mirror the token onto the shared AI Gateway host', () => {
|
|
// The AI Gateway host (gateway.ai.cloudflare.com) is not the Workers AI
|
|
// endpoint isCloudflareBaseUrl keys on (api.cloudflare.com), so the token
|
|
// must not be mirrored there either.
|
|
process.env.CLOUDFLARE_API_TOKEN = 'cf-token'
|
|
process.env.OPENAI_BASE_URL =
|
|
'https://gateway.ai.cloudflare.com/v1/acc/gw/workers-ai/v1'
|
|
delete process.env.OPENAI_API_KEY
|
|
applyProviderFlag('cloudflare', [])
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('does NOT mirror the token onto a general Cloudflare REST path on the same host', () => {
|
|
// api.cloudflare.com also serves the general Cloudflare REST API. A
|
|
// non-Workers-AI path (e.g. token verification) must NOT inherit Workers-AI
|
|
// credential mirroring just because it shares the host.
|
|
process.env.CLOUDFLARE_API_TOKEN = 'cf-token'
|
|
process.env.OPENAI_BASE_URL =
|
|
'https://api.cloudflare.com/client/v4/user/tokens/verify'
|
|
delete process.env.OPENAI_API_KEY
|
|
applyProviderFlag('cloudflare', [])
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('clears a stale OPENAI_API_KEY when no CLOUDFLARE_API_TOKEN is set', () => {
|
|
delete process.env.CLOUDFLARE_API_TOKEN
|
|
delete process.env.OPENAI_BASE_URL
|
|
process.env.OPENAI_API_KEY = 'leftover-from-another-provider'
|
|
applyProviderFlag('cloudflare', [])
|
|
// Without a Cloudflare token a lingering generic key must not be sent to
|
|
// Cloudflare; validation should report the missing token instead.
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('does NOT mirror the token onto the literal <ACCOUNT_ID> placeholder URL', () => {
|
|
// The placeholder shares the api.cloudflare.com host, so a host-only check
|
|
// would mirror the token onto an endpoint that cannot serve a request until
|
|
// the account id is filled in. Reject placeholder URLs before mirroring.
|
|
process.env.CLOUDFLARE_API_TOKEN = 'cf-token'
|
|
process.env.OPENAI_BASE_URL =
|
|
'https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/ai/v1'
|
|
delete process.env.OPENAI_API_KEY
|
|
applyProviderFlag('cloudflare', [])
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('preserves an existing OPENAI_API_KEY fallback on a real Cloudflare endpoint with no token', () => {
|
|
// The descriptor lists OPENAI_API_KEY as a documented compatibility
|
|
// fallback after CLOUDFLARE_API_TOKEN. A user who configured the generic
|
|
// key against a real Workers AI URL must stay authenticated even without a
|
|
// dedicated token.
|
|
delete process.env.CLOUDFLARE_API_TOKEN
|
|
process.env.OPENAI_BASE_URL =
|
|
'https://api.cloudflare.com/client/v4/accounts/real123/ai/v1'
|
|
process.env.OPENAI_API_KEY = 'compat-key'
|
|
applyProviderFlag('cloudflare', [])
|
|
expect(process.env.OPENAI_API_KEY).toBe('compat-key')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - gemini', () => {
|
|
test('sets CLAUDE_CODE_USE_GEMINI=1', () => {
|
|
const result = applyProviderFlag('gemini', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_GEMINI).toBe('1')
|
|
})
|
|
|
|
test('sets GEMINI_MODEL when --model is provided', () => {
|
|
applyProviderFlag('gemini', ['--model', 'gemini-2.0-flash'])
|
|
expect(process.env.GEMINI_MODEL).toBe('gemini-2.0-flash')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - github', () => {
|
|
test('sets CLAUDE_CODE_USE_GITHUB=1', () => {
|
|
const result = applyProviderFlag('github', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_GITHUB).toBe('1')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - bedrock', () => {
|
|
test('sets CLAUDE_CODE_USE_BEDROCK=1', () => {
|
|
const result = applyProviderFlag('bedrock', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_BEDROCK).toBe('1')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - vertex', () => {
|
|
test('sets CLAUDE_CODE_USE_VERTEX=1', () => {
|
|
const result = applyProviderFlag('vertex', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_VERTEX).toBe('1')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - ollama', () => {
|
|
test('sets CLAUDE_CODE_USE_OPENAI=1 with Ollama defaults when unset', () => {
|
|
delete process.env.OPENAI_BASE_URL
|
|
delete process.env.OPENAI_API_KEY
|
|
|
|
const result = applyProviderFlag('ollama', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL!).toBe('http://localhost:11434/v1')
|
|
expect(process.env.OPENAI_API_KEY!).toBe('ollama')
|
|
})
|
|
|
|
test('sets OPENAI_MODEL when --model is provided', () => {
|
|
applyProviderFlag('ollama', ['--model', 'llama3.2'])
|
|
expect(process.env.OPENAI_MODEL).toBe('llama3.2')
|
|
})
|
|
|
|
test('clears Azure-only routing mode', () => {
|
|
process.env.OPENAI_AZURE_STYLE = '1'
|
|
|
|
applyProviderFlag('ollama', [])
|
|
|
|
expect(process.env.OPENAI_AZURE_STYLE).toBeUndefined()
|
|
})
|
|
|
|
test('does not override existing OPENAI_BASE_URL when user set a custom one', () => {
|
|
process.env.OPENAI_BASE_URL = 'http://my-ollama:11434/v1'
|
|
applyProviderFlag('ollama', [])
|
|
expect(process.env.OPENAI_BASE_URL).toBe('http://my-ollama:11434/v1')
|
|
})
|
|
|
|
test('preserves explicit OPENAI_BASE_URL and OPENAI_API_KEY overrides', () => {
|
|
process.env.OPENAI_BASE_URL = 'http://remote-ollama.internal:11434/v1'
|
|
process.env.OPENAI_API_KEY = 'secret-token'
|
|
|
|
applyProviderFlag('ollama', [])
|
|
|
|
expect(process.env.OPENAI_BASE_URL).toBe('http://remote-ollama.internal:11434/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBe('secret-token')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - descriptor-backed openai-compatible routes', () => {
|
|
test('deepseek applies generic openai-compatible routing with descriptor defaults', () => {
|
|
const result = applyProviderFlag('deepseek', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.deepseek.com/v1')
|
|
expect(process.env.OPENAI_MODEL).toBe('deepseek-v4-pro')
|
|
})
|
|
|
|
test('openrouter applies gateway defaults from descriptors', () => {
|
|
const result = applyProviderFlag('openrouter', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
})
|
|
|
|
test('descriptor-backed provider selection preserves custom OPENAI_BASE_URL', () => {
|
|
process.env.OPENAI_BASE_URL = 'http://proxy.local:8080/v1'
|
|
|
|
const result = applyProviderFlag('openrouter', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('http://proxy.local:8080/v1')
|
|
})
|
|
|
|
test('descriptor-backed provider selection preserves custom OPENAI_API_BASE alias', () => {
|
|
process.env.OPENAI_API_BASE = 'http://proxy.local:8080/v1'
|
|
process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'
|
|
|
|
const result = applyProviderFlag('gitlawb-opengateway', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBeUndefined()
|
|
expect(process.env.OPENAI_API_BASE).toBe('http://proxy.local:8080/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBe('fake-ogw-key')
|
|
})
|
|
|
|
test('descriptor-backed provider selection ignores placeholder OPENAI_API_BASE alias values', () => {
|
|
process.env.OPENAI_API_BASE = 'undefined'
|
|
|
|
const result = applyProviderFlag('gitlawb-opengateway', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://opengateway.gitlawb.com/v1')
|
|
expect(process.env.OPENAI_API_BASE).toBe('undefined')
|
|
})
|
|
|
|
test('gitlawb-opengateway explicit provider overrides stale generic base URL', () => {
|
|
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
|
|
process.env.OPENAI_MODEL = 'gpt-5.5'
|
|
process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'
|
|
|
|
const result = applyProviderFlag('gitlawb-opengateway', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://opengateway.gitlawb.com/v1')
|
|
expect(process.env.OPENGATEWAY_API_KEY).toBe('fake-ogw-key')
|
|
expect(process.env.OPENAI_API_KEY).toBe('fake-ogw-key')
|
|
})
|
|
|
|
test('gitlawb-opengateway explicit provider respects OPENGATEWAY_BASE_URL override', () => {
|
|
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
|
|
process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'
|
|
|
|
const result = applyProviderFlag('gitlawb-opengateway', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('http://localhost:8181/v1')
|
|
})
|
|
|
|
test('gitlawb-opengateway explicit provider preserves custom OPENAI_BASE_URL when no OPENGATEWAY_BASE_URL is set', () => {
|
|
process.env.OPENAI_BASE_URL = 'http://localhost:8181/v1'
|
|
process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'
|
|
|
|
const result = applyProviderFlag('gitlawb-opengateway', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('http://localhost:8181/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBe('fake-ogw-key')
|
|
})
|
|
|
|
test('gitlawb-opengateway explicit provider prefers OPENGATEWAY_API_KEY over generic OPENAI_API_KEY', () => {
|
|
process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'
|
|
process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'
|
|
process.env.OPENAI_API_KEY = 'fake-generic-openai-key'
|
|
|
|
const result = applyProviderFlag('gitlawb-opengateway', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('http://localhost:8181/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBe('fake-ogw-key')
|
|
})
|
|
|
|
test('gitlawb-opengateway explicit provider ignores blank OPENGATEWAY_API_KEY fallback', () => {
|
|
process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'
|
|
process.env.OPENGATEWAY_API_KEY = ' '
|
|
process.env.OPENAI_API_KEY = 'fake-openai-fallback'
|
|
|
|
const result = applyProviderFlag('gitlawb-opengateway', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('http://localhost:8181/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBe('fake-openai-fallback')
|
|
})
|
|
|
|
test('gitlawb-opengateway trims scoped API key and clears the copied key when switching routes', () => {
|
|
process.env.OPENGATEWAY_API_KEY = ' fake-ogw-key '
|
|
|
|
const opengatewayResult = applyProviderFlag('gitlawb-opengateway', [])
|
|
expect(opengatewayResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBe('fake-ogw-key')
|
|
|
|
const openrouterResult = applyProviderFlag('openrouter', [])
|
|
|
|
expect(openrouterResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
expect(process.env.OPENGATEWAY_API_KEY).toBe(' fake-ogw-key ')
|
|
})
|
|
|
|
test('clears OPENGATEWAY_API_KEY copied into OPENAI_API_KEY when switching routes', () => {
|
|
process.env.OPENAI_API_KEY = 'fake-ogw-key'
|
|
process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'
|
|
|
|
const result = applyProviderFlag('openrouter', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
expect(process.env.OPENGATEWAY_API_KEY).toBe('fake-ogw-key')
|
|
})
|
|
|
|
test('descriptor-backed provider selection does not keep stale OpenGateway route', () => {
|
|
process.env.OPENAI_BASE_URL = 'https://opengateway.gitlawb.com/v1'
|
|
process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'
|
|
|
|
const result = applyProviderFlag('openrouter', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
expect(process.env.OPENGATEWAY_API_KEY).toBe('fake-ogw-key')
|
|
})
|
|
|
|
test('clears stale NVIDIA_NIM marker when switching to another OpenAI-compatible route', () => {
|
|
process.env.NVIDIA_NIM = '1'
|
|
|
|
const result = applyProviderFlag('openrouter', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.NVIDIA_NIM).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
})
|
|
|
|
test('clears NVIDIA_API_KEY copied into OPENAI_API_KEY when switching routes', () => {
|
|
process.env.NVIDIA_API_KEY = 'nvidia-live-key'
|
|
|
|
const nvidiaResult = applyProviderFlag('nvidia-nim', [])
|
|
expect(nvidiaResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBe('nvidia-live-key')
|
|
|
|
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'
|
|
const openrouterResult = applyProviderFlag('openrouter', [])
|
|
|
|
expect(openrouterResult.error).toBeUndefined()
|
|
expect(process.env.NVIDIA_NIM).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
})
|
|
|
|
test('clears BNKR_API_KEY copied into OPENAI_API_KEY when switching routes', () => {
|
|
process.env.BNKR_API_KEY = 'bankr-live-key'
|
|
|
|
const bankrResult = applyProviderFlag('bankr', [])
|
|
expect(bankrResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBe('bankr-live-key')
|
|
|
|
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'
|
|
const openrouterResult = applyProviderFlag('openrouter', [])
|
|
|
|
expect(openrouterResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
})
|
|
|
|
test('clears MIMO_API_KEY copied into OPENAI_API_KEY when switching routes', () => {
|
|
process.env.MIMO_API_KEY = 'mimo-live-key'
|
|
|
|
const mimoResult = applyProviderFlag('xiaomi-mimo', [])
|
|
expect(mimoResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBe('mimo-live-key')
|
|
|
|
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'
|
|
const openrouterResult = applyProviderFlag('openrouter', [])
|
|
|
|
expect(openrouterResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
})
|
|
|
|
test('clears XAI_API_KEY copied into OPENAI_API_KEY when switching routes', () => {
|
|
process.env.XAI_API_KEY = 'xai-live-key'
|
|
|
|
const xaiResult = applyProviderFlag('xai', [])
|
|
expect(xaiResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBe('xai-live-key')
|
|
|
|
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'
|
|
const openrouterResult = applyProviderFlag('openrouter', [])
|
|
|
|
expect(openrouterResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
})
|
|
|
|
test('clears MINIMAX_API_KEY copied into OPENAI_API_KEY when switching routes', () => {
|
|
process.env.MINIMAX_API_KEY = 'minimax-live-key'
|
|
process.env.OPENAI_API_KEY = 'minimax-live-key'
|
|
process.env.XAI_API_KEY = 'xai-live-key'
|
|
|
|
const xaiResult = applyProviderFlag('xai', [])
|
|
|
|
expect(xaiResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBe('xai-live-key')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.x.ai/v1')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - minimax', () => {
|
|
test('preserves MiniMax default base URL and model semantics', () => {
|
|
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
|
|
process.env.OPENAI_MODEL = 'gpt-4o'
|
|
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com'
|
|
process.env.ANTHROPIC_MODEL = 'claude-sonnet-4-5'
|
|
|
|
const result = applyProviderFlag('minimax', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_BASE_URL).toBe('https://api.minimax.io/anthropic')
|
|
expect(process.env.ANTHROPIC_MODEL).toBe('MiniMax-M3')
|
|
expect(process.env.OPENAI_BASE_URL).toBeUndefined()
|
|
expect(process.env.OPENAI_MODEL).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - nvidia-nim', () => {
|
|
test('maps NVIDIA_API_KEY into the OPENAI-compatible auth env when present', () => {
|
|
process.env.NVIDIA_API_KEY = 'nvidia-live-key'
|
|
|
|
const result = applyProviderFlag('nvidia-nim', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.NVIDIA_NIM).toBe('1')
|
|
expect(process.env.OPENAI_API_KEY).toBe('nvidia-live-key')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://integrate.api.nvidia.com/v1')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - zai', () => {
|
|
test('preserves Z.AI default base URL and model semantics', () => {
|
|
const result = applyProviderFlag('zai', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.z.ai/api/coding/paas/v4')
|
|
expect(process.env.OPENAI_MODEL).toBe('glm-5.2')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - longcat', () => {
|
|
test('sets LongCat OpenAI-compatible defaults and mirrors LONGCAT_API_KEY', () => {
|
|
process.env.LONGCAT_API_KEY = 'longcat-secret-key'
|
|
|
|
const result = applyProviderFlag('longcat', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.longcat.chat/openai/v1')
|
|
expect(process.env.OPENAI_MODEL).toBe('LongCat-2.0')
|
|
expect(process.env.OPENAI_API_KEY).toBe('longcat-secret-key')
|
|
})
|
|
|
|
test('sets LongCat OPENAI_MODEL when --model is provided', () => {
|
|
applyProviderFlag('longcat', ['--model', 'LongCat-2.0'])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('LongCat-2.0')
|
|
})
|
|
|
|
test('clears incompatible OpenAI transport and auth overrides', () => {
|
|
process.env.LONGCAT_API_KEY = 'longcat-secret-key'
|
|
process.env.OPENAI_API_FORMAT = 'responses'
|
|
process.env.OPENAI_AUTH_HEADER = 'api-key'
|
|
process.env.OPENAI_AUTH_SCHEME = 'token'
|
|
process.env.OPENAI_AUTH_HEADER_VALUE = 'stale-secret'
|
|
|
|
const result = applyProviderFlag('longcat', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
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()
|
|
})
|
|
|
|
test('does not mirror LONGCAT_API_KEY to a preserved custom base URL', () => {
|
|
process.env.OPENAI_BASE_URL = 'https://untrusted.example/v1'
|
|
process.env.LONGCAT_API_KEY = 'longcat-secret-key'
|
|
|
|
const result = applyProviderFlag('longcat', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://untrusted.example/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('clears LONGCAT_API_KEY copied into OPENAI_API_KEY when switching routes', () => {
|
|
process.env.LONGCAT_API_KEY = 'longcat-live-key'
|
|
|
|
const longcatResult = applyProviderFlag('longcat', [])
|
|
expect(longcatResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBe('longcat-live-key')
|
|
|
|
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'
|
|
const openrouterResult = applyProviderFlag('openrouter', [])
|
|
|
|
expect(openrouterResult.error).toBeUndefined()
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://openrouter.ai/api/v1')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - xiaomi-mimo', () => {
|
|
test('sets Xiaomi MiMo OpenAI-compatible defaults and mirrors MIMO_API_KEY', () => {
|
|
process.env.MIMO_API_KEY = 'mimo-secret-key'
|
|
|
|
const result = applyProviderFlag('xiaomi-mimo', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.xiaomimimo.com/v1')
|
|
expect(process.env.OPENAI_MODEL).toBe('mimo-v2.5-pro')
|
|
expect(process.env.OPENAI_API_KEY).toBe('mimo-secret-key')
|
|
})
|
|
|
|
test('sets Xiaomi MiMo OPENAI_MODEL when --model is provided', () => {
|
|
applyProviderFlag('xiaomi-mimo', ['--model', 'mimo-v2-flash'])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('mimo-v2-flash')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - xiaomi-mimo-token', () => {
|
|
test('sets Xiaomi MiMo Token Plan OpenAI-compatible defaults and mirrors MIMO_API_KEY', () => {
|
|
process.env.MIMO_API_KEY = 'tp-token-plan-key'
|
|
|
|
const result = applyProviderFlag('xiaomi-mimo-token', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe(
|
|
'https://token-plan-sgp.xiaomimimo.com/v1',
|
|
)
|
|
expect(process.env.OPENAI_MODEL).toBe('mimo-v2.5-pro')
|
|
expect(process.env.OPENAI_API_KEY).toBe('tp-token-plan-key')
|
|
})
|
|
|
|
test('replaces stale known provider base URL with the token-plan default', () => {
|
|
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'
|
|
|
|
const result = applyProviderFlag('xiaomi-mimo-token', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe(
|
|
'https://token-plan-sgp.xiaomimimo.com/v1',
|
|
)
|
|
})
|
|
|
|
test('sets Xiaomi MiMo Token Plan OPENAI_MODEL when --model is provided', () => {
|
|
applyProviderFlag('xiaomi-mimo-token', ['--model', 'mimo-v2-flash'])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('mimo-v2-flash')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - venice', () => {
|
|
test('sets Venice OpenAI-compatible defaults and mirrors VENICE_API_KEY', () => {
|
|
process.env.VENICE_API_KEY = 'venice-secret-key'
|
|
|
|
const result = applyProviderFlag('venice', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.venice.ai/api/v1')
|
|
expect(process.env.OPENAI_MODEL).toBe('venice-uncensored')
|
|
expect(process.env.OPENAI_API_KEY).toBe('venice-secret-key')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - atlas-cloud', () => {
|
|
test('sets Atlas Cloud OpenAI-compatible defaults and mirrors ATLAS_CLOUD_API_KEY', () => {
|
|
process.env.ATLAS_CLOUD_API_KEY = 'atlas-secret-key'
|
|
|
|
const result = applyProviderFlag('atlas-cloud', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.atlascloud.ai/v1')
|
|
expect(process.env.OPENAI_MODEL).toBe('deepseek-ai/deepseek-v4-pro')
|
|
expect(process.env.OPENAI_API_KEY).toBe('atlas-secret-key')
|
|
})
|
|
|
|
test('dedicated key overrides a lingering OPENAI_API_KEY from another provider', () => {
|
|
process.env.OPENAI_API_KEY = 'existing-openai-key'
|
|
process.env.ATLAS_CLOUD_API_KEY = 'atlas-secret-key'
|
|
|
|
applyProviderFlag('atlas-cloud', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBe('atlas-secret-key')
|
|
})
|
|
|
|
test('clears a stale OPENAI_API_KEY when no Atlas Cloud key is set', () => {
|
|
process.env.OPENAI_API_KEY = 'existing-openai-key'
|
|
|
|
applyProviderFlag('atlas-cloud', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('keeps a copied Atlas key in OPENAI_API_KEY when selecting atlas-cloud', () => {
|
|
process.env.ATLAS_CLOUD_API_KEY = 'atlas-secret-key'
|
|
process.env.OPENAI_API_KEY = 'atlas-secret-key'
|
|
|
|
applyProviderFlag('atlas-cloud', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBe('atlas-secret-key')
|
|
})
|
|
|
|
test('clears a copied Atlas key from OPENAI_API_KEY when switching to another provider', () => {
|
|
process.env.ATLAS_CLOUD_API_KEY = 'atlas-secret-key'
|
|
process.env.OPENAI_API_KEY = 'atlas-secret-key'
|
|
|
|
applyProviderFlag('openai', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('replaces a stale base URL belonging to another known provider', () => {
|
|
process.env.OPENAI_BASE_URL = 'https://api.venice.ai/api/v1'
|
|
process.env.ATLAS_CLOUD_API_KEY = 'atlas-secret-key'
|
|
|
|
applyProviderFlag('atlas-cloud', [])
|
|
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.atlascloud.ai/v1')
|
|
})
|
|
|
|
test('preserves a custom base URL that matches no known provider', () => {
|
|
process.env.OPENAI_BASE_URL = 'https://llm-proxy.internal.example/v1'
|
|
process.env.ATLAS_CLOUD_API_KEY = 'atlas-secret-key'
|
|
|
|
applyProviderFlag('atlas-cloud', [])
|
|
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://llm-proxy.internal.example/v1')
|
|
})
|
|
|
|
test('sets OPENAI_MODEL when --model is provided', () => {
|
|
process.env.ATLAS_CLOUD_API_KEY = 'atlas-secret-key'
|
|
|
|
applyProviderFlag('atlas-cloud', ['--model', 'zai-org/glm-5'])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('zai-org/glm-5')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - apismart', () => {
|
|
test('sets ApiSmart OpenAI-compatible defaults and mirrors APISMART_API_KEY', () => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
|
|
const result = applyProviderFlag('apismart', [])
|
|
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://gw.apismart.ai/v1')
|
|
expect(process.env.OPENAI_MODEL).toBe('DEEPSEEK_V4_FLASH')
|
|
expect(process.env.OPENAI_API_KEY).toBe('apismart-secret-key')
|
|
})
|
|
|
|
test('does not forward the dedicated key to a preserved custom base URL', () => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.OPENAI_BASE_URL = 'https://llm-proxy.internal.example/v1'
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://llm-proxy.internal.example/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('clears unsupported OpenAI shim settings from a previous route', () => {
|
|
process.env.APISMART_API_KEY = 'apismart-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'
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
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()
|
|
})
|
|
|
|
test('uses APISMART_MODEL from env when --model is not provided', () => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.APISMART_MODEL = 'KIMI_K3'
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('KIMI_K3')
|
|
})
|
|
|
|
test('ApiSmart-specific model replaces a stale generic OpenAI model', () => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.APISMART_MODEL = 'KIMI_K3'
|
|
process.env.OPENAI_MODEL = 'gpt-4o'
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('KIMI_K3')
|
|
})
|
|
|
|
test.each(['undefined', 'null', ' NULL '])(
|
|
'ignores stale generic model sentinel %s for ApiSmart',
|
|
placeholder => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.OPENAI_MODEL = placeholder
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('DEEPSEEK_V4_FLASH')
|
|
},
|
|
)
|
|
|
|
test.each(['undefined', 'null', ' NULL '])(
|
|
'ignores stale generic base URL sentinel %s for ApiSmart',
|
|
placeholder => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.OPENAI_BASE_URL = placeholder
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://gw.apismart.ai/v1')
|
|
expect(process.env.OPENAI_API_KEY).toBe('apismart-secret-key')
|
|
},
|
|
)
|
|
|
|
test('makes an explicit --model override APISMART_MODEL', () => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.APISMART_MODEL = 'KIMI_K3'
|
|
|
|
applyProviderFlag('apismart', ['--model', 'GLM_5.2'])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('GLM_5.2')
|
|
expect(process.env.APISMART_MODEL).toBe('GLM_5.2')
|
|
})
|
|
|
|
test('standalone --model overrides credential-only ApiSmart model selection', () => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.APISMART_MODEL = 'KIMI_K3'
|
|
|
|
applyModelFlagFromArgs(['--model', 'GLM_5.2'])
|
|
|
|
expect(process.env.OPENAI_MODEL).toBe('GLM_5.2')
|
|
expect(process.env.APISMART_MODEL).toBe('GLM_5.2')
|
|
})
|
|
|
|
test('dedicated key overrides a lingering OPENAI_API_KEY from another provider', () => {
|
|
process.env.OPENAI_API_KEY = 'existing-openai-key'
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBe('apismart-secret-key')
|
|
})
|
|
|
|
test('clears a stale OPENAI_API_KEY when no ApiSmart key is set', () => {
|
|
process.env.OPENAI_API_KEY = 'existing-openai-key'
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test.each(['SUA_CHAVE', 'null', 'undefined', ' NULL '])(
|
|
'does not mirror placeholder ApiSmart credential %s',
|
|
placeholder => {
|
|
process.env.APISMART_API_KEY = placeholder
|
|
|
|
applyProviderFlag('apismart', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
},
|
|
)
|
|
|
|
test('clears a copied ApiSmart key from OPENAI_API_KEY when switching to another provider', () => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.OPENAI_API_KEY = 'apismart-secret-key'
|
|
process.env.OPENAI_BASE_URL = 'https://gw.apismart.ai/v1'
|
|
|
|
applyProviderFlag('openai', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe('https://api.openai.com/v1')
|
|
expect(process.env.CLAUDE_CODE_PROVIDER_ROUTE_ID).toBe('openai')
|
|
expect(resolveEnvOnlyProviderRouteId(process.env)).toBeNull()
|
|
})
|
|
|
|
test.each([
|
|
['openai', 'https://api.openai.com/v1'],
|
|
['github', 'https://api.githubcopilot.com'],
|
|
['ollama', 'http://localhost:11434/v1'],
|
|
['nvidia-nim', 'https://integrate.api.nvidia.com/v1'],
|
|
['bankr', 'https://llm.bankr.bot/v1'],
|
|
['xai', 'https://api.x.ai/v1'],
|
|
['xiaomi-mimo', 'https://api.xiaomimimo.com/v1'],
|
|
['venice', 'https://api.venice.ai/api/v1'],
|
|
])(
|
|
'explicit %s selection replaces a stale ApiSmart endpoint',
|
|
(provider, expectedBaseUrl) => {
|
|
process.env.APISMART_API_KEY = 'apismart-secret-key'
|
|
process.env.OPENAI_API_KEY = 'apismart-secret-key'
|
|
process.env.OPENAI_BASE_URL = 'https://gw.apismart.ai/v1'
|
|
process.env.OPENAI_MODEL = 'KIMI_K3'
|
|
|
|
applyProviderFlag(provider, [])
|
|
|
|
expect(process.env.OPENAI_BASE_URL).toBe(expectedBaseUrl)
|
|
expect(process.env.OPENAI_API_KEY).not.toBe('apismart-secret-key')
|
|
expect(process.env.OPENAI_MODEL).not.toBe('KIMI_K3')
|
|
},
|
|
)
|
|
})
|
|
|
|
describe('applyProviderFlag - xai', () => {
|
|
test('sets CLAUDE_CODE_USE_OPENAI=1 with xAI defaults when unset', () => {
|
|
delete process.env.OPENAI_BASE_URL
|
|
delete process.env.OPENAI_API_KEY
|
|
|
|
const result = applyProviderFlag('xai', [])
|
|
expect(result.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL as string | undefined).toBe('https://api.x.ai/v1')
|
|
expect(process.env.OPENAI_MODEL).toBe('grok-4.3')
|
|
})
|
|
|
|
test('sets OPENAI_MODEL when --model is provided', () => {
|
|
applyProviderFlag('xai', ['--model', 'grok-3'])
|
|
expect(process.env.OPENAI_MODEL).toBe('grok-3')
|
|
})
|
|
|
|
test('propagates XAI_API_KEY to OPENAI_API_KEY when only XAI_API_KEY is set', () => {
|
|
delete process.env.OPENAI_API_KEY
|
|
process.env.XAI_API_KEY = 'xai-secret-key'
|
|
|
|
applyProviderFlag('xai', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY as string | undefined).toBe('xai-secret-key')
|
|
})
|
|
|
|
test('does not override existing OPENAI_API_KEY when both keys are set', () => {
|
|
process.env.OPENAI_API_KEY = 'existing-openai-key'
|
|
process.env.XAI_API_KEY = 'xai-secret-key'
|
|
|
|
applyProviderFlag('xai', [])
|
|
|
|
expect(process.env.OPENAI_API_KEY).toBe('existing-openai-key')
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlag - invalid provider', () => {
|
|
test('returns error for unknown provider', () => {
|
|
const result = applyProviderFlag('unknown-provider', [])
|
|
expect(result.error).toContain('unknown-provider')
|
|
expect(result.error).toContain(VALID_PROVIDERS.join(', '))
|
|
})
|
|
})
|
|
|
|
describe('applyProviderFlagFromArgs', () => {
|
|
test('applies ollama provider and model from argv in one step', () => {
|
|
delete process.env.OPENAI_BASE_URL
|
|
delete process.env.OPENAI_API_KEY
|
|
|
|
const result = applyProviderFlagFromArgs([
|
|
'--provider',
|
|
'ollama',
|
|
'--model',
|
|
'qwen2.5:3b',
|
|
])
|
|
|
|
expect(result?.error).toBeUndefined()
|
|
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
|
|
expect(process.env.OPENAI_BASE_URL!).toBe('http://localhost:11434/v1')
|
|
expect(process.env.OPENAI_API_KEY!).toBe('ollama')
|
|
expect(process.env.OPENAI_MODEL).toBe('qwen2.5:3b')
|
|
})
|
|
|
|
test('returns undefined when --provider is absent', () => {
|
|
expect(applyProviderFlagFromArgs(['--model', 'gpt-4o'])).toBeUndefined()
|
|
})
|
|
|
|
test('reapplies remembered gitlawb-opengateway after settings env restores stale OpenAI routing', () => {
|
|
const args = ['--provider', 'gitlawb-opengateway']
|
|
delete process.env.OPENGATEWAY_API_KEY
|
|
delete process.env.OPENAI_API_KEY
|
|
|
|
const earlyResult = applyProviderFlagFromArgs(args, {
|
|
rememberForSettingsEnv: true,
|
|
})
|
|
expect(earlyResult?.error).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe(
|
|
'https://opengateway.gitlawb.com/v1',
|
|
)
|
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
|
|
|
process.env.OPENGATEWAY_API_KEY = 'settings-ogw-key'
|
|
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
|
|
|
|
const lateResult = reapplyRememberedProviderFlag()
|
|
|
|
expect(lateResult?.error).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe(
|
|
'https://opengateway.gitlawb.com/v1',
|
|
)
|
|
expect(process.env.OPENAI_API_KEY as string | undefined).toBe(
|
|
'settings-ogw-key',
|
|
)
|
|
})
|
|
|
|
test('remembered provider reapply preserves an explicit --model', () => {
|
|
const result = applyProviderFlagFromArgs(
|
|
[
|
|
'--print',
|
|
'--provider',
|
|
'gitlawb-opengateway',
|
|
'--model',
|
|
'custom-ogw-model',
|
|
'do not retain prompt text',
|
|
],
|
|
{ rememberForSettingsEnv: true },
|
|
)
|
|
|
|
expect(result?.error).toBeUndefined()
|
|
process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'
|
|
process.env.OPENAI_MODEL = 'stale-openai-model'
|
|
|
|
const lateResult = reapplyRememberedProviderFlag()
|
|
|
|
expect(lateResult?.error).toBeUndefined()
|
|
expect(process.env.OPENAI_BASE_URL).toBe(
|
|
'https://opengateway.gitlawb.com/v1',
|
|
)
|
|
expect(process.env.OPENAI_MODEL).toBe('custom-ogw-model')
|
|
})
|
|
})
|
|
|
|
// --- parseModelFlag ---
|
|
|
|
describe('parseModelFlag', () => {
|
|
test('returns model value when --model is present', () => {
|
|
expect(parseModelFlag(['--model', 'gpt-4o-mini'])).toBe('gpt-4o-mini')
|
|
})
|
|
|
|
test('returns null when --model is absent', () => {
|
|
expect(parseModelFlag(['--provider', 'openai'])).toBeNull()
|
|
})
|
|
|
|
test('returns null when --model has no value', () => {
|
|
expect(parseModelFlag(['--model'])).toBeNull()
|
|
})
|
|
|
|
test('returns null when --model value looks like another flag', () => {
|
|
expect(parseModelFlag(['--model', '--provider'])).toBeNull()
|
|
})
|
|
})
|
|
|
|
// --- applyModelFlagFromArgs (#808) ---
|
|
|
|
describe('applyModelFlagFromArgs', () => {
|
|
test('is a no-op when --model is absent', () => {
|
|
applyModelFlagFromArgs(['--ide'])
|
|
expect(process.env.OPENAI_MODEL).toBeUndefined()
|
|
expect(process.env.GEMINI_MODEL).toBeUndefined()
|
|
expect(process.env.ANTHROPIC_MODEL).toBeUndefined()
|
|
})
|
|
|
|
test('is a no-op when --provider is also present (handled by applyProviderFlagFromArgs)', () => {
|
|
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
|
applyModelFlagFromArgs(['--provider', 'openai', '--model', 'gpt-4o'])
|
|
expect(process.env.OPENAI_MODEL).toBeUndefined()
|
|
})
|
|
|
|
test('sets OPENAI_MODEL when CLAUDE_CODE_USE_OPENAI is active', () => {
|
|
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
|
applyModelFlagFromArgs(['--model', 'gpt-4o-mini'])
|
|
expect(process.env.OPENAI_MODEL).toBe('gpt-4o-mini')
|
|
})
|
|
|
|
test('sets GEMINI_MODEL when CLAUDE_CODE_USE_GEMINI is active', () => {
|
|
process.env.CLAUDE_CODE_USE_GEMINI = '1'
|
|
applyModelFlagFromArgs(['--model', 'gemini-2.0-flash'])
|
|
expect(process.env.GEMINI_MODEL).toBe('gemini-2.0-flash')
|
|
})
|
|
|
|
test('sets MISTRAL_MODEL when CLAUDE_CODE_USE_MISTRAL is active', () => {
|
|
process.env.CLAUDE_CODE_USE_MISTRAL = '1'
|
|
applyModelFlagFromArgs(['--model', 'devstral-latest'])
|
|
expect(process.env.MISTRAL_MODEL).toBe('devstral-latest')
|
|
})
|
|
|
|
test('sets OPENAI_MODEL when CLAUDE_CODE_USE_GITHUB is active', () => {
|
|
process.env.CLAUDE_CODE_USE_GITHUB = '1'
|
|
applyModelFlagFromArgs(['--model', 'gpt-4.1'])
|
|
expect(process.env.OPENAI_MODEL).toBe('gpt-4.1')
|
|
})
|
|
|
|
test('falls back to ANTHROPIC_MODEL when no provider flag is set', () => {
|
|
applyModelFlagFromArgs(['--model', 'claude-sonnet-4-6'])
|
|
expect(process.env.ANTHROPIC_MODEL).toBe('claude-sonnet-4-6')
|
|
})
|
|
|
|
test('overrides an existing *_MODEL value (saved profile override)', () => {
|
|
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
|
process.env.OPENAI_MODEL = 'gpt-4o'
|
|
applyModelFlagFromArgs(['--model', 'gpt-4o-mini'])
|
|
expect(process.env.OPENAI_MODEL).toBe('gpt-4o-mini')
|
|
})
|
|
|
|
test('accepts --model value containing colons (ollama tag syntax)', () => {
|
|
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
|
applyModelFlagFromArgs(['--model', 'qwen2.5-coder:14b'])
|
|
expect(process.env.OPENAI_MODEL).toBe('qwen2.5-coder:14b')
|
|
})
|
|
})
|