merge: resolve conflicts with origin/master - keep both local and remote features

This commit is contained in:
2026-06-22 11:40:16 +07:00
424 changed files with 24520 additions and 11731 deletions

View File

@@ -0,0 +1,50 @@
// Guards the deduped Antigravity OAuth client: same values across all 3 sources after refactor.
import { describe, it, expect } from "vitest";
const EXPECTED = {
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
clientSecret: "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",
};
const GOOGLE = {
clientId: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
clientSecret: "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",
};
describe("antigravity oauth client (deduped)", () => {
it("shared source holds the canonical credentials", async () => {
const { ANTIGRAVITY_OAUTH_CLIENT } = await import("../../open-sse/providers/shared.js");
expect(ANTIGRAVITY_OAUTH_CLIENT).toEqual(EXPECTED);
});
it("registry transport keeps clientId/clientSecret", async () => {
const ag = (await import("../../open-sse/providers/registry/antigravity.js")).default;
expect(ag.transport.clientId).toBe(EXPECTED.clientId);
expect(ag.transport.clientSecret).toBe(EXPECTED.clientSecret);
});
it("google client shared by gemini + gemini-cli", async () => {
const { GOOGLE_OAUTH_CLIENT } = await import("../../open-sse/providers/shared.js");
expect(GOOGLE_OAUTH_CLIENT).toEqual(GOOGLE);
const gemini = (await import("../../open-sse/providers/registry/gemini.js")).default;
const gc = (await import("../../open-sse/providers/registry/gemini-cli.js")).default;
expect(gemini.transport.clientSecret).toBe(GOOGLE.clientSecret);
expect(gc.transport.clientSecret).toBe(GOOGLE.clientSecret);
});
// Guard: oauth.js must spread shared clients + derive from registry (PROVIDER_OAUTH).
it("src oauth.js imports shared client + keeps full shape", async () => {
const { readFileSync } = await import("node:fs");
const { fileURLToPath } = await import("node:url");
const { dirname, join } = await import("node:path");
const here = dirname(fileURLToPath(import.meta.url));
const src = readFileSync(join(here, "../../src/lib/oauth/constants/oauth.js"), "utf8");
expect(src).toContain('import { ANTIGRAVITY_OAUTH_CLIENT, GOOGLE_OAUTH_CLIENT } from "open-sse/providers/shared.js"');
expect(src).toContain("...ANTIGRAVITY_OAUTH_CLIENT");
expect(src).toContain("...GOOGLE_OAUTH_CLIENT");
// authorizeUrl now lives in registry; oauth.js derives via PROVIDER_OAUTH spread
expect(src).toContain('PROVIDER_OAUTH["antigravity"]');
expect(src).toContain('PROVIDER_OAUTH["gemini-cli"]');
expect(src).not.toContain(EXPECTED.clientSecret); // antigravity secret no longer hardcoded here
expect(src).not.toContain(GOOGLE.clientSecret); // gemini secret no longer hardcoded here
});
});

View File

@@ -0,0 +1,44 @@
// Guards D3: antigravity 429/503 retry merged into base via computeRetryDelay hook.
import { describe, it, expect } from "vitest";
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.js";
const MAX = 10000;
function res(status, headers = {}, body = null) {
return {
status,
headers: { get: (k) => headers[k.toLowerCase()] ?? null },
clone: () => ({ text: async () => (body == null ? "" : JSON.stringify(body)) }),
};
}
describe("antigravity computeRetryDelay hook (D3)", () => {
const ag = new AntigravityExecutor();
it("uses Retry-After header (seconds → ms) when within cap", async () => {
expect(await ag.computeRetryDelay(res(429, { "retry-after": "5" }), 1)).toBe(5000);
});
it("vetoes (false) when Retry-After exceeds cap", async () => {
expect(await ag.computeRetryDelay(res(429, { "retry-after": "60" }), 1)).toBe(false);
});
it("parses retry time from error body when no header", async () => {
const r = res(429, {}, { error: { message: "quota will reset after 3s" } });
expect(await ag.computeRetryDelay(r, 1)).toBe(3000);
});
it("exponential backoff for 429 when no retry info", async () => {
expect(await ag.computeRetryDelay(res(429), 1)).toBe(Math.min(1000 * 2 ** 1, MAX));
expect(await ag.computeRetryDelay(res(429), 3)).toBe(Math.min(1000 * 2 ** 3, MAX));
});
it("503 without retry info → veto (no auto backoff)", async () => {
expect(await ag.computeRetryDelay(res(503), 1)).toBe(false);
});
it("buildHeaders includes cached session id after transformRequest", () => {
ag._lastSessionId = "sess-123";
const h = ag.buildHeaders({ accessToken: "tok" }, true);
expect(h["X-Machine-Session-Id"]).toBe("sess-123");
});
});

View File

@@ -0,0 +1,97 @@
// Locks BaseExecutor.execute retry/fallback behavior (docs 04 GAP #1, docs 11 §7).
import { describe, it, expect, vi, beforeEach } from "vitest";
// Mock the network layer so we can script upstream responses.
const fetchMock = vi.fn();
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: (...args) => fetchMock(...args),
}));
const { BaseExecutor } = await import("../../open-sse/executors/base.js");
function res(status) {
return { status, headers: { get: () => "" } };
}
function makeExec(config) {
const ex = new BaseExecutor("test", config);
// make headers trivial; credentials empty
return ex;
}
const creds = { apiKey: "k" };
beforeEach(() => fetchMock.mockReset());
describe("BaseExecutor.execute — retry by status (config-driven)", () => {
it("retries 502 `attempts` times then succeeds", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 502: { attempts: 3, delayMs: 0 } } });
fetchMock
.mockResolvedValueOnce(res(502))
.mockResolvedValueOnce(res(502))
.mockResolvedValueOnce(res(200));
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
expect(out.response.status).toBe(200);
expect(fetchMock).toHaveBeenCalledTimes(3);
});
it("stops after exhausting 502 attempts on a single url and throws", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 502: { attempts: 2, delayMs: 0 } } });
fetchMock.mockResolvedValue(res(502));
// single url: 1 initial + 2 retries = 3 calls, then returns the 502 response (no fallback url)
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
expect(out.response.status).toBe(502);
expect(fetchMock).toHaveBeenCalledTimes(3);
});
});
describe("BaseExecutor.execute — baseUrls fallback", () => {
it("falls over to the next url on 429 (shouldRetry)", async () => {
const ex = makeExec({ baseUrls: ["https://a/api", "https://b/api"], retry: { 429: { attempts: 0 } } });
fetchMock
.mockResolvedValueOnce(res(429)) // url[0] → fallback
.mockResolvedValueOnce(res(200)); // url[1] ok
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
expect(out.response.status).toBe(200);
expect(out.url).toBe("https://b/api");
expect(fetchMock).toHaveBeenCalledTimes(2);
});
});
describe("BaseExecutor.execute — network error retry/fallback", () => {
it("maps network exception to 502 retry config", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 502: { attempts: 1, delayMs: 0 } } });
fetchMock
.mockImplementationOnce(async () => { throw new Error("ECONNRESET"); })
.mockResolvedValueOnce(res(200));
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
expect(out.response.status).toBe(200);
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it("throws when the only url fails with network error and no retries left", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 502: { attempts: 0 } } });
// mockImplementationOnce (not persistent) avoids vitest flagging a reused rejection.
fetchMock.mockImplementationOnce(async () => { throw new Error("boom"); });
let thrown = null;
try {
await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
} catch (e) {
thrown = e;
}
expect(thrown?.message).toBe("boom");
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});
describe("BaseExecutor.execute — computeRetryDelay hook veto", () => {
it("hook returning false skips retry (uses fallback path)", async () => {
const ex = makeExec({ baseUrl: "https://x/api", retry: { 429: { attempts: 5, delayMs: 0 } } });
ex.computeRetryDelay = vi.fn().mockResolvedValue(false);
fetchMock.mockResolvedValueOnce(res(429));
const out = await ex.execute({ model: "m", body: {}, stream: false, credentials: creds });
// hook vetoes retry → no fallback url → returns the 429 response as-is
expect(out.response.status).toBe(429);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,15 @@
import { describe, expect, it } from "vitest";
import { capabilitiesFromServiceKind } from "../../open-sse/providers/capabilities.js";
describe("capabilitiesFromServiceKind", () => {
it("maps imageToText custom models to vision-capable runtime models", () => {
expect(capabilitiesFromServiceKind("imageToText")).toMatchObject({ vision: true });
});
it("maps media output/input custom model kinds to runtime capabilities", () => {
expect(capabilitiesFromServiceKind("image")).toMatchObject({ imageOutput: true });
expect(capabilitiesFromServiceKind("stt")).toMatchObject({ audioInput: true });
expect(capabilitiesFromServiceKind("tts")).toMatchObject({ audioOutput: true });
});
});

View File

@@ -9,24 +9,40 @@
*/
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
// Mock DNS so the SSRF guard treats example.com as public.
vi.mock("node:dns/promises", () => ({ lookup: async () => ({ address: "93.184.216.34" }) }));
import { CodexExecutor } from "../../open-sse/executors/codex.js";
import * as proxyFetchModule from "../../open-sse/utils/proxyFetch.js";
const IMAGE_1MB_BYTES = 1024 * 1024;
const REMOTE_URL = "https://example.com/big.jpg";
const DATA_URI = "data:image/png;base64,iVBORw0KGgo=";
// JPEG magic bytes (FF D8 FF) so magic-byte verification passes.
const JPEG_MAGIC = [0xff, 0xd8, 0xff];
function makeImageBuffer(sizeBytes) {
const buf = new Uint8Array(sizeBytes);
for (let i = 0; i < sizeBytes; i++) buf[i] = i & 0xff;
return buf.buffer;
for (let i = 0; i < JPEG_MAGIC.length; i++) buf[i] = JPEG_MAGIC[i];
for (let i = JPEG_MAGIC.length; i < sizeBytes; i++) buf[i] = i & 0xff;
return buf;
}
function mockImageFetch(sizeBytes, mimeType = "image/jpeg") {
// Mock a streaming Response body (getReader) as the hardened fetcher expects.
function mockImageFetch(sizeBytes) {
const bytes = makeImageBuffer(sizeBytes);
return {
ok: true,
headers: { get: (k) => (k === "Content-Type" ? mimeType : null) },
arrayBuffer: async () => makeImageBuffer(sizeBytes),
body: {
getReader() {
let sent = false;
return {
read: async () => sent ? { done: true } : (sent = true, { done: false, value: bytes }),
cancel: async () => {},
};
},
},
};
}

View File

@@ -0,0 +1,99 @@
import { describe, expect, it } from "vitest";
import { CodexExecutor } from "../../open-sse/executors/codex.js";
function normalizeTools(tools) {
const executor = new CodexExecutor();
const body = {
model: "gpt-5.5",
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "probe" }] }],
tools,
stream: true,
};
executor.transformRequest("gpt-5.5", body, true, {
connectionId: "test-codex-tools",
providerSpecificData: {},
});
return body.tools;
}
describe("CodexExecutor tool normalization", () => {
it("preserves Responses-native tool_search tools", () => {
const tools = normalizeTools([
{
type: "tool_search",
execution: "sync",
description: "Discover deferred tools",
parameters: { type: "object", properties: {} },
},
{
type: "namespace",
name: "codex_app",
description: "app tools",
tools: [
{
type: "function",
name: "automation_update",
description: "automation",
parameters: { type: "object", properties: {} },
defer_loading: true,
},
],
},
{
type: "function",
name: "plain_fn",
description: "plain",
parameters: { type: "object", properties: {} },
},
]);
expect(tools.map((tool) => `${tool.type}:${tool.name || ""}`)).toEqual([
"tool_search:",
"namespace:codex_app",
"function:plain_fn",
]);
});
it("preserves hosted Responses tools", () => {
const tools = normalizeTools([
{ type: "web_search", search_context_size: "medium" },
{ type: "image_generation", size: "1024x1024" },
{ type: "mcp", server_label: "docs", server_url: "https://example.com/mcp" },
{ type: "local_shell" },
{ type: "code_interpreter", container: { type: "auto" } },
{ type: "computer", display_width: 1024, display_height: 768, environment: "browser" },
]);
expect(tools.map((tool) => tool.type)).toEqual([
"web_search",
"image_generation",
"mcp",
"local_shell",
"code_interpreter",
"computer",
]);
});
it("preserves custom freeform tools with format payloads", () => {
const tools = normalizeTools([
{
type: "custom",
name: "apply_patch",
description: "patch",
format: { type: "grammar", syntax: "lark", definition: "start: /.+/" },
},
]);
expect(tools).toEqual([
{
type: "custom",
name: "apply_patch",
description: "patch",
format: { type: "grammar", syntax: "lark", definition: "start: /.+/" },
},
]);
});
});

View File

@@ -0,0 +1,78 @@
import { describe, it, expect } from "vitest";
import { detectRequiredCapabilities, reorderByCapabilities } from "../../open-sse/services/combo.js";
describe("detectRequiredCapabilities", () => {
it("text-only -> empty", () => {
const r = detectRequiredCapabilities({ messages: [{ role: "user", content: "hi" }] });
expect(r.size).toBe(0);
});
it("openai image_url -> vision", () => {
const r = detectRequiredCapabilities({ messages: [{ role: "user", content: [
{ type: "image_url", image_url: { url: "x" } },
] }] });
expect(r.has("vision")).toBe(true);
});
it("openai file -> pdf", () => {
const r = detectRequiredCapabilities({ messages: [{ role: "user", content: [
{ type: "file", file: { file_data: "data:application/pdf;base64,x" } },
] }] });
expect(r.has("pdf")).toBe(true);
});
it("gemini inlineData image -> vision", () => {
const r = detectRequiredCapabilities({ contents: [{ role: "user", parts: [
{ inlineData: { mimeType: "image/png", data: "x" } },
] }] });
expect(r.has("vision")).toBe(true);
});
it("antigravity request.contents image -> vision", () => {
const r = detectRequiredCapabilities({ request: { contents: [{ role: "user", parts: [
{ inlineData: { mimeType: "image/jpeg", data: "x" } },
] }] } });
expect(r.has("vision")).toBe(true);
});
it("web_search tool -> search", () => {
const r = detectRequiredCapabilities({ messages: [{ role: "user", content: "q" }], tools: [
{ type: "web_search" },
] });
expect(r.has("search")).toBe(true);
});
it("responses input_image -> vision", () => {
const r = detectRequiredCapabilities({ input: [{ role: "user", content: [
{ type: "input_image", image_url: "x" },
] }] });
expect(r.has("vision")).toBe(true);
});
});
describe("reorderByCapabilities", () => {
it("no required -> unchanged", () => {
const models = ["a/x", "b/y"];
expect(reorderByCapabilities(models, new Set())).toBe(models);
});
it("floats vision-capable model to front, keeps fallback", () => {
// deepseek-chat = no vision; claude-sonnet = vision
const models = ["deepseek/deepseek-chat", "anthropic/claude-sonnet-4.6"];
const out = reorderByCapabilities(models, new Set(["vision"]));
expect(out[0]).toBe("anthropic/claude-sonnet-4.6");
expect(out).toContain("deepseek/deepseek-chat"); // not dropped
expect(out).toHaveLength(2);
});
it("keeps order when no model matches", () => {
const models = ["deepseek/deepseek-chat", "deepseek/deepseek-reasoner"];
const out = reorderByCapabilities(models, new Set(["vision"]));
expect(out).toBe(models);
});
it("single model -> unchanged", () => {
const models = ["a/x"];
expect(reorderByCapabilities(models, new Set(["vision"]))).toBe(models);
});
});

View File

@@ -0,0 +1,216 @@
import { describe, it, expect, vi } from "vitest";
import { handleFusionChat } from "../../open-sse/services/combo.js";
const log = { info: () => {}, warn: () => {}, debug: () => {} };
// Minimal OpenAI-chat Response stub with the .ok + .clone().json() surface the engine uses.
function okResponse(content, { delayMs = 0 } = {}) {
const json = { choices: [{ message: { role: "assistant", content } }] };
const make = () => ({ ok: true, status: 200, clone: make, json: async () => json });
const res = make();
return delayMs > 0 ? new Promise((r) => setTimeout(() => r(res), delayMs)) : res;
}
function errResponse(status = 500) {
const make = () => ({ ok: false, status, clone: make, json: async () => ({ error: { message: "boom" } }) });
return make();
}
describe("fusion combo", () => {
it("answers directly with a single-model panel (nothing to fuse)", async () => {
const handleSingleModel = vi.fn(async () => okResponse("solo"));
await handleFusionChat({
body: { messages: [{ role: "user", content: "hi" }] },
models: ["p/only"],
handleSingleModel,
log,
});
expect(handleSingleModel).toHaveBeenCalledTimes(1);
expect(handleSingleModel.mock.calls[0][1]).toBe("p/only");
});
it("fans out to the panel then routes a synthesis turn to the judge", async () => {
const seen = [];
const handleSingleModel = vi.fn(async (body, model, isPanel) => {
seen.push(model);
if (model === "p/judge") return okResponse("FINAL");
return okResponse(`ans-${model}`);
});
const res = await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }], stream: true, tools: [{ name: "x" }] },
models: ["p/a", "p/b", "p/c"],
handleSingleModel,
log,
judgeModel: "p/judge",
});
// 3 panel calls + 1 judge call.
expect(handleSingleModel).toHaveBeenCalledTimes(4);
expect(seen.slice(0, 3).sort()).toEqual(["p/a", "p/b", "p/c"]);
expect(seen[3]).toBe("p/judge");
// Panel calls are non-streaming with tools stripped.
for (const [body, model, isPanel] of handleSingleModel.mock.calls.filter(([, m]) => m !== "p/judge")) {
expect(body.stream).toBe(false);
expect(body.tools).toBeUndefined();
expect(isPanel).toBe(true);
}
// Judge call carries every panel answer + keeps the client's stream flag.
const [judgeBody, , isPanel] = handleSingleModel.mock.calls.find(([, m]) => m === "p/judge");
const judgeText = judgeBody.messages.at(-1).content;
expect(judgeText).toContain("ans-p/a");
expect(judgeText).toContain("ans-p/b");
expect(judgeText).toContain("ans-p/c");
expect(judgeText).toContain("Source 1");
expect(judgeBody.stream).toBe(true);
expect(isPanel).toBeUndefined();
expect(res.ok).toBe(true);
});
it("defaults the judge to the first panel model when none is set", async () => {
const seen = [];
const handleSingleModel = vi.fn(async (_body, model) => { seen.push(model); return okResponse(`ans-${model}`); });
await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }] },
models: ["p/first", "p/second"],
handleSingleModel,
log,
});
// Last call is the judge; defaults to panel[0].
expect(seen.at(-1)).toBe("p/first");
});
it("proceeds on quorum without waiting for a straggler (grace window)", async () => {
const handleSingleModel = vi.fn(async (_body, model) => {
if (model === "p/slow") return okResponse("slow", { delayMs: 5000 });
if (model === "p/judge") return okResponse("FINAL");
return okResponse(`fast-${model}`);
});
const t0 = Date.now();
await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }] },
models: ["p/x", "p/y", "p/slow"],
handleSingleModel,
log,
judgeModel: "p/judge",
tuning: { minPanel: 2, stragglerGraceMs: 50, panelHardTimeoutMs: 10000 },
});
const elapsed = Date.now() - t0;
// Two fast answers reach quorum; grace is 50ms, so we never wait ~5s for p/slow.
expect(elapsed).toBeLessThan(2000);
const judgeCall = handleSingleModel.mock.calls.find(([, m]) => m === "p/judge");
const judgeText = judgeCall[0].messages.at(-1).content;
expect(judgeText).toContain("fast-p/x");
expect(judgeText).toContain("fast-p/y");
expect(judgeText).not.toContain("slow");
});
it("returns the lone survivor directly when only one panel model succeeds", async () => {
const handleSingleModel = vi.fn(async (_body, model) => {
if (model === "p/ok") return okResponse("lone");
return errResponse(500);
});
await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }] },
models: ["p/ok", "p/bad"],
handleSingleModel,
log,
judgeModel: "p/judge",
tuning: { minPanel: 2, stragglerGraceMs: 50, panelHardTimeoutMs: 5000 },
});
// No judge call — single answer means there is nothing to fuse.
const judged = handleSingleModel.mock.calls.some(([, m]) => m === "p/judge");
expect(judged).toBe(false);
});
it("returns 503 when the whole panel fails", async () => {
const handleSingleModel = vi.fn(async () => errResponse(500));
const res = await handleFusionChat({
body: { messages: [{ role: "user", content: "Q" }] },
models: ["p/a", "p/b"],
handleSingleModel,
log,
tuning: { minPanel: 2, stragglerGraceMs: 50, panelHardTimeoutMs: 5000 },
});
expect(res.status).toBe(503);
});
it("flattens previous tool history and assistant tool_calls into prose for panel calls", async () => {
const handleSingleModel = vi.fn(async () => okResponse("ans"));
await handleFusionChat({
body: {
messages: [
{ role: "user", content: "find files" },
{ role: "assistant", content: "", tool_calls: [{ id: "c1", type: "function", function: { name: "find" } }] },
{ role: "tool", tool_call_id: "c1", content: "['a.js']" },
{ role: "user", content: "describe it" }
],
tools: [{ type: "function" }]
},
models: ["p/a", "p/b"],
handleSingleModel,
log,
judgeModel: "p/judge"
});
// Panel calls keep every turn but tool turns are flattened to assistant prose.
const panelCalls = handleSingleModel.mock.calls.filter(([,, isPanel]) => isPanel === true);
expect(panelCalls.length).toBe(2);
for (const [panelBody] of panelCalls) {
expect(panelBody.tools).toBeUndefined();
expect(panelBody.messages.length).toBe(4);
expect(panelBody.messages[0]).toEqual({ role: "user", content: "find files" });
expect(panelBody.messages[1].tool_calls).toBeUndefined();
expect(panelBody.messages[1].content).toContain("find");
expect(panelBody.messages[2].role).toBe("assistant");
expect(panelBody.messages[2].content).toContain("['a.js']");
expect(panelBody.messages[3]).toEqual({ role: "user", content: "describe it" });
}
// Judge call still receives the unmodified history + synthesis prompt.
const judgeCall = handleSingleModel.mock.calls.find(([, m]) => m === "p/judge");
expect(judgeCall).toBeDefined();
const judgeBody = judgeCall[0];
expect(judgeBody.messages.length).toBe(5); // original 4 + judge prompt turn
expect(judgeBody.messages[1].tool_calls).toBeDefined();
expect(judgeBody.messages[2].role).toBe("tool");
});
it("flattens Anthropic-style tool_use and tool_result blocks in arrays", async () => {
const handleSingleModel = vi.fn(async () => okResponse("ans"));
await handleFusionChat({
body: {
messages: [
{ role: "user", content: "do it" },
{ role: "assistant", content: [{ type: "text", text: "ok" }, { type: "tool_use", id: "t1", name: "run" }] },
{ role: "user", content: [{ type: "tool_result", tool_use_id: "t1", content: "done" }] }
],
tools: [{ name: "run", description: "d" }]
},
models: ["p/a", "p/b"],
handleSingleModel,
log,
judgeModel: "p/judge"
});
const panelCalls = handleSingleModel.mock.calls.filter(([,, isPanel]) => isPanel === true);
expect(panelCalls.length).toBe(2);
const panelBody = panelCalls[0][0];
expect(panelBody.tools).toBeUndefined();
expect(panelBody.messages.length).toBe(3);
// Flattened tool_use
expect(panelBody.messages[1].content).toBe("ok\n[Called tools: run]");
// Flattened tool_result
expect(panelBody.messages[2].content).toBe("[Tool result: done]");
});
});

View File

@@ -9,13 +9,13 @@
*/
import { describe, it, expect } from "vitest";
import { convertCommandCodeToOpenAI } from "../../open-sse/translator/response/commandcode-to-openai.js";
import { commandCodeToOpenAIResponse } from "../../open-sse/translator/response/commandcode-to-openai.js";
function feed(events) {
const state = {};
const all = [];
for (const e of events) {
const out = convertCommandCodeToOpenAI(JSON.stringify(e), state);
const out = commandCodeToOpenAIResponse(JSON.stringify(e), state);
if (out) for (const c of out) all.push(c);
}
return { state, chunks: all };

View File

@@ -61,6 +61,26 @@ describe("dashboard guard public LLM API access", () => {
expect(mocks.validateApiKey).not.toHaveBeenCalled();
});
it("rejects remote Host-spoof when real peer IP is non-loopback", async () => {
const response = await proxy(request("/v1/chat/completions", {
host: "localhost",
"x-9r-real-ip": "10.204.111.34",
}));
expect(response.status).toBe(401);
expect(response.body.error).toBe("API key required for remote API access");
});
it("allows loopback peer IP regardless of Host", async () => {
const response = await proxy(request("/v1/chat/completions", {
host: "localhost:20128",
"x-9r-real-ip": "127.0.0.1",
}));
expect(response).toBe(mocks.nextResponse);
expect(mocks.validateApiKey).not.toHaveBeenCalled();
});
it("rejects remote rewritten public LLM API without API key", async () => {
const response = await proxy(request("/api/v1/chat/completions", { host: "router.example.com" }));
@@ -89,6 +109,25 @@ describe("dashboard guard public LLM API access", () => {
expect(response.body.error).toBe("API key required for remote API access");
});
it("rejects remote codex rewrite without API key", async () => {
const response = await proxy(request("/codex/x", { host: "router.example.com" }));
expect(response.status).toBe(401);
expect(response.body.error).toBe("API key required for remote API access");
});
it("allows remote codex rewrite with valid API key", async () => {
mocks.validateApiKey.mockResolvedValue(true);
const response = await proxy(request("/codex/x", {
host: "router.example.com",
authorization: "Bearer sk-valid",
}));
expect(response).toBe(mocks.nextResponse);
expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid");
});
it("allows remote public LLM API with valid bearer API key", async () => {
mocks.validateApiKey.mockResolvedValue(true);

View File

@@ -0,0 +1,48 @@
// A5 (cases #7/#9/#10): lock hardcode->config no-op values.
import { describe, it, expect } from "vitest";
import {
OPENAI_COMPAT_BASE,
ANTHROPIC_COMPAT_BASE,
ANTHROPIC_API_VERSION,
} from "../../open-sse/providers/shared.js";
import { DEFAULT_MAX_TOKENS, DEFAULT_MIN_TOKENS } from "../../open-sse/config/runtimeConfig.js";
import mimoFree from "../../open-sse/providers/registry/mimo-free.js";
import opencode from "../../open-sse/providers/registry/opencode.js";
import antigravity from "../../open-sse/providers/registry/antigravity.js";
describe("compat base URLs / version", () => {
it("OPENAI_COMPAT_BASE", () => {
expect(OPENAI_COMPAT_BASE).toBe("https://api.openai.com/v1");
});
it("ANTHROPIC_COMPAT_BASE", () => {
expect(ANTHROPIC_COMPAT_BASE).toBe("https://api.anthropic.com/v1");
});
it("ANTHROPIC_API_VERSION", () => {
expect(ANTHROPIC_API_VERSION).toBe("2023-06-01");
});
});
describe("default token limits", () => {
it("max/min", () => {
expect(DEFAULT_MAX_TOKENS).toBe(64000);
expect(DEFAULT_MIN_TOKENS).toBe(32000);
});
});
describe("provider baseUrl const (full path, no trailing slash)", () => {
it("mimo-free full path", () => {
expect(mimoFree.transport.baseUrl).toBe("https://api.xiaomimimo.com/api/free-ai/openai/chat");
});
it("opencode no trailing slash", () => {
expect(opencode.transport.baseUrl).toBe("https://opencode.ai");
});
});
describe("antigravity retry (intentional change: 429=6, 503=3)", () => {
it("429 attempts = 6", () => {
expect(antigravity.transport.retry["429"].attempts).toBe(6);
});
it("503 attempts = 3", () => {
expect(antigravity.transport.retry["503"].attempts).toBe(3);
});
});

View File

@@ -0,0 +1,57 @@
import { describe, it, expect } from "vitest";
import { convertOpenAIContentToParts } from "../../open-sse/translator/formats/gemini.js";
import { openaiToClaudeRequest } from "../../open-sse/translator/request/openai-to-claude.js";
import { VALID_OPENAI_CONTENT_TYPES, OPENAI_BLOCK, CLAUDE_BLOCK } from "../../open-sse/translator/schema/index.js";
const PDF_DATA = "data:application/pdf;base64,JVBERi0xLjE=";
const PNG_DATA = "data:image/png;base64,iVBORw0KGgo=";
describe("file/document block support", () => {
it("schema: file is a valid openai content type", () => {
expect(VALID_OPENAI_CONTENT_TYPES).toContain(OPENAI_BLOCK.FILE);
expect(OPENAI_BLOCK.FILE).toBe("file");
expect(CLAUDE_BLOCK.DOCUMENT).toBe("document");
});
it("gemini: openai file block -> inlineData", () => {
const parts = convertOpenAIContentToParts([
{ type: "text", text: "read this" },
{ type: "file", file: { filename: "d.pdf", file_data: PDF_DATA } },
]);
const inline = parts.find((p) => p.inlineData);
expect(inline).toBeTruthy();
expect(inline.inlineData.mime_type).toBe("application/pdf");
expect(inline.inlineData.data).toBe("JVBERi0xLjE=");
});
it("gemini: ignores non-data-uri file", () => {
const parts = convertOpenAIContentToParts([
{ type: "file", file: { filename: "d.pdf", file_data: "https://x/d.pdf" } },
]);
expect(parts.some((p) => p.inlineData)).toBe(false);
});
it("claude: openai file (pdf) -> document block", () => {
const out = openaiToClaudeRequest("claude-x", {
messages: [{ role: "user", content: [
{ type: "text", text: "read" },
{ type: "file", file: { filename: "d.pdf", file_data: PDF_DATA } },
] }],
}, false);
const blocks = out.messages[0].content;
const doc = blocks.find((b) => b.type === "document");
expect(doc).toBeTruthy();
expect(doc.source.media_type).toBe("application/pdf");
});
it("claude: non-pdf file is dropped (not a document)", () => {
const out = openaiToClaudeRequest("claude-x", {
messages: [{ role: "user", content: [
{ type: "text", text: "read" },
{ type: "file", file: { filename: "i.png", file_data: PNG_DATA } },
] }],
}, false);
const blocks = out.messages[0].content;
expect(blocks.some((b) => b.type === "document")).toBe(false);
});
});

View File

@@ -0,0 +1,83 @@
// A1: locks toOpenAIFinish/fromOpenAIFinish behavior changes vs open-sse.old.
import { describe, it, expect } from "vitest";
import { toOpenAIFinish, fromOpenAIFinish } from "../../open-sse/translator/concerns/finishReason.js";
import { OPENAI_FINISH, CLAUDE_STOP, GEMINI_FINISH } from "../../open-sse/translator/schema/finishReasons.js";
describe("toOpenAIFinish - gemini", () => {
it.each([
["SAFETY", "content_filter"],
["RECITATION", "content_filter"],
["BLOCKLIST", "content_filter"],
["PROHIBITED_CONTENT", "content_filter"],
["OTHER", "stop"],
["UNKNOWN_XYZ", "stop"],
["STOP", "stop"],
["MAX_TOKENS", "length"],
])("%s -> %s", (input, expected) => {
expect(toOpenAIFinish(input, "gemini")).toBe(expected);
});
});
describe("toOpenAIFinish - ollama", () => {
it.each([
["length", "length"],
["max_tokens", "length"],
["tool_calls", "tool_calls"],
["unknown_xyz", "stop"],
])("%s -> %s", (input, expected) => {
expect(toOpenAIFinish(input, "ollama")).toBe(expected);
});
});
describe("toOpenAIFinish - kiro", () => {
it("tool_use -> tool_calls", () => {
expect(toOpenAIFinish("tool_use", "kiro")).toBe("tool_calls");
});
});
describe("toOpenAIFinish - claude", () => {
it.each([
["end_turn", "stop"],
["max_tokens", "length"],
["tool_use", "tool_calls"],
])("%s -> %s", (input, expected) => {
expect(toOpenAIFinish(input, "claude")).toBe(expected);
});
});
describe("toOpenAIFinish - commandcode", () => {
it("tool-calls -> tool_calls", () => {
expect(toOpenAIFinish("tool-calls", "commandcode")).toBe("tool_calls");
});
it("unknown passthrough", () => {
expect(toOpenAIFinish("xyz", "commandcode")).toBe("xyz");
});
});
describe("fromOpenAIFinish round-trip - claude", () => {
it("tool_calls -> tool_use", () => {
expect(fromOpenAIFinish("tool_calls", "claude")).toBe("tool_use");
});
it("length -> max_tokens", () => {
expect(fromOpenAIFinish("length", "claude")).toBe("max_tokens");
});
});
describe("enum literals (catch drift)", () => {
it("OPENAI_FINISH literals", () => {
expect(OPENAI_FINISH.STOP).toBe("stop");
expect(OPENAI_FINISH.LENGTH).toBe("length");
expect(OPENAI_FINISH.TOOL_CALLS).toBe("tool_calls");
expect(OPENAI_FINISH.CONTENT_FILTER).toBe("content_filter");
});
it("CLAUDE_STOP literals", () => {
expect(CLAUDE_STOP.END_TURN).toBe("end_turn");
expect(CLAUDE_STOP.MAX_TOKENS).toBe("max_tokens");
expect(CLAUDE_STOP.TOOL_USE).toBe("tool_use");
});
it("GEMINI_FINISH literals", () => {
expect(GEMINI_FINISH.STOP).toBe("STOP");
expect(GEMINI_FINISH.MAX_TOKENS).toBe("MAX_TOKENS");
expect(GEMINI_FINISH.SAFETY).toBe("SAFETY");
});
});

View File

@@ -0,0 +1,17 @@
// Guards forceStream moved from chatCore hardcode → PROVIDERS schema (#5).
import { describe, it, expect } from "vitest";
const FORCED = ["openai", "codex", "commandcode"];
describe("forceStream provider config", () => {
it("only openai/codex/commandcode force streaming", async () => {
const { PROVIDERS } = await import("../../open-sse/config/providers.js");
for (const id of FORCED) {
expect(PROVIDERS[id]?.forceStream, `${id} forced`).toBe(true);
}
// a sample of others must NOT force
for (const id of ["deepseek", "claude", "gemini", "openrouter"]) {
expect(PROVIDERS[id]?.forceStream, `${id} not forced`).not.toBe(true);
}
});
});

View File

@@ -0,0 +1,36 @@
import { describe, it, expect, vi, afterEach } from "vitest";
const mocks = vi.hoisted(() => ({
execSync: vi.fn(() => { throw new Error("not found"); }),
}));
vi.mock("child_process", () => ({
execSync: mocks.execSync,
}));
import { getHeadroomStatus, isLoopbackHeadroomUrl } from "../../src/lib/headroom/detect.js";
afterEach(() => {
vi.clearAllMocks();
});
describe("headroom detect", () => {
it("treats a reachable external proxy as running without local CLI", async () => {
global.fetch = vi.fn(async () => new Response("ok", { status: 200 }));
const status = await getHeadroomStatus("http://headroom:8787");
expect(status.installed).toBe(false);
expect(status.running).toBe(true);
expect(status.localUrl).toBe(false);
expect(status.canStart).toBe(false);
expect(global.fetch).toHaveBeenCalledWith("http://headroom:8787/health", expect.any(Object));
});
it("recognizes loopback URLs for managed local mode", () => {
expect(isLoopbackHeadroomUrl("http://localhost:8787")).toBe(true);
expect(isLoopbackHeadroomUrl("http://127.0.0.1:8787")).toBe(true);
expect(isLoopbackHeadroomUrl("http://headroom:8787")).toBe(false);
expect(isLoopbackHeadroomUrl("not-a-url")).toBe(false);
});
});

View File

@@ -0,0 +1,73 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { compressWithHeadroom, formatHeadroomLog } from "../../open-sse/rtk/headroom.js";
afterEach(() => {
vi.restoreAllMocks();
});
describe("compressWithHeadroom", () => {
it("no-ops when disabled", async () => {
global.fetch = vi.fn();
const body = { messages: [{ role: "user", content: "hello" }] };
const stats = await compressWithHeadroom(body, { enabled: false, url: "http://localhost:8787" });
expect(stats).toBeNull();
expect(global.fetch).not.toHaveBeenCalled();
expect(body.messages[0].content).toBe("hello");
});
it("compresses messages in-place", async () => {
global.fetch = vi.fn(async () => new Response(JSON.stringify({
messages: [{ role: "user", content: "short" }],
tokens_before: 100,
tokens_after: 20,
tokens_saved: 80,
}), { status: 200 }));
const body = { messages: [{ role: "user", content: "long" }] };
const stats = await compressWithHeadroom(body, { enabled: true, url: "http://headroom:8787/", model: "gpt-4o" });
expect(body.messages[0].content).toBe("short");
expect(stats.tokens_saved).toBe(80);
expect(global.fetch).toHaveBeenCalledWith("http://headroom:8787/v1/compress", expect.objectContaining({ method: "POST" }));
});
it("compresses responses input in-place", async () => {
global.fetch = vi.fn(async () => new Response(JSON.stringify({
messages: [{ role: "user", content: "short" }],
}), { status: 200 }));
const body = { input: [{ role: "user", content: "long" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787" });
expect(body.input[0].content).toBe("short");
});
it("fails open on bad response", async () => {
global.fetch = vi.fn(async () => new Response(JSON.stringify({ error: "bad" }), { status: 500 }));
const body = { messages: [{ role: "user", content: "long" }] };
const stats = await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787" });
expect(stats).toBeNull();
expect(body.messages[0].content).toBe("long");
});
it("skips unknown shapes", async () => {
global.fetch = vi.fn();
const body = { contents: [{ parts: [{ text: "long" }] }] };
const stats = await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787" });
expect(stats).toBeNull();
expect(global.fetch).not.toHaveBeenCalled();
});
});
describe("formatHeadroomLog", () => {
it("formats savings", () => {
expect(formatHeadroomLog({ tokens_before: 100, tokens_after: 25, tokens_saved: 75 }))
.toBe("saved 75 tokens / 100 (75.0%) after=25");
});
});

View File

@@ -0,0 +1,77 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// Mock DNS lookup so we control which host resolves to what IP.
const lookupMock = vi.fn();
vi.mock("node:dns/promises", () => ({ lookup: (...a) => lookupMock(...a) }));
import { fetchImageAsBase64 } from "../../open-sse/translator/concerns/image.js";
const PNG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
function mockFetchOnce(bytes, ok = true) {
const body = {
getReader() {
let sent = false;
return {
read: async () => sent ? { done: true } : (sent = true, { done: false, value: new Uint8Array(bytes) }),
cancel: async () => {},
};
},
};
globalThis.fetch = vi.fn(async () => ({ ok, body }));
}
beforeEach(() => {
lookupMock.mockReset();
lookupMock.mockResolvedValue({ address: "93.184.216.34" }); // public by default
});
afterEach(() => { vi.restoreAllMocks(); });
describe("fetchImageAsBase64 hardening", () => {
it("rejects non-http url", async () => {
expect(await fetchImageAsBase64("ftp://x/y.png")).toBeNull();
expect(await fetchImageAsBase64("data:image/png;base64,xx")).toBeNull();
});
it("SSRF: rejects private IP (10.x)", async () => {
lookupMock.mockResolvedValue({ address: "10.0.0.5" });
expect(await fetchImageAsBase64("http://internal.example/x.png")).toBeNull();
});
it("SSRF: rejects cloud metadata 169.254.169.254", async () => {
lookupMock.mockResolvedValue({ address: "169.254.169.254" });
expect(await fetchImageAsBase64("http://metadata/x.png")).toBeNull();
});
it("SSRF: rejects blocked hostname localhost", async () => {
expect(await fetchImageAsBase64("http://localhost/x.png")).toBeNull();
});
it("SSRF: rejects IPv6 loopback", async () => {
lookupMock.mockResolvedValue({ address: "::1" });
expect(await fetchImageAsBase64("http://x/y.png")).toBeNull();
});
it("accepts valid PNG from public host", async () => {
mockFetchOnce(PNG);
const r = await fetchImageAsBase64("https://example.com/a.png");
expect(r).not.toBeNull();
expect(r.mimeType).toBe("image/png");
expect(r.url.startsWith("data:image/png;base64,")).toBe(true);
});
it("rejects disguised non-image payload (magic byte mismatch)", async () => {
mockFetchOnce(Buffer.from("<?php system($_GET[c]); ?>"));
expect(await fetchImageAsBase64("https://example.com/evil.png")).toBeNull();
});
it("rejects payload over size cap", async () => {
mockFetchOnce(Buffer.alloc(1024));
expect(await fetchImageAsBase64("https://example.com/big.png", { maxBytes: 100 })).toBeNull();
});
it("returns null when fetch not ok", async () => {
mockFetchOnce(PNG, false);
expect(await fetchImageAsBase64("https://example.com/404.png")).toBeNull();
});
});

View File

@@ -0,0 +1,63 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { KiroService } from "../../src/lib/oauth/services/kiro.js";
/**
* Regression tests for Kiro API-key auth.
*
* KiroService.validateApiKey resolves a profileArn with the key (via
* CodeWhisperer ListAvailableProfiles) and returns a credential shaped for
* persistence with authMethod="api_key". The response profile field name
* varies (`arn` vs `profileArn`) — both are accepted by listAvailableProfiles.
*
* Note: OAuth (Builder ID / IDC) profileArn resolution is handled upstream by
* fetchKiroProfileArn in providers.js and is covered there — not here.
*/
describe("kiro API-key auth (KiroService.validateApiKey)", () => {
beforeEach(() => vi.restoreAllMocks());
afterEach(() => vi.restoreAllMocks());
it("validates an API key and resolves a credential with profileArn", async () => {
const expectedArn = "arn:aws:codewhisperer:us-east-1:444:profile/KEY";
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: true,
json: async () => ({ profiles: [{ arn: expectedArn }] }),
});
const svc = new KiroService();
const cred = await svc.validateApiKey(" my-secret-key ");
expect(cred).toEqual({
accessToken: "my-secret-key",
refreshToken: null,
profileArn: expectedArn,
region: "us-east-1",
authMethod: "api_key",
});
const [url, init] = fetchMock.mock.calls[0];
expect(url).toBe("https://codewhisperer.us-east-1.amazonaws.com");
expect(init.headers.Authorization).toBe("Bearer my-secret-key");
expect(init.headers["x-amz-target"]).toBe(
"AmazonCodeWhispererService.ListAvailableProfiles"
);
});
it("rejects an empty API key without a network call", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch");
const svc = new KiroService();
await expect(svc.validateApiKey(" ")).rejects.toThrow("API key is required");
expect(fetchMock).not.toHaveBeenCalled();
});
it("surfaces a validation error when the key is rejected", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: false,
status: 401,
text: async () => "Unauthorized",
});
const svc = new KiroService();
await expect(svc.validateApiKey("bad-key")).rejects.toThrow(
/API key validation failed/
);
});
});

View File

@@ -0,0 +1,60 @@
/**
* Live repro for issue #1933: MiMo Code Free returns HTTP 502 "MiMo bootstrap failed: 403".
* Root cause: upstream gates on Chrome-like User-Agent. Without UA → 403 "Illegal access".
* Hits real endpoints — no mocks. Free provider, safe to call.
*/
import { describe, it, expect } from "vitest";
import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
import { __test__ } from "../../open-sse/executors/mimo-free.js";
const { BOOTSTRAP_URL, CHAT_URL, generateFingerprint, MIMO_SYSTEM_MARKER } = __test__;
const CHROME_UA =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
async function bootstrapWith(ua) {
const headers = { "Content-Type": "application/json" };
if (ua) headers["User-Agent"] = ua;
const r = await proxyAwareFetch(BOOTSTRAP_URL, {
method: "POST",
headers,
body: JSON.stringify({ client: generateFingerprint() }),
});
const data = await r.json();
return { status: r.status, jwt: data.jwt };
}
async function chatWith(jwt, ua) {
const headers = {
"Content-Type": "application/json",
"X-Mimo-Source": "mimocode-cli-free",
Authorization: `Bearer ${jwt}`,
Accept: "application/json",
};
if (ua) headers["User-Agent"] = ua;
const body = {
model: "mimo-auto",
messages: [
{ role: "system", content: MIMO_SYSTEM_MARKER },
{ role: "user", content: "hi" },
],
stream: false,
};
return proxyAwareFetch(CHAT_URL, { method: "POST", headers, body: JSON.stringify(body) });
}
describe("MiMo Free bootstrap (live)", () => {
it("bootstrap returns 200 with JWT", async () => {
const { status, jwt } = await bootstrapWith(CHROME_UA);
expect(status).toBe(200);
expect(jwt).toBeTruthy();
});
});
describe("MiMo Free anti-abuse gate (live)", () => {
it("chat WITH Chrome User-Agent → 200", async () => {
const { jwt } = await bootstrapWith(CHROME_UA);
const r = await chatWith(jwt, CHROME_UA);
expect(r.status).toBe(200);
});
});

View File

@@ -0,0 +1,108 @@
import { describe, it, expect } from "vitest";
import { stripUnsupportedModalities } from "../../open-sse/translator/concerns/modality.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
const NO_VISION = { vision: false, audioInput: true, pdf: true };
const NO_AUDIO = { vision: true, audioInput: false, pdf: true };
const NO_PDF = { vision: true, audioInput: true, pdf: false };
const ALL = { vision: true, audioInput: true, pdf: true };
describe("stripUnsupportedModalities", () => {
it("fast-exits when model supports all modalities", () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "x" } }] }] };
expect(stripUnsupportedModalities(body, FORMATS.OPENAI, ALL)).toBe(false);
expect(body.messages[0].content).toHaveLength(1);
});
it("openai: strips image when vision:false, leaves placeholder", () => {
const body = { messages: [{ role: "user", content: [
{ type: "text", text: "hi" },
{ type: "image_url", image_url: { url: "data:image/png;base64,xx" } },
] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI, NO_VISION);
const types = body.messages[0].content.map((b) => b.type);
expect(types).toContain("text");
expect(types).not.toContain("image_url");
expect(body.messages[0].content.some((b) => b.type === "text" && /image omitted/.test(b.text))).toBe(true);
});
it("openai: strips input_audio when audioInput:false", () => {
const body = { messages: [{ role: "user", content: [
{ type: "input_audio", input_audio: { data: "x", format: "wav" } },
] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI, NO_AUDIO);
expect(body.messages[0].content.some((b) => b.type === "input_audio")).toBe(false);
expect(body.messages[0].content.some((b) => /audio omitted/.test(b.text || ""))).toBe(true);
});
it("openai: strips file when pdf:false", () => {
const body = { messages: [{ role: "user", content: [
{ type: "file", file: { filename: "d.pdf", file_data: "data:application/pdf;base64,x" } },
] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI, NO_PDF);
expect(body.messages[0].content.some((b) => b.type === "file")).toBe(false);
});
it("openai: keeps image when vision:true", () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "x" } }] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI, NO_AUDIO);
expect(body.messages[0].content.some((b) => b.type === "image_url")).toBe(true);
});
it("claude: strips image + document by capability", () => {
const body = { messages: [{ role: "user", content: [
{ type: "text", text: "hi" },
{ type: "image", source: { type: "base64", media_type: "image/png", data: "x" } },
{ type: "document", source: { type: "base64", media_type: "application/pdf", data: "x" } },
] }] };
stripUnsupportedModalities(body, FORMATS.CLAUDE, { vision: false, audioInput: true, pdf: false });
const types = body.messages[0].content.map((b) => b.type);
expect(types).not.toContain("image");
expect(types).not.toContain("document");
expect(types).toContain("text");
});
it("gemini: strips inlineData image by mime when vision:false", () => {
const body = { contents: [{ role: "user", parts: [
{ text: "hi" },
{ inlineData: { mimeType: "image/png", data: "x" } },
] }] };
stripUnsupportedModalities(body, FORMATS.GEMINI, NO_VISION);
expect(body.contents[0].parts.some((p) => p.inlineData)).toBe(false);
expect(body.contents[0].parts.some((p) => /image omitted/.test(p.text || ""))).toBe(true);
});
it("gemini: keeps inlineData pdf when pdf:true, strips image when vision:false", () => {
const body = { contents: [{ role: "user", parts: [
{ inlineData: { mimeType: "image/png", data: "x" } },
{ inlineData: { mimeType: "application/pdf", data: "y" } },
] }] };
stripUnsupportedModalities(body, FORMATS.GEMINI, NO_VISION);
const mimes = body.contents[0].parts.filter((p) => p.inlineData).map((p) => p.inlineData.mimeType);
expect(mimes).toEqual(["application/pdf"]);
});
it("antigravity: strips inside request.contents", () => {
const body = { request: { contents: [{ role: "user", parts: [
{ inlineData: { mimeType: "image/png", data: "x" } },
] }] } };
stripUnsupportedModalities(body, FORMATS.ANTIGRAVITY, NO_VISION);
expect(body.request.contents[0].parts.some((p) => p.inlineData)).toBe(false);
});
it("responses: strips input_image when vision:false", () => {
const body = { input: [{ role: "user", content: [
{ type: "input_text", text: "hi" },
{ type: "input_image", image_url: "data:image/png;base64,x" },
] }] };
stripUnsupportedModalities(body, FORMATS.OPENAI_RESPONSES, NO_VISION);
expect(body.input[0].content.some((b) => b.type === "input_image")).toBe(false);
expect(body.input[0].content.some((b) => b.type === "input_text" && /image omitted/.test(b.text))).toBe(true);
});
it("handles missing/empty body safely", () => {
expect(stripUnsupportedModalities(null, FORMATS.OPENAI, NO_VISION)).toBe(false);
expect(stripUnsupportedModalities({}, FORMATS.OPENAI, null)).toBe(false);
expect(stripUnsupportedModalities({ messages: [] }, FORMATS.OPENAI, NO_VISION)).toBe(true);
});
});

View File

@@ -0,0 +1,28 @@
// Guards C2: regex name fallback (no catalog). Terse entries derive name; existing names untouched.
import { describe, it, expect } from "vitest";
import { deriveModelName } from "../../open-sse/providers/models/namePatterns.js";
import { normalizeModel } from "../../open-sse/providers/models/schema.js";
describe("model name regex fallback (C2)", () => {
it("derives display name from id per family", () => {
expect(deriveModelName("kimi-k2.5")).toBe("Kimi K2.5");
expect(deriveModelName("glm-4.6v")).toBe("GLM 4.6V (Vision)");
expect(deriveModelName("minimax-m2.7")).toBe("MiniMax M2.7");
expect(deriveModelName("gpt-5.4-mini")).toBe("GPT 5.4 Mini");
expect(deriveModelName("grok-4")).toBe("Grok 4");
});
it("falls back to id verbatim when no pattern matches", () => {
expect(deriveModelName("some-unknown-model")).toBe("some-unknown-model");
});
it("normalizeModel: explicit name always wins over regex", () => {
expect(normalizeModel({ id: "kimi-k2.5", name: "Custom" }).name).toBe("Custom");
});
it("normalizeModel: terse string id becomes object with derived name", () => {
const m = normalizeModel("glm-5");
expect(m.id).toBe("glm-5");
expect(m.name).toBe("GLM 5");
});
});

View File

@@ -0,0 +1,80 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
const originalDataDir = process.env.DATA_DIR;
async function setupDb() {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "9router-model-routing-"));
process.env.DATA_DIR = tempDir;
vi.resetModules();
const { createProviderNode } = await import("@/models/index.js");
const { getModelInfo } = await import("@/sse/services/model.js");
return {
createProviderNode,
getModelInfo,
cleanup() {
fs.rmSync(tempDir, { recursive: true, force: true });
},
};
}
describe("model routing", () => {
let cleanup = () => {};
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetModules();
vi.clearAllMocks();
cleanup();
cleanup = () => {};
if (originalDataDir === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = originalDataDir;
});
it("keeps built-in provider aliases ahead of compatible node prefixes", async () => {
const ctx = await setupDb();
cleanup = ctx.cleanup;
await ctx.createProviderNode({
id: "openai-compatible-chat-test",
type: "openai-compatible",
name: "Compatible CF Collision",
prefix: "cf",
apiType: "chat",
baseUrl: "https://compatible.test/v1",
});
await expect(ctx.getModelInfo("cf/@cf/black-forest-labs/flux-2-klein-9b"))
.resolves.toEqual({
provider: "cloudflare-ai",
model: "@cf/black-forest-labs/flux-2-klein-9b",
});
});
it("still routes non-reserved compatible node prefixes", async () => {
const ctx = await setupDb();
cleanup = ctx.cleanup;
await ctx.createProviderNode({
id: "openai-compatible-chat-test",
type: "openai-compatible",
name: "Compatible OCT",
prefix: "oct",
apiType: "chat",
baseUrl: "https://compatible.test/v1",
});
await expect(ctx.getModelInfo("oct/gpt-image-1"))
.resolves.toEqual({
provider: "openai-compatible-chat-test",
model: "gpt-image-1",
});
});
});

View File

@@ -0,0 +1,58 @@
// Locks multimodal quirks flagged in docs 11 §4: image_url.detail drop + input_audio per-format.
import { describe, it, expect } from "vitest";
import { openaiToClaudeRequest } from "../../open-sse/translator/request/openai-to-claude.js";
import { convertOpenAIContentToParts } from "../../open-sse/translator/formats/gemini.js";
function userImage(detail) {
return {
model: "claude-sonnet-4-6",
messages: [{
role: "user",
content: [
{ type: "text", text: "look" },
{ type: "image_url", image_url: { url: "data:image/png;base64,AAAB", detail } },
],
}],
};
}
describe("openai→claude: image_url.detail is dropped (docs 11 §4)", () => {
it("converts image to base64 source WITHOUT a detail field", () => {
const out = openaiToClaudeRequest("claude-sonnet-4-6", userImage("high"), false);
const imgBlock = out.messages[0].content.find((b) => b.type === "image");
expect(imgBlock).toBeTruthy();
expect(imgBlock.source).toEqual({ type: "base64", media_type: "image/png", data: "AAAB" });
expect("detail" in imgBlock).toBe(false);
expect("detail" in imgBlock.source).toBe(false);
});
it("drops input_audio entirely (claude has no audio block)", () => {
const body = {
model: "claude-sonnet-4-6",
messages: [{ role: "user", content: [
{ type: "text", text: "hi" },
{ type: "input_audio", input_audio: { data: "ZZZ", format: "wav" } },
] }],
};
const out = openaiToClaudeRequest("claude-sonnet-4-6", body, false);
const blocks = out.messages[0].content;
expect(blocks.some((b) => b.type === "audio" || b.type === "input_audio")).toBe(false);
});
});
describe("openai→gemini: input_audio is mapped to inlineData (docs 11 §4)", () => {
it("maps wav → audio/wav inlineData", () => {
const parts = convertOpenAIContentToParts([{ type: "input_audio", input_audio: { data: "ZZZ", format: "wav" } }]);
expect(parts).toEqual([{ inlineData: { mime_type: "audio/wav", data: "ZZZ" } }]);
});
it("maps mp3 → audio/mpeg inlineData", () => {
const parts = convertOpenAIContentToParts([{ type: "input_audio", input_audio: { data: "ZZZ", format: "mp3" } }]);
expect(parts[0].inlineData.mime_type).toBe("audio/mpeg");
});
it("drops image_url.detail (not carried into inlineData)", () => {
const parts = convertOpenAIContentToParts([{ type: "image_url", image_url: { url: "data:image/png;base64,AAAB", detail: "high" } }]);
expect(parts).toEqual([{ inlineData: { mime_type: "image/png", data: "AAAB" } }]);
});
});

View File

@@ -9,13 +9,13 @@
*/
import { describe, it, expect } from "vitest";
import { openaiToCommandCode } from "../../open-sse/translator/request/openai-to-commandcode.js";
import { openaiToCommandCodeRequest } from "../../open-sse/translator/request/openai-to-commandcode.js";
const MODEL = "moonshotai/Kimi-K2.6";
describe("openaiToCommandCode — basic envelope", () => {
describe("openaiToCommandCodeRequest — basic envelope", () => {
it("returns the expected top-level envelope shape", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
}, true);
@@ -28,9 +28,9 @@ describe("openaiToCommandCode — basic envelope", () => {
});
});
describe("openaiToCommandCode — system handling", () => {
describe("openaiToCommandCodeRequest — system handling", () => {
it("hoists system messages to params.system (string), not messages[]", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "system", content: "You are concise." },
{ role: "user", content: "hi" },
@@ -44,7 +44,7 @@ describe("openaiToCommandCode — system handling", () => {
});
it("joins multiple system messages with blank line", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "system", content: "A" },
{ role: "system", content: "B" },
@@ -56,16 +56,16 @@ describe("openaiToCommandCode — system handling", () => {
});
it("omits params.system when no system messages", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
}, true);
expect(out.params.system).toBeUndefined();
});
});
describe("openaiToCommandCode — content shape", () => {
describe("openaiToCommandCodeRequest — content shape", () => {
it("MUST always emit content as Array (never string) for user", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hello" }],
}, true);
@@ -75,7 +75,7 @@ describe("openaiToCommandCode — content shape", () => {
});
it("MUST always emit content as Array for assistant", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "user", content: "a" },
{ role: "assistant", content: "b" },
@@ -87,9 +87,9 @@ describe("openaiToCommandCode — content shape", () => {
});
});
describe("openaiToCommandCode — tool role / tool-result (AI SDK)", () => {
describe("openaiToCommandCodeRequest — tool role / tool-result (AI SDK)", () => {
it("converts role:\"tool\" to role:\"tool\" with tool-result block; output is {type:\"text\",value}", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "user", content: "run X" },
{
@@ -113,9 +113,9 @@ describe("openaiToCommandCode — tool role / tool-result (AI SDK)", () => {
});
});
describe("openaiToCommandCode — assistant tool_calls / tool-call", () => {
describe("openaiToCommandCodeRequest — assistant tool_calls / tool-call", () => {
it("converts assistant.tool_calls[] into content blocks of type tool-call", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [
{ role: "user", content: "go" },
{
@@ -138,9 +138,9 @@ describe("openaiToCommandCode — assistant tool_calls / tool-call", () => {
});
});
describe("openaiToCommandCode — tools schema conversion", () => {
describe("openaiToCommandCodeRequest — tools schema conversion", () => {
it("converts OpenAI {type:\"function\", function:{...}} to Anthropic plain {name, input_schema}", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
tools: [
{
@@ -163,7 +163,7 @@ describe("openaiToCommandCode — tools schema conversion", () => {
});
it("preserves description on converted tool", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
tools: [
{ type: "function", function: { name: "ping", description: "Ping the server", parameters: { type: "object" } } },
@@ -173,7 +173,7 @@ describe("openaiToCommandCode — tools schema conversion", () => {
});
it("does not include tools field when input has none", () => {
const out = openaiToCommandCode(MODEL, {
const out = openaiToCommandCodeRequest(MODEL, {
messages: [{ role: "user", content: "hi" }],
}, true);
expect(out.params.tools).toBeUndefined();

View File

@@ -2,21 +2,24 @@
* Unit tests for open-sse/translator/request/openai-to-kiro.js
*
* Tests cover:
* - buildKiroPayload() - basic message conversion
* - openaiToKiroRequest() - basic message conversion
* - Image forwarding fix: images in currentMessage must be included in payload
*/
import { describe, it, expect } from "vitest";
import { buildKiroPayload } from "../../open-sse/translator/request/openai-to-kiro.js";
import { openaiToKiroRequest } from "../../open-sse/translator/request/openai-to-kiro.js";
describe("buildKiroPayload", () => {
const contentOf = (result) =>
result.conversationState.currentMessage.userInputMessage.content;
describe("openaiToKiroRequest", () => {
describe("basic message conversion", () => {
it("should convert a simple text message", () => {
const body = {
messages: [{ role: "user", content: "Hello" }]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.content).toContain("Hello");
@@ -29,7 +32,7 @@ describe("buildKiroPayload", () => {
messages: [{ role: "user", content: "No images here" }]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.images).toBeUndefined();
@@ -51,7 +54,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.images).toBeDefined();
@@ -75,7 +78,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.images).toHaveLength(2);
@@ -95,7 +98,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.images).toBeUndefined();
@@ -115,7 +118,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
expect(currentMsg.userInputMessage.content).toContain("What is in this image?");
@@ -135,7 +138,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const currentMsg = result.conversationState.currentMessage;
// HTTP URLs are not supported by Kiro — converted to text placeholder
@@ -166,7 +169,7 @@ describe("buildKiroPayload", () => {
// note: no `tools`
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const cs = result.conversationState;
// No structured tool content anywhere
@@ -201,7 +204,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const cs = result.conversationState;
const allJson = JSON.stringify(cs);
@@ -233,7 +236,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const cs = result.conversationState;
// Structured tool spec carried on currentMessage
@@ -270,7 +273,7 @@ describe("buildKiroPayload", () => {
]
};
const result = buildKiroPayload("claude-sonnet-4.6", body, true, {});
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
const cs = result.conversationState;
const allJson = JSON.stringify(cs);
@@ -280,4 +283,83 @@ describe("buildKiroPayload", () => {
expect(allJson).toContain("[Tool result: important orphaned output]");
});
});
describe("thinking budget", () => {
it("maps reasoning_effort low to max_thinking_length 1024", () => {
const body = {
reasoning_effort: "low",
messages: [{ role: "user", content: "Think lightly" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>1024</max_thinking_length>");
});
it("maps reasoning_effort high to max_thinking_length 24576", () => {
const body = {
reasoning_effort: "high",
messages: [{ role: "user", content: "Think deeply" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>24576</max_thinking_length>");
});
it("clamps reasoning_effort max to Kiro max_thinking_length 32000", () => {
const body = {
reasoning_effort: "max",
messages: [{ role: "user", content: "Think as much as possible" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>32000</max_thinking_length>");
});
it("clamps OpenAI Responses reasoning.effort xhigh to max_thinking_length 32000", () => {
const body = {
reasoning: { effort: "xhigh" },
messages: [{ role: "user", content: "Think extra deeply" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>32000</max_thinking_length>");
});
it("uses Claude thinking.budget_tokens as max_thinking_length", () => {
const body = {
thinking: { type: "enabled", budget_tokens: 4096 },
messages: [{ role: "user", content: "Use a fixed budget" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>4096</max_thinking_length>");
});
it("uses the default budget for synthetic -thinking models with no explicit config", () => {
const body = {
messages: [{ role: "user", content: "Think by model suffix" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6-thinking", body, true, {});
expect(contentOf(result)).toContain("<max_thinking_length>16000</max_thinking_length>");
});
it("does not inject thinking prefix for reasoning_effort none", () => {
const body = {
reasoning_effort: "none",
messages: [{ role: "user", content: "Do not think" }]
};
const result = openaiToKiroRequest("claude-sonnet-4.6", body, true, {});
expect(contentOf(result)).not.toContain("<thinking_mode>enabled</thinking_mode>");
expect(contentOf(result)).not.toContain("<max_thinking_length>");
});
});
});

View File

@@ -0,0 +1,30 @@
// A4 (case #10): malformed tool_calls args must not throw -> safeParseJSON returns {}.
import { describe, it, expect } from "vitest";
import { openaiToOllamaRequest } from "../../open-sse/translator/request/openai-to-ollama.js";
function reqWith(args) {
return {
messages: [
{
role: "assistant",
content: "",
tool_calls: [{ id: "c1", type: "function", function: { name: "get_weather", arguments: args } }],
},
],
};
}
describe("openaiToOllamaRequest - tool_calls arguments parsing", () => {
it("malformed JSON args -> {} (no throw)", () => {
let out;
expect(() => {
out = openaiToOllamaRequest("m", reqWith("{invalid json"), true);
}).not.toThrow();
expect(out.messages[0].tool_calls[0].function.arguments).toEqual({});
});
it("valid JSON args -> parsed object", () => {
const out = openaiToOllamaRequest("m", reqWith('{"a":1}'), true);
expect(out.messages[0].tool_calls[0].function.arguments).toEqual({ a: 1 });
});
});

View File

@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import { PROVIDER_MODELS, getModelTargetFormat } from "../../open-sse/config/providerModels.js";
import { OpenCodeGoExecutor } from "../../open-sse/executors/opencode-go.js";
const CHAT_MODELS = [
"glm-5.2",
"glm-5.1",
// OpenCode Go docs' endpoint table currently says kimi-k2.7, but its
// config example and the live API use kimi-k2.7-code.
"kimi-k2.7-code",
"kimi-k2.6",
"deepseek-v4-pro",
"deepseek-v4-flash",
"mimo-v2.5",
"mimo-v2.5-pro",
];
const MESSAGES_MODELS = [
"minimax-m3",
"minimax-m2.7",
"minimax-m2.5",
"qwen3.7-max",
"qwen3.7-plus",
"qwen3.6-plus",
];
describe("OpenCode Go official model catalog", () => {
it("matches the documented OpenCode Go model IDs", () => {
const ids = (PROVIDER_MODELS["opencode-go"] || []).map((model) => model.id);
expect(ids).toEqual([...CHAT_MODELS, ...MESSAGES_MODELS]);
});
it("marks documented Qwen and MiniMax models as Anthropic messages format", () => {
for (const model of MESSAGES_MODELS) {
expect(getModelTargetFormat("opencode-go", model)).toBe("claude");
}
});
it("keeps GLM, Kimi, DeepSeek, and MiMo on OpenAI-compatible chat format", () => {
for (const model of CHAT_MODELS) {
expect(getModelTargetFormat("opencode-go", model)).toBeNull();
}
});
});
describe("OpenCode Go endpoint routing", () => {
it("routes Qwen and MiniMax models to the messages endpoint with x-api-key auth", () => {
const executor = new OpenCodeGoExecutor();
for (const model of MESSAGES_MODELS) {
expect(executor.buildUrl(model)).toBe("https://opencode.ai/zen/go/v1/messages");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, false);
expect(headers["x-api-key"]).toBe("sk-test");
expect(headers["anthropic-version"]).toBeDefined();
expect(headers.Authorization).toBeUndefined();
}
});
it("routes GLM, Kimi, DeepSeek, and MiMo models to chat/completions with bearer auth", () => {
const executor = new OpenCodeGoExecutor();
for (const model of CHAT_MODELS) {
expect(executor.buildUrl(model)).toBe("https://opencode.ai/zen/go/v1/chat/completions");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, false);
expect(headers.Authorization).toBe("Bearer sk-test");
expect(headers["x-api-key"]).toBeUndefined();
expect(headers["anthropic-version"]).toBeUndefined();
}
});
});

View File

@@ -0,0 +1,58 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("../../open-sse/translator/concerns/image.js", async (orig) => {
const actual = await orig();
return {
...actual,
fetchImageAsBase64: vi.fn(async () => ({ url: "data:image/png;base64,QUJD", mimeType: "image/png" })),
};
});
import { prefetchRemoteImages } from "../../open-sse/translator/concerns/prefetch.js";
import { fetchImageAsBase64 } from "../../open-sse/translator/concerns/image.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
beforeEach(() => { fetchImageAsBase64.mockClear(); });
afterEach(() => { vi.restoreAllMocks(); });
describe("prefetchRemoteImages", () => {
it("no-op for targets that accept remote URLs (openai)", async () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "https://x/a.png" } }] }] };
const n = await prefetchRemoteImages(body, FORMATS.OPENAI, FORMATS.OPENAI);
expect(n).toBe(0);
expect(body.messages[0].content[0].image_url.url).toBe("https://x/a.png");
});
it("openai source -> ollama target: converts remote URL to base64", async () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "https://x/a.png" } }] }] };
const n = await prefetchRemoteImages(body, FORMATS.OPENAI, FORMATS.OLLAMA);
expect(n).toBe(1);
expect(body.messages[0].content[0].image_url.url.startsWith("data:image/png;base64,")).toBe(true);
});
it("skips data URI (already inline)", async () => {
const body = { messages: [{ role: "user", content: [{ type: "image_url", image_url: { url: "data:image/png;base64,xx" } }] }] };
const n = await prefetchRemoteImages(body, FORMATS.OPENAI, FORMATS.OLLAMA);
expect(n).toBe(0);
expect(fetchImageAsBase64).not.toHaveBeenCalled();
});
it("gemini source -> gemini target: fileData URL -> inlineData base64", async () => {
const body = { contents: [{ role: "user", parts: [
{ fileData: { mimeType: "image/png", fileUri: "https://x/a.png" } },
] }] };
const n = await prefetchRemoteImages(body, FORMATS.GEMINI, FORMATS.GEMINI);
expect(n).toBe(1);
expect(body.contents[0].parts[0].inlineData).toBeTruthy();
expect(body.contents[0].parts[0].fileData).toBeUndefined();
});
it("claude source -> kiro target: source.url -> base64", async () => {
const body = { messages: [{ role: "user", content: [
{ type: "image", source: { type: "url", url: "https://x/a.png" } },
] }] };
const n = await prefetchRemoteImages(body, FORMATS.CLAUDE, FORMATS.KIRO);
expect(n).toBe(1);
expect(body.messages[0].content[0].source.type).toBe("base64");
});
});

View File

@@ -0,0 +1,84 @@
import { describe, expect, it } from "vitest";
import { getProviderCustomModelRows } from "@/shared/utils/providerCustomModels.js";
describe("provider custom model rows", () => {
it("keeps identical model IDs separate per provider", () => {
const customModels = [
{ providerAlias: "ollama", id: "minimax-m2.5", type: "llm", name: "MiniMax M2.5" },
{ providerAlias: "opencode-go", id: "minimax-m2.5", type: "llm", name: "MiniMax M2.5" },
];
expect(getProviderCustomModelRows({ customModels, providerAlias: "ollama" })).toEqual([
{
id: "minimax-m2.5",
name: "MiniMax M2.5",
fullModel: "ollama/minimax-m2.5",
source: "custom",
type: "llm",
},
]);
expect(getProviderCustomModelRows({ customModels, providerAlias: "opencode-go" })).toEqual([
{
id: "minimax-m2.5",
name: "MiniMax M2.5",
fullModel: "opencode-go/minimax-m2.5",
source: "custom",
type: "llm",
},
]);
});
it("keeps legacy alias-backed models visible without duplicating custom models", () => {
const rows = getProviderCustomModelRows({
customModels: [
{ providerAlias: "ollama", id: "custom-a", type: "llm", name: "Custom A" },
],
modelAliases: {
"custom-a": "ollama/custom-a",
"legacy-b": "ollama/legacy-b",
"other-provider": "opencode-go/legacy-b",
},
providerAlias: "ollama",
});
expect(rows).toEqual([
{
id: "custom-a",
name: "Custom A",
fullModel: "ollama/custom-a",
source: "custom",
type: "llm",
},
{
id: "legacy-b",
alias: "legacy-b",
fullModel: "ollama/legacy-b",
source: "legacyAlias",
type: "llm",
},
]);
});
it("filters built-in models and typed custom models", () => {
const rows = getProviderCustomModelRows({
customModels: [
{ providerAlias: "ollama", id: "llama3", type: "llm", name: "Llama 3" },
{ providerAlias: "ollama", id: "custom-image", type: "image", name: "Custom Image" },
{ providerAlias: "ollama", id: "custom-llm", type: "llm", name: "Custom LLM" },
],
providerAlias: "ollama",
builtInModels: [{ id: "llama3" }],
type: "llm",
});
expect(rows).toEqual([
{
id: "custom-llm",
name: "Custom LLM",
fullModel: "ollama/custom-llm",
source: "custom",
type: "llm",
},
]);
});
});

View File

@@ -0,0 +1,34 @@
// Guards E1: display fields live in providersDisplay.js, merged back into AI_PROVIDERS (shape unchanged).
import { describe, it, expect } from "vitest";
const DISPLAY_FIELDS = ["name", "icon", "color"];
describe("provider display split (E1)", () => {
it("AI_PROVIDERS entries still carry merged display + transport", async () => {
const { AI_PROVIDERS } = await import("../../src/shared/constants/providers.js");
const kiro = AI_PROVIDERS.kiro;
// display merged
expect(kiro.name).toBe("Kiro AI");
expect(kiro.icon).toBe("psychology_alt");
// transport kept
expect(kiro.id).toBe("kiro");
expect(kiro.alias).toBe("kr");
// transport-heavy provider keeps its config
expect(AI_PROVIDERS.gemini.serviceKinds).toContain("tts");
expect(AI_PROVIDERS.gemini.ttsConfig).toBeTruthy();
});
it("display fields source from providersDisplay.js", async () => {
const { PROVIDER_DISPLAY } = await import("../../src/shared/constants/providersDisplay.js");
const { AI_PROVIDERS } = await import("../../src/shared/constants/providers.js");
for (const f of DISPLAY_FIELDS) {
expect(PROVIDER_DISPLAY.kiro[f]).toBe(AI_PROVIDERS.kiro[f]);
}
});
it("helpers still work after split", async () => {
const m = await import("../../src/shared/constants/providers.js");
expect(m.ALIAS_TO_ID.kr).toBe("kiro");
expect(m.getProvidersByKind("tts").length).toBeGreaterThan(0);
});
});

View File

@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { MODEL_PRICING } from "../../src/shared/constants/pricing.js";
import { MODEL_PRICING } from "../../open-sse/providers/pricing.js";
describe("MiniMax-M3 pricing", () => {
it("includes MiniMax-M3 in MODEL_PRICING", () => {

View File

@@ -95,9 +95,9 @@ describe("RTK filters", () => {
const input = makeFindOutput();
const out = find(input);
expect(out).toContain("55 files in 3 dirs:");
expect(out).toContain("./src/a/ (30):");
expect(out).toContain("./src/b/ (20):");
expect(out).toContain("./ (5):");
expect(out).toContain("./src/a/ (30)");
expect(out).toContain("./src/b/ (20)");
expect(out).toContain("./ (5)");
expect(out.length).toBeLessThan(input.length);
});

View File

@@ -0,0 +1,62 @@
// A2: locks resolveSessionId priority/stickiness (codex/kiro/antigravity centralization).
import { describe, it, expect, beforeEach } from "vitest";
import { resolveSessionId, deriveSessionId, clearSessionStore } from "../../open-sse/utils/sessionManager.js";
// Assistant text must reach ASSISTANT_MIN_LEN (80) to use assistant anchor; else first user message.
const longAssistant = "x".repeat(80);
const bodyWithAssistant = { messages: [{ role: "assistant", content: longAssistant }] };
const bodyWithUserOnly = { messages: [{ role: "user", content: "hello from first user message anchor" }] };
beforeEach(() => clearSessionStore());
describe("resolveSessionId", () => {
it("stickiness: same body+connectionId+scope -> same id", () => {
const opts = { body: bodyWithAssistant, connectionId: "conn1", scope: "codex" };
expect(resolveSessionId(opts)).toBe(resolveSessionId(opts));
});
it("different connectionId -> different id", () => {
const a = resolveSessionId({ body: bodyWithAssistant, connectionId: "connA", scope: "codex" });
const b = resolveSessionId({ body: bodyWithAssistant, connectionId: "connB", scope: "codex" });
expect(a).not.toBe(b);
});
it("different scope -> different id", () => {
const a = resolveSessionId({ body: bodyWithAssistant, connectionId: "conn1", scope: "codex" });
const b = resolveSessionId({ body: bodyWithAssistant, connectionId: "conn1", scope: "kiro" });
expect(a).not.toBe(b);
});
it("first user message anchor when assistant text below cap", () => {
const opts = { body: bodyWithUserOnly, connectionId: "conn1", scope: "codex" };
expect(resolveSessionId(opts)).toBe(resolveSessionId(opts));
});
it("assistant anchor wins once assistant text reaches cap", () => {
const shortAssistant = { messages: [{ role: "user", content: "same user" }, { role: "assistant", content: "y".repeat(80) }] };
const a = resolveSessionId({ body: shortAssistant, connectionId: "conn1", scope: "codex" });
const b = resolveSessionId({ body: shortAssistant, connectionId: "conn1", scope: "codex" });
expect(a).toBe(b);
});
it("fallback: empty body+no header+no workspaceId -> deriveSessionId(connectionId)", () => {
const got = resolveSessionId({ body: {}, connectionId: "connFallback" });
expect(got).toBe(deriveSessionId("connFallback"));
});
it("client override: x-session-id header wins, skips later steps", () => {
const got = resolveSessionId({
headers: { "x-session-id": "client-sess-123" },
body: bodyWithAssistant,
connectionId: "conn1",
workspaceId: "ws1",
scope: "codex",
});
expect(got).toBe("client-sess-123");
});
it("workspaceId path: empty body + workspaceId set -> normalized workspaceId", () => {
const got = resolveSessionId({ body: {}, connectionId: "conn1", workspaceId: "ws-abc" });
expect(got).toBe("ws-abc");
});
});

View File

@@ -0,0 +1,22 @@
// Guards the refactored REFRESH_HANDLERS dispatch: null-guards + the two different defaults.
import { describe, it, expect } from "vitest";
const load = () => import("../../open-sse/services/tokenRefresh.js");
describe("tokenRefresh dispatch", () => {
it("getAccessToken returns null for missing/invalid refreshToken", async () => {
const mod = await load();
expect(await mod.getAccessToken("claude", {}, null)).toBeNull();
expect(await mod.getAccessToken("claude", { refreshToken: 123 }, null)).toBeNull();
});
it("getAccessToken default: unsupported provider → null", async () => {
const mod = await load();
expect(await mod.getAccessToken("totally-unknown", { refreshToken: "x" }, null)).toBeNull();
});
it("refreshTokenByProvider returns null without refreshToken", async () => {
const mod = await load();
expect(await mod.refreshTokenByProvider("claude", {}, null)).toBeNull();
});
});

View File

@@ -0,0 +1,47 @@
// Locks edge cases flagged in docs 11 §1/§4 that were only covered indirectly.
import { describe, it, expect } from "vitest";
import { normalizeClaudePassthrough } from "../../open-sse/translator/formats/claude.js";
import { parseDataUri, encodeDataUri } from "../../open-sse/translator/concerns/image.js";
describe("normalizeClaudePassthrough — haiku adaptive thinking (docs 11 §1)", () => {
it("downgrades adaptive thinking to enabled+budget for haiku models", () => {
const out = normalizeClaudePassthrough({ thinking: { type: "adaptive" } }, "claude-haiku-4-5");
expect(out.thinking).toEqual({ type: "enabled", budget_tokens: 10000 });
});
it("keeps adaptive thinking for sonnet/opus", () => {
const out = normalizeClaudePassthrough({ thinking: { type: "adaptive" } }, "claude-sonnet-4-6");
expect(out.thinking).toEqual({ type: "adaptive" });
});
it("hoists mid-conversation system messages into top-level system", () => {
const out = normalizeClaudePassthrough({
messages: [
{ role: "user", content: "hi" },
{ role: "system", content: "be brief" },
],
});
expect(out.system).toEqual([{ type: "text", text: "be brief" }]);
expect(out.messages.every((m) => m.role !== "system")).toBe(true);
});
});
describe("parseDataUri / encodeDataUri (docs 11 §4)", () => {
it("parses a base64 data uri", () => {
expect(parseDataUri("data:image/png;base64,AAAB")).toEqual({ mimeType: "image/png", base64: "AAAB" });
});
it("tolerates newlines inside base64 payload", () => {
expect(parseDataUri("data:image/jpeg;base64,AA\nBB")?.base64).toBe("AA\nBB");
});
it("returns null for http urls and non-strings", () => {
expect(parseDataUri("https://x/y.png")).toBeNull();
expect(parseDataUri(null)).toBeNull();
});
it("encode/parse roundtrip", () => {
const uri = encodeDataUri("image/webp", "ZZZ");
expect(parseDataUri(uri)).toEqual({ mimeType: "image/webp", base64: "ZZZ" });
});
});

View File

@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
import { FORMATS } from "../../open-sse/translator/formats.js";
import { translateRequest } from "../../open-sse/translator/index.js";
import { claudeToOpenAIRequest } from "../../open-sse/translator/request/claude-to-openai.js";
import { filterToOpenAIFormat } from "../../open-sse/translator/helpers/openaiHelper.js";
import { filterToOpenAIFormat } from "../../open-sse/translator/formats/openai.js";
import { parseSSELine } from "../../open-sse/utils/streamHelpers.js";
describe("request normalization", () => {

View File

@@ -0,0 +1,69 @@
// A3: locks toOpenAIUsage per-provider token math (claude/gemini/kiro/ollama/commandcode).
import { describe, it, expect } from "vitest";
import { toOpenAIUsage } from "../../open-sse/translator/concerns/usage.js";
describe("toOpenAIUsage", () => {
it("claude: folds cache read+create into prompt, exposes details", () => {
const u = toOpenAIUsage(
{ input_tokens: 100, output_tokens: 20, cache_read_input_tokens: 30, cache_creation_input_tokens: 10 },
"claude"
);
expect(u.prompt_tokens).toBe(140);
expect(u.completion_tokens).toBe(20);
expect(u.total_tokens).toBe(160);
expect(u.prompt_tokens_details.cached_tokens).toBe(30);
expect(u.prompt_tokens_details.cache_creation_tokens).toBe(10);
});
it("claude: no cache -> no prompt_tokens_details", () => {
const u = toOpenAIUsage({ input_tokens: 50, output_tokens: 5 }, "claude");
expect(u.prompt_tokens).toBe(50);
expect(u.prompt_tokens_details).toBeUndefined();
});
it("gemini: full fields, completion = candidates + thoughts", () => {
const u = toOpenAIUsage(
{ promptTokenCount: 100, candidatesTokenCount: 40, thoughtsTokenCount: 10, totalTokenCount: 150 },
"gemini"
);
expect(u.prompt_tokens).toBe(100);
expect(u.completion_tokens).toBe(50);
expect(u.total_tokens).toBe(150);
expect(u.completion_tokens_details.reasoning_tokens).toBe(10);
});
it("gemini fallback: candidates=0 -> derive from total - prompt - thoughts", () => {
const u = toOpenAIUsage(
{ promptTokenCount: 100, candidatesTokenCount: 0, thoughtsTokenCount: 10, totalTokenCount: 150 },
"gemini"
);
// candidates derived = 150 - 100 - 10 = 40 ; completion = 40 + 10
expect(u.completion_tokens).toBe(50);
});
it("kiro: input/output straight", () => {
const u = toOpenAIUsage({ inputTokens: 12, outputTokens: 3 }, "kiro");
expect(u.prompt_tokens).toBe(12);
expect(u.completion_tokens).toBe(3);
expect(u.total_tokens).toBe(15);
});
it("ollama: prompt_eval_count/eval_count", () => {
const u = toOpenAIUsage({ prompt_eval_count: 7, eval_count: 4 }, "ollama");
expect(u.prompt_tokens).toBe(7);
expect(u.completion_tokens).toBe(4);
expect(u.total_tokens).toBe(11);
});
it("commandcode: keeps totalTokens fallback", () => {
const u = toOpenAIUsage({ inputTokens: 8, outputTokens: 2, totalTokens: 99 }, "commandcode");
expect(u.prompt_tokens).toBe(8);
expect(u.completion_tokens).toBe(2);
expect(u.total_tokens).toBe(99);
});
it("unknown kind / null raw -> null", () => {
expect(toOpenAIUsage({}, "nope")).toBeNull();
expect(toOpenAIUsage(null, "claude")).toBeNull();
});
});

View File

@@ -0,0 +1,39 @@
// Guards the refactored USAGE_HANDLERS dispatch: unsupported → message, supported → routed.
import { describe, it, expect, vi, beforeEach } from "vitest";
// Stub network so handlers don't hit real APIs; each call resolves an empty 200.
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: vi.fn(async () => ({
ok: true,
status: 200,
json: async () => ({}),
text: async () => "{}",
})),
}));
const load = () => import("../../open-sse/services/usage.js");
const SUPPORTED = [
"github", "gemini-cli", "antigravity", "claude", "codex", "kiro",
"qoder", "qwen", "iflow", "ollama", "glm", "glm-cn",
"minimax", "minimax-cn", "vercel-ai-gateway",
];
describe("usage dispatch", () => {
beforeEach(() => vi.clearAllMocks());
it("unsupported provider → not-implemented message", async () => {
const { getUsageForProvider } = await load();
const res = await getUsageForProvider({ provider: "totally-unknown" });
expect(res).toEqual({ message: "Usage API not implemented for totally-unknown" });
});
it("every supported provider routes to its handler (no fallback message)", async () => {
const { getUsageForProvider } = await load();
for (const provider of SUPPORTED) {
const res = await getUsageForProvider({ provider, accessToken: "t", apiKey: "k" });
// Routed handler must return an object and never the unsupported fallback
expect(res, `${provider} routed`).toBeTypeOf("object");
expect(res?.message).not.toBe(`Usage API not implemented for ${provider}`);
}
});
});