From fe6d543f3e9a73443861649cbd6c284027fd3329 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Wed, 12 Nov 2025 00:14:12 +0000 Subject: [PATCH] fix: update task.apiConfiguration after API handler rebuild to fix model switch regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed issue where switching back to a previously selected model would not reapply the model - The bug occurred because task.apiConfiguration was never updated after rebuilding the API handler - When switching A→B→A, the second switch was incorrectly skipped as the comparison still showed model A - Added comprehensive test case for A→B→A switching scenario - Ensures reasoning output is properly restored when switching back to reasoning-enabled models Fixes #9179 --- src/core/webview/ClineProvider.ts | 6 ++ .../ClineProvider.apiHandlerRebuild.spec.ts | 93 +++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index aa459bd737..676f6cbd0e 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1314,10 +1314,16 @@ export class ClineProvider if (currentProvider !== newProvider || currentModelId !== newModelId) { task.api = buildApiHandler(providerSettings) + // Update task.apiConfiguration to keep it in sync with the active API handler + // This prevents the "switch back" bug where the comparison would incorrectly + // skip rebuilding when switching back to a previously used model + task.apiConfiguration = providerSettings } } else if (task) { // Fallback: rebuild if apiConfiguration is not available task.api = buildApiHandler(providerSettings) + // Also update apiConfiguration in the fallback case + task.apiConfiguration = providerSettings } } diff --git a/src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts b/src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts index 4037d9df26..513288892a 100644 --- a/src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts @@ -497,6 +497,99 @@ describe("ClineProvider - API Handler Rebuild Guard", () => { }), ) }) + + test("handles A→B→A switching correctly (regression test for #9179)", async () => { + // Start with Model A (reasoning-enabled) + const mockTask = new Task({ + ...defaultTaskOptions, + apiConfiguration: { + apiProvider: "openrouter", + openRouterModelId: "openai/o1-preview", + }, + }) + const originalApiA = { + getModel: vi.fn().mockReturnValue({ + id: "openai/o1-preview", + info: { contextWindow: 128000 }, + }), + } + mockTask.api = originalApiA as any + + await provider.addClineToStack(mockTask) + + buildApiHandlerMock.mockClear() + + // Switch to Model B (no reasoning) + const newApiB = { + getModel: vi.fn().mockReturnValue({ + id: "openai/gpt-4", + info: { contextWindow: 128000 }, + }), + } + buildApiHandlerMock.mockReturnValue(newApiB) + + await provider.upsertProviderProfile( + "model-b-config", + { + apiProvider: "openrouter", + openRouterModelId: "openai/gpt-4", + }, + true, + ) + + // Verify API handler was rebuilt for Model B + expect(buildApiHandlerMock).toHaveBeenCalledWith( + expect.objectContaining({ + apiProvider: "openrouter", + openRouterModelId: "openai/gpt-4", + }), + ) + expect(mockTask.api).toBe(newApiB) + // Verify task.apiConfiguration was updated to Model B + expect(mockTask.apiConfiguration).toEqual( + expect.objectContaining({ + apiProvider: "openrouter", + openRouterModelId: "openai/gpt-4", + }), + ) + + buildApiHandlerMock.mockClear() + + // Switch back to Model A (this is where the bug occurred) + const newApiA = { + getModel: vi.fn().mockReturnValue({ + id: "openai/o1-preview", + info: { contextWindow: 128000 }, + }), + } + buildApiHandlerMock.mockReturnValue(newApiA) + + await provider.upsertProviderProfile( + "model-a-config", + { + apiProvider: "openrouter", + openRouterModelId: "openai/o1-preview", + }, + true, + ) + + // Verify API handler WAS rebuilt when switching back to Model A + // This is the key test - without the fix, this would fail + expect(buildApiHandlerMock).toHaveBeenCalledWith( + expect.objectContaining({ + apiProvider: "openrouter", + openRouterModelId: "openai/o1-preview", + }), + ) + expect(mockTask.api).toBe(newApiA) + // Verify task.apiConfiguration was updated back to Model A + expect(mockTask.apiConfiguration).toEqual( + expect.objectContaining({ + apiProvider: "openrouter", + openRouterModelId: "openai/o1-preview", + }), + ) + }) }) describe("getModelId helper", () => {