gemini模型环境变量分离 provider指令支持切换gemini

This commit is contained in:
HitMargin
2026-04-06 11:23:05 +08:00
parent 6f80e96fee
commit 14dc54a093
9 changed files with 324 additions and 47 deletions

View File

@@ -182,6 +182,37 @@ Feature flag `VOICE_MODE`dev/build 默认启用。Push-to-Talk 语音输入
关键环境变量:`CLAUDE_CODE_USE_OPENAI``OPENAI_API_KEY``OPENAI_BASE_URL``OPENAI_MODEL``OPENAI_DEFAULT_OPUS_MODEL``OPENAI_DEFAULT_SONNET_MODEL``OPENAI_DEFAULT_HAIKU_MODEL`。详见 `docs/plans/openai-compatibility.md`
### Gemini 兼容层
通过 `CLAUDE_CODE_USE_GEMINI=1` 环境变量或 `modelType: "gemini"` 设置启用,支持 Google Gemini API。独立的环境变量体系不与 OpenAI 或 Anthropic 配置混杂。
- **`src/services/api/gemini/`** — client、模型映射、类型定义
- **`src/utils/model/providers.ts`** — 添加 `'gemini'` provider 类型
- **`src/utils/managedEnvConstants.ts`** — Gemini 专用的 managed env vars
关键环境变量:
- `CLAUDE_CODE_USE_GEMINI` - 启用 Gemini provider
- `GEMINI_API_KEY` - API 密钥(必填)
- `GEMINI_BASE_URL` - API 端点(可选,默认 `https://generativelanguage.googleapis.com/v1beta`
- `GEMINI_MODEL` - 直接指定模型(最高优先级)
- `GEMINI_DEFAULT_HAIKU_MODEL` / `GEMINI_DEFAULT_SONNET_MODEL` / `GEMINI_DEFAULT_OPUS_MODEL` - 按能力级别映射
- `GEMINI_DEFAULT_HAIKU_MODEL_NAME` / `DESCRIPTION` / `SUPPORTED_CAPABILITIES` - 显示名称和描述
- `GEMINI_SMALL_FAST_MODEL` - 快速任务使用的模型(可选)
模型映射优先级(`src/services/api/gemini/modelMapping.ts`
1. `GEMINI_MODEL` - 直接覆盖
2. `GEMINI_DEFAULT_*_MODEL` - 独立配置(推荐)
3. `ANTHROPIC_DEFAULT_*_MODEL` - 向后兼容 fallback已废弃
4. 原样返回 Anthropic 模型名
使用示例:
```bash
export CLAUDE_CODE_USE_GEMINI=1
export GEMINI_API_KEY="your-api-key"
export GEMINI_DEFAULT_SONNET_MODEL="gemini-2.5-flash"
export GEMINI_DEFAULT_OPUS_MODEL="gemini-2.5-pro"
```
### Key Type Files
- **`src/types/global.d.ts`** — Declares `MACRO`, `BUILD_TARGET`, `BUILD_ENV` and internal Anthropic-only identifiers.

View File

@@ -0,0 +1,102 @@
import { describe, expect, test, beforeEach, afterEach, mock } from "bun:test";
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");
}
// Mock settings module
mock.module("../utils/settings/settings.js", () => ({
getInitialSettings: () => ({}),
getSettings_DEPRECATED: () => ({}),
getSettingsForSource: () => ({}),
updateSettingsForSource: () => {},
}));
mock.module("../utils/managedEnv.js", () => ({
applyConfigEnvironmentVariables: () => {},
}));
const { default: providerCommand } = await import("../provider.ts");
describe("provider command", () => {
const envKeys = [
"CLAUDE_CODE_USE_GEMINI",
"CLAUDE_CODE_USE_BEDROCK",
"CLAUDE_CODE_USE_VERTEX",
"CLAUDE_CODE_USE_FOUNDRY",
"CLAUDE_CODE_USE_OPENAI",
"GEMINI_API_KEY",
"OPENAI_API_KEY",
"OPENAI_BASE_URL",
] as const;
const savedEnv: Record<string, string | undefined> = {};
beforeEach(() => {
// 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];
} else {
delete process.env[key];
}
}
});
test("validates gemini as a valid provider", async () => {
const result = await providerCommand.load().then(cmd => cmd.call("gemini", {} as any));
expect(result).toBeDefined();
// Should not return an error about invalid provider
if (result && typeof result === 'object' && 'value' in result) {
expect(result.value as string).toContain("gemini");
}
});
test("switches to gemini without API key warning", async () => {
const result = await providerCommand.load().then(cmd => cmd.call("gemini", {} as any));
expect(result).toBeDefined();
if (result && typeof result === 'object' && 'value' in result) {
const value = result.value as string;
// Should either succeed or show warning about missing API key
expect(value).toMatch(/API provider set to gemini|Switched to Gemini provider/);
}
});
test("switches to gemini with API key set", async () => {
process.env.GEMINI_API_KEY = "test-key";
const result = await providerCommand.load().then(cmd => cmd.call("gemini", {} as any));
expect(result).toBeDefined();
if (result && typeof result === 'object' && 'value' in result) {
const value = result.value as string;
expect(value).toContain("API provider set to gemini");
expect(value).not.toContain("Warning");
}
});
test("provider list includes gemini", async () => {
// Test that help or description shows gemini is supported
expect(providerCommand.description).toContain("gemini");
expect(providerCommand.argumentHint).toContain("gemini");
});
test("unset clears gemini env var", async () => {
process.env.CLAUDE_CODE_USE_GEMINI = "1";
const result = await providerCommand.load().then(cmd => cmd.call("unset", {} as any));
expect(result).toBeDefined();
expect(process.env.CLAUDE_CODE_USE_GEMINI).toBeUndefined();
});
});

View File

@@ -13,6 +13,8 @@ function getEnvVarForProvider(provider: string): string {
return 'CLAUDE_CODE_USE_VERTEX'
case 'foundry':
return 'CLAUDE_CODE_USE_FOUNDRY'
case 'gemini':
return 'CLAUDE_CODE_USE_GEMINI'
default:
throw new Error(`Unknown provider: ${provider}`)
}
@@ -45,13 +47,27 @@ const call: LocalCommandCall = async (args, context) => {
delete process.env.CLAUDE_CODE_USE_VERTEX
delete process.env.CLAUDE_CODE_USE_FOUNDRY
delete process.env.CLAUDE_CODE_USE_OPENAI
return { type: 'text', value: 'API provider cleared (will use environment variables).' }
delete process.env.CLAUDE_CODE_USE_GEMINI
return {
type: 'text',
value: 'API provider cleared (will use environment variables).',
}
}
// Validate provider
const validProviders = ['anthropic', 'openai', 'bedrock', 'vertex', 'foundry']
const validProviders = [
'anthropic',
'openai',
'gemini',
'bedrock',
'vertex',
'foundry',
]
if (!validProviders.includes(arg)) {
return { type: 'text', value: `Invalid provider: ${arg}\nValid: ${validProviders.join(', ')}` }
return {
type: 'text',
value: `Invalid provider: ${arg}\nValid: ${validProviders.join(', ')}`,
}
}
// Check env vars when switching to openai (including settings.env)
@@ -71,37 +87,58 @@ const call: LocalCommandCall = async (args, context) => {
}
}
// Check env vars when switching to gemini (including settings.env)
if (arg === 'gemini') {
const mergedEnv = getMergedEnv()
const hasKey = !!mergedEnv.GEMINI_API_KEY
// GEMINI_BASE_URL is optional (has default)
if (!hasKey) {
updateSettingsForSource('userSettings', { modelType: 'gemini' })
return {
type: 'text',
value: `Switched to Gemini provider.\nWarning: Missing env var: GEMINI_API_KEY\nConfigure it via /login or set manually.`,
}
}
}
// Handle different provider types
// - 'anthropic' and 'openai' are stored in settings.json (persistent)
// - 'anthropic', 'openai', 'gemini' are stored in settings.json (persistent)
// - 'bedrock', 'vertex', 'foundry' are env-only (do NOT touch settings.json)
if (arg === 'anthropic' || arg === 'openai') {
if (arg === 'anthropic' || arg === 'openai' || arg === 'gemini') {
// Clear any cloud provider env vars to avoid conflicts
delete process.env.CLAUDE_CODE_USE_BEDROCK
delete process.env.CLAUDE_CODE_USE_VERTEX
delete process.env.CLAUDE_CODE_USE_FOUNDRY
delete process.env.CLAUDE_CODE_USE_OPENAI
delete process.env.CLAUDE_CODE_USE_GEMINI
// Update settings.json
updateSettingsForSource('userSettings', { modelType: arg })
// Ensure settings.env gets applied to process.env
applyConfigEnvironmentVariables()
return { type: 'text', value: `API provider set to ${arg}.` }
} else {
// Cloud providers: set env vars only, do NOT touch settings.modelType
// Cloud providers: set env vars only, do NOT touch settings.json
delete process.env.CLAUDE_CODE_USE_OPENAI
delete process.env.OPENAI_API_KEY
delete process.env.OPENAI_BASE_URL
delete process.env.CLAUDE_CODE_USE_GEMINI
process.env[getEnvVarForProvider(arg)] = '1'
// 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).` }
return {
type: 'text',
value: `API provider set to ${arg} (via environment variable).`,
}
}
}
const provider = {
type: 'local',
name: 'provider',
description: 'Switch API provider (anthropic/openai/bedrock/vertex/foundry)',
description:
'Switch API provider (anthropic/openai/gemini/bedrock/vertex/foundry)',
aliases: ['api'],
argumentHint: '[anthropic|openai|bedrock|vertex|foundry|unset]',
argumentHint: '[anthropic|openai|gemini|bedrock|vertex|foundry|unset]',
supportsNonInteractive: true,
load: () => Promise.resolve({ call }),
} satisfies Command

View File

@@ -561,9 +561,9 @@ function OAuthStatusMessage({
state: 'gemini_api',
baseUrl: process.env.GEMINI_BASE_URL ?? '',
apiKey: process.env.GEMINI_API_KEY ?? '',
haikuModel: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL ?? '',
sonnetModel: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL ?? '',
opusModel: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL ?? '',
haikuModel: process.env.GEMINI_DEFAULT_HAIKU_MODEL ?? '',
sonnetModel: process.env.GEMINI_DEFAULT_SONNET_MODEL ?? '',
opusModel: process.env.GEMINI_DEFAULT_OPUS_MODEL ?? '',
activeField: 'base_url',
})
} else if (value === 'platform') {
@@ -1127,9 +1127,9 @@ function OAuthStatusMessage({
const env: Record<string, string> = {}
if (finalVals.base_url) env.GEMINI_BASE_URL = finalVals.base_url
if (finalVals.api_key) env.GEMINI_API_KEY = finalVals.api_key
if (finalVals.haiku_model) env.ANTHROPIC_DEFAULT_HAIKU_MODEL = finalVals.haiku_model
if (finalVals.sonnet_model) env.ANTHROPIC_DEFAULT_SONNET_MODEL = finalVals.sonnet_model
if (finalVals.opus_model) env.ANTHROPIC_DEFAULT_OPUS_MODEL = finalVals.opus_model
if (finalVals.haiku_model) env.GEMINI_DEFAULT_HAIKU_MODEL = finalVals.haiku_model
if (finalVals.sonnet_model) env.GEMINI_DEFAULT_SONNET_MODEL = finalVals.sonnet_model
if (finalVals.opus_model) env.GEMINI_DEFAULT_OPUS_MODEL = finalVals.opus_model
const { error } = updateSettingsForSource('userSettings', {
modelType: 'gemini' as any,
env,

View File

@@ -4,6 +4,9 @@ import { resolveGeminiModel } from '../modelMapping.js'
describe('resolveGeminiModel', () => {
const originalEnv = {
GEMINI_MODEL: process.env.GEMINI_MODEL,
GEMINI_DEFAULT_HAIKU_MODEL: process.env.GEMINI_DEFAULT_HAIKU_MODEL,
GEMINI_DEFAULT_SONNET_MODEL: process.env.GEMINI_DEFAULT_SONNET_MODEL,
GEMINI_DEFAULT_OPUS_MODEL: process.env.GEMINI_DEFAULT_OPUS_MODEL,
ANTHROPIC_DEFAULT_HAIKU_MODEL: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL,
ANTHROPIC_DEFAULT_SONNET_MODEL: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL,
ANTHROPIC_DEFAULT_OPUS_MODEL: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL,
@@ -11,6 +14,9 @@ describe('resolveGeminiModel', () => {
beforeEach(() => {
delete process.env.GEMINI_MODEL
delete process.env.GEMINI_DEFAULT_HAIKU_MODEL
delete process.env.GEMINI_DEFAULT_SONNET_MODEL
delete process.env.GEMINI_DEFAULT_OPUS_MODEL
delete process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL
delete process.env.ANTHROPIC_DEFAULT_SONNET_MODEL
delete process.env.ANTHROPIC_DEFAULT_OPUS_MODEL
@@ -27,35 +33,57 @@ describe('resolveGeminiModel', () => {
expect(resolveGeminiModel('claude-sonnet-4-6')).toBe('gemini-2.5-pro')
})
test('resolves sonnet model from shared family override', () => {
test('GEMINI_DEFAULT_*_MODEL takes precedence over ANTHROPIC_DEFAULT_*', () => {
process.env.GEMINI_DEFAULT_SONNET_MODEL = 'gemini-2.5-flash-priority'
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = 'gemini-2.5-flash-fallback'
expect(resolveGeminiModel('claude-sonnet-4-6')).toBe(
'gemini-2.5-flash-priority',
)
})
test('resolves sonnet model from GEMINI_DEFAULT_SONNET_MODEL', () => {
process.env.GEMINI_DEFAULT_SONNET_MODEL = 'gemini-2.5-flash'
expect(resolveGeminiModel('claude-sonnet-4-6')).toBe('gemini-2.5-flash')
})
test('resolves haiku model from GEMINI_DEFAULT_HAIKU_MODEL', () => {
process.env.GEMINI_DEFAULT_HAIKU_MODEL = 'gemini-2.5-flash-lite'
expect(resolveGeminiModel('claude-haiku-4-5-20251001')).toBe(
'gemini-2.5-flash-lite',
)
})
test('resolves opus model from GEMINI_DEFAULT_OPUS_MODEL', () => {
process.env.GEMINI_DEFAULT_OPUS_MODEL = 'gemini-2.5-pro'
expect(resolveGeminiModel('claude-opus-4-6')).toBe('gemini-2.5-pro')
})
test('falls back to ANTHROPIC_DEFAULT_* when GEMINI_DEFAULT_* not set', () => {
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = 'gemini-2.5-flash'
expect(resolveGeminiModel('claude-sonnet-4-6')).toBe('gemini-2.5-flash')
})
test('resolves haiku model from shared family override', () => {
test('resolves haiku from ANTHROPIC_DEFAULT_HAIKU_MODEL as fallback', () => {
process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = 'gemini-2.5-flash-lite'
expect(resolveGeminiModel('claude-haiku-4-5-20251001')).toBe(
'gemini-2.5-flash-lite',
)
})
test('resolves opus model from shared family override', () => {
test('resolves opus from ANTHROPIC_DEFAULT_OPUS_MODEL as fallback', () => {
process.env.ANTHROPIC_DEFAULT_OPUS_MODEL = 'gemini-2.5-pro'
expect(resolveGeminiModel('claude-opus-4-6')).toBe('gemini-2.5-pro')
})
test('uses shared family override', () => {
test('uses backward compatible family override', () => {
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = 'legacy-gemini-sonnet'
expect(resolveGeminiModel('claude-sonnet-4-6')).toBe(
'legacy-gemini-sonnet',
)
expect(resolveGeminiModel('claude-sonnet-4-6')).toBe('legacy-gemini-sonnet')
})
test('strips [1m] suffix before resolving', () => {
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = 'gemini-2.5-flash'
expect(resolveGeminiModel('claude-sonnet-4-6[1m]')).toBe(
'gemini-2.5-flash',
)
process.env.GEMINI_DEFAULT_SONNET_MODEL = 'gemini-2.5-flash'
expect(resolveGeminiModel('claude-sonnet-4-6[1m]')).toBe('gemini-2.5-flash')
})
test('passes through explicit Gemini model names', () => {
@@ -64,9 +92,9 @@ describe('resolveGeminiModel', () => {
)
})
test('throws when family mapping is missing', () => {
test('throws when no Gemini model configuration is available', () => {
expect(() => resolveGeminiModel('claude-sonnet-4-6')).toThrow(
'Gemini provider requires GEMINI_MODEL or ANTHROPIC_DEFAULT_SONNET_MODEL to be configured.',
'Gemini provider requires GEMINI_MODEL or GEMINI_DEFAULT_SONNET_MODEL (or ANTHROPIC_DEFAULT_SONNET_MODEL for backward compatibility) to be configured.',
)
})
})

View File

@@ -17,6 +17,14 @@ export function resolveGeminiModel(anthropicModel: string): string {
return cleanModel
}
// First, try Gemini-specific DEFAULT variables (separated from Anthropic)
const geminiEnvVar = `GEMINI_DEFAULT_${family.toUpperCase()}_MODEL`
const geminiModel = process.env[geminiEnvVar]
if (geminiModel) {
return geminiModel
}
// Fallback to Anthropic DEFAULT variables for backward compatibility
const sharedEnvVar = `ANTHROPIC_DEFAULT_${family.toUpperCase()}_MODEL`
const resolvedModel = process.env[sharedEnvVar]
if (resolvedModel) {
@@ -24,7 +32,6 @@ export function resolveGeminiModel(anthropicModel: string): string {
}
throw new Error(
`Gemini provider requires GEMINI_MODEL or ${sharedEnvVar} to be configured.`,
`Gemini provider requires GEMINI_MODEL or ${geminiEnvVar} (or ${sharedEnvVar} for backward compatibility) to be configured.`,
)
}

View File

@@ -78,6 +78,20 @@ const PROVIDER_MANAGED_ENV_VARS = new Set([
'ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION',
'CLAUDE_CODE_SUBAGENT_MODEL',
'GEMINI_MODEL',
'GEMINI_SMALL_FAST_MODEL',
// Gemini provider specific - separate from Anthropic/OpenAI
'GEMINI_DEFAULT_HAIKU_MODEL',
'GEMINI_DEFAULT_HAIKU_MODEL_DESCRIPTION',
'GEMINI_DEFAULT_HAIKU_MODEL_NAME',
'GEMINI_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES',
'GEMINI_DEFAULT_OPUS_MODEL',
'GEMINI_DEFAULT_OPUS_MODEL_DESCRIPTION',
'GEMINI_DEFAULT_OPUS_MODEL_NAME',
'GEMINI_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES',
'GEMINI_DEFAULT_SONNET_MODEL',
'GEMINI_DEFAULT_SONNET_MODEL_DESCRIPTION',
'GEMINI_DEFAULT_SONNET_MODEL_NAME',
'GEMINI_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES',
])
const PROVIDER_MANAGED_ENV_PREFIXES = [
@@ -188,6 +202,19 @@ export const SAFE_ENV_VARS = new Set([
'CLAUDE_CODE_USE_GEMINI',
'CLAUDE_CODE_USE_VERTEX',
'GEMINI_MODEL',
'GEMINI_SMALL_FAST_MODEL',
'GEMINI_DEFAULT_HAIKU_MODEL',
'GEMINI_DEFAULT_HAIKU_MODEL_DESCRIPTION',
'GEMINI_DEFAULT_HAIKU_MODEL_NAME',
'GEMINI_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES',
'GEMINI_DEFAULT_OPUS_MODEL',
'GEMINI_DEFAULT_OPUS_MODEL_DESCRIPTION',
'GEMINI_DEFAULT_OPUS_MODEL_NAME',
'GEMINI_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES',
'GEMINI_DEFAULT_SONNET_MODEL',
'GEMINI_DEFAULT_SONNET_MODEL_DESCRIPTION',
'GEMINI_DEFAULT_SONNET_MODEL_NAME',
'GEMINI_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES',
'DISABLE_AUTOUPDATER',
'DISABLE_BUG_COMMAND',
'DISABLE_COST_WARNINGS',

View File

@@ -35,6 +35,15 @@ export type ModelName = string
export type ModelSetting = ModelName | ModelAlias | null
export function getSmallFastModel(): ModelName {
const provider = getAPIProvider()
// Provider-specific small fast model
if (provider === 'openai' && process.env.OPENAI_SMALL_FAST_MODEL) {
return process.env.OPENAI_SMALL_FAST_MODEL
}
if (provider === 'gemini' && process.env.GEMINI_SMALL_FAST_MODEL) {
return process.env.GEMINI_SMALL_FAST_MODEL
}
// Anthropic-specific or fallback
return process.env.ANTHROPIC_SMALL_FAST_MODEL || getDefaultHaikuModel()
}
@@ -104,10 +113,15 @@ export function getBestModel(): ModelName {
// @[MODEL LAUNCH]: Update the default Opus model (3P providers may lag so keep defaults unchanged).
export function getDefaultOpusModel(): ModelName {
const provider = getAPIProvider()
// For OpenAI provider, check OPENAI_DEFAULT_OPUS_MODEL first
if (getAPIProvider() === 'openai' && process.env.OPENAI_DEFAULT_OPUS_MODEL) {
if (provider === 'openai' && process.env.OPENAI_DEFAULT_OPUS_MODEL) {
return process.env.OPENAI_DEFAULT_OPUS_MODEL
}
// For Gemini provider, check GEMINI_DEFAULT_OPUS_MODEL
if (provider === 'gemini' && process.env.GEMINI_DEFAULT_OPUS_MODEL) {
return process.env.GEMINI_DEFAULT_OPUS_MODEL
}
// Anthropic-specific override (for first-party and other 3P providers)
if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL) {
return process.env.ANTHROPIC_DEFAULT_OPUS_MODEL
@@ -115,7 +129,7 @@ export function getDefaultOpusModel(): ModelName {
// 3P providers (Bedrock, Vertex, Foundry) — kept as a separate branch
// even when values match, since 3P availability lags firstParty and
// these will diverge again at the next model launch.
if (getAPIProvider() !== 'firstParty') {
if (provider !== 'firstParty') {
return getModelStrings().opus46
}
return getModelStrings().opus46
@@ -123,19 +137,24 @@ export function getDefaultOpusModel(): ModelName {
// @[MODEL LAUNCH]: Update the default Sonnet model (3P providers may lag so keep defaults unchanged).
export function getDefaultSonnetModel(): ModelName {
const provider = getAPIProvider()
// For OpenAI provider, check OPENAI_DEFAULT_SONNET_MODEL first
if (
getAPIProvider() === 'openai' &&
provider === 'openai' &&
process.env.OPENAI_DEFAULT_SONNET_MODEL
) {
return process.env.OPENAI_DEFAULT_SONNET_MODEL
}
// For Gemini provider, check GEMINI_DEFAULT_SONNET_MODEL
if (provider === 'gemini' && process.env.GEMINI_DEFAULT_SONNET_MODEL) {
return process.env.GEMINI_DEFAULT_SONNET_MODEL
}
// Anthropic-specific override (for first-party and other 3P providers)
if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL) {
return process.env.ANTHROPIC_DEFAULT_SONNET_MODEL
}
// Default to Sonnet 4.5 for 3P since they may not have 4.6 yet
if (getAPIProvider() !== 'firstParty') {
if (provider !== 'firstParty') {
return getModelStrings().sonnet45
}
return getModelStrings().sonnet46
@@ -143,10 +162,15 @@ export function getDefaultSonnetModel(): ModelName {
// @[MODEL LAUNCH]: Update the default Haiku model (3P providers may lag so keep defaults unchanged).
export function getDefaultHaikuModel(): ModelName {
const provider = getAPIProvider()
// For OpenAI provider, check OPENAI_DEFAULT_HAIKU_MODEL first
if (getAPIProvider() === 'openai' && process.env.OPENAI_DEFAULT_HAIKU_MODEL) {
if (provider === 'openai' && process.env.OPENAI_DEFAULT_HAIKU_MODEL) {
return process.env.OPENAI_DEFAULT_HAIKU_MODEL
}
// For Gemini provider, check GEMINI_DEFAULT_HAIKU_MODEL
if (provider === 'gemini' && process.env.GEMINI_DEFAULT_HAIKU_MODEL) {
return process.env.GEMINI_DEFAULT_HAIKU_MODEL
}
// Anthropic-specific override (for first-party and other 3P providers)
if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL) {
return process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL

View File

@@ -76,22 +76,29 @@ export function getDefaultOptionForUser(fastMode = false): ModelOption {
function getCustomSonnetOption(): ModelOption | undefined {
const is3P = getAPIProvider() !== 'firstParty'
// For OpenAI provider, use OPENAI_DEFAULT_SONNET_MODEL; for other 3P, use ANTHROPIC_DEFAULT_SONNET_MODEL
const provider = getAPIProvider()
// Use provider-specific DEFAULT_SONNET_MODEL
const customSonnetModel =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_SONNET_MODEL
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_SONNET_MODEL
: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL
// When a 3P user has a custom sonnet model string, show it directly
if (is3P && customSonnetModel) {
const is1m = has1mContext(customSonnetModel)
// Use appropriate NAME/DESCRIPTION env vars based on provider
const nameEnv =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_SONNET_MODEL_NAME
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_SONNET_MODEL_NAME
: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME
const descEnv =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_SONNET_MODEL_DESCRIPTION
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_SONNET_MODEL_DESCRIPTION
: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION
return {
value: 'sonnet',
@@ -118,22 +125,29 @@ function getSonnet46Option(): ModelOption {
function getCustomOpusOption(): ModelOption | undefined {
const is3P = getAPIProvider() !== 'firstParty'
// For OpenAI provider, use OPENAI_DEFAULT_OPUS_MODEL; for other 3P, use ANTHROPIC_DEFAULT_OPUS_MODEL
const provider = getAPIProvider()
// Use provider-specific DEFAULT_OPUS_MODEL
const customOpusModel =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_OPUS_MODEL
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_OPUS_MODEL
: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL
// When a 3P user has a custom opus model string, show it directly
if (is3P && customOpusModel) {
const is1m = has1mContext(customOpusModel)
// Use appropriate NAME/DESCRIPTION env vars based on provider
const nameEnv =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_OPUS_MODEL_NAME
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_OPUS_MODEL_NAME
: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME
const descEnv =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_OPUS_MODEL_DESCRIPTION
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_OPUS_MODEL_DESCRIPTION
: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION
return {
value: 'opus',
@@ -187,21 +201,28 @@ export function getOpus46_1MOption(fastMode = false): ModelOption {
function getCustomHaikuOption(): ModelOption | undefined {
const is3P = getAPIProvider() !== 'firstParty'
// For OpenAI provider, use OPENAI_DEFAULT_HAIKU_MODEL; for other 3P, use ANTHROPIC_DEFAULT_HAIKU_MODEL
const provider = getAPIProvider()
// Use provider-specific DEFAULT_HAIKU_MODEL
const customHaikuModel =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_HAIKU_MODEL
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_HAIKU_MODEL
: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL
// When a 3P user has a custom haiku model string, show it directly
if (is3P && customHaikuModel) {
// Use appropriate NAME/DESCRIPTION env vars based on provider
const nameEnv =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_HAIKU_MODEL_NAME
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_HAIKU_MODEL_NAME
: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME
const descEnv =
getAPIProvider() === 'openai'
provider === 'openai'
? process.env.OPENAI_DEFAULT_HAIKU_MODEL_DESCRIPTION
: provider === 'gemini'
? process.env.GEMINI_DEFAULT_HAIKU_MODEL_DESCRIPTION
: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION
return {
value: 'haiku',