From 16ea03114b9ffcbef085703e26459746ad313408 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 13 Apr 2026 01:36:57 +0000 Subject: [PATCH] fix: strip thinking blocks from qwen-code completePrompt responses qwen3-coder models include ... blocks in their responses. The completePrompt() method (used for prompt enhancement) was returning these blocks as-is, causing the enhanced prompt to either be empty (triggering "Failed to enhance prompt" toast) or contain raw thinking markup. This strips blocks from the response content before returning, consistent with how createMessage() already handles them for streaming. Closes #12102 --- .../qwen-code-complete-prompt.spec.ts | 146 ++++++++++++++++++ src/api/providers/qwen-code.ts | 5 +- 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/api/providers/__tests__/qwen-code-complete-prompt.spec.ts diff --git a/src/api/providers/__tests__/qwen-code-complete-prompt.spec.ts b/src/api/providers/__tests__/qwen-code-complete-prompt.spec.ts new file mode 100644 index 0000000000..7c6233d64d --- /dev/null +++ b/src/api/providers/__tests__/qwen-code-complete-prompt.spec.ts @@ -0,0 +1,146 @@ +// npx vitest run api/providers/__tests__/qwen-code-complete-prompt.spec.ts + +// Mock filesystem - must come before other imports +vi.mock("node:fs", () => ({ + promises: { + readFile: vi.fn(), + writeFile: vi.fn(), + }, +})) + +const mockCreate = vi.fn() +vi.mock("openai", () => { + return { + __esModule: true, + default: vi.fn().mockImplementation(() => ({ + apiKey: "test-key", + baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", + chat: { + completions: { + create: mockCreate, + }, + }, + })), + } +}) + +import { promises as fs } from "node:fs" +import { QwenCodeHandler } from "../qwen-code" +import type { ApiHandlerOptions } from "../../../shared/api" + +describe("QwenCodeHandler completePrompt", () => { + let handler: QwenCodeHandler + let mockOptions: ApiHandlerOptions & { qwenCodeOauthPath?: string } + + const validCredentials = { + access_token: "test-access-token", + refresh_token: "test-refresh-token", + token_type: "Bearer", + expiry_date: Date.now() + 3600000, + resource_url: "https://dashscope.aliyuncs.com/compatible-mode/v1", + } + + beforeEach(() => { + vi.clearAllMocks() + + mockOptions = { + apiModelId: "qwen3-coder-plus", + qwenCodeOauthPath: "/tmp/test-creds.json", + } + + handler = new QwenCodeHandler(mockOptions) + ;(fs.readFile as ReturnType).mockResolvedValue(JSON.stringify(validCredentials)) + }) + + it("should return plain text content as-is", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [ + { + message: { + content: "Here is your enhanced prompt with more details.", + }, + }, + ], + }) + + const result = await handler.completePrompt("Enhance this prompt") + expect(result).toBe("Here is your enhanced prompt with more details.") + }) + + it("should strip blocks from response content", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [ + { + message: { + content: + "Let me analyze this prompt and think about how to enhance it...Here is your enhanced prompt with more details.", + }, + }, + ], + }) + + const result = await handler.completePrompt("Enhance this prompt") + expect(result).toBe("Here is your enhanced prompt with more details.") + }) + + it("should strip multiple blocks from response content", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [ + { + message: { + content: "First thought...Part one. Second thought...Part two.", + }, + }, + ], + }) + + const result = await handler.completePrompt("Enhance this prompt") + expect(result).toBe("Part one. Part two.") + }) + + it("should handle multiline blocks", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [ + { + message: { + content: + "\nLet me think about this.\nI need to consider multiple things.\n\nThe enhanced prompt.", + }, + }, + ], + }) + + const result = await handler.completePrompt("Enhance this prompt") + expect(result).toBe("The enhanced prompt.") + }) + + it("should return empty string when content is only a think block", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [ + { + message: { + content: "Only thinking, no actual content.", + }, + }, + ], + }) + + const result = await handler.completePrompt("Enhance this prompt") + expect(result).toBe("") + }) + + it("should return empty string when message content is null", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [ + { + message: { + content: null, + }, + }, + ], + }) + + const result = await handler.completePrompt("Enhance this prompt") + expect(result).toBe("") + }) +}) diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index f2a207051e..50a8119e6b 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -340,6 +340,9 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan const response = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions)) - return response.choices[0]?.message.content || "" + const content = response.choices[0]?.message.content || "" + + // Strip ... blocks that qwen3-coder thinking models include + return content.replace(/[\s\S]*?<\/think>/g, "").trim() } }