From 21ee80c722f8a48784fef72d6c53ca0a0ff4974e Mon Sep 17 00:00:00 2001 From: linmaog <12575858+linmaog@user.noreply.gitee.com> Date: Thu, 30 Jul 2026 10:45:47 +0800 Subject: [PATCH] fix(providers): use Sonnet mapping for connection-test model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Connection tests previously preferred a hidden preset catalog alias (for example "sonnet") over the user's mapped Sonnet model, so third- party Anthropic providers could fail the test while chat still worked. Resolve the test payload as explicit default → mapped Sonnet → server fallback, and only pre-fill modelName when the preset exposes the model_names field (preserving MiMo #577A). Related: #461 --- src/__tests__/unit/mimo-model-mapping.test.ts | 8 +-- .../provider-connection-test-model.test.ts | 49 +++++++++++++++++++ .../settings/PresetConnectDialog.tsx | 17 ++++--- src/lib/provider-connection-test-model.ts | 6 +++ 4 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 src/__tests__/unit/provider-connection-test-model.test.ts create mode 100644 src/lib/provider-connection-test-model.ts diff --git a/src/__tests__/unit/mimo-model-mapping.test.ts b/src/__tests__/unit/mimo-model-mapping.test.ts index b86b69d9..9bfaa0b9 100644 --- a/src/__tests__/unit/mimo-model-mapping.test.ts +++ b/src/__tests__/unit/mimo-model-mapping.test.ts @@ -126,7 +126,7 @@ describe('MiMo resolver honors a user-set model (no silent revert) — #577A', ( // ── Wiring source pins (connect dialog pre-fill) ───────────────────────────── -describe('connect dialog pre-fills the model field from the preset default (#577A)', () => { +describe('connect dialog pre-fills exposed model fields from the preset default (#577A)', () => { const presetsSrc = fs.readFileSync( path.resolve(__dirname, '../../components/settings/provider-presets.tsx'), 'utf8', @@ -144,11 +144,11 @@ describe('connect dialog pre-fills the model field from the preset default (#577 ); }); - it('create mode pre-fills modelName from preset.defaultModelId (not empty)', () => { + it('create mode pre-fills modelName only when the preset exposes model_names', () => { assert.match( dialogSrc, - /setModelName\(preset\.defaultModelId \|\| ""\)/, - 'create mode must pre-fill the model field from the preset default', + /setModelName\(\s*preset\.fields\.includes\("model_names"\)\s*\?\s*\(preset\.defaultModelId \|\| ""\)\s*:\s*""\s*,?\s*\)/, + 'create mode must pre-fill the model field from the preset default without seeding a hidden default for mapping-only presets', ); }); }); diff --git a/src/__tests__/unit/provider-connection-test-model.test.ts b/src/__tests__/unit/provider-connection-test-model.test.ts new file mode 100644 index 00000000..a71f36e4 --- /dev/null +++ b/src/__tests__/unit/provider-connection-test-model.test.ts @@ -0,0 +1,49 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { resolveConnectionTestModelName } from '../../lib/provider-connection-test-model'; + +describe('resolveConnectionTestModelName', () => { + it('uses the mapped Sonnet model when no default model is configured', () => { + assert.equal( + resolveConnectionTestModelName('', 'claude-sonnet-4-6'), + 'claude-sonnet-4-6', + ); + }); + + it('prefers an explicitly configured default model over the Sonnet mapping', () => { + assert.equal( + resolveConnectionTestModelName('custom-default-model', 'claude-sonnet-4-6'), + 'custom-default-model', + ); + }); + + it('leaves model selection to the existing connection-test fallback when both fields are empty', () => { + assert.equal(resolveConnectionTestModelName('', ''), undefined); + }); + + it('uses the resolver when building the connection-test payload', () => { + const dialogSource = fs.readFileSync( + path.resolve(process.cwd(), 'src/components/settings/PresetConnectDialog.tsx'), + 'utf8', + ); + + assert.match( + dialogSource, + /modelName:\s*resolveConnectionTestModelName\(modelName,\s*mapSonnet\)/, + ); + }); + + it('does not seed a hidden default model for mapping-only presets', () => { + const dialogSource = fs.readFileSync( + path.resolve(process.cwd(), 'src/components/settings/PresetConnectDialog.tsx'), + 'utf8', + ); + + assert.match( + dialogSource, + /setModelName\(\s*preset\.fields\.includes\("model_names"\)\s*\?\s*\(preset\.defaultModelId \|\| ""\)\s*:\s*""\s*,?\s*\)/, + ); + }); +}); diff --git a/src/components/settings/PresetConnectDialog.tsx b/src/components/settings/PresetConnectDialog.tsx index c95bb2b7..c80391da 100644 --- a/src/components/settings/PresetConnectDialog.tsx +++ b/src/components/settings/PresetConnectDialog.tsx @@ -28,6 +28,7 @@ import { QUICK_PRESETS } from "./provider-presets"; import type { ApiProvider } from "@/types"; import { useTranslation } from "@/hooks/useTranslation"; import type { TranslationKey } from "@/i18n"; +import { resolveConnectionTestModelName } from "@/lib/provider-connection-test-model"; /** Infer auth style from base URL by fuzzy-matching preset hostnames */ function inferAuthStyleFromUrl(url: string): "api_key" | "auth_token" | null { @@ -156,7 +157,7 @@ export function PresetConnectDialog({ protocol: preset?.protocol || 'anthropic', authStyle: preset?.key === 'anthropic-thirdparty' ? authStyle : (preset?.authStyle || authStyle), envOverrides, - modelName: modelName || undefined, + modelName: resolveConnectionTestModelName(modelName, mapSonnet), providerName: name || preset?.name, }; if (isEdit && editProvider) { @@ -272,12 +273,14 @@ export function PresetConnectDialog({ setBaseUrl(preset.base_url); setName(preset.name); setExtraEnv(preset.extra_env); - // Pre-fill the model-name field with the preset's default model id so a - // preset that requires a user-specified model (e.g. MiMo) shows its - // current model (editable) rather than an empty box (#577). Harmless for - // presets without the model_names field — the value is only read on save - // when that field is exposed. - setModelName(preset.defaultModelId || ""); + // Pre-fill only an exposed model-name field with the preset default so a + // provider such as MiMo shows its editable current model (#577). + // Mapping-only presets must keep this empty: otherwise their hidden + // catalog alias (for example, "sonnet") overrides a Sonnet mapping in + // the connection-test payload. + setModelName( + preset.fields.includes("model_names") ? (preset.defaultModelId || "") : "", + ); // Use authStyle directly from preset (single source of truth) const detectedStyle = (preset.authStyle === 'auth_token' ? 'auth_token' : 'api_key') as 'api_key' | 'auth_token'; // If preset doesn't expose api_key field, pre-fill from extra_env default diff --git a/src/lib/provider-connection-test-model.ts b/src/lib/provider-connection-test-model.ts new file mode 100644 index 00000000..088a490f --- /dev/null +++ b/src/lib/provider-connection-test-model.ts @@ -0,0 +1,6 @@ +export function resolveConnectionTestModelName( + defaultModelName: string, + mappedSonnetModelName: string, +): string | undefined { + return defaultModelName || mappedSonnetModelName || undefined; +}