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.
This commit is contained in:
程序员阿江(Relakkes)
2026-09-22 02:32:16 +08:00
parent d25b789803
commit ba7de19a1f
2 changed files with 74 additions and 0 deletions
@@ -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(<Settings />)
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<string, unknown>
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<string, unknown>
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({
@@ -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