Merge origin/master (v0.5.69) into gitea/new_feature

This commit is contained in:
2026-09-07 14:10:11 +07:00
222 changed files with 12285 additions and 3371 deletions

View File

@@ -0,0 +1,89 @@
import { createRequire } from "module";
import { describe, expect, it } from "vitest";
const require = createRequire(import.meta.url);
const {
ANTIGRAVITY_IDE_VERSION,
applyAntigravityIdeVersionOverride,
} = require("../../src/mitm/antigravityIdeVersion.js");
const CURRENT_VERSION = "2.11.0";
function makeRequest(metadata = { ideName: "antigravity", ideVersion: CURRENT_VERSION }) {
const bodyBuffer = Buffer.from(JSON.stringify({ metadata, request: { contents: [] } }));
const headers = {
"content-type": "application/json",
"content-length": String(bodyBuffer.length),
"user-agent": `antigravity/${CURRENT_VERSION}`,
};
return { bodyBuffer, headers };
}
describe("Antigravity IDE version override", () => {
it("preserves catalog request identity and bytes", () => {
const { bodyBuffer, headers } = makeRequest();
const result = applyAntigravityIdeVersionOverride(
bodyBuffer,
headers,
"/v1internal:fetchAvailableModels"
);
expect(result.applied).toBe(false);
expect(result.bodyBuffer).toBe(bodyBuffer);
expect(result.headers).toBe(headers);
expect(result.headers["user-agent"]).toBe(`antigravity/${CURRENT_VERSION}`);
expect(JSON.parse(result.bodyBuffer.toString()).metadata.ideVersion).toBe(CURRENT_VERSION);
expect(result.headers["content-length"]).toBe(String(bodyBuffer.length));
});
it.each([":generateContent", ":streamGenerateContent"])(
"rewrites identity for %s requests",
(endpoint) => {
const { bodyBuffer, headers } = makeRequest();
const result = applyAntigravityIdeVersionOverride(
bodyBuffer,
headers,
`/v1internal/models/gemini-3.7-flash-tiered${endpoint}`
);
expect(result.applied).toBe(true);
expect(result.bodyBuffer).not.toBe(bodyBuffer);
expect(result.headers["user-agent"]).toBe(`antigravity/${ANTIGRAVITY_IDE_VERSION}`);
expect(JSON.parse(result.bodyBuffer.toString()).metadata.ideVersion).toBe(ANTIGRAVITY_IDE_VERSION);
}
);
it("preserves malformed non-generation request content byte-for-byte", () => {
const bodyBuffer = Buffer.from([0xff, 0x00, 0x7b, 0x6e, 0x6f, 0x74, 0x2d, 0x6a, 0x73, 0x6f, 0x6e]);
const headers = {
"content-type": "application/octet-stream",
"content-length": String(bodyBuffer.length),
"user-agent": `antigravity/${CURRENT_VERSION}`,
};
const result = applyAntigravityIdeVersionOverride(bodyBuffer, headers, "/v1internal:loadCodeAssist");
expect(result.applied).toBe(false);
expect(result.bodyBuffer).toBe(bodyBuffer);
expect(result.headers).toBe(headers);
});
it("does not synthesize missing Antigravity identity", () => {
const bodyBuffer = Buffer.from(JSON.stringify({ metadata: {}, request: { contents: [] } }));
const headers = { "content-type": "application/json", "content-length": String(bodyBuffer.length) };
const result = applyAntigravityIdeVersionOverride(
bodyBuffer,
headers,
"/v1internal/models/gemini-3.7-flash-tiered:generateContent"
);
expect(result.applied).toBe(false);
expect(result.bodyBuffer).toBe(bodyBuffer);
expect(result.headers).toEqual(headers);
expect(result.headers).not.toHaveProperty("user-agent");
expect(JSON.parse(result.bodyBuffer.toString()).metadata).not.toHaveProperty("ideVersion");
});
});

View File

@@ -0,0 +1,61 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
const proxyAwareFetch = vi.fn(async (url) => ({
ok: true,
status: 200,
json: async () => url.includes(":loadCodeAssist")
? { cloudaicompanionProject: "project-1", currentTier: { name: "Pro" } }
: {
models: {
"gemini-3.8-flash-high": {
displayName: "Gemini 3.8 Flash (High)",
quotaInfo: { remainingFraction: 0.85, resetTime: "2026-08-25T12:00:00Z" },
},
"gemini-3.8-flash-medium": {
displayName: "Gemini 3.8 Flash (Medium)",
quotaInfo: { remainingFraction: 0.6, resetTime: "2026-08-25T12:00:00Z" },
},
"gemini-3.8-flash-low": {
displayName: "Gemini 3.8 Flash (Low)",
quotaInfo: { remainingFraction: 0.35, resetTime: "2026-08-25T12:00:00Z" },
},
"internal-model": {
displayName: "Internal",
isInternal: true,
quotaInfo: { remainingFraction: 0.5 },
},
},
},
text: async () => "{}",
}));
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch,
}));
describe("Antigravity quota tracker: Gemini 3.8 Flash usage bars", () => {
beforeEach(() => proxyAwareFetch.mockClear());
it("returns Gemini 3.8 Flash tier quotas so the dashboard can render usage bars", async () => {
const { getAntigravityUsage } = await import("../../open-sse/services/usage/google.js");
const usage = await getAntigravityUsage("access-token", {});
expect(usage.quotas["gemini-3.8-flash-high"]).toMatchObject({
used: 150,
total: 1000,
remainingPercentage: 85,
displayName: "Gemini 3.8 Flash (High)",
});
expect(usage.quotas["gemini-3.8-flash-medium"]).toMatchObject({
used: 400,
total: 1000,
remainingPercentage: 60,
});
expect(usage.quotas["gemini-3.8-flash-low"]).toMatchObject({
used: 650,
total: 1000,
remainingPercentage: 35,
});
});
});

View File

@@ -0,0 +1,311 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
getProviderConnections: vi.fn(),
getSettings: vi.fn(),
resolveConnectionProxyConfig: vi.fn(),
getAntigravityUsage: vi.fn(),
}));
vi.mock("@/lib/localDb", () => ({
getProviderConnections: mocks.getProviderConnections,
getSettings: mocks.getSettings,
getProxyPools: vi.fn(),
validateApiKey: vi.fn(),
updateProviderConnection: vi.fn(),
}));
vi.mock("@/lib/network/connectionProxy", () => ({
resolveConnectionProxyConfig: mocks.resolveConnectionProxyConfig,
pickProxyPoolId: vi.fn(),
}));
vi.mock("@/shared/constants/providers.js", () => ({
FREE_PROVIDERS: {},
resolveProviderId: (provider) => provider,
}));
vi.mock("open-sse/services/usage/google.js", () => ({
getAntigravityUsage: mocks.getAntigravityUsage,
}));
vi.mock("@/sse/utils/logger.js", () => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn() }));
const { getAntigravityQuotaCache, handleAntigravityQuotaError, refreshAntigravityQuota, clearAntigravityStrikes } = await import("@/sse/services/antigravityQuota.js");
const { getProviderCredentials } = await import("@/sse/services/auth.js");
const MODEL = "claude-opus-4-6-thinking";
const FUTURE_RESET = "2026-09-01T00:00:00.000Z";
beforeEach(() => {
vi.clearAllMocks();
getAntigravityQuotaCache().clear();
mocks.resolveConnectionProxyConfig.mockResolvedValue({});
mocks.getSettings.mockResolvedValue({});
});
describe("Antigravity quota-aware routing", () => {
it("records exhausted upstream quota after 429 and returns its exact reset time", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
mocks.getAntigravityUsage.mockResolvedValue({ quotas: {
[MODEL]: { remainingPercentage: 0, resetAt: FUTURE_RESET },
} });
try {
await expect(handleAntigravityQuotaError("ag-a", 429, MODEL, "token", {}))
.resolves.toBe(Date.parse(FUTURE_RESET));
expect(getAntigravityQuotaCache().get("ag-a")[MODEL]).toEqual({
remainingPercentage: 0,
resetAt: FUTURE_RESET,
});
} finally {
vi.useRealTimers();
}
});
it("skips exhausted account/model and selects the next account", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
mocks.getProviderConnections.mockResolvedValue([
{ id: "ag-a", email: "a@example.com", isActive: true },
{ id: "ag-b", email: "b@example.com", isActive: true },
]);
getAntigravityQuotaCache().set("ag-a", {
[MODEL]: { remainingPercentage: 0, resetAt: FUTURE_RESET },
});
try {
await expect(getProviderCredentials("antigravity", null, MODEL)).resolves.toMatchObject({
connectionId: "ag-b",
connectionName: "b@example.com",
});
} finally {
vi.useRealTimers();
}
});
it("reports retry time when every account is cache-blocked", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
mocks.getProviderConnections.mockResolvedValue([{ id: "ag-a", email: "a@example.com", isActive: true }]);
getAntigravityQuotaCache().set("ag-a", {
[MODEL]: { remainingPercentage: 0, resetAt: FUTURE_RESET },
});
try {
await expect(getProviderCredentials("antigravity", null, MODEL)).resolves.toMatchObject({
allRateLimited: true,
retryAfter: FUTURE_RESET,
});
} finally {
vi.useRealTimers();
}
});
it("lets account back into rotation once reset time has passed", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-09-01T00:00:01.000Z"));
mocks.getProviderConnections.mockResolvedValue([{ id: "ag-a", email: "a@example.com", isActive: true }]);
getAntigravityQuotaCache().set("ag-a", {
[MODEL]: { remainingPercentage: 0, resetAt: FUTURE_RESET },
});
try {
await expect(getProviderCredentials("antigravity", null, MODEL)).resolves.toMatchObject({
connectionId: "ag-a",
connectionName: "a@example.com",
});
} finally {
vi.useRealTimers();
}
});
it("coalesces concurrent quota refreshes for one account", async () => {
let resolveUsage;
mocks.getAntigravityUsage.mockReturnValue(new Promise(resolve => { resolveUsage = resolve; }));
const first = refreshAntigravityQuota("ag-concurrent", "token", {});
const second = refreshAntigravityQuota("ag-concurrent", "token", {});
resolveUsage({ quotas: { [MODEL]: { remainingPercentage: 0, resetAt: FUTURE_RESET } } });
await expect(Promise.all([first, second])).resolves.toEqual([
{ [MODEL]: { remainingPercentage: 0, resetAt: FUTURE_RESET } },
{ [MODEL]: { remainingPercentage: 0, resetAt: FUTURE_RESET } },
]);
expect(mocks.getAntigravityUsage).toHaveBeenCalledTimes(1);
});
it("preserves strict proxy policy for usage refresh", async () => {
mocks.resolveConnectionProxyConfig.mockResolvedValue({ strictProxy: true });
mocks.getAntigravityUsage.mockResolvedValue({ quotas: {} });
await refreshAntigravityQuota("ag-strict-proxy", "token", {});
expect(mocks.getAntigravityUsage).toHaveBeenCalledWith("token", {}, expect.objectContaining({
strictProxy: true,
}));
});
it("keeps known cache when quota endpoint returns an error payload", async () => {
const cached = { [MODEL]: { remainingPercentage: 0, resetAt: FUTURE_RESET } };
getAntigravityQuotaCache().set("ag-error-response", cached);
mocks.getAntigravityUsage.mockResolvedValue({ message: "Unauthorized", quotas: {} });
await expect(refreshAntigravityQuota("ag-error-response", "token", {})).resolves.toBeNull();
expect(getAntigravityQuotaCache().get("ag-error-response")).toBe(cached);
});
it("throttles failed refresh attempts for 30 seconds", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
mocks.getAntigravityUsage.mockRejectedValue(new Error("usage unavailable"));
try {
await refreshAntigravityQuota("ag-failed-refresh", "token", {});
await refreshAntigravityQuota("ag-failed-refresh", "token", {});
expect(mocks.getAntigravityUsage).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(30_000);
await refreshAntigravityQuota("ag-failed-refresh", "token", {});
expect(mocks.getAntigravityUsage).toHaveBeenCalledTimes(2);
} finally {
vi.useRealTimers();
}
});
it("strike-breaks after 3 optimistic 429s within 60s and cache-blocks 15 minutes", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
// Quota API lies: reports 90% remaining while generation keeps 429ing.
mocks.getAntigravityUsage.mockResolvedValue({ quotas: {
[MODEL]: { remainingPercentage: 90, resetAt: FUTURE_RESET },
} });
try {
const first = await handleAntigravityQuotaError("ag-strike", 429, MODEL, "token", {});
expect(first).toBeNull();
const second = await handleAntigravityQuotaError("ag-strike", 429, MODEL, "token", {});
expect(second).toBeNull();
const third = await handleAntigravityQuotaError("ag-strike", 429, MODEL, "token", {});
expect(third).toBe(Date.parse("2026-08-26T00:15:00.000Z"));
} finally {
vi.useRealTimers();
}
});
it("resets the strike counter when strikes fall outside the 60s window", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
mocks.getAntigravityUsage.mockResolvedValue({ quotas: {
[MODEL]: { remainingPercentage: 90, resetAt: FUTURE_RESET },
} });
try {
await handleAntigravityQuotaError("ag-window", 429, MODEL, "token", {});
await handleAntigravityQuotaError("ag-window", 429, MODEL, "token", {});
await vi.advanceTimersByTimeAsync(61_000);
const result = await handleAntigravityQuotaError("ag-window", 429, MODEL, "token", {});
expect(result).toBeNull(); // window lapsed — counter restarted at 1
} finally {
vi.useRealTimers();
}
});
it("strike-breaks when the quota API is unavailable (null reading) too", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
// Quota endpoint failing/forbidden => quota unknown. Strikes must still count.
mocks.getAntigravityUsage.mockResolvedValue({ message: "forbidden", quotas: {} });
try {
await handleAntigravityQuotaError("ag-null", 429, MODEL, "token", {});
await handleAntigravityQuotaError("ag-null", 429, MODEL, "token", {});
const third = await handleAntigravityQuotaError("ag-null", 429, MODEL, "token", {});
expect(third).toBe(Date.parse("2026-08-26T00:15:00.000Z"));
} finally {
vi.useRealTimers();
}
});
it("persists the block into the shared cache so the next request skips the pair", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
mocks.getAntigravityUsage.mockResolvedValue({ quotas: {
[MODEL]: { remainingPercentage: 90, resetAt: FUTURE_RESET },
} });
try {
await handleAntigravityQuotaError("ag-persist", 429, MODEL, "token", {});
await handleAntigravityQuotaError("ag-persist", 429, MODEL, "token", {});
await handleAntigravityQuotaError("ag-persist", 429, MODEL, "token", {});
// The synthesized entry must be visible to the auth pre-filter reading
// the shared cache — and must survive an optimistic upstream refresh.
const cached = getAntigravityQuotaCache().get("ag-persist")?.[MODEL];
expect(cached).toMatchObject({ remainingPercentage: 0 });
expect(Date.parse(cached.resetAt)).toBe(Date.parse("2026-08-26T00:15:00.000Z"));
await refreshAntigravityQuota("ag-persist", "token", {});
expect(getAntigravityQuotaCache().get("ag-persist")?.[MODEL]).toMatchObject({
remainingPercentage: 0,
});
} finally {
vi.useRealTimers();
}
});
it("clears strike state and the synthesized block after a successful request", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
mocks.getAntigravityUsage.mockResolvedValue({ quotas: {
[MODEL]: { remainingPercentage: 90, resetAt: FUTURE_RESET },
} });
try {
await handleAntigravityQuotaError("ag-clear", 429, MODEL, "token", {});
await handleAntigravityQuotaError("ag-clear", 429, MODEL, "token", {});
await handleAntigravityQuotaError("ag-clear", 429, MODEL, "token", {});
expect(getAntigravityQuotaCache().get("ag-clear")?.[MODEL]?.remainingPercentage).toBe(0);
clearAntigravityStrikes("ag-clear", MODEL);
// Synthesized entry gone — pair selectable again immediately.
expect(getAntigravityQuotaCache().get("ag-clear")?.[MODEL]).toBeUndefined();
// Two more 429s do NOT inherit earlier strikes: no block on the third-in-episode.
await handleAntigravityQuotaError("ag-clear", 429, MODEL, "token", {});
await expect(handleAntigravityQuotaError("ag-clear", 429, MODEL, "token", {})).resolves.toBeNull();
} finally {
vi.useRealTimers();
}
});
it("anchors the window at the first strike: 3 strikes spread over 90s do not trip", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-26T00:00:00.000Z"));
mocks.getAntigravityUsage.mockResolvedValue({ quotas: {
[MODEL]: { remainingPercentage: 90, resetAt: FUTURE_RESET },
} });
try {
await handleAntigravityQuotaError("ag-anchor", 429, MODEL, "token", {}); // t=0
await vi.advanceTimersByTimeAsync(45_000);
await handleAntigravityQuotaError("ag-anchor", 429, MODEL, "token", {}); // t=45s
await vi.advanceTimersByTimeAsync(45_000);
// t=90s: within 60s of strike #2 but outside 60s of strike #1 => new window
const result = await handleAntigravityQuotaError("ag-anchor", 429, MODEL, "token", {});
expect(result).toBeNull();
} finally {
vi.useRealTimers();
}
});
it("keeps the optimistic path null without touching the quota cache", async () => {
mocks.getAntigravityUsage.mockResolvedValue({ quotas: {
[MODEL]: { remainingPercentage: 90, resetAt: FUTURE_RESET },
} });
await expect(handleAntigravityQuotaError("ag-optimistic", 429, MODEL, "token", {}))
.resolves.toBeNull();
// Optimistic reading must NOT poison the shared cache (auth pre-filter
// treats cached 0% as exhausted).
expect(getAntigravityQuotaCache().get("ag-optimistic")?.[MODEL]?.remainingPercentage).toBe(90);
});
});

View File

@@ -69,13 +69,13 @@ describe("antigravity computeRetryDelay hook (D3)", () => {
it("registry uses the daily IDE cloudcode host and user agent", () => {
expect(antigravity.transport.baseUrls).toEqual(["https://daily-cloudcode-pa.googleapis.com"]);
expect(antigravity.transport.headers["User-Agent"]).toBe("antigravity/ide/2.1.1 darwin/arm64");
expect(antigravity.transport.headers["User-Agent"]).toBe("antigravity/ide/2.11.0 darwin/arm64");
});
it("buildHeaders matches official IDE stream headers", () => {
ag._lastSessionId = "sess-123";
const h = ag.buildHeaders({ accessToken: "tok" }, true);
expect(h["User-Agent"]).toBe("antigravity/ide/2.1.1 darwin/arm64");
expect(h["User-Agent"]).toBe("antigravity/ide/2.11.0 darwin/arm64");
expect(h["Content-Type"]).toBe("application/json");
expect(h["Authorization"]).toBe("Bearer tok");
expect(h).not.toHaveProperty("X-Machine-Session-Id");

View File

@@ -23,7 +23,7 @@ describe("Antigravity usage headers", () => {
expect(proxyAwareFetch).toHaveBeenCalledTimes(2);
for (const [, options] of proxyAwareFetch.mock.calls) {
expect(options.headers["User-Agent"]).toBe("antigravity/ide/2.1.1 darwin/arm64");
expect(options.headers["User-Agent"]).toBe("antigravity/ide/2.11.0 darwin/arm64");
expect(options.headers).not.toHaveProperty("x-request-source");
}
});

View File

@@ -1,7 +1,7 @@
import { describe, it, expect } from "vitest";
import { canonicalizeUsage, extractUsage, mergeUsage } from "../../open-sse/utils/usageTracking.js";
import { calculateCostFromTokens } from "../../open-sse/providers/pricing.js";
import { toOpenAIUsage } from "../../open-sse/translator/concerns/usage.js";
import { buildUsage, toOpenAIUsage } from "../../open-sse/translator/concerns/usage.js";
// Canonical convention (single source of truth for storage + cost):
// prompt_tokens = total input INCLUDING cache read + cache creation
@@ -49,6 +49,18 @@ describe("canonicalizeUsage", () => {
expect(out.reasoning_tokens).toBe(40);
});
it("reads cached_tokens from the nested buildUsage() shape", () => {
// buildUsage() only emits cache reads under prompt_tokens_details. The
// Responses translator overwrites state.usage with that shape on
// response.completed, so a top-level-only read silently drops the cache
// count for every Responses provider (codex, grok-cli, ...).
const out = canonicalizeUsage(
buildUsage({ promptTokens: 330, completionTokens: 50, totalTokens: 380, cachedTokens: 200 })
);
expect(out.prompt_tokens).toBe(330);
expect(out.cached_tokens).toBe(200);
});
it("handles no-cache usage", () => {
const out = canonicalizeUsage({ prompt_tokens: 100, completion_tokens: 50 });
expect(out.prompt_tokens).toBe(100);

View File

@@ -32,6 +32,13 @@ describe("getCapabilitiesForModel", () => {
}
});
it("reports Claude Fable 5.1 as a permanent adaptive-thinking model", () => {
expect(getCapabilitiesForModel("claude", "claude-fable-5-1")).toMatchObject({
...claudeSonnet5Expected,
thinkingCanDisable: false,
});
});
it("reports Kiro Claude Opus 4.8 as a 1M context model", () => {
expect(getCapabilitiesForModel("kiro", "claude-opus-4.8").contextWindow).toBe(1000000);
expect(getCapabilitiesForModel("kiro", "anthropic/claude-opus-4.8").contextWindow).toBe(1000000);
@@ -55,4 +62,15 @@ describe("getCapabilitiesForModel", () => {
expect(getCapabilitiesForModel("kiro", "gpt-5.6-luna-agentic")).toMatchObject(kiroGpt56Expected);
expect(getCapabilitiesForModel("kiro", "gpt-5.6-sol-thinking-agentic")).toMatchObject(kiroGpt56Expected);
});
it("reports Codex GPT 6.0 Astra as a vision and thinking capable model", () => {
expect(getCapabilitiesForModel("codex", "gpt-6-astra")).toMatchObject({
vision: true,
reasoning: true,
search: true,
thinkingFormat: "openai",
contextWindow: 272000,
maxOutput: 128000,
});
});
});

View File

@@ -3,12 +3,18 @@
*
* Tests cover:
* - cloakClaudeTools() - tool renaming and forced tool_choice suffixing
* - decloakStreamChunk() - restoring tool names in streamed Claude SSE events
*/
import { describe, it, expect } from "vitest";
import { cloakClaudeTools } from "../../open-sse/utils/claudeCloaking.js";
import { applyCloaking, cloakClaudeTools, decloakStreamChunk } from "../../open-sse/utils/claudeCloaking.js";
import { CLAUDE_TOOL_SUFFIX } from "../../open-sse/config/appConstants.js";
it("advertises a Claude Code version accepted by Fable 5.1", () => {
const body = applyCloaking({ messages: [] }, "sk-ant-oat-test", "session-id");
expect(body.system[0].text).toMatch(/^x-anthropic-billing-header: cc_version=2.1.258\./);
});
describe("cloakClaudeTools", () => {
const baseBody = {
tools: [{ name: "todo_write", description: "write todos", input_schema: { type: "object", properties: {} } }],
@@ -74,3 +80,44 @@ describe("cloakClaudeTools", () => {
expect(toolNameMap).toBeNull();
});
});
describe("decloakStreamChunk", () => {
// Cloaked exactly as cloakClaudeTools() does on the request side
const toolNameMap = new Map([["run_code" + CLAUDE_TOOL_SUFFIX, "run_code"]]);
const toolUseStart = (name) => ({
type: "content_block_start",
index: 1,
content_block: { type: "tool_use", id: "toolu_01abc", name, input: {} }
});
it("restores the original name on a tool_use content_block_start", () => {
const out = decloakStreamChunk(toolUseStart("run_code" + CLAUDE_TOOL_SUFFIX), toolNameMap);
expect(out.content_block.name).toBe("run_code");
});
it("does not mutate the input chunk", () => {
const chunk = toolUseStart("run_code" + CLAUDE_TOOL_SUFFIX);
decloakStreamChunk(chunk, toolNameMap);
expect(chunk.content_block.name).toBe("run_code" + CLAUDE_TOOL_SUFFIX);
});
it("passes through names the map does not know (e.g. decoy tools)", () => {
const chunk = toolUseStart("Bash");
expect(decloakStreamChunk(chunk, toolNameMap)).toBe(chunk);
});
it("passes through non-tool_use events unchanged", () => {
const textStart = { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } };
expect(decloakStreamChunk(textStart, toolNameMap)).toBe(textStart);
const delta = { type: "content_block_delta", index: 1, delta: { type: "input_json_delta", partial_json: "{}" } };
expect(decloakStreamChunk(delta, toolNameMap)).toBe(delta);
});
it("tolerates null chunks and missing maps (stream flush path)", () => {
expect(decloakStreamChunk(null, toolNameMap)).toBeNull();
expect(decloakStreamChunk(toolUseStart("run_code" + CLAUDE_TOOL_SUFFIX), null).content_block.name).toBe("run_code" + CLAUDE_TOOL_SUFFIX);
expect(decloakStreamChunk(toolUseStart("run_code" + CLAUDE_TOOL_SUFFIX), new Map()).content_block.name).toBe("run_code" + CLAUDE_TOOL_SUFFIX);
});
});

View File

@@ -0,0 +1,88 @@
// A combo that mixes providers leaks foreign block shapes into the Claude history.
// Anthropic validates server_tool_use ids against ^srvtoolu_[a-zA-Z0-9_]+$ and 400s
// the whole request when a provider (e.g. z.ai/glm) emits OpenAI-style call_ ids.
import { describe, it, expect } from "vitest";
import { normalizeClaudePassthrough } from "../../open-sse/translator/formats/claude.js";
const glmServerToolUse = () => ({
role: "assistant",
content: [
{ type: "text", text: "searching" },
{ type: "server_tool_use", id: "call_50b82aba1b754d82a4408a53", name: "analyze_image", input: {} },
],
});
describe("normalizeClaudePassthrough — foreign server_tool_use ids", () => {
it("drops a server_tool_use block whose id is not an srvtoolu_ id", () => {
const out = normalizeClaudePassthrough({ messages: [glmServerToolUse()] });
expect(out.messages[0].content).toEqual([{ type: "text", text: "searching" }]);
});
it("drops the paired tool_result so no orphan reference is left behind", () => {
const out = normalizeClaudePassthrough({
messages: [
glmServerToolUse(),
{
role: "user",
content: [
{ type: "tool_result", tool_use_id: "call_50b82aba1b754d82a4408a53", content: "boom" },
{ type: "text", text: "keep me" },
],
},
],
});
expect(out.messages[1].content).toEqual([{ type: "text", text: "keep me" }]);
});
it("keeps a well-formed Anthropic server_tool_use block", () => {
const block = { type: "server_tool_use", id: "srvtoolu_01EUi6RNgHntbStfCjgLyLzz", name: "web_search", input: {} };
const out = normalizeClaudePassthrough({ messages: [{ role: "assistant", content: [block] }] });
expect(out.messages[0].content).toEqual([block]);
});
it("keeps regular tool_use blocks, whatever their id looks like", () => {
const block = { type: "tool_use", id: "call_942248714fef4a9abb8e8eff", name: "Bash", input: { command: "ls" } };
const out = normalizeClaudePassthrough({ messages: [{ role: "assistant", content: [block] }] });
expect(out.messages[0].content).toEqual([block]);
});
it("drops a message whose blocks were all stripped instead of padding it with empty text", () => {
const out = normalizeClaudePassthrough({
messages: [
{ role: "user", content: [{ type: "text", text: "hi" }] },
{ role: "assistant", content: [{ type: "server_tool_use", id: "call_x", name: "analyze_image", input: {} }] },
{ role: "user", content: [{ type: "text", text: "bye" }] },
],
});
expect(out.messages).toHaveLength(2);
expect(out.messages.map(m => m.role)).toEqual(["user", "user"]);
});
it("strips empty text blocks a client put in the history (Anthropic 400s them)", () => {
const out = normalizeClaudePassthrough({
messages: [{ role: "assistant", content: [{ type: "text", text: "real" }, { type: "text", text: "" }] }],
});
expect(out.messages[0].content).toEqual([{ type: "text", text: "real" }]);
});
it("drops a message whose content is a single empty text block", () => {
const out = normalizeClaudePassthrough({
messages: [
{ role: "user", content: [{ type: "text", text: "hi" }] },
{ role: "assistant", content: [{ type: "text", text: "" }] },
],
});
expect(out.messages).toHaveLength(1);
});
it("drops a message whose string content is empty", () => {
const out = normalizeClaudePassthrough({
messages: [
{ role: "user", content: "hello" },
{ role: "assistant", content: "" },
],
});
expect(out.messages).toHaveLength(1);
expect(out.messages[0].content).toBe("hello");
});
});

View File

@@ -29,6 +29,7 @@ describe("DefaultExecutor.buildHeaders() — claude provider", () => {
headers["Anthropic-Version"] === "2023-06-01" ||
headers["anthropic-version"] === "2023-06-01";
expect(hasVersion).toBe(true);
expect(headers["User-Agent"]).toBe("claude-cli/2.1.258 (external, sdk-cli)");
});
it("includes heavy-agent beta flags for claude-opus-5", () => {
@@ -186,6 +187,46 @@ describe("DefaultExecutor.buildHeaders() — anthropic-compatible stripping", ()
headers["Anthropic-Version"] || headers["anthropic-version"];
expect(hasVersion).toBeDefined();
});
// A node fronting Anthropic (rotating multi-account proxy, corporate gateway)
// needs the same beta flags the `claude` provider sends. Without
// context-management-2025-06-27 upstream answers HTTP 400
// "context_management: Extra inputs are not permitted" and the combo falls
// through to the next model without anyone noticing.
it("sends context-management beta for a Claude model on a custom host", () => {
const executor = new DefaultExecutor("anthropic-compatible-custom");
const headers = executor.buildHeaders(
{
apiKey: "key",
providerSpecificData: { baseUrl: "https://myproxy.example.com/v1" },
},
true,
undefined,
"claude-opus-5"
);
const betaFlags = (headers["Anthropic-Beta"] || headers["anthropic-beta"] || "")
.split(",").map(s => s.trim());
expect(betaFlags).toContain("context-management-2025-06-27");
// The first-party identity flag is still stripped for a non-Anthropic host.
expect(betaFlags).not.toContain("claude-code-20250219");
});
it("gates the beta flags on the model id, not the provider prefix", () => {
const executor = new DefaultExecutor("anthropic-compatible-custom");
const headers = executor.buildHeaders(
{
apiKey: "key",
providerSpecificData: { baseUrl: "https://myproxy.example.com/v1" },
},
true,
undefined,
"kimi-k3"
);
const betaVal = headers["Anthropic-Beta"] || headers["anthropic-beta"] || "";
expect(betaVal).not.toContain("context-management-2025-06-27");
});
});
// ─── proxyFetch anthropicFetch routing ────────────────────────────────────────

View File

@@ -0,0 +1,76 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { getModelsByProviderId, getModelType, isValidModel } from "../../open-sse/config/providerModels.js";
import { getModelInfoCore } from "../../open-sse/services/model.js";
import { handleImageGenerationCore } from "../../open-sse/handlers/imageGenerationCore.js";
const models = ["gpt-5.6-sol", "gpt-5.6-luna", "gpt-5.6-terra"];
afterEach(() => vi.unstubAllGlobals());
describe("Codex GPT-5.6 image models", () => {
it.each(models)("exposes %s-image as an image model while retaining its chat entry", (model) => {
const catalog = getModelsByProviderId("codex");
expect(catalog.filter((entry) => entry.id === `${model}-image`)).toHaveLength(1);
expect(catalog.find((entry) => entry.id === `${model}-image`)).toMatchObject({
kind: "image",
capabilities: ["text2img", "edit"],
params: ["size", "quality", "background", "image_detail", "output_format"],
});
expect(isValidModel("cx", `${model}-image`)).toBe(true);
expect(getModelType("cx", `${model}-image`)).toBe("image");
expect(catalog.find((entry) => entry.id === model)).toBeDefined();
expect(getModelType("cx", model)).not.toBe("image");
});
it.each(models)("routes %s-image edits and streams image events", async (model) => {
const events = [
["response.image_generation_call.partial_image", { partial_image_b64: "cGFydGlhbA==", partial_image_index: 0 }],
["response.output_item.done", { item: { type: "image_generation_call", result: "ZmluYWw=" } }],
];
const fetchMock = vi.fn().mockResolvedValue(new Response(
events.map(([event, data]) => `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`).join(""),
{ headers: { "Content-Type": "text/event-stream" } },
));
vi.stubGlobal("fetch", fetchMock);
const onRequestSuccess = vi.fn();
const modelInfo = await getModelInfoCore(`cx/${model}-image`);
expect(modelInfo).toEqual({ provider: "codex", model: `${model}-image` });
expect(await getModelInfoCore(`codex/${model}-image`)).toEqual(modelInfo);
const result = await handleImageGenerationCore({
modelInfo,
body: {
prompt: "Make the square blue",
image: "data:image/png;base64,cmVmZXJlbmNl",
image_detail: "low",
size: "1024x1024",
quality: "high",
background: "transparent",
output_format: "WEBP",
},
credentials: { accessToken: "test-token" },
streamToClient: true,
onRequestSuccess,
});
expect(result.success).toBe(true);
expect(result.response.status).toBe(200);
expect(result.response.headers.get("content-type")).toBe("text/event-stream");
const [url, options] = fetchMock.mock.calls[0];
expect(url).toBe("https://chatgpt.com/backend-api/codex/responses");
const upstreamBody = JSON.parse(options.body);
expect(upstreamBody.model).toBe(model);
expect(upstreamBody.tools).toEqual([{
type: "image_generation", output_format: "webp", size: "1024x1024",
quality: "high", background: "transparent",
}]);
expect(upstreamBody.input[0].content).toContainEqual({
type: "input_image", image_url: "data:image/png;base64,cmVmZXJlbmNl", detail: "low",
});
const stream = await result.response.text();
expect(stream).toContain('event: partial_image\ndata: {"b64_json":"cGFydGlhbA==","index":0}');
expect(stream).toContain("event: done\n");
expect(stream).toContain('"data":[{"b64_json":"ZmluYWw="}]');
expect(onRequestSuccess).toHaveBeenCalledTimes(1);
});
});

View File

@@ -91,6 +91,17 @@ describe("Codex reset credits", () => {
});
});
it("surfaces structured upstream errors as readable messages", async () => {
mocks.proxyAwareFetch.mockResolvedValue({
ok: false,
status: 403,
json: async () => ({ error: { message: "Reset credits are unavailable for this account" } }),
});
const { getCodexRateLimitResetCredits } = await import("../../open-sse/services/usage/codex.js");
await expect(getCodexRateLimitResetCredits("token")).rejects.toThrow("Reset credits are unavailable for this account");
});
it("GET refreshes OAuth credentials before returning reset credit details", async () => {
const connection = {
id: "conn_1",

View File

@@ -0,0 +1,37 @@
import { describe, it, expect } from "vitest";
import { parseQuotaData } from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";
describe("Codex Spark Quota Tracking (#3431)", () => {
it("correctly normalizes spark_session and spark_weekly quotas with display labels", () => {
const mockCodexUsage = {
plan: "team",
quotas: {
session: { used: 20, total: 100, remaining: 80, resetAt: "2026-08-22T05:00:00.000Z" },
weekly: { used: 40, total: 100, remaining: 60, resetAt: "2026-08-28T05:00:00.000Z" },
review_session: { used: 0, total: 100, remaining: 100, resetAt: "2026-08-22T05:00:00.000Z" },
spark_session: { used: 12, total: 100, remaining: 88, resetAt: "2026-08-22T05:00:00.000Z" },
spark_weekly: { used: 25, total: 100, remaining: 75, resetAt: "2026-08-28T05:00:00.000Z" },
},
};
const parsed = parseQuotaData("codex", mockCodexUsage);
const sparkSession = parsed.find((q) => q.name === "Spark (5h)");
const sparkWeekly = parsed.find((q) => q.name === "Spark (Weekly)");
const session = parsed.find((q) => q.name === "5h");
const weekly = parsed.find((q) => q.name === "Weekly");
expect(sparkSession).toBeDefined();
expect(sparkSession.used).toBe(12);
expect(sparkSession.remaining).toBe(88);
expect(sparkWeekly).toBeDefined();
expect(sparkWeekly.used).toBe(25);
expect(sparkWeekly.remaining).toBe(75);
expect(session).toBeDefined();
expect(session.used).toBe(20);
expect(weekly).toBeDefined();
expect(weekly.used).toBe(40);
});
});

View File

@@ -1,126 +1,192 @@
/**
* Unit tests for the CommandCode executor early-error peek.
*
* The upstream emits AI SDK v5 NDJSON over an HTTP 200 stream, so a terminal
* `{"type":"error"}` event is invisible to the normal `response.ok` success
* check. `peekForUpstreamError` reads the first events before committing the
* response: an error event → non-ok Response (fallback can kick in); otherwise
* the buffered bytes are re-emitted and streaming proceeds as before.
*/
import { describe, it, expect } from "vitest";
import { peekForUpstreamError } from "../../open-sse/executors/commandcode.js";
import { describe, it, expect, vi } from "vitest";
import {
parseCommandCodeError,
inspectAndWrapCommandCodeResponse,
CommandCodeExecutor,
} from "../../open-sse/executors/commandcode.js";
import { handleComboChat } from "../../open-sse/services/combo.js";
const encoder = new TextEncoder();
function ndjsonResponse(lines) {
const body = new ReadableStream({
start(controller) {
for (const line of lines) controller.enqueue(encoder.encode(line + "\n"));
controller.close();
},
});
return new Response(body, {
status: 200,
headers: { "content-type": "text/event-stream" },
});
function createNdjsonStream(lines) {
const encoder = new TextEncoder();
return new ReadableStream({
start(controller) {
for (const line of lines) {
controller.enqueue(encoder.encode(typeof line === "string" ? line : JSON.stringify(line) + "\n"));
}
controller.close();
},
});
}
describe("commandcode executor — early-error peek", () => {
it("returns 502 when the first meaningful event is an error", async () => {
const res = await peekForUpstreamError(
ndjsonResponse([
'{"type":"error","error":{"type":"server_error","message":"Network connection lost."}}',
]),
"model",
);
expect(res.status).toBe(502);
const body = await res.json();
expect(body.error.message).toBe("Network connection lost.");
expect(body.error.type).toBe("server_error");
});
describe("parseCommandCodeError", () => {
it("parses user exact error payload with statusCode 503 and isRetryable", () => {
const event = {
type: "error",
error: {
type: "server_error",
message: "Service temporarily unavailable. Please try again shortly.",
statusCode: 503,
isRetryable: true,
},
};
const parsed = parseCommandCodeError(event);
expect(parsed.statusCode).toBe(503);
expect(parsed.message).toBe("Service temporarily unavailable. Please try again shortly.");
expect(parsed.type).toBe("server_error");
});
it("detects an error event even when metadata events arrive first", async () => {
const res = await peekForUpstreamError(
ndjsonResponse([
'{"type":"start"}',
'{"type":"start-step"}',
'{"type":"error","error":{"type":"server_error","message":"Network connection lost."}}',
]),
"model",
);
expect(res.status).toBe(502);
const body = await res.json();
expect(body.error.message).toContain("Network connection lost");
});
it("handles string error message", () => {
const event = {
type: "error",
message: "Rate limit exceeded. Please wait 30s.",
};
const parsed = parseCommandCodeError(event);
expect(parsed.statusCode).toBe(429);
expect(parsed.message).toBe("Rate limit exceeded. Please wait 30s.");
});
it("commits and streams normally when the first meaningful event is content", async () => {
const res = await peekForUpstreamError(
ndjsonResponse([
'{"type":"start"}',
'{"type":"text-delta","text":"hi there"}',
'{"type":"finish"}',
]),
"model",
);
expect(res.status).toBe(200);
const text = await res.text();
expect(text).toContain('"content":"hi there"');
expect(text).not.toContain("[CommandCode error:");
});
it("commits when the stream ends without any event", async () => {
const res = await peekForUpstreamError(ndjsonResponse([]), "model");
expect(res.status).toBe(200);
await res.body.cancel();
});
it("commits (does not hang) when no event arrives before the peek timeout", async () => {
const stalled = new Response(new ReadableStream({ start() {} }), {
status: 200,
headers: { "content-type": "text/event-stream" },
});
const res = await peekForUpstreamError(stalled, "model", { timeoutMs: 50 });
expect(res.status).toBe(200);
await res.body.cancel();
});
it("does not hang when the request signal aborts during the peek", async () => {
const controller = new AbortController();
const stalled = new Response(new ReadableStream({ start() {} }), {
status: 200,
headers: { "content-type": "text/event-stream" },
});
setTimeout(() => controller.abort(new Error("client gone")), 10);
const res = await peekForUpstreamError(stalled, "model", {
signal: controller.signal,
timeoutMs: 2000,
});
expect(res.status).toBe(200);
await res.body.cancel();
});
it("re-emits raw bytes so a multi-byte char split across the peek boundary survives", async () => {
// chunk1 ends mid-é (0xC3); the peek commits on the first complete line
// (text-delta "hi") while the decoder still holds 0xC3. Re-emission must
// use RAW bytes — re-encoding decoded text would replace 0xC3 with U+FFFD.
const first = Buffer.from(
'{"type":"text-delta","text":"hi"}\n{"type":"text-delta","text":"caf',
);
const chunk1 = new Uint8Array([...first, 0xc3]);
const rest = new Uint8Array([0xa9, 0x22, 0x7d, 0x0a]); // é"}\n
const body = new ReadableStream({
start(c) {
c.enqueue(chunk1);
c.enqueue(rest);
c.close();
},
});
const res = await peekForUpstreamError(
new Response(body, { status: 200 }),
"m",
);
const text = await res.text();
expect(text).toContain("café");
expect(text).not.toContain("\uFFFD");
});
it("handles plain error string in error property", () => {
const event = {
type: "error",
error: "Unauthorized access",
};
const parsed = parseCommandCodeError(event);
expect(parsed.statusCode).toBe(401);
expect(parsed.message).toBe("Unauthorized access");
});
});
describe("inspectAndWrapCommandCodeResponse", () => {
it("converts initial upstream 200 with error event to 503 Response", async () => {
const ndjsonBody = createNdjsonStream([
JSON.stringify({
type: "error",
error: {
type: "server_error",
message: "Service temporarily unavailable. Please try again shortly.",
statusCode: 503,
isRetryable: true,
},
}) + "\n",
]);
const fakeResponse = new Response(ndjsonBody, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
const result = await inspectAndWrapCommandCodeResponse(fakeResponse, "poolside/laguna-s-2.1-free");
expect(result.ok).toBe(false);
expect(result.status).toBe(503);
const body = await result.json();
expect(body.error.message).toContain("Service temporarily unavailable");
expect(body.error.code).toBe(503);
});
it("converts initial upstream 200 with start/start-step followed by error to 503 Response", async () => {
const ndjsonBody = createNdjsonStream([
JSON.stringify({ type: "start" }) + "\n",
JSON.stringify({ type: "start-step" }) + "\n",
JSON.stringify({
type: "error",
error: {
type: "server_error",
message: "Service temporarily unavailable. Please try again shortly.",
statusCode: 503,
isRetryable: true,
},
}) + "\n",
]);
const fakeResponse = new Response(ndjsonBody, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
const result = await inspectAndWrapCommandCodeResponse(fakeResponse, "poolside/laguna-s-2.1-free");
expect(result.ok).toBe(false);
expect(result.status).toBe(503);
const body = await result.json();
expect(body.error.message).toContain("Service temporarily unavailable");
});
it("streams successful responses when content is emitted", async () => {
const ndjsonBody = createNdjsonStream([
JSON.stringify({ type: "start" }) + "\n",
JSON.stringify({ type: "text-delta", text: "Hello from Laguna" }) + "\n",
JSON.stringify({ type: "finish" }) + "\n",
]);
const fakeResponse = new Response(ndjsonBody, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
const result = await inspectAndWrapCommandCodeResponse(fakeResponse, "poolside/laguna-s-2.1-free");
expect(result.ok).toBe(true);
expect(result.status).toBe(200);
const text = await result.text();
expect(text).toContain("Hello from Laguna");
expect(text).toContain("data: [DONE]");
});
});
describe("CommandCode in Combo Fallback", () => {
it("automatically falls back to next model when commandcode returns 503 error", async () => {
const log = {
info: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
};
const handleSingleModel = vi.fn(async (body, modelStr) => {
if (modelStr === "commandcode/poolside/laguna-s-2.1-free") {
// Simulated failed CommandCode response
return new Response(
JSON.stringify({
error: {
message: "Service temporarily unavailable. Please try again shortly.",
type: "server_error",
code: 503,
},
}),
{ status: 503, headers: { "Content-Type": "application/json" } }
);
}
if (modelStr === "openai/gpt-4o-mini") {
// Fallback model succeeds
return new Response(
JSON.stringify({
id: "chatcmpl-test",
choices: [{ message: { role: "assistant", content: "Fallback success!" } }],
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
return new Response("Not found", { status: 404 });
});
const comboResponse = await handleComboChat({
body: { messages: [{ role: "user", content: "Hello" }] },
models: ["commandcode/poolside/laguna-s-2.1-free", "openai/gpt-4o-mini"],
handleSingleModel,
log,
comboName: "test-combo",
comboStrategy: "fallback",
});
expect(comboResponse.ok).toBe(true);
expect(comboResponse.status).toBe(200);
const data = await comboResponse.json();
expect(data.choices[0].message.content).toBe("Fallback success!");
expect(handleSingleModel).toHaveBeenCalledTimes(2);
expect(handleSingleModel).toHaveBeenNthCalledWith(1, expect.anything(), "commandcode/poolside/laguna-s-2.1-free");
expect(handleSingleModel).toHaveBeenNthCalledWith(2, expect.anything(), "openai/gpt-4o-mini");
});
});

View File

@@ -0,0 +1,61 @@
/**
* SSRF guard on POST /api/cli-tools/cowork-mcp-tools (#3782).
*
* Remote callers must not be able to force server-side fetches to
* internal URLs; local-host use (self-hosted MCP servers) keeps working.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("next/server", () => ({
NextResponse: {
json: (body, init) =>
new Response(JSON.stringify(body), {
status: init?.status ?? 200,
headers: { "content-type": "application/json" },
}),
},
}));
const { POST } = await import(
"../../src/app/api/cli-tools/cowork-mcp-tools/route.js"
);
function remoteRequest(url) {
return new Request("http://gateway.example.com/api/cli-tools/cowork-mcp-tools", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ url }),
});
}
describe("cowork-mcp-tools SSRF guard", () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it("rejects loopback URLs from remote callers without fetching", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch");
const res = await POST(remoteRequest("http://127.0.0.1:18731/internal-admin"));
expect(res.status).toBe(400);
expect(await res.json()).toEqual({ error: "URL not allowed" });
expect(fetchSpy).not.toHaveBeenCalled();
});
it("rejects private-network URLs from remote callers", async () => {
for (const url of ["http://10.0.0.5/mcp", "http://192.168.1.1/mcp", "http://localhost:3000/mcp"]) {
const res = await POST(remoteRequest(url));
expect(res.status, `should reject ${url}`).toBe(400);
}
});
it("still requires a url", async () => {
const res = await POST(
new Request("http://gateway.example.com/api/cli-tools/cowork-mcp-tools", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({}),
})
);
expect(res.status).toBe(400);
});
});

View File

@@ -125,6 +125,25 @@ describe("dashboard guard public LLM API access", () => {
expect(response.body.error).toBe("API key required for remote API access");
});
it("rejects remote /responses rewrite without API key", async () => {
const response = await proxy(request("/responses", { host: "router.example.com" }));
expect(response.status).toBe(401);
expect(response.body.error).toBe("API key required for remote API access");
});
it("allows remote /responses rewrite with a valid API key", async () => {
mocks.validateApiKey.mockResolvedValue(true);
const response = await proxy(request("/responses", {
host: "router.example.com",
authorization: "Bearer sk-valid",
}));
expect(response).toBe(mocks.nextResponse);
expect(mocks.validateApiKey).toHaveBeenCalledWith("sk-valid");
});
it("allows remote codex rewrite with valid API key", async () => {
mocks.validateApiKey.mockResolvedValue(true);

View File

@@ -0,0 +1,82 @@
/**
* Regression: Anthropic rejects a tool that carries BOTH `defer_loading: true`
* and `cache_control`:
*
* [400] Tool 'mcp__x__y' cannot both defer_loading=true cache_control set.
* Tools defer_loading cannot use prompt caching.
*
* 9router anchors the 1h cache breakpoint on the LAST tool of the array with
* no guard. Clients that speak MCP (Claude Code) put deferred tools at the
* tail, so the anchor lands exactly on a tool that cannot be cached and the
* request 400s before combo fallback can try the next hop.
*
* The fix anchors on the last tool that is NOT deferred, so prompt caching is
* kept for the tools that can use it instead of being dropped wholesale.
*
* See: #3567.
*/
import { describe, it, expect } from "vitest";
import { anchorClaudeCache } from "../../open-sse/translator/formats/claude.js";
import { prepareClaudeRequest } from "../../open-sse/translator/formats/claude.js";
const tool = (name, extra = {}) => ({
name,
description: "t",
input_schema: { type: "object", properties: {} },
...extra,
});
describe("defer_loading tools never carry cache_control (#3567)", () => {
it("anchorClaudeCache: anchor moves to the last non-deferred tool", () => {
const body = anchorClaudeCache({
messages: [{ role: "user", content: "hi" }],
tools: [tool("a"), tool("b"), tool("mcp__x__y", { defer_loading: true })],
});
expect(body.tools[2].cache_control).toBeUndefined();
expect(body.tools[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
expect(body.tools[0].cache_control).toBeUndefined();
});
it("anchorClaudeCache: no tool is cached when every tool is deferred", () => {
const body = anchorClaudeCache({
messages: [{ role: "user", content: "hi" }],
tools: [tool("mcp__a", { defer_loading: true }), tool("mcp__b", { defer_loading: true })],
});
expect(body.tools.every(t => t.cache_control === undefined)).toBe(true);
});
it("anchorClaudeCache: strips a cache_control the client put on a deferred tool", () => {
const body = anchorClaudeCache({
messages: [{ role: "user", content: "hi" }],
tools: [tool("mcp__a", { defer_loading: true, cache_control: { type: "ephemeral" } })],
});
expect(body.tools[0].cache_control).toBeUndefined();
});
it("anchorClaudeCache: unchanged behaviour when no tool is deferred", () => {
const body = anchorClaudeCache({
messages: [{ role: "user", content: "hi" }],
tools: [tool("a"), tool("b")],
});
expect(body.tools[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
expect(body.tools[0].cache_control).toBeUndefined();
});
it("prepareClaudeRequest: deferred tail tool does not get the anchor", () => {
const out = prepareClaudeRequest({
model: "claude-sonnet-4.5",
messages: [{ role: "user", content: "hi" }],
tools: [tool("a"), tool("mcp__x__y", { defer_loading: true })],
}, "claude");
expect(out.tools).toHaveLength(2);
expect(out.tools[1].cache_control).toBeUndefined();
expect(out.tools[1].defer_loading).toBe(true);
expect(out.tools[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
});
});

View File

@@ -9,6 +9,7 @@ import { DEFAULT_MAX_TOKENS, DEFAULT_MIN_TOKENS } from "../../open-sse/config/ru
import mimoFree from "../../open-sse/providers/registry/mimo-free.js";
import opencode from "../../open-sse/providers/registry/opencode.js";
import antigravity from "../../open-sse/providers/registry/antigravity.js";
import { OpenCodeExecutor } from "../../open-sse/executors/opencode.js";
describe("compat base URLs / version", () => {
it("OPENAI_COMPAT_BASE", () => {
@@ -46,3 +47,41 @@ describe("antigravity retry (intentional change: 429=6, 503=3)", () => {
expect(antigravity.transport.retry["503"].attempts).toBe(3);
});
});
describe("OpenCode Free endpoint routing", () => {
const MUSE = "muse-spark-1.2-contributor-free";
it("declares the Responses format only on the Muse Spark model", () => {
expect(opencode.transport.format).toBeUndefined();
const muse = opencode.models.find((m) => m.id === MUSE);
expect(muse?.targetFormat).toBe("openai-responses");
const muse13 = opencode.models.find((m) => m.id === "muse-spark-1.3-contributor-free");
expect(muse13?.targetFormat).toBe("openai-responses");
});
it("routes Muse Spark to /responses and every other model to /chat/completions", () => {
const executor = new OpenCodeExecutor();
expect(executor.buildUrl(MUSE)).toBe("https://opencode.ai/zen/v1/responses");
expect(executor.buildUrl(`${MUSE}(xhigh)`)).toBe("https://opencode.ai/zen/v1/responses");
expect(executor.buildUrl("muse-spark-1.3-contributor-free")).toBe("https://opencode.ai/zen/v1/responses");
expect(executor.buildUrl("muse-spark-1.4-contributor-free")).toBe("https://opencode.ai/zen/v1/responses");
expect(executor.buildUrl("muse-spark-2.0-contributor-free(xhigh)")).toBe("https://opencode.ai/zen/v1/responses");
expect(executor.buildUrl("big-pickle")).toBe("https://opencode.ai/zen/v1/chat/completions");
expect(executor.buildUrl("hy3-free")).toBe("https://opencode.ai/zen/v1/chat/completions");
});
it("normalizes Chat token/thinking fields only for the Responses model", () => {
const executor = new OpenCodeExecutor();
const muse = { max_tokens: 4096, reasoning_effort: "high" };
executor.transformRequest(MUSE, muse, true, {});
expect(muse.max_output_tokens).toBe(4096);
expect(muse.max_tokens).toBeUndefined();
expect(muse.reasoning).toEqual({ effort: "high", summary: "auto" });
const chat = { max_tokens: 4096, reasoning_effort: "high" };
executor.transformRequest("big-pickle", chat, true, {});
expect(chat.max_tokens).toBe(4096);
expect(chat.max_output_tokens).toBeUndefined();
expect(chat.reasoning_effort).toBe("high");
});
});

View File

@@ -0,0 +1,74 @@
import { describe, it, expect, vi } from "vitest";
// sever the DB import chain (usageDb -> @/lib/db/*) — not under test
vi.mock("@/lib/usageDb.js", () => ({
saveRequestUsage: vi.fn(),
appendRequestLog: vi.fn(),
saveRequestDetail: vi.fn(),
}));
// and the stream/console-coloring utils that drag in the translator graph
vi.mock("../../open-sse/utils/stream.js", () => ({
COLORS: {},
formatSSE: vi.fn(),
}));
import { extractUsageFromResponse } from "../../open-sse/handlers/chatCore/requestDetail.js";
import { canonicalizeUsage } from "../../open-sse/utils/usageTracking.js";
// The three real-world usage shapes and how extractUsageFromResponse() must
// surface their cache-read count so canonicalizeUsage() produces a correct
// cached_tokens. Regression for non-streaming codex/Responses traffic, where
// cache reads were silently dropped and usage recorded cached_tokens: 0.
describe("extractUsageFromResponse cache surfaces", () => {
it("surfaces OpenAI Responses input_tokens_details.cached_tokens", () => {
// codex / /v1/responses shape: prompt is cache-INCLUSIVE
const out = extractUsageFromResponse({
usage: { input_tokens: 25421, output_tokens: 5, total_tokens: 25426,
input_tokens_details: { cached_tokens: 24320 } },
});
expect(out.cached_tokens).toBe(24320);
expect(out.prompt_tokens).toBe(25421);
expect(out.cache_read_input_tokens).toBeUndefined();
});
it("canonicalizes Responses usage without double-counting the prompt", () => {
const extracted = extractUsageFromResponse({
usage: { input_tokens: 25421, output_tokens: 5,
input_tokens_details: { cached_tokens: 24320 } },
});
const out = canonicalizeUsage(extracted);
// inclusive prompt passes through unchanged; cache reported as subset
expect(out.prompt_tokens).toBe(25421);
expect(out.cached_tokens).toBe(24320);
expect(out.total_tokens).toBe(25426);
expect(out.cache_creation_input_tokens).toBe(0);
});
it("still folds genuine Claude exclusive cache (regression)", () => {
const extracted = extractUsageFromResponse({
usage: { input_tokens: 100, output_tokens: 50,
cache_read_input_tokens: 200, cache_creation_input_tokens: 30 },
});
expect(extracted.cached_tokens).toBeUndefined();
const out = canonicalizeUsage(extracted);
expect(out.prompt_tokens).toBe(330); // 100 + 200 + 30
expect(out.cached_tokens).toBe(200);
expect(out.cache_creation_input_tokens).toBe(30);
});
it("surfaces flat cached_tokens on the OpenAI branch (SSE-to-JSON shape)", () => {
const out = extractUsageFromResponse({
usage: { prompt_tokens: 300, completion_tokens: 10, cached_tokens: 240 },
});
expect(out.cached_tokens).toBe(240);
});
it("keeps nested prompt_tokens_details.cached_tokens working (regression)", () => {
const out = extractUsageFromResponse({
usage: { prompt_tokens: 300, completion_tokens: 10,
prompt_tokens_details: { cached_tokens: 240 } },
});
expect(out.cached_tokens).toBe(240);
expect(canonicalizeUsage(out).cached_tokens).toBe(240);
});
});

View File

@@ -44,7 +44,7 @@ vi.mock("@/sse/utils/logger.js", () => ({
}));
vi.mock("@/shared/utils/ssrfGuard.js", () => ({
assertPublicUrl: vi.fn(),
assertPublicUrlResolved: vi.fn(async () => {}),
}));
import { handleFetch } from "@/sse/handlers/fetch.js";
@@ -85,7 +85,40 @@ describe("web fetch account state", () => {
expect(mocks.clearAccountError).toHaveBeenCalledWith(
"jina-connection",
expect.objectContaining({ connectionName: "Jina Test" }),
"webfetch:jina-reader",
);
expect(mocks.getProviderCredentials).toHaveBeenCalledWith(
"jina-reader",
expect.any(Set),
"webfetch:jina-reader",
);
expect(mocks.markAccountUnavailable).not.toHaveBeenCalled();
});
it("scopes provider failures to web fetch", async () => {
mocks.handleFetchCore.mockResolvedValue({
success: false,
status: 429,
error: "quota exceeded",
});
mocks.markAccountUnavailable.mockResolvedValue({ shouldFallback: false });
const response = await handleFetch(new Request("http://localhost/v1/web/fetch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
provider: "jina-reader",
url: "https://example.com/article",
}),
}));
expect(response.status).toBe(429);
expect(mocks.markAccountUnavailable).toHaveBeenCalledWith(
"jina-connection",
429,
"quota exceeded",
"jina-reader",
"webfetch:jina-reader",
);
});
});

View File

@@ -0,0 +1,36 @@
import { describe, it, expect } from "vitest";
import { getCapabilitiesForModel } from "../../open-sse/providers/capabilities.js";
import antigravityRegistry from "../../open-sse/providers/registry/antigravity.js";
import geminiRegistry from "../../open-sse/providers/registry/gemini.js";
import { MODEL_PRICING } from "../../open-sse/providers/pricing.js";
describe("Gemini 3.8 Flash Support & Config", () => {
it("registers gemini-3.8-flash tiered models in antigravity provider registry", () => {
const agIds = antigravityRegistry.models.map(m => m.id);
expect(agIds).toContain("gemini-3.8-flash-high");
expect(agIds).toContain("gemini-3.8-flash-medium");
expect(agIds).toContain("gemini-3.8-flash-low");
expect(agIds).toContain("gemini-3.8-flash");
});
it("registers gemini-3.8-flash in gemini provider registry", () => {
const geminiIds = geminiRegistry.models.map(m => m.id);
expect(geminiIds).toContain("gemini-3.8-flash");
});
it("resolves capabilities correctly for gemini-3.8 models with official limits", () => {
const caps = getCapabilitiesForModel("antigravity", "gemini-3.8-flash-high");
expect(caps.vision).toBe(true);
expect(caps.reasoning).toBe(true);
expect(caps.thinkingFormat).toBe("gemini-level");
expect(caps.contextWindow).toBe(1048576);
expect(caps.maxOutput).toBe(65536);
});
it("defines pricing matching gemini-3.7-flash baseline", () => {
expect(MODEL_PRICING["gemini-3.8-flash"]).toEqual(MODEL_PRICING["gemini-3.7-flash"]);
expect(MODEL_PRICING["gemini-3.8-flash-high"]).toEqual(MODEL_PRICING["gemini-3.7-flash-high"]);
expect(MODEL_PRICING["gemini-3.8-flash-medium"]).toEqual(MODEL_PRICING["gemini-3.7-flash-medium"]);
expect(MODEL_PRICING["gemini-3.8-flash-low"]).toEqual(MODEL_PRICING["gemini-3.7-flash-low"]);
});
});

View File

@@ -0,0 +1,100 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { createRequire } from "node:module";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { getModelUpstreamId } from "../../open-sse/config/providerModels.js";
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.js";
import { applyThinking, stripThinkingSuffix } from "../../open-sse/translator/concerns/thinkingUnified.js";
import gemini from "../../open-sse/providers/registry/gemini.js";
import { MODEL_PRICING } from "../../open-sse/providers/pricing.js";
import { MITM_TOOLS } from "../../src/shared/constants/cliTools.js";
const require = createRequire(import.meta.url);
const mitmConfig = require("../../src/mitm/config.js");
const here = dirname(fileURLToPath(import.meta.url));
afterEach(() => {
vi.restoreAllMocks();
});
describe("Gemini 3.7 Antigravity tiers", () => {
it.each(["high", "medium", "low"])(
"maps the %s tier to the shared upstream model with matching thinking level",
(tier) => {
const publicModel = `gemini-3.7-flash-${tier}`;
const upstreamModel = getModelUpstreamId("ag", publicModel);
const body = {
model: stripThinkingSuffix(upstreamModel),
request: {
contents: [{ role: "user", parts: [{ text: "hello" }] }],
generationConfig: {},
},
};
applyThinking("antigravity", upstreamModel, body, "antigravity");
const finalBody = new AntigravityExecutor().transformRequest(
publicModel,
body,
true,
{ projectId: "project", connectionId: "connection" }
);
expect(upstreamModel).toBe(`gemini-3.7-flash-tiered(${tier})`);
expect(finalBody.model).toBe("gemini-3.7-flash-tiered");
expect(finalBody.request.generationConfig.thinkingConfig).toEqual({
thinkingLevel: tier,
includeThoughts: true,
});
}
);
});
describe("Gemini 3.7 MITM model extraction", () => {
it.each(["high", "medium", "low"])("extracts the %s thinking tier for gemini-3.7-flash-tiered", (tier) => {
const body = Buffer.from(JSON.stringify({
request: { generationConfig: { thinkingConfig: { thinkingLevel: tier } } },
}));
expect(mitmConfig.extractModel(
"/v1internal/models/gemini-3.7-flash-tiered:streamGenerateContent",
body
)).toBe(`gemini-3.7-flash-${tier}`);
});
it("defaults invalid or missing thinking levels to medium", () => {
const body = Buffer.from(JSON.stringify({
request: { generationConfig: { thinkingConfig: { thinkingLevel: "unknown" } } },
}));
expect(mitmConfig.extractModel(
"/v1internal/models/gemini-3.7-flash-tiered:streamGenerateContent",
body
)).toBe("gemini-3.7-flash-medium");
});
});
describe("Gemini 3.7 MITM tools and catalog", () => {
it("includes gemini-3.7-flash tiers in MITM_TOOLS defaultModels", () => {
const defaultModelIds = MITM_TOOLS.antigravity.defaultModels.map((m) => m.id);
expect(defaultModelIds).toContain("gemini-3.7-flash-high");
expect(defaultModelIds).toContain("gemini-3.7-flash-medium");
expect(defaultModelIds).toContain("gemini-3.7-flash-low");
});
it("exposes the direct Gemini 3.7 API models and pricing", () => {
const ids = gemini.models.map((model) => model.id);
expect(ids).toContain("gemini-3.7-flash");
expect(MODEL_PRICING["gemini-3.7-flash"]).toMatchObject({ input: 1.5, output: 7.5 });
});
it("keeps the standalone CLI Antigravity catalog synchronized", () => {
const source = readFileSync(join(here, "../../cli/src/cli/menus/providers.js"), "utf8");
const agCatalog = source.match(/\n ag: \[([\s\S]*?)\n \],/)?.[1] || "";
expect(agCatalog).toContain("gemini-3.7-flash-high");
expect(agCatalog).toContain("gemini-3.7-flash-medium");
expect(agCatalog).toContain("gemini-3.7-flash-low");
});
});

View File

@@ -0,0 +1,100 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { createRequire } from "node:module";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { getModelUpstreamId } from "../../open-sse/config/providerModels.js";
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.js";
import { applyThinking, stripThinkingSuffix } from "../../open-sse/translator/concerns/thinkingUnified.js";
import gemini from "../../open-sse/providers/registry/gemini.js";
import { MODEL_PRICING } from "../../open-sse/providers/pricing.js";
import { MITM_TOOLS } from "../../src/shared/constants/cliTools.js";
const require = createRequire(import.meta.url);
const mitmConfig = require("../../src/mitm/config.js");
const here = dirname(fileURLToPath(import.meta.url));
afterEach(() => {
vi.restoreAllMocks();
});
describe("Gemini 3.8 Antigravity tiers", () => {
it.each(["high", "medium", "low"])(
"maps the %s tier to the shared upstream model with matching thinking level",
(tier) => {
const publicModel = `gemini-3.8-flash-${tier}`;
const upstreamModel = getModelUpstreamId("ag", publicModel);
const body = {
model: stripThinkingSuffix(upstreamModel),
request: {
contents: [{ role: "user", parts: [{ text: "hello" }] }],
generationConfig: {},
},
};
applyThinking("antigravity", upstreamModel, body, "antigravity");
const finalBody = new AntigravityExecutor().transformRequest(
publicModel,
body,
true,
{ projectId: "project", connectionId: "connection" }
);
expect(upstreamModel).toBe(`gemini-3.8-flash-${tier}(${tier})`);
expect(finalBody.model).toBe(`gemini-3.8-flash-${tier}`);
expect(finalBody.request.generationConfig.thinkingConfig).toEqual({
thinkingLevel: tier,
includeThoughts: true,
});
}
);
});
describe("Gemini 3.8 MITM model extraction", () => {
it.each(["high", "medium", "low"])("extracts the %s thinking tier for gemini-3.8-flash-tiered", (tier) => {
const body = Buffer.from(JSON.stringify({
request: { generationConfig: { thinkingConfig: { thinkingLevel: tier } } },
}));
expect(mitmConfig.extractModel(
"/v1internal/models/gemini-3.8-flash-tiered:streamGenerateContent",
body
)).toBe(`gemini-3.8-flash-${tier}`);
});
it("defaults invalid or missing thinking levels to medium", () => {
const body = Buffer.from(JSON.stringify({
request: { generationConfig: { thinkingConfig: { thinkingLevel: "unknown" } } },
}));
expect(mitmConfig.extractModel(
"/v1internal/models/gemini-3.8-flash-tiered:streamGenerateContent",
body
)).toBe("gemini-3.8-flash-medium");
});
});
describe("Gemini 3.8 MITM tools and catalog", () => {
it("includes gemini-3.8-flash tiers in MITM_TOOLS defaultModels", () => {
const defaultModelIds = MITM_TOOLS.antigravity.defaultModels.map((m) => m.id);
expect(defaultModelIds).toContain("gemini-3.8-flash-high");
expect(defaultModelIds).toContain("gemini-3.8-flash-medium");
expect(defaultModelIds).toContain("gemini-3.8-flash-low");
});
it("exposes the direct Gemini 3.8 API models and pricing", () => {
const ids = gemini.models.map((model) => model.id);
expect(ids).toContain("gemini-3.8-flash");
expect(MODEL_PRICING["gemini-3.8-flash"]).toMatchObject({ input: 1.5, output: 7.5 });
});
it("keeps the standalone CLI Antigravity catalog synchronized", () => {
const source = readFileSync(join(here, "../../cli/src/cli/menus/providers.js"), "utf8");
const agCatalog = source.match(/\n ag: \[([\s\S]*?)\n \],/)?.[1] || "";
expect(agCatalog).toContain("gemini-3.8-flash-high");
expect(agCatalog).toContain("gemini-3.8-flash-medium");
expect(agCatalog).toContain("gemini-3.8-flash-low");
});
});

View File

@@ -0,0 +1,192 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: vi.fn(),
}));
import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
import { getUsageForProvider } from "../../open-sse/services/usage.js";
import { getGlmUsage } from "../../open-sse/services/usage/glm.js";
import {
USAGE_SUPPORTED_PROVIDERS,
USAGE_APIKEY_PROVIDERS,
} from "../../src/shared/constants/providers.js";
function jsonResponse(body, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
const SAMPLE_GLM_CREDIT_USAGE = {
code: 200,
msg: "Operation successful",
data: {
limits: [
{
type: "CREDIT_LIMIT",
unit: 3,
number: 5,
usage: 2000,
currentValue: 0,
remaining: 1999,
percentage: 25,
nextResetTime: 1787905548392,
},
{
type: "CREDIT_LIMIT",
unit: 6,
number: 1,
usage: 10000,
currentValue: 0,
remaining: 9999,
percentage: 10,
nextResetTime: 1788492142997,
},
],
level: "lite",
},
success: true,
};
const SAMPLE_GLM_TOKENS_USAGE = {
code: 200,
msg: "Operation successful",
data: {
limits: [
{
type: "TOKENS_LIMIT",
percentage: 40,
nextResetTime: 1787905548392,
},
],
level: "standard",
},
success: true,
};
describe("glm registry usage flags", () => {
it("is listed for apikey quota dashboard", () => {
expect(USAGE_SUPPORTED_PROVIDERS).toContain("glm");
expect(USAGE_SUPPORTED_PROVIDERS).toContain("glm-cn");
expect(USAGE_APIKEY_PROVIDERS).toContain("glm");
expect(USAGE_APIKEY_PROVIDERS).toContain("glm-cn");
});
});
describe("getGlmUsage and getUsageForProvider(glm)", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("handles CREDIT_LIMIT with session 5h and weekly 7d quotas", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse(SAMPLE_GLM_CREDIT_USAGE));
const usage = await getUsageForProvider({
provider: "glm",
apiKey: "glm-key-123",
});
expect(usage.message).toBeUndefined();
expect(usage.plan).toBe("Lite");
expect(usage.quotas["Session (5h)"]).toEqual({
used: 25,
total: 100,
remaining: 75,
remainingPercentage: 75,
resetAt: new Date(1787905548392).toISOString(),
unlimited: false,
});
expect(usage.quotas["Weekly (7d)"]).toEqual({
used: 100 ? 10 : 10,
total: 100,
remaining: 90,
remainingPercentage: 90,
resetAt: new Date(1788492142997).toISOString(),
unlimited: false,
});
});
it("handles TOKENS_LIMIT quotas", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse(SAMPLE_GLM_TOKENS_USAGE));
const usage = await getUsageForProvider({
provider: "glm-cn",
apiKey: "glm-cn-key",
});
expect(usage.message).toBeUndefined();
expect(usage.plan).toBe("Standard");
expect(usage.quotas["Tokens"]).toEqual({
used: 40,
total: 100,
remaining: 60,
remainingPercentage: 60,
resetAt: new Date(1787905548392).toISOString(),
unlimited: false,
});
});
it("handles fallback key for custom limit units", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({
code: 200,
data: {
limits: [
{
type: "CREDIT_LIMIT",
unit: 99,
number: 12,
percentage: 5,
nextResetTime: 0,
},
],
level: "pro",
},
})
);
const usage = await getGlmUsage("glm-key", "glm");
expect(usage.plan).toBe("Pro");
expect(usage.quotas["Limit (12)"]).toEqual({
used: 5,
total: 100,
remaining: 95,
remainingPercentage: 95,
resetAt: null,
unlimited: false,
});
});
it("surfaces invalid key message on 401", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ error: "unauthorized" }, 401));
const usage = await getUsageForProvider({
provider: "glm",
apiKey: "invalid-key",
});
expect(usage.message).toMatch(/invalid or expired/i);
});
it("handles non-200 error response", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ error: "server error" }, 500));
const usage = await getUsageForProvider({
provider: "glm",
apiKey: "valid-key",
});
expect(usage.message).toMatch(/GLM quota API error \(500\)/);
});
it("returns message when apiKey is missing", async () => {
const usage = await getUsageForProvider({
provider: "glm",
apiKey: "",
});
expect(usage.message).toBe("GLM API key not available.");
});
});

View File

@@ -0,0 +1,127 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: vi.fn(),
}));
import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
import { getUsageForProvider } from "../../open-sse/services/usage.js";
import {
USAGE_SUPPORTED_PROVIDERS,
USAGE_APIKEY_PROVIDERS,
} from "../../src/shared/constants/providers.js";
import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";
const MODELS_URL = "https://api.groq.com/openai/v1/models";
function response(body, { status = 200, headers = {} } = {}) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json", ...headers },
});
}
const RATE_LIMIT_HEADERS = {
"x-ratelimit-limit-requests": "14400",
"x-ratelimit-remaining-requests": "14370",
"x-ratelimit-reset-requests": "2m59.56s",
"x-ratelimit-limit-tokens": "18000",
"x-ratelimit-remaining-tokens": "17997",
"x-ratelimit-reset-tokens": "7.66s",
};
describe("groq registry usage flags", () => {
it("is listed for apikey quota dashboard", () => {
expect(USAGE_SUPPORTED_PROVIDERS).toContain("groq");
expect(USAGE_APIKEY_PROVIDERS).toContain("groq");
});
});
describe("getUsageForProvider(groq)", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("GETs the models endpoint with Bearer apiKey", async () => {
proxyAwareFetch.mockResolvedValueOnce(
response({ data: [] }, { headers: RATE_LIMIT_HEADERS }),
);
const usage = await getUsageForProvider({
provider: "groq",
apiKey: "gsk_test",
});
expect(usage.message).toBeUndefined();
expect(usage.plan).toBe("Groq");
expect(proxyAwareFetch).toHaveBeenCalledTimes(1);
const [url, opts] = proxyAwareFetch.mock.calls[0];
expect(url).toBe(MODELS_URL);
expect(opts.method).toBe("GET");
expect(opts.headers.Authorization).toBe("Bearer gsk_test");
});
it("parses request + token rate-limit headers into quotas", async () => {
proxyAwareFetch.mockResolvedValueOnce(
response({ data: [] }, { headers: RATE_LIMIT_HEADERS }),
);
const usage = await getUsageForProvider({
provider: "groq",
apiKey: "gsk_test",
});
expect(usage.quotas["Requests"]).toMatchObject({
used: 30,
total: 14400,
unlimited: false,
});
expect(usage.quotas["Tokens"]).toMatchObject({
used: 3,
total: 18000,
unlimited: false,
});
// Duration-string reset headers resolve to a real future ISO timestamp.
expect(new Date(usage.quotas["Requests"].resetAt).getTime()).toBeGreaterThan(Date.now());
expect(new Date(usage.quotas["Tokens"].resetAt).getTime()).toBeGreaterThan(Date.now());
});
it("returns a soft message (not an error) when no rate-limit headers are present", async () => {
proxyAwareFetch.mockResolvedValueOnce(response({ data: [] }));
const usage = await getUsageForProvider({
provider: "groq",
apiKey: "gsk_test",
});
expect(usage.error).toBeUndefined();
expect(usage.message).toMatch(/no rate-limit data/i);
expect(usage.quotas).toEqual({});
});
it("returns message on missing key / 401", async () => {
const missing = await getUsageForProvider({ provider: "groq" });
expect(missing.message).toMatch(/api key/i);
expect(proxyAwareFetch).not.toHaveBeenCalled();
proxyAwareFetch.mockResolvedValueOnce(response({ error: "invalid_api_key" }, { status: 401 }));
const auth = await getUsageForProvider({ provider: "groq", apiKey: "bad" });
expect(auth.message).toMatch(/auth|key/i);
});
});
describe("parseQuotaData(groq)", () => {
it("forwards used/total/resetAt for the dashboard table", () => {
const rows = parseQuotaData("groq", {
plan: "Groq",
quotas: {
Requests: { used: 30, total: 14400, resetAt: "2026-01-01T00:03:00.000Z" },
Tokens: { used: 3, total: 18000, resetAt: "2026-01-01T00:00:08.000Z" },
},
});
expect(rows).toHaveLength(2);
expect(rows[0]).toMatchObject({ name: "Requests", used: 30, total: 14400 });
expect(rows[1]).toMatchObject({ name: "Tokens", used: 3, total: 18000 });
});
});

View File

@@ -202,6 +202,101 @@ describe("compressWithHeadroom", () => {
expect(stats).toBeNull();
expect(global.fetch).not.toHaveBeenCalled();
});
describe("timeout normalization", () => {
const mockResponse = JSON.stringify({
messages: [{ role: "user", content: "short" }],
tokens_before: 100,
tokens_after: 20,
tokens_saved: 80,
});
function makeSuccessfulFetch() {
global.fetch = vi.fn(async () =>
new Response(mockResponse, { status: 200 })
);
}
function captureTimeoutCalls() {
const calls = [];
vi.spyOn(AbortSignal, "timeout").mockImplementation((ms) => {
calls.push(ms);
const controller = new AbortController();
return controller.signal;
});
return calls;
}
it("passes a valid positive timeout to AbortSignal.timeout", async () => {
makeSuccessfulFetch();
const calls = captureTimeoutCalls();
const body = { messages: [{ role: "user", content: "hello" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787", timeoutMs: 5000 });
expect(calls).toContain(5000);
});
it("falls back to the default timeout when timeoutMs is null", async () => {
makeSuccessfulFetch();
const calls = captureTimeoutCalls();
const body = { messages: [{ role: "user", content: "hello" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787", timeoutMs: null });
expect(calls).toContain(3000);
});
it("falls back to the default timeout when timeoutMs is 0", async () => {
makeSuccessfulFetch();
const calls = captureTimeoutCalls();
const body = { messages: [{ role: "user", content: "hello" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787", timeoutMs: 0 });
expect(calls).toContain(3000);
});
it("falls back to the default timeout when timeoutMs is negative", async () => {
makeSuccessfulFetch();
const calls = captureTimeoutCalls();
const body = { messages: [{ role: "user", content: "hello" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787", timeoutMs: -100 });
expect(calls).toContain(3000);
});
it("falls back to the default timeout when timeoutMs is NaN", async () => {
makeSuccessfulFetch();
const calls = captureTimeoutCalls();
const body = { messages: [{ role: "user", content: "hello" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787", timeoutMs: NaN });
expect(calls).toContain(3000);
});
it("falls back to the default timeout when timeoutMs is Infinity", async () => {
makeSuccessfulFetch();
const calls = captureTimeoutCalls();
const body = { messages: [{ role: "user", content: "hello" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787", timeoutMs: Infinity });
expect(calls).toContain(3000);
});
it("falls back to the default timeout when timeoutMs is a string", async () => {
makeSuccessfulFetch();
const calls = captureTimeoutCalls();
const body = { messages: [{ role: "user", content: "hello" }] };
await compressWithHeadroom(body, { enabled: true, url: "http://localhost:8787", timeoutMs: "5000" });
expect(calls).toContain(3000);
});
});
});
describe("formatHeadroomLog", () => {

View File

@@ -316,7 +316,7 @@ describe("handleImageGenerationCore", () => {
expect(responseBody.data[0].b64_json).toBeTruthy();
});
it("generates image with Codex gpt-5.5-image using current Codex version header", async () => {
it.each(["gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"])("generates image with Codex %s-image using current Codex version header", async (model) => {
global.fetch.mockResolvedValueOnce(
new Response(
[
@@ -335,7 +335,7 @@ describe("handleImageGenerationCore", () => {
size: "1024x1024",
output_format: "png",
},
modelInfo: { provider: "codex", model: "gpt-5.5-image" },
modelInfo: { provider: "codex", model: `${model}-image` },
credentials: {
accessToken: "codex-token",
providerSpecificData: { chatgptAccountId: "account-123" },
@@ -358,7 +358,7 @@ describe("handleImageGenerationCore", () => {
const fetchCall = global.fetch.mock.calls[0];
const requestBody = JSON.parse(fetchCall[1].body);
expect(requestBody.model).toBe("gpt-5.5");
expect(requestBody.model).toBe(model);
expect(requestBody.tools).toEqual([
{ type: "image_generation", output_format: "png", size: "1024x1024" },
]);

View File

@@ -0,0 +1,192 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const { intercept } = require("../../src/mitm/handlers/kiro.js");
const MODEL = "offline-test-model";
function makeResponseCollector() {
const chunks = [];
const response = {
headersSent: false,
statusCode: undefined,
ended: false,
writeHead(statusCode) {
this.statusCode = statusCode;
this.headersSent = true;
return this;
},
write(chunk) {
chunks.push(Buffer.from(chunk));
return true;
},
end(chunk) {
if (chunk !== undefined) chunks.push(Buffer.from(chunk));
this.ended = true;
this.headersSent = true;
return this;
},
};
return { response, chunks };
}
async function captureOpenAIRequest(request) {
const originalFetch = globalThis.fetch;
const { response, chunks } = makeResponseCollector();
let captured;
const fetchMock = vi.fn(async (url, init) => {
captured = { url: String(url), init };
return new Response("data: [DONE]\n\n", {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
});
globalThis.fetch = fetchMock;
try {
await intercept(
{ headers: { "x-test": "kiro-image-forwarding" } },
response,
Buffer.from(JSON.stringify(request)),
MODEL,
);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(captured.url.endsWith("/v1/chat/completions")).toBe(true);
expect(captured.init.method).toBe("POST");
expect(response.statusCode).toBe(200);
expect(response.ended).toBe(true);
expect(chunks.length).toBeGreaterThan(0);
return JSON.parse(captured.init.body);
} finally {
if (originalFetch === undefined) delete globalThis.fetch;
else globalThis.fetch = originalFetch;
}
}
function image(format, bytes) {
return { format, source: { bytes } };
}
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
describe("Kiro MITM inline image forwarding", () => {
it("forwards text and inline images as OpenAI image_url content parts", async () => {
const outboundBody = await captureOpenAIRequest({
conversationState: {
history: [],
currentMessage: {
userInputMessage: {
content: " Describe this ",
images: [image("png", "aGVsbG8=")],
},
},
},
});
expect(outboundBody).toMatchObject({
model: MODEL,
stream: true,
messages: [{
role: "user",
content: [
{ type: "text", text: "Describe this" },
{ type: "image_url", image_url: { url: "data:image/png;base64,aGVsbG8=" } },
],
}],
});
});
it("emits an image-only user turn even when tool results are present", async () => {
const outboundBody = await captureOpenAIRequest({
conversationState: {
history: [],
currentMessage: {
userInputMessage: {
content: " ",
images: [image("jpg", "LzlqLzQ=")],
userInputMessageContext: {
toolResults: [
{ toolUseId: "tool-a", content: [{ text: "first" }, { text: "result" }] },
{ toolUseId: "tool-b", content: [{ text: "second" }] },
],
},
},
},
},
});
expect(outboundBody.messages).toEqual([
{ role: "tool", tool_call_id: "tool-a", content: "first\nresult" },
{ role: "tool", tool_call_id: "tool-b", content: "second" },
{
role: "user",
content: [
{ type: "image_url", image_url: { url: "data:image/jpeg;base64,LzlqLzQ=" } },
],
},
]);
});
it("keeps historical images on their original user turn", async () => {
const outboundBody = await captureOpenAIRequest({
conversationState: {
history: [
{
userInputMessage: {
content: " historical evidence ",
images: [image("jpeg", "anBlZw=="), image("webp", "d2VicA==")],
},
},
{ assistantResponseMessage: { content: "assistant reply" } },
],
currentMessage: { userInputMessage: { content: "current question" } },
},
});
expect(outboundBody.messages).toEqual([
{
role: "user",
content: [
{ type: "text", text: "historical evidence" },
{ type: "image_url", image_url: { url: "data:image/jpeg;base64,anBlZw==" } },
{ type: "image_url", image_url: { url: "data:image/webp;base64,d2VicA==" } },
],
},
{ role: "assistant", content: "assistant reply" },
{ role: "user", content: "current question" },
]);
});
it("ignores malformed and unsupported image entries without changing text-only behavior", async () => {
const outboundBody = await captureOpenAIRequest({
conversationState: {
history: [],
currentMessage: {
userInputMessage: {
content: " keep this text ",
images: [
image("svg", "ignored"),
image("PNG", "ignored"),
image("jpeg", ""),
{ format: "gif", source: { bytes: 42 } },
null,
[],
],
},
},
},
});
expect(outboundBody.messages).toEqual([
{ role: "user", content: "keep this text" },
]);
});
});

View File

@@ -0,0 +1,189 @@
/**
* Multi-transport providers must keep the request body and selected endpoint on
* the same wire format. MiniMax-M3 declares a Claude target for compatibility,
* but an OpenAI client should use MiniMax's matching OpenAI transport without
* an OpenAI -> Claude translation.
* Regression: https://github.com/decolua/9router/issues/3418
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
const {
executeMock,
translateRequestMock,
handleNonStreamingResponseMock,
} = vi.hoisted(() => ({
executeMock: vi.fn(),
translateRequestMock: vi.fn((sourceFormat, targetFormat, model, body) => ({
...body,
model,
_translatedFrom: sourceFormat,
_translatedTo: targetFormat,
})),
handleNonStreamingResponseMock: vi.fn(async () => ({ success: true })),
}));
vi.mock("../../open-sse/executors/index.js", () => ({
getExecutor: vi.fn(() => ({
execute: executeMock,
refreshCredentials: vi.fn().mockResolvedValue(null),
})),
}));
vi.mock("../../open-sse/translator/index.js", () => ({
translateRequest: translateRequestMock,
}));
vi.mock("../../open-sse/handlers/chatCore/nonStreamingHandler.js", () => ({
handleNonStreamingResponse: handleNonStreamingResponseMock,
}));
vi.mock("../../open-sse/utils/requestLogger.js", () => ({
createRequestLogger: vi.fn(async () => ({
logClientRawRequest: vi.fn(),
logRawRequest: vi.fn(),
logTargetRequest: vi.fn(),
logError: vi.fn(),
})),
}));
vi.mock("../../open-sse/utils/clientDetector.js", () => ({
detectClientTool: vi.fn(() => null),
isNativePassthrough: vi.fn(() => false),
}));
vi.mock("../../open-sse/utils/bypassHandler.js", () => ({
handleBypassRequest: vi.fn(() => null),
}));
vi.mock("../../open-sse/utils/streamHandler.js", () => ({
createStreamController: vi.fn(() => ({
signal: undefined,
handleComplete: vi.fn(),
handleError: vi.fn(),
})),
}));
vi.mock("../../open-sse/services/tokenRefresh.js", () => ({
refreshWithRetry: vi.fn(),
}));
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
default: vi.fn(),
proxyAwareFetch: vi.fn(),
}));
vi.mock("../../open-sse/translator/formats/claude.js", () => ({
normalizeClaudePassthrough: vi.fn(),
anchorClaudeCache: vi.fn(),
}));
vi.mock("../../open-sse/utils/toolDeduper.js", () => ({
dedupeTools: vi.fn((tools) => ({ tools, stripped: [] })),
}));
vi.mock("../../open-sse/rtk/caveman.js", () => ({ injectCaveman: vi.fn() }));
vi.mock("../../open-sse/rtk/ponytail.js", () => ({ injectPonytail: vi.fn() }));
vi.mock("../../open-sse/rtk/index.js", () => ({
compressMessages: vi.fn(() => null),
formatRtkLog: vi.fn(() => ""),
}));
vi.mock("../../open-sse/rtk/headroom.js", () => ({
compressWithHeadroom: vi.fn(async () => null),
formatHeadroomLog: vi.fn(() => ""),
formatHeadroomSizeLog: vi.fn(() => ""),
isHeadroomPhantomSavings: vi.fn(() => false),
}));
vi.mock("../../open-sse/rtk/pxpipe.js", () => ({
compressWithPxpipe: vi.fn(async () => ({ body: null, summary: null })),
}));
vi.mock("../../open-sse/translator/concerns/prefetch.js", () => ({
prefetchRemoteImages: vi.fn(async () => 0),
}));
vi.mock("../../open-sse/handlers/chatCore/requestDetail.js", () => ({
buildRequestDetail: vi.fn((detail) => detail),
extractRequestConfig: vi.fn((body, stream) => ({ body, stream })),
}));
vi.mock("../../open-sse/utils/error.js", () => ({
createErrorResult: vi.fn((status, message) => ({ success: false, status, error: message })),
formatProviderError: vi.fn((error) => error.message),
parseUpstreamError: vi.fn(),
}));
vi.mock("@/lib/usageDb.js", () => ({
trackPendingRequest: vi.fn(),
appendRequestLog: vi.fn(() => Promise.resolve()),
saveRequestDetail: vi.fn(() => Promise.resolve()),
}));
function makeOptions(body) {
return {
body,
modelInfo: { provider: "minimax-cn", model: "MiniMax-M3" },
credentials: { apiKey: "test-api-key", providerSpecificData: {} },
clientRawRequest: {
endpoint: "/v1/chat/completions",
body,
headers: { accept: "application/json" },
},
connectionId: "test-connection",
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
};
}
describe("MiniMax-M3 multi-transport routing", () => {
beforeEach(() => {
executeMock.mockReset();
translateRequestMock.mockClear();
handleNonStreamingResponseMock.mockClear();
executeMock.mockResolvedValue({
response: new Response("{}", {
status: 200,
headers: { "content-type": "application/json" },
}),
url: "https://api.minimaxi.com/v1/chat/completions",
headers: {},
transformedBody: {},
});
});
it("keeps OpenAI image blocks on the matching OpenAI transport", async () => {
const imageBlock = {
type: "image_url",
image_url: { url: "data:image/png;base64,AAAB" },
};
const body = {
model: "minimax-cn/MiniMax-M3",
stream: false,
messages: [{
role: "user",
content: [{ type: "text", text: "Describe this image" }, imageBlock],
}],
};
const { handleChatCore } = await import("../../open-sse/handlers/chatCore.js");
await handleChatCore(makeOptions(body));
expect(translateRequestMock).toHaveBeenCalledWith(
"openai",
"openai",
"MiniMax-M3",
expect.any(Object),
false,
expect.any(Object),
"minimax-cn",
expect.any(Object),
expect.anything(),
"test-connection",
null,
);
expect(executeMock).toHaveBeenCalledTimes(1);
const requestBody = executeMock.mock.calls[0][0].body;
expect(requestBody.messages[0].content).toContainEqual(imageBlock);
expect(requestBody._translatedTo).toBe("openai");
expect(requestBody).not.toHaveProperty("system");
expect(executeMock.mock.calls[0][0].credentials.runtimeTransport.format).toBe("openai");
});
});

View File

@@ -0,0 +1,54 @@
/**
* Regression: Claude Code appends `[1m]` to the model name when the
* 1M-context beta is on, so `/v1/messages` arrives with
* `model: "claude-opus-5[1m]"`. Nothing in 9router knows about the marker:
* it matches no combo, no alias and no `provider/model` pair, so the request
* is rejected at model resolution and the client reports
*
* There's an issue with the selected model (claude-opus-5[1m]).
* It may not exist or you may not have access to it.
*
* The capability itself rides in the `anthropic-beta` header, which is
* forwarded untouched, so stripping the marker is all that is needed.
*/
import { describe, it, expect } from "vitest";
import { stripModelContextMarker } from "../../open-sse/utils/modelMarkers.js";
describe("model context marker", () => {
it("strips the [1m] marker and reports it", () => {
expect(stripModelContextMarker("claude-opus-5[1m]")).toEqual({
model: "claude-opus-5",
contextMarker: "1m",
});
});
it("strips it from a provider-prefixed model too", () => {
expect(stripModelContextMarker("cc/claude-sonnet-4.5[1m]")).toEqual({
model: "cc/claude-sonnet-4.5",
contextMarker: "1m",
});
});
it("is case insensitive", () => {
expect(stripModelContextMarker("claude-opus-5[1M]").model).toBe("claude-opus-5");
});
it("leaves a plain model untouched", () => {
expect(stripModelContextMarker("claude-opus-5")).toEqual({
model: "claude-opus-5",
contextMarker: null,
});
});
it("only strips a trailing marker, never one inside the name", () => {
expect(stripModelContextMarker("weird[1m]name")).toEqual({
model: "weird[1m]name",
contextMarker: null,
});
});
it("tolerates a non-string model", () => {
expect(stripModelContextMarker(undefined)).toEqual({ model: undefined, contextMarker: null });
});
});

View File

@@ -0,0 +1,96 @@
import { describe, expect, it } from "vitest";
import { FORMATS } from "../../open-sse/translator/formats.js";
import { createSSETransformStreamWithLogger } from "../../open-sse/utils/stream.js";
// Ollama streams NDJSON — one raw JSON object per line, no "data: " prefix.
// Whatever arrives without a closing newline stays in the line buffer and is
// only parsed when the transform flushes.
async function runOllamaStream(input) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(input));
controller.close();
},
});
const output = stream.pipeThrough(
createSSETransformStreamWithLogger(FORMATS.OLLAMA, FORMATS.OPENAI, "ollama", null, null, "gpt-oss:120b"),
);
const reader = output.getReader();
const decoder = new TextDecoder();
let text = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
text += decoder.decode(value, { stream: true });
}
return text + decoder.decode();
}
const chunk = (content, done = false) => JSON.stringify({
model: "gpt-oss:120b",
created_at: "2026-08-25T00:00:00Z",
message: { role: "assistant", content },
done,
...(done ? { done_reason: "stop", prompt_eval_count: 11, eval_count: 7 } : {}),
});
const deltas = (sse) => sse
.split("\n")
.filter((l) => l.startsWith("data: ") && l !== "data: [DONE]")
.map((l) => JSON.parse(l.slice(6)));
describe("Ollama NDJSON stream: the tail left in the line buffer", () => {
it("delivers a content chunk that arrived without its newline", async () => {
const out = await runOllamaStream([chunk("hello"), chunk(" world")].join("\n"));
const content = deltas(out).map((c) => c.choices?.[0]?.delta?.content || "").join("");
expect(content).toBe("hello world");
});
it("delivers the final chunk — finish_reason and usage — when it arrives without its newline", async () => {
const out = await runOllamaStream([chunk("hello"), chunk("", true)].join("\n"));
const last = deltas(out).at(-1);
expect(last.choices[0].finish_reason).toBe("stop");
expect(last.usage).toEqual({ prompt_tokens: 11, completion_tokens: 7, total_tokens: 18 });
});
it("is unchanged when every line is newline-terminated", async () => {
const out = await runOllamaStream(`${[chunk("hello"), chunk(" world"), chunk("", true)].join("\n")}\n`);
const parsed = deltas(out);
expect(parsed.map((c) => c.choices?.[0]?.delta?.content || "").join("")).toBe("hello world");
expect(parsed.at(-1).choices[0].finish_reason).toBe("stop");
expect(parsed.at(-1).usage.total_tokens).toBe(18);
});
});
describe("SSE providers keep their sentinel handling", () => {
it("does not translate a trailing data: [DONE]", async () => {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(
`data: ${JSON.stringify({ choices: [{ delta: { content: "hi" } }] })}\ndata: [DONE]`,
));
controller.close();
},
});
const out = stream.pipeThrough(
createSSETransformStreamWithLogger(FORMATS.OPENAI, FORMATS.OPENAI, "openai", null, null, "gpt-4o"),
);
const reader = out.getReader();
const decoder = new TextDecoder();
let text = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
text += decoder.decode(value, { stream: true });
}
text += decoder.decode();
expect(text).toContain('"content":"hi"');
// The sentinel is a framing marker, not a chunk — it must not be translated.
expect(text).not.toContain('"done":true');
});
});

View File

@@ -0,0 +1,112 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import REGISTRY from "../../open-sse/providers/registry/index.js";
import { handleFetchCore } from "../../open-sse/handlers/fetch/index.js";
import { AI_PROVIDERS, getProvidersByKind } from "@/shared/constants/providers.js";
const CONFIG = {
baseUrl: "https://ollama.com/api/web_fetch",
timeoutMs: 30000,
};
afterEach(() => {
vi.unstubAllGlobals();
});
describe("Ollama Cloud web fetch provider", () => {
it("registers web fetch on the existing Ollama Cloud connection", () => {
const entry = REGISTRY.find((candidate) => candidate.id === "ollama");
expect(entry).toMatchObject({
category: "freeTier",
serviceKinds: ["llm", "webFetch"],
fetchConfig: {
baseUrl: "https://ollama.com/api/web_fetch",
method: "POST",
authHeader: "bearer",
formats: ["markdown"],
},
});
expect(AI_PROVIDERS.ollama?.fetchConfig).toEqual(entry.fetchConfig);
expect(getProvidersByKind("webFetch").map((provider) => provider.id)).toContain("ollama");
});
it("calls Ollama with bearer auth and normalizes the response", async () => {
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({
title: "Example Domain",
content: "Hello from Ollama",
links: ["https://www.iana.org/domains/example"],
}), {
status: 200,
headers: { "Content-Type": "application/json" },
})));
const result = await handleFetchCore({
url: "https://example.com",
format: "markdown",
maxCharacters: 5,
provider: "ollama",
providerConfig: CONFIG,
credentials: { apiKey: "ollama-test-key" },
});
expect(result.success).toBe(true);
expect(global.fetch).toHaveBeenCalledTimes(1);
const [requestUrl, init] = global.fetch.mock.calls[0];
expect(requestUrl).toBe("https://ollama.com/api/web_fetch");
expect(init.method).toBe("POST");
expect(init.headers).toEqual({
"content-type": "application/json",
authorization: "Bearer ollama-test-key",
});
expect(JSON.parse(init.body)).toEqual({ url: "https://example.com" });
expect(result.data).toMatchObject({
provider: "ollama",
url: "https://example.com",
title: "Example Domain",
content: { format: "markdown", text: "Hello", length: 5 },
links: ["https://www.iana.org/domains/example"],
usage: { fetch_cost_usd: null },
});
});
it("returns the upstream status and error message", async () => {
vi.stubGlobal("fetch", vi.fn(async () => new Response(
JSON.stringify({ error: "invalid API key" }),
{ status: 401, headers: { "Content-Type": "application/json" } },
)));
const result = await handleFetchCore({
url: "https://example.com",
provider: "ollama",
providerConfig: CONFIG,
credentials: { apiKey: "bad-key" },
});
expect(result).toMatchObject({
success: false,
status: 401,
error: "invalid API key",
});
});
it("treats an empty successful response as an upstream error", async () => {
vi.stubGlobal("fetch", vi.fn(async () => new Response(null, {
status: 200,
headers: { "Content-Type": "application/json" },
})));
const result = await handleFetchCore({
url: "https://example.com",
provider: "ollama",
providerConfig: CONFIG,
credentials: { apiKey: "ollama-test-key" },
});
expect(result).toEqual({
success: false,
status: 502,
error: "Ollama returned an empty or invalid web fetch response",
});
});
});

View File

@@ -22,11 +22,12 @@ describe("OpenCode Go model catalog", () => {
it("matches the documented model IDs", () => {
const ids = (PROVIDER_MODELS["opencode-go"] || []).map((m) => m.id);
expect(ids).toEqual([
"glm-5.2", "glm-5.1", "kimi-k2.7-code", "kimi-k2.6",
"deepseek-v4-pro", "deepseek-v4-flash",
"glm-5.3-flash", "glm-5.2", "glm-5.1", "kimi-k2.7-code", "kimi-k2.6",
"deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp",
"mimo-v2.5", "mimo-v2.5-pro",
"minimax-m3", "minimax-m2.7", "minimax-m2.5",
"qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus",
"muse-spark-1.2-contributor", "muse-spark-1.3-contributor",
]);
});
});
@@ -89,6 +90,15 @@ describe("OpenCode Go per-model transport guard (chatCore logic)", () => {
}
});
it("routes Muse Spark (responses-only) to /responses, never to /messages", () => {
for (const m of ["muse-spark-1.2-contributor", "muse-spark-1.3-contributor"]) {
expect(getModelSupportedFormats("opencode-go", m)).toEqual(["openai-responses"]);
expect(pickTransport("opencode-go", "openai-responses", "opencode-go", m)?.baseUrl).toBe("https://opencode.ai/zen/go/v1/responses");
expect(pickTransport("opencode-go", "claude", "opencode-go", m)).toBeNull();
expect(pickTransport("opencode-go", "openai", "opencode-go", m)).toBeNull();
}
});
it("does NOT route MiniMax (no responses support) to /responses", () => {
for (const m of CLAUDE_CAPABLE) {
expect(pickTransport("opencode-go", "openai-responses", "opencode-go", m)).toBeNull();

View File

@@ -0,0 +1,180 @@
import { describe, expect, it } from "vitest";
import { PROVIDER_MODELS, getModelTargetFormat, getModelSupportedFormats } from "../../open-sse/config/providerModels.js";
import { PROVIDERS } from "../../open-sse/config/providers.js";
import { resolveTransport } from "../../open-sse/services/provider.js";
import { getCapabilitiesForModel } from "../../open-sse/providers/capabilities.js";
import { getThinkingLevels } from "../../open-sse/providers/thinkingLevels.js";
import { getExecutor } from "../../open-sse/executors/index.js";
import { OpenCodeGoExecutor } from "../../open-sse/executors/opencode-go.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
import "../translator/registerAll.js";
import { translateRequest } from "../../open-sse/translator/index.js";
const MODEL = "muse-spark-1.3-contributor";
const PROVIDER = "opencode-go";
// Mirror of chatCore's per-model transport guard
function pickTransport(provider, sourceFormat, alias, model) {
const supported = getModelSupportedFormats(alias, model);
const rt = resolveTransport(provider, sourceFormat);
return supported?.includes(sourceFormat) ? rt : null;
}
describe("ocg/muse-spark-1.3-contributor catalog", () => {
it("is registered responses-only", () => {
const entry = (PROVIDER_MODELS["opencode-go"] || []).find((m) => m.id === MODEL);
expect(entry).toBeDefined();
expect(entry.targetFormat).toBe("openai-responses");
expect(getModelSupportedFormats("opencode-go", MODEL)).toEqual(["openai-responses"]);
expect(getModelTargetFormat("ocg", MODEL)).toBe(FORMATS.OPENAI_RESPONSES);
expect(getModelTargetFormat("opencode-go", MODEL)).toBe(FORMATS.OPENAI_RESPONSES);
});
it("never takes the sourceFormat-matched transport (always translates)", () => {
expect(pickTransport(PROVIDER, "openai", "opencode-go", MODEL)).toBeNull();
expect(pickTransport(PROVIDER, "claude", "opencode-go", MODEL)).toBeNull();
expect(pickTransport(PROVIDER, "openai-responses", "opencode-go", MODEL)?.baseUrl)
.toBe("https://opencode.ai/zen/go/v1/responses");
});
it("advertises reasoning via the shared muse-spark pattern", () => {
expect(getCapabilitiesForModel(PROVIDER, MODEL)).toMatchObject({
vision: true,
reasoning: true,
thinkingFormat: "openai",
});
expect(getThinkingLevels(PROVIDER, MODEL)).toContain("xhigh");
});
});
describe("OpenCodeGoExecutor routing + sanitization", () => {
it("is wired for opencode-go and routes muse-spark to /responses", () => {
expect(getExecutor("opencode-go")).toBeInstanceOf(OpenCodeGoExecutor);
const ex = new OpenCodeGoExecutor();
expect(ex.buildUrl(MODEL)).toBe("https://opencode.ai/zen/go/v1/responses");
// Even a stale runtimeTransport must not drag muse-spark onto chat/messages
expect(ex.buildUrl(MODEL, true, 0, {
runtimeTransport: { baseUrl: "https://opencode.ai/zen/go/v1/chat/completions" },
})).toBe("https://opencode.ai/zen/go/v1/responses");
});
it("leaves non-muse models on the default/runtime transport", () => {
const ex = new OpenCodeGoExecutor();
expect(ex.buildUrl("kimi-k2.6")).toBe("https://opencode.ai/zen/go/v1/chat/completions");
expect(ex.buildUrl("minimax-m3", true, 0, {
runtimeTransport: { baseUrl: "https://opencode.ai/zen/go/v1/messages" },
})).toBe("https://opencode.ai/zen/go/v1/messages");
});
it("normalizes caps + reasoning and coerces tool items exactly once", () => {
const ex = new OpenCodeGoExecutor();
const args = { path: "a\"b\nc\\d", emoji: "🚀 ü", nested: { q: "x'y\"z" } };
const body = {
model: MODEL,
input: [
{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] },
{ type: "function_call", call_id: "x".repeat(100), name: "read", arguments: args },
{ type: "function_call", call_id: "bad", name: " ", arguments: "{}" },
{ type: "function_call", call_id: "frag", name: "exec", arguments: "{not json" },
{ type: "function_call_output", call_id: "c1", output: { ok: true, text: "héllo \"w\"" } },
{ type: "function_call_output", call_id: "c2", output: null },
],
tools: [
{ type: "function", function: { name: "read", description: "r", parameters: { type: "object", properties: {} } } },
{ type: "function", function: { name: " ", parameters: {} } },
],
max_tokens: 2048,
reasoning_effort: "high",
};
const out = ex.transformRequest(MODEL, body, true, {});
expect(out.max_output_tokens).toBe(2048);
expect(out.max_tokens).toBeUndefined();
expect(out.reasoning).toEqual({ effort: "high", summary: "auto" });
expect(out.stream).toBe(true);
expect(out.store).toBe(false);
// nameless declaration dropped, nameless call dropped
expect(out.tools.map((t) => t.name)).toEqual(["read"]);
const calls = out.input.filter((i) => i.type === "function_call");
expect(calls.map((c) => c.name)).toEqual(["read", "exec"]);
// overlong id clamped, object args stringified exactly once
expect(calls[0].call_id).toHaveLength(64);
expect(JSON.parse(calls[0].arguments)).toEqual(args);
// invalid fragment coerced, never double-encoded
expect(calls[1].arguments).toBe("{}");
const outputs = out.input.filter((i) => i.type === "function_call_output");
expect(JSON.parse(outputs[0].output)).toEqual({ ok: true, text: "héllo \"w\"" });
expect(outputs[1].output).toBe("");
});
it("fills in properties for object tool schemas missing them", () => {
const ex = new OpenCodeGoExecutor();
const body = {
model: MODEL,
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] }],
tools: [
{ type: "function", function: { name: "bare", parameters: { type: "object" } } },
{ type: "function", function: { name: "full", parameters: { type: "object", properties: { a: { type: "string" } } } } },
],
};
const out = ex.transformRequest(MODEL, body, true, {});
expect(out.tools.find((t) => t.name === "bare").parameters).toEqual({ type: "object", properties: {} });
expect(out.tools.find((t) => t.name === "full").parameters).toEqual({ type: "object", properties: { a: { type: "string" } } });
});
});
describe("chat/claude clients translate to Responses without breaking tools", () => {
const tricky = { cmd: "echo \"hi\"\nnewline\ttab\\slash", emoji: "🎉 café naïve", nested: { a: [1, "x'y"] } };
it("openai chat → responses keeps arguments parseable", () => {
const translated = translateRequest(
FORMATS.OPENAI,
FORMATS.OPENAI_RESPONSES,
MODEL,
{
model: `ocg/${MODEL}`,
messages: [
{ role: "system", content: [{ type: "text", text: "sys one" }, { type: "text", text: "sys two" }] },
{ role: "user", content: "run it" },
{
role: "assistant", content: null,
tool_calls: [{ id: "call_1", type: "function", function: { name: "exec", arguments: tricky } }],
},
{ role: "tool", tool_call_id: "call_1", content: tricky },
],
tools: [{ type: "function", function: { name: "exec", description: "e", parameters: { type: "object", properties: {} } } }],
},
true, {}, PROVIDER,
);
expect(translated.instructions).toBe("sys one\nsys two");
const fc = translated.input.find((i) => i.type === "function_call");
expect(JSON.parse(fc.arguments)).toEqual(tricky);
const fco = translated.input.find((i) => i.type === "function_call_output");
expect(JSON.parse(fco.output)).toEqual(tricky);
});
it("claude messages → responses double-hop keeps tool input intact", () => {
const viaOpenAI = translateRequest(FORMATS.CLAUDE, FORMATS.OPENAI, MODEL, {
system: "be terse",
messages: [
{ role: "user", content: [{ type: "text", text: "go" }] },
{
role: "assistant",
content: [
{ type: "text", text: "calling" },
{ type: "tool_use", id: "tu_1", name: "exec", input: tricky },
],
},
{
role: "user",
content: [{ type: "tool_result", tool_use_id: "tu_1", content: [{ type: "text", text: JSON.stringify(tricky) }] }],
},
],
tools: [{ name: "exec", description: "e", input_schema: { type: "object", properties: {} } }],
}, true, {}, PROVIDER);
const translated = translateRequest(FORMATS.OPENAI, FORMATS.OPENAI_RESPONSES, MODEL, viaOpenAI, true, {}, PROVIDER);
const fc = translated.input.find((i) => i.type === "function_call");
expect(JSON.parse(fc.arguments)).toEqual(tricky);
const fco = translated.input.find((i) => i.type === "function_call_output");
expect(JSON.parse(fco.output)).toEqual(tricky);
});
});

View File

@@ -0,0 +1,166 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
const { fetchMock } = vi.hoisted(() => ({
fetchMock: vi.fn(),
}));
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: fetchMock,
}));
import { DefaultExecutor } from "../../open-sse/executors/default.js";
import { getExecutor } from "../../open-sse/executors/index.js";
const TRANSPORTS = [
{ format: "openai", baseUrl: "https://opencode.ai/zen/go/v1/chat/completions", auth: { combined: true, header: "Authorization", scheme: "bearer" } },
{ format: "claude", baseUrl: "https://opencode.ai/zen/go/v1/messages", auth: { combined: true, header: "x-api-key", scheme: "raw", anthropicVersion: true } },
{ format: "openai-responses", baseUrl: "https://opencode.ai/zen/go/v1/responses", auth: { combined: true, header: "Authorization", scheme: "bearer" } },
];
function makeCredentials(overrides = {}) {
return {
apiKey: "test-key",
connectionId: "connection-a",
rawHeaders: {},
runtimeTransport: TRANSPORTS[0],
...overrides,
};
}
function prepare(executor, overrides = {}) {
const credentials = overrides.credentials || makeCredentials();
const prepared = executor.prepareRequestCredentials({
body: overrides.body || { messages: [{ role: "user", content: "hello" }] },
credentials,
providerSessionId: overrides.providerSessionId ?? "conversation-a",
clientTool: overrides.clientTool ?? "claude",
});
return { credentials, prepared };
}
beforeEach(() => {
fetchMock.mockReset();
fetchMock.mockResolvedValue(new Response("{}", {
status: 200,
headers: { "content-type": "application/json" },
}));
});
describe("OpenCode Go x-opencode-session", () => {
it("uses a dedicated executor with request-local session credentials", () => {
const executor = getExecutor("opencode-go");
const { credentials, prepared } = prepare(executor);
expect(executor.constructor.name).toBe("OpenCodeGoExecutor");
expect(prepared).not.toBe(credentials);
expect(prepared._opencodeGoSession).toMatch(/^ses_[0-9a-f]{32}$/);
expect(credentials).not.toHaveProperty("_opencodeGoSession");
expect(executor).not.toHaveProperty("_currentSessionId");
expect(executor).not.toHaveProperty("_opencodeGoSession");
});
it("preserves a valid native session header case-insensitively", () => {
const executor = getExecutor("opencode-go");
const { prepared } = prepare(executor, {
credentials: makeCredentials({ rawHeaders: { "X-OpenCode-Session": " native-session-a " } }),
});
expect(prepared._opencodeGoSession).toBe("native-session-a");
});
it("ignores an oversized native session and uses the translated identity", () => {
const executor = getExecutor("opencode-go");
const { prepared } = prepare(executor, {
credentials: makeCredentials({ rawHeaders: { "x-opencode-session": "x".repeat(257) } }),
});
expect(prepared._opencodeGoSession).toMatch(/^ses_[0-9a-f]{32}$/);
});
it("keeps the same translated conversation stable across all transports", () => {
const executor = getExecutor("opencode-go");
const values = TRANSPORTS.map((runtimeTransport) => {
const { prepared } = prepare(executor, {
credentials: makeCredentials({ runtimeTransport }),
});
return executor.buildHeaders(prepared, true)["x-opencode-session"];
});
expect(new Set(values).size).toBe(1);
expect(values[0]).toMatch(/^ses_[0-9a-f]{32}$/);
expect(values[0]).not.toContain("conversation-a");
});
it("isolates different conversations", () => {
const executor = getExecutor("opencode-go");
const a = prepare(executor, { providerSessionId: "conversation-a" }).prepared._opencodeGoSession;
const b = prepare(executor, { providerSessionId: "conversation-b" }).prepared._opencodeGoSession;
expect(a).not.toBe(b);
});
it("isolates different downstream agents that reuse the same raw id", () => {
const executor = getExecutor("opencode-go");
const claude = prepare(executor, { clientTool: "claude" }).prepared._opencodeGoSession;
const codex = prepare(executor, { clientTool: "codex" }).prepared._opencodeGoSession;
expect(claude).not.toBe(codex);
});
it("uses a stable opaque connection fallback when no session is supplied", () => {
const executor = getExecutor("opencode-go");
const options = {
credentials: makeCredentials({ connectionId: "fallback-connection" }),
providerSessionId: null,
clientTool: null,
body: { messages: [{ role: "user", content: "headerless" }] },
};
const first = prepare(executor, options).prepared._opencodeGoSession;
const second = prepare(executor, options).prepared._opencodeGoSession;
expect(first).toBe(second);
expect(first).toMatch(/^ses_[0-9a-f]{32}$/);
expect(first).not.toContain("fallback-connection");
});
it("adds the prepared session to the actual fetch headers", async () => {
const executor = getExecutor("opencode-go");
const credentials = makeCredentials();
const result = await executor.execute({
model: "glm-5.2",
body: { messages: [{ role: "user", content: "hello" }] },
stream: false,
credentials,
providerSessionId: "conversation-fetch",
clientTool: "codex",
});
expect(result.headers["x-opencode-session"]).toMatch(/^ses_[0-9a-f]{32}$/);
expect(fetchMock).toHaveBeenCalledOnce();
expect(fetchMock.mock.calls[0][1].headers["x-opencode-session"]).toBe(result.headers["x-opencode-session"]);
expect(credentials).not.toHaveProperty("_opencodeGoSession");
});
it("does not add the header to unrelated default executors", () => {
const headers = new DefaultExecutor("openai").buildHeaders({ apiKey: "test-key" }, false);
expect(headers["x-opencode-session"]).toBeUndefined();
});
});
describe("chatCore provider session forwarding", () => {
it("passes the original provider session and client tool on initial and retry execution", () => {
const source = readFileSync(
fileURLToPath(new URL("../../open-sse/handlers/chatCore.js", import.meta.url)),
"utf8",
);
const calls = [...source.matchAll(/executor\.execute\(\{([\s\S]*?)\}\)/g)].map((match) => match[1]);
expect(calls).toHaveLength(2);
for (const call of calls) {
expect(call).toMatch(/providerSessionId:\s*sessionSeed/);
expect(call).toMatch(/\bclientTool\b/);
}
});
});

View File

@@ -0,0 +1,129 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../open-sse/utils/proxyFetch.js", () => ({
proxyAwareFetch: vi.fn(),
}));
import { proxyAwareFetch } from "../../open-sse/utils/proxyFetch.js";
import { getUsageForProvider } from "../../open-sse/services/usage.js";
import {
USAGE_APIKEY_PROVIDERS,
USAGE_SUPPORTED_PROVIDERS,
} from "../../src/shared/constants/providers.js";
const USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
function jsonResponse(body, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
describe("OpenCode Go registry usage flags", () => {
it("is listed for the API key quota dashboard", () => {
expect(USAGE_SUPPORTED_PROVIDERS).toContain("opencode-go");
expect(USAGE_APIKEY_PROVIDERS).toContain("opencode-go");
});
});
describe("getUsageForProvider(opencode-go)", () => {
beforeEach(() => vi.clearAllMocks());
it("fetches and normalizes subscription usage", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({
usage: {
rolling: { status: "ok", percent: 13, resetsAt: "2026-09-04T14:28:02.617Z" },
weekly: { status: "ok", percent: 5, resetsAt: "2026-09-07T00:00:00.617Z" },
monthly: { status: "ok", percent: 2, resetsAt: "2026-10-02T12:14:24.617Z" },
},
}),
);
const usage = await getUsageForProvider({
provider: "opencode-go",
apiKey: "sk-go-test",
});
expect(proxyAwareFetch).toHaveBeenCalledWith(
USAGE_URL,
expect.objectContaining({
method: "GET",
headers: expect.objectContaining({ Authorization: "Bearer sk-go-test" }),
}),
null,
);
expect(usage).toEqual({
plan: "OpenCode Go",
quotas: {
Rolling: {
used: 13,
total: 100,
remaining: 87,
remainingPercentage: 87,
resetAt: "2026-09-04T14:28:02.617Z",
unlimited: false,
},
Weekly: expect.objectContaining({ used: 5, remainingPercentage: 95 }),
Monthly: expect.objectContaining({ used: 2, remainingPercentage: 98 }),
},
});
});
it("reports missing and rejected credentials", async () => {
const missing = await getUsageForProvider({ provider: "opencode-go" });
expect(missing.message).toMatch(/api key/i);
expect(proxyAwareFetch).not.toHaveBeenCalled();
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ error: "unauthorized" }, 401));
const rejected = await getUsageForProvider({
provider: "opencode-go",
apiKey: "bad",
});
expect(rejected.message).toMatch(/authentication failed/i);
});
it("distinguishes a missing subscription from invalid credentials", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({ error: { type: "EntitlementError" } }, 403),
);
const usage = await getUsageForProvider({
provider: "opencode-go",
apiKey: "sk-without-go",
});
expect(usage.message).toMatch(/subscription required/i);
});
it("rejects responses without a valid quota percentage", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({ usage: { rolling: { status: "ok" }, future: { percent: 10 } } }),
);
const usage = await getUsageForProvider({
provider: "opencode-go",
apiKey: "sk-go-test",
});
expect(usage.quotas).toBeUndefined();
expect(usage.message).toMatch(/valid quota data/i);
});
it("reports upstream and network failures", async () => {
proxyAwareFetch.mockResolvedValueOnce(jsonResponse({ error: "unavailable" }, 500));
const upstream = await getUsageForProvider({
provider: "opencode-go",
apiKey: "sk-go-test",
});
expect(upstream.message).toContain("500");
proxyAwareFetch.mockRejectedValueOnce(new Error("socket closed"));
const network = await getUsageForProvider({
provider: "opencode-go",
apiKey: "sk-go-test",
});
expect(network.message).toContain("socket closed");
});
});

View File

@@ -0,0 +1,135 @@
import { describe, expect, it } from "vitest";
import { getCapabilitiesForModel } from "../../open-sse/providers/capabilities.js";
import { PROVIDER_MODELS, getModelTargetFormat } from "../../open-sse/config/providerModels.js";
import { getThinkingLevels } from "../../open-sse/providers/thinkingLevels.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
import { OpenCodeExecutor } from "../../open-sse/executors/opencode.js";
import "../translator/registerAll.js";
import { translateRequest } from "../../open-sse/translator/index.js";
const MODEL = "muse-spark-1.2-contributor-free";
const PROVIDER = "opencode";
const input = [{
type: "message",
role: "user",
content: [{ type: "input_text", text: "Think, then answer: 2 + 2?" }],
}];
describe("OpenCode Free Muse Spark thinking", () => {
it("advertises reasoning and the requested model limits", () => {
expect(PROVIDER_MODELS.oc?.some((model) => model.id === MODEL)).toBe(true);
expect(PROVIDER_MODELS.oc?.some((model) => model.id === "muse-spark-1.3-contributor-free")).toBe(true);
for (const m of [MODEL, "muse-spark-1.3-contributor-free", "muse-spark-1.4-contributor-free", "muse-spark-2.0-contributor-free"]) {
expect(getCapabilitiesForModel(PROVIDER, m)).toMatchObject({
reasoning: true,
thinkingFormat: "openai",
contextWindow: 1048576,
maxOutput: 131072,
});
expect(getCapabilitiesForModel(PROVIDER, `oc/${m}`)).toMatchObject({
reasoning: true,
contextWindow: 1048576,
maxOutput: 131072,
});
expect(getThinkingLevels(PROVIDER, m)).toEqual([
"none",
"minimal",
"low",
"medium",
"high",
"xhigh",
]);
expect(getModelTargetFormat("oc", m)).toBe(FORMATS.OPENAI_RESPONSES);
expect(getModelTargetFormat("opencode", m)).toBe(FORMATS.OPENAI_RESPONSES);
expect(getModelTargetFormat("openrouter", m)).toBeNull();
}
});
it("clamps max to xhigh and emits the Responses reasoning shape", () => {
const body = {
input,
reasoning: { effort: "max" },
max_tokens: 131072,
};
const out = new OpenCodeExecutor().transformRequest(MODEL, body, true, {
connectionId: "opencode-muse-spark-test",
});
expect(out.reasoning).toEqual({ effort: "xhigh", summary: "auto" });
expect(out.reasoning_effort).toBeUndefined();
expect(out.max_output_tokens).toBe(131072);
expect(out.max_tokens).toBeUndefined();
});
it("leaves the other free models on Chat Completions", () => {
const executor = new OpenCodeExecutor();
const body = { messages: [{ role: "user", content: "hi" }], max_tokens: 1024 };
executor.transformRequest("big-pickle", body, true, {});
expect(executor.buildUrl("big-pickle")).toBe("https://opencode.ai/zen/v1/chat/completions");
expect(body.max_tokens).toBe(1024);
expect(body.max_output_tokens).toBeUndefined();
});
it("translates Chat Completions max thinking into a Responses request", () => {
const body = {
model: `oc/${MODEL}`,
messages: [{ role: "user", content: "Think, then answer: 2 + 2?" }],
reasoning_effort: "max",
max_tokens: 131072,
};
const translated = translateRequest(
FORMATS.OPENAI,
FORMATS.OPENAI_RESPONSES,
MODEL,
body,
true,
{},
PROVIDER,
);
const out = new OpenCodeExecutor().transformRequest(MODEL, translated, true, {
connectionId: "opencode-muse-spark-translation-test",
});
expect(out.reasoning).toEqual({ effort: "xhigh", summary: "auto" });
expect(out.max_output_tokens).toBe(131072);
expect(out.max_tokens).toBeUndefined();
});
it("routes muse-spark-1.3-contributor-free and future Muse Spark models to Responses API", () => {
const executor = new OpenCodeExecutor();
const futureModel = "muse-spark-1.4-contributor-free";
for (const m of ["muse-spark-1.3-contributor-free", futureModel]) {
expect(executor.buildUrl(m)).toBe("https://opencode.ai/zen/v1/responses");
expect(executor.buildUrl(`${m}(high)`)).toBe("https://opencode.ai/zen/v1/responses");
expect(getModelTargetFormat("oc", m)).toBe("openai-responses");
const body = {
model: `oc/${m}`,
messages: [{ role: "user", content: "Hello" }],
reasoning_effort: "high",
max_tokens: 2048,
};
const translated = translateRequest(
FORMATS.OPENAI,
FORMATS.OPENAI_RESPONSES,
m,
body,
true,
{},
PROVIDER,
);
const out = executor.transformRequest(m, translated, true, {
connectionId: "opencode-muse-spark-13-test",
});
expect(out.reasoning).toEqual({ effort: "high", summary: "auto" });
expect(out.max_output_tokens).toBe(2048);
expect(out.max_tokens).toBeUndefined();
}
});
});

View File

@@ -3,6 +3,7 @@ import {
filterQuotasByVisibility,
getHiddenQuotaRows,
parseQuotaData,
trimHiddenQuotaKeys,
} from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";
describe("provider quota visibility", () => {
@@ -13,22 +14,26 @@ describe("provider quota visibility", () => {
used: 200,
total: 1000,
resetAt: "2026-07-04T00:00:00Z",
remainingPercentage: 80,
},
"claude-opus-4-6-thinking": {
displayName: "Claude Opus 4.6 (Thinking)",
used: 100,
total: 1000,
resetAt: "2026-07-04T00:00:00Z",
remainingPercentage: 90,
},
},
};
it("keeps Antigravity modelKey so hidden settings use stable quota ids", () => {
it("groups Antigravity model quotas into Gemini and Claude families", () => {
const quotas = parseQuotaData("antigravity", data);
expect(quotas.map((q) => q.modelKey)).toEqual([
"gemini-pro-agent",
"claude-opus-4-6-thinking",
"gemini",
"claude",
]);
expect(quotas[0].name).toBe("Gemini (Flash / Pro)");
expect(quotas[1].name).toBe("Claude (Sonnet / Opus)");
});
it("shows all quotas by default and hides configured provider rows", () => {
@@ -36,19 +41,34 @@ describe("provider quota visibility", () => {
expect(filterQuotasByVisibility("antigravity", quotas, {})).toHaveLength(2);
const visibility = {
antigravity: { hidden: ["claude-opus-4-6-thinking"] },
antigravity: { hidden: ["claude"] },
};
const visible = filterQuotasByVisibility("antigravity", quotas, visibility);
const hidden = getHiddenQuotaRows("antigravity", quotas, visibility);
expect(visible.map((q) => q.modelKey)).toEqual(["gemini-pro-agent"]);
expect(hidden.map((q) => q.modelKey)).toEqual(["claude-opus-4-6-thinking"]);
expect(visible.map((q) => q.modelKey)).toEqual(["gemini"]);
expect(hidden.map((q) => q.modelKey)).toEqual(["claude"]);
});
it("trims stale or obsolete model keys", () => {
const quotas = parseQuotaData("antigravity", data);
const trimmed = trimHiddenQuotaKeys(["claude", "stale-model-xyz", "gemini-3.8-flash-low"], quotas);
expect(trimmed).toEqual(["claude"]);
const visibility = {
antigravity: { hidden: ["claude", "stale-model-xyz"] },
};
const visible = filterQuotasByVisibility("antigravity", quotas, visibility);
const hidden = getHiddenQuotaRows("antigravity", quotas, visibility);
expect(visible.map((q) => q.modelKey)).toEqual(["gemini"]);
expect(hidden.map((q) => q.modelKey)).toEqual(["claude"]);
});
it("does not apply one provider hidden list to another provider", () => {
const quotas = parseQuotaData("antigravity", data);
const visibility = {
codex: { hidden: ["gemini-pro-agent"] },
codex: { hidden: ["gemini"] },
};
expect(filterQuotasByVisibility("antigravity", quotas, visibility)).toHaveLength(2);
});

View File

@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import {
STATUS_FILTER_OPTIONS,
getConnectionStatus,
matchesStatusFilter,
} from "@/app/(dashboard)/dashboard/providers/utils.js";
describe("providers status filter", () => {
it("exposes all/active/inactive/none options", () => {
expect(STATUS_FILTER_OPTIONS.map((o) => o.value)).toEqual([
"all",
"active",
"inactive",
"none",
]);
});
it("classifies a provider with no connections as none", () => {
expect(getConnectionStatus({ total: 0, allDisabled: false })).toBe("none");
});
it("classifies a provider whose only connections are disabled as inactive", () => {
expect(getConnectionStatus({ total: 2, allDisabled: true })).toBe(
"inactive",
);
});
it("classifies a provider with at least one enabled connection as active", () => {
expect(getConnectionStatus({ total: 1, allDisabled: false })).toBe(
"active",
);
});
it("treats noAuth providers as active even with no stored connection", () => {
expect(getConnectionStatus({ total: 0, allDisabled: false }, true)).toBe(
"active",
);
});
it("matchesStatusFilter always passes for 'all'", () => {
expect(matchesStatusFilter("all", { total: 0, allDisabled: false })).toBe(
true,
);
});
it("matchesStatusFilter compares against the derived status", () => {
const disabledStats = { total: 3, allDisabled: true };
expect(matchesStatusFilter("inactive", disabledStats)).toBe(true);
expect(matchesStatusFilter("active", disabledStats)).toBe(false);
expect(matchesStatusFilter("none", disabledStats)).toBe(false);
});
});

View File

@@ -367,6 +367,70 @@ describe("normalizeMessages", () => {
expect(result.messages).toEqual([]);
expect(result.systemText).toBe("");
});
it("preserves image_url blocks (http URL) instead of dropping them", () => {
const result = normalizeMessages([
{
role: "user",
content: [
{ type: "text", text: "describe" },
{ type: "image_url", image_url: { url: "https://example.com/a.png" } },
],
},
]);
const content = result.messages[0].content;
expect(Array.isArray(content)).toBe(true);
expect(content).toContainEqual({ type: "text", text: "describe" });
expect(content).toContainEqual({ type: "image_url", image_url: { url: "https://example.com/a.png" } });
});
it("preserves base64 data: URI images (no OSS upload needed)", () => {
const dataUri = "data:image/png;base64,iVBORw0KGgo=";
const result = normalizeMessages([
{
role: "user",
content: [
{ type: "image_url", image_url: { url: dataUri } },
{ type: "text", text: "what color?" },
],
},
]);
const content = result.messages[0].content;
expect(Array.isArray(content)).toBe(true);
expect(content[0]).toEqual({ type: "image_url", image_url: { url: dataUri } });
expect(content.some((b) => b.type === "text" && b.text === "what color?")).toBe(true);
});
it("converts claude-style base64 image blocks to image_url data URIs", () => {
const result = normalizeMessages([
{
role: "user",
content: [
{ type: "text", text: "see this" },
{ type: "image", source: { type: "base64", media_type: "image/jpeg", data: "AAAA" } },
],
},
]);
const content = result.messages[0].content;
expect(content).toContainEqual({
type: "image_url",
image_url: { url: "data:image/jpeg;base64,AAAA" },
});
});
it("drops image blocks with no usable url but keeps the text", () => {
const result = normalizeMessages([
{
role: "user",
content: [
{ type: "text", text: "hi" },
{ type: "image_url", image_url: {} },
{ type: "image", source: { type: "base64" } },
],
},
]);
expect(result.messages[0].content).toBe("hi");
});
});
describe("wrapQoderSSE", () => {

View File

@@ -0,0 +1,165 @@
// Parallel function_calls from a Responses upstream must stay on separate
// chat tool_calls indices. Regression: response/openai-responses.js attributed
// every arguments delta to the positional toolCallIndex (advanced only on
// output_item.done), so all-added-then-deltas ordering concatenated N JSON
// payloads into index 0 and clients failed with InputValidationError.
import { describe, expect, it } from "vitest";
import "../translator/registerAll.js";
import { openaiResponsesToOpenAIResponse } from "../../open-sse/translator/response/openai-responses.js";
import { clampResponsesCallId, coerceResponsesOutput, MAX_RESPONSES_CALL_ID_LEN } from "../../open-sse/translator/formats/responsesApi.js";
import { initState, translateResponse } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
const added = (id, call_id, name, type = "function_call") => ({
type: "response.output_item.added",
item: { id, type, call_id, name, arguments: "" },
});
const delta = (item_id, text) => ({
type: "response.function_call_arguments.delta",
item_id,
delta: text,
});
const done = (id, call_id, name) => ({
type: "response.output_item.done",
item: { id, type: "function_call", call_id, name },
});
// Reassemble translated chunks the way an OpenAI client accumulator does.
function accumulate(calls, chunks) {
for (const chunk of chunks) {
if (!chunk) continue;
for (const tc of chunk.choices?.[0]?.delta?.tool_calls || []) {
const slot = (calls[tc.index] ??= { id: null, name: "", args: "" });
if (tc.id) slot.id = tc.id;
if (tc.function?.name) slot.name = tc.function.name;
if (tc.function?.arguments) slot.args += tc.function.arguments;
}
}
return calls;
}
function runStream(events) {
const state = {};
const chunks = [];
for (const ev of events) {
const out = openaiResponsesToOpenAIResponse(ev, state);
if (out) chunks.push(out);
}
const flush = openaiResponsesToOpenAIResponse(null, state);
if (flush) chunks.push(flush);
return { state, chunks };
}
const PAYLOADS = [
'{"file_path":"/docs/PRODUCT.md"}',
'{"file_path":"/docs/ROADMAP.md"}',
'{"file_path":"/docs/openapi.custom.yaml"}',
'{"file_path":"/docs/.gitignore"}',
];
function hostileOrdering() {
const events = PAYLOADS.map((_, i) => added(`fc_${i}`, `call_${i}`, "read_file"));
// Interleaved deltas AFTER all addeds — the ordering that used to merge all
// four payloads into index 0.
PAYLOADS.forEach((p, i) => events.push(delta(`fc_${i}`, p.slice(0, 20)), delta(`fc_${i}`, p.slice(20))));
PAYLOADS.forEach((_, i) => events.push(done(`fc_${i}`, `call_${i}`, "read_file")));
return events;
}
describe("responses parallel tool calls keep their own index", () => {
it("all-added-then-deltas ordering yields 4 separately parseable calls", () => {
const { chunks } = runStream(hostileOrdering());
const calls = accumulate({}, chunks);
expect(Object.keys(calls)).toHaveLength(4);
PAYLOADS.forEach((p, i) => {
expect(calls[i].id).toBe(`call_${i}`);
expect(calls[i].name).toBe("read_file");
expect(JSON.parse(calls[i].args)).toEqual(JSON.parse(p));
});
});
it("sequential ordering still yields indices 0,1 in order", () => {
const events = [
added("fc_0", "call_0", "read_file"),
delta("fc_0", PAYLOADS[0]),
done("fc_0", "call_0", "read_file"),
added("fc_1", "call_1", "read_file"),
delta("fc_1", PAYLOADS[1]),
done("fc_1", "call_1", "read_file"),
];
const { chunks } = runStream(events);
const calls = accumulate({}, chunks);
expect(Object.keys(calls)).toEqual(["0", "1"]);
expect(JSON.parse(calls[0].args)).toEqual(JSON.parse(PAYLOADS[0]));
expect(JSON.parse(calls[1].args)).toEqual(JSON.parse(PAYLOADS[1]));
});
it("done carrying full arguments (no deltas) emits them once", () => {
const state = {};
const out1 = openaiResponsesToOpenAIResponse(added("fc_9", "call_9", "read_file"), state);
const out2 = openaiResponsesToOpenAIResponse({
type: "response.output_item.done",
item: { id: "fc_9", type: "function_call", call_id: "call_9", name: "read_file", arguments: PAYLOADS[0] },
}, state);
const calls = accumulate({}, [out1, out2]);
expect(JSON.parse(calls[0].args)).toEqual(JSON.parse(PAYLOADS[0]));
});
it("deltas without item_id fall back to the most recent call (legacy behavior)", () => {
const events = [
added("fc_0", "call_0", "read_file"),
{ type: "response.function_call_arguments.delta", delta: PAYLOADS[0] },
done("fc_0", "call_0", "read_file"),
];
const { chunks } = runStream(events);
const calls = accumulate({}, chunks);
expect(JSON.parse(calls[0].args)).toEqual(JSON.parse(PAYLOADS[0]));
});
});
describe("responses → claude end-to-end keeps parallel tool_use blocks separate", () => {
it("four read_file calls arrive as four parseable tool_use blocks", () => {
const state = initState(FORMATS.CLAUDE);
const out = [];
for (const ev of hostileOrdering()) {
for (const r of translateResponse(FORMATS.OPENAI_RESPONSES, FORMATS.CLAUDE, ev, state)) out.push(r);
}
for (const r of translateResponse(FORMATS.OPENAI_RESPONSES, FORMATS.CLAUDE, null, state)) out.push(r);
const starts = out.filter((r) => r?.type === "content_block_start" && r?.content_block?.type === "tool_use");
expect(starts).toHaveLength(4);
const partials = out.filter((r) => r?.delta?.type === "input_json_delta");
expect(partials).toHaveLength(4);
const bodies = partials.map((r) => JSON.parse(r.delta.partial_json).file_path).sort();
expect(bodies).toEqual([
"/docs/.gitignore",
"/docs/PRODUCT.md",
"/docs/ROADMAP.md",
"/docs/openapi.custom.yaml",
]);
});
});
describe("fallback call_ids stay unique within a batch", () => {
it("same-millisecond fallbacks never collide", () => {
const ids = new Set(Array.from({ length: 50 }, () => clampResponsesCallId(undefined)));
expect(ids.size).toBe(50);
for (const id of ids) {
expect(id.startsWith("call_")).toBe(true);
expect(id.length).toBeLessThanOrEqual(MAX_RESPONSES_CALL_ID_LEN);
}
expect(new Set([clampResponsesCallId(""), clampResponsesCallId(null)]).size).toBe(2);
});
});
describe("output coercion stays fail-soft on unstringifiable values", () => {
it("never throws on BigInt/circular array elements", () => {
const circular = {};
circular.self = circular;
const input = [1n, circular, { text: "ok" }];
expect(() => coerceResponsesOutput(input)).not.toThrow();
const out = coerceResponsesOutput(input);
expect(typeof out).toBe("string");
expect(out).toContain("ok");
});
});

View File

@@ -0,0 +1,147 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// Regression coverage for #3714: the SSRF guard's literal-hostname/IP checks
// matched specific textual representations rather than the underlying address,
// so a different (but equivalent) representation slipped through. Each case
// below is a bypass the issue reported, or one found while fixing it.
const { lookupMock } = vi.hoisted(() => ({ lookupMock: vi.fn() }));
vi.mock("node:dns", () => ({
default: { promises: { lookup: lookupMock } },
promises: { lookup: lookupMock },
}));
const { assertPublicUrl, assertPublicUrlResolved, fetchPublic } = await import("../../src/shared/utils/ssrfGuard.js");
describe("assertPublicUrl: literal hostname/IP bypasses from #3714", () => {
it("blocks a trailing-dot FQDN the same as the bare hostname", () => {
expect(() => assertPublicUrl("http://localhost/")).toThrow();
expect(() => assertPublicUrl("http://localhost./")).toThrow();
expect(() => assertPublicUrl("http://LOCALHOST./")).toThrow();
});
it("blocks IPv4-mapped IPv6 loopback regardless of which textual form the URL parser picks", () => {
// WHATWG URL parsing normalizes dotted-decimal IPv4-in-IPv6 to hex form —
// the original regex only matched the dotted form.
expect(() => assertPublicUrl("http://[::ffff:127.0.0.1]/")).toThrow();
expect(() => assertPublicUrl("http://[::ffff:7f00:1]/")).toThrow(); // hex form directly
expect(() => assertPublicUrl("http://[0000::ffff:127.0.0.1]/")).toThrow();
});
it("blocks IPv4-mapped IPv6 cloud metadata address (169.254.169.254)", () => {
expect(() => assertPublicUrl("http://[::ffff:169.254.169.254]/")).toThrow();
expect(() => assertPublicUrl("http://[::ffff:a9fe:a9fe]/")).toThrow(); // hex form
});
it("blocks other loopback/private/link-local/ULA IPv6 forms", () => {
for (const url of [
"http://[::1]/",
"http://[::127.0.0.1]/",
"http://[fe80::1]/",
"http://[fc00::1]/",
"http://[fd12:3456::1]/",
"http://[64:ff9b::127.0.0.1]/", // NAT64 well-known prefix embedding a private IPv4
]) {
expect(() => assertPublicUrl(url), url).toThrow();
}
});
it("blocks alternate IPv4 literal encodings (already normalized by the URL parser)", () => {
for (const url of ["http://127.1/", "http://0177.0.0.1/", "http://2130706433/", "http://0x7f.0.0.1/"]) {
expect(() => assertPublicUrl(url), url).toThrow();
}
});
it("still allows public hosts, including public IPv6", () => {
expect(() => assertPublicUrl("https://api.openai.com/v1/models")).not.toThrow();
expect(() => assertPublicUrl("http://8.8.8.8/")).not.toThrow();
expect(() => assertPublicUrl("https://[2001:4860:4860::8888]/")).not.toThrow();
});
});
describe("assertPublicUrlResolved: DNS-resolving hostname bypass from #3714", () => {
beforeEach(() => lookupMock.mockReset());
it("blocks a hostname that resolves to a loopback address (nip.io-style wildcard DNS)", async () => {
lookupMock.mockResolvedValue([{ address: "127.0.0.1", family: 4 }]);
await expect(assertPublicUrlResolved("http://127.0.0.1.nip.io/")).rejects.toThrow();
});
it("blocks a hostname that resolves to a private range even if one of several addresses is public", async () => {
lookupMock.mockResolvedValue([{ address: "203.0.113.5", family: 4 }, { address: "10.0.0.5", family: 4 }]);
await expect(assertPublicUrlResolved("http://multi-a-record.example.test/")).rejects.toThrow();
});
it("blocks a hostname that resolves to a blocked IPv6 address", async () => {
lookupMock.mockResolvedValue([{ address: "::1", family: 6 }]);
await expect(assertPublicUrlResolved("http://evil.example.test/")).rejects.toThrow();
});
it("allows a hostname that resolves only to public addresses", async () => {
lookupMock.mockResolvedValue([{ address: "93.184.216.34", family: 4 }]);
await expect(assertPublicUrlResolved("https://example.com/")).resolves.not.toThrow();
});
// Note: "fails open when the DNS lookup itself rejects" is deliberately not
// covered here as a vitest case — a mocked node:dns rejection in this file
// trips what looks like a vitest 4 / rolldown-transform source-map bug
// (the same rejection pattern passes in an isolated single-function probe
// module; only reproduces once mocked against this larger file). Verified
// instead with a standalone Node script exercising the real try/catch
// directly: dns.promises.lookup rejecting resolves assertPublicUrlResolved
// with undefined rather than propagating, exactly as the source shows.
it("skips DNS lookup entirely for literal IP hosts (already covered by the sync check)", async () => {
await expect(assertPublicUrlResolved("http://127.0.0.1/")).rejects.toThrow();
expect(lookupMock).not.toHaveBeenCalled();
});
});
describe("fetchPublic: redirect-target re-validation from #3714", () => {
const originalFetch = global.fetch;
afterEach(() => { global.fetch = originalFetch; });
beforeEach(() => {
lookupMock.mockReset();
// These tests exercise redirect-chasing, not DNS behavior — give every
// synthetic *.example.test hostname a default public resolution so it
// doesn't get blocked (or throw on an unmocked undefined return) before
// reaching the redirect logic under test.
lookupMock.mockResolvedValue([{ address: "203.0.113.10", family: 4 }]);
});
it("blocks a redirect from a validated public URL to an internal target", async () => {
global.fetch = vi.fn(async () => new Response(null, {
status: 302,
headers: { Location: "http://127.0.0.1:9999/admin" },
}));
await expect(fetchPublic("https://public.example.test/redirect")).rejects.toThrow();
expect(global.fetch).toHaveBeenCalledTimes(1); // never followed the redirect
});
it("follows a redirect chain of public URLs, re-validating each hop", async () => {
global.fetch = vi.fn()
.mockResolvedValueOnce(new Response(null, { status: 302, headers: { Location: "https://hop2.example.test/" } }))
.mockResolvedValueOnce(new Response("ok", { status: 200 }));
const res = await fetchPublic("https://hop1.example.test/");
expect(await res.text()).toBe("ok");
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(global.fetch.mock.calls[1][0]).toBe("https://hop2.example.test/");
});
it("bounds the redirect chain instead of looping forever", async () => {
global.fetch = vi.fn(async (url) => new Response(null, {
status: 302,
headers: { Location: url === "https://loop.example.test/a" ? "https://loop.example.test/b" : "https://loop.example.test/a" },
}));
await expect(fetchPublic("https://loop.example.test/a", {}, { maxRedirects: 3 })).rejects.toThrow(/too many redirects/i);
});
it("rejects the initial URL before ever calling fetch", async () => {
global.fetch = vi.fn();
await expect(fetchPublic("http://127.0.0.1/steal")).rejects.toThrow();
expect(global.fetch).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,491 @@
import { describe, it, expect } from "vitest";
import { injectSystemPrompt } from "../../open-sse/rtk/systemInject.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
import { OPENAI_BLOCK, CLAUDE_BLOCK, RESPONSES_ITEM } from "../../open-sse/translator/schema/blocks.js";
import { ROLE } from "../../open-sse/translator/schema/roles.js";
import { injectCaveman } from "../../open-sse/rtk/caveman.js";
import { injectPonytail } from "../../open-sse/rtk/ponytail.js";
import { CAVEMAN_PROMPTS } from "../../open-sse/rtk/cavemanPrompts.js";
import { PONYTAIL_PROMPTS } from "../../open-sse/rtk/ponytailPrompt.js";
const SEP = "\n\n";
const P1 = "CAVEMAN_TEST_PROMPT_AAA";
const P2 = "PONYTAIL_TEST_PROMPT_BBB";
describe("system-inject chat messages", () => {
it("appends TEXT block to existing system string with SEP", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: "hello" }, { role: ROLE.USER, content: "hi" }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.messages[0].content).toBe(`hello${SEP}${P1}`);
});
it("appends TEXT block to existing system array with OPENAI_BLOCK.TEXT never input_text", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: [{ type: OPENAI_BLOCK.TEXT, text: "hello" }] }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
const arr = body.messages[0].content;
expect(arr[arr.length - 1]).toEqual({ type: OPENAI_BLOCK.TEXT, text: P1 });
expect(arr.some(c => c.type === "input_text")).toBe(false);
});
it("unshifts system message when no system/developer present", () => {
const body = { messages: [{ role: ROLE.USER, content: "hi" }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.messages[0]).toEqual({ role: ROLE.SYSTEM, content: P1 });
expect(body.messages[1].role).toBe(ROLE.USER);
});
it("handles developer role as system", () => {
const body = { messages: [{ role: ROLE.DEVELOPER, content: "dev" }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.messages[0].content).toBe(`dev${SEP}${P1}`);
});
it("exact full-prompt idempotency for chat string", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: "hello" }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.messages[0].content).toBe(`hello${SEP}${P1}`);
// different prompt both apply
injectSystemPrompt(body, FORMATS.OPENAI, P2);
expect(body.messages[0].content).toBe(`hello${SEP}${P1}${SEP}${P2}`);
});
it("exact full-prompt idempotency for chat array", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: [{ type: OPENAI_BLOCK.TEXT, text: "hello" }] }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
injectSystemPrompt(body, FORMATS.OPENAI, P1);
const texts = body.messages[0].content.filter(c => c.text === P1);
expect(texts.length).toBe(1);
injectSystemPrompt(body, FORMATS.OPENAI, P2);
expect(body.messages[0].content.filter(c => c.text === P2).length).toBe(1);
});
it("never uses first-100 fingerprint: long prompt exact idempotency", () => {
const longA = "X".repeat(150) + "_A";
const longB = "X".repeat(150) + "_B";
const body = { messages: [{ role: ROLE.SYSTEM, content: "base" }] };
injectSystemPrompt(body, FORMATS.OPENAI, longA);
injectSystemPrompt(body, FORMATS.OPENAI, longB);
expect(body.messages[0].content).toContain(longA);
expect(body.messages[0].content).toContain(longB);
// retry same longA is idempotent
injectSystemPrompt(body, FORMATS.OPENAI, longA);
const countA = body.messages[0].content.split(longA).length - 1;
expect(countA).toBe(1);
});
});
describe("system-inject responses input[]", () => {
it("modifies only type: message system/developer and preserves non-message order", () => {
const body = {
input: [
{ type: RESPONSES_ITEM.FUNCTION_CALL, call_id: "c1", name: "fn" },
{ type: RESPONSES_ITEM.MESSAGE, role: ROLE.SYSTEM, content: [{ type: RESPONSES_ITEM.INPUT_TEXT, text: "sys" }] },
{ type: RESPONSES_ITEM.REASONING, summary: "x" },
{ type: RESPONSES_ITEM.FUNCTION_CALL_OUTPUT, call_id: "c1", output: "ok" },
],
};
const before = JSON.parse(JSON.stringify(body.input));
injectSystemPrompt(body, FORMATS.OPENAI, P1);
// length unchanged except injection inside message
expect(body.input.length).toBe(before.length);
expect(body.input[0]).toEqual(before[0]);
expect(body.input[2]).toEqual(before[2]);
expect(body.input[3]).toEqual(before[3]);
// system message got INPUT_TEXT appended
const sys = body.input[1];
expect(sys.content[sys.content.length - 1]).toEqual({ type: RESPONSES_ITEM.INPUT_TEXT, text: P1 });
});
it("appends INPUT_TEXT to array content", () => {
const body = { input: [{ type: RESPONSES_ITEM.MESSAGE, role: ROLE.USER, content: [{ type: RESPONSES_ITEM.INPUT_TEXT, text: "hi" }] }, { type: RESPONSES_ITEM.MESSAGE, role: ROLE.SYSTEM, content: [{ type: RESPONSES_ITEM.INPUT_TEXT, text: "base" }] }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
const sys = body.input.find(m => m.role === ROLE.SYSTEM);
expect(sys.content[sys.content.length - 1].type).toBe(RESPONSES_ITEM.INPUT_TEXT);
expect(sys.content[sys.content.length - 1].text).toBe(P1);
});
it("creates typed message at index 0 if absent preserving order", () => {
const body = { input: [{ type: RESPONSES_ITEM.MESSAGE, role: ROLE.USER, content: [{ type: RESPONSES_ITEM.INPUT_TEXT, text: "hi" }] }, { type: RESPONSES_ITEM.FUNCTION_CALL, call_id: "1", name: "a" }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.input[0]).toEqual({ type: RESPONSES_ITEM.MESSAGE, role: ROLE.SYSTEM, content: [{ type: RESPONSES_ITEM.INPUT_TEXT, text: P1 }] });
expect(body.input[1].role).toBe(ROLE.USER);
});
it("instructions string takes precedence over input[]", () => {
const body = { instructions: "instr", input: [{ type: RESPONSES_ITEM.MESSAGE, role: ROLE.USER, content: [{ type: RESPONSES_ITEM.INPUT_TEXT, text: "hi" }] }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.instructions).toBe(`instr${SEP}${P1}`);
expect(body.input.length).toBe(1);
expect(body.input[0].content[0].text).toBe("hi");
});
it("does not coerce string input", () => {
const body = { input: "hello string" };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.input).toBe("hello string");
expect(body.instructions).toBeUndefined();
});
it("exact idempotency for responses input", () => {
const body = { input: [{ type: RESPONSES_ITEM.MESSAGE, role: ROLE.SYSTEM, content: [{ type: RESPONSES_ITEM.INPUT_TEXT, text: "base" }] }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
injectSystemPrompt(body, FORMATS.OPENAI, P1);
const sys = body.input[0];
expect(sys.content.filter(c => c.text === P1).length).toBe(1);
injectSystemPrompt(body, FORMATS.OPENAI, P2);
expect(sys.content.filter(c => c.text === P2).length).toBe(1);
});
});
describe("system-inject instructions", () => {
it("appends to instructions string with idempotency", () => {
const body = { instructions: "base" };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.instructions).toBe(`base${SEP}${P1}`);
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.instructions).toBe(`base${SEP}${P1}`);
injectSystemPrompt(body, FORMATS.OPENAI, P2);
expect(body.instructions).toBe(`base${SEP}${P1}${SEP}${P2}`);
});
it("creates instructions when empty", () => {
const body = { instructions: "" };
// empty string still taken as string field, should become prompt
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.instructions).toBe(P1);
});
});
describe("system-inject dispatch by wire shape", () => {
it("messages[] means Chat even when format is openai-responses label", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: "hi" }] };
injectSystemPrompt(body, FORMATS.OPENAI_RESPONSES, P1);
// should still treat as Chat because messages present
expect(body.messages[0].content).toBe(`hi${SEP}${P1}`);
});
it("input[] means Responses even when format is openai", () => {
const body = { input: [{ type: RESPONSES_ITEM.MESSAGE, role: ROLE.USER, content: [{ type: RESPONSES_ITEM.INPUT_TEXT, text: "hi" }] }] };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.input[0].role).toBe(ROLE.SYSTEM);
expect(body.input[0].content[0].type).toBe(RESPONSES_ITEM.INPUT_TEXT);
});
});
describe("system-inject claude", () => {
it("string system appends with SEP and idempotent", () => {
const body = { system: "base" };
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
expect(body.system).toBe(`base${SEP}${P1}`);
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
expect(body.system).toBe(`base${SEP}${P1}`);
injectSystemPrompt(body, FORMATS.CLAUDE, P2);
expect(body.system).toBe(`base${SEP}${P1}${SEP}${P2}`);
});
it("array system uses CLAUDE_BLOCK.TEXT and inserts before last cache_control", () => {
const body = { system: [{ type: CLAUDE_BLOCK.TEXT, text: "a" }, { type: CLAUDE_BLOCK.TEXT, text: "b", cache_control: { type: "ephemeral" } }, { type: CLAUDE_BLOCK.TEXT, text: "c", cache_control: { type: "ephemeral" } }] };
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
// should be inserted before last cache_control (index 2)
expect(body.system[2]).toEqual({ type: CLAUDE_BLOCK.TEXT, text: P1 });
expect(body.system[3].text).toBe("c");
expect(body.system[3].cache_control).toBeDefined();
});
it("array without cache_control appends", () => {
const body = { system: [{ type: CLAUDE_BLOCK.TEXT, text: "a" }] };
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
expect(body.system[body.system.length - 1]).toEqual({ type: CLAUDE_BLOCK.TEXT, text: P1 });
});
it("exact idempotency for claude array", () => {
const body = { system: [{ type: CLAUDE_BLOCK.TEXT, text: "a" }] };
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
expect(body.system.filter(b => b.text === P1).length).toBe(1);
});
it("creates system when absent", () => {
const body = {};
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
expect(body.system).toBe(P1);
});
it("real body with messages[] injects into system, never a system role turn", () => {
const body = { system: "base", messages: [{ role: ROLE.USER, content: "hi" }] };
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
expect(body.system).toBe(`base${SEP}${P1}`);
expect(body.messages).toEqual([{ role: ROLE.USER, content: "hi" }]);
});
it("absent system with messages[] creates system field, not a system message", () => {
const body = { messages: [{ role: ROLE.USER, content: "hi" }] };
injectSystemPrompt(body, FORMATS.CLAUDE, P1);
expect(body.system).toBe(P1);
expect(body.messages.some(m => m.role === ROLE.SYSTEM)).toBe(false);
});
});
describe("system-inject gemini", () => {
it("preserves snake_case key", () => {
const body = { system_instruction: { parts: [{ text: "base" }] } };
injectSystemPrompt(body, FORMATS.GEMINI, P1);
expect(body.system_instruction.parts.length).toBe(2);
expect(body.system_instruction.parts[1].text).toBe(P1);
expect(body.systemInstruction).toBeUndefined();
});
it("preserves camelCase key", () => {
const body = { systemInstruction: { parts: [{ text: "base" }] } };
injectSystemPrompt(body, FORMATS.GEMINI, P1);
expect(body.systemInstruction.parts[1].text).toBe(P1);
expect(body.system_instruction).toBeUndefined();
});
it("handles Antigravity wrapper request.systemInstruction", () => {
const body = { request: { systemInstruction: { parts: [{ text: "base" }] } } };
injectSystemPrompt(body, FORMATS.ANTIGRAVITY, P1);
expect(body.request.systemInstruction.parts[1].text).toBe(P1);
});
it("exact idempotency for gemini", () => {
const body = { systemInstruction: { parts: [{ text: "base" }] } };
injectSystemPrompt(body, FORMATS.GEMINI, P1);
injectSystemPrompt(body, FORMATS.GEMINI, P1);
expect(body.systemInstruction.parts.filter(p => p.text === P1).length).toBe(1);
injectSystemPrompt(body, FORMATS.GEMINI, P2);
expect(body.systemInstruction.parts.filter(p => p.text === P2).length).toBe(1);
});
it("creates when absent", () => {
const body = {};
injectSystemPrompt(body, FORMATS.GEMINI, P1);
expect(body.systemInstruction.parts[0].text).toBe(P1);
});
});
describe("system-inject kiro", () => {
it("updates systemPrompt and mirrored prefix of first history user preserving tail", () => {
const oldPrompt = "OLD_SYS";
const timeCtx = "[Context: Current time is 2026-01-01T00:00:00.000Z]";
const tail = "user tail content";
const historyUserContent = `${oldPrompt}${SEP}${timeCtx}${SEP}${tail}`;
const body = {
systemPrompt: oldPrompt,
conversationState: {
history: [{ userInputMessage: { content: historyUserContent, modelId: "m" } }, { assistantResponseMessage: { content: "..." } }],
currentMessage: { userInputMessage: { content: "current " + tail, modelId: "m" } },
},
};
injectSystemPrompt(body, FORMATS.KIRO, P1);
const next = `${oldPrompt}${SEP}${P1}`;
expect(body.systemPrompt).toBe(next);
expect(body.conversationState.history[0].userInputMessage.content).toBe(`${next}${SEP}${timeCtx}${SEP}${tail}`);
// currentMessage must stay untouched
expect(body.conversationState.currentMessage.userInputMessage.content).toBe("current " + tail);
});
it("when no history user, updates currentMessage instead", () => {
const oldPrompt = "OLD";
const body = {
systemPrompt: oldPrompt,
conversationState: {
history: [],
currentMessage: { userInputMessage: { content: `${oldPrompt}${SEP}tail`, modelId: "m" } },
},
};
injectSystemPrompt(body, FORMATS.KIRO, P1);
expect(body.systemPrompt).toBe(`${oldPrompt}${SEP}${P1}`);
expect(body.conversationState.currentMessage.userInputMessage.content).toBe(`${oldPrompt}${SEP}${P1}${SEP}tail`);
});
it("empty old prompt prepends to chosen user content", () => {
const body = {
systemPrompt: "",
conversationState: {
history: [{ userInputMessage: { content: "tail hello", modelId: "m" } }],
currentMessage: { userInputMessage: { content: "cur", modelId: "m" } },
},
};
injectSystemPrompt(body, FORMATS.KIRO, P1);
expect(body.systemPrompt).toBe(P1);
expect(body.conversationState.history[0].userInputMessage.content).toBe(`${P1}${SEP}tail hello`);
});
it("if old prompt not mirrored at head, do not alter user content", () => {
const body = {
systemPrompt: "OLD",
conversationState: {
history: [{ userInputMessage: { content: "different head content", modelId: "m" } }],
currentMessage: { userInputMessage: { content: "cur", modelId: "m" } },
},
};
injectSystemPrompt(body, FORMATS.KIRO, P1);
expect(body.systemPrompt).toBe(`OLD${SEP}${P1}`);
expect(body.conversationState.history[0].userInputMessage.content).toBe("different head content");
});
it("exact retry idempotency for kiro", () => {
const oldPrompt = "OLD";
const body = {
systemPrompt: oldPrompt,
conversationState: {
history: [{ userInputMessage: { content: `${oldPrompt}${SEP}tail`, modelId: "m" } }],
currentMessage: { userInputMessage: { content: "cur", modelId: "m" } },
},
};
injectSystemPrompt(body, FORMATS.KIRO, P1);
const after1 = JSON.parse(JSON.stringify(body));
injectSystemPrompt(body, FORMATS.KIRO, P1);
expect(body.systemPrompt).toBe(after1.systemPrompt);
expect(body.conversationState.history[0].userInputMessage.content).toBe(after1.conversationState.history[0].userInputMessage.content);
// different prompt both apply
injectSystemPrompt(body, FORMATS.KIRO, P2);
expect(body.systemPrompt).toBe(`${oldPrompt}${SEP}${P1}${SEP}${P2}`);
});
it("preserves non-enumerable _kiroUpstreamModel", () => {
const body = {
systemPrompt: "OLD",
conversationState: { history: [{ userInputMessage: { content: "OLD" + SEP + "tail", modelId: "m" } }], currentMessage: { userInputMessage: { content: "OLD" + SEP + "tail2", modelId: "m" } } },
};
Object.defineProperty(body, "_kiroUpstreamModel", { value: "m", enumerable: false });
injectSystemPrompt(body, FORMATS.KIRO, P1);
expect(body._kiroUpstreamModel).toBe("m");
expect(Object.getOwnPropertyDescriptor(body, "_kiroUpstreamModel").enumerable).toBe(false);
});
});
describe("system-inject regression fixes", () => {
it("kiro partial mutation converges on retry after transient content write failure", () => {
const oldPrompt = "OLD";
let failNextWrite = true;
const um = { content: `${oldPrompt}${SEP}tail`, modelId: "m" };
const proxiedUm = new Proxy(um, {
set(t, p, v) {
if (p === "content" && failNextWrite) { failNextWrite = false; throw new Error("transient"); }
t[p] = v; return true;
},
});
const body = {
systemPrompt: oldPrompt,
conversationState: {
history: [{ userInputMessage: proxiedUm }],
},
};
injectSystemPrompt(body, FORMATS.KIRO, P1);
// first pass rolled back atomically — nothing half-applied
expect(body.systemPrompt).toBe(oldPrompt);
expect(um.content).toBe(`${oldPrompt}${SEP}tail`);
// retry converges
injectSystemPrompt(body, FORMATS.KIRO, P1);
expect(body.systemPrompt).toBe(`${oldPrompt}${SEP}${P1}`);
expect(um.content).toBe(`${oldPrompt}${SEP}${P1}${SEP}tail`);
});
it("kiro rolls back systemPrompt when user content write fails (atomicity)", () => {
const oldPrompt = "OLD";
const body = {
systemPrompt: oldPrompt,
conversationState: {
history: [{ userInputMessage: Object.freeze({ content: `${oldPrompt}${SEP}tail`, modelId: "m" }) }],
},
};
injectSystemPrompt(body, FORMATS.KIRO, P1);
expect(body.systemPrompt).toBe(oldPrompt);
});
it("kiro shape gate: stray conversationState without history/currentMessage does not hijack chat body", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: "hello" }], systemPrompt: "", conversationState: {} };
injectSystemPrompt(body, FORMATS.OPENAI, P1);
expect(body.messages[0].content).toBe(`hello${SEP}${P1}`);
});
it("substring occurrence does not suppress injection (exact SEP-delimited idempotency)", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: "You are RULE follower" }] };
injectSystemPrompt(body, FORMATS.OPENAI, "RULE");
expect(body.messages[0].content).toBe(`You are RULE follower${SEP}RULE`);
});
it("instructions substring occurrence does not suppress injection", () => {
const body = { instructions: "You are RULE follower" };
injectSystemPrompt(body, FORMATS.OPENAI, "RULE");
expect(body.instructions).toBe(`You are RULE follower${SEP}RULE`);
});
it("kiro empty-old prepend fires when prompt appears mid-tail only", () => {
const body = {
systemPrompt: "",
conversationState: {
history: [{ userInputMessage: { content: `some ${P1} here`, modelId: "m" } }],
},
};
injectSystemPrompt(body, FORMATS.KIRO, P1);
expect(body.conversationState.history[0].userInputMessage.content).toBe(`${P1}${SEP}some ${P1} here`);
});
});
describe("system-inject fail-open", () => {
it("null/undefined bodies never throw", () => {
expect(() => injectSystemPrompt(null, FORMATS.OPENAI, P1)).not.toThrow();
expect(() => injectSystemPrompt(undefined, FORMATS.OPENAI, P1)).not.toThrow();
expect(() => injectSystemPrompt({}, FORMATS.OPENAI, null)).not.toThrow();
});
it("malformed messages array never throws", () => {
expect(() => injectSystemPrompt({ messages: null }, FORMATS.OPENAI, P1)).not.toThrow();
expect(() => injectSystemPrompt({ messages: "bad" }, FORMATS.OPENAI, P1)).not.toThrow();
expect(() => injectSystemPrompt({ messages: [{ role: null, content: null }] }, FORMATS.OPENAI, P1)).not.toThrow();
});
it("frozen body never throws and does not partially mutate", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: "hello" }] };
Object.freeze(body);
Object.freeze(body.messages);
Object.freeze(body.messages[0]);
expect(() => injectSystemPrompt(body, FORMATS.OPENAI, P1)).not.toThrow();
expect(body.messages[0].content).toBe("hello");
});
it("Proxy throwing setter never throws", () => {
const throwingMsg = new Proxy({ role: ROLE.SYSTEM, content: "hello" }, {
set() { throw new Error("msg setter fail"); },
});
const arrProxy = new Proxy([throwingMsg], {
get(t, p, r) { return Reflect.get(t, p, r); },
set() { throw new Error("arr setter fail"); },
});
const proxy = new Proxy({}, {
set(t, p, v) { if (p === "messages") throw new Error("setter fail"); return Reflect.set(t, p, v); },
get(t, p) { if (p === "messages") return arrProxy; return t[p]; },
});
expect(() => injectSystemPrompt(proxy, FORMATS.OPENAI, P1)).not.toThrow();
expect(() => injectSystemPrompt(proxy, FORMATS.OPENAI_RESPONSES, P1)).not.toThrow();
});
it("frozen claude never throws", () => {
const body = { system: [{ type: CLAUDE_BLOCK.TEXT, text: "a" }] };
Object.freeze(body.system);
expect(() => injectSystemPrompt(body, FORMATS.CLAUDE, P1)).not.toThrow();
});
it("frozen gemini never throws", () => {
const body = { systemInstruction: { parts: [{ text: "a" }] } };
Object.freeze(body.systemInstruction.parts);
expect(() => injectSystemPrompt(body, FORMATS.GEMINI, P1)).not.toThrow();
});
it("injectCaveman and injectPonytail fail open on frozen", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: "hi" }] };
Object.freeze(body);
Object.freeze(body.messages);
expect(() => injectCaveman(body, FORMATS.OPENAI, "full")).not.toThrow();
expect(() => injectPonytail(body, FORMATS.OPENAI, "full")).not.toThrow();
});
it("different caveman and ponytail prompts both apply", () => {
const body = { messages: [{ role: ROLE.SYSTEM, content: "base" }] };
injectCaveman(body, FORMATS.OPENAI, "full");
const afterCaveman = body.messages[0].content;
expect(afterCaveman).toContain(CAVEMAN_PROMPTS.full.slice(0, 30));
injectPonytail(body, FORMATS.OPENAI, "full");
expect(body.messages[0].content).toContain(PONYTAIL_PROMPTS.full.slice(0, 30));
expect(body.messages[0].content).toContain(afterCaveman);
});
});

View File

@@ -102,19 +102,38 @@ describe("refreshAccessToken — config-driven profiles", () => {
expect(fm).toHaveBeenCalledTimes(1);
});
});
describe("refreshAccessToken — legacy generic path (no profile)", () => {
describe("Cline refresh", () => {
beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); global.fetch = originalFetch; });
afterEach(() => { global.fetch = originalFetch; });
it("still works for an unprofiled provider via config.refreshUrl/clientId/clientSecret", async () => {
const fm = mockFetchOnce({ access_token: "gen-acc", expires_in: 3600 });
const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
it("uses the extension JSON refresh contract", async () => {
const expiresAt = new Date(Date.now() + 3600 * 1000).toISOString();
const fm = mockFetchOnce({
data: {
accessToken: "cline-acc",
refreshToken: "cline-rot",
expiresAt,
},
});
const { refreshTokenByProvider } = await import(
"open-sse/services/tokenRefresh.js"
);
await refreshAccessToken("cline", "gen-old", {}, console);
const out = await refreshTokenByProvider(
"cline",
{ refreshToken: "cline-old" },
console
);
const body = new URLSearchParams(fm.mock.calls[0][1].body);
expect(body.get("grant_type")).toBe("refresh_token");
expect(body.get("client_id")).toBeTruthy();
const [, init] = fm.mock.calls[0];
expect(init.headers["Content-Type"]).toBe("application/json");
expect(JSON.parse(init.body)).toEqual({
refreshToken: "cline-old",
grantType: "refresh_token",
clientType: "extension",
});
expect(out.accessToken).toBe("cline-acc");
expect(out.refreshToken).toBe("cline-rot");
expect(out.expiresIn).toBeGreaterThan(0);
});
});

View File

@@ -16,7 +16,7 @@ const SUPPORTED = [
"github", "gemini-cli", "antigravity", "claude", "codex", "kiro",
"qoder", "iflow", "ollama", "glm", "glm-cn",
"minimax", "minimax-cn", "vercel-ai-gateway", "grok-cli", "kimi",
"deepseek",
"deepseek", "opencode-go", "zed",
];
describe("usage dispatch", () => {

View File

@@ -0,0 +1,71 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
buildModelsList: vi.fn(),
}));
vi.mock("../../src/app/api/v1/models/route.js", () => ({
buildModelsList: mocks.buildModelsList,
}));
const { GET } = await import("../../src/app/api/v1/models/[...model]/route.js");
const chatModel = {
id: "cc/claude-sonnet-5",
object: "model",
owned_by: "cc",
context_length: 1_000_000,
};
function params(model) {
return { params: Promise.resolve({ model }) };
}
describe("GET /v1/models/{id}", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("retrieves a provider-prefixed model ID split across URL path segments", async () => {
mocks.buildModelsList.mockResolvedValue([chatModel]);
const response = await GET(new Request("https://router.test/v1/models/cc/claude-sonnet-5"), params(["cc", "claude-sonnet-5"]));
expect(response.status).toBe(200);
expect(await response.json()).toEqual(chatModel);
expect(mocks.buildModelsList).toHaveBeenCalledWith(["llm"]);
});
it("also handles a decoded slash in a single catch-all segment", async () => {
mocks.buildModelsList.mockResolvedValue([chatModel]);
const response = await GET(new Request("https://router.test/v1/models/cc%2Fclaude-sonnet-5"), params(["cc/claude-sonnet-5"]));
expect(response.status).toBe(200);
expect(await response.json()).toEqual(chatModel);
});
it("keeps capability-list routes unchanged", async () => {
const imageModel = { id: "image/gpt-image-1", object: "model", owned_by: "image" };
mocks.buildModelsList.mockResolvedValue([imageModel]);
const response = await GET(new Request("https://router.test/v1/models/image"), params(["image"]));
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ object: "list", data: [imageModel] });
expect(mocks.buildModelsList).toHaveBeenCalledWith(["image"]);
});
it("returns an OpenAI-style model_not_found response for an unknown model", async () => {
mocks.buildModelsList.mockResolvedValue([chatModel]);
const response = await GET(new Request("https://router.test/v1/models/cc/missing-model"), params(["cc", "missing-model"]));
const body = await response.json();
expect(response.status).toBe(404);
expect(body.error).toMatchObject({
type: "invalid_request_error",
code: "model_not_found",
});
});
});

View File

@@ -0,0 +1,154 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import REGISTRY from "../../open-sse/providers/registry/index.js";
import { buildSearchRequest } from "../../open-sse/handlers/search/callers.js";
import { handleSearchCore } from "../../open-sse/handlers/search/index.js";
import { normalizeSearchResponse } from "../../open-sse/handlers/search/normalizers.js";
import { AI_PROVIDERS, getProvidersByKind } from "@/shared/constants/providers.js";
const CONFIG = {
id: "xquik",
baseUrl: "https://xquik.com/api/v1/x/tweets/search",
method: "GET",
authType: "apikey",
searchTypes: ["x"],
defaultMaxResults: 5,
maxMaxResults: 100,
creditsPerResult: 1,
};
const PARAMS = {
query: "from:github release notes",
searchType: "x",
maxResults: 10,
token: "xq_test_key",
language: "en",
providerOptions: { queryType: "Latest", cursor: "next page" },
};
const RESPONSE = {
tweets: [
{
id: "1234567890",
text: "Release notes are live.",
createdAt: "2026-08-25T12:00:00Z",
author: { username: "github", name: "GitHub" },
media: [{ mediaUrl: "https://pbs.twimg.com/media/example.jpg", type: "photo" }],
},
],
has_next_page: true,
next_cursor: "cursor-2",
};
afterEach(() => {
vi.unstubAllGlobals();
});
describe("Xquik search provider", () => {
it("registers a dedicated X search provider with no-charge key validation", () => {
const entry = REGISTRY.find((candidate) => candidate.id === "xquik");
expect(entry).toMatchObject({
category: "apikey",
serviceKinds: ["webSearch"],
searchConfig: {
authHeader: "x-api-key",
validateUrl: "https://xquik.com/api/v1/credits",
searchTypes: ["x"],
creditsPerResult: 1,
},
});
expect(AI_PROVIDERS.xquik?.searchConfig).toEqual(entry.searchConfig);
expect(getProvidersByKind("webSearch").map((provider) => provider.id)).toContain("xquik");
});
it("builds the documented GET request without putting the key in the URL", () => {
const request = buildSearchRequest(CONFIG, PARAMS);
const url = new URL(request.url);
expect(url.origin + url.pathname).toBe("https://xquik.com/api/v1/x/tweets/search");
expect(Object.fromEntries(url.searchParams)).toEqual({
q: "from:github release notes",
limit: "10",
cursor: "next page",
queryType: "Latest",
language: "en",
});
expect(url.search).not.toContain("xq_test_key");
expect(request.init).toEqual({
method: "GET",
headers: { Accept: "application/json", "x-api-key": "xq_test_key" },
});
});
it("rejects unsupported query types before contacting Xquik", () => {
expect(() => buildSearchRequest(CONFIG, {
...PARAMS,
providerOptions: { queryType: "Popular" },
})).toThrow("Xquik queryType must be Latest or Top");
});
it("normalizes posts and preserves cursor pagination", () => {
const normalized = normalizeSearchResponse("xquik", RESPONSE, PARAMS.query, "x");
expect(normalized.totalResults).toBeNull();
expect(normalized.pagination).toEqual({ has_more: true, next_cursor: "cursor-2" });
expect(normalized.results).toHaveLength(1);
expect(normalized.results[0]).toMatchObject({
title: "@github on X",
url: "https://x.com/github/status/1234567890",
display_url: "x.com/github/status/1234567890",
snippet: "Release notes are live.",
published_at: "2026-08-25T12:00:00Z",
metadata: {
author: "@github",
source_type: "x_post",
image_url: "https://pbs.twimg.com/media/example.jpg",
},
citation: { provider: "xquik", rank: 1 },
});
expect(normalized.results[0].content).toEqual({
format: "text",
text: "Release notes are live.",
length: 23,
});
});
it("uses the stable status URL when author data is unavailable", () => {
const normalized = normalizeSearchResponse("xquik", {
tweets: [{ id: "9876543210", text: "Author data is unavailable." }],
has_next_page: false,
next_cursor: "",
}, PARAMS.query, "x");
expect(normalized.results[0]).toMatchObject({
title: "X post",
url: "https://x.com/i/web/status/9876543210",
metadata: { author: null, source_type: "x_post" },
});
expect(normalized.pagination).toEqual({ has_more: false, next_cursor: null });
});
it("reports Xquik credits without claiming an unknown USD cost", async () => {
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify(RESPONSE), {
status: 200,
headers: { "Content-Type": "application/json" },
})));
const result = await handleSearchCore({
body: { query: PARAMS.query, max_results: 10, provider_options: PARAMS.providerOptions },
provider: { id: "xquik" },
providerConfig: CONFIG,
credentials: { apiKey: "xq_test_key" },
});
const payload = await result.response.json();
expect(result.success).toBe(true);
expect(payload.usage).toEqual({
queries_used: 1,
search_cost_usd: null,
provider_credits_used: 1,
});
expect(payload.pagination).toEqual({ has_more: true, next_cursor: "cursor-2" });
});
});

View File

@@ -0,0 +1,189 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("../../open-sse/shared/zedAuth.js", async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
fetchZedAuthenticatedUser: vi.fn(),
};
});
import { fetchZedAuthenticatedUser } from "../../open-sse/shared/zedAuth.js";
import { getUsageForProvider } from "../../open-sse/services/usage.js";
import { USAGE_SUPPORTED_PROVIDERS } from "../../src/shared/constants/providers.js";
import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";
import {
formatZedPlanLabel,
parseZedUsageLimit,
parseZedAuthenticatedUserUsage,
} from "../../open-sse/services/usage/zed.js";
describe("zed registry usage flags", () => {
it("is listed in USAGE_SUPPORTED_PROVIDERS", () => {
expect(USAGE_SUPPORTED_PROVIDERS).toContain("zed");
});
});
describe("parseZedUsageLimit", () => {
it("parses unlimited string and object forms", () => {
expect(parseZedUsageLimit("unlimited")).toEqual({ unlimited: true, total: 0 });
expect(parseZedUsageLimit({ unlimited: true })).toEqual({ unlimited: true, total: 0 });
});
it("parses numeric and limited object forms", () => {
expect(parseZedUsageLimit(50)).toEqual({ unlimited: false, total: 50 });
expect(parseZedUsageLimit("25")).toEqual({ unlimited: false, total: 25 });
expect(parseZedUsageLimit({ limited: 40 })).toEqual({ unlimited: false, total: 40 });
});
});
describe("formatZedPlanLabel", () => {
it("maps known plan ids", () => {
expect(formatZedPlanLabel("zed_pro")).toBe("Zed Pro");
expect(formatZedPlanLabel("zed_pro_trial")).toBe("Zed Pro Trial");
});
});
describe("parseZedAuthenticatedUserUsage", () => {
it("maps edit_predictions and billing cycle reset", () => {
const parsed = parseZedAuthenticatedUserUsage({
plan: {
plan_v3: "zed_pro",
subscription_period: {
started_at: "2026-07-01T00:00:00Z",
ended_at: "2026-08-01T00:00:00Z",
},
usage: {
edit_predictions: { used: 12, limit: 50 },
},
},
});
expect(parsed.plan).toBe("Zed Pro");
expect(parsed.quotas["Edit Predictions"]).toMatchObject({
used: 12,
total: 50,
remainingPercentage: 76,
resetAt: "2026-08-01T00:00:00.000Z",
});
});
it("marks unlimited edit predictions at 100% remaining", () => {
const parsed = parseZedAuthenticatedUserUsage({
plan: {
plan_v3: "zed_pro",
usage: {
edit_predictions: { used: 999, limit: "unlimited" },
},
},
});
expect(parsed.quotas["Edit Predictions"]).toMatchObject({
used: 999,
total: 0,
remainingPercentage: 100,
unlimited: true,
});
});
it("skips token-billed model_requests limit=0 and adds billing note", () => {
const parsed = parseZedAuthenticatedUserUsage({
plan: {
plan_v3: "zed_student",
usage: {
model_requests: { used: 0, limit: { limited: 0 } },
edit_predictions: { used: 0, limit: "unlimited" },
},
},
});
expect(parsed.quotas["Hosted Model Requests"]).toBeUndefined();
expect(parsed.quotas["Edit Predictions"]).toBeDefined();
expect(parsed.message).toMatch(/token/i);
expect(parsed.message).toMatch(/dashboard\.zed\.dev/);
});
it("surfaces overdue invoice warning", () => {
const parsed = parseZedAuthenticatedUserUsage({
plan: {
plan_v3: "zed_pro",
has_overdue_invoices: true,
usage: {
edit_predictions: { used: 0, limit: "unlimited" },
},
},
});
expect(parsed.hasOverdueInvoices).toBe(true);
expect(parsed.message).toMatch(/overdue invoices/i);
});
});
describe("getUsageForProvider(zed)", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("returns quotas from /client/users/me", async () => {
fetchZedAuthenticatedUser.mockResolvedValueOnce({
plan: {
plan_v3: "zed_student",
usage: {
edit_predictions: { used: 3, limit: 30 },
},
},
});
const usage = await getUsageForProvider({
provider: "zed",
accessToken: "plain-token",
providerSpecificData: { userId: "user-42", systemId: "sys-1" },
});
expect(usage.plan).toBe("Zed Student");
expect(usage.quotas["Edit Predictions"]).toMatchObject({
used: 3,
total: 30,
remainingPercentage: 90,
});
expect(fetchZedAuthenticatedUser).toHaveBeenCalledWith(
{
accessToken: "plain-token",
providerSpecificData: { userId: "user-42", systemId: "sys-1" },
},
{ proxyOptions: null },
);
});
it("requires user id on the connection", async () => {
const usage = await getUsageForProvider({
provider: "zed",
accessToken: "plain-token",
providerSpecificData: {},
});
expect(usage.message).toMatch(/missing user id/i);
expect(fetchZedAuthenticatedUser).not.toHaveBeenCalled();
});
});
describe("parseQuotaData(zed)", () => {
it("normalizes zed quotas for QuotaTable", () => {
const data = parseZedAuthenticatedUserUsage({
plan: {
plan_v3: "zed_pro",
usage: { edit_predictions: { used: 10, limit: 20 } },
},
});
const rows = parseQuotaData("zed", data);
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
name: "Edit Predictions",
used: 10,
total: 20,
remainingPercentage: 50,
});
});
});