From ba7de19a1fac46abd0962df357d2b29b6652bb6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Tue, 22 Sep 2026 02:32:16 +0800 Subject: [PATCH] fix(desktop): keep the session model out of a new provider's settings JSON The add-model editor was spreading the whole managed settings file, so the model picker's selected default (model / modelContext) showed up as if it belonged to the provider being added and was written back on save. --- .../src/__tests__/generalSettings.test.tsx | 63 +++++++++++++++++++ .../src/pages/settings/ProviderSettings.tsx | 11 ++++ 2 files changed, 74 insertions(+) diff --git a/desktop/src/__tests__/generalSettings.test.tsx b/desktop/src/__tests__/generalSettings.test.tsx index 0170230f..8ff1d62b 100644 --- a/desktop/src/__tests__/generalSettings.test.tsx +++ b/desktop/src/__tests__/generalSettings.test.tsx @@ -2733,6 +2733,69 @@ describe('Settings > Providers tab', () => { }) }) + it('omits the session default model from a new provider settings JSON', async () => { + MOCK_GET_SETTINGS.mockResolvedValue({ + model: 'grok-4.7', + modelContext: '1m', + futureSetting: true, + env: { EXISTING_ENV: '1' }, + }) + providerStoreState.createProvider = vi.fn().mockResolvedValue({ + id: 'provider-new', + presetId: 'custom', + name: 'Custom', + apiKey: 'sk-test', + baseUrl: 'https://api.example.com/anthropic', + apiFormat: 'anthropic', + models: { + main: 'custom-main', + haiku: 'custom-main', + sonnet: 'custom-main', + opus: 'custom-main', + }, + }) + providerStoreState.presets = [ + { + id: 'custom', + name: 'Custom', + baseUrl: 'https://api.example.com/anthropic', + apiFormat: 'anthropic', + defaultModels: { + main: 'custom-main', + haiku: '', + sonnet: '', + opus: '', + }, + needsApiKey: true, + websiteUrl: '', + }, + ] + + render() + fireEvent.click(screen.getByRole('button', { name: /Add Model/i })) + const dialog = screen.getByRole('dialog') + const settingsTextarea = await waitFor(() => { + const textarea = dialog.querySelector('textarea') + expect(textarea?.value).toContain('"EXISTING_ENV"') + return textarea as HTMLTextAreaElement + }) + const displayed = JSON.parse(settingsTextarea.value) as Record + expect(displayed).not.toHaveProperty('model') + expect(displayed).not.toHaveProperty('modelContext') + expect(displayed).toMatchObject({ futureSetting: true }) + + fireEvent.change(within(dialog).getByPlaceholderText('sk-...'), { target: { value: 'sk-test' } }) + fireEvent.click(within(dialog).getByRole('button', { name: /Save|Add/i })) + + await waitFor(() => { + expect(MOCK_UPDATE_SETTINGS).toHaveBeenCalled() + }) + const saved = MOCK_UPDATE_SETTINGS.mock.calls.at(-1)?.[0] as Record + expect(saved).not.toHaveProperty('model') + expect(saved).not.toHaveProperty('modelContext') + expect(saved).toMatchObject({ futureSetting: true, env: expect.objectContaining({ EXISTING_ENV: '1' }) }) + }) + it('defaults Tool Search off and requires confirmation before persisting an explicit enable', async () => { MOCK_GET_SETTINGS.mockResolvedValue({ env: { EXISTING_ENV: '1' } }) providerStoreState.createProvider = vi.fn().mockResolvedValue({ diff --git a/desktop/src/pages/settings/ProviderSettings.tsx b/desktop/src/pages/settings/ProviderSettings.tsx index 4ca09510..0465c6af 100644 --- a/desktop/src/pages/settings/ProviderSettings.tsx +++ b/desktop/src/pages/settings/ProviderSettings.tsx @@ -1156,6 +1156,12 @@ function ProviderFormModal({ open, onClose, mode, provider, presets, browserMode skipWebFetchPreflight: settings.skipWebFetchPreflight ?? true, env: mergedEnv, } + // `model` / `modelContext` are the session's selected default, written by + // the model picker. They are not part of the provider being added, so + // showing them here makes a new provider look like it inherits Grok 4.7 + // (or whatever was last selected) and saving would write that back. + delete merged.model + delete merged.modelContext setSettingsJson(JSON.stringify(writeCompatibilityJson(merged, apiFormat === 'anthropic' ? undefined : parseCompatibilityForm(compatibility)), null, 2)) }).catch(() => { if (!cancelled && !settingsJsonUserEditedRef.current) { @@ -1569,6 +1575,11 @@ function ProviderFormModal({ open, onClose, mode, provider, presets, browserMode const { providersApi } = await import('../../api/providers') const settings = writeCompatibilityJson(parsed, storedCompatibility) delete settings.requestCompatibility + // The editor never owns the session default model. updateSettings merges + // by replacing the whole object, so omitting these keys keeps the + // model picker's selection instead of clearing it. + delete settings.model + delete settings.modelContext await providersApi.updateSettings(settings) } catch { // JSON validation already prevents this