mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
fix(desktop): import cc-switch model ids from settings
Reproduce: pasted a cc-switch-style settings JSON with PROXY_MANAGED auth, claude-* request model env vars, and Qwen3Coder *_MODEL_NAME display labels into the Provider form test path. Fix: derive the Provider main model from ANTHROPIC_MODEL or the Sonnet model env fallback, and keep *_MODEL_NAME values as display labels only. Treat both proxy-managed and PROXY_MANAGED placeholders as non-secret display placeholders. Tested: bun test desktop/src/lib/__tests__/providerSettingsJson.test.ts Tested: cd desktop && bun run test generalSettings.test.tsx Tested: bun run check:desktop Confidence: high Scope-risk: narrow
This commit is contained in:
parent
c907144788
commit
be8b004c36
@ -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(<Settings />)
|
||||
|
||||
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<SavedProvider>((resolve) => {
|
||||
|
||||
@ -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<string, string> }
|
||||
|
||||
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: {
|
||||
|
||||
@ -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<string, unknown> | 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)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -928,6 +928,27 @@ function normalizeModelMapping(models: ModelMapping): ModelMapping {
|
||||
}
|
||||
}
|
||||
|
||||
function readSettingsEnvString(env: Record<string, unknown>, 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<string, unknown>): Partial<ModelMapping> {
|
||||
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<string, unknown>,
|
||||
apiFormat: ApiFormat,
|
||||
@ -1854,11 +1875,7 @@ function ProviderFormModal({ open, onClose, mode, provider, presets }: ProviderF
|
||||
parsedContextWindows = {}
|
||||
}
|
||||
}
|
||||
const newModels: Partial<ModelMapping> = {}
|
||||
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 }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user