diff --git a/ui/desktop/src/acp/__tests__/providers.test.ts b/ui/desktop/src/acp/__tests__/providers.test.ts new file mode 100644 index 000000000..0d419e244 --- /dev/null +++ b/ui/desktop/src/acp/__tests__/providers.test.ts @@ -0,0 +1,80 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { getAcpClient } from '../acpConnection'; +import { acpSetSessionProviderModel } from '../providers'; + +vi.mock('../acpConnection', () => ({ + getAcpClient: vi.fn(), +})); + +function selectConfigOption(id: string, currentValue: string) { + return { + id, + name: id, + type: 'select', + currentValue, + options: [], + }; +} + +describe('ACP providers', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('sets thinking effort after provider and model, then returns the final config response', async () => { + const client = { + setSessionConfigOption: vi + .fn() + .mockResolvedValueOnce({ + configOptions: [ + selectConfigOption('provider', 'anthropic'), + selectConfigOption('model', 'provider-default-model'), + ], + }) + .mockResolvedValueOnce({ + configOptions: [ + selectConfigOption('provider', 'anthropic'), + selectConfigOption('model', 'claude-sonnet-4-5'), + ], + }) + .mockResolvedValueOnce({ + configOptions: [ + selectConfigOption('provider', 'anthropic'), + selectConfigOption('model', 'claude-sonnet-4-5'), + selectConfigOption('thinking_effort', 'high'), + ], + }), + }; + vi.mocked(getAcpClient).mockResolvedValue( + client as unknown as Awaited> + ); + + const applied = await acpSetSessionProviderModel( + 'session-1', + 'anthropic', + 'claude-sonnet-4-5', + 'high' + ); + + expect(client.setSessionConfigOption).toHaveBeenCalledTimes(3); + expect(client.setSessionConfigOption).toHaveBeenNthCalledWith(1, { + sessionId: 'session-1', + configId: 'provider', + value: 'anthropic', + }); + expect(client.setSessionConfigOption).toHaveBeenNthCalledWith(2, { + sessionId: 'session-1', + configId: 'model', + value: 'claude-sonnet-4-5', + }); + expect(client.setSessionConfigOption).toHaveBeenNthCalledWith(3, { + sessionId: 'session-1', + configId: 'thinking_effort', + value: 'high', + }); + expect(applied).toEqual({ + providerId: 'anthropic', + modelId: 'claude-sonnet-4-5', + }); + }); +}); diff --git a/ui/desktop/src/acp/providers.ts b/ui/desktop/src/acp/providers.ts index 933559a17..e2e2100ce 100644 --- a/ui/desktop/src/acp/providers.ts +++ b/ui/desktop/src/acp/providers.ts @@ -198,6 +198,55 @@ export async function acpSaveThinkingEffort(effort: ThinkingEffort): Promise { + modelId?: string | null, + thinkingEffort?: ThinkingEffort | null +): Promise { const client = await getAcpClient(); - await client.setSessionConfigOption({ sessionId, configId: 'provider', value: providerId }); + let response = await client.setSessionConfigOption({ + sessionId, + configId: 'provider', + value: providerId, + }); if (modelId) { - await client.setSessionConfigOption({ sessionId, configId: 'model', value: modelId }); + response = await client.setSessionConfigOption({ + sessionId, + configId: 'model', + value: modelId, + }); } + if (thinkingEffort != null) { + response = await client.setSessionConfigOption({ + sessionId, + configId: 'thinking_effort', + value: thinkingEffort, + }); + } + + return extractAppliedSessionProviderModel(response.configOptions); } diff --git a/ui/desktop/src/components/ModelAndProviderContext.tsx b/ui/desktop/src/components/ModelAndProviderContext.tsx index 077c3b349..81b41baa4 100644 --- a/ui/desktop/src/components/ModelAndProviderContext.tsx +++ b/ui/desktop/src/components/ModelAndProviderContext.tsx @@ -2,7 +2,13 @@ import React, { createContext, useContext, useState, useEffect, useMemo, useCall import { toastError, toastSuccess } from '../toasts'; import Model, { getProviderMetadata } from './settings/models/modelInterface'; import { ProviderMetadata } from '../api'; -import { acpReadDefaults, acpSaveDefaults, acpSetSessionProviderModel } from '../acp/providers'; +import { acpChatSessionActions, acpChatSessionStore } from '../acp/chatSessionStore'; +import { + acpReadDefaults, + acpSaveDefaults, + acpSetSessionProviderModel, + type AppliedSessionProviderModel, +} from '../acp/providers'; import { errorMessage } from '../utils/conversionUtils'; import { getModelDisplayName, @@ -57,6 +63,27 @@ const ModelAndProviderContext = createContext = ({ children }) => { const [currentModel, setCurrentModel] = useState(null); const [currentProvider, setCurrentProvider] = useState(null); @@ -70,7 +97,13 @@ export const ModelAndProviderProvider: React.FC = try { if (sessionId) { - await acpSetSessionProviderModel(sessionId, providerName, modelName); + const applied = await acpSetSessionProviderModel( + sessionId, + providerName, + modelName, + model.request_params?.thinking_effort ?? null + ); + patchAcpSessionProviderModel(sessionId, applied); } // Only update the global config default when there's no session