import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" import { Hono } from "hono" import type { ResolvedProviderConfig } from "../src/lib/config" const actualConfigModule = await import("../src/lib/config") const actualTokenUsageModule = await import("../src/lib/token-usage") let providerConfig: ResolvedProviderConfig | null = null const noopTokenUsageRecorder = () => {} const createNoopProviderTokenUsageRecorder = () => noopTokenUsageRecorder await mock.module("~/lib/config", () => ({ ...actualConfigModule, getProviderConfig: () => providerConfig, })) await mock.module("~/lib/token-usage", () => ({ ...actualTokenUsageModule, createProviderTokenUsageRecorder: createNoopProviderTokenUsageRecorder, })) const { providerMessageRoutes } = await import( "../src/routes/provider/messages/route" ) const originalFetch = globalThis.fetch const fetchMock = mock((_url: string | URL | Request, _init?: RequestInit) => Promise.resolve( new Response( JSON.stringify({ id: "chatcmpl-test", object: "chat.completion", created: 0, model: "qwen-plus", choices: [ { index: 0, message: { role: "assistant", reasoning_content: "thinking text", content: "answer text", }, finish_reason: "stop", logprobs: null, }, ], usage: { prompt_tokens: 8, completion_tokens: 2, total_tokens: 10, prompt_tokens_details: { cache_creation_input_tokens: 2, cached_tokens: 1, }, }, }), { headers: { "content-type": "application/json", }, }, ), ), ) const createApp = () => { const app = new Hono() app.route("/:provider/v1/messages", providerMessageRoutes) return app } beforeEach(() => { providerConfig = { name: "dash", type: "openai-compatible", baseUrl: "https://dashscope.example/compatible-mode", apiKey: "provider-key", authType: "authorization", models: { "qwen-plus": { extraBody: { enable_thinking: true, preserve_thinking: true, temperature: 0.9, }, temperature: 0.2, toolContentSupportType: [], topK: 50, topP: 0.8, }, }, } fetchMock.mockClear() ;(globalThis as unknown as { fetch: typeof fetch }).fetch = fetchMock as unknown as typeof fetch }) afterEach(() => { ;(globalThis as unknown as { fetch: typeof fetch }).fetch = originalFetch providerConfig = null }) describe("openai-compatible provider messages", () => { test("translates Anthropic messages to OpenAI chat completions", async () => { const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "anthropic-version": "2023-06-01", "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [{ role: "user", content: "hello" }], model: "qwen-plus", enable_thinking: false, temperature: 0.4, }), }) expect(response.status).toBe(200) expect(fetchMock).toHaveBeenCalledTimes(1) const [url, init] = fetchMock.mock.calls[0] expect(url).toBe( "https://dashscope.example/compatible-mode/v1/chat/completions", ) expect((init as RequestInit).headers).toEqual({ "content-type": "application/json", accept: "application/json", authorization: "Bearer provider-key", }) const body = JSON.parse((init as RequestInit).body as string) as Record< string, unknown > expect(body).toMatchObject({ enable_thinking: false, max_tokens: 128, model: "qwen-plus", preserve_thinking: true, temperature: 0.4, top_k: 50, top_p: 0.8, }) expect(body.messages).toEqual([ { role: "user", content: [ { type: "text", text: "hello", cache_control: { type: "ephemeral", }, }, ], }, ]) expect(body).not.toHaveProperty("stream_options") const json = (await response.json()) as Record expect(json).toMatchObject({ model: "qwen-plus", role: "assistant", stop_reason: "end_turn", usage: { cache_creation_input_tokens: 2, cache_read_input_tokens: 1, input_tokens: 5, output_tokens: 2, }, }) expect(json.content).toEqual([ { type: "thinking", thinking: "thinking text", signature: "", }, { type: "text", text: "answer text", }, ]) }) test("adds stream_options include_usage for OpenAI-compatible streams", async () => { providerConfig = { ...providerConfig, models: { "qwen-plus": { toolContentSupportType: [], }, }, } as ResolvedProviderConfig const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [{ role: "user", content: "hello" }], model: "qwen-plus", stream: true, }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as Record expect(body.stream).toBe(true) expect(body.stream_options).toEqual({ include_usage: true, }) }) test("allows extraBody to disable parallel tool calls", async () => { providerConfig = { ...providerConfig, models: { "qwen-plus": { extraBody: { parallel_tool_calls: false, }, toolContentSupportType: [], }, }, } as ResolvedProviderConfig const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [{ role: "user", content: "hello" }], model: "qwen-plus", }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as Record expect(body.parallel_tool_calls).toBe(false) }) test("maps Anthropic thinking budget to OpenAI-compatible thinking_budget", async () => { const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [{ role: "user", content: "hello" }], model: "qwen-plus", thinking: { type: "enabled", budget_tokens: 4096, }, }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as Record expect(body.thinking_budget).toBe(4096) expect(body).not.toHaveProperty("thinking") }) test("forces thinking_budget from extraBody over request thinking budget", async () => { providerConfig = { ...providerConfig, models: { "qwen-plus": { extraBody: { thinking_budget: 8192, }, toolContentSupportType: [], }, }, } as ResolvedProviderConfig const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [{ role: "user", content: "hello" }], model: "qwen-plus", thinking: { type: "enabled", budget_tokens: 4096, }, }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as Record expect(body.thinking_budget).toBe(8192) }) }) describe("openai-compatible provider context cache", () => { test("marks first system and last two non-system messages for OpenAI-compatible context cache", async () => { const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, system: "system prompt", messages: [ { role: "user", content: "first" }, { role: "assistant", content: "first answer" }, { role: "user", content: "second" }, { role: "assistant", content: "second answer" }, { role: "user", content: "latest" }, ], model: "qwen-plus", }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as { messages: Array<{ content: unknown; role: string }> } const markedMessages = body.messages.filter( (message) => Array.isArray(message.content) && message.content.some( (part) => typeof part === "object" && part !== null && "cache_control" in part, ), ) expect(markedMessages).toHaveLength(3) expect(body.messages[0]).toEqual({ role: "system", content: [ { type: "text", text: "system prompt", cache_control: { type: "ephemeral", }, }, ], }) expect(body.messages[1]).toEqual({ role: "user", content: "first", }) expect(body.messages[2]).toEqual({ role: "assistant", content: "first answer", }) expect(body.messages[3]).toEqual({ role: "user", content: "second", }) expect(body.messages[4]).toEqual({ role: "assistant", content: [ { type: "text", text: "second answer", cache_control: { type: "ephemeral", }, }, ], }) expect(body.messages[5]).toEqual({ role: "user", content: [ { type: "text", text: "latest", cache_control: { type: "ephemeral", }, }, ], }) }) test("allows disabling OpenAI-compatible context cache per model", async () => { providerConfig = { ...providerConfig, models: { "qwen-plus": { contextCache: false, toolContentSupportType: [], }, }, } as ResolvedProviderConfig const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, system: "system prompt", messages: [{ role: "user", content: "hello" }], model: "qwen-plus", }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as { messages: Array> } expect(body.messages).toEqual([ { role: "system", content: "system prompt", }, { role: "user", content: "hello", }, ]) }) }) describe("openai-compatible provider message content", () => { test("sends assistant thinking history as reasoning_content", async () => { const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [ { role: "user", content: "first", }, { role: "assistant", content: [ { type: "thinking", thinking: "empty signature thinking", signature: "", }, { type: "text", text: "previous answer", }, ], }, { role: "user", content: "continue", }, ], model: "qwen-plus", }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as { messages: Array> } expect(body.messages[1]).toMatchObject({ content: [ { type: "text", text: "previous answer", }, ], reasoning_content: "empty signature thinking", role: "assistant", }) expect(body.messages[1]).not.toHaveProperty("reasoning_text") expect(body.messages[1]).not.toHaveProperty("reasoning_opaque") }) }) describe("openai-compatible provider tool array content", () => { test("marks string tool result content for context cache", async () => { const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [ { role: "user", content: [ { type: "tool_result", tool_use_id: "tool_text", content: "plain tool result", }, ], }, ], model: "qwen-plus", }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as { messages: Array> } expect(body.messages[0]).toEqual({ role: "tool", tool_call_id: "tool_text", content: [ { type: "text", text: "plain tool result", cache_control: { type: "ephemeral", }, }, ], }) }) test("marks tool result array content when array tool content is enabled", async () => { providerConfig = { ...providerConfig, models: { "qwen-plus": { toolContentSupportType: ["array"], }, }, } as ResolvedProviderConfig const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [ { role: "user", content: [ { type: "tool_result", tool_use_id: "tool_text", content: [ { type: "text", text: "first line", }, { type: "text", text: "second line", }, ], }, ], }, ], model: "qwen-plus", }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as { messages: Array> } expect(body.messages[0]).toEqual({ role: "tool", tool_call_id: "tool_text", content: [ { type: "text", text: "first line", }, { type: "text", text: "second line", cache_control: { type: "ephemeral", }, }, ], }) }) }) describe("openai-compatible provider PDF message content", () => { test("sends PDF tool results as file parts when PDF tool content is enabled", async () => { providerConfig = { ...providerConfig, models: { "qwen-plus": { supportPdf: true, toolContentSupportType: ["pdf"], }, }, } as ResolvedProviderConfig const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [ { role: "user", content: [ { type: "tool_result", tool_use_id: "tool_pdf", content: [ { type: "text", text: "PDF file read: report.pdf", }, { type: "document", source: { type: "base64", media_type: "application/pdf", data: "pdf-data", }, title: "report.pdf", }, ], }, ], }, ], model: "qwen-plus", }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as { messages: Array> } expect(body.messages[0]).toEqual({ role: "tool", tool_call_id: "tool_pdf", content: [ { type: "text", text: "PDF file read: report.pdf", }, { type: "file", file: { file_data: "data:application/pdf;base64,pdf-data", filename: "report.pdf", }, cache_control: { type: "ephemeral", }, }, ], }) }) test("moves PDF file parts to user messages when tool PDF support is missing", async () => { providerConfig = { ...providerConfig, models: { "qwen-plus": { supportPdf: true, toolContentSupportType: [], }, }, } as ResolvedProviderConfig const app = createApp() const response = await app.request("/dash/v1/messages", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ max_tokens: 128, messages: [ { role: "user", content: [ { type: "tool_result", tool_use_id: "tool_pdf", content: [ { type: "text", text: "PDF file read: report.pdf", }, { type: "document", source: { type: "base64", media_type: "application/pdf", data: "pdf-data", }, title: "report.pdf", }, ], }, ], }, ], model: "qwen-plus", }), }) expect(response.status).toBe(200) const init = fetchMock.mock.calls[0][1] as RequestInit const body = JSON.parse(init.body as string) as { messages: Array> } expect(body.messages[0]).toEqual({ role: "tool", tool_call_id: "tool_pdf", content: [ { type: "text", text: "PDF file read: report.pdf", cache_control: { type: "ephemeral", }, }, ], }) expect(body.messages[1]).toEqual({ role: "user", content: [ { type: "text", text: "Tool result for tool_pdf:", }, { type: "text", text: "PDF file read: report.pdf", }, { type: "file", file: { file_data: "data:application/pdf;base64,pdf-data", filename: "report.pdf", }, cache_control: { type: "ephemeral", }, }, ], }) }) })