diff --git a/src/commands/model/model.test.tsx b/src/commands/model/model.test.tsx index d35c1f0fe..a1e0888ef 100644 --- a/src/commands/model/model.test.tsx +++ b/src/commands/model/model.test.tsx @@ -21,6 +21,7 @@ type SettingsModule = typeof import('../../utils/settings/settings.js') const originalEnv = { CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI, CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI, + CLAUDE_CODE_USE_GEMINI_VERTEX: process.env.CLAUDE_CODE_USE_GEMINI_VERTEX, CLAUDE_CODE_USE_GITHUB: process.env.CLAUDE_CODE_USE_GITHUB, CLAUDE_CODE_USE_MISTRAL: process.env.CLAUDE_CODE_USE_MISTRAL, CLAUDE_CODE_USE_BEDROCK: process.env.CLAUDE_CODE_USE_BEDROCK, @@ -202,6 +203,10 @@ afterEach(() => { settingsForTest = {} restoreEnv('CLAUDE_CODE_USE_OPENAI', originalEnv.CLAUDE_CODE_USE_OPENAI) restoreEnv('CLAUDE_CODE_USE_GEMINI', originalEnv.CLAUDE_CODE_USE_GEMINI) + restoreEnv( + 'CLAUDE_CODE_USE_GEMINI_VERTEX', + originalEnv.CLAUDE_CODE_USE_GEMINI_VERTEX, + ) restoreEnv('CLAUDE_CODE_USE_GITHUB', originalEnv.CLAUDE_CODE_USE_GITHUB) restoreEnv('CLAUDE_CODE_USE_MISTRAL', originalEnv.CLAUDE_CODE_USE_MISTRAL) restoreEnv('CLAUDE_CODE_USE_BEDROCK', originalEnv.CLAUDE_CODE_USE_BEDROCK) diff --git a/src/components/StartupScreen.ts b/src/components/StartupScreen.ts index df8ba7cfd..ec886dc8a 100644 --- a/src/components/StartupScreen.ts +++ b/src/components/StartupScreen.ts @@ -85,10 +85,10 @@ const LOGO_CLAUDE = [ export function detectProvider(modelOverride?: string): { name: string; model: string; baseUrl: string; isLocal: boolean } { const useGeminiVertex = isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI_VERTEX) - const useGemini = process.env.CLAUDE_CODE_USE_GEMINI === '1' || process.env.CLAUDE_CODE_USE_GEMINI === 'true' - const useGithub = process.env.CLAUDE_CODE_USE_GITHUB === '1' || process.env.CLAUDE_CODE_USE_GITHUB === 'true' - const useOpenAI = process.env.CLAUDE_CODE_USE_OPENAI === '1' || process.env.CLAUDE_CODE_USE_OPENAI === 'true' - const useMistral = process.env.CLAUDE_CODE_USE_MISTRAL === '1' || process.env.CLAUDE_CODE_USE_MISTRAL === 'true' + const useGemini = isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI) + const useGithub = isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB) + const useOpenAI = isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI) + const useMistral = isEnvTruthy(process.env.CLAUDE_CODE_USE_MISTRAL) if (useGeminiVertex) { // Use the shared resolvers so this display matches the runtime/provider diff --git a/src/services/api/promptCacheBreakDetection.test.ts b/src/services/api/promptCacheBreakDetection.test.ts index 5ad9423e3..fbefb41b5 100644 --- a/src/services/api/promptCacheBreakDetection.test.ts +++ b/src/services/api/promptCacheBreakDetection.test.ts @@ -50,6 +50,7 @@ const PROVIDER_ENV_KEYS = [ 'CLAUDE_CODE_USE_OPENAI', 'CLAUDE_CODE_USE_GITHUB', 'CLAUDE_CODE_USE_GEMINI', + 'CLAUDE_CODE_USE_GEMINI_VERTEX', 'CLAUDE_CODE_USE_MISTRAL', 'CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_VERTEX', diff --git a/src/services/api/withRetry.test.ts b/src/services/api/withRetry.test.ts index 63bc273d8..e747c42b6 100644 --- a/src/services/api/withRetry.test.ts +++ b/src/services/api/withRetry.test.ts @@ -22,6 +22,7 @@ let originalProvidersModule: ProvidersModule | undefined const envKeys = [ 'CLAUDE_CODE_USE_OPENAI', 'CLAUDE_CODE_USE_GEMINI', + 'CLAUDE_CODE_USE_GEMINI_VERTEX', 'CLAUDE_CODE_USE_GITHUB', 'CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_VERTEX', diff --git a/src/utils/betas.test.ts b/src/utils/betas.test.ts index 1e38e0ea5..be9e90d88 100644 --- a/src/utils/betas.test.ts +++ b/src/utils/betas.test.ts @@ -22,6 +22,7 @@ import { setSdkBetas } from '../bootstrap/state.js' const PROVIDER_ENV_KEYS = [ 'CLAUDE_CODE_USE_OPENAI', 'CLAUDE_CODE_USE_GEMINI', + 'CLAUDE_CODE_USE_GEMINI_VERTEX', 'CLAUDE_CODE_USE_MISTRAL', 'CLAUDE_CODE_USE_GITHUB', 'CLAUDE_CODE_USE_BEDROCK', diff --git a/src/utils/conversationRecovery.test.ts b/src/utils/conversationRecovery.test.ts index 5d6043e9d..478826584 100644 --- a/src/utils/conversationRecovery.test.ts +++ b/src/utils/conversationRecovery.test.ts @@ -17,6 +17,7 @@ const tempDirs: string[] = [] const originalSimple = process.env.CLAUDE_CODE_SIMPLE const providerEnvKeys = [ 'CLAUDE_CODE_USE_GEMINI', + 'CLAUDE_CODE_USE_GEMINI_VERTEX', 'CLAUDE_CODE_USE_MISTRAL', 'CLAUDE_CODE_USE_GITHUB', 'CLAUDE_CODE_USE_BEDROCK', diff --git a/src/utils/envFile.test.ts b/src/utils/envFile.test.ts index 433516b06..7df9ee449 100644 --- a/src/utils/envFile.test.ts +++ b/src/utils/envFile.test.ts @@ -11,6 +11,7 @@ import { reapplyRememberedEnvFileValues, rememberLoadedEnvFileValues, } from './envFile.js' +import { PROVIDER_SELECTION_FLAGS } from './providerSelectionFlags.js' const TEST_ENV_KEYS = [ 'NODE_OPTIONS', @@ -21,6 +22,16 @@ const TEST_ENV_KEYS = [ 'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL', + ...PROVIDER_SELECTION_FLAGS, + 'GEMINI_ACCESS_TOKEN', + 'GEMINI_VERTEX_PROJECT', + 'GEMINI_VERTEX_LOCATION', + 'GEMINI_VERTEX_MODEL', + 'GEMINI_VERTEX_AUTH_MODE', + 'GOOGLE_APPLICATION_CREDENTIALS', + 'GOOGLE_CLOUD_PROJECT', + 'GCLOUD_PROJECT', + 'GOOGLE_PROJECT_ID', 'WEB_AUTH_HEADER', 'WEB_AUTH_SCHEME', 'WEB_BODY_TEMPLATE', @@ -335,6 +346,49 @@ describe('loadEnvFile', () => { expect(process.env.CODEX_AUTH_JSON_PATH).toBe('/tmp/codex-auth.json') }) + it('accepts every registered provider-selection flag', () => { + // Guards against a new CLAUDE_CODE_USE_* flag (like Gemini Vertex) being + // added to PROVIDER_SELECTION_FLAGS but silently rejected by the env-file + // allowlist, which would break --provider-env-file end-to-end wiring. + const filePath = writeTempEnvFile( + PROVIDER_SELECTION_FLAGS.map(flag => `${flag}=1`).join('\n'), + ) + + const loaded = loadEnvFile(filePath) + + for (const flag of PROVIDER_SELECTION_FLAGS) { + expect(loaded[flag]).toBe('1') + expect(process.env[flag]).toBe('1') + } + }) + + it('loads the documented Gemini Vertex provider setup', () => { + const filePath = writeTempEnvFile([ + 'CLAUDE_CODE_USE_GEMINI_VERTEX=1', + 'GEMINI_VERTEX_PROJECT=my-project', + 'GEMINI_VERTEX_LOCATION=us-central1', + 'GEMINI_VERTEX_MODEL=gemini-2.0-flash', + 'GEMINI_VERTEX_AUTH_MODE=adc', + 'GOOGLE_APPLICATION_CREDENTIALS=/tmp/sa.json', + 'GCLOUD_PROJECT=my-project', + 'GOOGLE_PROJECT_ID=my-project', + ].join('\n')) + + const loaded = loadEnvFile(filePath) + + expect(loaded).toEqual({ + CLAUDE_CODE_USE_GEMINI_VERTEX: '1', + GEMINI_VERTEX_PROJECT: 'my-project', + GEMINI_VERTEX_LOCATION: 'us-central1', + GEMINI_VERTEX_MODEL: 'gemini-2.0-flash', + GEMINI_VERTEX_AUTH_MODE: 'adc', + GOOGLE_APPLICATION_CREDENTIALS: '/tmp/sa.json', + GCLOUD_PROJECT: 'my-project', + GOOGLE_PROJECT_ID: 'my-project', + }) + expect(process.env.CLAUDE_CODE_USE_GEMINI_VERTEX).toBe('1') + }) + it('rejects unsupported variables before mutating process environment', () => { const filePath = writeTempEnvFile([ 'OPENAI_MODEL=from-file', diff --git a/src/utils/envFile.ts b/src/utils/envFile.ts index be01239d7..e521fd6de 100644 --- a/src/utils/envFile.ts +++ b/src/utils/envFile.ts @@ -37,6 +37,7 @@ const ALLOWED_ENV_FILE_KEYS = new Set([ 'CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_FOUNDRY', 'CLAUDE_CODE_USE_GEMINI', + 'CLAUDE_CODE_USE_GEMINI_VERTEX', 'CLAUDE_CODE_USE_GITHUB', 'CLAUDE_CODE_USE_MISTRAL', 'CLAUDE_CODE_USE_OPENAI', @@ -53,11 +54,16 @@ const ALLOWED_ENV_FILE_KEYS = new Set([ 'FIRECRAWL_API_KEY', 'FIRECRAWL_API_URL', 'FIREWORKS_API_KEY', + 'GCLOUD_PROJECT', 'GEMINI_ACCESS_TOKEN', 'GEMINI_API_KEY', 'GEMINI_AUTH_MODE', 'GEMINI_BASE_URL', 'GEMINI_MODEL', + 'GEMINI_VERTEX_AUTH_MODE', + 'GEMINI_VERTEX_LOCATION', + 'GEMINI_VERTEX_MODEL', + 'GEMINI_VERTEX_PROJECT', 'GH_TOKEN', 'GITHUB_COPILOT_ALLOW_SUBAGENTS', 'GITHUB_COPILOT_FORCE_SYNC_SUBAGENTS', @@ -65,8 +71,10 @@ const ALLOWED_ENV_FILE_KEYS = new Set([ 'GITHUB_COPILOT_OPTIMIZATION_DISABLED', 'GITHUB_TOKEN', 'GOOGLE_API_KEY', + 'GOOGLE_APPLICATION_CREDENTIALS', 'GOOGLE_CLOUD_PROJECT', 'GOOGLE_CSE_ID', + 'GOOGLE_PROJECT_ID', 'GROQ_API_KEY', 'HICAP_API_KEY', 'JINA_API_KEY', diff --git a/src/utils/providerFlag.ts b/src/utils/providerFlag.ts index 0b834d887..87d4a22f9 100644 --- a/src/utils/providerFlag.ts +++ b/src/utils/providerFlag.ts @@ -225,18 +225,10 @@ export function applyModelFlagFromArgs(args: string[]): void { if (!model) return const useGeminiVertex = isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI_VERTEX) - const useGemini = - process.env.CLAUDE_CODE_USE_GEMINI === '1' || - process.env.CLAUDE_CODE_USE_GEMINI === 'true' - const useMistral = - process.env.CLAUDE_CODE_USE_MISTRAL === '1' || - process.env.CLAUDE_CODE_USE_MISTRAL === 'true' - const useOpenAI = - process.env.CLAUDE_CODE_USE_OPENAI === '1' || - process.env.CLAUDE_CODE_USE_OPENAI === 'true' - const useGithub = - process.env.CLAUDE_CODE_USE_GITHUB === '1' || - process.env.CLAUDE_CODE_USE_GITHUB === 'true' + const useGemini = isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI) + const useMistral = isEnvTruthy(process.env.CLAUDE_CODE_USE_MISTRAL) + const useOpenAI = isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI) + const useGithub = isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB) if (useGeminiVertex) { process.env.GEMINI_VERTEX_MODEL = model