| |
| |
| import { describe, it, expect } from "vitest"; |
| import "./registerAll.js"; |
| import { translateRequest } from "../../open-sse/translator/index.js"; |
| import { FORMATS } from "../../open-sse/translator/formats.js"; |
|
|
| const T = (src, tgt, body, provider = null) => |
| translateRequest(src, tgt, "m", body, true, null, provider); |
|
|
| describe("bug: Claude → OpenAI bridge data loss", () => { |
| |
| |
| it.fails("image with source.type=url is preserved (NOT dropped)", () => { |
| const out = T(FORMATS.CLAUDE, FORMATS.OPENAI, { |
| messages: [{ role: "user", content: [ |
| { type: "text", text: "look" }, |
| { type: "image", source: { type: "url", url: "https://x.com/a.png" } }, |
| ] }], |
| }); |
| const json = JSON.stringify(out); |
| expect(json, "remote image url silently dropped").toContain("a.png"); |
| }); |
|
|
| |
| it("thinking block survives round-trip Claude→OpenAI→Claude", () => { |
| const body = { |
| messages: [{ role: "assistant", content: [ |
| { type: "thinking", thinking: "secret reasoning", signature: "sig" }, |
| { type: "text", text: "answer" }, |
| ] }, { role: "user", content: "go" }], |
| }; |
| const out = T(FORMATS.CLAUDE, FORMATS.CLAUDE, body); |
| const json = JSON.stringify(out); |
| expect(json, "thinking content lost via OpenAI bridge").toContain("secret reasoning"); |
| }); |
|
|
| |
| |
| it.fails("tool_result with image block is not turned into raw JSON / dropped", () => { |
| const out = T(FORMATS.CLAUDE, FORMATS.OPENAI, { |
| messages: [ |
| { role: "assistant", content: [ |
| { type: "tool_use", id: "call_1", name: "shot", input: {} }, |
| ] }, |
| { role: "user", content: [ |
| { type: "tool_result", tool_use_id: "call_1", content: [ |
| { type: "image", source: { type: "base64", media_type: "image/png", data: "ZZZ" } }, |
| ] }, |
| ] }, |
| ], |
| }); |
| const toolMsg = out.messages.find((m) => m.role === "tool"); |
| |
| expect(toolMsg?.content, "image in tool_result lost").not.toMatch(/^\[/); |
| }); |
|
|
| |
| |
| it.fails("tool_result is_error flag is preserved", () => { |
| const out = T(FORMATS.CLAUDE, FORMATS.OPENAI, { |
| messages: [ |
| { role: "assistant", content: [{ type: "tool_use", id: "call_1", name: "f", input: {} }] }, |
| { role: "user", content: [ |
| { type: "tool_result", tool_use_id: "call_1", is_error: true, content: "boom" }, |
| ] }, |
| ], |
| }); |
| const json = JSON.stringify(out); |
| expect(json, "is_error dropped → model can't see tool failure").toContain("is_error"); |
| }); |
|
|
| |
| it("system array non-text parts are not silently dropped", () => { |
| const out = T(FORMATS.CLAUDE, FORMATS.OPENAI, { |
| system: [ |
| { type: "text", text: "rule1", cache_control: { type: "ephemeral" } }, |
| { type: "text", text: "rule2" }, |
| ], |
| messages: [{ role: "user", content: "hi" }], |
| }); |
| const sys = out.messages.find((m) => m.role === "system"); |
| expect(sys?.content).toContain("rule1"); |
| expect(sys?.content).toContain("rule2"); |
| }); |
| }); |
|
|
| describe("bug: tool_call id stability across bridge", () => { |
| |
| it("sanitized tool id stays matched between call and result", () => { |
| const out = T(FORMATS.OPENAI, FORMATS.OPENAI, { |
| messages: [ |
| { role: "assistant", tool_calls: [ |
| { id: "call/with:bad*chars", type: "function", function: { name: "f", arguments: "{}" } }, |
| ] }, |
| { role: "tool", tool_call_id: "call/with:bad*chars", content: "ok" }, |
| ], |
| }); |
| const asst = out.messages.find((m) => m.role === "assistant"); |
| const tool = out.messages.find((m) => m.role === "tool"); |
| expect(tool.tool_call_id, "id mismatch after sanitize").toBe(asst.tool_calls[0].id); |
| }); |
| }); |
|
|
| describe("bug: empty content message handling", () => { |
| |
| it("assistant message with only tool_calls is not dropped", () => { |
| const out = T(FORMATS.OPENAI, FORMATS.OPENAI, { |
| messages: [ |
| { role: "user", content: "do it" }, |
| { role: "assistant", content: "", tool_calls: [ |
| { id: "call_1", type: "function", function: { name: "f", arguments: "{}" } }, |
| ] }, |
| { role: "tool", tool_call_id: "call_1", content: "done" }, |
| ], |
| }); |
| const asst = out.messages.find((m) => m.role === "assistant" && m.tool_calls); |
| expect(asst, "assistant tool_calls message dropped").toBeTruthy(); |
| }); |
| }); |
|
|