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() } }