diff --git a/desktop/src/components/controls/ModelSelector.test.tsx b/desktop/src/components/controls/ModelSelector.test.tsx
index 25495a67..84c318c8 100644
--- a/desktop/src/components/controls/ModelSelector.test.tsx
+++ b/desktop/src/components/controls/ModelSelector.test.tsx
@@ -97,11 +97,12 @@ describe('ModelSelector', () => {
render()
- await clickByRole(/alpha/i)
+ await clickByRole(/provider-main/i)
await act(async () => {
+ fireEvent.keyDown(document, { key: 'Escape' })
await Promise.resolve()
})
- await clickByRole(/alpha/i)
+ await clickByRole(/provider-main/i)
expect(fetchClaudeStatus).toHaveBeenCalledTimes(1)
expect(fetchOpenAIStatus).toHaveBeenCalledTimes(1)
@@ -192,7 +193,7 @@ describe('ModelSelector', () => {
render()
- await clickByRole(/alpha/i)
+ await clickByRole(/provider-main/i)
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /provider-fast/ }))
await Promise.resolve()
@@ -210,6 +211,51 @@ describe('ModelSelector', () => {
})
})
+ it('defaults blank provider-scoped runtime selections to the active provider main model', async () => {
+ useSettingsStore.setState({
+ locale: 'en',
+ availableModels: [
+ { id: 'deepseek-v4-flash', name: 'deepseek-v4-flash', description: 'Main Model · Haiku Model', context: '' },
+ { id: 'deepseek-v4-pro', name: 'deepseek-v4-pro', description: 'Sonnet Model · Opus Model', context: '' },
+ ],
+ currentModel: { id: 'deepseek-v4-pro', name: 'deepseek-v4-pro', description: 'Sonnet Model · Opus Model', context: '' },
+ activeProviderName: 'Custom-DeepSeek-OpenAI',
+ })
+ useProviderStore.setState({
+ providers: [{
+ id: 'deepseek-provider',
+ presetId: 'custom',
+ name: 'Custom-DeepSeek-OpenAI',
+ apiKey: '***',
+ baseUrl: 'https://api.deepseek.com',
+ apiFormat: 'openai_chat',
+ models: {
+ main: 'deepseek-v4-flash',
+ haiku: 'deepseek-v4-flash',
+ sonnet: 'deepseek-v4-pro',
+ opus: 'deepseek-v4-pro',
+ },
+ }],
+ activeId: 'deepseek-provider',
+ hasLoadedProviders: true,
+ isLoading: true,
+ })
+
+ render()
+
+ const trigger = screen.getByRole('button', { name: /deepseek-v4-flash/i })
+ await act(async () => {
+ fireEvent.click(trigger)
+ await Promise.resolve()
+ })
+
+ const flashOption = screen
+ .getAllByRole('button', { name: /deepseek-v4-flash/i })
+ .find((button) => button.textContent?.includes('Main Model'))
+ expect(flashOption).toBeDefined()
+ expect(flashOption?.className).toContain('border-[var(--color-model-option-selected-border)]')
+ })
+
it('keeps runtime effort scoped to the selected session', async () => {
const setSessionRuntime = vi.fn()
useSettingsStore.setState({
@@ -249,12 +295,12 @@ describe('ModelSelector', () => {
render()
- await clickByRole(/alpha/i)
+ await clickByRole(/provider-main/i)
await clickByRole(/^High$/)
expect(useSessionRuntimeStore.getState().selections['session-1']).toEqual({
providerId: 'provider-a',
- modelId: 'alpha',
+ modelId: 'provider-main',
effortLevel: 'high',
})
expect(useSessionRuntimeStore.getState().selections['session-2']).toEqual({
@@ -264,7 +310,7 @@ describe('ModelSelector', () => {
})
expect(setSessionRuntime).toHaveBeenCalledWith('session-1', {
providerId: 'provider-a',
- modelId: 'alpha',
+ modelId: 'provider-main',
effortLevel: 'high',
})
expect(useSettingsStore.getState().effortLevel).toBe('max')
@@ -357,7 +403,7 @@ describe('ModelSelector', () => {
render()
- await clickByRole(/alpha/i)
+ await clickByRole(/provider-main/i)
const dropdown = screen.getByTestId('model-selector-dropdown')
expect(dropdown.textContent).not.toContain('Claude Official')
diff --git a/desktop/src/components/controls/ModelSelector.tsx b/desktop/src/components/controls/ModelSelector.tsx
index 392e2c5c..306ea2a0 100644
--- a/desktop/src/components/controls/ModelSelector.tsx
+++ b/desktop/src/components/controls/ModelSelector.tsx
@@ -146,15 +146,17 @@ function resolveDefaultRuntimeSelection(
providers: SavedProvider[],
currentModelId: string | undefined,
): RuntimeSelection {
- const inferredProviderId = activeId ?? (
- activeProviderName
- ? providers.find((provider) => provider.name === activeProviderName)?.id ?? null
- : null
- )
+ const activeProvider = activeId
+ ? providers.find((provider) => provider.id === activeId)
+ : activeProviderName
+ ? providers.find((provider) => provider.name === activeProviderName)
+ : undefined
+ const inferredProviderId = activeId ?? activeProvider?.id ?? null
+ const providerMainModelId = activeProvider?.models.main.trim()
return {
providerId: inferredProviderId,
- modelId: currentModelId ?? (
+ modelId: providerMainModelId || currentModelId || (
inferredProviderId === OPENAI_OFFICIAL_PROVIDER_ID
? OPENAI_OFFICIAL_DEFAULT_MODEL_ID
: OFFICIAL_DEFAULT_MODEL_ID
diff --git a/desktop/src/stores/providerStore.test.ts b/desktop/src/stores/providerStore.test.ts
index e66149da..20795170 100644
--- a/desktop/src/stores/providerStore.test.ts
+++ b/desktop/src/stores/providerStore.test.ts
@@ -115,6 +115,7 @@ describe('providerStore runtime refresh', () => {
providerId: provider.id,
modelId: 'model-main',
})
+ expect(settingsSetModelMock).not.toHaveBeenCalled()
})
it('keeps an explicit provider model selection when the model still exists', async () => {
@@ -181,6 +182,21 @@ describe('providerStore runtime refresh', () => {
expect(settingsSetModelMock).toHaveBeenCalledWith('model-main')
expect(settingsFetchAllMock).toHaveBeenCalled()
})
+
+ it('sets the provider main model when updating the active saved provider', async () => {
+ const provider = makeProvider({ models: { main: 'model-flash', haiku: 'model-flash', sonnet: 'model-pro', opus: 'model-pro' } })
+ providersApiMock.update.mockResolvedValue({ provider })
+ providersApiMock.list.mockResolvedValue({
+ providers: [provider],
+ activeId: provider.id,
+ })
+
+ const { useProviderStore } = await import('./providerStore')
+ await useProviderStore.getState().updateProvider(provider.id, { models: provider.models })
+
+ expect(settingsSetModelMock).toHaveBeenCalledWith('model-flash')
+ expect(settingsFetchAllMock).toHaveBeenCalled()
+ })
})
describe('providerStore reorderProviders', () => {
diff --git a/desktop/src/stores/providerStore.ts b/desktop/src/stores/providerStore.ts
index b1912a8a..ffb68d6d 100644
--- a/desktop/src/stores/providerStore.ts
+++ b/desktop/src/stores/providerStore.ts
@@ -203,7 +203,16 @@ export const useProviderStore = create((set, get) => ({
updateProvider: async (id, input) => {
const { provider } = await providersApi.update(id, input)
await get().fetchProviders()
- refreshConnectedSessionsForProvider(provider, get().activeId)
+ const activeId = get().activeId
+ if (activeId === provider.id && input.models !== undefined) {
+ const mainModelId = provider.models.main.trim()
+ if (mainModelId) {
+ const settings = useSettingsStore.getState()
+ await settings.setModel(mainModelId)
+ await settings.fetchAll()
+ }
+ }
+ refreshConnectedSessionsForProvider(provider, activeId)
return provider
},