copilot-api / tests /provider-openai-compatible.test.ts
imspsycho's picture
Initial upload from Google Colab
98c9143 verified
Raw
History Blame Contribute Delete
20.5 kB
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<string, unknown>
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<string, unknown>
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<string, unknown>
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<string, unknown>
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<string, unknown>
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<Record<string, unknown>>
}
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<Record<string, unknown>>
}
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<Record<string, unknown>>
}
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<Record<string, unknown>>
}
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<Record<string, unknown>>
}
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<Record<string, unknown>>
}
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",
},
},
],
})
})
})