diff --git a/desktop/src/__tests__/generalSettings.test.tsx b/desktop/src/__tests__/generalSettings.test.tsx
index 0a6fb50e..40367de5 100644
--- a/desktop/src/__tests__/generalSettings.test.tsx
+++ b/desktop/src/__tests__/generalSettings.test.tsx
@@ -1683,6 +1683,100 @@ describe('Settings > Providers tab', () => {
})
})
+ it('uses request model env instead of cc-switch display model names when testing pasted settings JSON', async () => {
+ providerStoreState.testConfig = vi.fn().mockResolvedValue({
+ connectivity: {
+ success: false,
+ latencyMs: 3,
+ error: '未配置供应商',
+ modelUsed: 'claude-sonnet-4-6',
+ httpStatus: 503,
+ },
+ })
+ providerStoreState.presets = [
+ {
+ id: 'deepseek',
+ name: 'DeepSeek',
+ baseUrl: 'https://api.deepseek.com/anthropic',
+ apiFormat: 'anthropic',
+ defaultModels: {
+ main: 'deepseek-v4-pro',
+ haiku: 'deepseek-v4-flash',
+ sonnet: 'deepseek-v4-pro',
+ opus: 'deepseek-v4-pro',
+ },
+ needsApiKey: true,
+ websiteUrl: '',
+ },
+ {
+ id: 'custom',
+ name: 'Custom',
+ baseUrl: '',
+ apiFormat: 'anthropic',
+ defaultModels: {
+ main: '',
+ haiku: '',
+ sonnet: '',
+ opus: '',
+ },
+ needsApiKey: true,
+ websiteUrl: '',
+ },
+ ]
+
+ render()
+
+ fireEvent.click(screen.getByRole('button', { name: /Add Provider|添加服务商/i }))
+ const dialog = screen.getByRole('dialog')
+ const settingsTextarea = await waitFor(() => {
+ const textarea = dialog.querySelector('textarea')
+ expect(textarea?.value).toContain('"ANTHROPIC_MODEL"')
+ return textarea as HTMLTextAreaElement
+ })
+
+ fireEvent.change(settingsTextarea, {
+ target: {
+ value: JSON.stringify({
+ env: {
+ ANTHROPIC_API_KEY: 'PROXY_MANAGED',
+ ANTHROPIC_BASE_URL: 'http://127.0.0.1:15721',
+ ANTHROPIC_DEFAULT_FABLE_MODEL: 'Qwen3Coder',
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5',
+ ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME: 'Qwen3Coder',
+ ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-8',
+ ANTHROPIC_DEFAULT_OPUS_MODEL_NAME: 'Qwen3Coder',
+ ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6',
+ ANTHROPIC_DEFAULT_SONNET_MODEL_NAME: 'Qwen3Coder',
+ },
+ }, null, 2),
+ },
+ })
+
+ await waitFor(() => {
+ expect(within(dialog).getByLabelText(/Main Model|主模型/i)).toHaveValue('claude-sonnet-4-6')
+ expect(within(dialog).getByLabelText(/Haiku Model/i)).toHaveValue('claude-haiku-4-5')
+ expect(within(dialog).getByLabelText(/Opus Model/i)).toHaveValue('claude-opus-4-8')
+ })
+
+ fireEvent.click(within(dialog).getByRole('button', { name: /Test Connection/i }))
+
+ await waitFor(() => {
+ expect(providerStoreState.testConfig).toHaveBeenCalledWith(expect.objectContaining({
+ baseUrl: 'http://127.0.0.1:15721',
+ apiKey: 'PROXY_MANAGED',
+ modelId: 'claude-sonnet-4-6',
+ authStrategy: 'api_key',
+ apiFormat: 'anthropic',
+ }))
+ })
+ expect(providerStoreState.testConfig).not.toHaveBeenCalledWith(expect.objectContaining({
+ modelId: 'Qwen3Coder',
+ }))
+ expect(providerStoreState.testConfig).not.toHaveBeenCalledWith(expect.objectContaining({
+ modelId: 'deepseek-v4-pro',
+ }))
+ })
+
it('keeps the provider form locked while save is in flight', async () => {
let resolveCreate!: (provider: SavedProvider) => void
providerStoreState.createProvider = vi.fn().mockImplementation(() => new Promise((resolve) => {
diff --git a/desktop/src/lib/__tests__/providerSettingsJson.test.ts b/desktop/src/lib/__tests__/providerSettingsJson.test.ts
index 6bd9795d..d6dd7300 100644
--- a/desktop/src/lib/__tests__/providerSettingsJson.test.ts
+++ b/desktop/src/lib/__tests__/providerSettingsJson.test.ts
@@ -24,6 +24,22 @@ describe('provider settings JSON helpers', () => {
expect(masked.env.OTHER_VALUE).toBe('visible')
})
+ it('keeps cc-switch proxy-managed placeholders visible', () => {
+ const raw = JSON.stringify({
+ env: {
+ ANTHROPIC_API_KEY: 'PROXY_MANAGED',
+ ANTHROPIC_AUTH_TOKEN: 'proxy-managed',
+ OTHER_VALUE: 'visible',
+ },
+ })
+
+ const masked = JSON.parse(maskSettingsJsonSecrets(raw)) as { env: Record }
+
+ expect(masked.env.ANTHROPIC_API_KEY).toBe('PROXY_MANAGED')
+ expect(masked.env.ANTHROPIC_AUTH_TOKEN).toBe('proxy-managed')
+ expect(masked.env.OTHER_VALUE).toBe('visible')
+ })
+
it('restores masked Anthropic API key env vars from their previous field values', () => {
const previousRaw = JSON.stringify({
env: {
diff --git a/desktop/src/lib/providerSettingsJson.ts b/desktop/src/lib/providerSettingsJson.ts
index 78bd34df..486908a2 100644
--- a/desktop/src/lib/providerSettingsJson.ts
+++ b/desktop/src/lib/providerSettingsJson.ts
@@ -1,6 +1,7 @@
export const API_KEY_JSON_PLACEHOLDER = '••••••••'
const API_KEY_JSON_KEYS = ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'] as const
+const PROXY_MANAGED_PLACEHOLDERS = new Set(['proxy-managed', 'proxy_managed'])
const PROVIDER_SETTINGS_JSON_ENV_KEYS = new Set([
'ANTHROPIC_BASE_URL',
@@ -41,12 +42,13 @@ function getEnvRecord(raw: string): Record | null {
}
function isSecretDisplayValue(value: unknown): value is string {
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : ''
return (
typeof value === 'string' &&
value.trim() !== '' &&
value !== API_KEY_JSON_PLACEHOLDER &&
value !== '(your API key)' &&
- value !== 'proxy-managed'
+ !PROXY_MANAGED_PLACEHOLDERS.has(normalized)
)
}
diff --git a/desktop/src/pages/Settings.tsx b/desktop/src/pages/Settings.tsx
index 241b8f85..cfdc963a 100644
--- a/desktop/src/pages/Settings.tsx
+++ b/desktop/src/pages/Settings.tsx
@@ -928,6 +928,27 @@ function normalizeModelMapping(models: ModelMapping): ModelMapping {
}
}
+function readSettingsEnvString(env: Record, key: string): string | undefined {
+ const value = env[key]
+ if (typeof value !== 'string') return undefined
+ const trimmed = value.trim()
+ return trimmed || undefined
+}
+
+function readModelMappingFromSettingsEnv(env: Record): Partial {
+ const haiku = readSettingsEnvString(env, 'ANTHROPIC_DEFAULT_HAIKU_MODEL')
+ const sonnet = readSettingsEnvString(env, 'ANTHROPIC_DEFAULT_SONNET_MODEL')
+ const opus = readSettingsEnvString(env, 'ANTHROPIC_DEFAULT_OPUS_MODEL')
+ const main = readSettingsEnvString(env, 'ANTHROPIC_MODEL') ?? sonnet ?? haiku ?? opus
+
+ return {
+ ...(main ? { main } : {}),
+ ...(haiku ? { haiku } : {}),
+ ...(sonnet ? { sonnet } : {}),
+ ...(opus ? { opus } : {}),
+ }
+}
+
function applyToolSearchEnv(
env: Record,
apiFormat: ApiFormat,
@@ -1854,11 +1875,7 @@ function ProviderFormModal({ open, onClose, mode, provider, presets }: ProviderF
parsedContextWindows = {}
}
}
- const newModels: Partial = {}
- if (env.ANTHROPIC_MODEL) newModels.main = env.ANTHROPIC_MODEL
- if (env.ANTHROPIC_DEFAULT_HAIKU_MODEL) newModels.haiku = env.ANTHROPIC_DEFAULT_HAIKU_MODEL
- if (env.ANTHROPIC_DEFAULT_SONNET_MODEL) newModels.sonnet = env.ANTHROPIC_DEFAULT_SONNET_MODEL
- if (env.ANTHROPIC_DEFAULT_OPUS_MODEL) newModels.opus = env.ANTHROPIC_DEFAULT_OPUS_MODEL
+ const newModels = readModelMappingFromSettingsEnv(env)
if (Object.keys(newModels).length > 0) {
setModels((prev) => {
const mergedModels = { ...prev, ...newModels }