mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
gemini模型环境变量分离 provider指令支持切换gemini
This commit is contained in:
31
CLAUDE.md
31
CLAUDE.md
@@ -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.
|
||||
|
||||
102
src/commands/__tests__/provider.test.ts
Normal file
102
src/commands/__tests__/provider.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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.`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user