diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 3febab5b7..4708e0ef4 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -474,6 +474,45 @@ describe('Server Config (config.ts)', () => { ).toHaveBeenCalledTimes(2); expect(vi.mocked(createContentGenerator)).toHaveBeenCalledTimes(1); }); + + it('should strip thoughts from history on model switch (#3304)', async () => { + const config = new Config(baseParams); + + const mockContentConfig: ContentGeneratorConfig = { + authType: AuthType.QWEN_OAUTH, + model: 'coder-model', + apiKey: 'QWEN_OAUTH_DYNAMIC_TOKEN', + baseUrl: DEFAULT_DASHSCOPE_BASE_URL, + timeout: 60000, + maxRetries: 3, + } as ContentGeneratorConfig; + + vi.mocked(resolveContentGeneratorConfigWithSources).mockImplementation( + (_config, authType, generationConfig) => ({ + config: { + ...mockContentConfig, + authType, + model: generationConfig?.model ?? mockContentConfig.model, + } as ContentGeneratorConfig, + sources: {}, + }), + ); + vi.mocked(createContentGenerator).mockResolvedValue({ + generateContent: vi.fn(), + generateContentStream: vi.fn(), + countTokens: vi.fn(), + embedContent: vi.fn(), + } as unknown as ContentGenerator); + + await config.refreshAuth(AuthType.QWEN_OAUTH); + + const stripSpy = config.getGeminiClient().stripThoughtsFromHistory; + vi.mocked(stripSpy).mockClear(); + + await config.switchModel(AuthType.QWEN_OAUTH, 'coder-model'); + + expect(stripSpy).toHaveBeenCalledTimes(1); + }); }); describe('model switching with different credentials (OpenAI)', () => { diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index a86c8e0d7..40ebe6277 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1334,6 +1334,12 @@ export class Config { return; } + // Strip thinking blocks from conversation history on model switch. + // reasoning_content is a non-standard field that causes strict + // OpenAI-compatible providers to reject requests with 422 errors + // when thought parts from a previous model leak into the payload (#3304). + this.geminiClient.stripThoughtsFromHistory(); + // Hot update path: only supported for qwen-oauth. // For other auth types we always refresh to recreate the ContentGenerator. //