Release v3.8.26 (#3875)

OmniRoute v3.8.26 — see CHANGELOG.md [3.8.26] for the full notes.

Highlights: Vertex AI media generation (#3929), GLM-5.2 effort-tier routing (#3885),
sticky round-robin combos (#3846), OpenRouter connection presets (#3878), compression
prompt-cache fix (#3936/#3890), and a security pass (form-data/vite + workflow hardening, #3949).

Co-authored-by: artickc <artickc@users.noreply.github.com>
Co-authored-by: rdself <rdself@users.noreply.github.com>
Co-authored-by: herjarsa <herjarsa@users.noreply.github.com>
Co-authored-by: Jack Smith <16862258+YunyunZhai@users.noreply.github.com>
Co-authored-by: dhaern <dhaern@users.noreply.github.com>
Co-authored-by: adivekar-utexas <adivekar-utexas@users.noreply.github.com>
Co-authored-by: megamen32 <megamen32@users.noreply.github.com>
Co-authored-by: zhiru <zhiru@users.noreply.github.com>
Co-authored-by: insoln <insoln@users.noreply.github.com>
Co-authored-by: diego-anselmo <diego-anselmo@users.noreply.github.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-16 01:00:40 -03:00
committed by GitHub
parent 1f87a9589c
commit 81a37b67ed
272 changed files with 14856 additions and 2991 deletions

View File

@@ -4,9 +4,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(
path.join(os.tmpdir(), "omniroute-active-stream-lifecycle-")
);
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-active-stream-lifecycle-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
@@ -123,14 +121,11 @@ function mergeDetailData(
data: Record<string, unknown>
): Record<string, unknown> {
const dataHasPipeline =
data?.pipelinePayloads &&
Object.keys(data.pipelinePayloads || {}).length > 0;
data?.pipelinePayloads && Object.keys(data.pipelinePayloads || {}).length > 0;
return {
...prev,
...data,
pipelinePayloads: dataHasPipeline
? data.pipelinePayloads
: prev?.pipelinePayloads,
pipelinePayloads: dataHasPipeline ? data.pipelinePayloads : prev?.pipelinePayloads,
};
}
@@ -144,17 +139,11 @@ test("streamChunks survive the full lifecycle: in-flight → completed → persi
const connectionId = "conn-lifecycle-1";
// ── Phase 1: Track a pending request ──
const requestId = usageHistory.trackPendingRequest(
model,
provider,
connectionId,
true,
{
clientRequest: { messages: [{ role: "user", content: "hello" }] },
providerRequest: { model: "gpt-4" },
clientEndpoint: "/v1/chat/completions",
}
);
const requestId = usageHistory.trackPendingRequest(model, provider, connectionId, true, {
clientRequest: { messages: [{ role: "user", content: "hello" }] },
providerRequest: { model: "gpt-4" },
clientEndpoint: "/v1/chat/completions",
});
assert.ok(requestId, "trackPendingRequest should return a request ID");
assert.ok(
@@ -185,10 +174,7 @@ test("streamChunks survive the full lifecycle: in-flight → completed → persi
apiResponse1!.pipelinePayloads as Record<string, unknown>
);
assert.ok(text1, "streamChunksText should be non-null with debugEnabled");
assert.ok(
text1!.includes("message_start"),
"streamChunksText should contain the chunk content"
);
assert.ok(text1!.includes("message_start"), "streamChunksText should contain the chunk content");
// Verify frontend state merge
const initialDetail = mergeDetailData(null, apiResponse1 as unknown as Record<string, unknown>);
@@ -308,10 +294,7 @@ test("streamChunks survive the full lifecycle: in-flight → completed → persi
// Verify DB has the entry
const dbEntry = await callLogs.getCallLogById(requestId);
assert.ok(dbEntry, "should find persisted call log by the same ID");
assert.ok(
dbEntry.pipelinePayloads,
"persisted entry should have pipelinePayloads"
);
assert.ok(dbEntry.pipelinePayloads, "persisted entry should have pipelinePayloads");
// The pipelinePayloads in the DB may have been compacted/truncated.
// streamChunks may or may not be there depending on captureStreamChunks,
@@ -390,11 +373,7 @@ test("pooling effect state merge preserves streamChunks across updates", () => {
const mergedChunks = (detailData!.pipelinePayloads as Record<string, unknown>)
.streamChunks as Record<string, string[]>;
assert.equal(mergedChunks.provider.length, 2, "merged should have 2 chunks");
assert.equal(
mergedChunks.provider[1],
'data: {"b":2}\n\n',
"merged should include new chunk"
);
assert.equal(mergedChunks.provider[1], 'data: {"b":2}\n\n', "merged should include new chunk");
// Simulate: polling response loses pipelinePayloads (null)
// This should NOT overwrite the existing streamChunks
@@ -476,15 +455,15 @@ test("pendingById references are live: push mutates the shared arrays visible to
// Verify via simulated API response
const apiResp = buildApiResponseFromPending(requestId);
const apiChunks = (apiResp!.pipelinePayloads as Record<string, unknown>)
.streamChunks as Record<string, string[]>;
const apiChunks = (apiResp!.pipelinePayloads as Record<string, unknown>).streamChunks as Record<
string,
string[]
>;
assert.equal(apiChunks.provider.length, 1, "API should see the live mutation");
});
test("no connectionId in request logger does not break anything", async () => {
const { createRequestLogger } = await import(
"../../open-sse/utils/requestLogger.ts"
);
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
usageHistory.clearPendingRequests();
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-noop-1", true);
@@ -527,9 +506,7 @@ test("createRequestLogger and trackPendingRequest with matching model propagate
// The fix: chatCore.ts now passes `model` (not `effectiveModel`) to
// createRequestLogger, so the modelKey matches what trackPendingRequest uses.
const { createRequestLogger } = await import(
"../../open-sse/utils/requestLogger.ts"
);
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
usageHistory.clearPendingRequests();
@@ -537,10 +514,9 @@ test("createRequestLogger and trackPendingRequest with matching model propagate
const provider = "openai";
const connectionId = "conn-match-1";
const requestId = usageHistory.trackPendingRequest(
model, provider, connectionId, true,
{ clientRequest: { messages: [] } }
);
const requestId = usageHistory.trackPendingRequest(model, provider, connectionId, true, {
clientRequest: { messages: [] },
});
// Use matching model (the fix)
const logger = await createRequestLogger("openai", "openai", model, {
@@ -564,11 +540,72 @@ test("createRequestLogger and trackPendingRequest with matching model propagate
const apiResp = buildApiResponseFromPending(requestId!);
assert.ok(apiResp);
const apiChunks = (apiResp!.pipelinePayloads as Record<string, unknown>).streamChunks as Record<string, string[]>;
const apiChunks = (apiResp!.pipelinePayloads as Record<string, unknown>).streamChunks as Record<
string,
string[]
>;
assert.equal(apiChunks?.provider[0], 'data: {"chunk":"hello"}');
});
test("streamChunks in completedDetails survives 5-second TTL window", async () => {
test("finalizePendingRequestById completes the exact stream when same model requests overlap", () => {
usageHistory.clearPendingRequests();
const model = "gpt-4";
const provider = "openai";
const connectionId = "conn-overlap-1";
const firstId = usageHistory.trackPendingRequest(model, provider, connectionId, true, {
clientRequest: { messages: [{ role: "user", content: "first" }] },
});
const secondId = usageHistory.trackPendingRequest(model, provider, connectionId, true, {
clientRequest: { messages: [{ role: "user", content: "second" }] },
});
const completed = usageHistory.finalizePendingRequestById(firstId, {
providerResponse: { id: "first-response" },
clientResponse: { id: "first-response" },
});
assert.equal(completed, true);
assert.ok(usageHistory.getCompletedDetails().has(firstId));
assert.equal(usageHistory.getCompletedDetails().has(secondId), false);
assert.equal(usageHistory.getPendingById().has(firstId), false);
assert.equal(usageHistory.getPendingById().has(secondId), true);
const pending = usageHistory.getPendingRequests();
const details = pending.details[connectionId]?.[`${model} (${provider})`] ?? [];
assert.equal(details.length, 1);
assert.equal(details[0].id, secondId);
assert.equal(pending.byModel[`${model} (${provider})`], 1);
assert.equal(pending.byAccount[connectionId]?.[`${model} (${provider})`], 1);
});
test("completedDetails cache evicts oldest entries when bounded", () => {
usageHistory.clearPendingRequests();
const model = "gpt-4";
const provider = "openai";
const connectionId = "conn-completed-bound";
const ids: string[] = [];
for (let i = 0; i < 260; i++) {
const id = usageHistory.trackPendingRequest(model, provider, connectionId, true);
ids.push(id!);
const completed = usageHistory.finalizePendingRequestById(id, {
clientResponse: { choices: [{ message: { content: `done ${i}` } }] },
});
assert.equal(completed, true);
}
assert.ok(
usageHistory.getCompletedDetails().size <= 256,
"completedDetails should remain bounded"
);
assert.equal(usageHistory.getCompletedDetails().has(ids[0]), false);
assert.equal(usageHistory.getCompletedDetails().has(ids[ids.length - 1]), true);
});
test("streamChunks in completedDetails survives beyond the logs polling window", async () => {
usageHistory.clearPendingRequests();
const model = "gpt-4";
@@ -605,10 +642,9 @@ test("streamChunks in completedDetails survives 5-second TTL window", async () =
"streamChunks should be in completedDetails"
);
// The completedDetails TTL is 5000ms. We verify by saving to DB first, then
// waiting for the TTL to expire, then verifying the data is gone from
// completedDetails but still accessible from the DB (via getCallLogById).
// Save to DB
// Save to DB as the normal durable path, but keep the completedDetails cache
// long enough that a slow Logs-page poll does not see the row disappear
// between pending removal and DB/detail refresh.
await callLogs.saveCallLog({
id: requestId,
method: "POST",
@@ -640,18 +676,17 @@ test("streamChunks in completedDetails survives 5-second TTL window", async () =
const dbEntry1 = await callLogs.getCallLogById(requestId);
assert.ok(dbEntry1, "DB should have the entry after saveCallLog");
// Wait for TTL to expire
// This used to expire after 5 seconds. Keep it visible beyond that window so
// a slow client poll can still resolve the completed row/details.
await new Promise((r) => setTimeout(r, 5100));
// completedDetails should be cleared
assert.ok(
!usageHistory.getCompletedDetails().has(requestId),
"completedDetails should have been cleared after TTL"
usageHistory.getCompletedDetails().has(requestId),
"completedDetails should still be available after a 5-second polling gap"
);
// But DB should still have it
const dbEntry2 = await callLogs.getCallLogById(requestId);
assert.ok(dbEntry2, "DB should still have the entry after TTL expiry");
assert.ok(dbEntry2, "DB should still have the entry after the polling gap");
assert.equal(
(dbEntry2 as Record<string, unknown>).id,
requestId,

View File

@@ -8,22 +8,30 @@
import test from "node:test";
import assert from "node:assert/strict";
// @ts-expect-error — .mjs helper has no type declarations; runtime shape is known.
import { parseCodeQLAlerts } from "../../../scripts/check/check-codeql-ratchet.mjs";
import {
parseCodeQLAlerts,
evaluateCodeqlRatchet,
} from "../../../scripts/check/check-codeql-ratchet.mjs";
type RatchetVerdict = { regressed: boolean; improved: boolean };
const evaluate = evaluateCodeqlRatchet as (current: number, baseline: number) => RatchetVerdict;
// ---------------------------------------------------------------------------
// Fixtures — synthetic GitHub code-scanning/alerts API responses
// ---------------------------------------------------------------------------
/** Helper to build a minimal alert object. */
function makeAlert(overrides: {
number?: number;
state?: "open" | "dismissed" | "fixed";
tool?: string;
ruleId?: string;
severity?: string;
securitySeverity?: string;
dismissedReason?: string | null;
} = {}) {
function makeAlert(
overrides: {
number?: number;
state?: "open" | "dismissed" | "fixed";
tool?: string;
ruleId?: string;
severity?: string;
securitySeverity?: string;
dismissedReason?: string | null;
} = {}
) {
return {
number: overrides.number ?? 1,
state: overrides.state ?? "open",
@@ -92,17 +100,13 @@ test("parseCodeQLAlerts: array vazio retorna alertCount=0", () => {
// ---------------------------------------------------------------------------
test("parseCodeQLAlerts: alerta dismissed NÃO conta (Hard Rule #14)", () => {
const alerts = [
makeAlert({ state: "dismissed", dismissedReason: "false positive" }),
];
const alerts = [makeAlert({ state: "dismissed", dismissedReason: "false positive" })];
const result = parseCodeQLAlerts(alerts);
assert.equal(result.alertCount, 0, "dismissed alert must not be counted");
});
test("parseCodeQLAlerts: alerta dismissed com razão 'used in tests' NÃO conta", () => {
const alerts = [
makeAlert({ state: "dismissed", dismissedReason: "used in tests" }),
];
const alerts = [makeAlert({ state: "dismissed", dismissedReason: "used in tests" })];
const result = parseCodeQLAlerts(alerts);
assert.equal(result.alertCount, 0);
});
@@ -122,9 +126,7 @@ test("parseCodeQLAlerts: alerta dismissed independente da razão NÃO conta", ()
// ---------------------------------------------------------------------------
test("parseCodeQLAlerts: alerta fixed NÃO conta", () => {
const alerts = [
makeAlert({ state: "fixed" as "open" | "dismissed" | "fixed" }),
];
const alerts = [makeAlert({ state: "fixed" as "open" | "dismissed" | "fixed" })];
const result = parseCodeQLAlerts(alerts);
assert.equal(result.alertCount, 0, "fixed alerts are not open, must not count");
});
@@ -276,9 +278,50 @@ test("parseCodeQLAlerts: alerta sem rule retorna byRule com 'unknown'", () => {
test("parseCodeQLAlerts: dismissed com mesmo ruleId que open — dismissed não aparece em byRule", () => {
const alerts = [
makeAlert({ number: 1, state: "open", ruleId: "js/sql-injection" }),
makeAlert({ number: 2, state: "dismissed", ruleId: "js/sql-injection", dismissedReason: "false positive" }),
makeAlert({
number: 2,
state: "dismissed",
ruleId: "js/sql-injection",
dismissedReason: "false positive",
}),
];
const result = parseCodeQLAlerts(alerts);
assert.equal(result.alertCount, 1);
assert.equal(result.byRule["js/sql-injection"], 1, "only the open alert should appear in byRule");
});
// ---------------------------------------------------------------------------
// evaluateCodeqlRatchet — ratchet direction:down (Task 7.3 promote to blocking)
// Mirror of evaluateDeadCode: regression when measured > baseline; the baseline
// is 0 (clean), so ANY open CodeQL alert is a regression that blocks.
// ---------------------------------------------------------------------------
test("evaluateCodeqlRatchet: equal to baseline passes (0 vs 0 — clean)", () => {
const r = evaluate(0, 0);
assert.equal(r.regressed, false);
assert.equal(r.improved, false);
});
test("evaluateCodeqlRatchet: one more alert than baseline 0 is a regression", () => {
const r = evaluate(1, 0);
assert.equal(r.regressed, true, "a single new open CodeQL alert must block");
assert.equal(r.improved, false);
});
test("evaluateCodeqlRatchet: fewer alerts than a non-zero baseline is an improvement", () => {
const r = evaluate(2, 5);
assert.equal(r.regressed, false);
assert.equal(r.improved, true);
});
test("evaluateCodeqlRatchet: zero alerts against a non-zero baseline is a maximum improvement", () => {
const r = evaluate(0, 5);
assert.equal(r.regressed, false);
assert.equal(r.improved, true);
});
test("evaluateCodeqlRatchet: strict integer comparison — any increase regresses", () => {
assert.equal(evaluate(6, 5).regressed, true);
assert.equal(evaluate(5, 5).regressed, false);
assert.equal(evaluate(4, 5).regressed, false);
});

View File

@@ -0,0 +1,174 @@
// tests/unit/build/check-openapi-breaking.test.ts
// TDD unit tests for scripts/check/check-openapi-breaking.mjs — Fase 8 B.4 oasdiff.
//
// Strategy:
// • parseOasdiffBreaking() — pure parser, tested with synthetic oasdiff JSON
// (the REAL shape emitted by `oasdiff breaking --format json`, verified at 1.19.1).
// • binary-absent SKIP — spawn the gate with a PATH stripped of oasdiff and assert
// it prints `openapiBreaking=SKIP reason=binary-absent` and exits 0 (advisory).
import test from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
// @ts-expect-error — .mjs helper has no type declarations; runtime shape is known.
import { parseOasdiffBreaking } from "../../../scripts/check/check-openapi-breaking.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, "../../..");
const GATE = path.join(REPO_ROOT, "scripts/check/check-openapi-breaking.mjs");
// ---------------------------------------------------------------------------
// Fixtures — REAL shape of `oasdiff breaking --format json` (oasdiff 1.19.1).
// e.g. removing a path emits:
// {"id":"api-path-removed-without-deprecation","text":"api path removed...",
// "level":3,"operation":"GET","path":"/api/bar","section":"paths",...}
// ---------------------------------------------------------------------------
function makeBreaking(
overrides: {
id?: string;
text?: string;
level?: number;
operation?: string;
path?: string;
} = {}
) {
return {
id: overrides.id ?? "api-path-removed-without-deprecation",
text: overrides.text ?? "api path removed without deprecation",
level: overrides.level ?? 3,
operation: overrides.operation ?? "GET",
path: overrides.path ?? "/api/foo",
section: "paths",
baseSource: { file: "/tmp/base.yaml", line: 12, column: 5 },
fingerprint: "33e8aeb41d4a",
};
}
// ---------------------------------------------------------------------------
// parseOasdiffBreaking — empty / invalid input
// ---------------------------------------------------------------------------
test("parseOasdiffBreaking: null retorna count=0", () => {
const r = parseOasdiffBreaking(null);
assert.equal(r.count, 0);
assert.deepEqual(r.byId, {});
assert.deepEqual(r.byPath, {});
assert.deepEqual(r.items, []);
});
test("parseOasdiffBreaking: undefined retorna count=0", () => {
const r = parseOasdiffBreaking(undefined as unknown as null);
assert.equal(r.count, 0);
});
test("parseOasdiffBreaking: array vazio (sem breaking change) retorna count=0", () => {
const r = parseOasdiffBreaking([]);
assert.equal(r.count, 0);
assert.deepEqual(r.byId, {});
assert.deepEqual(r.byPath, {});
});
test("parseOasdiffBreaking: objeto (não-array) retorna count=0", () => {
const r = parseOasdiffBreaking({ id: "x" } as unknown as null);
assert.equal(r.count, 0);
});
test("parseOasdiffBreaking: string retorna count=0", () => {
const r = parseOasdiffBreaking("breaking" as unknown as null);
assert.equal(r.count, 0);
});
test("parseOasdiffBreaking: número retorna count=0", () => {
const r = parseOasdiffBreaking(42 as unknown as null);
assert.equal(r.count, 0);
});
// ---------------------------------------------------------------------------
// parseOasdiffBreaking — counting
// ---------------------------------------------------------------------------
test("parseOasdiffBreaking: 1 breaking change retorna count=1", () => {
const r = parseOasdiffBreaking([makeBreaking()]);
assert.equal(r.count, 1);
assert.equal(r.items.length, 1);
});
test("parseOasdiffBreaking: 3 breaking changes retorna count=3", () => {
const r = parseOasdiffBreaking([
makeBreaking({ id: "api-path-removed-without-deprecation", path: "/api/a" }),
makeBreaking({ id: "request-parameter-became-required", path: "/api/b" }),
makeBreaking({ id: "response-property-removed", path: "/api/c" }),
]);
assert.equal(r.count, 3);
});
test("parseOasdiffBreaking: ignora entradas null/não-objeto no array", () => {
const r = parseOasdiffBreaking([makeBreaking(), null, 5, makeBreaking({ path: "/api/x" })]);
assert.equal(r.count, 2);
});
// ---------------------------------------------------------------------------
// parseOasdiffBreaking — grouping
// ---------------------------------------------------------------------------
test("parseOasdiffBreaking: agrupa por id em byId", () => {
const r = parseOasdiffBreaking([
makeBreaking({ id: "api-path-removed-without-deprecation", path: "/api/a" }),
makeBreaking({ id: "response-property-removed", path: "/api/b" }),
makeBreaking({ id: "api-path-removed-without-deprecation", path: "/api/c" }),
]);
assert.equal(r.byId["api-path-removed-without-deprecation"], 2);
assert.equal(r.byId["response-property-removed"], 1);
});
test("parseOasdiffBreaking: agrupa por path em byPath", () => {
const r = parseOasdiffBreaking([
makeBreaking({ path: "/api/chat", operation: "POST" }),
makeBreaking({ path: "/api/chat", operation: "GET" }),
makeBreaking({ path: "/api/models" }),
]);
assert.equal(r.byPath["/api/chat"], 2);
assert.equal(r.byPath["/api/models"], 1);
});
test("parseOasdiffBreaking: id ausente usa 'unknown'", () => {
const r = parseOasdiffBreaking([{ path: "/api/x", text: "weird" }]);
assert.equal(r.count, 1);
assert.equal(r.byId["unknown"], 1);
});
test("parseOasdiffBreaking: path ausente usa 'unknown'", () => {
const r = parseOasdiffBreaking([{ id: "some-rule", text: "weird" }]);
assert.equal(r.count, 1);
assert.equal(r.byPath["unknown"], 1);
});
// ---------------------------------------------------------------------------
// Integration — binary-absent SKIP (advisory, exit 0)
//
// Run the gate with a PATH that does NOT contain oasdiff. `findOasdiff()` then
// returns null and the gate must SKIP gracefully with exit 0. We point HOME to a
// temp dir too so a user-local ~/.local/bin/oasdiff cannot leak into the lookup.
// ---------------------------------------------------------------------------
test("gate: SKIP graceful (exit 0) quando oasdiff ausente do PATH", () => {
const res = spawnSync(process.execPath, [GATE, "--quiet"], {
cwd: REPO_ROOT,
encoding: "utf8",
timeout: 30_000,
env: {
// Minimal PATH with only the dir holding the node binary — no oasdiff.
PATH: path.dirname(process.execPath),
HOME: "/nonexistent-home-for-oasdiff-test",
BASE_REF: "origin/release/v3.8.26",
},
});
assert.equal(res.status, 0, `expected exit 0, got ${res.status}; stderr: ${res.stderr}`);
assert.match(
res.stdout,
/openapiBreaking=SKIP reason=binary-absent/,
`expected binary-absent SKIP, got stdout: ${res.stdout}`
);
});

View File

@@ -30,6 +30,11 @@ test("checkTrackedArtifacts: quality-metrics.json is flagged", () => {
assert.equal(result.length, 1);
});
test("checkTrackedArtifacts: config/quality/quality-metrics.json is flagged", () => {
const result = checkTrackedArtifacts(["config/quality/quality-metrics.json"]);
assert.equal(result.length, 1);
});
test("checkTrackedArtifacts: symlink mode (120000) is flagged", () => {
const result = checkTrackedArtifacts([], ["node_modules"]);
assert.equal(result.length, 1);

View File

@@ -0,0 +1,65 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
// Regression guard for #3883: the @omniroute/opencode-plugin must ship an
// ESM-only bundle. OpenCode's Bun-based plugin loader resolves the package
// `main`/`exports` and applies CJS-to-ESM interop on a dual CJS bundle, which
// turns `mod.default` into the whole exports namespace, fails V1 plugin
// detection, and makes the loader throw "Plugin export is not a function".
// Shipping ESM-only (with a `./runtime` subpath) keeps `mod.default` the V1
// plugin object so the loader registers it. Do NOT re-introduce a CJS bundle.
const PKG_URL = new URL("../../../@omniroute/opencode-plugin/package.json", import.meta.url);
const TSUP_URL = new URL("../../../@omniroute/opencode-plugin/tsup.config.ts", import.meta.url);
function readJson(url: URL): Record<string, unknown> {
return JSON.parse(readFileSync(fileURLToPath(url), "utf8"));
}
test("opencode-plugin package.json ships ESM-only (no CJS bundle)", () => {
const pkg = readJson(PKG_URL);
// main must point at the ESM build, never the .cjs bundle.
assert.equal(pkg.main, "./dist/index.js", "main must be the ESM build");
assert.equal(
typeof pkg.main === "string" && pkg.main.endsWith(".cjs"),
false,
"main must not be a .cjs bundle"
);
// The legacy dual-bundle `module` field should be gone.
assert.equal("module" in pkg, false, "the dual-bundle `module` field must be removed");
const exports = pkg.exports as Record<string, unknown> | undefined;
assert.ok(exports && typeof exports === "object", "exports map must exist");
const root = exports["."] as Record<string, unknown>;
assert.ok(root && typeof root === "object", 'exports["."] must exist');
assert.equal(root.import, "./dist/index.js", 'exports["."].import must be the ESM build');
// A `require` condition would re-introduce the CJS path the loader chokes on.
assert.equal("require" in root, false, 'exports["."] must not declare a `require` condition');
// The `./runtime` subpath used by the OpenCode loader must resolve to ESM.
const runtime = exports["./runtime"] as Record<string, unknown>;
assert.ok(runtime && typeof runtime === "object", 'exports["./runtime"] must exist');
assert.equal(runtime.import, "./dist/index.js", 'exports["./runtime"].import must be ESM');
});
test("opencode-plugin tsup config builds ESM-only with cjsInterop disabled", () => {
const tsup = readFileSync(fileURLToPath(TSUP_URL), "utf8");
const formatMatch = tsup.match(/format:\s*\[([^\]]*)\]/);
assert.ok(formatMatch, "tsup config must declare a format array");
const formats = formatMatch[1]
.split(",")
.map((s) => s.trim().replace(/['"]/g, ""))
.filter(Boolean);
assert.deepEqual(formats, ["esm"], "tsup must build esm only (no cjs)");
assert.equal(
/cjsInterop:\s*true/.test(tsup),
false,
"cjsInterop must not be true for an ESM-only build"
);
});

View File

@@ -40,6 +40,7 @@ const CHAT_OPENAI_COMPAT_PROVIDER_IDS = [
"predibase",
"bytez",
"reka",
"byteplus",
];
test("chat-openai-compat providers are registered across provider metadata, registry and local catalog", () => {

View File

@@ -18,17 +18,13 @@ test("no unapproved deps when all are allowlisted", () => {
});
test("flags a dependency not on the allowlist (potential slopsquat)", () => {
assert.deepEqual(
findUnapprovedDeps(["react", "reactt-router"], new Set(["react"])),
["reactt-router"]
);
assert.deepEqual(findUnapprovedDeps(["react", "reactt-router"], new Set(["react"])), [
"reactt-router",
]);
});
test("flags multiple new deps, preserves order, de-dupes", () => {
assert.deepEqual(
findUnapprovedDeps(["a", "b", "a", "c"], new Set(["a"])),
["b", "c"]
);
assert.deepEqual(findUnapprovedDeps(["a", "b", "a", "c"], new Set(["a"])), ["b", "c"]);
});
// --- 6A.8: automatic workspace discovery ---
@@ -64,7 +60,7 @@ test("6A.8: discoverManifests does NOT include node_modules, .next, or deep refe
});
test("6A.8: all workspace package deps are in the allowlist (gate exits 0 with expanded scope)", () => {
const allowlistPath = path.join(repoRoot, "dependency-allowlist.json");
const allowlistPath = path.join(repoRoot, "config/quality/dependency-allowlist.json");
const allowlist = new Set(JSON.parse(fs.readFileSync(allowlistPath, "utf8")).allowed || []);
const manifests = discoverManifests(repoRoot);
const allDeps: string[] = [];
@@ -80,7 +76,11 @@ test("6A.8: all workspace package deps are in the allowlist (gate exits 0 with e
);
}
const unapproved = findUnapprovedDeps(allDeps, allowlist);
assert.deepEqual(unapproved, [], `expected all deps to be approved, got: ${unapproved.join(", ")}`);
assert.deepEqual(
unapproved,
[],
`expected all deps to be approved, got: ${unapproved.join(", ")}`
);
});
// --- 6A.8: stale-allowlist enforcement ---
@@ -102,10 +102,9 @@ test("6A.8 stale: a dep removed from all manifests is detected as stale in allow
test("7.8 evaluateDepAge: package older than 72h is OK", () => {
const now = Date.now();
const createdMs = now - 73 * 60 * 60 * 1000; // 73 hours ago
const { ok, ageHours } = (evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number })(
createdMs,
now
);
const { ok, ageHours } = (
evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number }
)(createdMs, now);
assert.ok(ok, "package older than 72h should be ok");
assert.ok(ageHours >= 73, "ageHours should reflect elapsed time");
});
@@ -113,20 +112,18 @@ test("7.8 evaluateDepAge: package older than 72h is OK", () => {
test("7.8 evaluateDepAge: package exactly at 72h boundary is OK (boundary inclusive)", () => {
const now = Date.now();
const createdMs = now - 72 * 60 * 60 * 1000; // exactly 72 hours ago
const { ok } = (evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number })(
createdMs,
now
);
const { ok } = (
evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number }
)(createdMs, now);
assert.ok(ok, "package at exactly 72h should be ok (inclusive boundary)");
});
test("7.8 evaluateDepAge: package published 1h ago is NOT OK", () => {
const now = Date.now();
const createdMs = now - 1 * 60 * 60 * 1000; // 1 hour ago
const { ok, ageHours } = (evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number })(
createdMs,
now
);
const { ok, ageHours } = (
evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number }
)(createdMs, now);
assert.ok(!ok, "package published 1h ago should NOT be ok");
assert.ok(ageHours < 72, "ageHours should reflect < 72h");
});
@@ -135,28 +132,23 @@ test("7.8 evaluateDepAge: respects custom minAgeHours", () => {
const now = Date.now();
const createdMs = now - 10 * 60 * 60 * 1000; // 10 hours ago
// With 24h minimum, 10h-old package should fail
const { ok: failWith24 } = (evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number })(
createdMs,
now,
24
);
const { ok: failWith24 } = (
evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number }
)(createdMs, now, 24);
assert.ok(!failWith24, "10h-old package should fail with 24h minimum");
// With 6h minimum, 10h-old package should pass
const { ok: passWith6 } = (evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number })(
createdMs,
now,
6
);
const { ok: passWith6 } = (
evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number }
)(createdMs, now, 6);
assert.ok(passWith6, "10h-old package should pass with 6h minimum");
});
test("7.8 evaluateDepAge: future timestamp (time.created in future) is NOT OK", () => {
const now = Date.now();
const createdMs = now + 1000; // 1s in future (clock skew edge case)
const { ok, ageHours } = (evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number })(
createdMs,
now
);
const { ok, ageHours } = (
evaluateDepAge as (a: number, b: number, c?: number) => { ok: boolean; ageHours: number }
)(createdMs, now);
assert.ok(!ok, "future timestamp should not be ok");
assert.ok(ageHours < 0, "ageHours should be negative for future timestamp");
});
@@ -169,11 +161,17 @@ test("7.8 evaluateDepAge: future timestamp (time.created in future) is NOT OK",
// - any real npm package like "react" is found and old → does NOT appear in any list
test("7.8 auditNewDepsRegistry: empty dep list returns all-empty results", () => {
const result = (auditNewDepsRegistry as (deps: string[], minAge?: number, now?: number) => {
notFound: string[];
tooNew: Array<{ name: string; ageHours: number }>;
offline: string[];
})([], 72, Date.now());
const result = (
auditNewDepsRegistry as (
deps: string[],
minAge?: number,
now?: number
) => {
notFound: string[];
tooNew: Array<{ name: string; ageHours: number }>;
offline: string[];
}
)([], 72, Date.now());
assert.deepEqual(result.notFound, []);
assert.deepEqual(result.tooNew, []);
assert.deepEqual(result.offline, []);

View File

@@ -0,0 +1,100 @@
import { test } from "node:test";
import assert from "node:assert";
import { execFileSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
parseProviderTotal,
tallyDrift,
readProviderTotal,
countLocales,
} from "../../scripts/check/check-docs-counts-sync.mjs";
// Explicit types for the .mjs exports — keep the test at 0 no-explicit-any warnings.
const parse = parseProviderTotal as (text: string) => number;
const tally = tallyDrift as (
checks: {
label: string;
actual: number;
docKey: string;
strict: boolean;
files: string[];
}[],
getContent: (file: string) => string | null
) => { strict: number; soft: number; lines: string[] };
const readTotal = readProviderTotal as () => number;
const locales = countLocales as () => number;
const here = path.dirname(fileURLToPath(import.meta.url));
const GATE = path.resolve(here, "../../scripts/check/check-docs-counts-sync.mjs");
// --- parseProviderTotal (pure) -------------------------------------------------------
test("parses the canonical provider total from the auto-generated catalog text", () => {
assert.equal(parse("Total providers: **226**. See category breakdown below."), 226);
});
test("returns 0 when no total marker is present", () => {
assert.equal(parse("# Provider Reference\n\nNo total here."), 0);
assert.equal(parse(""), 0);
});
// --- tallyDrift (pure) ---------------------------------------------------------------
const strictCheck = {
label: "Provider count",
actual: 226,
docKey: "providers",
strict: true,
files: ["README.md", "AGENTS.md"],
};
test("no drift when every file mentions the real count", () => {
const { strict, soft } = tally([strictCheck], () => "we have 226 providers");
assert.equal(strict, 0);
assert.equal(soft, 0);
});
test("STRICT drift is counted when a file omits the real count", () => {
const { strict, soft } = tally([strictCheck], (f) =>
f === "README.md" ? "we have 226 providers" : "we have 177 providers"
);
assert.equal(strict, 1, "AGENTS.md (177) should register one strict drift");
assert.equal(soft, 0);
});
test("SOFT drift does not count as strict", () => {
const softCheck = { ...strictCheck, strict: false };
const { strict, soft } = tally([softCheck], () => "no number here");
assert.equal(strict, 0);
assert.equal(soft, 2, "both files miss → two soft drifts");
});
test("a check with actual=0 is skipped (source count undetermined)", () => {
const zero = { ...strictCheck, actual: 0 };
const { strict, soft } = tally([zero], () => null);
assert.equal(strict, 0);
assert.equal(soft, 0);
});
test("a missing file (null content) registers drift, not a crash", () => {
const { strict } = tally([strictCheck], () => null);
assert.equal(strict, 2);
});
// --- live source readers (smoke) -----------------------------------------------------
test("readProviderTotal reads a real, positive total from the catalog", () => {
assert.ok(readTotal() > 100, "provider catalog total should be > 100");
});
test("countLocales reads a real, positive locale count from config/i18n.json", () => {
assert.ok(locales() >= 40, "i18n config should define at least 40 locales");
});
// --- live gate smoke -----------------------------------------------------------------
test("the gate exits 0 against the current (synced) repo state", () => {
// Throws if exit code is non-zero; current docs are synced so this must pass.
assert.doesNotThrow(() => execFileSync("node", [GATE], { encoding: "utf8", stdio: "pipe" }));
});

View File

@@ -20,6 +20,13 @@ import {
diffCloudAgents,
type ExecutorLike,
} from "../../scripts/check/check-known-symbols.ts";
import { reportStaleEntries } from "../../scripts/check/lib/allowlist.mjs";
import { ROUTING_STRATEGY_VALUES } from "../../src/shared/constants/routingStrategies.ts";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), "../../..");
// ───────────────────────────────────────────────────────────────────────────
// (2) COMBO STRATEGIES — extractHandledStrategies + diffComboStrategies
@@ -174,9 +181,57 @@ test("findNewTranslatorPairs returns [] when live is a subset of frozen", () =>
// Allowlist / snapshot sanity (documented frozen sets stay well-formed)
// ───────────────────────────────────────────────────────────────────────────
test("IMPLICIT_DEFAULT_STRATEGIES documents `priority` with a justification", () => {
assert.ok(Object.prototype.hasOwnProperty.call(IMPLICIT_DEFAULT_STRATEGIES, "priority"));
assert.ok(IMPLICIT_DEFAULT_STRATEGIES.priority.length > 20);
test("IMPLICIT_DEFAULT_STRATEGIES: every documented key carries a non-trivial justification", () => {
// The map may be empty (all canonical strategies are referenced in combo.ts), but any
// entry that DOES exist must explain why the strategy has no explicit dispatch branch.
for (const [key, justification] of Object.entries(IMPLICIT_DEFAULT_STRATEGIES)) {
assert.ok(typeof justification === "string" && justification.length > 20, `weak/missing justification for "${key}"`);
}
});
// --- stale-allowlist enforcement (6A.3) ---
test("stale-enforcement: an IMPLICIT_DEFAULT_STRATEGIES key now referenced in dispatch is reported as stale", () => {
// "priority" gained a `strategy === "priority"` reference in combo.ts, so it is now in
// the handled set and no longer in canonicalNotHandled — an implicit-default for it would
// suppress nothing → stale.
const liveImplicitNeeded: string[] = []; // canonicalNotHandled WITHOUT the allowlist
const stale = (reportStaleEntries as (a: string[], l: string[], g: string) => string[])(
["priority"],
liveImplicitNeeded,
"known-symbols:combo"
);
assert.deepEqual(stale, ["priority"]);
});
test("stale-enforcement: an IMPLICIT_DEFAULT_STRATEGIES key still uncovered by dispatch is NOT stale", () => {
// A canonical strategy with no `strategy === "..."` reference IS still suppressed by the
// implicit-default, so the entry is live (must stay).
const liveImplicitNeeded = ["priority"]; // priority would be canonicalNotHandled without it
const stale = (reportStaleEntries as (a: string[], l: string[], g: string) => string[])(
["priority"],
liveImplicitNeeded,
"known-symbols:combo"
);
assert.deepEqual(stale, []);
});
test("stale-enforcement: live repo IMPLICIT_DEFAULT_STRATEGIES has no stale entries", () => {
// Every key must still be a canonical strategy that lacks a `strategy === "..."` reference
// in combo.ts; otherwise the suppression is dead and must be removed.
const comboSource = fs.readFileSync(path.join(REPO_ROOT, "open-sse/services/combo.ts"), "utf8");
const handled = extractHandledStrategies(comboSource);
const liveImplicitNeeded = diffComboStrategies(
ROUTING_STRATEGY_VALUES as readonly string[],
handled,
{}
).canonicalNotHandled;
const stale = (reportStaleEntries as (a: string[], l: string[], g: string) => string[])(
Object.keys(IMPLICIT_DEFAULT_STRATEGIES),
liveImplicitNeeded,
"known-symbols:combo"
);
assert.deepEqual(stale, [], `IMPLICIT_DEFAULT_STRATEGIES has stale entries: ${stale.join(", ")}`);
});
test("KNOWN_TRANSLATOR_PAIRS is a non-empty, well-formed, deduped from:to snapshot", () => {

View File

@@ -6,6 +6,7 @@ const {
CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH,
CLAUDE_CODE_COMPATIBLE_DEFAULT_MAX_TOKENS,
CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA,
CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA,
CLAUDE_CODE_COMPATIBLE_STAINLESS_TIMEOUT_SECONDS,
isClaudeCodeCompatibleProvider,
stripAnthropicMessagesSuffix,
@@ -14,6 +15,7 @@ const {
joinClaudeCodeCompatibleUrl,
buildClaudeCodeCompatibleHeaders,
buildClaudeCodeCompatibleValidationPayload,
resolveClaudeCodeCompatibleAnthropicBeta,
resolveClaudeCodeCompatibleSessionId,
} = await import("../../open-sse/services/claudeCodeCompatible.ts");
@@ -61,6 +63,10 @@ test("Claude Code compatible beta set matches the stable API-key Claude CLI prof
assert.ok(CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA.includes("claude-code-20250219"));
assert.ok(CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA.includes("interleaved-thinking-2025-05-14"));
assert.ok(CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA.includes("effort-2025-11-24"));
assert.equal(
CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA.includes(CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA),
false
);
assert.equal(CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA.includes("oauth-2025-04-20"), false);
assert.equal(
CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA.includes("context-management-2025-06-27"),
@@ -72,6 +78,33 @@ test("Claude Code compatible beta set matches the stable API-key Claude CLI prof
);
});
test("Claude Code compatible redacted thinking beta is explicit opt-in", () => {
assert.equal(
resolveClaudeCodeCompatibleAnthropicBeta({}).includes(
CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA
),
false
);
assert.equal(
resolveClaudeCodeCompatibleAnthropicBeta({ redactThinking: true }).includes(
CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA
),
true
);
assert.equal(
buildClaudeCodeCompatibleHeaders("sk-demo", true)["anthropic-beta"].includes(
CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA
),
false
);
assert.equal(
buildClaudeCodeCompatibleHeaders("sk-demo", true, undefined, {
redactThinking: true,
})["anthropic-beta"].includes(CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA),
true
);
});
test("resolveClaudeCodeCompatibleSessionId prefers explicit session headers and generates a fallback id", () => {
const headers = new Headers({
"x-session-id": "legacy-session",

View File

@@ -92,6 +92,29 @@ test("expandAutoComboCandidatePool is a no-op when an explicit candidatePool exi
assert.equal(result[0].modelStr, "openai/gpt-4o");
});
test("expandAutoComboCandidatePool falls through to active connections when candidatePool is an empty array", async () => {
await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
name: "OpenAI",
apiKey: "sk-test-openai",
defaultModel: "gpt-4o-mini",
});
const expanded = await combo.expandAutoComboCandidatePool([], {
config: { auto: { candidatePool: [] } },
});
// An empty candidatePool should NOT trigger early return — the function
// should fall through and expand from active connections instead.
assert.ok(
expanded.length > 0,
"expected expansion from active connections despite empty candidatePool"
);
const openaiTargets = expanded.filter((t) => t.provider === "openai");
assert.ok(openaiTargets.length > 0, "expected openai targets to be expanded");
});
test("expandAutoComboCandidatePool does not duplicate an already-present modelStr", async () => {
await providersDb.createProviderConnection({
provider: "openai",

View File

@@ -18,7 +18,8 @@ const settingsDb = await import("../../src/lib/db/settings.ts");
const { resetAllComboMetrics } = await import("../../open-sse/services/comboMetrics.ts");
const { resetAllCircuitBreakers, getCircuitBreaker } =
await import("../../src/shared/utils/circuitBreaker.ts");
const { resetAll: resetAllSemaphores } = await import("../../open-sse/services/rateLimitSemaphore.ts");
const { resetAll: resetAllSemaphores } =
await import("../../open-sse/services/rateLimitSemaphore.ts");
const { _resetAllDecks } = await import("../../src/shared/utils/shuffleDeck.ts");
const { clearSessions } = await import("../../open-sse/services/sessionManager.ts");
@@ -290,6 +291,74 @@ test("strict-random falls back to the remaining target when the deck pick fails"
assert.notEqual(calls[0], calls[1]);
});
test("round-robin uses existing stickyRoundRobinLimit for combo target batching", async () => {
const calls: string[] = [];
const combo = {
name: "rr-sticky-combo-batches",
strategy: "round-robin",
models: ["openai/a", "claude/b", "gemini/c"],
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0 },
};
for (let i = 0; i < 10; i += 1) {
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: { stickyRoundRobinLimit: 3 },
allCombos: null,
});
assert.equal(result.ok, true);
}
assert.deepEqual(calls, [
"openai/a",
"openai/a",
"openai/a",
"claude/b",
"claude/b",
"claude/b",
"gemini/c",
"gemini/c",
"gemini/c",
"openai/a",
]);
});
test("round-robin sticky batching fallback success becomes sticky target", async () => {
const calls: string[] = [];
const combo = {
name: "rr-sticky-fallback-success",
strategy: "round-robin",
models: ["openai/a", "claude/b", "gemini/c"],
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0 },
};
for (let i = 0; i < 4; i += 1) {
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
if (modelStr === "openai/a") return errorResponse(503, "a is down");
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: { stickyRoundRobinLimit: 2 },
allCombos: null,
});
assert.equal(result.ok, true);
}
assert.deepEqual(calls, ["openai/a", "claude/b", "claude/b", "gemini/c", "gemini/c"]);
});
test("strict-random survives a stale deck entry after a target is removed", async () => {
const comboTwoTargets = {
name: "strict-random-stale",

View File

@@ -0,0 +1,58 @@
/**
* Tests for #3890: the cache-aware `skipSystemPrompt` flag was computed by
* getCacheAwareStrategy() but dropped by selectCompressionStrategy() (which can only
* return a mode string). resolveCacheAwareConfig() applies it: in a caching context the
* system prompt must stay uncompressed (it is part of the cacheable prefix) even when the
* operator disabled preserveSystemPrompt.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { resolveCacheAwareConfig } from "../../../open-sse/services/compression/strategySelector.ts";
import type { CompressionConfig } from "../../../open-sse/services/compression/types.ts";
function cfg(overrides: Partial<CompressionConfig> = {}): CompressionConfig {
return {
enabled: true,
defaultMode: "standard",
autoTriggerTokens: 0,
cacheMinutes: 5,
preserveSystemPrompt: true,
comboOverrides: {},
...overrides,
} as CompressionConfig;
}
describe("resolveCacheAwareConfig (#3890)", () => {
it("forces preserveSystemPrompt on for a caching request that disabled it", () => {
const out = resolveCacheAwareConfig(
cfg({ preserveSystemPrompt: false }),
{ messages: [{ role: "system", content: "x", cache_control: { type: "ephemeral" } }] },
{ provider: "anthropic", targetFormat: "claude" }
);
assert.equal(out.preserveSystemPrompt, true);
});
it("leaves a non-caching request untouched (preserveSystemPrompt stays false)", () => {
const out = resolveCacheAwareConfig(
cfg({ preserveSystemPrompt: false }),
{ messages: [{ role: "system", content: "x" }] },
{ provider: "openai" }
);
assert.equal(out.preserveSystemPrompt, false);
});
it("returns the same config object when there is no body", () => {
const base = cfg({ preserveSystemPrompt: false });
assert.equal(resolveCacheAwareConfig(base), base);
});
it("does not change a config that already preserves the system prompt", () => {
const out = resolveCacheAwareConfig(
cfg({ preserveSystemPrompt: true }),
{ messages: [{ role: "system", content: "x", cache_control: { type: "ephemeral" } }] },
{ provider: "anthropic", targetFormat: "claude" }
);
assert.equal(out.preserveSystemPrompt, true);
});
});

View File

@@ -14,6 +14,7 @@ import { PROVIDERS } from "../../open-sse/config/constants.ts";
import {
CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION,
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA,
CONTEXT_1M_BETA_HEADER,
} from "../../open-sse/services/claudeCodeCompatible.ts";
@@ -103,10 +104,7 @@ test("DefaultExecutor.buildUrl uses full chat endpoints for hosted OpenAI-compat
bazaarlink.buildUrl("auto:free", true),
"https://bazaarlink.ai/api/v1/chat/completions"
);
assert.equal(
crof.buildUrl("gpt-4.1", true),
"https://crof.ai/v1/chat/completions"
);
assert.equal(crof.buildUrl("gpt-4.1", true), "https://crof.ai/v1/chat/completions");
});
test("DefaultExecutor.buildUrl handles openai-compatible and anthropic-compatible providers", () => {
@@ -570,7 +568,7 @@ test("DefaultExecutor.execute uses CC-compatible connection defaults to append 1
apiKey: "cc-key",
providerSpecificData: {
ccSessionId: "session-1",
requestDefaults: { context1m: true },
requestDefaults: { context1m: true, redactThinking: true },
},
},
extendedContext: false,
@@ -598,7 +596,15 @@ test("DefaultExecutor.execute uses CC-compatible connection defaults to append 1
}
assert.equal(calls[0].headers["anthropic-beta"].includes(CONTEXT_1M_BETA_HEADER), false);
assert.equal(
calls[0].headers["anthropic-beta"].includes(CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA),
false
);
assert.equal(calls[1].headers["anthropic-beta"].includes(CONTEXT_1M_BETA_HEADER), true);
assert.equal(
calls[1].headers["anthropic-beta"].includes(CLAUDE_CODE_COMPATIBLE_REDACT_THINKING_BETA),
true
);
assert.equal(calls[2].headers["anthropic-beta"], undefined);
});
@@ -742,6 +748,44 @@ test("DefaultExecutor.transformRequest respects disableStreamOptions for OpenAI
assert.deepEqual((chatResultEnabled as any).stream_options, { include_usage: true });
});
test("DefaultExecutor.transformRequest injects OpenRouter connection preset", () => {
const executor = new DefaultExecutor("openrouter");
const body = { model: "openai/gpt-4", messages: [{ role: "user", content: "hi" }] };
const result = executor.transformRequest("openai/gpt-4", body, true, {
providerSpecificData: { preset: " email-copywriter " },
});
assert.equal((result as any).preset, "email-copywriter");
assert.deepEqual((result as any).stream_options, { include_usage: true });
assert.equal((body as any).preset, undefined);
const explicit = executor.transformRequest(
"openai/gpt-4",
{ ...body, preset: "client-preset" },
true,
{ providerSpecificData: { preset: "connection-preset" } }
);
assert.equal((explicit as any).preset, "client-preset");
const explicitNull = executor.transformRequest("openai/gpt-4", { ...body, preset: null }, true, {
providerSpecificData: { preset: "connection-preset" },
});
assert.equal((explicitNull as any).preset, null);
const explicitEmpty = executor.transformRequest("openai/gpt-4", { ...body, preset: "" }, true, {
providerSpecificData: { preset: "connection-preset" },
});
assert.equal((explicitEmpty as any).preset, "");
const blank = executor.transformRequest("openai/gpt-4", body, true, {
providerSpecificData: { preset: " " },
});
assert.equal((blank as any).preset, undefined);
});
test("DefaultExecutor.transformRequest strips stream_options from Anthropic-compatible targets", () => {
const anthropicCompat = new DefaultExecutor("anthropic-compatible-test");
const anthropicCcCompat = new DefaultExecutor("anthropic-compatible-cc-test");

View File

@@ -0,0 +1,105 @@
/**
* Tests for #3890: prompt-cache misses when memory injection is enabled.
*
* When the client uses prompt caching (cache_control breakpoints), the previous behavior
* prepended the (per-query, varying) memory message at index 0 — shifting the entire
* cacheable prefix and forcing a cache miss on every turn. With `cacheSafe`, memory is
* inserted just before the last user message so the cacheable prefix stays byte-stable.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { injectMemory } from "../../src/lib/memory/injection.ts";
import type { ChatRequest } from "../../src/lib/memory/injection.ts";
import type { Memory } from "../../src/lib/memory/types.ts";
function mem(content: string): Memory {
return {
id: `mem-${content}`,
content,
type: "factual" as any,
apiKeyId: "k",
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
importance: 0.5,
};
}
function multiTurn(): ChatRequest {
return {
model: "anthropic/claude-sonnet-4-6",
messages: [
{ role: "system", content: "SYSTEM PROMPT", cache_control: { type: "ephemeral" } } as any,
{ role: "user", content: "turn 1 question" },
{ role: "assistant", content: "turn 1 answer" },
{ role: "user", content: "turn 2 question" },
],
};
}
describe("injectMemory cache-safe positioning (#3890)", () => {
it("default (cacheSafe off) prepends memory at index 0 — unchanged legacy behavior", () => {
const out = injectMemory(multiTurn(), [mem("dark mode")], "anthropic");
assert.equal(out.messages[0].role, "system");
assert.ok(out.messages[0].content.includes("Memory context"));
assert.equal(out.messages[1].content, "SYSTEM PROMPT");
});
it("cacheSafe inserts memory just before the last user message, preserving the prefix", () => {
const req = multiTurn();
const prefixBefore = JSON.stringify(req.messages.slice(0, 3)); // sys, u1, a1
const out = injectMemory(req, [mem("dark mode")], "anthropic", { cacheSafe: true });
// The cacheable prefix (system + prior turns up to the last assistant) is byte-identical.
assert.equal(JSON.stringify(out.messages.slice(0, 3)), prefixBefore);
// Memory is injected right before the last user message...
assert.equal(out.messages[3].role, "system");
assert.ok(out.messages[3].content.includes("Memory context"));
// ...and the last user message is preserved at the tail.
assert.equal(out.messages[4].content, "turn 2 question");
assert.equal(out.messages.length, 5);
// Memory must NOT be at index 0 (that is what broke caching).
assert.notEqual(out.messages[0].content, out.messages[3].content);
assert.equal(out.messages[0].content, "SYSTEM PROMPT");
});
it("cacheSafe keeps the cacheable prefix identical across two turns despite different memories", () => {
// Turn 1: [sys, u1]; Turn 2: [sys, u1, a1, u2]. Different memories retrieved per query.
// Same conversation, observed on two consecutive turns.
const turn1: ChatRequest = {
model: "anthropic/claude-sonnet-4-6",
messages: [
{ role: "system", content: "SYSTEM PROMPT", cache_control: { type: "ephemeral" } } as any,
{ role: "user", content: "turn 1 question" },
],
};
const turn2 = multiTurn();
const out1 = injectMemory(turn1, [mem("A")], "anthropic", { cacheSafe: true });
const out2 = injectMemory(turn2, [mem("B")], "anthropic", { cacheSafe: true });
// The cache-breakpoint-bearing system message stays at the head, byte-identical, in
// both turns (and is NOT displaced by the per-query memory) — so the prompt cache
// created on turn 1 still matches on turn 2. (Memory is inserted before the last user
// turn: [SYS, MEM_A, u1] and [SYS, u1, a1, MEM_B, u2] respectively.)
assert.deepEqual(out1.messages[0], out2.messages[0]);
assert.equal(out1.messages[0].content, "SYSTEM PROMPT");
assert.equal((out1.messages[0] as any).cache_control?.type, "ephemeral");
// In turn 2 the earlier turns up to the last assistant are preserved before memory.
assert.equal(out2.messages[1].content, "turn 1 question");
assert.equal(out2.messages[2].content, "turn 1 answer");
assert.ok(out2.messages[3].content.includes("Memory context"));
assert.equal(out2.messages[4].content, "turn 2 question");
});
it("cacheSafe falls back to leading injection when there is no user message", () => {
const req: ChatRequest = {
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "system", content: "SYS" }],
};
const out = injectMemory(req, [mem("x")], "anthropic", { cacheSafe: true });
assert.equal(out.messages[0].role, "system");
assert.ok(out.messages[0].content.includes("Memory context"));
assert.equal(out.messages[1].content, "SYS");
});
});

View File

@@ -767,7 +767,9 @@ test("Request: posts to correct Perplexity SSE endpoint", async () => {
assert.equal(capturedUrl, "https://www.perplexity.ai/rest/sse/perplexity_ask");
assert.equal(capturedHeaders["Origin"], "https://www.perplexity.ai");
assert.equal(capturedHeaders["X-App-ApiVersion"], "client-1.11.0");
assert.equal(capturedHeaders["x-perplexity-request-endpoint"], "https://www.perplexity.ai/rest/sse/perplexity_ask");
assert.equal(capturedHeaders["x-perplexity-request-reason"], "ask-query-state-provider");
assert.ok(capturedHeaders["x-request-id"], "x-request-id header should be set");
assert.equal(capturedHeaders["Accept"], "text/event-stream");
} finally {
globalThis.fetch = original;

View File

@@ -53,6 +53,25 @@ test("provider models helpers resolve provider IDs through aliases", () => {
assert.deepEqual(getModelsByProviderId("provider-that-does-not-exist"), []);
});
test("getProviderModels returns models for both the alias and the raw provider id", () => {
// Pick a provider whose alias differs from its id (e.g. "github" → "gh").
const aliased = Object.entries(PROVIDER_ID_TO_ALIAS).find(([id, a]) => id !== a) as
| [string, string]
| undefined;
if (!aliased) return; // no aliased providers → trivially satisfied
const [rawId, alias] = aliased;
const byAlias = getProviderModels(alias);
const byRawId = getProviderModels(rawId);
assert.ok(byAlias.length > 0, `expected models under alias "${alias}"`);
assert.deepEqual(
byRawId,
byAlias,
`getProviderModels("${rawId}") should return the same models as getProviderModels("${alias}")`
);
});
test("Reka registry exposes preset models", () => {
const rekaModels = getModelsByProviderId("reka");
const ids = rekaModels.map((model) => model.id);

View File

@@ -202,9 +202,10 @@ test("getCodexRequestDefaults returns reasoningEffort (transitive import guard)"
assert.equal(typeof result.reasoningEffort, "string");
});
test("getClaudeCodeCompatibleRequestDefaults returns context1m boolean", () => {
test("getClaudeCodeCompatibleRequestDefaults returns CC-compatible booleans", () => {
const result = getClaudeCodeCompatibleRequestDefaults(null);
assert.equal(typeof result.context1m, "boolean");
assert.equal(typeof result.redactThinking, "boolean");
});
test("compatProtocolLabelKey maps protocol strings to i18n keys", () => {
@@ -216,10 +217,7 @@ test("compatProtocolLabelKey maps protocol strings to i18n keys", () => {
test("extractCommandCodeCredentialInput extracts from JSON/URL/raw (transitive import guard)", () => {
assert.equal(extractCommandCodeCredentialInput(" "), "");
assert.equal(extractCommandCodeCredentialInput("rawtoken"), "rawtoken");
assert.equal(
extractCommandCodeCredentialInput(JSON.stringify({ apiKey: "abc123" })),
"abc123"
);
assert.equal(extractCommandCodeCredentialInput(JSON.stringify({ apiKey: "abc123" })), "abc123");
});
test("normalizeAndValidateHttpBaseUrl validates http/https URLs", () => {

View File

@@ -10,6 +10,7 @@ process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const { invalidateDbCache } = await import("../../src/lib/db/readCache.ts");
const { invalidateCacheControlSettingsCache } =
await import("../../src/lib/cacheControlSettings.ts");
const { clearCache } = await import("../../src/lib/semanticCache.ts");
@@ -17,14 +18,12 @@ const { clearIdempotency } = await import("../../src/lib/idempotencyLayer.ts");
const { getPendingRequests, clearPendingRequests } =
await import("../../src/lib/usage/usageHistory.ts");
const { clearInflight } = await import("../../open-sse/services/requestDedup.ts");
const {
resetAll: resetAccountSemaphores,
} = await import("../../open-sse/services/accountSemaphore.ts");
const { resetAll: resetAccountSemaphores } =
await import("../../open-sse/services/accountSemaphore.ts");
const { clearModelLock } = await import("../../open-sse/services/accountFallback.ts");
const { getCallLogs, getCallLogById } = await import("../../src/lib/usage/callLogs.ts");
const { handleChatCore } = await import("../../open-sse/handlers/chatCore.ts");
const { resetPayloadRulesConfigForTests } =
await import("../../open-sse/services/payloadRules.ts");
const { resetPayloadRulesConfigForTests } = await import("../../open-sse/services/payloadRules.ts");
const originalFetch = globalThis.fetch;
@@ -61,11 +60,22 @@ async function resetStorage() {
clearInflight();
clearModelLock();
core.resetDbInstance();
// A full reset must also drop the settings read-cache. Otherwise the cached
// value (e.g. call_log_pipeline_enabled=true seeded earlier) survives the DB
// wipe and silently masks the fact that the fresh DB has the default. In CI
// under load this cache is evicted at unpredictable times, so tests that rely
// on the stale cache flake. Make the reset honest and deterministic here.
invalidateDbCache("settings");
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.before(async () => {
// Re-seed per test, NOT once: every `afterEach` runs resetStorage(), which wipes
// the DB and drops the settings cache. A run-once `before` only guaranteed the
// flag for the first test; later tests depended on a stale cache surviving the
// wipe, which flakes under CI load. beforeEach re-establishes it deterministically.
test.beforeEach(async () => {
await resetStorage();
await settingsDb.updateSettings({ call_log_pipeline_enabled: true });
});
@@ -117,22 +127,23 @@ test("network failure persisted call log includes providerRequest in pipeline pa
const detail = await waitFor(getLatestCallLog);
assert.ok(detail, "expected a call log to be persisted");
assert.ok(detail.pipelinePayloads, "expected pipeline payloads when call_log_pipeline_enabled is true");
assert.ok(
detail.pipelinePayloads,
"expected pipeline payloads when call_log_pipeline_enabled is true"
);
assert.ok(
detail.pipelinePayloads.providerRequest,
"providerRequest must be present in pipeline payloads even on network failure"
);
const providerReqBody =
detail.pipelinePayloads.providerRequest.body ??
detail.pipelinePayloads.providerRequest;
detail.pipelinePayloads.providerRequest.body ?? detail.pipelinePayloads.providerRequest;
assert.equal(
providerReqBody.model,
"gpt-4o-mini",
"providerRequest should contain the translated model"
);
const messages =
providerReqBody.messages ??
(Array.isArray(providerReqBody) ? providerReqBody : null);
providerReqBody.messages ?? (Array.isArray(providerReqBody) ? providerReqBody : null);
if (messages) {
assert.equal(messages[0]?.content, "hello");
}
@@ -141,10 +152,7 @@ test("network failure persisted call log includes providerRequest in pipeline pa
null,
"providerResponse should be null/absent on network failure (no response received)"
);
assert.ok(
detail.pipelinePayloads.error,
"pipeline payloads should include the error details"
);
assert.ok(detail.pipelinePayloads.error, "pipeline payloads should include the error details");
});
test("network timeout persisted call log includes providerRequest in pipeline payloads", async () => {
@@ -181,10 +189,7 @@ test("network timeout persisted call log includes providerRequest in pipeline pa
await waitForAsyncSideEffects();
assert.equal(result.success, false);
assert.ok(
result.status === 504,
`expected 504 timeout, got ${result.status}`
);
assert.ok(result.status === 504, `expected 504 timeout, got ${result.status}`);
const detail = await waitFor(getLatestCallLog);
assert.ok(detail, "expected a call log to be persisted");
@@ -195,12 +200,8 @@ test("network timeout persisted call log includes providerRequest in pipeline pa
"providerRequest must be present in pipeline payloads on timeout"
);
const providerReqBody =
detail.pipelinePayloads?.providerRequest?.body ??
detail.pipelinePayloads?.providerRequest;
assert.equal(
providerReqBody?.model,
"gpt-4o-mini"
);
detail.pipelinePayloads?.providerRequest?.body ?? detail.pipelinePayloads?.providerRequest;
assert.equal(providerReqBody?.model, "gpt-4o-mini");
} finally {
if (originalGetTimeoutMs) executor.getTimeoutMs = originalGetTimeoutMs;
}
@@ -311,10 +312,7 @@ test("successful response includes both providerRequest and providerResponse in
assert.ok(detail, "expected a call log to be persisted");
assert.ok(detail.pipelinePayloads, "expected pipeline payloads");
assert.ok(
detail.pipelinePayloads.providerRequest,
"providerRequest must be present on success"
);
assert.ok(detail.pipelinePayloads.providerRequest, "providerRequest must be present on success");
assert.ok(
detail.pipelinePayloads.providerResponse,
"providerResponse must be present on success"

View File

@@ -42,7 +42,7 @@ test("provider schemas reject non-boolean openaiStoreEnabled values", () => {
assert.equal(updated.success, false);
});
test("provider schemas accept boolean requestDefaults.context1m for CC-compatible providers", () => {
test("provider schemas accept boolean CC-compatible request defaults", () => {
const created = createProviderSchema.safeParse({
provider: "anthropic-compatible-cc-demo",
apiKey: "token",
@@ -50,6 +50,7 @@ test("provider schemas accept boolean requestDefaults.context1m for CC-compatibl
providerSpecificData: {
requestDefaults: {
context1m: true,
redactThinking: true,
},
},
});
@@ -57,6 +58,7 @@ test("provider schemas accept boolean requestDefaults.context1m for CC-compatibl
providerSpecificData: {
requestDefaults: {
context1m: false,
redactThinking: false,
},
},
});
@@ -65,7 +67,7 @@ test("provider schemas accept boolean requestDefaults.context1m for CC-compatibl
assert.equal(updated.success, true);
});
test("provider schemas reject non-boolean requestDefaults.context1m values", () => {
test("provider schemas reject non-boolean CC-compatible request defaults", () => {
const created = createProviderSchema.safeParse({
provider: "anthropic-compatible-cc-demo",
apiKey: "token",
@@ -73,6 +75,7 @@ test("provider schemas reject non-boolean requestDefaults.context1m values", ()
providerSpecificData: {
requestDefaults: {
context1m: "yes",
redactThinking: "yes",
},
},
});
@@ -80,6 +83,7 @@ test("provider schemas reject non-boolean requestDefaults.context1m values", ()
providerSpecificData: {
requestDefaults: {
context1m: 1,
redactThinking: 1,
},
},
});
@@ -127,3 +131,47 @@ test("provider schemas reject unknown Codex service tiers", () => {
assert.equal(created.success, false);
assert.equal(updated.success, false);
});
test("provider schemas accept OpenRouter preset in providerSpecificData", () => {
const created = createProviderSchema.safeParse({
provider: "openrouter",
apiKey: "token",
name: "OpenRouter",
providerSpecificData: {
preset: "email-copywriter",
},
});
const updated = updateProviderConnectionSchema.safeParse({
providerSpecificData: {
preset: "code-reviewer",
},
});
const padded = updateProviderConnectionSchema.safeParse({
providerSpecificData: {
preset: `${" ".repeat(120)}prefer${" ".repeat(120)}`,
},
});
assert.equal(created.success, true);
assert.equal(updated.success, true);
assert.equal(padded.success, true);
});
test("provider schemas reject oversized OpenRouter preset values", () => {
const created = createProviderSchema.safeParse({
provider: "openrouter",
apiKey: "token",
name: "OpenRouter",
providerSpecificData: {
preset: "x".repeat(201),
},
});
const updated = updateProviderConnectionSchema.safeParse({
providerSpecificData: {
preset: 123,
},
});
assert.equal(created.success, false);
assert.equal(updated.success, false);
});

View File

@@ -1780,7 +1780,7 @@ test("specialty validator accepts Nous Research credentials on chat completions"
const headers = init.headers as Record<string, string>;
const body = JSON.parse(String(init.body));
assert.equal(headers.Authorization, "Bearer nous-key");
assert.equal(body.model, "nousresearch/hermes-4-70b");
assert.equal(body.model, "Hermes-4-70B");
return new Response(
JSON.stringify({
id: "chatcmpl-nous",
@@ -1802,6 +1802,33 @@ test("specialty validator accepts Nous Research credentials on chat completions"
assert.equal(nous.method, "nous_chat_completions");
});
test("BytePlus key validation reaches the Ark endpoint instead of 'not supported' (#3877)", async () => {
// #3877: byteplus was in APIKEY_PROVIDERS but never registered in the routing
// registry, so validation returned {unsupported:true} → UI showed "invalid" for any
// key. With the registry entry, a valid ark-... key probes the Ark /models endpoint
// with Bearer auth and validates.
let probedModelsUrl: string | null = null;
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://ark.ap-southeast.bytepluses.com/api/v3/models") {
probedModelsUrl = target;
const headers = toPlainHeaders(init.headers);
assert.equal(headers.Authorization, "Bearer ark-test-key");
return new Response(JSON.stringify({ data: [{ id: "kimi-k2-thinking" }] }), { status: 200 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const result = await validateProviderApiKey({
provider: "byteplus",
apiKey: "ark-test-key",
});
assert.equal(result.unsupported, undefined, "byteplus must not be 'validation not supported'");
assert.equal(result.valid, true);
assert.equal(probedModelsUrl, "https://ark.ap-southeast.bytepluses.com/api/v3/models");
});
test("specialty validator rejects invalid Nous Research credentials", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
@@ -1823,6 +1850,34 @@ test("specialty validator rejects invalid Nous Research credentials", async () =
assert.equal(nous.error, "Invalid API key");
});
test("specialty validator accepts Nous Research key when probe model is rejected (400)", async () => {
// #3881: a valid key whose probe model is rejected (model-not-found / bad request)
// must still validate — the 4xx proves auth was accepted, only the request shape
// was wrong. Mirrors the longcat/nvidia validators.
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target === "https://inference-api.nousresearch.com/v1/chat/completions") {
const headers = init.headers as Record<string, string>;
assert.equal(headers.Authorization, "Bearer nous-key");
return new Response(
JSON.stringify({ error: { message: "model not found", type: "invalid_request_error" } }),
{ status: 400 }
);
}
throw new Error(`unexpected fetch: ${target}`);
};
const nous = await validateProviderApiKey({
provider: "nous-research",
apiKey: "nous-key",
});
assert.equal(nous.valid, true);
assert.equal(nous.method, "nous_chat_completions");
});
test("specialty validator rejects invalid Poe credentials", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);

View File

@@ -4,6 +4,7 @@ import http from "node:http";
import proxyFetch, {
runWithProxyContext,
runWithProxyContextOrDirect,
runWithTlsTracking,
isTlsFingerprintActive,
} from "../../open-sse/utils/proxyFetch.ts";
@@ -165,3 +166,40 @@ test("runWithProxyContext accepts reachable HTTP proxy endpoints and returns cal
}
);
});
test("runWithProxyContext throws PROXY_UNREACHABLE for an unreachable proxy by default", async () => {
// 127.0.0.1:9 (discard) refuses connections — the proxy is unreachable.
await assert.rejects(
runWithProxyContext({ type: "http", host: "127.0.0.1", port: "9" }, async () => "unreachable"),
/Proxy unreachable/
);
});
test("runWithProxyContext degrades to a direct connection when directFallbackOnUnreachable is set", async () => {
let ran = false;
const result = await runWithProxyContext(
{ type: "http", host: "127.0.0.1", port: "9" },
async () => {
ran = true;
return "direct-ok";
},
{ directFallbackOnUnreachable: true }
);
assert.equal(ran, true, "callback must still run via a direct connection");
assert.equal(result, "direct-ok");
});
test("runWithProxyContextOrDirect runs the callback directly when the proxy is unreachable", async () => {
let ran = false;
const result = await runWithProxyContextOrDirect(
{ type: "http", host: "127.0.0.1", port: "9" },
async () => {
ran = true;
return "ok";
}
);
assert.equal(ran, true);
assert.equal(result, "ok");
});

View File

@@ -43,26 +43,30 @@ test("ensureOpenAIStoreSessionFallback injects session_id only when no stable ca
assert.equal(withExplicitSession.session_id, "existing-session");
});
test("normalizeProviderSpecificData keeps only boolean CC-compatible 1M request defaults", () => {
test("normalizeProviderSpecificData keeps only boolean CC-compatible request defaults", () => {
const normalized = normalizeProviderSpecificData("anthropic-compatible-cc-demo", {
baseUrl: "https://proxy.example.com/v1/messages?beta=true",
requestDefaults: {
context1m: true,
redactThinking: true,
customFlag: "keep-me",
},
});
assert.deepEqual(getClaudeCodeCompatibleRequestDefaults(normalized), {
context1m: true,
redactThinking: true,
});
assert.deepEqual(normalized?.requestDefaults, {
context1m: true,
redactThinking: true,
customFlag: "keep-me",
});
const stripped = normalizeProviderSpecificData("anthropic-compatible-cc-demo", {
requestDefaults: {
context1m: "yes",
redactThinking: "yes",
customFlag: "keep-me",
},
});
@@ -70,3 +74,37 @@ test("normalizeProviderSpecificData keeps only boolean CC-compatible 1M request
customFlag: "keep-me",
});
});
test("normalizeProviderSpecificData trims OpenRouter preset and clears empty values", () => {
const normalized = normalizeProviderSpecificData("openrouter", {
preset: " email-copywriter ",
tag: "primary",
});
assert.equal(normalized?.preset, "email-copywriter");
assert.equal(normalized?.tag, "primary");
const stripped = normalizeProviderSpecificData("openrouter", {
preset: " ",
tag: "primary",
});
assert.equal(stripped?.preset, undefined);
assert.equal(stripped?.tag, "primary");
const oversized = normalizeProviderSpecificData("openrouter", {
preset: "x".repeat(201),
tag: "primary",
});
assert.equal(oversized?.preset, undefined);
assert.equal(oversized?.tag, "primary");
const ignored = normalizeProviderSpecificData("openai", {
preset: "email-copywriter",
tag: "primary",
});
assert.equal(ignored?.preset, undefined);
assert.equal(ignored?.tag, "primary");
});

View File

@@ -87,6 +87,40 @@ test("updatePendingRequestStreamChunks stores stream chunks in the detail", () =
assert.equal(detail.streamChunks.provider[0], 'data: {"a":1}');
});
test("updatePendingRequest keeps pending detail API view in sync", () => {
usageHistory.clearPendingRequests();
const requestId = usageHistory.trackPendingRequest(
"claude-sonnet-4-6",
"cc-test",
"conn-1",
true,
{
clientRequest: { model: "cc-test/claude-sonnet-4-6", reasoning_effort: "xhigh" },
providerRequest: { model: "claude-sonnet-4-6", reasoning_effort: "xhigh" },
}
);
assert.ok(requestId, "trackPendingRequest should return an id");
usageHistory.updatePendingRequest("claude-sonnet-4-6", "cc-test", "conn-1", {
providerRequest: { model: "claude-sonnet-4-6", reasoning_effort: "high" },
stage: "provider_response_started",
});
const modelKey = "claude-sonnet-4-6 (cc-test)";
const detailFromQueue = usageHistory.getPendingRequests().details["conn-1"]?.[modelKey]?.[0];
const detailFromId = usageHistory.getPendingById().get(requestId);
assert.equal(detailFromId, detailFromQueue);
assert.deepEqual(detailFromId?.providerRequest, {
model: "claude-sonnet-4-6",
reasoning_effort: "high",
});
assert.deepEqual(detailFromId?.clientRequest, {
model: "cc-test/claude-sonnet-4-6",
reasoning_effort: "xhigh",
});
});
test("updatePendingRequestStreamChunks stores empty streamChunks object (not null)", () => {
usageHistory.clearPendingRequests();
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", true);
@@ -168,6 +202,75 @@ test("GET /api/usage/call-logs/[id] route exists and uses auth", () => {
assert.ok(content.includes("getCallLogById"), "should import getCallLogById");
});
test("GET /api/usage/call-logs includes completed in-memory fallback rows", async () => {
const { buildCallLogListRows } = await import("../../src/app/api/usage/call-logs/route.ts");
usageHistory.clearPendingRequests();
const requestId = usageHistory.trackPendingRequest("gpt-4", "openai", "completed-conn-1", true, {
clientEndpoint: "/v1/chat/completions",
});
assert.ok(requestId);
const completed = usageHistory.finalizePendingRequestById(requestId, {
clientResponse: { choices: [{ message: { content: "done" } }] },
});
assert.equal(completed, true);
const completedDetail = usageHistory.getCompletedDetails().get(requestId);
assert.ok(completedDetail);
const rows = buildCallLogListRows({
logs: [],
connections: [{ id: "completed-conn-1", displayName: "Completed Account" }],
pendingDetails: usageHistory.getPendingById().values(),
completedDetails: usageHistory.getCompletedDetails().values(),
now: completedDetail!.startedAt + 60_000,
});
assert.equal(rows.length, 1);
assert.equal(rows[0].id, requestId);
assert.equal(rows[0].detailState, "in-memory");
assert.equal(rows[0].completed, true);
assert.equal(rows[0].account, "Completed Account");
assert.equal(rows[0].duration, completedDetail!.durationMs);
});
test("GET /api/usage/call-logs rows are globally timestamp ordered", async () => {
const { buildCallLogListRows } = await import("../../src/app/api/usage/call-logs/route.ts");
const rows = buildCallLogListRows({
logs: [
{
id: "persisted-newest",
timestamp: new Date(3000).toISOString(),
active: false,
},
{
id: "persisted-oldest",
timestamp: new Date(1000).toISOString(),
active: false,
},
],
connections: [],
pendingDetails: [],
completedDetails: [
{
id: "completed-middle",
model: "gpt-4",
provider: "openai",
connectionId: "conn",
startedAt: 2000,
completedAt: 2500,
durationMs: 500,
},
],
now: 4000,
});
assert.deepEqual(
rows.map((row) => row.id),
["persisted-newest", "completed-middle", "persisted-oldest"]
);
});
test("getCallLogById returns null for unknown id", async () => {
const log = await callLogs.getCallLogById("nonexistent-id-12345");
assert.equal(log, null);
@@ -382,6 +485,49 @@ test("createRequestLogger with connectionId/model/provider populates streamChunk
assert.deepEqual(detail.streamChunks.client, []);
});
test("createRequestLogger requestId binds streamChunks to the exact pending request", async () => {
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
usageHistory.clearPendingRequests();
const firstId = usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-a", true);
const secondId = usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-b", true);
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
enabled: true,
captureStreamChunks: true,
requestId: secondId,
});
logger.appendProviderChunk('data: {"content":"second"}');
const first = usageHistory.getPendingById().get(firstId);
const second = usageHistory.getPendingById().get(secondId);
assert.equal(first?.streamChunks, undefined);
assert.equal(second?.streamChunks?.provider[0], 'data: {"content":"second"}');
});
test("createRequestLogger fallback match does not cross connectionId boundaries", async () => {
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
usageHistory.clearPendingRequests();
usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-a", true);
const secondId = usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-b", true);
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
enabled: true,
captureStreamChunks: true,
connectionId: "test-conn-b",
model: "gpt-4",
provider: "openai",
});
logger.appendProviderChunk('data: {"content":"conn-b"}');
const second = usageHistory.getPendingById().get(secondId);
assert.equal(second?.streamChunks?.provider[0], 'data: {"content":"conn-b"}');
});
test("createRequestLogger without connectionId does not populate streamChunks", async () => {
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
usageHistory.clearPendingRequests();

View File

@@ -0,0 +1,70 @@
import test from "node:test";
import assert from "node:assert/strict";
const { openaiResponsesToOpenAIResponse } =
await import("../../open-sse/translator/response/openai-responses.ts");
/**
* Regression: Responses → Chat Completions streaming must announce the assistant
* role on the FIRST emitted delta.
*
* OpenAI Chat Completions streams put `role: "assistant"` on the first chunk's
* delta. The Responses API has no role-announcement event, so the translator must
* synthesize it. Strict streaming clients — notably @langchain/openai's
* `_convertDeltaToMessageChunk` (used by n8n's AI Agent) — key off the first
* chunk's role to build an AIMessageChunk. Without it, streamed tool_call deltas
* are dropped and the agent receives an empty response.
*/
test("Responses->Chat: first tool_call chunk announces role=assistant", () => {
const state: Record<string, unknown> = {};
const first = openaiResponsesToOpenAIResponse(
{
type: "response.output_item.added",
item: {
type: "function_call",
call_id: "call_abc",
name: "get_weather",
arguments: "",
},
},
state
);
assert.ok(first, "should emit a chunk for output_item.added");
assert.equal(
first.choices[0].delta.role,
"assistant",
"first emitted delta must carry role=assistant"
);
assert.equal(first.choices[0].delta.tool_calls[0].function.name, "get_weather");
// Subsequent argument deltas must NOT repeat the role announcement.
const next = openaiResponsesToOpenAIResponse(
{ type: "response.function_call_arguments.delta", delta: '{"x":1}' },
state
);
assert.ok(next, "should emit a chunk for arguments.delta");
assert.equal(next.choices[0].delta.role, undefined, "only the first delta announces the role");
});
test("Responses->Chat: first text chunk announces role=assistant", () => {
const state: Record<string, unknown> = {};
const first = openaiResponsesToOpenAIResponse(
{ type: "response.output_text.delta", delta: "Olá" },
state
);
assert.ok(first, "should emit a chunk for output_text.delta");
assert.equal(first.choices[0].delta.role, "assistant");
assert.equal(first.choices[0].delta.content, "Olá");
const next = openaiResponsesToOpenAIResponse(
{ type: "response.output_text.delta", delta: " mundo" },
state
);
assert.equal(next.choices[0].delta.role, undefined);
assert.equal(next.choices[0].delta.content, " mundo");
});

View File

@@ -399,6 +399,44 @@ test("pipeWithDisconnect clears pending requests when the upstream stream errors
assert.equal(pending.details[connectionId], undefined);
});
test("pipeWithDisconnect lets controller onError own pending cleanup", async () => {
clearPendingRequests();
const provider = "openai";
const model = "gpt-stream-error-owned";
const connectionId = "conn-stream-error-owned";
const modelKey = `${model} (${provider})`;
let errorEvent = null;
trackPendingRequest(model, provider, connectionId, true);
const source = new ReadableStream({
start(controller) {
controller.error(Object.assign(new Error("terminated"), { statusCode: 502 }));
},
});
const stream = pipeWithDisconnect(
new Response(source),
new TransformStream(),
createStreamController({
provider,
model,
connectionId,
onError(event) {
errorEvent = event;
return true;
},
})
);
const text = await readStreamText(stream);
const pending = getPendingRequests();
assert.match(text, /"message":"terminated"/);
assert.equal(errorEvent?.statusCode, 502);
assert.equal(pending.byModel[modelKey], 1);
assert.equal(pending.byAccount[connectionId][modelKey], 1);
});
test("pipeWithDisconnect does not double-clear transform errors already accounted for", async () => {
clearPendingRequests();
const provider = "openai";

View File

@@ -65,6 +65,78 @@ function parseJsonDataPayloads(text) {
.map((data) => JSON.parse(data));
}
function parseSillyTavernCustomOpenAIStream(text) {
const events = [];
let reasoning = "";
let content = "";
for (const payload of parseJsonDataPayloads(text)) {
const choices = Array.isArray(payload.choices) ? payload.choices : [];
const reasoningDelta =
choices.find((choice) => choice?.delta?.reasoning_content)?.delta?.reasoning_content ??
choices.find((choice) => choice?.delta?.reasoning)?.delta?.reasoning ??
"";
const contentDelta =
choices[0]?.delta?.content ?? choices[0]?.message?.content ?? choices[0]?.text ?? "";
reasoning += reasoningDelta;
content += contentDelta;
events.push({ reasoningDelta, contentDelta, reasoning, content });
}
return { reasoning, content, events };
}
test("createSSEStream leaves successful pending requests for onComplete finalization", async () => {
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
usageHistory.clearPendingRequests();
const model = "gpt-4";
const provider = "openai";
const connectionId = "stream-on-complete-finalize";
const requestId = usageHistory.trackPendingRequest(model, provider, connectionId, true);
let finalizedInOnComplete = false;
await readTransformed(
[
`data: ${JSON.stringify({
id: "chatcmpl_pending_finalize",
object: "chat.completion.chunk",
created: 1,
model,
choices: [{ index: 0, delta: { content: "hello" }, finish_reason: null }],
})}\n\n`,
`data: ${JSON.stringify({
id: "chatcmpl_pending_finalize",
object: "chat.completion.chunk",
created: 1,
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
})}\n\n`,
],
{
mode: "passthrough",
sourceFormat: FORMATS.OPENAI,
provider,
model,
connectionId,
body: { messages: [{ role: "user", content: "hello" }] },
onComplete(payload) {
finalizedInOnComplete = usageHistory.finalizePendingRequestById(requestId, {
providerResponse: payload.providerPayload,
clientResponse: payload.clientPayload,
});
},
}
);
assert.equal(finalizedInOnComplete, true);
assert.ok(usageHistory.getCompletedDetails().has(requestId));
assert.equal(usageHistory.getPendingById().has(requestId), false);
usageHistory.clearPendingRequests();
});
test.after(() => {
core.resetDbInstance();
if (fs.existsSync(TEST_DATA_DIR)) {
@@ -726,8 +798,9 @@ test("createSSEStream translate mode converts Claude SSE into OpenAI chunks and
assert.match(text, /\[DONE\]/);
assert.equal(onCompletePayload.status, 200);
assert.equal(onCompletePayload.responseBody.choices[0].message.content, "Hello Claude");
assert.equal(onCompletePayload.responseBody.usage.prompt_tokens, 3);
assert.equal(onCompletePayload.responseBody.usage.completion_tokens, 4);
assert.equal(onCompletePayload.responseBody.usage.total_tokens, 4);
assert.equal(onCompletePayload.responseBody.usage.total_tokens, 7);
});
test("createSSEStream translate mode preserves Claude text_delta thinking tags as content", async () => {
@@ -1378,6 +1451,53 @@ test("createSSEStream passthrough splits mixed reasoning and content deltas and
assert.ok(onCompletePayload.responseBody.usage.total_tokens > 0);
});
test("createSSEStream passthrough output is consumable by SillyTavern-style reasoning parser", async () => {
const text = await readTransformed(
[
`data: ${JSON.stringify({
id: "chatcmpl_reasoning_st",
object: "chat.completion.chunk",
created: 1,
model: "gpt-4.1-mini",
choices: [
{
index: 0,
delta: {
reasoning_content: "First think",
content: "Then answer",
},
},
],
})}\n\n`,
`data: ${JSON.stringify({
id: "chatcmpl_reasoning_st",
object: "chat.completion.chunk",
created: 1,
model: "gpt-4.1-mini",
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
})}\n\n`,
],
{
mode: "passthrough",
sourceFormat: FORMATS.OPENAI,
provider: "openai-compatible",
model: "gpt-4.1-mini",
body: {
messages: [{ role: "user", content: "hello world" }],
},
}
);
const parsed = parseSillyTavernCustomOpenAIStream(text);
const reasoningEventIndex = parsed.events.findIndex((event) => event.reasoningDelta);
const contentEventIndex = parsed.events.findIndex((event) => event.contentDelta);
assert.equal(parsed.reasoning, "First think");
assert.equal(parsed.content, "Then answer");
assert.ok(reasoningEventIndex >= 0);
assert.ok(contentEventIndex > reasoningEventIndex);
});
test("createSSEStream passthrough writes complete SSE events per converted chunk", async () => {
const convertedChunks = [];
await readTransformed(
@@ -1688,8 +1808,9 @@ test("createSSETransformStreamWithLogger flushes a trailing Claude usage event w
assert.match(text, /Buffered tail/);
assert.match(text, /\[DONE\]/);
assert.equal(onCompletePayload.responseBody.choices[0].message.content, "Buffered tail");
assert.equal(onCompletePayload.responseBody.usage.prompt_tokens, 3);
assert.equal(onCompletePayload.responseBody.usage.completion_tokens, 5);
assert.equal(onCompletePayload.responseBody.usage.total_tokens, 5);
assert.equal(onCompletePayload.responseBody.usage.total_tokens, 8);
});
test("buildStreamSummaryFromEvents compacts Responses API deltas into a synthetic response", () => {

View File

@@ -0,0 +1,71 @@
// Unit tests for the pure helpers in scripts/docs/sync-wiki.mjs (the GitHub wiki sync).
import { test } from "node:test";
import assert from "node:assert/strict";
import {
normKey,
toWikiName,
toWikiContent,
syncHomeCounts,
parseWikiPage,
WIKI_BANNER,
NEW_PAGE_EXCLUDE,
} from "../../scripts/docs/sync-wiki.mjs";
test("normKey: case/separator-insensitive fuzzy key (matches docs basename to curated wiki name)", () => {
// The wiki names are hand-curated and not deterministic — normKey is what lets us
// match e.g. FLY_IO_DEPLOYMENT_GUIDE.md to the existing "Fly-io-Deployment-Guide" page.
assert.equal(normKey("FLY_IO_DEPLOYMENT_GUIDE.md"), normKey("Fly-io-Deployment-Guide"));
assert.equal(normKey("API_REFERENCE"), normKey("API-Reference"));
assert.equal(normKey("A2A-SERVER.md"), "a2aserver");
});
test("toWikiName: acronym-aware Title-Case-dashed name for NEW pages", () => {
assert.equal(toWikiName("SUPPLY_CHAIN.md"), "Supply-Chain");
assert.equal(toWikiName("API_REFERENCE"), "API-Reference");
assert.equal(toWikiName("QUOTA_SHARE"), "Quota-Share");
assert.equal(toWikiName("ACP"), "ACP");
});
test("toWikiContent: strips YAML frontmatter and prepends the language banner", () => {
const doc = '---\ntitle: "X"\nversion: 3.8.2\n---\n\n# Heading\n\nBody text.\n';
const out = toWikiContent(doc);
assert.ok(out.startsWith(WIKI_BANNER), "must start with the wiki language banner");
assert.ok(!out.includes("version: 3.8.2"), "frontmatter must be removed");
assert.ok(out.includes("# Heading"), "content body must be preserved");
assert.ok(out.endsWith("Body text.\n"), "trailing newline normalized");
});
test("toWikiContent: a document without frontmatter is kept intact (plus banner)", () => {
const doc = "# No Frontmatter\n\nHello.\n";
const out = toWikiContent(doc);
assert.equal(out, WIKI_BANNER + "# No Frontmatter\n\nHello.\n");
});
test("syncHomeCounts: rewrites the cover-page provider/strategy counts", () => {
const home = "Connect every AI tool to 177 providers.\n**177 AI Providers** · **14 Routing Strategies**\n";
const out = syncHomeCounts(home, { providers: 226, strategies: 15, mcpTools: null, locales: 42 });
assert.ok(out.includes("226 providers"));
assert.ok(out.includes("**226 AI Providers**"));
assert.ok(out.includes("**15 Routing Strategies**"));
assert.ok(!out.includes("177"));
});
test("syncHomeCounts: leaves text untouched when a count is null (best-effort MCP)", () => {
const home = "| **MCP Server** | 87 tools |\n";
const out = syncHomeCounts(home, { providers: null, strategies: null, mcpTools: null, locales: null });
assert.equal(out, home);
});
test("parseWikiPage: splits the locale prefix (U+2010) from the page name", () => {
assert.deepEqual(parseWikiPage("Architecture"), { locale: null, name: "Architecture" });
// U+2010 HYPHEN separator used by the localized mirrors (e.g. "pt-BRArchitecture").
assert.deepEqual(parseWikiPage("pt-BRArchitecture"), { locale: "pt-BR", name: "Architecture" });
assert.deepEqual(parseWikiPage("phiAPI-Reference"), { locale: "phi", name: "API-Reference" });
});
test("NEW_PAGE_EXCLUDE: internal reports/plans never become public wiki pages", () => {
assert.ok(NEW_PAGE_EXCLUDE.has("DOCUMENTATION_AUDIT_REPORT"));
assert.ok(NEW_PAGE_EXCLUDE.has("README"));
assert.ok(!NEW_PAGE_EXCLUDE.has("SUPPLY_CHAIN"));
});

View File

@@ -0,0 +1,171 @@
import test from "node:test";
import assert from "node:assert/strict";
import { Buffer } from "node:buffer";
import {
pcmToWav,
vertexGenerateSpeech,
vertexTranscribe,
vertexGenerateMusic,
vertexGenerateVideo,
} from "../../open-sse/executors/vertexMedia.ts";
// Service Account credential with a pre-set accessToken so resolveVertexAuth never
// performs a real OAuth token exchange (getAccessToken is skipped when accessToken is present).
function saCredentials(region = "us-central1") {
return {
apiKey: JSON.stringify({ project_id: "proj-test", client_email: "svc@x.iam", private_key: "x" }),
accessToken: "test-bearer-token",
providerSpecificData: { region },
};
}
function expressCredentials() {
return { apiKey: "express-key-abc", accessToken: null, providerSpecificData: {} };
}
interface FetchCall {
url: string;
init: any;
}
function installFetch(responders: Array<(call: FetchCall) => unknown>) {
const calls: FetchCall[] = [];
let i = 0;
(globalThis as any).fetch = async (url: string, init: any) => {
const call = { url: String(url), init };
calls.push(call);
const payload = responders[Math.min(i, responders.length - 1)](call);
i += 1;
return {
ok: true,
status: 200,
json: async () => payload,
text: async () => JSON.stringify(payload),
};
};
return calls;
}
test("pcmToWav writes a valid RIFF/WAVE header with correct sizes", () => {
const pcm = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
const wav = pcmToWav(pcm, 24000);
assert.equal(wav.subarray(0, 4).toString("ascii"), "RIFF");
assert.equal(wav.subarray(8, 12).toString("ascii"), "WAVE");
assert.equal(wav.readUInt32LE(4), 36 + pcm.length); // RIFF chunk size
assert.equal(wav.readUInt32LE(24), 24000); // sample rate
assert.equal(wav.readUInt32LE(40), pcm.length); // data chunk size
assert.equal(wav.length, 44 + pcm.length);
});
test("vertexGenerateSpeech posts generateContent with AUDIO modality and returns WAV", async () => {
const pcmB64 = Buffer.from([10, 20, 30, 40]).toString("base64");
const calls = installFetch([
() => ({
candidates: [
{ content: { parts: [{ inlineData: { data: pcmB64, mimeType: "audio/L16;codec=pcm;rate=24000" } }] } },
],
}),
]);
const { audio, contentType } = await vertexGenerateSpeech(saCredentials("europe-west4"), {
model: "gemini-2.5-flash-preview-tts",
input: "Hello world",
voice: "Puck",
});
assert.equal(contentType, "audio/wav");
assert.equal(audio.subarray(0, 4).toString("ascii"), "RIFF");
assert.equal(
calls[0].url,
"https://europe-west4-aiplatform.googleapis.com/v1/projects/proj-test/locations/europe-west4/publishers/google/models/gemini-2.5-flash-preview-tts:generateContent"
);
const body = JSON.parse(calls[0].init.body);
assert.deepEqual(body.generationConfig.responseModalities, ["AUDIO"]);
assert.equal(body.generationConfig.speechConfig.voiceConfig.prebuiltVoiceConfig.voiceName, "Puck");
assert.equal(calls[0].init.headers.Authorization, "Bearer test-bearer-token");
});
test("vertexGenerateSpeech defaults the voice to Kore", async () => {
const pcmB64 = Buffer.from([1, 2]).toString("base64");
const calls = installFetch([
() => ({ candidates: [{ content: { parts: [{ inlineData: { data: pcmB64, mimeType: "audio/L16;rate=16000" } }] } }] }),
]);
await vertexGenerateSpeech(saCredentials(), { model: "gemini-2.5-flash-preview-tts", input: "hi" });
const body = JSON.parse(calls[0].init.body);
assert.equal(body.generationConfig.speechConfig.voiceConfig.prebuiltVoiceConfig.voiceName, "Kore");
});
test("vertexTranscribe posts audio inlineData and returns the joined text", async () => {
const calls = installFetch([
() => ({ candidates: [{ content: { parts: [{ text: "the quick brown fox" }] } }] }),
]);
const text = await vertexTranscribe(saCredentials(), {
model: "gemini-2.5-flash",
audioBase64: "QUJD",
mimeType: "audio/mpeg",
prompt: "Transcribe please",
});
assert.equal(text, "the quick brown fox");
const body = JSON.parse(calls[0].init.body);
const parts = body.contents[0].parts;
assert.equal(parts[0].text, "Transcribe please");
assert.equal(parts[1].inlineData.mimeType, "audio/mpeg");
assert.equal(parts[1].inlineData.data, "QUJD");
assert.ok(calls[0].url.endsWith("/gemini-2.5-flash:generateContent"));
});
test("vertexGenerateMusic posts predict to lyria and returns base64 WAV", async () => {
const calls = installFetch([() => ({ predictions: [{ bytesBase64Encoded: "TY9MUA==" }] })]);
const { base64, format } = await vertexGenerateMusic(saCredentials(), {
model: "lyria-002",
prompt: "relaxing sax",
});
assert.equal(base64, "TY9MUA==");
assert.equal(format, "wav");
assert.ok(calls[0].url.endsWith("/lyria-002:predict"));
const body = JSON.parse(calls[0].init.body);
assert.equal(body.instances[0].prompt, "relaxing sax");
});
test("vertexGenerateVideo submits predictLongRunning then polls fetchPredictOperation", async () => {
const calls = installFetch([
() => ({ name: "projects/proj-test/.../operations/op-1" }), // submit
() => ({ name: "projects/proj-test/.../operations/op-1" }), // poll #1 (not done)
() => ({
done: true,
response: { videos: [{ bytesBase64Encoded: "TVA0VklERU8=" }] },
}), // poll #2 (done)
]);
const result = await vertexGenerateVideo(saCredentials(), {
model: "veo-3.0-fast-generate-001",
prompt: "a cat playing piano",
aspectRatio: "16:9",
durationSeconds: 4,
pollIntervalMs: 1,
maxWaitMs: 5000,
});
assert.equal(result.base64, "TVA0VklERU8=");
assert.equal(result.format, "mp4");
assert.ok(calls[0].url.endsWith("/veo-3.0-fast-generate-001:predictLongRunning"));
assert.ok(calls[1].url.endsWith("/veo-3.0-fast-generate-001:fetchPredictOperation"));
const submitBody = JSON.parse(calls[0].init.body);
assert.equal(submitBody.parameters.aspectRatio, "16:9");
assert.equal(submitBody.parameters.durationSeconds, 4);
assert.equal(submitBody.instances[0].prompt, "a cat playing piano");
});
test("Express API key uses the project-less publisher endpoint with ?key=", async () => {
const pcmB64 = Buffer.from([1]).toString("base64");
const calls = installFetch([
() => ({ candidates: [{ content: { parts: [{ inlineData: { data: pcmB64, mimeType: "audio/L16;rate=24000" } }] } }] }),
]);
await vertexGenerateSpeech(expressCredentials(), { model: "gemini-2.5-flash-preview-tts", input: "hi" });
assert.equal(
calls[0].url,
"https://aiplatform.googleapis.com/v1/publishers/google/models/gemini-2.5-flash-preview-tts:generateContent?key=express-key-abc"
);
assert.equal(calls[0].init.headers.Authorization, undefined);
});

View File

@@ -0,0 +1,31 @@
import test from "node:test";
import assert from "node:assert/strict";
// #3922 — the Vertex SA access token must carry BOTH cloud-platform (chat/image
// execution on aiplatform.googleapis.com) AND generative-language.retriever
// (live model discovery on generativelanguage.googleapis.com). Dropping the
// second scope makes discovery return 403 ACCESS_TOKEN_SCOPE_INSUFFICIENT and
// silently fall back to the static ~10-model registry list. This guards both.
const { VERTEX_OAUTH_SCOPES } = await import("../../open-sse/executors/vertex.ts");
test("Vertex OAuth scopes include cloud-platform (execution)", () => {
assert.ok(
VERTEX_OAUTH_SCOPES.includes("https://www.googleapis.com/auth/cloud-platform"),
"cloud-platform scope is required for Vertex AI execution"
);
});
test("Vertex OAuth scopes include generative-language.retriever (discovery)", () => {
assert.ok(
VERTEX_OAUTH_SCOPES.includes("https://www.googleapis.com/auth/generative-language.retriever"),
"generative-language.retriever scope is required for live model discovery"
);
});
test("Vertex OAuth scopes serialize as a space-delimited string for the JWT", () => {
const serialized = VERTEX_OAUTH_SCOPES.join(" ");
assert.equal(
serialized,
"https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/generative-language.retriever"
);
});

View File

@@ -0,0 +1,226 @@
import test from "node:test";
import assert from "node:assert/strict";
import http from "node:http";
// #3905 — Z.AI (glm) provider validation must use directHttpsRequest (native HTTPS,
// bypass the undici pool) for the same reason NVIDIA uses it in #3226: api.z.ai
// silently drops idle keep-alive sockets without TCP RST after 502 responses,
// causing undici to reuse dead sockets and hang for up to headersTimeout (600 s).
//
// Test design (same approach as nvidia-nim-validator.test.ts):
// - Import at FILE LOAD so proxyFetch captures the unpatched globalThis.fetch.
// - Redirect the validator at a local HTTP server via providerSpecificData.baseUrl.
// The safeOutboundFetch guard is "none" for validation calls, so 127.0.0.1 is
// reachable. directHttpsRequest accepts plain HTTP URLs in test environments.
// - Assert that (a) the correct auth header is used, (b) 401/403 → "Invalid API key",
// (c) any other status → valid (including 502 which is z.ai's queue timeout, not auth).
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
async function withMockServer(
handler: (req: http.IncomingMessage, res: http.ServerResponse) => void,
fn: (baseUrl: string) => Promise<void>
): Promise<void> {
const server = http.createServer(handler);
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
const port = typeof address === "object" && address ? address.port : 0;
// Omit the path — the validator appends ?beta=true itself via the baseUrl override.
const baseUrl = `http://127.0.0.1:${port}`;
try {
await fn(baseUrl);
} finally {
await new Promise<void>((resolve) => server.close(() => resolve()));
}
}
test("zai validator returns Invalid API key on 401", async () => {
await withMockServer(
(_req, res) => {
res.writeHead(401, { "content-type": "application/json" });
res.end(JSON.stringify({ error: { message: "token expired or incorrect", type: "401" } }));
},
async (baseUrl) => {
const result = await validateProviderApiKey({
provider: "zai",
apiKey: "bad-key",
providerSpecificData: { baseUrl },
});
assert.equal(result.valid, false);
assert.equal(result.error, "Invalid API key");
}
);
});
test("zai validator returns Invalid API key on 403", async () => {
await withMockServer(
(_req, res) => {
res.writeHead(403, { "content-type": "application/json" });
res.end(JSON.stringify({ error: { message: "forbidden" } }));
},
async (baseUrl) => {
const result = await validateProviderApiKey({
provider: "zai",
apiKey: "bad-key",
providerSpecificData: { baseUrl },
});
assert.equal(result.valid, false);
assert.equal(result.error, "Invalid API key");
}
);
});
test("zai validator accepts a successful 200 probe", async () => {
await withMockServer(
(_req, res) => {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ content: [{ text: "x" }] }));
},
async (baseUrl) => {
const result = await validateProviderApiKey({
provider: "zai",
apiKey: "valid-key",
providerSpecificData: { baseUrl },
});
assert.equal(result.valid, true);
assert.equal(result.error, null);
}
);
});
test("zai validator treats 502 as valid (z.ai queue timeout is not an auth error)", async () => {
await withMockServer(
(_req, res) => {
res.writeHead(502, { "content-type": "application/json" });
res.end(JSON.stringify({ error: { message: "job timed out after 120s" } }));
},
async (baseUrl) => {
const result = await validateProviderApiKey({
provider: "zai",
apiKey: "valid-key",
providerSpecificData: { baseUrl },
});
assert.equal(result.valid, true);
}
);
});
test("zai validator returns error on 404 (wrong endpoint)", async () => {
await withMockServer(
(_req, res) => {
res.writeHead(404, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "not found" }));
},
async (baseUrl) => {
const result = await validateProviderApiKey({
provider: "zai",
apiKey: "any-key",
providerSpecificData: { baseUrl },
});
assert.equal(result.valid, false);
assert.equal(result.error, "Provider validation endpoint not supported");
}
);
});
test("zai validator returns error on 5xx other than 502 (provider down)", async () => {
await withMockServer(
(_req, res) => {
res.writeHead(503, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "service unavailable" }));
},
async (baseUrl) => {
const result = await validateProviderApiKey({
provider: "zai",
apiKey: "any-key",
providerSpecificData: { baseUrl },
});
assert.equal(result.valid, false);
assert.equal(result.error, "Provider unavailable (503)");
}
);
});
test("zai validator sends x-api-key header (Anthropic wire format, not Bearer)", async () => {
let capturedHeaders: http.IncomingHttpHeaders = {};
let capturedMethod = "";
let capturedBody = "";
await withMockServer(
(req, res) => {
capturedHeaders = req.headers;
capturedMethod = req.method ?? "";
let body = "";
req.on("data", (chunk) => {
body += String(chunk);
});
req.on("end", () => {
capturedBody = body;
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({}));
});
},
async (baseUrl) => {
await validateProviderApiKey({
provider: "zai",
apiKey: "test-zai-key-123",
providerSpecificData: { baseUrl },
});
}
);
assert.equal(capturedMethod, "POST");
assert.equal(
capturedHeaders["x-api-key"],
"test-zai-key-123",
"must use x-api-key, not Authorization: Bearer"
);
assert.ok(
!capturedHeaders["authorization"],
`must not send Authorization header, got: ${capturedHeaders["authorization"]}`
);
assert.equal(capturedHeaders["anthropic-version"], "2023-06-01");
assert.equal(capturedHeaders["content-type"], "application/json");
const body = JSON.parse(capturedBody);
assert.equal(body.model, "glm-5.1");
assert.equal(body.max_tokens, 1);
assert.deepEqual(body.messages, [{ role: "user", content: "test" }]);
});
test("zai validator uses directHttpsRequest (does not proxy through undici pool)", async () => {
// The key invariant: directHttpsRequest calls safeOutboundFetch with
// bypassProxyPatch:true, which uses the original (pre-patch) native fetch.
// This means patching globalThis.fetch AFTER module load must NOT intercept it.
// If the validator were using validationWrite (undici), globalThis.fetch would
// still be the patched version at call time and we would see our mock called.
let mockCalled = false;
const originalFetch = globalThis.fetch;
globalThis.fetch = async (...args: Parameters<typeof fetch>) => {
mockCalled = true;
return originalFetch(...args);
};
try {
await withMockServer(
(_req, res) => {
res.writeHead(200, { "content-type": "application/json" });
res.end("{}");
},
async (baseUrl) => {
await validateProviderApiKey({
provider: "zai",
apiKey: "key",
providerSpecificData: { baseUrl },
});
}
);
} finally {
globalThis.fetch = originalFetch;
}
assert.equal(
mockCalled,
false,
"zai validator must use bypassProxyPatch path, not the patched globalThis.fetch"
);
});