merge: integrate origin/master (v0.5.50) into gitea/new_feature

- Resolve conflicts in chatCore handlers: keep apiKey/streamErrorPatterns
  from the details-filters feature, adopt origin's stripContinuityFields,
  customToolNames, cache-inclusive usage accounting, and Responses-API
  SSE→JSON conversion
- Adopt origin's provider usage handlers (codebuddy-intl, qoder creds)
  and modality detection (audio/video inputs)
- Keep requestDetails apiKey column (schema v2) + masked key persistence

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
2026-08-06 09:54:31 +07:00
180 changed files with 8622 additions and 6233 deletions

View File

@@ -0,0 +1,79 @@
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.6-flash-high": {
displayName: "Gemini 3.6 Flash (High)",
quotaInfo: { remainingFraction: 0.8, resetTime: "2026-07-25T12:00:00Z" },
},
"gemini-3.6-flash-medium": {
displayName: "Gemini 3.6 Flash (Medium)",
quotaInfo: { remainingFraction: 0.5, resetTime: "2026-07-25T12:00:00Z" },
},
"gemini-3.6-flash-low": {
displayName: "Gemini 3.6 Flash (Low)",
quotaInfo: { remainingFraction: 0.2, resetTime: "2026-07-25T12:00:00Z" },
},
"gemini-3.5-flash-low": {
displayName: "Gemini 3.5 Flash (Medium)",
quotaInfo: { remainingFraction: 0.9, resetTime: "2026-07-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.6 Flash usage bars", () => {
beforeEach(() => proxyAwareFetch.mockClear());
it("returns Gemini 3.6 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.6-flash-high"]).toMatchObject({
used: 200,
total: 1000,
remainingPercentage: 80,
displayName: "Gemini 3.6 Flash (High)",
});
expect(usage.quotas["gemini-3.6-flash-medium"]).toMatchObject({
used: 500,
total: 1000,
remainingPercentage: 50,
});
expect(usage.quotas["gemini-3.6-flash-low"]).toMatchObject({
used: 800,
total: 1000,
remainingPercentage: 20,
});
});
it("still filters out internal and non-important models", async () => {
const { getAntigravityUsage } = await import("../../open-sse/services/usage/google.js");
const usage = await getAntigravityUsage("access-token", {});
expect(usage.quotas).not.toHaveProperty("internal-model");
expect(Object.keys(usage.quotas)).toEqual([
"gemini-3.6-flash-high",
"gemini-3.6-flash-medium",
"gemini-3.6-flash-low",
"gemini-3.5-flash-low",
]);
});
});

View File

@@ -0,0 +1,69 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
const mocks = vi.hoisted(() => ({
json: vi.fn((body, init) => ({
status: init?.status || 200,
body,
})),
cookies: vi.fn(),
getSettings: vi.fn(),
isOidcConfigured: vi.fn(),
getDashboardAuthSession: vi.fn(),
}));
vi.mock("next/server", () => ({
NextResponse: { json: mocks.json },
}));
vi.mock("next/headers", () => ({
cookies: mocks.cookies,
}));
vi.mock("@/lib/localDb", () => ({
getSettings: mocks.getSettings,
}));
vi.mock("@/lib/auth/oidc", () => ({
isOidcConfigured: mocks.isOidcConfigured,
}));
vi.mock("@/lib/auth/dashboardSession", () => ({
getDashboardAuthSession: mocks.getDashboardAuthSession,
}));
const { GET } = await import("../../src/app/api/auth/status/route.js");
describe("GET /api/auth/status", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.getSettings.mockResolvedValue({ requireLogin: true, authMode: "password" });
mocks.cookies.mockResolvedValue({ get: vi.fn(() => ({ value: "session-token" })) });
mocks.isOidcConfigured.mockReturnValue(false);
});
it("reports an authenticated session when the auth cookie is valid", async () => {
mocks.getDashboardAuthSession.mockResolvedValue({ authenticated: true });
const response = await GET();
expect(response.body.authenticated).toBe(true);
expect(mocks.getDashboardAuthSession).toHaveBeenCalledWith("session-token");
});
it("reports unauthenticated when the auth cookie is invalid", async () => {
mocks.getDashboardAuthSession.mockResolvedValue(null);
const response = await GET();
expect(response.body.authenticated).toBe(false);
});
it("fails closed when status dependencies throw", async () => {
mocks.getSettings.mockRejectedValue(new Error("database unavailable"));
const response = await GET();
expect(response.body.authenticated).toBe(false);
expect(response.body.requireLogin).toBe(true);
});
});

View File

@@ -0,0 +1,203 @@
/**
* Background OAuth token-refresh scheduler.
*
* Covers pure selection (selectConnectionsNeedingRefresh) and a fake tick that
* exercises checkAndRefreshToken dispatch + fail-open per connection.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
const NOW = Date.parse("2026-08-01T12:00:00.000Z");
function conn(overrides = {}) {
return {
id: "c1",
provider: "grok-cli",
authType: "oauth",
refreshToken: "rt-1",
expiresAt: new Date(NOW + 10 * 60 * 1000).toISOString(),
isActive: true,
...overrides,
};
}
describe("selectConnectionsNeedingRefresh", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(NOW);
});
afterEach(() => {
vi.useRealTimers();
vi.resetModules();
});
it("selects oauth grok-cli connection expiring in 10 minutes", async () => {
const { selectConnectionsNeedingRefresh } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
const list = selectConnectionsNeedingRefresh(
[conn({ expiresAt: new Date(NOW + 10 * 60 * 1000).toISOString() })],
NOW
);
expect(list).toHaveLength(1);
expect(list[0].id).toBe("c1");
});
it("skips connection expiring in 2 hours", async () => {
const { selectConnectionsNeedingRefresh } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
const list = selectConnectionsNeedingRefresh(
[conn({ expiresAt: new Date(NOW + 2 * 60 * 60 * 1000).toISOString() })],
NOW
);
expect(list).toHaveLength(0);
});
it("never selects apikey connections", async () => {
const { selectConnectionsNeedingRefresh } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
const list = selectConnectionsNeedingRefresh(
[
conn({ authType: "apikey", refreshToken: "rt" }),
conn({ id: "c2", authType: "api_key", refreshToken: "rt" }),
],
NOW
);
expect(list).toHaveLength(0);
});
it("skips oauth connection without refreshToken", async () => {
const { selectConnectionsNeedingRefresh } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
const list = selectConnectionsNeedingRefresh(
[conn({ refreshToken: null }), conn({ id: "c2", refreshToken: undefined })],
NOW
);
expect(list).toHaveLength(0);
});
it("selects already-expired oauth connection", async () => {
const { selectConnectionsNeedingRefresh } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
const list = selectConnectionsNeedingRefresh(
[conn({ expiresAt: new Date(NOW - 60 * 1000).toISOString() })],
NOW
);
expect(list).toHaveLength(1);
});
});
describe("runBackgroundTokenRefreshTick", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(NOW);
vi.resetModules();
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
it("calls refresh only for due connections and swallows per-connection errors", async () => {
const due = conn({
id: "due",
expiresAt: new Date(NOW + 10 * 60 * 1000).toISOString(),
});
const notDue = conn({
id: "not-due",
expiresAt: new Date(NOW + 2 * 60 * 60 * 1000).toISOString(),
});
const apikey = conn({
id: "key",
authType: "apikey",
expiresAt: new Date(NOW + 60 * 1000).toISOString(),
});
const refreshConnection = vi.fn(async (c) => {
if (c.id === "due") throw new Error("boom");
return c;
});
const loadConnections = vi.fn(async () => [due, notDue, apikey]);
const { runBackgroundTokenRefreshTick } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
await expect(
runBackgroundTokenRefreshTick({ loadConnections, refreshConnection })
).resolves.toBeUndefined();
expect(loadConnections).toHaveBeenCalledTimes(1);
expect(refreshConnection).toHaveBeenCalledTimes(1);
expect(refreshConnection.mock.calls[0][0].id).toBe("due");
});
it("does not call refresh when nothing is due", async () => {
const refreshConnection = vi.fn();
const loadConnections = vi.fn(async () => [
conn({
expiresAt: new Date(NOW + 3 * 60 * 60 * 1000).toISOString(),
}),
]);
const { runBackgroundTokenRefreshTick } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
await runBackgroundTokenRefreshTick({ loadConnections, refreshConnection });
expect(refreshConnection).not.toHaveBeenCalled();
});
it("swallows top-level load errors", async () => {
const refreshConnection = vi.fn();
const loadConnections = vi.fn(async () => {
throw new Error("db down");
});
const { runBackgroundTokenRefreshTick } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
await expect(
runBackgroundTokenRefreshTick({ loadConnections, refreshConnection })
).resolves.toBeUndefined();
expect(refreshConnection).not.toHaveBeenCalled();
});
});
describe("start/stop guards", () => {
afterEach(async () => {
vi.unstubAllEnvs();
const mod = await import("../../src/sse/services/backgroundTokenRefresh.js");
mod.stopBackgroundTokenRefresh();
vi.resetModules();
});
it("honors DISABLE_BACKGROUND_TOKEN_REFRESH kill-switch", async () => {
vi.stubEnv("DISABLE_BACKGROUND_TOKEN_REFRESH", "1");
const { startBackgroundTokenRefresh, stopBackgroundTokenRefresh } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
expect(startBackgroundTokenRefresh()).toBe(false);
stopBackgroundTokenRefresh();
});
it("is idempotent: second start is no-op", async () => {
vi.stubEnv("DISABLE_BACKGROUND_TOKEN_REFRESH", "");
const { startBackgroundTokenRefresh, stopBackgroundTokenRefresh } = await import(
"../../src/sse/services/backgroundTokenRefresh.js"
);
const first = startBackgroundTokenRefresh({ intervalMs: 60_000 });
const second = startBackgroundTokenRefresh({ intervalMs: 60_000 });
expect(first).toBe(true);
expect(second).toBe(false);
stopBackgroundTokenRefresh();
});
});

View File

@@ -1,10 +1,8 @@
/**
* Unit tests for Anthropic header caching + forwarding pipeline
* Unit tests for Anthropic header forwarding pipeline
*
* Tests cover:
* - claudeHeaderCache: detection, capture, and retrieval of Claude Code headers
* - default.js buildHeaders(): live header overlay for "claude" provider
* - default.js buildHeaders(): cold-start fallback when cache is empty
* - default.js buildHeaders(): static provider defaults + model-gated anthropic-beta
* - default.js buildHeaders(): anthropic-compatible non-Anthropic host stripping
* - default.js buildHeaders(): anthropic-compatible official host keeps headers
* - proxyFetch.js: api.anthropic.com routes through anthropicFetch path
@@ -12,109 +10,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// ─── claudeHeaderCache ────────────────────────────────────────────────────────
describe("claudeHeaderCache", () => {
let cacheModule;
beforeEach(async () => {
// Re-import fresh module each time to reset singleton state
vi.resetModules();
cacheModule = await import("open-sse/utils/claudeHeaderCache.js");
});
it("returns null before any headers are cached (cold start)", () => {
expect(cacheModule.getCachedClaudeHeaders()).toBeNull();
});
it("caches headers when user-agent contains 'claude-code'", () => {
cacheModule.cacheClaudeHeaders({
"user-agent": "claude-code/2.1.63 node/24.3.0",
"anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
"anthropic-version": "2023-06-01",
"x-app": "cli",
"x-stainless-os": "MacOS",
"x-stainless-arch": "arm64",
"x-stainless-lang": "js",
"x-stainless-runtime": "node",
"x-stainless-runtime-version": "v24.3.0",
"x-stainless-package-version": "0.74.0",
"x-stainless-helper-method": "stream",
"x-stainless-retry-count": "0",
"x-stainless-timeout": "600",
"anthropic-dangerous-direct-browser-access": "true",
// Non-identity header — should NOT be captured
"content-type": "application/json",
});
const cached = cacheModule.getCachedClaudeHeaders();
expect(cached).not.toBeNull();
expect(cached["user-agent"]).toBe("claude-code/2.1.63 node/24.3.0");
expect(cached["anthropic-beta"]).toBe("claude-code-20250219,oauth-2025-04-20");
expect(cached["x-app"]).toBe("cli");
expect(cached["x-stainless-os"]).toBe("MacOS");
// Non-identity header must not leak in
expect(cached["content-type"]).toBeUndefined();
});
it("caches headers when user-agent contains 'claude-cli'", () => {
cacheModule.cacheClaudeHeaders({
"user-agent": "claude-cli/1.0.0",
"anthropic-version": "2023-06-01",
});
expect(cacheModule.getCachedClaudeHeaders()).not.toBeNull();
expect(cacheModule.getCachedClaudeHeaders()["user-agent"]).toBe("claude-cli/1.0.0");
});
it("caches headers when x-app is 'cli' (regardless of user-agent)", () => {
cacheModule.cacheClaudeHeaders({
"user-agent": "axios/1.7.0",
"x-app": "cli",
"anthropic-version": "2023-06-01",
});
expect(cacheModule.getCachedClaudeHeaders()).not.toBeNull();
});
it("does NOT cache headers for non-Claude clients", () => {
cacheModule.cacheClaudeHeaders({
"user-agent": "PostmanRuntime/7.43.0",
"anthropic-version": "2023-06-01",
});
expect(cacheModule.getCachedClaudeHeaders()).toBeNull();
});
it("refreshes cache on each matching request", () => {
cacheModule.cacheClaudeHeaders({
"user-agent": "claude-code/2.0.0",
"x-stainless-package-version": "0.70.0",
});
cacheModule.cacheClaudeHeaders({
"user-agent": "claude-code/2.1.63",
"x-stainless-package-version": "0.74.0",
});
const cached = cacheModule.getCachedClaudeHeaders();
expect(cached["user-agent"]).toBe("claude-code/2.1.63");
expect(cached["x-stainless-package-version"]).toBe("0.74.0");
});
it("ignores calls with null or non-object headers", () => {
cacheModule.cacheClaudeHeaders(null);
cacheModule.cacheClaudeHeaders(undefined);
cacheModule.cacheClaudeHeaders("string");
expect(cacheModule.getCachedClaudeHeaders()).toBeNull();
});
it("only stores keys that are actually present in the headers object", () => {
cacheModule.cacheClaudeHeaders({
"user-agent": "claude-code/2.1.63",
// Most stainless headers absent
});
const cached = cacheModule.getCachedClaudeHeaders();
expect(cached["x-stainless-os"]).toBeUndefined();
expect(cached["user-agent"]).toBe("claude-code/2.1.63");
});
});
// ─── DefaultExecutor.buildHeaders() ──────────────────────────────────────────
describe("DefaultExecutor.buildHeaders() — claude provider", () => {
@@ -122,55 +17,51 @@ describe("DefaultExecutor.buildHeaders() — claude provider", () => {
beforeEach(async () => {
vi.resetModules();
// Prime the cache with live client headers before importing executor
const cache = await import("open-sse/utils/claudeHeaderCache.js");
cache.cacheClaudeHeaders({
"user-agent": "claude-code/2.1.63 node/24.3.0",
"anthropic-beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14",
"anthropic-version": "2023-06-01",
"anthropic-dangerous-direct-browser-access": "true",
"x-app": "cli",
"x-stainless-os": "MacOS",
"x-stainless-arch": "arm64",
"x-stainless-lang": "js",
"x-stainless-runtime": "node",
"x-stainless-runtime-version": "v24.3.0",
"x-stainless-package-version": "0.74.0",
"x-stainless-helper-method": "stream",
"x-stainless-retry-count": "0",
"x-stainless-timeout": "600",
});
const mod = await import("open-sse/executors/default.js");
DefaultExecutor = mod.DefaultExecutor || mod.default;
});
it("overlays live cached headers over static provider defaults", () => {
it("uses static provider defaults when no model is given", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, true);
// Live values should win over static providers.js values
expect(headers["user-agent"]).toBe("claude-code/2.1.63 node/24.3.0");
// Beta flags are MERGED (static + cached) to preserve required flags like oauth
const betaFlags = headers["anthropic-beta"].split(",").map(s => s.trim());
expect(betaFlags).toContain("claude-code-20250219");
expect(betaFlags).toContain("oauth-2025-04-20");
expect(betaFlags).toContain("interleaved-thinking-2025-05-14");
expect(headers["x-stainless-package-version"]).toBe("0.74.0");
expect(headers["x-stainless-os"]).toBe("MacOS");
const hasVersion =
headers["Anthropic-Version"] === "2023-06-01" ||
headers["anthropic-version"] === "2023-06-01";
expect(hasVersion).toBe(true);
});
it("removes conflicting Title-Case static keys when cached lowercase keys exist", () => {
it("includes heavy-agent beta flags for claude-opus-5", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, true);
const headers = executor.buildHeaders({ apiKey: "sk-test" }, true, undefined, "claude-opus-5");
const betaFlags = headers["Anthropic-Beta"].split(",").map(s => s.trim());
expect(betaFlags).toContain("advanced-tool-use-2025-11-20");
expect(betaFlags).toContain("effort-2025-11-24");
});
// Title-Case variants from providers.js must be gone
expect(headers["Anthropic-Version"]).toBeUndefined();
expect(headers["Anthropic-Beta"]).toBeUndefined();
expect(headers["User-Agent"]).toBeUndefined();
expect(headers["X-App"]).toBeUndefined();
// Lowercase variants must be present
expect(headers["anthropic-version"]).toBe("2023-06-01");
expect(headers["x-app"]).toBe("cli");
it("includes heavy-agent beta flags for claude-sonnet-5", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, true, undefined, "claude-sonnet-5");
const betaFlags = headers["Anthropic-Beta"].split(",").map(s => s.trim());
expect(betaFlags).toContain("advanced-tool-use-2025-11-20");
expect(betaFlags).toContain("effort-2025-11-24");
});
it("omits heavy-agent beta flags for claude-haiku-4-5-20251001", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, true, undefined, "claude-haiku-4-5-20251001");
const betaFlags = headers["Anthropic-Beta"].split(",").map(s => s.trim());
expect(betaFlags).not.toContain("advanced-tool-use-2025-11-20");
expect(betaFlags).not.toContain("effort-2025-11-24");
expect(betaFlags).toContain("claude-code-20250219");
});
it("omits heavy-agent beta flags for claude-fable-5", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, true, undefined, "claude-fable-5");
const betaFlags = headers["Anthropic-Beta"].split(",").map(s => s.trim());
expect(betaFlags).not.toContain("advanced-tool-use-2025-11-20");
expect(betaFlags).not.toContain("effort-2025-11-24");
});
it("sets x-api-key auth when apiKey is provided", () => {
@@ -198,31 +89,8 @@ describe("DefaultExecutor.buildHeaders() — claude provider", () => {
const headers = executor.buildHeaders({ apiKey: "k" }, false);
expect(headers["Accept"]).toBeUndefined();
});
});
describe("DefaultExecutor.buildHeaders() — claude provider cold start (no cache)", () => {
let DefaultExecutor;
beforeEach(async () => {
vi.resetModules();
// Do NOT prime cache — simulate cold start
const mod = await import("open-sse/executors/default.js");
DefaultExecutor = mod.DefaultExecutor || mod.default;
});
it("falls back to static provider headers when cache is empty", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ apiKey: "sk-test" }, true);
// Static fallback values from providers.js must still be present
// They may be Title-Case since no cache to conflict with them
const hasVersion =
headers["Anthropic-Version"] === "2023-06-01" ||
headers["anthropic-version"] === "2023-06-01";
expect(hasVersion).toBe(true);
});
it("does not throw when cache returns null", () => {
it("does not throw when no model is given", () => {
const executor = new DefaultExecutor("claude");
expect(() => executor.buildHeaders({ apiKey: "sk" }, false)).not.toThrow();
});

View File

@@ -0,0 +1,141 @@
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
let testApi;
try {
testApi = await import("vitest");
} catch (error) {
if (error.code !== "ERR_MODULE_NOT_FOUND") throw error;
testApi = await import("node:test");
}
const { afterEach, describe, it } = testApi;
const require = createRequire(import.meta.url);
const {
assertRequiredApiArtifacts,
copyStandaloneBuild,
mergeServerArtifacts,
} = require("../../cli/scripts/build-cli.js");
const tempDirs = [];
function createTempDir() {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "9router-cli-build-"));
tempDirs.push(tempDir);
return tempDir;
}
function writeFixture(root, relativePath, contents = relativePath) {
const filePath = path.join(root, relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, contents);
return filePath;
}
function createCompleteServer(buildDistDir) {
const serverDir = path.join(buildDistDir, "server");
writeFixture(serverDir, "app/api/v1/chat/completions/route.js", "chat route");
writeFixture(serverDir, "app/api/v1/messages/route.js", "messages route");
writeFixture(serverDir, "chunks/openai-provider.js", "openai chunk");
writeFixture(serverDir, "chunks/anthropic-provider.js", "anthropic chunk");
return serverDir;
}
afterEach(() => {
for (const tempDir of tempDirs.splice(0)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
describe("CLI build server artifacts", () => {
for (const { name, standalonePath } of [
{
name: "legacy nested app",
standalonePath: (appDir, buildDistDir) => path.join(appDir, ".next", "standalone", "app"),
},
{
name: "Next 16 workspace",
standalonePath: (appDir, buildDistDir) => path.join(buildDistDir, "standalone", path.basename(appDir)),
},
]) {
it(`merges complete API routes and provider chunks for the ${name} layout`, () => {
const root = createTempDir();
const appDir = path.join(root, "9router");
const buildDistDir = path.join(appDir, ".next-cli-build");
const cliAppDir = path.join(root, "cli-app");
const standaloneDir = standalonePath(appDir, buildDistDir);
writeFixture(standaloneDir, "server.js", "standalone server");
writeFixture(
standaloneDir,
".next-cli-build/server/app/api/v1/chat/completions/route.js",
"standalone chat route",
);
createCompleteServer(buildDistDir);
copyStandaloneBuild(appDir, buildDistDir, cliAppDir);
mergeServerArtifacts(buildDistDir, cliAppDir);
assertRequiredApiArtifacts(cliAppDir);
const packagedServer = path.join(cliAppDir, ".next-cli-build", "server");
assert.equal(
fs.readFileSync(path.join(packagedServer, "app/api/v1/messages/route.js"), "utf8"),
"messages route",
);
assert.equal(
fs.readFileSync(path.join(packagedServer, "chunks/openai-provider.js"), "utf8"),
"openai chunk",
);
assert.equal(
fs.readFileSync(path.join(packagedServer, "chunks/anthropic-provider.js"), "utf8"),
"anthropic chunk",
);
});
}
it("merges idempotently without removing standalone-generated files", () => {
const root = createTempDir();
const buildDistDir = path.join(root, ".next-cli-build");
const cliAppDir = path.join(root, "cli-app");
const packagedServer = path.join(cliAppDir, ".next-cli-build", "server");
createCompleteServer(buildDistDir);
writeFixture(packagedServer, "standalone-only.js", "keep me");
mergeServerArtifacts(buildDistDir, cliAppDir);
mergeServerArtifacts(buildDistDir, cliAppDir);
assert.equal(
fs.readFileSync(path.join(packagedServer, "standalone-only.js"), "utf8"),
"keep me",
);
assert.equal(
fs.readFileSync(path.join(packagedServer, "app/api/v1/messages/route.js"), "utf8"),
"messages route",
);
});
it("reports the missing required API route artifact path", () => {
const root = createTempDir();
const buildDistDir = path.join(root, ".next-cli-build");
const cliAppDir = path.join(root, "cli-app");
writeFixture(
path.join(buildDistDir, "server"),
"app/api/v1/chat/completions/route.js",
"chat route",
);
mergeServerArtifacts(buildDistDir, cliAppDir);
assert.throws(
() => assertRequiredApiArtifacts(cliAppDir),
(error) => error.message.includes(path.join(
cliAppDir,
".next-cli-build/server/app/api/v1/messages/route.js",
)),
);
});
});

View File

@@ -69,3 +69,33 @@ describe("Codex fast tier and capacity handling", () => {
await expect(new Response(peek.replacementBody).text()).resolves.toBe(text);
});
});
describe("Codex reasoning normalization", () => {
it.each([
["gpt-5.6-sol", "max", "max"],
["gpt-5.6-sol", "ultra", "ultra"],
["gpt-5.6-terra", "max", "max"],
["gpt-5.6-terra", "ultra", "ultra"],
["gpt-5.6-luna", "max", "max"],
["gpt-5.6-luna", "ultra", "max"],
])("normalizes %s effort %s to %s", (model, effort, expected) => {
const body = new CodexExecutor().transformRequest(model, {
model,
input: "hi",
reasoning: { effort },
}, true, {});
expect(body.reasoning.effort).toBe(expected);
});
it("resolves review models before applying the reasoning matrix", () => {
const body = new CodexExecutor().transformRequest("gpt-5.6-terra-review", {
model: "gpt-5.6-terra-review",
input: "hi",
reasoning_effort: "ultra",
}, true, {});
expect(body.model).toBe("gpt-5.6-terra");
expect(body.reasoning.effort).toBe("ultra");
});
});

View File

@@ -0,0 +1,111 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const { executeMock, forcedSSEToJsonMock } = vi.hoisted(() => ({
executeMock: vi.fn(),
forcedSSEToJsonMock: vi.fn(),
}));
vi.mock("../../open-sse/executors/index.js", () => ({
getExecutor: () => ({
noAuth: true,
execute: executeMock,
}),
}));
vi.mock("../../open-sse/utils/requestLogger.js", () => ({
createRequestLogger: async () => ({
logClientRawRequest: vi.fn(),
logRawRequest: vi.fn(),
logTargetRequest: vi.fn(),
logProviderResponse: vi.fn(),
logConvertedResponse: vi.fn(),
logError: vi.fn(),
}),
}));
vi.mock("@/lib/usageDb.js", () => ({
trackPendingRequest: vi.fn(),
appendRequestLog: vi.fn(async () => {}),
saveRequestDetail: vi.fn(async () => {}),
}));
vi.mock("../../open-sse/handlers/chatCore/sseToJsonHandler.js", () => ({
handleForcedSSEToJson: forcedSSEToJsonMock,
}));
const { handleChatCore } = await import("../../open-sse/handlers/chatCore.js");
async function runNativeCodexRequest(model, reasoning) {
const body = {
model,
input: "hello",
stream: false,
...(reasoning ? { reasoning } : {}),
};
await handleChatCore({
body,
modelInfo: { provider: "codex", model },
credentials: { accessToken: "test-token", providerSpecificData: {} },
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn() },
connectionId: "test-connection",
rtkEnabled: false,
headroomEnabled: false,
cavemanEnabled: false,
ponytailEnabled: false,
pxpipeEnabled: false,
sourceFormatOverride: "openai-responses",
clientRawRequest: {
endpoint: "/v1/responses",
body,
headers: {
accept: "application/json",
"user-agent": "codex-cli/0.144.1",
},
},
});
return executeMock.mock.calls.at(-1)[0].body;
}
describe("native Codex passthrough thinking suffixes", () => {
beforeEach(() => {
vi.clearAllMocks();
executeMock.mockResolvedValue({
response: new Response("", { status: 200 }),
url: "https://chatgpt.com/backend-api/codex/responses",
headers: {},
transformedBody: null,
});
forcedSSEToJsonMock.mockResolvedValue({
success: true,
response: new Response("{}", { status: 200 }),
});
});
it("forwards Ultra for Sol", async () => {
const body = await runNativeCodexRequest("gpt-5.6-sol(ultra)");
expect(body.model).toBe("gpt-5.6-sol");
expect(body.reasoning).toEqual({ effort: "ultra" });
});
it("converts unsupported Luna Ultra to Max without dropping reasoning metadata", async () => {
const body = await runNativeCodexRequest("gpt-5.6-luna(ultra)", {
effort: "low",
summary: "detailed",
});
expect(body.model).toBe("gpt-5.6-luna");
expect(body.reasoning).toEqual({ effort: "max", summary: "detailed" });
});
it("forwards Ultra through a Terra review alias", async () => {
const body = await runNativeCodexRequest("gpt-5.6-terra-review(ultra)", {
effort: "low",
});
expect(body.model).toBe("gpt-5.6-terra");
expect(body.reasoning).toEqual({ effort: "ultra" });
});
});

View File

@@ -151,7 +151,6 @@ describe("Codex Refresh Token", () => {
expect(getRefreshLeadMs("codex")).toBe(5 * 24 * 60 * 60 * 1000); // 5 days
expect(getRefreshLeadMs("claude")).toBe(4 * 60 * 60 * 1000); // 4 hours
expect(getRefreshLeadMs("iflow")).toBe(24 * 60 * 60 * 1000); // 24 hours
expect(getRefreshLeadMs("qwen")).toBe(20 * 60 * 1000); // 20 minutes
expect(getRefreshLeadMs("kimi")).toBe(5 * 60 * 1000); // 5 minutes
expect(getRefreshLeadMs("kimi-coding")).toBe(5 * 60 * 1000); // legacy alias
expect(getRefreshLeadMs("antigravity")).toBe(5 * 60 * 1000); // 5 minutes

View File

@@ -0,0 +1,80 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("@/lib/usageDb.js", () => ({
appendRequestLog: vi.fn(async () => {}),
saveRequestDetail: vi.fn(async () => {}),
saveRequestUsage: vi.fn(async () => {})
}));
const { stripContinuityFields } = await import("../../open-sse/handlers/chatCore.js");
const { openaiResponsesToOpenAIRequest } = await import("../../open-sse/translator/request/openai-responses.js");
// Multi-turn Codex-style Responses input: reasoning item carrying a
// store=false encrypted_content continuity blob between tool turns.
const makeResponsesBody = () => ({
model: "x",
instructions: "You are Codex.",
store: false,
include: ["reasoning.encrypted_content"],
reasoning: { effort: "low", summary: "auto" },
input: [
{ role: "user", content: [{ type: "input_text", text: "Run: echo hi" }] },
{
type: "reasoning",
id: "rs_1",
summary: [{ type: "summary_text", text: "thinking" }],
encrypted_content: "B".repeat(5000)
},
{ type: "function_call", id: "fc_1", call_id: "call_1", name: "shell", arguments: "{\"command\":\"echo hi\"}" },
{ type: "function_call_output", call_id: "call_1", output: "hi" },
{ role: "user", content: [{ type: "input_text", text: "Now run: echo bye" }] }
],
tools: [{
type: "function",
name: "shell",
description: "Run a shell command",
parameters: { type: "object", properties: { command: { type: "string" } }, required: ["command"] }
}]
});
describe("stripContinuityFields (outbound boundary)", () => {
it("removes continuity blobs from assistant messages", () => {
const body = {
messages: [
{ role: "user", content: "hi" },
{ role: "assistant", content: null, reasoning_content: "thinking",
encrypted_content: "B".repeat(500), reasoning_encrypted_content: "alias",
tool_calls: [{ id: "call_1", type: "function", function: { name: "shell", arguments: "{}" } }] }
]
};
stripContinuityFields(body);
const assistant = body.messages[1];
expect(assistant).not.toHaveProperty("encrypted_content");
expect(assistant).not.toHaveProperty("reasoning_encrypted_content");
// legitimate fields survive
expect(assistant.reasoning_content).toBe("thinking");
expect(assistant.tool_calls[0].function.name).toBe("shell");
});
it("is a no-op for bodies without a messages array", () => {
const body = { input: [], instructions: "x" };
expect(stripContinuityFields(body)).toBe(body);
expect(stripContinuityFields(null)).toBe(null);
});
it("end-to-end: Responses multi-turn translation stripped before dispatch", () => {
const translated = openaiResponsesToOpenAIRequest("x", makeResponsesBody(), false, {});
// The translator stashes the blob for internal round-trip symmetry...
const assistantBefore = translated.messages.find((m) => m.role === "assistant");
expect(assistantBefore.encrypted_content).toBe("B".repeat(5000));
// ...and the outbound boundary removes it before it reaches any upstream.
stripContinuityFields(translated);
expect(JSON.stringify(translated)).not.toContain("encrypted_content");
expect(JSON.stringify(translated)).not.toContain("B".repeat(100));
const assistant = translated.messages.find((m) => m.role === "assistant");
expect(assistant.reasoning_content).toContain("thinking");
expect(assistant.tool_calls[0].function.name).toBe("shell");
const roles = translated.messages.map((m) => m.role);
expect(roles).toEqual(["system", "user", "assistant", "tool", "user"]);
});
});

View File

@@ -0,0 +1,65 @@
const assert = require("node:assert/strict");
const http = require("node:http");
const net = require("node:net");
const test = require("node:test");
test("serves h2c POST requests as HTTP/1.1", async () => {
const originalCreateServer = http.createServer;
delete require.cache[require.resolve("../../custom-server.js")];
require("../../custom-server.js");
const server = http.createServer(async (req, res) => {
assert.equal(req.url, "/v1/chat/completions");
assert.equal(req.headers.upgrade, undefined);
assert.equal(req.headers["http2-settings"], undefined);
assert.equal(req.headers.connection, "close");
const body = [];
for await (const chunk of req) body.push(chunk);
assert.equal(Buffer.concat(body).toString("utf8"), '{"model":"test","stream":true}');
res.setHeader("Content-Type", "text/event-stream");
res.end("data: [DONE]\n\n");
});
server.on("upgrade", (_req, socket) => socket.destroy());
try {
await new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const port = server.address().port;
const response = await new Promise((resolve, reject) => {
const chunks = [];
const socket = net.createConnection({ host: "127.0.0.1", port }, () => {
const body = '{"model":"test","stream":true}';
socket.write([
"POST /v1/chat/completions HTTP/1.1",
`Host: 127.0.0.1:${port}`,
"Connection: Upgrade, HTTP2-Settings",
"Upgrade: h2c",
"HTTP2-Settings: AAEAAEAAAAIAAAAAAAMAAAAAAAQBAAAAAAUAAEAAAAYABgAA",
`Content-Length: ${Buffer.byteLength(body)}`,
"Content-Type: application/json",
"",
"",
].join("\r\n"));
setImmediate(() => socket.write(body));
});
socket.setTimeout(2_000, () => {
socket.destroy();
reject(new Error("h2c fallback response timed out"));
});
socket.on("data", (chunk) => chunks.push(chunk));
socket.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
socket.on("error", reject);
});
assert.match(response, /^HTTP\/1\.1 200 OK\r\n/);
assert.match(response, /\r\nContent-Type: text\/event-stream\r\n/i);
assert.match(response, /\r\nConnection: close\r\n/i);
assert.match(response, /\r\n\r\ndata: \[DONE\]\n\n$/);
} finally {
await new Promise((resolve) => server.close(resolve));
http.createServer = originalCreateServer;
}
});

View File

@@ -0,0 +1,86 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const dbMocks = vi.hoisted(() => ({
getProviderConnections: vi.fn(),
updateProviderConnection: vi.fn(),
}));
vi.mock("@/lib/localDb", () => dbMocks);
vi.mock("@/lib/network/connectionProxy", () => ({
pickProxyPoolId: vi.fn(),
resolveConnectionProxyConfig: vi.fn(),
}));
vi.mock("@/shared/constants/providers.js", () => ({
FREE_PROVIDERS: {},
resolveProviderId: (provider) => provider,
}));
vi.mock("@/sse/utils/logger.js", () => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn() }));
const { markAccountUnavailable } = await import("../../src/sse/services/auth.js");
beforeEach(() => {
vi.clearAllMocks();
dbMocks.getProviderConnections.mockResolvedValue([{
id: "github-a",
provider: "github",
name: "github-a",
backoffLevel: 4,
}]);
});
describe("GitHub monthly usage exhaustion", () => {
it("locks the whole account until the next UTC month", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-04T19:30:00.000Z"));
try {
await markAccountUnavailable(
"github-a",
402,
"You've reached your additional usage limit for your plan. Go to GitHub settings for details.",
"github",
"claude-fable-5",
);
expect(dbMocks.updateProviderConnection).toHaveBeenCalledWith(
"github-a",
expect.objectContaining({
modelLock___all: "2026-09-01T00:00:00.000Z",
testStatus: "unavailable",
errorCode: 402,
backoffLevel: 0,
}),
);
expect(dbMocks.updateProviderConnection.mock.calls[0][1])
.not.toHaveProperty("modelLock_claude-fable-5");
} finally {
vi.useRealTimers();
}
});
it("keeps unrelated GitHub 402 errors model-scoped", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-04T19:30:00.000Z"));
try {
await markAccountUnavailable(
"github-a",
402,
"Payment required",
"github",
"claude-fable-5",
);
expect(dbMocks.updateProviderConnection).toHaveBeenCalledWith(
"github-a",
expect.objectContaining({
"modelLock_claude-fable-5": "2026-08-04T19:32:00.000Z",
}),
);
expect(dbMocks.updateProviderConnection.mock.calls[0][1])
.not.toHaveProperty("modelLock___all");
} finally {
vi.useRealTimers();
}
});
});

View File

@@ -226,6 +226,11 @@ function binaryResponse(buffer, status = 200) {
});
}
function accessTokenWithTier(tier) {
const payload = Buffer.from(JSON.stringify({ tier })).toString("base64url");
return `header.${payload}.signature`;
}
const EMPTY_GRPC_WEB_FRAME = Buffer.from([0, 0, 0, 0, 0]);
const GRPC_CREDITS_URL =
"https://grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig";
@@ -311,6 +316,7 @@ describe("getUsageForProvider(grok-cli)", () => {
});
it("falls back to GetGrokCreditsConfig gRPC when paid sub has no REST numeric quota", async () => {
const accessToken = accessTokenWithTier(5);
const resetSeconds = 1784825940;
const resetNanos = 867850000;
const resetAt = new Date(
@@ -329,11 +335,11 @@ describe("getUsageForProvider(grok-cli)", () => {
const usage = await getUsageForProvider({
provider: "grok-cli",
accessToken: "test-token",
accessToken,
});
expect(usage.message).toBeUndefined();
expect(usage.plan).toBe("XPremiumPlus");
expect(usage.plan).toBe("SuperGrok Heavy");
expect(usage.quotas["Weekly SuperGrok"]).toMatchObject({
used: 35,
total: 100,
@@ -345,7 +351,7 @@ describe("getUsageForProvider(grok-cli)", () => {
const grpcCall = proxyAwareFetch.mock.calls[2];
expect(grpcCall[0]).toBe(GRPC_CREDITS_URL);
expect(grpcCall[1].method).toBe("POST");
expect(grpcCall[1].headers.Authorization).toBe("Bearer test-token");
expect(grpcCall[1].headers.Authorization).toBe(`Bearer ${accessToken}`);
expect(grpcCall[1].headers["Content-Type"]).toBe("application/grpc-web+proto");
expect(grpcCall[1].headers["X-Grpc-Web"]).toBe("1");
// Empty gRPC-web request frame is required (flag 0 + length 0)

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { compressWithHeadroom, formatHeadroomLog } from "../../open-sse/rtk/headroom.js";
import { compressWithHeadroom, formatHeadroomLog, formatHeadroomSizeLog } from "../../open-sse/rtk/headroom.js";
afterEach(() => {
vi.restoreAllMocks();
@@ -209,4 +209,11 @@ describe("formatHeadroomLog", () => {
expect(formatHeadroomLog({ tokens_before: 100, tokens_after: 25, tokens_saved: 75 }))
.toBe("reported token delta=75 before=100 after=25 (75.0%)");
});
it("reports effective payload, tool-schema, and tool-history sizes", () => {
expect(formatHeadroomSizeLog({
before: { bodyBytes: 1000, messageBytes: 800, toolSchemaBytes: 100, toolHistoryBytes: 500 },
after: { bodyBytes: 900, messageBytes: 700, toolSchemaBytes: 100, toolHistoryBytes: 400 },
})).toContain("tools=100B→100B toolHistory=500B→400B effective=10.0%");
});
});

View File

@@ -0,0 +1,165 @@
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 USAGE_URL = "https://ollama.com/api/usage";
const ME_URL = "https://ollama.com/api/me";
function jsonResponse(body, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
const SAMPLE_USAGE = {
activity: {
cost: "0.00000",
period: {
type: "last_4_weeks",
starting_at: "2026-07-01T00:00:00Z",
ending_at: "2026-07-29T00:00:00Z",
},
models: [],
},
limits: {
session: { usage: 0, models: [] },
weekly: {
usage: 1,
models: [
{ name: "glm-5.2", request_count: 5967 },
{ name: "kimi-k2.5", request_count: 2 },
],
},
},
};
const SAMPLE_ME = {
Plan: "max",
};
describe("ollama registry usage flags", () => {
it("is listed for apikey quota dashboard", () => {
expect(USAGE_SUPPORTED_PROVIDERS).toContain("ollama");
expect(USAGE_APIKEY_PROVIDERS).toContain("ollama");
});
});
describe("getUsageForProvider(ollama)", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("GETs /api/usage with Bearer apiKey and POSTs /api/me for plan", async () => {
proxyAwareFetch
.mockResolvedValueOnce(jsonResponse(SAMPLE_USAGE))
.mockResolvedValueOnce(jsonResponse(SAMPLE_ME));
const usage = await getUsageForProvider({
provider: "ollama",
apiKey: "k",
providerSpecificData: {},
});
expect(usage.message).toBeUndefined();
expect(usage.plan).toBe("Max");
expect(usage.quotas["Session (5h)"]).toMatchObject({
used: 0,
total: 100,
remainingPercentage: 100,
unlimited: false,
});
expect(usage.quotas["Weekly (7d)"]).toMatchObject({
used: 100,
total: 100,
remainingPercentage: 0,
unlimited: false,
});
// Must not set absolute remaining — UI treats remaining as %
expect(usage.quotas["Session (5h)"].remaining).toBeUndefined();
expect(usage.quotas["Weekly (7d)"].remaining).toBeUndefined();
expect(proxyAwareFetch).toHaveBeenCalledTimes(2);
const [usageUrl, usageOpts] = proxyAwareFetch.mock.calls[0];
expect(usageUrl).toBe(USAGE_URL);
expect(usageOpts.headers.Authorization).toBe("Bearer k");
expect(usageOpts.headers.Accept).toBe("application/json");
const [meUrl, meOpts] = proxyAwareFetch.mock.calls[1];
expect(meUrl).toBe(ME_URL);
expect(meOpts.method).toBe("POST");
expect(meOpts.headers.Authorization).toBe("Bearer k");
expect(meOpts.headers["Content-Length"]).toBe("0");
});
it("surfaces invalid key message on 401", async () => {
proxyAwareFetch.mockResolvedValueOnce(
jsonResponse({ error: "unauthorized" }, 401),
);
const usage = await getUsageForProvider({
provider: "ollama",
apiKey: "bad",
});
expect(usage.message).toMatch(/invalid/i);
expect(proxyAwareFetch).toHaveBeenCalledTimes(1);
});
it("returns message when apiKey missing", async () => {
const usage = await getUsageForProvider({
provider: "ollama",
providerSpecificData: {},
});
expect(usage.message).toMatch(/api key/i);
expect(proxyAwareFetch).not.toHaveBeenCalled();
});
});
describe("parseQuotaData(ollama)", () => {
it("forwards remainingPercentage for dashboard bars", () => {
const rows = parseQuotaData("ollama", {
plan: "Max",
quotas: {
"Session (5h)": {
used: 0,
total: 100,
remainingPercentage: 100,
resetAt: null,
},
"Weekly (7d)": {
used: 100,
total: 100,
remainingPercentage: 0,
resetAt: null,
},
},
});
expect(rows).toHaveLength(2);
expect(rows[0]).toMatchObject({
name: "Session (5h)",
used: 0,
total: 100,
remainingPercentage: 100,
});
expect(rows[1]).toMatchObject({
name: "Weekly (7d)",
used: 100,
total: 100,
remainingPercentage: 0,
});
});
});

View File

@@ -0,0 +1,38 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
// Regression guard for the Windows xAI/Grok token refresh failure.
//
// `open` computes its own directory from `import.meta.url`. When it is bundled, webpack
// replaces that with the BUILD machine's absolute path as a string literal, so a release
// built on macOS ships `fileURLToPath("file:///Users/.../open/index.js")`. On Windows that
// throws ERR_INVALID_FILE_URL_PATH ("File URL path must be absolute" — no drive letter) at
// module scope, which kills every importer. `refreshXaiToken` imports the xAI OAuth
// service, which imports `open`, so no Grok refresh could ever reach auth.x.ai on Windows.
//
// Keeping the package external preserves the real `import.meta.url` at runtime.
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
describe("open must not be bundled into the server build", () => {
const config = readFileSync(path.join(repoRoot, "next.config.mjs"), "utf8");
it("is declared in serverExternalPackages", () => {
const match = config.match(/serverExternalPackages:\s*\[([^\]]*)\]/);
expect(match, "serverExternalPackages not found in next.config.mjs").toBeTruthy();
const packages = match[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
expect(packages).toContain("open");
});
it("stays a runtime dependency so the standalone output can resolve it", async () => {
const pkg = JSON.parse(readFileSync(path.join(repoRoot, "package.json"), "utf8"));
expect(pkg.dependencies?.open).toBeTruthy();
});
it("resolves its own directory from import.meta.url, which is why it must stay external", async () => {
const entry = path.join(repoRoot, "node_modules", "open", "index.js");
const source = readFileSync(entry, "utf8");
expect(source).toMatch(/import\.meta\.url/);
});
});

View File

@@ -0,0 +1,76 @@
// Locks openai-compatible apiType resolution: the stored apiType on the
// connection's providerSpecificData (kept in sync with the node) is
// authoritative, and the node-ID substring is only a legacy fallback.
//
// Regression for: editing a node's apiType to "responses" had no effect because
// runtime derived chat/responses from the immutable node ID string
// (`openai-compatible-<chat|responses>-<uuid>`) instead of the stored value.
import { describe, it, expect } from "vitest";
import { resolveOpenAICompatibleApiType, getTargetFormat } from "open-sse/services/provider.js";
import { DefaultExecutor } from "open-sse/executors/default.js";
import { BaseExecutor } from "open-sse/executors/base.js";
const CHAT_ID = "openai-compatible-chat-3d8d3de8-1206-47ee-a42f-22113a5f2387";
const RESPONSES_ID = "openai-compatible-responses-11111111-2222-3333-4444-555555555555";
const BASE = "https://api.ericding.io.vn/v1";
function creds(apiType) {
return { providerSpecificData: apiType === undefined ? { baseUrl: BASE } : { baseUrl: BASE, apiType } };
}
describe("resolveOpenAICompatibleApiType", () => {
it("prefers stored apiType over the ID substring (edited node on a legacy -chat- ID)", () => {
expect(resolveOpenAICompatibleApiType(CHAT_ID, creds("responses"))).toBe("responses");
expect(resolveOpenAICompatibleApiType(RESPONSES_ID, creds("chat"))).toBe("chat");
});
it("falls back to the ID substring when apiType is absent", () => {
expect(resolveOpenAICompatibleApiType(CHAT_ID, creds(undefined))).toBe("chat");
expect(resolveOpenAICompatibleApiType(RESPONSES_ID, creds(undefined))).toBe("responses");
expect(resolveOpenAICompatibleApiType(CHAT_ID, null)).toBe("chat");
expect(resolveOpenAICompatibleApiType(RESPONSES_ID, null)).toBe("responses");
});
it("ignores an invalid stored apiType and falls back to the ID", () => {
expect(resolveOpenAICompatibleApiType(RESPONSES_ID, creds("bogus"))).toBe("responses");
expect(resolveOpenAICompatibleApiType(CHAT_ID, creds(""))).toBe("chat");
});
});
describe("getTargetFormat", () => {
it("selects openai-responses when the stored apiType is responses (even on a -chat- ID)", () => {
expect(getTargetFormat(CHAT_ID, creds("responses"))).toBe("openai-responses");
});
it("selects openai for chat, and honors stored chat over a -responses- ID", () => {
expect(getTargetFormat(CHAT_ID, creds(undefined))).toBe("openai");
expect(getTargetFormat(RESPONSES_ID, creds("chat"))).toBe("openai");
});
it("keeps the ID-based fallback when credentials are absent", () => {
expect(getTargetFormat(RESPONSES_ID)).toBe("openai-responses");
expect(getTargetFormat(CHAT_ID)).toBe("openai");
});
});
describe("executor buildUrl endpoint path", () => {
for (const [name, Ex] of [["DefaultExecutor", DefaultExecutor], ["BaseExecutor", BaseExecutor]]) {
describe(name, () => {
const ex = new Ex(CHAT_ID);
it("routes to /responses when stored apiType is responses, despite the -chat- ID", () => {
expect(ex.buildUrl("cx/gpt-5.6-sol", true, 0, creds("responses"))).toBe(`${BASE}/responses`);
});
it("routes to /chat/completions when apiType is chat", () => {
expect(ex.buildUrl("cx/gpt-5.6-sol", true, 0, creds("chat"))).toBe(`${BASE}/chat/completions`);
});
it("falls back to the ID substring (legacy) when apiType is absent", () => {
expect(ex.buildUrl("cx/gpt-5.6-sol", true, 0, creds(undefined))).toBe(`${BASE}/chat/completions`);
const exResp = new Ex(RESPONSES_ID);
expect(exResp.buildUrl("m", true, 0, creds(undefined))).toBe(`${BASE}/responses`);
});
});
}
});

View File

@@ -0,0 +1,153 @@
import { describe, expect, it } from "vitest";
import {
openaiResponsesToOpenAIRequest,
} from "../../open-sse/translator/request/openai-responses.js";
import { openaiToOpenAIResponsesResponse } from "../../open-sse/translator/response/openai-responses.js";
import { initState } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
const EXEC_TOOL = {
type: "custom",
name: "exec",
description: "Run JavaScript code to orchestrate tool calls.",
format: {
type: "grammar",
syntax: "lark",
definition: "start: /(.|\\n)+/",
},
};
describe("Codex Responses Lite custom tools → OpenAI Chat", () => {
it("promotes additional_tools custom declarations into Chat tools", () => {
const out = openaiResponsesToOpenAIRequest("cx/gpt-5.6-sol", {
input: [
{ type: "additional_tools", role: "developer", tools: [EXEC_TOOL] },
{ type: "message", role: "user", content: [{ type: "input_text", text: "Run pwd" }] },
],
tool_choice: "auto",
}, true, null);
expect(out.tools).toHaveLength(1);
expect(out.tools[0]).toMatchObject({
type: "function",
function: {
name: "exec",
parameters: {
type: "object",
required: ["input"],
properties: { input: { type: "string" } },
},
},
});
expect(out._customToolNames).toEqual(["exec"]);
expect(out.messages.some((message) => message.role === "developer")).toBe(false);
});
it("translates custom tool call/output history into Chat assistant/tool messages", () => {
const program = "const result = await tools.shell({command: 'pwd'});\nreturn result;";
const out = openaiResponsesToOpenAIRequest("cx/gpt-5.6-sol", {
input: [
{ type: "additional_tools", role: "developer", tools: [EXEC_TOOL] },
{ type: "custom_tool_call", call_id: "call_exec_1", name: "exec", input: program },
{ type: "custom_tool_call_output", call_id: "call_exec_1", output: "/srv/app" },
{ type: "message", role: "user", content: [{ type: "input_text", text: "Continue" }] },
],
}, true, null);
const assistant = out.messages.find((message) => message.role === "assistant");
expect(assistant.tool_calls[0]).toMatchObject({
id: "call_exec_1",
type: "function",
function: { name: "exec" },
});
expect(JSON.parse(assistant.tool_calls[0].function.arguments)).toEqual({ input: program });
expect(out.messages.find((message) => message.role === "tool")).toEqual({
role: "tool",
tool_call_id: "call_exec_1",
content: "/srv/app",
});
});
it("merges additional_tools with normal top-level function tools", () => {
const out = openaiResponsesToOpenAIRequest("cx/gpt-5.6-sol", {
input: [{ type: "additional_tools", role: "developer", tools: [EXEC_TOOL] }],
tools: [{ type: "function", name: "search", parameters: { type: "object", properties: {} } }],
}, true, null);
expect(out.tools.map((tool) => tool.function.name)).toEqual(["search", "exec"]);
expect(out._customToolNames).toEqual(["exec"]);
});
});
describe("OpenAI Chat stream → Codex custom_tool_call", () => {
it("unwraps the Chat input parameter and emits custom-tool events", () => {
const state = initState(FORMATS.OPENAI_RESPONSES);
state.customToolNames = new Set(["exec"]);
const chunks = [
{
id: "chatcmpl-custom",
choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: "call_exec_2", type: "function", function: { name: "exec", arguments: "" } }] }, finish_reason: null }],
},
{
id: "chatcmpl-custom",
choices: [{ index: 0, delta: { tool_calls: [{ index: 0, function: { arguments: "{\"input\":\"const x = await tools.shell({command: 'pwd'});\"}" } }] }, finish_reason: null }],
},
{ id: "chatcmpl-custom", choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }] },
];
const events = chunks.flatMap((chunk) => openaiToOpenAIResponsesResponse(chunk, state));
const added = events.find((event) => event.event === "response.output_item.added");
const delta = events.find((event) => event.event === "response.custom_tool_call_input.delta");
const done = events.find((event) => event.event === "response.output_item.done");
expect(added.data.item).toMatchObject({
type: "custom_tool_call",
call_id: "call_exec_2",
name: "exec",
input: "",
});
expect(delta.data.delta).toBe("const x = await tools.shell({command: 'pwd'});");
expect(done.data.item).toMatchObject({
type: "custom_tool_call",
call_id: "call_exec_2",
name: "exec",
input: "const x = await tools.shell({command: 'pwd'});",
});
expect(events.some((event) => event.event === "response.function_call_arguments.delta")).toBe(false);
});
it("waits for the function name when id and name arrive in separate chunks", () => {
const state = initState(FORMATS.OPENAI_RESPONSES);
state.customToolNames = new Set(["exec"]);
const chunks = [
{ id: "chatcmpl-split", choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: "call_split", type: "function", function: { arguments: "" } }] }, finish_reason: null }] },
{ id: "chatcmpl-split", choices: [{ index: 0, delta: { tool_calls: [{ index: 0, function: { name: "exec", arguments: "{\"input\":\"return 1;\"}" } }] }, finish_reason: null }] },
{ id: "chatcmpl-split", choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }] },
];
const events = chunks.flatMap((chunk) => openaiToOpenAIResponsesResponse(chunk, state));
const added = events.filter((event) => event.event === "response.output_item.added");
expect(added).toHaveLength(1);
expect(added[0].data.item).toMatchObject({
type: "custom_tool_call",
call_id: "call_split",
name: "exec",
});
});
it("leaves normal Chat tool calls as Responses function_call events", () => {
const state = initState(FORMATS.OPENAI_RESPONSES);
state.customToolNames = new Set(["exec"]);
const events = [
{ id: "chatcmpl-normal", choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: "call_search", type: "function", function: { name: "search", arguments: "{\"q\":\"x\"}" } }] }, finish_reason: null }] },
{ id: "chatcmpl-normal", choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }] },
].flatMap((chunk) => openaiToOpenAIResponsesResponse(chunk, state));
expect(events.find((event) => event.event === "response.output_item.added").data.item.type).toBe("function_call");
expect(events.find((event) => event.event === "response.output_item.done").data.item).toMatchObject({
type: "function_call",
name: "search",
arguments: "{\"q\":\"x\"}",
});
});
});

View File

@@ -0,0 +1,148 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("@/lib/usageDb.js", () => ({
appendRequestLog: vi.fn(async () => {}),
saveRequestDetail: vi.fn(async () => {}),
saveRequestUsage: vi.fn(async () => {})
}));
const { FORMATS } = await import("../../open-sse/translator/formats.js");
const { translateNonStreamingResponse } = await import("../../open-sse/handlers/chatCore/nonStreamingHandler.js");
const { handleForcedSSEToJson } = await import("../../open-sse/handlers/chatCore/sseToJsonHandler.js");
// A chat.completion body as returned by a chat-native upstream (e.g. op-ericding)
const CHAT_TOOL_BODY = {
id: "chatcmpl-abc123",
object: "chat.completion",
created: 1700000000,
model: "cl/claude-haiku-4-5",
choices: [{
index: 0,
message: {
role: "assistant",
content: null,
tool_calls: [{ id: "call_1", type: "function", function: { name: "shell", arguments: "{\"cmd\":\"ls\"}" } }]
},
finish_reason: "tool_calls"
}],
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
};
describe("non-stream Chat upstream for a Responses-API client (op-ericding bug)", () => {
it("translates chat.completion tool_calls into Responses function_call output", () => {
// translateNonStreamingResponse(body, targetFormat=PROVIDER format, sourceFormat=CLIENT format)
const out = translateNonStreamingResponse(CHAT_TOOL_BODY, FORMATS.OPENAI, FORMATS.OPENAI_RESPONSES);
expect(out.object).toBe("response");
expect(out).not.toHaveProperty("choices");
const fc = (out.output || []).find((o) => o.type === "function_call");
expect(fc).toBeTruthy();
expect(fc.call_id).toBe("call_1");
expect(fc.name).toBe("shell");
expect(fc.arguments).toBe("{\"cmd\":\"ls\"}");
});
it("translates marked Chat tools into Responses custom_tool_call output", () => {
const customBody = structuredClone(CHAT_TOOL_BODY);
customBody.choices[0].message.tool_calls[0] = {
id: "call_exec",
type: "function",
function: {
name: "exec",
arguments: "{\"input\":\"return await tools.shell({command: 'pwd'});\"}"
}
};
const out = translateNonStreamingResponse(
customBody,
FORMATS.OPENAI,
FORMATS.OPENAI_RESPONSES,
new Set(["exec"])
);
const call = (out.output || []).find((item) => item.type === "custom_tool_call");
expect(call).toMatchObject({
call_id: "call_exec",
name: "exec",
input: "return await tools.shell({command: 'pwd'});"
});
expect(out.output.some((item) => item.type === "function_call")).toBe(false);
});
it("keeps chat.completion text content as a Responses message item", () => {
const body = {
...CHAT_TOOL_BODY,
choices: [{ index: 0, message: { role: "assistant", content: "hello" }, finish_reason: "stop" }]
};
const out = translateNonStreamingResponse(body, FORMATS.OPENAI, FORMATS.OPENAI_RESPONSES);
const msg = (out.output || []).find((o) => o.type === "message");
expect(msg).toBeTruthy();
expect(msg.content[0].type).toBe("output_text");
expect(msg.content[0].text).toBe("hello");
});
it("leaves chat->chat untouched", () => {
const out = translateNonStreamingResponse(CHAT_TOOL_BODY, FORMATS.OPENAI, FORMATS.OPENAI);
expect(out.object).toBe("chat.completion");
expect(out.choices[0].message.tool_calls[0].function.name).toBe("shell");
});
});
describe("forced-SSE JSON path for a Responses-API client behind a chat upstream", () => {
const sseCtx = (sourceFormat, targetFormat) => {
const encoder = new TextEncoder();
const raw = [
'data: {"id":"chatcmpl-sse","object":"chat.completion.chunk","created":1700000000,"model":"gpt-x","choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_9","type":"function","function":{"name":"shell","arguments":""}}]},"finish_reason":null}]}',
'data: {"id":"chatcmpl-sse","object":"chat.completion.chunk","created":1700000000,"model":"gpt-x","choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\\"cmd\\":\\"pwd\\"}"}}]},"finish_reason":null}]}',
'data: {"id":"chatcmpl-sse","object":"chat.completion.chunk","created":1700000000,"model":"gpt-x","choices":[{"delta":{},"finish_reason":"tool_calls"}]}',
"data: [DONE]",
""
].join("\n\n");
return {
providerResponse: new Response(new ReadableStream({
start(controller) { controller.enqueue(encoder.encode(raw)); controller.close(); }
}), { headers: { "content-type": "text/event-stream" } }),
sourceFormat,
targetFormat,
provider: "op-test-chat",
model: "gpt-x",
body: { model: "gpt-x", messages: [] },
stream: false,
requestStartTime: Date.now(),
connectionId: "test-connection",
clientRawRequest: { endpoint: "/v1/responses" },
trackDone: vi.fn(),
appendLog: vi.fn()
};
};
it("parses chat SSE chunks and returns a Responses function_call body", async () => {
const result = await handleForcedSSEToJson(sseCtx(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI));
expect(result.success).toBe(true);
const json = await result.response.json();
expect(json.object).toBe("response");
const fc = (json.output || []).find((o) => o.type === "function_call");
expect(fc).toBeTruthy();
expect(fc.name).toBe("shell");
expect(fc.arguments).toBe("{\"cmd\":\"pwd\"}");
});
it("returns a custom_tool_call for a marked tool", async () => {
const ctx = sseCtx(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI);
ctx.customToolNames = new Set(["shell"]);
const result = await handleForcedSSEToJson(ctx);
expect(result.success).toBe(true);
const json = await result.response.json();
const call = (json.output || []).find((item) => item.type === "custom_tool_call");
expect(call).toMatchObject({
call_id: "call_9",
name: "shell",
input: "{\"cmd\":\"pwd\"}"
});
});
it("still returns chat.completion for a plain chat client", async () => {
const result = await handleForcedSSEToJson(sseCtx(FORMATS.OPENAI, FORMATS.OPENAI));
expect(result.success).toBe(true);
const json = await result.response.json();
expect(json.object).toBe("chat.completion");
expect(json.choices[0].message.tool_calls[0].function.name).toBe("shell");
});
});

View File

@@ -31,4 +31,10 @@ describe("provider display split (E1)", () => {
expect(m.ALIAS_TO_ID.kr).toBe("kiro");
expect(m.getProvidersByKind("tts").length).toBeGreaterThan(0);
});
it("Cloudflare exposes API-key authentication on its free-tier card", async () => {
const { FREE_TIER_PROVIDERS } = await import("../../src/shared/constants/providers.js");
expect(FREE_TIER_PROVIDERS["cloudflare-ai"].authType).toBe("apikey");
expect(FREE_TIER_PROVIDERS["cloudflare-ai"].authModes).toEqual(["apikey"]);
});
});

View File

@@ -0,0 +1,55 @@
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { mkdtempSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { copyStandaloneAssets } from "../../scripts/copy-standalone-assets.mjs";
function createBuildFixture(distDir) {
const projectRoot = mkdtempSync(join(tmpdir(), "9router-standalone-assets-"));
const buildRoot = join(projectRoot, distDir);
mkdirSync(join(buildRoot, "standalone"), { recursive: true });
mkdirSync(join(buildRoot, "static", "chunks"), { recursive: true });
mkdirSync(join(projectRoot, "public"), { recursive: true });
writeFileSync(join(buildRoot, "static", "chunks", "app.js"), "static asset");
writeFileSync(join(projectRoot, "public", "favicon.svg"), "public asset");
return projectRoot;
}
describe("standalone build assets", () => {
it("copies static and public assets into the default standalone layout", () => {
const projectRoot = createBuildFixture(".next");
copyStandaloneAssets({ projectRoot, distDir: ".next" });
expect(readFileSync(join(projectRoot, ".next", "standalone", ".next", "static", "chunks", "app.js"), "utf8"))
.toBe("static asset");
expect(readFileSync(join(projectRoot, ".next", "standalone", "public", "favicon.svg"), "utf8"))
.toBe("public asset");
});
it("uses a custom Next dist directory", () => {
const projectRoot = createBuildFixture(".next-cli-build");
copyStandaloneAssets({ projectRoot, distDir: ".next-cli-build" });
expect(readFileSync(join(projectRoot, ".next-cli-build", "standalone", ".next-cli-build", "static", "chunks", "app.js"), "utf8"))
.toBe("static asset");
});
it("does not modify workspace-traced CLI builds", () => {
const projectRoot = createBuildFixture(".next-cli-build");
const previousMode = process.env.NEXT_TRACING_ROOT_MODE;
process.env.NEXT_TRACING_ROOT_MODE = "workspace";
try {
copyStandaloneAssets({ projectRoot, distDir: ".next-cli-build" });
} finally {
if (previousMode === undefined) delete process.env.NEXT_TRACING_ROOT_MODE;
else process.env.NEXT_TRACING_ROOT_MODE = previousMode;
}
expect(() => readFileSync(join(projectRoot, ".next-cli-build", "standalone", ".next-cli-build", "static", "chunks", "app.js")))
.toThrow();
});
});

View File

@@ -2,11 +2,21 @@ import { describe, it, expect } from "vitest";
import { getThinkingLevels } from "../../open-sse/providers/thinkingLevels.js";
describe("getThinkingLevels", () => {
it("adds max for gpt-5.6-sol on codex", () => {
const levels = getThinkingLevels("codex", "gpt-5.6-sol");
expect(levels).toContain("max");
expect(levels).toContain("xhigh");
expect(levels).not.toContain("ultra");
it.each([
["gpt-5.6-sol", ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]],
["gpt-5.6-terra", ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]],
["gpt-5.6-luna", ["none", "minimal", "low", "medium", "high", "xhigh", "max"]],
["gpt-5.6-sol-review", ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]],
["gpt-5.6-terra-review", ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]],
["gpt-5.6-luna-review", ["none", "minimal", "low", "medium", "high", "xhigh", "max"]],
])("returns Codex levels for %s", (model, expected) => {
expect(getThinkingLevels("codex", model)).toEqual(expected);
});
it("does not expose Codex-only GPT-5.6 overrides on Kiro", () => {
expect(getThinkingLevels("kiro", "gpt-5.6-sol")).toEqual([
"none", "minimal", "low", "medium", "high", "xhigh",
]);
});
it("does not add max for other codex models", () => {
@@ -14,7 +24,7 @@ describe("getThinkingLevels", () => {
expect(levels).toEqual(["low", "medium", "high", "xhigh"]);
});
it("does not add max for other gpt-5.6 models", () => {
it("does not add max for other Codex models", () => {
const levels = getThinkingLevels("codex", "gpt-5.5");
expect(levels || []).not.toContain("max");
});

View File

@@ -1,8 +1,8 @@
/**
* Generic OAuth2 token refresh — config-driven profiles.
*
* Verifies refreshAccessToken() handles the 5 foldable providers
* (qwen, iflow, github, kimi, claude) via a REFRESH_PROFILES table,
* Verifies refreshAccessToken() handles the 4 foldable providers
* (iflow, github, kimi, claude) via a REFRESH_PROFILES table,
* while preserving the legacy generic path for unknown providers.
*/
@@ -25,32 +25,6 @@ describe("refreshAccessToken — config-driven profiles", () => {
beforeEach(() => { vi.clearAllMocks(); vi.resetModules(); global.fetch = originalFetch; });
afterEach(() => { global.fetch = originalFetch; });
it("qwen: form body + clientId, surfaces resource_url as providerSpecificData", async () => {
const fm = mockFetchOnce({
access_token: "qw-acc",
refresh_token: "qw-refresh-rotated",
expires_in: 7200,
resource_url: "https://dashscope.aliyuncs.com",
});
const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
const out = await refreshAccessToken("qwen", "qw-old-refresh", {}, console);
expect(out).toEqual({
accessToken: "qw-acc",
refreshToken: "qw-refresh-rotated",
expiresIn: 7200,
providerSpecificData: { resourceUrl: "https://dashscope.aliyuncs.com" },
});
const [url, init] = fm.mock.calls[0];
expect(init.method).toBe("POST");
expect(init.headers["Content-Type"]).toBe("application/x-www-form-urlencoded");
const body = new URLSearchParams(init.body);
expect(body.get("grant_type")).toBe("refresh_token");
expect(body.get("refresh_token")).toBe("qw-old-refresh");
expect(body.get("client_id")).toBeTruthy();
});
it("iflow: Basic Auth header from clientId:clientSecret, form body keeps client_secret", async () => {
const fm = mockFetchOnce({ access_token: "if-acc", refresh_token: "if-rot", expires_in: 3600 });
const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
@@ -107,13 +81,13 @@ describe("refreshAccessToken — config-driven profiles", () => {
it("returns null on non-ok response", async () => {
mockFetchOnce({ error: "invalid_grant" }, { ok: false, status: 400 });
const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
const out = await refreshAccessToken("qwen", "dead", {}, console);
const out = await refreshAccessToken("iflow", "dead", {}, console);
expect(out).toBeNull();
});
it("returns null when refreshToken missing", async () => {
const { refreshAccessToken } = await import("open-sse/services/tokenRefresh/providers.js");
const out = await refreshAccessToken("qwen", "", {}, console);
const out = await refreshAccessToken("iflow", "", {}, console);
expect(out).toBeNull();
});

View File

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

View File

@@ -0,0 +1,150 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { handleTtsCore } from "../../open-sse/handlers/ttsCore.js";
import { buildTtsProviderModels, getTtsVoicesForModel } from "../../open-sse/config/ttsModels.js";
import { AI_PROVIDERS } from "../../src/shared/constants/providers.js";
import { getTtsAdapter } from "../../open-sse/handlers/ttsProviders/index.js";
import { PROVIDER_MODELS } from "../../open-sse/config/providerModels.js";
import { TTS_PROVIDER_CONFIG } from "../../src/shared/constants/ttsProviders.js";
const originalFetch = global.fetch;
function mockMiMoAudioResponse() {
global.fetch.mockResolvedValueOnce(
new Response(
JSON.stringify({
choices: [
{
message: {
role: "assistant",
audio: {
data: Buffer.from([0, 1, 2, 3]).toString("base64"),
format: "wav",
transcript: "Hello from MiMo",
},
},
},
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
)
);
}
describe("Xiaomi MiMo TTS", () => {
beforeEach(() => {
global.fetch = vi.fn();
});
afterEach(() => {
global.fetch = originalFetch;
});
it("posts to chat completions with text as assistant message and voice in audio.voice", async () => {
mockMiMoAudioResponse();
const result = await handleTtsCore({
provider: "xiaomi-mimo",
model: "mimo-v2.5-tts/冰糖",
input: "Hello from MiMo",
credentials: { apiKey: "test-key" },
responseFormat: "json",
});
expect(result.success).toBe(true);
expect(global.fetch.mock.calls[0][0]).toBe("https://api.xiaomimimo.com/v1/chat/completions");
expect(global.fetch.mock.calls[0][1].headers.Authorization).toBe("Bearer test-key");
const sent = JSON.parse(global.fetch.mock.calls[0][1].body);
expect(sent.model).toBe("mimo-v2.5-tts");
expect(sent.stream).toBe(false);
expect(sent.audio.format).toBe("wav");
expect(sent.audio.voice).toBe("冰糖");
const assistant = sent.messages.find((m) => m.role === "assistant");
expect(assistant.content).toBe("Hello from MiMo");
const body = await result.response.json();
expect(body.format).toBe("wav");
expect(body.audio).toEqual(expect.any(String));
});
it("uses mimo_default when no voice is provided", async () => {
mockMiMoAudioResponse();
await handleTtsCore({
provider: "xiaomi-mimo",
model: "mimo-v2.5-tts",
input: "Hello from MiMo",
credentials: { apiKey: "test-key" },
responseFormat: "json",
});
const sent = JSON.parse(global.fetch.mock.calls[0][1].body);
expect(sent.model).toBe("mimo-v2.5-tts");
expect(sent.audio.voice).toBe("mimo_default");
});
it("threads the style field into the role:user message", async () => {
mockMiMoAudioResponse();
await handleTtsCore({
provider: "xiaomi-mimo",
model: "mimo-v2.5-tts/Chloe",
input: "Hello from MiMo",
style: "a calm, warm female voice speaking slowly",
credentials: { apiKey: "test-key" },
responseFormat: "json",
});
const sent = JSON.parse(global.fetch.mock.calls[0][1].body);
const user = sent.messages.find((m) => m.role === "user");
expect(user.content).toBe("a calm, warm female voice speaking slowly");
});
it("exposes MiMo TTS models + preset voices in the TTS catalog", () => {
const entries = buildTtsProviderModels();
expect(entries["xiaomi-mimo-tts-models"].map((model) => model.id)).toEqual(["mimo-v2.5-tts"]);
const voices = getTtsVoicesForModel("xiaomi-mimo", "mimo-v2.5-tts");
expect(voices.map((v) => v.id)).toEqual([
"mimo_default", "冰糖", "茉莉", "苏打", "白桦", "Mia", "Chloe", "Milo", "Dean",
]);
expect(voices.find((v) => v.id === "冰糖")).toMatchObject({ name: "冰糖" });
expect(voices.find((v) => v.id === "Dean")).toMatchObject({ name: "Dean" });
// Voices are language-independent and gender-neutral (no language/gender labels in the UI)
expect(voices[0]).not.toHaveProperty("language");
expect(voices[0]).not.toHaveProperty("gender");
expect(getTtsVoicesForModel("xiaomi-mimo", "mimo-v2.5-tts-voiceclone")).toBeNull();
});
it("threads the language hint into a role:user instruction", async () => {
mockMiMoAudioResponse();
await handleTtsCore({
provider: "xiaomi-mimo",
model: "mimo-v2.5-tts/冰糖",
input: "Hello from MiMo",
language: "English",
credentials: { apiKey: "test-key" },
responseFormat: "json",
});
const sent = JSON.parse(global.fetch.mock.calls[0][1].body);
const user = sent.messages.find((m) => m.role === "user");
expect(user.content).toBe("Speak in English.");
});
it("wires the provider into media-providers TTS (serviceKind, adapter, UI config)", () => {
expect(AI_PROVIDERS["xiaomi-mimo"].serviceKinds).toContain("tts");
expect(AI_PROVIDERS["xiaomi-mimo"].ttsConfig.baseUrl).toBe("https://api.xiaomimimo.com/v1/chat/completions");
expect(getTtsAdapter("xiaomi-mimo")).toBeTruthy();
const ttsModels = PROVIDER_MODELS["xiaomi-mimo"].filter((m) => (m.kind || m.type) === "tts").map((m) => m.id);
expect(ttsModels).toEqual(["mimo-v2.5-tts"]);
expect(TTS_PROVIDER_CONFIG["xiaomi-mimo"].hasStyleInput).toBe(true);
expect(TTS_PROVIDER_CONFIG["xiaomi-mimo"].hasLanguageHint).toBe(true);
expect(TTS_PROVIDER_CONFIG["xiaomi-mimo"].languageOptions).toEqual(["Chinese", "English"]);
expect(TTS_PROVIDER_CONFIG["xiaomi-mimo"].hasVoiceIdInput).toBe(false);
});
});