Merge remote-tracking branch 'origin/master' into gitea/new_feature

# Conflicts:
#	open-sse/executors/qoder.js
#	open-sse/handlers/chatCore.js
#	open-sse/handlers/chatCore/sseToJsonHandler.js
#	open-sse/providers/registry/commandcode.js
#	src/app/(dashboard)/dashboard/combos/page.js
#	src/app/api/v1/models/route.js
#	src/lib/db/repos/usageRepo.js
#	src/shared/components/UsageStats.js
This commit is contained in:
2026-09-25 10:25:56 +07:00
154 changed files with 10246 additions and 1241 deletions

View File

@@ -82,17 +82,97 @@ describe("Antigravity dashboard normalization with weekly quotas", () => {
expect(weeklyRows[1].name).toMatch(/Weekly/);
});
it("order: gemini family, claude family, weekly, then other", () => {
const quotas = parseQuotaData("antigravity", data);
it("order: session, weekly, then other models", () => {
const dataWithBoth = {
quotas: {
gemini_session: {
displayName: "Gemini (5h)",
used: 100,
total: 1000,
resetAt: "2026-09-08T05:00:00Z",
remainingPercentage: 90,
},
gemini_weekly: {
displayName: "Gemini (Weekly)",
used: 250,
total: 1000,
resetAt: "2026-09-15T00:00:00Z",
remainingPercentage: 75,
},
claude_gpt_session: {
displayName: "Claude & GPT (5h)",
used: 50,
total: 1000,
resetAt: "2026-09-08T05:00:00Z",
remainingPercentage: 95,
},
claude_gpt_weekly: {
displayName: "Claude & GPT (Weekly)",
used: 500,
total: 1000,
resetAt: "2026-09-14T00:00:00Z",
remainingPercentage: 50,
},
},
};
const quotas = parseQuotaData("antigravity", dataWithBoth);
const keys = quotas.map((q) => q.modelKey);
const geminiIdx = keys.indexOf("gemini");
const claudeIdx = keys.indexOf("claude");
const geminiSessionIdx = keys.indexOf("gemini_session");
const geminiWeeklyIdx = keys.indexOf("gemini_weekly");
const claudeSessionIdx = keys.indexOf("claude_gpt_session");
const claudeWeeklyIdx = keys.indexOf("claude_gpt_weekly");
expect(geminiIdx).toBeLessThan(geminiWeeklyIdx);
expect(claudeIdx).toBeLessThan(claudeWeeklyIdx);
expect(geminiSessionIdx).toBeLessThan(geminiWeeklyIdx);
expect(claudeSessionIdx).toBeLessThan(claudeWeeklyIdx);
});
it("excludes redundant duplicates when individual models mirror weekly reset and summary is present", () => {
// Exact scenario from Christian's account:
const liveLikeData = {
quotas: {
"gemini-3.8-flash-high": { used: 1000, total: 1000, remainingPercentage: 0, resetAt: "2026-09-23T06:00:17Z", displayName: "Gemini 3.8 Flash (High)" },
"claude-sonnet-4-6": { used: 1000, total: 1000, remainingPercentage: 0, resetAt: "2026-09-20T19:00:21Z", displayName: "Claude 3.7 Sonnet" },
"gpt-oss-120b-medium": { used: 1000, total: 1000, remainingPercentage: 0, resetAt: "2026-09-20T19:00:21Z", displayName: "GPT-OSS 120B (Medium)" },
"gemini-3.1-flash-image": { used: 1000, total: 1000, remainingPercentage: 0, resetAt: "2026-09-23T06:00:17Z", displayName: "Gemini 3.1 Flash Image" },
gemini_weekly: {
displayName: "Gemini (Weekly)",
used: 1000,
total: 1000,
resetAt: "2026-09-23T06:00:17Z",
remainingPercentage: 0,
},
claude_gpt_weekly: {
displayName: "Claude & GPT (Weekly)",
used: 807,
total: 1000,
resetAt: "2026-09-24T18:09:46Z",
remainingPercentage: 19.3,
},
claude_gpt_session: {
displayName: "Claude & GPT (5h)",
used: 1000,
total: 1000,
resetAt: "2026-09-20T19:00:21Z",
remainingPercentage: 0,
},
},
};
const quotas = parseQuotaData("antigravity", liveLikeData);
const names = quotas.map((q) => q.name);
// Should contain unique usages:
expect(names).toContain("Claude & GPT (5h)");
expect(names).toContain("Claude & GPT (Weekly)");
expect(names).toContain("Gemini (Weekly)");
expect(names).toContain("Gemini 3.1 Flash Image");
// Should NOT contain redundant duplicate entries:
expect(names).not.toContain("Gemini (Flash / Pro)"); // duplicate of Gemini (Weekly)
expect(names).not.toContain("Claude (Sonnet / Opus)"); // duplicate of Claude & GPT (5h)
expect(names).not.toContain("GPT-OSS 120B (Medium)"); // covered by Claude & GPT family
expect(quotas).toHaveLength(4);
});
it("works with no weekly keys present (backward compat)", () => {

View File

@@ -53,7 +53,7 @@ const NESTED_RESPONSE = {
// — parseWeeklyQuotaSummary ———————————————————————————————
describe("parseWeeklyQuotaSummary", () => {
it("extracts Gemini weekly quota from top-level groups", () => {
it("extracts Gemini weekly and session quotas from top-level groups", () => {
const result = parseWeeklyQuotaSummary(FULL_RESPONSE);
expect(result.gemini_weekly).toMatchObject({
used: 250,
@@ -63,6 +63,14 @@ describe("parseWeeklyQuotaSummary", () => {
unlimited: false,
});
expect(result.gemini_weekly.resetAt).toBe("2026-09-15T00:00:00.000Z");
expect(result.gemini_session).toMatchObject({
used: 100,
total: 1000,
remainingPercentage: 90,
displayName: "Gemini (5h)",
unlimited: false,
});
expect(result.gemini_session.resetAt).toBe("2026-09-09T00:00:00.000Z");
});
it("extracts Claude & GPT weekly quota", () => {
@@ -85,14 +93,14 @@ describe("parseWeeklyQuotaSummary", () => {
expect(result.claude_gpt_weekly.remainingPercentage).toBe(50);
});
it("skips non-weekly buckets", () => {
it("skips unrecognized non-weekly non-session buckets", () => {
const data = {
groups: [{
displayName: "Gemini Models",
buckets: [
{
bucketId: "gemini-daily-bucket",
displayName: "Daily Limit",
bucketId: "gemini-monthly-bucket",
displayName: "Monthly Limit",
remainingFraction: 0.9,
resetTime: "2026-09-09T00:00:00Z",
},
@@ -404,7 +412,7 @@ describe("weekly quota isolation from existing quota", () => {
});
});
it("reconciles weekly quota to 0% when all paid-tier family models are exhausted", async () => {
it("reconciles 5h session quota to 0% when all paid-tier family models are exhausted without clobbering weekly quota", async () => {
proxyAwareFetch.mockImplementation(async (url) => {
if (url.includes(":loadCodeAssist")) {
return {
@@ -421,7 +429,7 @@ describe("weekly quota isolation from existing quota", () => {
models: {
"gemini-3.8-flash-high": {
displayName: "Gemini 3.8 Flash (High)",
// Exhausted model: no remainingFraction, future resetTime
// Exhausted model: no remainingFraction, future resetTime (5h window reset)
quotaInfo: { resetTime: "2026-09-13T12:00:00Z" },
},
},
@@ -435,30 +443,49 @@ describe("weekly quota isolation from existing quota", () => {
json: async () => ({
groups: [{
displayName: "Gemini Models",
buckets: [{
bucketId: "gemini-weekly",
displayName: "Weekly Limit Remaining",
remainingFraction: 1,
resetTime: "2026-09-15T00:00:00Z",
}],
buckets: [
{
bucketId: "gemini-weekly",
displayName: "Weekly Limit Remaining",
window: "weekly",
remainingFraction: 0.75,
resetTime: "2026-09-15T00:00:00Z",
},
{
bucketId: "gemini-5h",
displayName: "Five Hour Limit Remaining",
window: "5h",
remainingFraction: 1,
resetTime: "2026-09-13T11:00:00Z",
},
],
}],
}),
};
}
return { ok: false, status: 404 };
return { ok: true, status: 200, json: async () => ({}) };
});
const { getAntigravityUsage } = await import("../../open-sse/services/usage/google.js");
const result = await getAntigravityUsage("token", {});
const result = await getAntigravityUsage("token-exhausted", null);
// Per-model quota should show exhausted
expect(result.quotas["gemini-3.8-flash-high"].remainingPercentage).toBe(0);
// Weekly quota should be reconciled to 0% with the family reset time
expect(result.quotas.gemini_weekly).toMatchObject({
// 5h session quota should be reconciled to 0% with the family reset time
expect(result.quotas.gemini_session).toMatchObject({
used: 1000,
total: 1000,
remainingPercentage: 0,
resetAt: "2026-09-13T12:00:00.000Z",
});
// Weekly quota should remain intact and NOT be clobbered to 0% or steal the 5h resetAt
expect(result.quotas.gemini_weekly).toMatchObject({
used: 250,
total: 1000,
remainingPercentage: 75,
resetAt: "2026-09-15T00:00:00.000Z",
});
});
});

View File

@@ -114,3 +114,139 @@ describe("getCapabilitiesForModel", () => {
});
});
});
describe("getCapabilitiesForModel — MiMo (<think>-tag reasoning, always-on)", () => {
it("mimo-v2.5 has vision + reasoning + deepseek format, cannot disable", () => {
const caps = getCapabilitiesForModel(null, "mimo-v2.5");
expect(caps.vision).toBe(true);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingFormat).toBe("deepseek");
expect(caps.thinkingCanDisable).toBe(false);
});
it("mimo-v2.5-pro has vision (matches *mimo*v2.5* pattern)", () => {
const caps = getCapabilitiesForModel(null, "mimo-v2.5-pro");
expect(caps.vision).toBe(true);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingFormat).toBe("deepseek");
expect(caps.thinkingCanDisable).toBe(false);
});
it("xiaomi/mimo-v2.5-pro (vendor-prefixed) has vision", () => {
const caps = getCapabilitiesForModel(null, "xiaomi/mimo-v2.5-pro");
expect(caps.vision).toBe(true);
expect(caps.thinkingFormat).toBe("deepseek");
});
it("mimo-omni-x has audioInput via the omni pattern", () => {
const caps = getCapabilitiesForModel(null, "mimo-omni-x");
expect(caps.vision).toBe(true);
expect(caps.audioInput).toBe(true);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingCanDisable).toBe(false);
});
it("generic mimo has vision + reasoning (fallback pattern)", () => {
const caps = getCapabilitiesForModel(null, "mimo");
expect(caps.vision).toBe(true);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingCanDisable).toBe(false);
});
});
describe("getCapabilitiesForModel — Qwen max/plus vision", () => {
it("qwen3.7-max has vision (*qwen*max* fires before *qwen3.7*)", () => {
const caps = getCapabilitiesForModel(null, "qwen3.7-max");
expect(caps.vision).toBe(true);
expect(caps.reasoning).toBe(true);
});
it("Qwen3.6-Max-Preview has vision (case-insensitive pattern match)", () => {
const caps = getCapabilitiesForModel(null, "Qwen3.6-Max-Preview");
expect(caps.vision).toBe(true);
});
it("qwen3.7-plus has vision", () => {
const caps = getCapabilitiesForModel(null, "qwen3.7-plus");
expect(caps.vision).toBe(true);
});
it("qwen3.7 has vision from the qwen3.7 pattern", () => {
const caps = getCapabilitiesForModel(null, "qwen3.7");
expect(caps.vision).toBe(true);
});
it("qwq has no vision (thinking-only model)", () => {
const caps = getCapabilitiesForModel(null, "qwq-32b");
expect(caps.vision).toBe(false);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingCanDisable).toBe(false);
});
});
describe("getCapabilitiesForModel — MiniMax M2.x vision", () => {
it("minimax-m2.7 has vision", () => {
const caps = getCapabilitiesForModel(null, "minimax-m2.7");
expect(caps.vision).toBe(true);
expect(caps.thinkingCanDisable).toBe(false);
});
it("minimax-m2.5 has vision", () => {
const caps = getCapabilitiesForModel(null, "minimax-m2.5");
expect(caps.vision).toBe(true);
expect(caps.thinkingCanDisable).toBe(false);
});
it("MiniMax-M2.7 has vision (vendor prefix MiniMaxAI/ stripped by route)", () => {
const caps = getCapabilitiesForModel(null, "MiniMaxAI/MiniMax-M2.7");
expect(caps.vision).toBe(true);
});
it("minimax-m3 has vision (separate pattern)", () => {
const caps = getCapabilitiesForModel(null, "minimax-m3");
expect(caps.vision).toBe(true);
});
});
describe("getCapabilitiesForModel — DeepSeek V4 text-only", () => {
it("deepseek-v4-pro has no vision", () => {
const caps = getCapabilitiesForModel(null, "deepseek-v4-pro");
expect(caps.vision).toBe(false);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingFormat).toBe("deepseek");
});
it("deepseek-v4-flash has no vision", () => {
const caps = getCapabilitiesForModel(null, "deepseek-v4-flash");
expect(caps.vision).toBe(false);
expect(caps.reasoning).toBe(true);
});
it("deepseek/deepseek-v4-pro (vendor-prefixed) has no vision", () => {
const caps = getCapabilitiesForModel(null, "deepseek/deepseek-v4-pro");
expect(caps.vision).toBe(false);
});
});
describe("getCapabilitiesForModel — codebuddy-cn provider overrides", () => {
it("deepseek-v4-pro via codebuddy-cn uses openai thinking format", () => {
const caps = getCapabilitiesForModel("codebuddy-cn", "deepseek-v4-pro");
expect(caps.vision).toBe(true);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingFormat).toBe("openai");
expect(caps.thinkingCanDisable).toBe(true);
});
it("minimax-m3 via codebuddy-cn has vision (provider override)", () => {
const caps = getCapabilitiesForModel("codebuddy-cn", "minimax-m3");
expect(caps.vision).toBe(true);
expect(caps.thinkingFormat).toBe("openai");
expect(caps.thinkingCanDisable).toBe(false);
});
it("unknown provider falls through to pattern matching", () => {
const caps = getCapabilitiesForModel("unknown-provider", "mimo-v2.5");
expect(caps.vision).toBe(true);
expect(caps.thinkingFormat).toBe("deepseek");
});
});

View File

@@ -0,0 +1,76 @@
// A refusal from the Anthropic API (stop_reason "refusal", zero output tokens, no
// content blocks) must reach an OpenAI-format client as finish_reason
// "content_filter" carrying Anthropic's explanation — not as a clean, empty "stop".
// Captured live on 2026-09-20 against claude-opus-5 via a Claude Code OAuth
// connection: 9Router logged "Model succeeded · OUT 0" and the client saw nothing.
import { describe, it, expect } from "vitest";
import { claudeToOpenAIResponse } from "../../open-sse/translator/response/claude-to-openai.js";
const EXPLANATION =
"This request was blocked as it seems to violate Anthropic's Terms of Service restrictions on reverse engineering or duplicating model outputs.";
function runStream(events) {
const state = {};
const out = [];
for (const ev of events) {
const r = claudeToOpenAIResponse(ev, state);
if (Array.isArray(r)) out.push(...r);
else if (r) out.push(r);
}
return { state, out };
}
const refusalStream = [
{
type: "message_start",
message: {
id: "msg_refusal", model: "claude-opus-5", role: "assistant", content: [],
usage: { input_tokens: 637, cache_creation_input_tokens: 206779, cache_read_input_tokens: 0, output_tokens: 0 }
}
},
{
type: "message_delta",
delta: {
stop_reason: "refusal",
stop_sequence: null,
stop_details: { type: "refusal", category: "reasoning_extraction", explanation: EXPLANATION }
},
usage: { input_tokens: 637, cache_creation_input_tokens: 206779, cache_read_input_tokens: 0, output_tokens: 0 }
},
{ type: "message_stop" }
];
describe("claude-to-openai: refusal stop_reason", () => {
it("finishes with content_filter, not stop", () => {
const { out } = runStream(refusalStream);
const finishes = out.map(c => c.choices?.[0]?.finish_reason).filter(Boolean);
expect(finishes).toEqual(["content_filter"]);
});
it("surfaces Anthropic's explanation as message content", () => {
const { out } = runStream(refusalStream);
const text = out.map(c => c.choices?.[0]?.delta?.content || "").join("");
expect(text).toBe(EXPLANATION);
});
it("keeps usage on the final chunk (prompt tokens were billed)", () => {
const { out } = runStream(refusalStream);
const final = out.find(c => c.choices?.[0]?.finish_reason === "content_filter");
expect(final.usage.prompt_tokens).toBe(637 + 206779);
expect(final.usage.completion_tokens).toBe(0);
});
it("leaves a normal end_turn untouched", () => {
const { out } = runStream([
{ type: "message_start", message: { id: "m", model: "claude-opus-5", role: "assistant", content: [], usage: { input_tokens: 5, output_tokens: 0 } } },
{ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } },
{ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "ok" } },
{ type: "content_block_stop", index: 0 },
{ type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null, stop_details: null }, usage: { output_tokens: 1 } },
{ type: "message_stop" }
]);
const finishes = out.map(c => c.choices?.[0]?.finish_reason).filter(Boolean);
expect(finishes).toEqual(["stop"]);
expect(out.map(c => c.choices?.[0]?.delta?.content || "").join("")).toBe("ok");
});
});

View File

@@ -0,0 +1,155 @@
import { describe, expect, it } from "vitest";
import { aggregateComboCapabilities } from "../../open-sse/providers/capabilities.js";
describe("aggregateComboCapabilities — null / empty", () => {
it("returns null for null", () => {
expect(aggregateComboCapabilities(null)).toBeNull();
});
it("returns null for empty array", () => {
expect(aggregateComboCapabilities([])).toBeNull();
});
});
describe("aggregateComboCapabilities — single model passthrough", () => {
it("single model returns its own capabilities", () => {
const caps = aggregateComboCapabilities(["opencode-go/mimo-v2.5"]);
expect(caps.vision).toBe(true);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingFormat).toBe("deepseek");
expect(caps.thinkingCanDisable).toBe(false);
expect(caps.contextWindow).toBe(1048576);
expect(caps.maxOutput).toBe(131072);
});
});
describe("aggregateComboCapabilities — union fields (vision, audioInput, search)", () => {
it("vision is true if any backend has it", () => {
// deepseek-v4-pro: no vision; mimo-v2.5: vision
const caps = aggregateComboCapabilities([
"opencode-go/deepseek-v4-pro",
"opencode-go/mimo-v2.5",
]);
expect(caps.vision).toBe(true);
});
it("vision is false if no backend has it", () => {
const caps = aggregateComboCapabilities([
"opencode-go/deepseek-v4-pro",
"opencode-go/deepseek-v4-flash",
]);
expect(caps.vision).toBe(false);
});
it("audioInput is true if any backend has it", () => {
// mimo-omni has audioInput; mimo-v2.5 does not
const caps = aggregateComboCapabilities([
"opencode-go/mimo-v2.5",
"opencode-go/mimo-omni-test",
]);
expect(caps.audioInput).toBe(true);
});
it("search is true if any backend has it", () => {
// gpt-5: search; mimo-v2.5: no search
const caps = aggregateComboCapabilities([
"openai/gpt-5",
"opencode-go/mimo-v2.5",
]);
expect(caps.search).toBe(true);
});
});
describe("aggregateComboCapabilities — intersection: tools", () => {
it("tools is false if any backend lacks it", () => {
// gpt-image-1: tools:false; gpt-5: tools:true
const caps = aggregateComboCapabilities([
"openai/gpt-5",
"openai/gpt-image-1",
]);
expect(caps.tools).toBe(false);
});
it("tools is true when all backends support it", () => {
const caps = aggregateComboCapabilities([
"opencode-go/mimo-v2.5",
"opencode-go/kimi-k2.5",
]);
expect(caps.tools).toBe(true);
});
});
describe("aggregateComboCapabilities — primary model drives reasoning fields", () => {
it("thinkingFormat comes from the first model", () => {
// primary: mimo-v2.5 (deepseek); secondary: kimi-k2.5 (kimi)
const caps = aggregateComboCapabilities([
"opencode-go/mimo-v2.5",
"opencode-go/kimi-k2.5",
]);
expect(caps.thinkingFormat).toBe("deepseek");
expect(caps.reasoning).toBe(true);
});
it("flipping order changes thinkingFormat to the new primary", () => {
const caps = aggregateComboCapabilities([
"opencode-go/kimi-k2.5",
"opencode-go/mimo-v2.5",
]);
expect(caps.thinkingFormat).toBe("kimi");
});
});
describe("aggregateComboCapabilities — context/output limits", () => {
it("contextWindow is the minimum across all models", () => {
// mimo-v2.5: 1048576; kimi-k2.5 (*kimi*k2* pattern): 262144
const caps = aggregateComboCapabilities([
"opencode-go/mimo-v2.5",
"opencode-go/kimi-k2.5",
]);
expect(caps.contextWindow).toBe(262144);
});
it("maxOutput is the maximum across all models", () => {
// mimo-v2.5: 131072; kimi-k2.5 (*kimi*k2* pattern): 262144
const caps = aggregateComboCapabilities([
"opencode-go/mimo-v2.5",
"opencode-go/kimi-k2.5",
]);
expect(caps.maxOutput).toBe(262144);
});
});
describe("aggregateComboCapabilities — nested combo resolution via comboLookup", () => {
it("resolves nested combo and unions vision from its members", () => {
const lookup = { "inner-combo": ["opencode-go/deepseek-v4-pro", "opencode-go/mimo-v2.5"] };
const caps = aggregateComboCapabilities(["inner-combo"], lookup);
expect(caps.reasoning).toBe(true);
expect(caps.vision).toBe(true); // mimo brings vision through the lookup
});
it("outer combo gets vision via nested combo containing mimo", () => {
const lookup = { "deepseek-v4-pro-fusion": ["opencode-go/deepseek-v4-pro", "opencode-go/mimo-v2.5"] };
const caps = aggregateComboCapabilities(["deepseek-v4-pro-fusion", "openai/gpt-5"], lookup);
expect(caps.vision).toBe(true);
expect(caps.reasoning).toBe(true);
});
it("contextWindow is min across all resolved leaves", () => {
// deepseek-v4-pro (*deepseek-v4*): 1000000; mimo-v2.5: 1048576 → min = 1000000
const lookup = { "inner": ["opencode-go/deepseek-v4-pro"] };
const caps = aggregateComboCapabilities(["inner", "opencode-go/mimo-v2.5"], lookup);
expect(caps.contextWindow).toBe(1000000);
});
it("handles cycles without throwing", () => {
const lookup = { "a": ["b"], "b": ["a"] };
expect(() => aggregateComboCapabilities(["a"], lookup)).not.toThrow();
});
it("without comboLookup bare combo name falls through to pattern match", () => {
// *deepseek-v4* pattern: reasoning true, vision false
const caps = aggregateComboCapabilities(["deepseek-v4-pro-fusion"]);
expect(caps.reasoning).toBe(true);
expect(caps.vision).toBe(false);
});
});

View File

@@ -0,0 +1,102 @@
import { describe, it, expect } from "vitest";
import {
buildPresetItems,
buildCursorPresetItems,
buildClaudePresetItems,
isValidComboPresetName,
} from "../../src/lib/comboPresets.js";
describe("combo presets", () => {
it("rejects combo names with slashes or invalid chars", () => {
expect(isValidComboPresetName("composer-2.5")).toBe(true);
expect(isValidComboPresetName("claude-opus-5")).toBe(true);
expect(isValidComboPresetName("cu/composer-2.5")).toBe(false);
expect(isValidComboPresetName("bad name")).toBe(false);
expect(isValidComboPresetName("")).toBe(false);
});
it("Cursor live ids become unprefixed names seeded with cu/…", () => {
const items = buildCursorPresetItems({
liveModels: [
{ id: "composer-2.5", name: "Composer 2.5" },
{ id: "cursor-grok-4.6-high-fast", name: "Grok" },
{ id: "bad/with-slash", name: "Invalid" },
],
});
expect(items).toEqual([
{ name: "composer-2.5", models: ["cu/composer-2.5"] },
{ name: "cursor-grok-4.6-high-fast", models: ["cu/cursor-grok-4.6-high-fast"] },
]);
});
it("Cursor falls back to static cu registry when live catalog is empty", () => {
const items = buildCursorPresetItems({ liveModels: [] });
expect(items.length).toBeGreaterThan(0);
expect(items.every((i) => i.models[0].startsWith("cu/"))).toBe(true);
expect(items.some((i) => i.name === "default")).toBe(true);
// No slash in combo name
expect(items.every((i) => !i.name.includes("/"))).toBe(true);
});
it("Claude aliases map opus → cc/claude-opus-5 and registry models seed cc/…", () => {
const items = buildClaudePresetItems();
const byName = Object.fromEntries(items.map((i) => [i.name, i]));
expect(byName["claude-opus-5"]).toEqual({
name: "claude-opus-5",
models: ["cc/claude-opus-5"],
});
expect(byName.opus).toEqual({
name: "opus",
models: ["cc/claude-opus-5"],
});
expect(byName.sonnet).toEqual({
name: "sonnet",
models: ["cc/claude-sonnet-5"],
});
expect(byName.haiku).toEqual({
name: "haiku",
models: ["cc/claude-haiku-4-5-20251001"],
});
expect(byName.fable).toEqual({
name: "fable",
models: ["cc/claude-fable-5"],
});
expect(byName.default).toEqual({
name: "default",
models: ["cc/claude-sonnet-5"],
});
expect(byName.opusplan).toEqual({
name: "opusplan",
models: ["cc/claude-opus-5"],
});
});
it("marks existing names with exists: true", () => {
const items = buildPresetItems("cursor", {
liveModels: [
{ id: "composer-2.5" },
{ id: "gpt-5.3-codex" },
],
existingNames: ["composer-2.5"],
});
expect(items).toEqual([
{ name: "composer-2.5", models: ["cu/composer-2.5"], exists: true },
{ name: "gpt-5.3-codex", models: ["cu/gpt-5.3-codex"], exists: false },
]);
});
it("returns empty for unknown source", () => {
expect(buildPresetItems("unknown")).toEqual([]);
});
it("drops invalid names from Claude/Cursor catalogs", () => {
const cursor = buildPresetItems("cursor", {
liveModels: [{ id: "ok-model" }, { id: "no/slash" }, { id: "has space" }],
existingNames: [],
});
expect(cursor.map((i) => i.name)).toEqual(["ok-model"]);
});
});

View File

@@ -18,6 +18,18 @@ function textFrame(text) {
return Buffer.from(wrapConnectRPCFrame(encodeField(1, LEN, update)));
}
// InteractionUpdate.thinking_delta (field 4) + turn_ended (field 14).
function thinkingFrame(text) {
const thinkingPart = Buffer.from(encodeField(1, LEN, text));
const update = Buffer.from(encodeField(4, LEN, thinkingPart));
return Buffer.from(wrapConnectRPCFrame(encodeField(1, LEN, update)));
}
function turnEndedFrame() {
const update = Buffer.from(encodeField(14, LEN, new Uint8Array()));
return Buffer.from(wrapConnectRPCFrame(encodeField(1, LEN, update)));
}
function stubAgentSession(executor, frames) {
const written = [];
const queue = [...frames];
@@ -48,12 +60,12 @@ function parseSSE(text) {
.map((data) => JSON.parse(data));
}
async function runAgent({ frames, stream }) {
async function runAgent({ frames, stream, model = "gpt-5.2", tools }) {
const executor = new CursorExecutor();
const written = stubAgentSession(executor, frames);
const result = await executor.executeAgent({
model: "gpt-5.2",
body: { messages: [{ role: "user", content: "hi" }] },
model,
body: { messages: [{ role: "user", content: "hi" }], ...(tools ? { tools } : {}) },
stream,
credentials,
});
@@ -73,32 +85,45 @@ describe("CursorExecutor AgentService exec_request handling", () => {
expect(content).toBe("hello");
});
it("does not echo client tools on the request_context ack", async () => {
const { written, result } = await runAgent({
tools: [{ function: { name: "read_file", parameters: { type: "object" } } }],
frames: [execRequestFrame(10), textFrame("hello")],
stream: true,
});
expect(written.length).toBe(2);
expect(written[1].toString("utf8")).not.toContain("read_file");
const content = parseSSE(await result.response.text())
.map((e) => e.choices?.[0]?.delta?.content || "")
.join("");
expect(content).toBe("hello");
});
it("does not render an unsupported exec request as assistant content", async () => {
const { result } = await runAgent({
frames: [textFrame("partial answer"), execRequestFrame(2)],
const { result, written } = await runAgent({
frames: [textFrame("partial answer"), execRequestFrame(2), textFrame(" more")],
stream: true,
});
const body = await result.response.text();
expect(body).not.toContain("unsupported IDE tool\\n");
expect(body).not.toContain("unsupported IDE tool");
const events = parseSSE(body);
const content = events.map((e) => e.choices?.[0]?.delta?.content || "").join("");
expect(content).toBe("partial answer");
const errorEvent = events.find((e) => e.error);
expect(errorEvent?.error?.message).toContain("unsupported IDE tool");
expect(events.some((e) => e.choices?.[0]?.finish_reason === "stop")).toBe(false);
expect(content).toBe("partial answer more");
expect(events.some((e) => e.error)).toBe(false);
expect(written.length).toBe(2); // run frame + IDE rejection
});
it("drops frames batched behind an unsupported exec request in the same read", async () => {
it("still emits later text after rejecting an IDE exec in the same read", async () => {
const { result } = await runAgent({
frames: [Buffer.concat([execRequestFrame(2), textFrame("late")])],
stream: true,
});
const body = await result.response.text();
expect(body).toContain("unsupported IDE tool");
expect(body).not.toContain("late");
expect(body).not.toContain("unsupported IDE tool");
expect(body).toContain("late");
});
it("returns a non-200 error body for an unsupported exec request when not streaming", async () => {
@@ -111,4 +136,32 @@ describe("CursorExecutor AgentService exec_request handling", () => {
const payload = await result.response.json();
expect(payload.error.message).toContain("unsupported IDE tool");
});
it("streams Composer visible content from thinking_delta after </think>", async () => {
const { result } = await runAgent({
model: "composer-2.5",
frames: [
thinkingFrame("private reasoning that must not leak</think>OK"),
turnEndedFrame(),
],
stream: true,
});
const events = parseSSE(await result.response.text());
const content = events.map((e) => e.choices?.[0]?.delta?.content || "").join("");
expect(content).toBe("OK");
expect(JSON.stringify(events)).not.toContain("private reasoning");
});
it("flushes Grok thinking as visible content when the turn has no text_delta", async () => {
const { result } = await runAgent({
model: "grok-4.5",
frames: [thinkingFrame("hello from grok"), turnEndedFrame()],
stream: true,
});
const events = parseSSE(await result.response.text());
const content = events.map((e) => e.choices?.[0]?.delta?.content || "").join("");
expect(content).toBe("hello from grok");
});
});

View File

@@ -246,6 +246,15 @@ describe("Cursor AgentService executor helpers (cursor.js)", () => {
const run = decodeMessage(clientMsg.get(1)[0].value);
expect(run.has(2)).toBe(true); // action
expect(run.has(9)).toBe(true); // requested_model
// custom_system_prompt (field 8) makes AgentService return an empty turn.
expect(run.has(8)).toBe(false);
expect(run.has(3)).toBe(true); // ModelDetails — required for thinking variants
const action = decodeMessage(run.get(2)[0].value);
const userAction = decodeMessage(action.get(1)[0].value);
const userMessage = decodeMessage(userAction.get(1)[0].value);
const userText = Buffer.from(userMessage.get(1)[0].value).toString("utf8");
expect(userText).toContain("be brief");
expect(userText).toContain("hi");
});
it("encodes mcp_tools (field 4) when tools are provided", () => {

View File

@@ -40,6 +40,9 @@ describe("toOpenAIFinish - claude", () => {
["end_turn", "stop"],
["max_tokens", "length"],
["tool_use", "tool_calls"],
["stop_sequence", "stop"],
["refusal", "content_filter"],
["unknown_xyz", "stop"],
])("%s -> %s", (input, expected) => {
expect(toOpenAIFinish(input, "claude")).toBe(expected);
});
@@ -58,6 +61,9 @@ describe("fromOpenAIFinish round-trip - claude", () => {
it("tool_calls -> tool_use", () => {
expect(fromOpenAIFinish("tool_calls", "claude")).toBe("tool_use");
});
it("content_filter -> refusal", () => {
expect(fromOpenAIFinish("content_filter", "claude")).toBe("refusal");
});
it("length -> max_tokens", () => {
expect(fromOpenAIFinish("length", "claude")).toBe("max_tokens");
});

View File

@@ -0,0 +1,144 @@
/**
* HuggingFace image generation — end-to-end through the real core handler.
*
* The registry/adapter tests pin the URL and payload in isolation. These tests
* drive `handleImageGenerationCore` — the same function the `/v1/images/generations`
* route calls — so the whole seam is exercised: adapter selection, buildUrl /
* buildBody / buildHeaders, the fetch call, and the binary response parse.
*
* The mocked `fetch` asserts on the exact request the router would receive, which
* is the strongest check available without burning live Inference Providers credits
* (the router bills before validating the payload, so a live probe can only prove
* the path exists, never that the body is right).
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { handleImageGenerationCore } from "../../open-sse/handlers/imageGenerationCore.js";
const originalFetch = global.fetch;
const CREDS = { apiKey: "hf_test_token" };
// A 1x1 transparent PNG — enough to prove the bytes survive the round trip.
const PNG_1X1 = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
"base64"
);
function mockBinaryResponse() {
return {
ok: true,
status: 200,
arrayBuffer: async () => PNG_1X1.buffer.slice(PNG_1X1.byteOffset, PNG_1X1.byteOffset + PNG_1X1.byteLength),
};
}
async function generate(body, model) {
return handleImageGenerationCore({
body,
modelInfo: { provider: "huggingface", model },
credentials: CREDS,
log: null,
});
}
describe("HuggingFace image generation — end to end", () => {
beforeEach(() => {
global.fetch = vi.fn().mockResolvedValue(mockBinaryResponse());
});
afterEach(() => {
global.fetch = originalFetch;
});
it("posts a text-to-image request to the fal-ai router path", async () => {
const result = await generate({ prompt: "a lighthouse at dusk" }, "black-forest-labs/FLUX.1-schnell");
expect(result.success).toBe(true);
const [url, init] = global.fetch.mock.calls[0];
expect(url).toBe("https://router.huggingface.co/fal-ai/fal-ai/flux/schnell");
expect(init.method).toBe("POST");
expect(JSON.parse(init.body)).toEqual({ inputs: "a lighthouse at dusk" });
});
it("authenticates with the connection's API key", async () => {
await generate({ prompt: "x" }, "black-forest-labs/FLUX.1-schnell");
const [, init] = global.fetch.mock.calls[0];
expect(init.headers.Authorization).toBe("Bearer hf_test_token");
});
it("never touches the dead api-inference host", async () => {
await generate({ prompt: "x" }, "black-forest-labs/FLUX.1-schnell");
expect(global.fetch.mock.calls[0][0]).not.toContain("api-inference.huggingface.co");
});
it("posts an image-to-image request with the source image in inputs", async () => {
const result = await generate(
{ prompt: "make it snow", image: "data:image/png;base64,AAAB" },
"Qwen/Qwen-Image-Edit"
);
expect(result.success).toBe(true);
const [url, init] = global.fetch.mock.calls[0];
expect(url).toBe("https://router.huggingface.co/fal-ai/fal-ai/qwen-image-edit");
// The router takes raw base64 in inputs and the prompt under parameters —
// the data-URL prefix must be stripped, not forwarded.
expect(JSON.parse(init.body)).toEqual({
inputs: "AAAB",
parameters: { prompt: "make it snow" },
});
});
it("rejects an image-to-image model that was given no source image", async () => {
const result = await generate({ prompt: "make it snow" }, "Qwen/Qwen-Image-Edit");
expect(result.success).toBe(false);
expect(result.status).toBe(400);
expect(result.error).toMatch(/requires a source image/i);
expect(global.fetch).not.toHaveBeenCalled();
});
it("rejects a model with no router mapping before calling upstream", async () => {
const result = await generate({ prompt: "x" }, "some-org/unmapped-model");
expect(result.success).toBe(false);
expect(result.status).toBe(400);
expect(result.error).toMatch(/no HuggingFace router mapping/i);
expect(global.fetch).not.toHaveBeenCalled();
});
it("returns the generated image as base64 to the client", async () => {
const result = await generate({ prompt: "x" }, "black-forest-labs/FLUX.1-schnell");
const payload = await result.response.json();
expect(payload.data[0].b64_json).toBe(PNG_1X1.toString("base64"));
});
it("routes a self-hosted connection to its own endpoint", async () => {
await handleImageGenerationCore({
body: { prompt: "x" },
modelInfo: { provider: "huggingface", model: "my-org/my-tgi-model" },
credentials: { apiKey: "k", providerSpecificData: { baseUrl: "https://tgi.internal" } },
log: null,
});
expect(global.fetch.mock.calls[0][0]).toBe("https://tgi.internal/my-org/my-tgi-model");
});
it("surfaces an upstream error instead of a broken image", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 402,
text: async () => JSON.stringify({ error: "You have depleted your monthly included credits." }),
json: async () => ({ error: "You have depleted your monthly included credits." }),
});
const result = await generate({ prompt: "x" }, "black-forest-labs/FLUX.1-schnell");
expect(result.success).toBe(false);
expect(result.status).toBe(402);
});
});

View File

@@ -0,0 +1,349 @@
/**
* HuggingFace registry migration to router.huggingface.co
*
* The legacy base URL `https://api-inference.huggingface.co` no longer resolves
* (DNS ENOTFOUND), so every HuggingFace image/STT request failed at the fetch
* layer. The replacement is `https://router.huggingface.co`, which routes by
* `<provider>/<providerResolvedModelId>` — the provider-resolved id is NOT the
* Hub model id and must be resolved from the Hub API's inferenceProviderMapping.
*
* Covers:
* - imageConfig base URL is the live router host, not the dead legacy host
* - image URL builder emits the provider-resolved id, not the Hub id
* - image URL builder throws a descriptive error for unmapped models
* - sttConfig exists and points at the live router host
* - every registered public model resolves through a provider the router serves
*/
import { describe, it, expect } from "vitest";
import huggingface from "../../open-sse/providers/registry/huggingface.js";
import imageAdapter from "../../open-sse/handlers/imageProviders/huggingface.js";
const DEAD_HOST = "api-inference.huggingface.co";
const LIVE_ROUTER = "router.huggingface.co";
// Providers the router actually forwards to. replicate/wavespeed/deepinfra appear
// in the Hub's inferenceProviderMapping but reject every router request with
// "Model not supported by provider <name>", so they must not be used here.
const ROUTABLE_PROVIDERS = new Set(["fal-ai", "hf-inference", "nscale", "together", "novita", "hyperbolic"]);
const imageConfig = huggingface.imageConfig;
const modelMap = imageConfig.modelMap || {};
// modelMap values are either a bare path (text-to-image) or
// { path, task: "image-to-image" } for models that require a source image.
const mappingPath = (value) => (typeof value === "string" ? value : value.path);
const mappingTask = (value) => (typeof value === "string" ? "text-to-image" : value.task || "text-to-image");
describe("HuggingFace registry — legacy host removal", () => {
it("does not use the dead api-inference host for images", () => {
expect(imageConfig.baseUrl).not.toContain(DEAD_HOST);
});
it("points imageConfig at the live router host", () => {
expect(imageConfig.baseUrl).toContain(LIVE_ROUTER);
});
it("does not use the dead api-inference host for STT", () => {
expect(huggingface.sttConfig?.baseUrl).not.toContain(DEAD_HOST);
});
});
describe("HuggingFace STT dispatch", () => {
it("declares an sttConfig so sttCore can dispatch", () => {
expect(huggingface.sttConfig).toBeDefined();
});
it("uses the HuggingFace ASR wire format", () => {
expect(huggingface.sttConfig.format).toBe("huggingface-asr");
});
it("authenticates with a bearer API key", () => {
expect(huggingface.sttConfig.authType).toBe("apikey");
expect(huggingface.sttConfig.authHeader).toBe("bearer");
});
it("advertises stt in serviceKinds", () => {
expect(huggingface.serviceKinds).toContain("stt");
});
it("points sttConfig at the hf-inference model route", () => {
expect(huggingface.sttConfig.baseUrl).toBe("https://router.huggingface.co/hf-inference/models");
});
});
describe("HuggingFace image URL builder", () => {
it("routes FLUX.1-schnell through its fal-ai provider id", () => {
expect(imageAdapter.buildUrl("black-forest-labs/FLUX.1-schnell")).toBe(
"https://router.huggingface.co/fal-ai/fal-ai/flux/schnell"
);
});
it("routes SDXL through its fal-ai provider id", () => {
expect(imageAdapter.buildUrl("stabilityai/stable-diffusion-xl-base-1.0")).toBe(
"https://router.huggingface.co/fal-ai/fal-ai/fast-sdxl"
);
});
it("never leaks the dead host into a built URL", () => {
expect(imageAdapter.buildUrl("black-forest-labs/FLUX.1-schnell")).not.toContain(DEAD_HOST);
});
it("throws a descriptive error for a model with no provider mapping", () => {
expect(() => imageAdapter.buildUrl("some-org/not-mapped-model")).toThrow(/no HuggingFace router mapping/i);
});
it("lets a connection override the endpoint for a self-hosted model", () => {
const creds = { providerSpecificData: { baseUrl: "https://tgi.internal/" } };
expect(imageAdapter.buildUrl("my-org/my-tgi-model", creds)).toBe("https://tgi.internal/my-org/my-tgi-model");
});
it("does not apply the router mapping when a custom endpoint is set", () => {
const creds = { providerSpecificData: { baseUrl: "https://tgi.internal" } };
// The custom endpoint knows its own model ids — the Hub id passes through verbatim.
expect(imageAdapter.buildUrl("black-forest-labs/FLUX.1-schnell", creds)).toBe(
"https://tgi.internal/black-forest-labs/FLUX.1-schnell"
);
});
it("ignores a blank custom endpoint", () => {
expect(imageAdapter.buildUrl("black-forest-labs/FLUX.1-schnell", { providerSpecificData: { baseUrl: " " } })).toBe(
"https://router.huggingface.co/fal-ai/fal-ai/flux/schnell"
);
});
it("rejects traversal or query injection in the model id on a custom endpoint", () => {
const creds = { providerSpecificData: { baseUrl: "https://tgi.internal" } };
for (const model of ["x/../../admin", "org//model", "model?x=1", "model#f"]) {
expect(() => imageAdapter.buildUrl(model, creds), model).toThrow(/invalid model ID/i);
}
});
});
describe("HuggingFace registry model table", () => {
const modelsById = Object.fromEntries(huggingface.models.map((m) => [m.id, m]));
const imageModels = huggingface.models.filter((m) => m.kind === "image");
const sttModels = huggingface.models.filter((m) => m.kind === "stt");
it("every image model is present in imageConfig.modelMap", () => {
for (const model of imageModels) {
expect(modelMap[model.id], `model ${model.id} is missing from imageConfig.modelMap`).toBeTruthy();
}
});
it("every modelMap entry points at a routable provider", () => {
for (const [hubId, value] of Object.entries(modelMap)) {
const provider = String(mappingPath(value)).split("/")[0];
expect(ROUTABLE_PROVIDERS.has(provider), `${hubId} -> unsupported provider ${provider}`).toBe(true);
}
});
it("every modelMap entry has a provider/model path shape", () => {
for (const [hubId, value] of Object.entries(modelMap)) {
expect(String(mappingPath(value)), `${hubId} has a malformed target`).toMatch(/^[a-z0-9-]+\/[A-Za-z0-9._/-]+$/);
}
});
it("does not advertise whisper-small, which has no live provider", () => {
expect(modelsById["openai/whisper-small"]).toBeUndefined();
});
it("advertises whisper-large-v3-turbo as its STT replacement", () => {
expect(modelsById["openai/whisper-large-v3-turbo"]?.kind).toBe("stt");
});
it("exposes the FLUX family image models", () => {
for (const id of [
"black-forest-labs/FLUX.1-schnell",
"black-forest-labs/FLUX.1-dev",
"black-forest-labs/FLUX.1-Krea-dev",
"black-forest-labs/FLUX.1-Kontext-dev",
"black-forest-labs/FLUX.2-dev",
"black-forest-labs/FLUX.2-klein-9B",
"black-forest-labs/FLUX.2-klein-4B",
"black-forest-labs/FLUX.2-klein-base-9B",
"black-forest-labs/FLUX.2-klein-base-4B",
]) {
expect(modelsById[id]?.kind, `${id} should be registered as an image model`).toBe("image");
}
});
it("exposes the Qwen-Image family", () => {
for (const id of [
"Qwen/Qwen-Image",
"Qwen/Qwen-Image-2512",
"Qwen/Qwen-Image-Edit",
"Qwen/Qwen-Image-Edit-2509",
"Qwen/Qwen-Image-Edit-2511",
]) {
expect(modelsById[id]?.kind, `${id} should be registered as an image model`).toBe("image");
}
});
it("exposes the Stable Diffusion family", () => {
for (const id of [
"stabilityai/stable-diffusion-xl-base-1.0",
"stabilityai/stable-diffusion-3.5-large",
"stabilityai/stable-diffusion-3.5-large-turbo",
]) {
expect(modelsById[id]?.kind, `${id} should be registered as an image model`).toBe("image");
}
});
it("exposes the HuggingFace ASR models", () => {
for (const id of ["openai/whisper-large-v3", "openai/whisper-large-v3-turbo"]) {
expect(modelsById[id]?.kind, `${id} should be registered as an stt model`).toBe("stt");
}
});
it("exposes the remaining third-party image models", () => {
for (const id of [
"tencent/HunyuanImage-3.0",
"Tongyi-MAI/Z-Image-Turbo",
"krea/Krea-2-Turbo",
"HiDream-ai/HiDream-I1-Fast",
"playgroundai/playground-v2.5-1024px-aesthetic",
"ideogram-ai/ideogram-4-fp8",
]) {
expect(modelsById[id]?.kind, `${id} should be registered as an image model`).toBe("image");
}
});
it("keeps the model table free of duplicates", () => {
const ids = huggingface.models.map((m) => m.id);
expect(new Set(ids).size).toBe(ids.length);
});
it("keeps STT models free of image-only router mappings", () => {
for (const model of sttModels) {
expect(modelMap[model.id], `STT model ${model.id} should not be in the image model map`).toBeUndefined();
}
});
});
// The router is a switchboard in front of many providers; every Hub model that is
// `pipeline_tag: image-to-image` needs a source image, and the request shape differs
// from text-to-image: `inputs` carries the base64 source image and the prompt moves
// under `parameters.prompt`. Verified against
// https://huggingface.co/docs/inference-providers/tasks/image-to-image
describe("HuggingFace image-to-image models", () => {
const IMAGE_TO_IMAGE = [
"black-forest-labs/FLUX.2-dev",
"black-forest-labs/FLUX.1-Kontext-dev",
"black-forest-labs/FLUX.2-klein-9B",
"black-forest-labs/FLUX.2-klein-4B",
"black-forest-labs/FLUX.2-klein-base-9B",
"black-forest-labs/FLUX.2-klein-base-4B",
"Qwen/Qwen-Image-Edit",
"Qwen/Qwen-Image-Edit-2509",
"Qwen/Qwen-Image-Edit-2511",
];
it("marks every image-to-image model as such in modelMap", () => {
for (const hubId of IMAGE_TO_IMAGE) {
expect(mappingTask(modelMap[hubId]), `${hubId} must be declared image-to-image`).toBe("image-to-image");
}
});
it("declares text-to-image as the default for the remaining image models", () => {
for (const [hubId, value] of Object.entries(modelMap)) {
if (IMAGE_TO_IMAGE.includes(hubId)) continue;
expect(mappingTask(value), `${hubId} should default to text-to-image`).toBe("text-to-image");
}
});
it("sends the source image as inputs and the prompt under parameters", async () => {
const body = await imageAdapter.buildBody("Qwen/Qwen-Image-Edit", {
prompt: "make it snow",
image: "data:image/png;base64,AAAA",
});
expect(body.inputs).toBe("AAAA");
expect(body.parameters).toEqual({ prompt: "make it snow" });
});
it("accepts a source image given as a bare base64 payload", async () => {
const body = await imageAdapter.buildBody("black-forest-labs/FLUX.2-dev", {
prompt: "winter",
image: "AAAA",
});
expect(body.inputs).toBe("AAAA");
});
it("accepts a source image given as an array", async () => {
const body = await imageAdapter.buildBody("Qwen/Qwen-Image-Edit-2509", {
prompt: "winter",
images: ["data:image/png;base64,BBBB"],
});
expect(body.inputs).toBe("BBBB");
});
it("throws a descriptive error when an image-to-image model gets no source image", async () => {
await expect(
imageAdapter.buildBody("black-forest-labs/FLUX.1-Kontext-dev", { prompt: "winter" })
).rejects.toThrow(/requires a source image/i);
});
it("keeps the text-to-image shape prompt-only", async () => {
const body = await imageAdapter.buildBody("black-forest-labs/FLUX.1-schnell", {
prompt: "a lighthouse",
image: "data:image/png;base64,AAAA",
});
expect(body).toEqual({ inputs: "a lighthouse" });
});
it("still throws for a model with no router mapping", () => {
expect(() => imageAdapter.buildUrl("some-org/unknown")).toThrow(/no HuggingFace router mapping/i);
});
});
// The dashboard's GenericExampleCard only renders the source-image field when the
// selected model declares capabilities: ["edit"] (GenericExampleCard.js:47), and it
// then sends the value as `image`. Without the flag the edit models are unusable
// from the UI even though the adapter supports them.
describe("HuggingFace edit models reach the dashboard", () => {
const IMAGE_TO_IMAGE = ["black-forest-labs/FLUX.2-dev", "Qwen/Qwen-Image-Edit"];
it("declares the edit capability on image-to-image models", () => {
const modelsById = Object.fromEntries(huggingface.models.map((m) => [m.id, m]));
for (const hubId of IMAGE_TO_IMAGE) {
expect(modelsById[hubId]?.capabilities, `${hubId} must declare the edit capability`).toContain("edit");
}
});
it("keeps the capability on text-to-image models that do not take a source image", () => {
const modelsById = Object.fromEntries(huggingface.models.map((m) => [m.id, m]));
expect(modelsById["black-forest-labs/FLUX.1-schnell"]?.capabilities || []).not.toContain("edit");
});
});
describe("HuggingFace registry prototype safety", () => {
it("does not resolve inherited object keys as models", () => {
// A plain-object map returns a truthy inherited value for these, which would
// build a URL like `<base>/function Object() { [native code] }`.
for (const key of ["toString", "constructor", "__proto__", "hasOwnProperty"]) {
expect(() => imageAdapter.buildUrl(key)).toThrow(/no HuggingFace router mapping/i);
}
});
});
describe("HuggingFace STT model parameters", () => {
const sttModels = huggingface.models.filter((m) => m.kind === "stt");
it("does not advertise a language parameter the ASR route cannot carry", () => {
// transcribeHuggingFace posts raw audio bytes and never reads formData, and the
// router's ASR payload has no `language` field — so a UI-declared "language"
// param is silently dropped. Declaring it lies to the dashboard.
for (const model of sttModels) {
expect(model.params, `${model.id} advertises an unusable language param`).toEqual([]);
}
});
});

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: vi.fn(),
@@ -44,6 +44,27 @@ const SAMPLE_USAGE = {
},
};
const SAMPLE_FREE_USAGE = {
activity: {
cost: "0.00000",
period: {
type: "last_4_weeks",
starting_at: "2026-08-24T00:00:00Z",
ending_at: "2026-09-18T15:03:00Z",
},
models: [],
},
limits: {
monthly: {
usage: 0.021,
models: [
{ name: "gpt-oss:120b", request_count: 6 },
{ name: "gemma4:31b", request_count: 6 },
],
},
},
};
const SAMPLE_ME = {
Plan: "max",
};
@@ -103,6 +124,98 @@ describe("getUsageForProvider(ollama)", () => {
expect(meOpts.headers["Content-Length"]).toBe("0");
});
it("maps the free plan's monthly window", async () => {
proxyAwareFetch
.mockResolvedValueOnce(jsonResponse(SAMPLE_FREE_USAGE))
.mockResolvedValueOnce(jsonResponse({ Plan: "free" }));
const usage = await getUsageForProvider({
provider: "ollama",
apiKey: "k",
providerSpecificData: {},
});
expect(usage.message).toBeUndefined();
expect(usage.plan).toBe("Free");
expect(Object.keys(usage.quotas)).toEqual(["Monthly"]);
expect(usage.quotas["Monthly"]).toMatchObject({
used: 2,
total: 100,
remainingPercentage: 98,
unlimited: false,
});
expect(usage.quotas["Monthly"].remaining).toBeUndefined();
expect(usage.quotas["Monthly"].resetAt).toBeNull();
});
describe("free plan monthly reset from signup date", () => {
afterEach(() => {
vi.useRealTimers();
});
async function monthlyResetAt(createdAt, now) {
vi.useFakeTimers();
vi.setSystemTime(new Date(now));
proxyAwareFetch
.mockResolvedValueOnce(jsonResponse(SAMPLE_FREE_USAGE))
.mockResolvedValueOnce(jsonResponse({ Plan: "free", CreatedAt: createdAt }));
const usage = await getUsageForProvider({
provider: "ollama",
apiKey: "k",
providerSpecificData: {},
});
return usage.quotas["Monthly"].resetAt;
}
it("uses the signup day of the next month", async () => {
expect(await monthlyResetAt("2025-09-06T22:15:39.871687Z", "2026-09-18T15:03:00Z"))
.toBe("2026-10-06T22:15:39.000Z");
});
it("stays in the current month when the signup day is still ahead", async () => {
expect(await monthlyResetAt("2026-09-18T09:50:49.514335Z", "2026-09-18T15:33:33Z"))
.toBe("2026-10-18T09:50:49.000Z");
expect(await monthlyResetAt("2025-09-25T10:00:00Z", "2026-09-18T15:33:33Z"))
.toBe("2026-09-25T10:00:00.000Z");
});
it("clamps the signup day to shorter months", async () => {
expect(await monthlyResetAt("2026-01-31T12:00:00Z", "2026-02-10T00:00:00Z"))
.toBe("2026-02-28T12:00:00.000Z");
});
it("skips the reset when the plan is not free", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-09-18T15:03:00Z"));
proxyAwareFetch
.mockResolvedValueOnce(jsonResponse(SAMPLE_FREE_USAGE))
.mockResolvedValueOnce(jsonResponse({ Plan: "pro", CreatedAt: "2025-09-06T22:15:39Z" }));
const usage = await getUsageForProvider({
provider: "ollama",
apiKey: "k",
providerSpecificData: {},
});
expect(usage.quotas["Monthly"].resetAt).toBeNull();
});
});
it("reports no limits when no known window is present", async () => {
proxyAwareFetch
.mockResolvedValueOnce(jsonResponse({ activity: {}, limits: {} }))
.mockResolvedValueOnce(jsonResponse({ Plan: "free" }));
const usage = await getUsageForProvider({
provider: "ollama",
apiKey: "k",
providerSpecificData: {},
});
expect(usage.message).toMatch(/no usage limits/i);
expect(usage.quotas).toEqual({});
});
it("surfaces invalid key message on 401", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({ error: "unauthorized" }, 401),

View File

@@ -0,0 +1,162 @@
import { describe, expect, it } from "vitest";
import { FORMATS } from "../../open-sse/translator/formats.js";
import { createSSETransformStreamWithLogger } from "../../open-sse/utils/stream.js";
/**
* Upstream chunks -> client Responses API events.
*
* The converter under test is openaiToOpenAIResponsesResponse(), reached through
* the registered OPENAI:OPENAI_RESPONSES pair. Without it, /v1/responses never
* reports usage and Responses clients (Codex CLI) keep their context gauge at 0,
* so they never auto-compact and eventually hit the upstream context limit.
*
* Signature is (targetFormat, sourceFormat, ...) — targetFormat is what the
* UPSTREAM speaks, sourceFormat is what the CLIENT speaks.
*/
async function runTransform(chunks, targetFormat = FORMATS.OPENAI) {
const encoder = new TextEncoder();
const input = chunks.map((c) => `data: ${JSON.stringify(c)}\n\n`).join("");
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(input));
controller.close();
},
});
const output = stream.pipeThrough(
createSSETransformStreamWithLogger(
targetFormat,
FORMATS.OPENAI_RESPONSES,
"deepseek",
null,
null,
"deepseek-flash",
),
);
const reader = output.getReader();
const decoder = new TextDecoder();
let text = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
text += decoder.decode(value, { stream: true });
}
text += decoder.decode();
return text;
}
function completedEvents(output) {
return output
.split("\n")
.filter((l) => l.startsWith("data: ") && l.includes('"type":"response.completed"'));
}
function completedResponse(output) {
const lines = completedEvents(output);
expect(lines.length, "expected exactly one response.completed").toBe(1);
return JSON.parse(lines[0].slice(6)).response;
}
const TEXT_CHUNK = {
id: "chatcmpl-test",
object: "chat.completion.chunk",
created: 1700000000,
model: "deepseek-flash",
choices: [{ index: 0, delta: { role: "assistant", content: "好" } }],
};
const FINISH_CHUNK = {
id: "chatcmpl-test",
object: "chat.completion.chunk",
created: 1700000000,
model: "deepseek-flash",
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
};
// Usage-only trailer: `choices` is empty, exactly as OpenAI emits it when
// stream_options.include_usage is set.
const USAGE_ONLY_CHUNK = {
id: "chatcmpl-test",
object: "chat.completion.chunk",
created: 1700000000,
model: "deepseek-flash",
choices: [],
usage: {
prompt_tokens: 884,
completion_tokens: 37,
total_tokens: 921,
prompt_tokens_details: { cached_tokens: 256 },
},
};
const EXPECTED_USAGE = {
input_tokens: 884,
output_tokens: 37,
total_tokens: 921,
input_tokens_details: { cached_tokens: 256 },
};
// Claude-shaped stream with NO usage anywhere: the only way the client gets a
// terminal event is the finish_reason branch, because the pivot never reaches
// flushEvents() with the terminal null chunk.
const CLAUDE_CHUNKS = [
{ type: "message_start", message: { id: "msg_1", model: "claude-x" } },
{ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } },
{ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "hi" } },
{ type: "content_block_stop", index: 0 },
{ type: "message_delta", delta: { stop_reason: "end_turn" } },
{ type: "message_stop" },
];
describe("OpenAI Responses usage on response.completed", () => {
it("maps usage reported on the finish chunk", async () => {
const output = await runTransform([
TEXT_CHUNK,
{
...FINISH_CHUNK,
usage: {
prompt_tokens: 884,
completion_tokens: 37,
total_tokens: 921,
prompt_tokens_details: { cached_tokens: 256 },
completion_tokens_details: { reasoning_tokens: 12 },
},
},
]);
expect(completedResponse(output).usage).toEqual({
...EXPECTED_USAGE,
output_tokens_details: { reasoning_tokens: 12 },
});
});
it("maps usage reported on a trailing usage-only chunk with empty choices", async () => {
const output = await runTransform([TEXT_CHUNK, FINISH_CHUNK, USAGE_ONLY_CHUNK]);
expect(completedResponse(output).usage).toEqual(EXPECTED_USAGE);
});
it("still completes when the upstream reports no usage at all", async () => {
const output = await runTransform([TEXT_CHUNK, FINISH_CHUNK]);
const response = completedResponse(output);
expect(response.status).toBe("completed");
expect(response).not.toHaveProperty("usage");
});
// Regression guard for the pivot: with a Claude upstream the converter runs as
// the second hop, translateResponse() drops the terminal null chunk before it
// reaches this converter, so flushEvents() never runs. Deferring completion
// there would leave the client without any terminal event.
it("completes on a pivoted stream whose upstream never reports usage", async () => {
const output = await runTransform(CLAUDE_CHUNKS, FORMATS.CLAUDE);
const response = completedResponse(output);
expect(response.status).toBe("completed");
});
});

View File

@@ -0,0 +1,191 @@
import { describe, it, expect } from "vitest";
import {
applyFingerprintTools,
concealFingerprintToolNames,
appendMissingFingerprintTools,
fingerprintToolKey,
restoreToolNames,
takeRenamedToolNames,
OPENCODE_FINGERPRINT_TOOLS,
} from "open-sse/utils/opencodeFingerprint.js";
const CC_TOOLS = ["Task", "Bash", "Glob", "Grep", "Read", "Edit", "Write", "WebFetch"];
const flat = (names) => names.map((name) => ({ type: "function", name }));
const chat = (names) => names.map((name) => ({ type: "function", function: { name } }));
describe("opencodeFingerprint — request side", () => {
it("renames capitalised quartet members to lowercase", () => {
const body = { tools: flat(CC_TOOLS) };
const map = applyFingerprintTools(body, true);
const names = body.tools.map((tool) => tool.name);
expect(names).toContain("bash");
expect(names).not.toContain("Bash");
expect(names).toContain("Edit");
expect(map.get("bash")).toBe("Bash");
});
it("removes quartet case duplicates without dropping unrelated case variants", () => {
const body = { tools: flat(["Bash", "bash", "Glob", "grep", "Read", "Foo", "foo"]) };
applyFingerprintTools(body, true);
const names = body.tools.map((tool) => tool.name);
expect(names.filter((name) => name === "bash")).toHaveLength(1);
expect(names).toContain("Foo");
expect(names).toContain("foo");
});
it("preserves tool count when a complete quartet is only renamed", () => {
const body = { tools: flat(CC_TOOLS) };
applyFingerprintTools(body, true);
expect(body.tools).toHaveLength(CC_TOOLS.length);
});
it("handles the nested chat shape without dropping .function", () => {
const body = { tools: chat(CC_TOOLS) };
applyFingerprintTools(body, false);
const names = body.tools.map((tool) => tool.function.name);
expect(names).toContain("bash");
expect(names).not.toContain("Bash");
expect(body.tools[1].function.name).toBe("bash");
});
it("injects all four fingerprint tools when the body carries no tools", () => {
const body = { tools: [] };
applyFingerprintTools(body, true);
expect(body.tools.map((tool) => tool.name).sort()).toEqual([...OPENCODE_FINGERPRINT_TOOLS].sort());
expect(body.tool_choice).toBe("auto");
});
it("preserves the chat no-tool default tool_choice=none", () => {
const body = {};
applyFingerprintTools(body, false);
expect(body.tools.map((tool) => tool.function.name)).toEqual(OPENCODE_FINGERPRINT_TOOLS);
expect(body.tool_choice).toBe("none");
});
it("does not invent a chat tool_choice when the caller already supplied tools", () => {
const body = { tools: chat(["Edit"]) };
applyFingerprintTools(body, false);
expect(body.tool_choice).toBeUndefined();
});
it("appends only genuinely missing quartet members", () => {
const body = { tools: flat(["Bash", "Read", "terminal"]) };
applyFingerprintTools(body, true);
const names = body.tools.map((tool) => tool.name);
expect(names).toContain("glob");
expect(names).toContain("grep");
expect(names).toContain("terminal");
expect(body.tools).toHaveLength(5);
});
it("retargets flat tool_choice that points at a renamed tool", () => {
const body = { tools: flat(CC_TOOLS), tool_choice: { type: "function", name: "Bash" } };
applyFingerprintTools(body, true);
expect(body.tool_choice.name).toBe("bash");
});
it("retargets nested tool_choice that points at a renamed tool", () => {
const body = {
tools: chat(CC_TOOLS),
tool_choice: { type: "function", function: { name: "Read" } },
};
applyFingerprintTools(body, false);
expect(body.tool_choice.function.name).toBe("read");
});
it("records the rename map against the body for the response side", () => {
const body = { tools: flat(CC_TOOLS) };
const map = applyFingerprintTools(body, true);
expect(takeRenamedToolNames(body)).toBe(map);
});
it("never throws on malformed tools", () => {
for (const tools of [null, undefined, "nope", [null, 42, []], [{}, { name: "" }]]) {
expect(() => concealFingerprintToolNames(tools)).not.toThrow();
expect(() => appendMissingFingerprintTools(tools, true)).not.toThrow();
}
});
});
describe("opencodeFingerprint — response side", () => {
const map = new Map([["bash", "Bash"], ["grep", "Grep"], ["read", "Read"]]);
it("restores names in Claude content_block_start chunks", () => {
const chunk = {
type: "content_block_start",
content_block: { type: "tool_use", name: "bash", id: "t1" },
};
const out = restoreToolNames(chunk, map);
expect(out.content_block.name).toBe("Bash");
expect(chunk.content_block.name).toBe("bash");
});
it("recursively restores streaming chunks inside arrays", () => {
const chunks = [{
choices: [{ delta: { tool_calls: [{ function: { name: "grep", arguments: "{}" } }] } }],
}];
const out = restoreToolNames(chunks, map);
expect(out[0].choices[0].delta.tool_calls[0].function.name).toBe("Grep");
});
it("restores names in Claude non-streaming bodies", () => {
const body = { type: "message", content: [{ type: "tool_use", name: "bash", input: {} }] };
expect(restoreToolNames(body, map).content[0].name).toBe("Bash");
});
it("restores names in Chat Completions message and delta shapes", () => {
const body = {
choices: [
{ message: { tool_calls: [{ function: { name: "grep", arguments: "{}" } }] } },
{ delta: { tool_calls: [{ function: { name: "read", arguments: "{}" } }] } },
],
};
const out = restoreToolNames(body, map);
expect(out.choices[0].message.tool_calls[0].function.name).toBe("Grep");
expect(out.choices[1].delta.tool_calls[0].function.name).toBe("Read");
});
it("restores names in Responses final output items", () => {
const body = { output: [{ type: "function_call", name: "bash", call_id: "c1" }] };
expect(restoreToolNames(body, map).output[0].name).toBe("Bash");
});
it("restores names in Responses streaming output_item events", () => {
const event = {
type: "response.output_item.added",
item: { type: "function_call", name: "read", call_id: "c1" },
};
expect(restoreToolNames(event, map).item.name).toBe("Read");
});
it("is a no-op without a map or with an empty map", () => {
const body = { choices: [{ message: { tool_calls: [{ function: { name: "bash" } }] } }] };
expect(restoreToolNames(body, null)).toBe(body);
expect(restoreToolNames(body, new Map())).toBe(body);
});
it("leaves unknown tool names untouched", () => {
const body = { output: [{ type: "function_call", name: "Edit" }] };
expect(restoreToolNames(body, map).output[0].name).toBe("Edit");
});
});
describe("fingerprintToolKey", () => {
it("maps quartet case/whitespace variants and rejects other tools", () => {
expect(fingerprintToolKey("Bash")).toBe("bash");
expect(fingerprintToolKey(" bash ")).toBe("bash");
expect(fingerprintToolKey("GLOB")).toBe("glob");
expect(fingerprintToolKey("Read")).toBe("read");
expect(fingerprintToolKey("Edit")).toBe("");
expect(fingerprintToolKey("terminal")).toBe("");
expect(fingerprintToolKey(null)).toBe("");
});
});

View File

@@ -277,39 +277,68 @@ describe("OpenCode Stable Session Reuse (429 follow-up)", () => {
expect(second).toBe(first);
});
it("cloaks free-tier requests with bash and read decoy tools", () => {
it("applies the full lowercase free-tier fingerprint quartet", () => {
const executor = getExecutor("opencode");
const chatNoTools = executor.transformRequest("nemotron-3-ultra-free", {
messages: [{ role: "user", content: "hi" }],
});
expect(chatNoTools.stream).toBe(true);
expect(chatNoTools.tool_choice).toBe("none");
expect(chatNoTools.tools.map((t) => t.function?.name)).toEqual([
"bash", "glob", "grep", "read",
]);
const chatWithTools = executor.transformRequest("nemotron-3-ultra-free", {
messages: [{ role: "user", content: "hi" }],
tools: [
{ type: "function", function: { name: "Bash", description: "Claude Code tool" } },
{ type: "function", function: { name: "Glob", description: "Claude Code tool" } },
{ type: "function", function: { name: "Grep", description: "Claude Code tool" } },
{ type: "function", function: { name: "Read", description: "Claude Code tool" } },
],
tool_choice: "auto",
});
expect(chatWithTools.tool_choice).toBe("auto");
expect(chatWithTools.tools.map((t) => t.function?.name)).toEqual([
"bash", "glob", "grep", "read",
]);
const chatPartial = executor.transformRequest("nemotron-3-ultra-free", {
messages: [{ role: "user", content: "hi" }],
tools: [
{ type: "function", function: { name: "bash", description: "existing" } },
{ type: "function", function: { name: "read", description: "existing" } },
],
});
expect(chatPartial.tools.map((t) => t.function?.name)).toEqual([
"bash", "read", "glob", "grep",
]);
expect(chatPartial.tools[0].function.description).toBe("existing");
});
it("cloaks Muse Responses requests even when the client already supplies tools", () => {
const executor = getExecutor("opencode");
// Case 1: no tools sent by client -> injects bash + read with tool_choice none
const chatNoTools = executor.transformRequest("nemotron-3-ultra-free", {
messages: [{ role: "user", content: "hi" }],
});
expect(chatNoTools.stream).toBe(true);
expect(chatNoTools.tool_choice).toBe("none");
expect(chatNoTools.tools.map((t) => t.function?.name)).toEqual(["bash", "read"]);
// Case 2: external CLI tools (e.g. Claude Code Bash) -> preserves Bash, appends read
const chatWithTools = executor.transformRequest("nemotron-3-ultra-free", {
messages: [{ role: "user", content: "hi" }],
tools: [{ type: "function", function: { name: "Bash", description: "Claude Code tool" } }],
const transformed = executor.transformRequest("muse-spark-1.3-contributor-free(xhigh)", {
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] }],
tools: [{
type: "function",
name: "zcode_search",
description: "client-provided tool",
parameters: { type: "object", properties: {} },
}],
tool_choice: "auto",
});
expect(chatWithTools.tool_choice).toBe("auto");
const names = chatWithTools.tools.map((t) => t.function?.name);
expect(names).toContain("Bash");
reasoning_effort: "xhigh",
}, true, {});
expect(transformed.stream).toBe(true);
expect(transformed.reasoning?.effort).toBe("xhigh");
const names = transformed.tools.map((tool) => tool.name);
expect(names).toContain("zcode_search");
expect(names).toContain("bash");
expect(names).toContain("read");
// Case 3: already has both bash and read -> do not insert anything
const chatFull = executor.transformRequest("nemotron-3-ultra-free", {
messages: [{ role: "user", content: "hi" }],
tools: [
{ type: "function", function: { name: "bash", description: "existing" } },
{ type: "function", function: { name: "read", description: "existing" } },
],
});
expect(chatFull.tools.length).toBe(2);
expect(chatFull.tools[0].function.description).toBe("existing");
expect(names.filter((name) => name === "bash")).toHaveLength(1);
expect(names.filter((name) => name === "read")).toHaveLength(1);
});
it("declares forceStream on the opencode transport so chatCore serves SSE upstream", async () => {

View File

@@ -0,0 +1,123 @@
import { describe, expect, it } from "vitest";
import { PROVIDER_MODELS, getModelSupportedFormats } from "../../open-sse/config/providerModels.js";
import { PROVIDERS } from "../../open-sse/config/providers.js";
import { resolveTransport } from "../../open-sse/services/provider.js";
// Chat-only models (no /messages, no /responses support on opencode-zen)
const CHAT_ONLY = ["deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp",
"glm-5.3-flash", "glm-5.3", "glm-5.2", "glm-5.1", "glm-5",
"minimax-m3", "minimax-m2.7", "minimax-m2.5",
"kimi-k3", "kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5",
"big-pickle", "deepseek-v4-flash-free",
"mimo-v2.6-flash-free", "mimo-v2.5-free", "ling-3.0-flash-fin-free", "nemotron-3-ultra-free", "nemotron-3.5-lightning-free"];
// Models that also expose the Anthropic /messages endpoint
const CLAUDE_CAPABLE = ["claude-fable-5", "claude-fable-5-1", "claude-opus-5",
"claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-opus-4-5",
"claude-sonnet-5", "claude-sonnet-4-6", "claude-sonnet-4-5", "claude-sonnet-4",
"claude-haiku-4-5", "qwen3.6-plus", "qwen3.5-plus", "union-alpha"];
// Models that also expose the OpenAI /responses endpoint
const RESPONSES_CAPABLE = ["gpt-6-astra",
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
"gpt-5.5", "gpt-5.5-pro",
"gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini", "gpt-5.4-nano",
"gpt-5.3-codex-spark", "gpt-5.3-codex",
"gpt-5.2", "gpt-5.2-codex",
"gpt-5.1", "gpt-5.1-codex-max", "gpt-5.1-codex", "gpt-5.1-codex-mini",
"gpt-5", "gpt-5-codex", "gpt-5-nano",
"grok-build-0.1", "grok-4.6", "grok-4.5",
"muse-spark-1.3", "muse-spark-1.2",
"muse-spark-1.3-contributor-free", "muse-spark-1.2-contributor-free"];
// Mirror of chatCore's per-model transport guard: use the sourceFormat-matched
// transport only when the model declares support for that sourceFormat.
function pickTransport(provider, sourceFormat, alias, model) {
const supported = getModelSupportedFormats(alias, model);
const rt = resolveTransport(provider, sourceFormat);
return supported?.includes(sourceFormat) ? rt : null;
}
describe("OpenCode Zen model catalog", () => {
it("matches the documented model IDs", () => {
const ids = (PROVIDER_MODELS["ocz"] || []).map((m) => m.id);
expect(ids).toContain("muse-spark-1.3-contributor-free");
expect(ids).toContain("gpt-5.5");
expect(ids).toContain("claude-opus-5");
expect(ids).toContain("kimi-k3");
expect(ids).toContain("deepseek-v4-pro");
expect(ids.length).toBeGreaterThan(60);
});
});
describe("OpenCode Zen per-model supportedFormats", () => {
it("declares [claude] for Claude + Qwen + union-alpha models", () => {
for (const m of CLAUDE_CAPABLE) {
expect(getModelSupportedFormats("ocz", m)).toEqual(["claude"]);
}
});
it("declares [openai-responses] for GPT/Grok/Spark responses models", () => {
for (const m of RESPONSES_CAPABLE) {
expect(getModelSupportedFormats("ocz", m)).toEqual(["openai-responses"]);
}
});
it("declares [openai] only for chat-only models (GLM/Kimi/MiMo) → guards /messages routing", () => {
for (const m of CHAT_ONLY) {
expect(getModelSupportedFormats("ocz", m)).toEqual(["openai"]);
}
});
});
describe("OpenCode Zen multi-endpoint transports", () => {
it("declares openai / claude / openai-responses transports", () => {
const formats = (PROVIDERS["opencode-zen"].transports || []).map((t) => t.format);
expect(formats).toEqual(["openai", "claude", "openai-responses"]);
});
it("resolveTransport picks the endpoint matching the client sourceFormat", () => {
expect(resolveTransport("opencode-zen", "claude").baseUrl).toBe("https://opencode.ai/zen/v1/messages");
expect(resolveTransport("opencode-zen", "openai-responses").baseUrl).toBe("https://opencode.ai/zen/v1/responses");
expect(resolveTransport("opencode-zen", "openai").baseUrl).toBe("https://opencode.ai/zen/v1/chat/completions");
});
it("uses x-api-key + anthropicVersion on the claude transport", () => {
const t = resolveTransport("opencode-zen", "claude");
expect(t.auth.header).toBe("x-api-key");
expect(t.auth.anthropicVersion).toBe(true);
});
});
describe("OpenCode Zen per-model transport guard (chatCore logic)", () => {
it("routes MiniMax/Qwen + claude-format client to /messages", () => {
for (const m of CLAUDE_CAPABLE) {
expect(pickTransport("opencode-zen", "claude", "ocz", m)?.baseUrl).toBe("https://opencode.ai/zen/v1/messages");
}
});
it("does NOT route chat-only models to /messages on a claude-format request", () => {
for (const m of CHAT_ONLY) {
expect(pickTransport("opencode-zen", "claude", "ocz", m)).toBeNull();
}
});
it("routes DeepSeek + responses-format client to /responses", () => {
for (const m of RESPONSES_CAPABLE) {
expect(pickTransport("opencode-zen", "openai-responses", "ocz", m)?.baseUrl).toBe("https://opencode.ai/zen/v1/responses");
}
});
it("routes Muse Spark (responses-only) to /responses, never to /messages", () => {
for (const m of ["muse-spark-1.2", "muse-spark-1.3", "muse-spark-1.2-contributor-free", "muse-spark-1.3-contributor-free", "grok-4.6", "gpt-5.6-luna"]) {
expect(getModelSupportedFormats("ocz", m)).toEqual(["openai-responses"]);
expect(pickTransport("opencode-zen", "openai-responses", "ocz", m)?.baseUrl).toBe("https://opencode.ai/zen/v1/responses");
expect(pickTransport("opencode-zen", "claude", "ocz", m)).toBeNull();
expect(pickTransport("opencode-zen", "openai", "ocz", m)).toBeNull();
}
});
it("does NOT route MiniMax (no responses support) to /responses", () => {
for (const m of CLAUDE_CAPABLE) {
expect(pickTransport("opencode-zen", "openai-responses", "ocz", m)).toBeNull();
}
});
});

View File

@@ -52,4 +52,48 @@ describe("stripUnsupportedParams", () => {
expect(body.max_tokens).toBe(64000);
});
it("drops replayed reasoning fields from assistant messages for strict providers", () => {
const makeBody = () => ({
messages: [
{ role: "user", content: "hi" },
{
role: "assistant",
content: "hello",
reasoning_content: "thinking...",
reasoning: "thinking...",
reasoning_details: [{ text: "thinking..." }],
tool_calls: [{ id: "c1", type: "function", function: { name: "f", arguments: "{}" } }],
},
{ role: "user", content: "again", reasoning_content: "user-side field stays" },
],
});
for (const [provider, model] of [
["groq", "openai/gpt-oss-120b"],
["mistral", "codestral-latest"],
["cerebras", "gpt-oss-120b"],
]) {
const body = makeBody();
stripUnsupportedParams(provider, model, body);
expect(body.messages[1]).toEqual({
role: "assistant",
content: "hello",
tool_calls: [{ id: "c1", type: "function", function: { name: "f", arguments: "{}" } }],
});
// only assistant turns are touched
expect(body.messages[2].reasoning_content).toBe("user-side field stays");
}
});
it("leaves reasoning fields alone for providers that accept or require them", () => {
const body = {
messages: [{ role: "assistant", content: "hello", reasoning_content: "thinking..." }],
};
stripUnsupportedParams("deepseek", "deepseek-reasoner", body);
stripUnsupportedParams("openrouter", "nvidia/nemotron-3-ultra-550b-a55b:free", body);
expect(body.messages[0].reasoning_content).toBe("thinking...");
});
});

View File

@@ -82,6 +82,148 @@ describe("wrapQoderSSE billing detection", () => {
expect(wrapped.ok).toBe(false);
});
it("returns 403 response when first frame is billing block (code 110 string)", async () => {
const billingEnv = JSON.stringify({
statusCodeValue: 403,
body: '{"code":"110","message":"Billing daily count exceeded"}',
});
const upstream = `data: ${billingEnv}\n\n`;
const wrapped = await wrapQoderSSE(makeResponse([upstream]), "qoder/qfmodel");
expect(wrapped.status).toBe(403);
expect(wrapped.ok).toBe(false);
const json = await wrapped.json();
expect(json.error.message).toContain("Billing daily count exceeded");
});
it("returns 403 response when first frame is billing block (code 110 numeric)", async () => {
const billingEnv = JSON.stringify({
statusCodeValue: 403,
body: '{"code":110,"message":"Billing daily count exceeded"}',
});
const upstream = `data: ${billingEnv}\n\n`;
const wrapped = await wrapQoderSSE(makeResponse([upstream]), "qoder/qfmodel");
expect(wrapped.status).toBe(403);
expect(wrapped.ok).toBe(false);
});
it("returns 403 response when statusCodeValue is string \"403\" (code 110)", async () => {
const billingEnv = JSON.stringify({
statusCodeValue: "403",
body: '{"code":"110","message":"Billing daily count exceeded"}',
});
const upstream = `data: ${billingEnv}\n\n`;
const wrapped = await wrapQoderSSE(makeResponse([upstream]), "qoder/qfmodel");
expect(wrapped.status).toBe(403);
expect(wrapped.ok).toBe(false);
const json = await wrapped.json();
expect(json.error.message).toContain("Billing daily count exceeded");
});
it("emits structured 403 error chunk for object-body billing after a data frame (peek miss)", async () => {
const okEnv = JSON.stringify({
statusCodeValue: 200,
body: JSON.stringify({ choices: [{ delta: { content: "hi" } }] }),
});
const billingEnv = JSON.stringify({
statusCodeValue: 403,
body: { code: "110", message: "Billing daily count exceeded" },
});
const upstream = `data: ${okEnv}\n\ndata: ${billingEnv}\n\n`;
const wrapped = await wrapQoderSSE(makeResponse([upstream]), "qoder/qfmodel");
const reader = wrapped.body.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
}
buf += decoder.decode();
expect(buf).not.toContain("[qoder error");
const errLine = buf.split("\n").find((l) => l.includes('"error"'));
expect(errLine).toBeDefined();
const errChunk = JSON.parse(errLine.slice(5).trim());
expect(errChunk.error.status).toBe(403);
expect(errChunk.error.message).toContain("Billing daily count exceeded");
expect(errChunk.choices).toBeUndefined();
});
it("does not treat legitimate assistant text mentioning code 110 as billing", async () => {
const inner = JSON.stringify({
choices: [{ delta: { content: "error 110 means billing daily count exceeded in docs" } }],
});
const successEnv = JSON.stringify({ statusCodeValue: 200, body: inner });
const upstream = `data: ${successEnv}\n\n`;
const wrapped = await wrapQoderSSE(makeResponse([upstream]), "qoder/qfmodel");
expect(wrapped.status).toBe(200);
const reader = wrapped.body.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
}
buf += decoder.decode();
expect(buf).toContain("billing daily count exceeded");
expect(buf).not.toContain("[qoder error");
});
it("emits structured 403 error chunk for billing envelope after a data frame (peek miss)", async () => {
const okEnv = JSON.stringify({
statusCodeValue: 200,
body: JSON.stringify({ choices: [{ delta: { content: "hi" } }] }),
});
const billingEnv = JSON.stringify({
statusCodeValue: 403,
body: '{"code":"110","message":"Billing daily count exceeded"}',
});
const upstream = `data: ${okEnv}\n\ndata: ${billingEnv}\n\n`;
const wrapped = await wrapQoderSSE(makeResponse([upstream]), "qoder/qfmodel");
const reader = wrapped.body.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
}
buf += decoder.decode();
expect(buf).not.toContain("[qoder error");
const errLine = buf.split("\n").find((l) => l.includes('"error"'));
expect(errLine).toBeDefined();
const errChunk = JSON.parse(errLine.slice(5).trim());
expect(errChunk.error.status).toBe(403);
expect(errChunk.error.message).toContain("Billing daily count exceeded");
});
it("emits structured 403 error chunk for object-body billing envelope (peek miss)", async () => {
const billingEnv = JSON.stringify({
statusCodeValue: 403,
body: { code: "110", message: "Billing daily count exceeded" },
});
const upstream = `data: ${billingEnv}\n\n`;
const wrapped = await wrapQoderSSE(makeResponse([upstream]), "qoder/qfmodel");
expect(wrapped.status).toBe(403);
});
it("returns 403 response when first frame has pricingUrl", async () => {
const billingEnv = JSON.stringify({
statusCodeValue: 402,
@@ -94,7 +236,7 @@ describe("wrapQoderSSE billing detection", () => {
expect(wrapped.status).toBe(403);
});
it("passes through normal errors (non-billing) as wrapped SSE", async () => {
it("returns non-billing errors with their upstream HTTP status", async () => {
const errorEnv = JSON.stringify({
statusCodeValue: 500,
body: "Internal server error",
@@ -103,22 +245,11 @@ describe("wrapQoderSSE billing detection", () => {
const wrapped = await wrapQoderSSE(makeResponse([upstream]), "qoder/ultimate");
// Normal error: still 200 response, error text in SSE body
expect(wrapped.status).toBe(200);
expect(wrapped.ok).toBe(true);
const reader = wrapped.body.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
}
buf += decoder.decode();
expect(buf).toContain("[qoder error 500");
expect(buf).toContain("data: [DONE]");
expect(wrapped.status).toBe(500);
expect(wrapped.ok).toBe(false);
expect(await wrapped.json()).toEqual({
error: { message: "Internal server error", code: 500 },
});
});
it("passes through successful responses unchanged", async () => {

View File

@@ -0,0 +1,97 @@
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("../../open-sse/services/qoderModels.js", () => ({
getQoderModelConfig: vi.fn(async () => ({ key: "auto", max_output_tokens: 32 })),
resolveQoderModels: vi.fn(),
isQoderPat: () => false,
resolveQoderCredentials: vi.fn(),
}));
const request = {
model: "auto",
body: { messages: [{ role: "user", content: "hello" }], max_tokens: 32 },
stream: true,
credentials: {
accessToken: "dt-test-token",
providerSpecificData: { userId: "test-user", machineId: "test-machine" },
},
};
function success() {
return new Response('data: {"statusCodeValue":200,"body":"[DONE]"}\n\n', {
headers: { "Content-Type": "text/event-stream" },
});
}
async function loadExecutor(fetchMock, useProxy = true) {
vi.resetModules();
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "all_proxy", "no_proxy"]) {
vi.stubEnv(key, "");
}
if (useProxy) vi.stubEnv("HTTPS_PROXY", "http://proxy.test:3128");
// Exercise the real proxyAwareFetch: it captures fetch when imported.
vi.stubGlobal("fetch", fetchMock);
const { QoderExecutor } = await import("../../open-sse/executors/qoder.js");
return new QoderExecutor();
}
afterEach(() => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
});
describe("Qoder signed inference transport", () => {
it.each([null, { strictProxy: false }])("does not replay a signed POST after proxy response loss (%j)", async (proxyOptions) => {
const seen = new Set();
const fetchMock = vi.fn(async (_url, options) => {
const authorization = options.headers.Authorization;
if (seen.has(authorization)) {
return new Response('data: {"statusCodeValue":403,"body":"{\\"code\\":\\"103\\",\\"message\\":\\"Duplicate request\\"}"}\n\n');
}
seen.add(authorization);
throw new TypeError("response lost after upstream accepted request");
});
const executor = await loadExecutor(fetchMock);
await expect(executor.execute({ ...request, proxyOptions })).rejects.toThrow("response lost");
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][1].dispatcher).toBeDefined();
if (proxyOptions) expect(proxyOptions.strictProxy).toBe(false);
});
it("generates a fresh COSY identity when the caller retries after transport failure", async () => {
const fetchMock = vi.fn()
.mockRejectedValueOnce(new TypeError("response lost"))
.mockResolvedValueOnce(success());
const executor = await loadExecutor(fetchMock);
await expect(executor.execute(request)).rejects.toThrow("response lost");
const result = await executor.execute(request);
expect(result.response.ok).toBe(true);
await result.response.text();
expect(fetchMock).toHaveBeenCalledTimes(2);
const ids = fetchMock.mock.calls.map(([, options]) => JSON.parse(
Buffer.from(options.headers.Authorization.split(".")[1], "base64").toString(),
).requestId);
expect(ids[0]).not.toBe(ids[1]);
});
it.each([true, false])("still supports successful inference with proxy=%s", async (useProxy) => {
const fetchMock = vi.fn(async () => success());
const executor = await loadExecutor(fetchMock, useProxy);
const result = await executor.execute(request);
expect(result.response.ok).toBe(true);
await result.response.text();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(!!fetchMock.mock.calls[0][1].dispatcher).toBe(useProxy);
});
it("preserves caller cancellation without replaying the request", async () => {
const controller = new AbortController();
const fetchMock = vi.fn(async (_url, options) => {
controller.abort();
throw options.signal.reason;
});
const executor = await loadExecutor(fetchMock);
await expect(executor.execute({ ...request, signal: controller.signal })).rejects.toMatchObject({ name: "AbortError" });
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,81 @@
import { describe, it, expect, vi } from "vitest";
import { __test__ } from "../../open-sse/executors/qoder.js";
const { wrapQoderSSE } = __test__;
const duplicate = '{"code":"103","message":"Duplicate request"}';
const frame = (statusCodeValue, body) => `data: ${JSON.stringify({ statusCodeValue, body })}\n\n`;
function upstream(chunks, { keepOpen = false } = {}) {
const cancel = vi.fn();
const response = new Response(new ReadableStream({
start(controller) {
for (const chunk of chunks) controller.enqueue(new TextEncoder().encode(chunk));
if (!keepOpen) controller.close();
},
cancel,
}));
return { response, cancel };
}
describe("Qoder first-frame errors", () => {
it.each([
["one chunk", [frame(403, duplicate)]],
["fragmented frame", [frame(403, duplicate).slice(0, 35), frame(403, duplicate).slice(35)]],
["heartbeat prefix", [": keepalive\r\n\r\n", frame(403, duplicate)]],
["prefix and frame in one chunk", [": keepalive\n\nevent: message\n" + frame(403, duplicate)]],
["EOF without newline", [frame(403, duplicate).trimEnd()]],
["object body", [frame(403, JSON.parse(duplicate))]],
])("surfaces duplicate-request errors as HTTP 403: %s", async (_name, chunks) => {
const { response } = upstream(chunks);
const wrapped = await wrapQoderSSE(response, "qoder/kmodel_latest");
expect(wrapped.status).toBe(403);
expect(wrapped.ok).toBe(false);
expect(wrapped.headers.get("content-type")).toBe("application/json");
const body = await wrapped.json();
expect(body.error.message).toBe(duplicate);
expect(body).not.toHaveProperty("choices");
});
it("cancels the upstream keepalive immediately after an error", async () => {
const { response, cancel } = upstream([": keepalive\n\n", frame(403, duplicate)], { keepOpen: true });
const wrapped = await wrapQoderSSE(response, "qoder/kmodel_latest");
expect(wrapped.status).toBe(403);
expect(cancel).toHaveBeenCalledOnce();
});
it.each([401, 429, 500, 503])("preserves non-billing HTTP status %s", async (status) => {
const { response } = upstream([frame(status, "upstream failure")]);
const wrapped = await wrapQoderSSE(response, "qoder/auto");
expect(wrapped.status).toBe(status);
expect((await wrapped.json()).error.message).toBe("upstream failure");
});
it.each([0, 302, 600, 403.5])("maps invalid error status %s to 502", async (status) => {
const { response } = upstream([frame(status, "invalid upstream status")]);
const wrapped = await wrapQoderSSE(response, "qoder/auto");
expect(wrapped.status).toBe(502);
});
it("replays successful frames after a heartbeat without losing or duplicating content", async () => {
const first = JSON.stringify({ choices: [{ delta: { content: "hello" } }] });
const second = JSON.stringify({ choices: [{ delta: { content: "world" } }] });
const { response } = upstream([": keepalive\n\n", frame(200, first) + frame(200, second) + "data: [DONE]\n\n"]);
const wrapped = await wrapQoderSSE(response, "qoder/auto");
expect(wrapped.status).toBe(200);
expect(await wrapped.text()).toBe(`data: ${first}\n\ndata: ${second}\n\ndata: [DONE]\n\n`);
});
it("starts forwarding success without waiting for the upstream to close", async () => {
const inner = JSON.stringify({ choices: [{ delta: { content: "hello" } }] });
const { response, cancel } = upstream([": keepalive\n\n", frame(200, inner)], { keepOpen: true });
const wrapped = await wrapQoderSSE(response, "qoder/auto");
const reader = wrapped.body.getReader();
try {
const { value } = await reader.read();
expect(new TextDecoder().decode(value)).toBe(`data: ${inner}\n\n`);
} finally {
await reader.cancel();
}
expect(cancel).toHaveBeenCalledOnce();
});
});

View File

@@ -506,14 +506,14 @@ describe("wrapQoderSSE", () => {
// Regression for review finding #3: chunks could leak past [DONE] when
// the success branch had no doneEmitted guard. We synthesize an error
// envelope (which sets doneEmitted=true) followed by a valid envelope
// envelope after content (which sets doneEmitted=true), followed by a valid envelope
// and assert the second envelope is NOT forwarded.
it("does not forward chunks after [DONE] has been emitted", async () => {
const errorEnv = JSON.stringify({ statusCodeValue: 500, body: "boom" });
const validInner = JSON.stringify({ choices: [{ delta: { content: "leak" } }] });
const validEnv = JSON.stringify({ statusCodeValue: 200, body: validInner });
const wrapped = await wrapQoderSSE(
makeResponse([`data: ${errorEnv}\n\ndata: ${validEnv}\n\n`]),
makeResponse([envelope(JSON.stringify({ choices: [{ delta: { content: "hi" } }] })) + `data: ${errorEnv}\n\ndata: ${validEnv}\n\n`]),
"qoder/auto",
);
const out = await drain(wrapped);
@@ -539,12 +539,13 @@ describe("wrapQoderSSE", () => {
expect(() => JSON.parse(dataLine.slice("data: ".length))).not.toThrow();
});
it("upstream error envelope produces an error chunk + [DONE]", async () => {
it("upstream first-frame error envelope produces an HTTP error", async () => {
const env = JSON.stringify({ statusCodeValue: 503, body: "service unavailable" });
const wrapped = await wrapQoderSSE(makeResponse([`data: ${env}\n\n`]), "qoder/lite");
const out = await drain(wrapped);
expect(out).toContain("[qoder error 503");
expect(out).toContain("data: [DONE]\n\n");
expect(wrapped.status).toBe(503);
expect(await wrapped.json()).toEqual({
error: { message: "service unavailable", code: 503 },
});
});
it("non-ok responses are returned unchanged (no transform)", async () => {
@@ -651,6 +652,11 @@ describe("qoderInferenceBase", () => {
expect(qoderInferenceBase({ accessToken: "jt-abc" })).toContain("api2.qoder.sh");
expect(qoderInferenceBase({ accessToken: "dt-abc" })).toContain("api3.qoder.sh");
});
it("serves every token kind from the CN gateway for the qoder-cn region", () => {
expect(qoderInferenceBase({ accessToken: "jt-abc" }, "cn")).toContain("gateway.qoder.com.cn");
expect(qoderInferenceBase({ accessToken: "dt-abc" }, "cn")).toContain("gateway.qoder.com.cn");
});
});
describe("rewriteQoderMessageAttachments", () => {

View File

@@ -0,0 +1,131 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
const { executeMock } = vi.hoisted(() => ({
executeMock: vi.fn(),
}));
vi.mock("../../open-sse/executors/index.js", () => ({
getExecutor: () => ({
noAuth: true,
execute: executeMock,
}),
}));
vi.mock("../../open-sse/utils/requestLogger.js", () => ({
createRequestLogger: async () => ({
logClientRawRequest: vi.fn(),
logRawRequest: vi.fn(),
logTargetRequest: vi.fn(),
logProviderResponse: vi.fn(),
logConvertedResponse: vi.fn(),
logError: vi.fn(),
}),
}));
vi.mock("../../open-sse/utils/stream.js", () => ({
COLORS: { red: "", reset: "" },
createPassthroughStreamWithLogger: vi.fn(() => new TransformStream()),
}));
vi.mock("@/lib/usageDb.js", () => ({
trackPendingRequest: vi.fn(),
appendRequestLog: vi.fn(async () => {}),
saveRequestDetail: vi.fn(async () => {}),
}));
const { handleChatCore } = await import("../../open-sse/handlers/chatCore.js");
function makeLongDiff() {
const lines = ["diff --git a/foo.js b/foo.js", "index abc..def 100644", "--- a/foo.js", "+++ b/foo.js", "@@ -1,3 +1,200 @@"];
for (let i = 0; i < 200; i++) lines.push(`+added line ${i} UNIQUE_PADDING_${i} ${"x".repeat(20)}`);
return lines.join("\n");
}
describe("token savers on Cursor (pre-translate RTK)", () => {
beforeEach(() => {
vi.clearAllMocks();
global.fetch = vi.fn(async (url, init) => {
if (String(url).includes("/v1/compress")) {
const payload = JSON.parse(init.body);
return new Response(JSON.stringify({
messages: payload.messages,
tokens_before: 8000,
tokens_after: 2500,
tokens_saved: 5500,
}), { status: 200, headers: { "content-type": "application/json" } });
}
throw new Error(`unexpected fetch: ${url}`);
});
executeMock.mockResolvedValue({
response: new Response(JSON.stringify({
id: "chatcmpl-test",
object: "chat.completion",
choices: [{ message: { role: "assistant", content: "ok" }, finish_reason: "stop", index: 0 }],
}), { status: 200, headers: { "content-type": "application/json" } }),
url: "https://api2.cursor.sh/agent",
headers: {},
transformedBody: null,
});
});
it("compresses role:tool git diffs before openai→cursor rewrite, then injects Headroom/Caveman/Ponytail", async () => {
const diff = makeLongDiff();
const log = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), line: vi.fn() };
await handleChatCore({
body: {
model: "cu/default",
stream: false,
messages: [
{ role: "system", content: "hi" },
{ role: "user", content: "run git diff" },
{
role: "assistant",
content: null,
tool_calls: [{ id: "call_1", type: "function", function: { name: "Bash", arguments: JSON.stringify({ command: "git diff" }) } }],
},
{ role: "tool", tool_call_id: "call_1", content: diff },
{ role: "user", content: "summarize" },
],
},
modelInfo: { provider: "cursor", model: "default" },
credentials: { apiKey: "test-key", providerSpecificData: {} },
log,
connectionId: "test-conn",
rtkEnabled: true,
headroomEnabled: true,
headroomUrl: "http://localhost:8787",
cavemanEnabled: true,
cavemanLevel: "full",
ponytailEnabled: true,
ponytailLevel: "full",
clientRawRequest: {
endpoint: "/v1/chat/completions",
body: { model: "cu/default" },
headers: { accept: "application/json" },
},
});
expect(executeMock).toHaveBeenCalled();
const dispatched = executeMock.mock.calls[0][0].body;
const blob = JSON.stringify(dispatched.messages);
expect(dispatched.messages.some((m) => m.role === "tool")).toBe(false);
expect(blob).toContain("<tool_result>");
expect(blob).toContain("lines truncated");
expect(blob).not.toContain("UNIQUE_PADDING_150");
expect(blob).toContain("lazy senior developer");
expect(blob).toMatch(/Respond like a caveman|drop filler|ACTIVE EVERY RESPONSE/i);
expect(global.fetch).toHaveBeenCalledWith(
"http://localhost:8787/v1/compress",
expect.any(Object)
);
const xf = log.line.mock.calls.find((c) => c[1] === "⚙");
expect(xf, "expected ⚙ saver log").toBeTruthy();
expect(xf[2]).toContain("RTK:");
expect(xf[2]).toContain("CAVEMAN:full");
expect(xf[2]).toContain("PONYTAIL:full");
});
});

View File

@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import { budgetToLevel } from "../../open-sse/translator/concerns/thinking.js";
import { applyThinking } from "../../open-sse/translator/concerns/thinkingUnified.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
// Reverse map must be able to reach "max": LEVEL_TO_BUDGET.max = 128000 and
// xhigh = 32768, so the xhigh/max threshold is their midpoint (80384).
// Previously any budget > 28672 collapsed to "xhigh", making "max"
// unreachable from Claude Code budget_tokens — its default thinking budget
// (MAX_THINKING_TOKENS) could never produce effort "max".
describe("budgetToLevel reaches max tier", () => {
it("budget 98304 → \"max\"", () => {
expect(budgetToLevel(98304)).toBe("max");
});
it("budget 128000 → \"max\"", () => {
expect(budgetToLevel(128000)).toBe("max");
});
it("budget 80385 → \"max\" (just above midpoint)", () => {
expect(budgetToLevel(80385)).toBe("max");
});
it("budget 80384 → \"xhigh\" (midpoint still xhigh)", () => {
expect(budgetToLevel(80384)).toBe("xhigh");
});
it("budget 31999 stays \"xhigh\"", () => {
expect(budgetToLevel(31999)).toBe("xhigh");
});
});
describe("applyThinking (openai-responses): large budgets map to max effort", () => {
it("budget 98304 → reasoning_effort \"max\" for gpt-5.6-sol (openai wire)", () => {
const body = { thinking: { type: "enabled", budget_tokens: 98304 } };
const out = applyThinking(FORMATS.OPENAI_RESPONSES, "gpt-5.6-sol", body, "codex");
expect(out?.reasoning_effort).toBe("max");
});
});

View File

@@ -14,7 +14,7 @@ vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
const load = () => import("../../open-sse/services/usage.js");
const SUPPORTED = [
"github", "gemini-cli", "antigravity", "claude", "codex", "kiro",
"qoder", "iflow", "ollama", "glm", "glm-cn",
"qoder", "qoder-cn", "iflow", "ollama", "glm", "glm-cn",
"minimax", "minimax-cn", "vercel-ai-gateway", "grok-cli", "kimi",
"deepseek", "opencode-go", "zed", "commandcode",
];

View File

@@ -1,6 +1,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { describe, it, expect, beforeEach, vi } from "vitest";
import { XiaomiMimoExecutor, __test__ } from "../../open-sse/executors/xiaomi-mimo.js";
import { getExecutor } from "../../open-sse/executors/index.js";
import * as mimoAccount from "../../open-sse/shared/mimoAccount.js";
const { bareModel, COOKIE_KEY } = __test__;
@@ -17,22 +18,52 @@ describe("xiaomi-mimo executor", () => {
expect(getExecutor("xiaomi-mimo")).toBeInstanceOf(XiaomiMimoExecutor);
});
it("routes Preview models to the account-service route regardless of transport", () => {
const expected = "https://mimo-server-cn.xiaomimimo.com/api/route/chat/completions";
expect(ex.buildUrl("mimo-x-pro-preview", true, 0, OPENAI_T)).toBe(expected);
expect(ex.buildUrl("mimo-x-pro-preview", true, 0, CLAUDE_T)).toBe(expected);
// body.model arrives as `xiaomi/<id>` via upstreamModelId
expect(ex.buildUrl("xiaomi/mimo-x-flash-preview", true, 0, OPENAI_T)).toBe(expected);
});
it("keeps the sourceFormat-matched endpoint for cloud models", () => {
// Regression: a Claude client must reach /anthropic/v1/messages, not /v1/chat/completions.
expect(ex.buildUrl("mimo-v2.5-pro", true, 0, CLAUDE_T)).toBe(CLAUDE_T.runtimeTransport.baseUrl);
expect(ex.buildUrl("mimo-v2.5-pro", true, 0, OPENAI_T)).toBe(OPENAI_T.runtimeTransport.baseUrl);
});
it("authenticates Preview calls with the account cookie", () => {
const headers = ex.buildHeaders({ [COOKIE_KEY]: "serviceToken=abc", accessToken: "sk-x" }, true, "u", "mimo-x-pro-preview");
it("routes v2.6 models to account route when desktop credentials are present", () => {
// No region → SGP default
const expected = "https://mimo-server-sgp.xiaomimimo.com/api/route/chat/completions";
const credsWithToken = { providerSpecificData: { mimoPassToken: "token123" } };
const credsWithCookie = { [COOKIE_KEY]: "serviceToken=abc" };
expect(ex.buildUrl("mimo-v2.6-flash", true, 0, credsWithToken)).toBe(expected);
expect(ex.buildUrl("mimo-v2.6-pro", true, 0, credsWithCookie)).toBe(expected);
expect(ex.buildUrl("xiaomi/mimo-v2.6-flash", true, 0, credsWithToken)).toBe(expected);
});
it("routes v2.6 models to cloud API when no desktop credentials are present", () => {
expect(ex.buildUrl("mimo-v2.6-flash", true, 0, OPENAI_T)).toBe(OPENAI_T.runtimeTransport.baseUrl);
expect(ex.buildUrl("mimo-v2.6-pro", true, 0, CLAUDE_T)).toBe(CLAUDE_T.runtimeTransport.baseUrl);
});
it("resolves the account-service cluster per connection region", () => {
const cn = "https://mimo-server-cn.xiaomimimo.com/api/route/chat/completions";
const sgp = "https://mimo-server-sgp.xiaomimimo.com/api/route/chat/completions";
const ams = "https://mimo-server-ams.xiaomimimo.com/api/route/chat/completions";
const ru = "https://mimo-server-ru.xiaomimimo.com/api/route/chat/completions";
const inRegion = "https://mimo-server-in.xiaomimimo.com/api/route/chat/completions";
// default (no region) falls back to SGP (the international cluster)
expect(ex.buildUrl("mimo-v2.6-flash", true, 0, { providerSpecificData: { mimoPassToken: "t" } })).toBe(sgp);
expect(ex.buildUrl("mimo-v2.6-pro", true, 0, { providerSpecificData: { region: "cn", mimoPassToken: "t" } })).toBe(cn);
expect(ex.buildUrl("mimo-v2.6-pro", true, 0, { providerSpecificData: { region: "sgp", mimoPassToken: "t" } })).toBe(sgp);
expect(ex.buildUrl("mimo-v2.6-flash", true, 0, { providerSpecificData: { region: "SGP", mimoPassToken: "t" } })).toBe(sgp);
expect(ex.buildUrl("mimo-v2.6-pro", true, 0, { providerSpecificData: { region: "ams", mimoPassToken: "t" } })).toBe(ams);
expect(ex.buildUrl("mimo-v2.6-pro", true, 0, { providerSpecificData: { region: "ru", mimoPassToken: "t" } })).toBe(ru);
expect(ex.buildUrl("mimo-v2.6-pro", true, 0, { providerSpecificData: { region: "in", mimoPassToken: "t" } })).toBe(inRegion);
// unknown region falls back to SGP
expect(ex.buildUrl("mimo-v2.6-pro", true, 0, { providerSpecificData: { region: "eu", mimoPassToken: "t" } })).toBe(sgp);
});
it("authenticates v2.6 calls with account cookie when on account route", () => {
const headers = ex.buildHeaders(
{ [COOKIE_KEY]: "serviceToken=abc", accessToken: "sk-x" },
true,
"u",
"mimo-v2.6-flash",
);
expect(headers.Cookie).toBe("serviceToken=abc");
expect(headers.Authorization).toBeUndefined();
});
@@ -43,38 +74,55 @@ describe("xiaomi-mimo executor", () => {
expect(headers.Cookie).toBeUndefined();
});
it("fails fast when a Preview call has no account session", async () => {
await expect(
ex.execute({ model: "mimo-x-pro-preview", body: {}, stream: true, credentials: {}, log: null }),
).rejects.toThrow(/account session unavailable/);
});
it("flattens content-part arrays to plain strings", () => {
it("preserves content-part arrays for multimodal inputs", () => {
const parts = [{ type: "image_url", image_url: { url: "data:image/png;base64,xyz" } }, { type: "text", text: "hi" }];
const out = ex.transformRequest(
"mimo-x-pro-preview",
{ messages: [{ role: "user", content: [{ type: "text", text: "a" }, { type: "text", text: "b" }] }] },
"mimo-v2.6-pro",
{ messages: [{ role: "user", content: parts }] },
true,
{},
{ providerSpecificData: { mimoPassToken: "token" } },
);
expect(out.messages[0].content).toBe("ab");
expect(out.messages[0].content).toEqual(parts);
});
it("applies Preview defaults without overriding explicit values", () => {
it("bridges reasoning_effort to official output_config.effort", () => {
const creds = { providerSpecificData: { mimoPassToken: "token" } };
const body = {
messages: [{ role: "user", content: "solve" }],
reasoning_effort: "high",
};
const out = ex.transformRequest("mimo-v2.6-pro", body, true, creds);
expect(out.reasoning_effort).toBeUndefined();
expect(out.output_config).toEqual({ effort: "high" });
});
it("normalizes xhigh reasoning_effort to high in output_config.effort", () => {
const creds = { providerSpecificData: { mimoPassToken: "token" } };
const body = {
messages: [{ role: "user", content: "complex" }],
reasoning_effort: "xhigh",
};
const out = ex.transformRequest("mimo-v2.6-pro", body, true, creds);
expect(out.reasoning_effort).toBeUndefined();
expect(out.output_config).toEqual({ effort: "high" });
});
it("applies defaults without overriding explicit values", () => {
const creds = { providerSpecificData: { mimoPassToken: "token" } };
const body = { messages: [{ role: "user", content: "hi" }], temperature: 0.2 };
const out = ex.transformRequest("mimo-x-pro-preview", body, true, {});
expect(out.temperature).toBe(0.2); // caller's value kept
expect(out.top_p).toBe(0.95); // default filled in
expect(out.max_tokens).toBe(4096);
const out = ex.transformRequest("mimo-v2.6-pro", body, true, creds);
expect(out.temperature).toBe(0.2);
expect(out.top_p).toBe(0.95);
});
it("leaves cloud bodies free of Preview defaults", () => {
it("leaves cloud bodies free of account defaults", () => {
const out = ex.transformRequest("mimo-v2.5-pro", { messages: [{ role: "user", content: "hi" }] }, true, {});
expect(out.thinking).toBeUndefined();
expect(out.max_tokens).toBeUndefined();
expect(out.output_config).toBeUndefined();
expect(out.temperature).toBeUndefined();
});
it("strips a provider/model prefix when testing preview ids", () => {
expect(bareModel("xiaomi/mimo-x-pro-preview")).toBe("mimo-x-pro-preview");
expect(bareModel("mimo-x-pro-preview")).toBe("mimo-x-pro-preview");
it("strips a provider/model prefix when testing model ids", () => {
expect(bareModel("xiaomi/mimo-v2.6-pro")).toBe("mimo-v2.6-pro");
expect(bareModel("mimo-v2.6-flash")).toBe("mimo-v2.6-flash");
});
});

View File

@@ -0,0 +1,40 @@
/**
* Security invariants of the server-assisted MiMo login proxy
* (src/lib/mimoLoginSession.js):
* - credentials bound to 9router's own origin are never forwarded upstream
* - upstream Set-Cookie is never replayed onto the app's own cookie jar
*/
import { describe, it, expect } from "vitest";
import { __test__ } from "../../src/lib/mimoLoginSession.js";
const { STRIP_UPSTREAM_HEADERS, buildBrowserResponse } = __test__;
describe("mimo login proxy security", () => {
it("strips auth credentials and session cookies before forwarding upstream", () => {
for (const h of ["authorization", "proxy-authorization", "cookie", "host"]) {
expect(STRIP_UPSTREAM_HEADERS.has(h)).toBe(true);
}
});
it("does not replay upstream Set-Cookie onto the app origin", async () => {
const upstream = new Response("ok", {
status: 200,
headers: {
"content-type": "text/html",
"set-cookie": "userId=123; Path=/", // plain object header: visible via getSetCookie
},
});
const out = await buildBrowserResponse({ jar: new Map() }, upstream, "http://localhost:20128", "/pass/");
expect(out.headers.getSetCookie()).toEqual([]);
});
it("keeps ordinary response headers intact", async () => {
const upstream = new Response("<html></html>", {
status: 200,
headers: { "content-type": "text/html" },
});
const out = await buildBrowserResponse({ jar: new Map() }, upstream, "http://localhost:20128", "/fe/");
expect(out.status).toBe(200);
expect(out.headers.get("content-type")).toBe("text/html");
});
});