fix: update task.apiConfiguration after API handler rebuild to fix model switch regression

- 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
This commit is contained in:
Roo Code 2025-11-12 00:14:12 +00:00
parent 6e6341346e
commit fe6d543f3e
2 changed files with 99 additions and 0 deletions

View file

@ -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
}
}

View file

@ -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", () => {