fix: strip thinking blocks from qwen-code completePrompt responses

qwen3-coder models include <think>...</think> 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 <think> blocks from the response content before returning,
consistent with how createMessage() already handles them for streaming.

Closes #12102
This commit is contained in:
Roo Code 2026-04-13 01:36:57 +00:00
parent 7adbfec2a4
commit 16ea03114b
2 changed files with 150 additions and 1 deletions

View file

@ -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<typeof vi.fn>).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 <think> blocks from response content", async () => {
mockCreate.mockResolvedValueOnce({
choices: [
{
message: {
content:
"<think>Let me analyze this prompt and think about how to enhance it...</think>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 <think> blocks from response content", async () => {
mockCreate.mockResolvedValueOnce({
choices: [
{
message: {
content: "<think>First thought...</think>Part one. <think>Second thought...</think>Part two.",
},
},
],
})
const result = await handler.completePrompt("Enhance this prompt")
expect(result).toBe("Part one. Part two.")
})
it("should handle multiline <think> blocks", async () => {
mockCreate.mockResolvedValueOnce({
choices: [
{
message: {
content:
"<think>\nLet me think about this.\nI need to consider multiple things.\n</think>\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: "<think>Only thinking, no actual content.</think>",
},
},
],
})
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("")
})
})

View file

@ -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 <think>...</think> blocks that qwen3-coder thinking models include
return content.replace(/<think>[\s\S]*?<\/think>/g, "").trim()
}
}