diff --git a/src/commands/provider.ts b/src/commands/provider.ts index 78080b769..4379dba78 100644 --- a/src/commands/provider.ts +++ b/src/commands/provider.ts @@ -85,14 +85,12 @@ const call: LocalCommandCall = async (args, context) => { applyConfigEnvironmentVariables() return { type: 'text', value: `API provider set to ${arg}.` } } else { - // Cloud providers: set env vars only, clear settings.modelType + // Cloud providers: set env vars only, do NOT touch settings.modelType delete process.env.CLAUDE_CODE_USE_OPENAI delete process.env.OPENAI_API_KEY delete process.env.OPENAI_BASE_URL process.env[getEnvVarForProvider(arg)] = '1' - // Clear settings.json to 'anthropic' to avoid validation errors - updateSettingsForSource('userSettings', { modelType: 'anthropic' }) - // Ensure settings.env gets applied to process.env (in case it contains provider-specific creds) + // Do not modify settings.json - cloud providers controlled solely by env vars applyConfigEnvironmentVariables() return { type: 'text', value: `API provider set to ${arg} (via environment variable).` } } diff --git a/src/utils/model/__tests__/providers.test.ts b/src/utils/model/__tests__/providers.test.ts index b028f1084..afe9b126c 100644 --- a/src/utils/model/__tests__/providers.test.ts +++ b/src/utils/model/__tests__/providers.test.ts @@ -1,19 +1,57 @@ -import { describe, expect, test, beforeEach, afterEach } from "bun:test"; +import { describe, expect, test, beforeEach, afterEach, beforeAll, afterAll } from "bun:test"; import { getAPIProvider, isFirstPartyAnthropicBaseUrl } from "../providers"; +import { readFileSync, writeFileSync } from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { homedir } from "os"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +function getSettingsPath(): string { + return path.join(homedir(), ".claude", "settings.json"); +} describe("getAPIProvider", () => { const envKeys = [ "CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_VERTEX", "CLAUDE_CODE_USE_FOUNDRY", + "CLAUDE_CODE_USE_OPENAI", ] as const; const savedEnv: Record = {}; + let originalSettings: string = ""; + + beforeAll(() => { + // Backup and clear settings.json modelType + const settingsPath = getSettingsPath(); + try { + originalSettings = readFileSync(settingsPath, "utf-8"); + const parsed = JSON.parse(originalSettings); + delete parsed.modelType; + writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + "\n"); + } catch (e) { + // If file doesn't exist or can't parse, ignore + } + }); + + afterAll(() => { + // Restore original settings.json + if (originalSettings) { + writeFileSync(getSettingsPath(), originalSettings); + } + }); beforeEach(() => { - for (const key of envKeys) savedEnv[key] = process.env[key]; + // Save and clear environment variables + for (const key of envKeys) { + savedEnv[key] = process.env[key]; + delete process.env[key]; + } }); afterEach(() => { + // Restore environment variables for (const key of envKeys) { if (savedEnv[key] !== undefined) { process.env[key] = savedEnv[key]; @@ -24,9 +62,6 @@ describe("getAPIProvider", () => { }); test('returns "firstParty" by default', () => { - delete process.env.CLAUDE_CODE_USE_BEDROCK; - delete process.env.CLAUDE_CODE_USE_VERTEX; - delete process.env.CLAUDE_CODE_USE_FOUNDRY; expect(getAPIProvider()).toBe("firstParty"); }); diff --git a/src/utils/model/providers.ts b/src/utils/model/providers.ts index bce43ba92..09c14cfc4 100644 --- a/src/utils/model/providers.ts +++ b/src/utils/model/providers.ts @@ -5,23 +5,20 @@ import { isEnvTruthy } from '../envUtils.js' export type APIProvider = 'firstParty' | 'bedrock' | 'vertex' | 'foundry' | 'openai' export function getAPIProvider(): APIProvider { - // 1. Check settings.json modelType field (highest priority) + // Cloud provider env vars have highest priority (they are explicit switches) + if (isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK)) return 'bedrock' + if (isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX)) return 'vertex' + if (isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY)) return 'foundry' + + // Check settings.json modelType field const modelType = getInitialSettings().modelType if (modelType === 'openai') return 'openai' - if (modelType === 'bedrock') return 'bedrock' - if (modelType === 'vertex') return 'vertex' - if (modelType === 'foundry') return 'foundry' - // 2. Check environment variables (backward compatibility) - return isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI) - ? 'openai' - : isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) - ? 'bedrock' - : isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) - ? 'vertex' - : isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) - ? 'foundry' - : 'firstParty' + // Backward compatibility: check legacy OpenAI env var + if (isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI)) return 'openai' + + // Default: Anthropic first-party API + return 'firstParty' } export function getAPIProviderForStatsig(): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS {