Release v3.8.33 (#4515)

Release v3.8.33 — full CHANGELOG in the PR body. Blocking gates green (Build, Lint, Unit Tests 8/8, Package Artifact, Quality Gates, Quality Ratchet, Docs Sync, PR Test Policy, test-vitest). Admin-merged over a Node 26 future-compat timer flake (1/4) + an E2E UI flake (3/9) — both verified non-deterministic; full test:unit validated locally (16936 pass).
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-22 03:17:02 -03:00
committed by GitHub
parent bfaf459f3c
commit ee24eb52d4
241 changed files with 12035 additions and 1450 deletions

View File

@@ -1184,8 +1184,9 @@ test("chat pipeline allows unauthenticated requests through to provider resoluti
// handleChat does not enforce REQUIRE_API_KEY — that's the authz pipeline's job.
// Without provider credentials seeded, the request falls through to the "no credentials" path.
assert.equal(response.status, 400);
assert.match(json.error.message, /No credentials for provider/i);
// Upstream port decolua/9router#336: 400 → 404 so combo routing can fall through.
assert.equal(response.status, 404);
assert.match(json.error.message, /No active credentials for provider/i);
});
test("chat pipeline returns 400 when the model field is omitted", async () => {
@@ -1298,8 +1299,9 @@ test("chat pipeline returns current no-credentials contract when no provider con
);
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /No credentials for provider: openai/);
// Upstream port decolua/9router#336: 400 → 404 so combo routing can fall through.
assert.equal(response.status, 404);
assert.match(json.error.message, /No active credentials for provider: openai/);
});
test("chat pipeline surfaces upstream 500 responses as structured errors", async () => {

View File

@@ -364,8 +364,10 @@ test("unmapped custom model requests fail after combo resolution falls through",
);
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /No credentials for provider: tenant/);
// Upstream port decolua/9router#336: 400 → 404 so combo routing can fall through
// to the next target when a provider has zero usable credentials.
assert.equal(response.status, 404);
assert.match(json.error.message, /No active credentials for provider: tenant/);
});
test("strategy updates take effect for later requests on the same combo name", async () => {

View File

@@ -171,7 +171,8 @@ test("llama-cpp provider: alias matching works via model catalog prefix", async
assert.equal(json.choices[0].message.content, "42");
});
test("llama-cpp provider: returns 400 when no connection exists", async () => {
test("llama-cpp provider: returns 404 when no connection exists", async () => {
// Upstream port decolua/9router#336: 400 → 404 so combo routing can fall through.
const response = await handleChat(
buildRequest({
body: {
@@ -182,7 +183,7 @@ test("llama-cpp provider: returns 400 when no connection exists", async () => {
})
);
assert.equal(response.status, 400);
assert.equal(response.status, 404);
const json = (await response.json()) as any;
assert.match(json.error.message, /No credentials for provider/);
assert.match(json.error.message, /No active credentials for provider/);
});

View File

@@ -10,6 +10,7 @@ const harness = await createChatPipelineHarness("memory-pipeline");
// The harness sets DATA_DIR before importing DB modules, so these must resolve after that.
const { extractFactsFromText } = await import("../../src/lib/memory/extraction.ts");
const { retrieveMemories } = await import("../../src/lib/memory/retrieval.ts");
const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/settings.ts");
const { injectMemory, formatMemoryContext } = await import("../../src/lib/memory/injection.ts");
const {
BaseExecutor,
@@ -45,12 +46,14 @@ function dropFts5Artifacts() {
test.beforeEach(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = 0;
await resetStorage();
invalidateMemorySettingsCache();
dropFts5Artifacts();
});
test.afterEach(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = harness.originalRetryDelayMs;
await resetStorage();
invalidateMemorySettingsCache();
});
test.after(async () => {

View File

@@ -0,0 +1,54 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { AntigravityExecutor } from "../../open-sse/executors/antigravity.ts";
// Regression for #1926: Claude/OpenAI "thinking" fields leaked into the Antigravity
// (Google Cloud Code) request envelope, which Google rejects at the top level with
// `400 Bad input: Error: oneOf at '/' not met` (and, for named fields, `Unknown name
// "thinking"`). This broke every reasoning/thinking model served via Antigravity
// (e.g. `claude-opus-4-x-thinking`). The envelope spreads `...passthroughFields`
// (every top-level body field except a known few), so a top-level thinking field set
// by the unified thinking adapter leaked straight into the envelope Google validates.
//
// The #1944 fix only dropped `output_config`/`output_format`; the thinking family
// (`thinking`, `reasoning_effort`, `reasoning`, `enable_thinking`, `thinking_budget`)
// still passed through. This test guards that the whole family is stripped.
const THINKING_FIELDS = [
"thinking",
"reasoning_effort",
"reasoning",
"enable_thinking",
"thinking_budget",
] as const;
test("transformRequest drops Claude/OpenAI thinking fields from the Antigravity envelope (#1926)", async () => {
const executor = new AntigravityExecutor();
const body = {
thinking: { type: "enabled", budget_tokens: 4096 },
reasoning_effort: "high",
reasoning: { effort: "high" },
enable_thinking: true,
thinking_budget: 4096,
request: {
contents: [{ role: "user", parts: [{ text: "hi" }] }],
generationConfig: {},
},
};
const result = await executor.transformRequest("antigravity/claude-opus-4-8-thinking", body, true, {
projectId: "project-1",
});
if (result instanceof Response) throw new Error("Unexpected Response from transformRequest");
const envelope = result as Record<string, unknown>;
for (const field of THINKING_FIELDS) {
assert.equal(
envelope[field],
undefined,
`top-level "${field}" must not reach the Google Cloud Code envelope`
);
}
});

View File

@@ -138,26 +138,152 @@ test("getApiKeyUsageLimitStatus aligns weekly USD spend with provider resetAt wh
assert.equal(status.weeklySpentUsd, 5);
assert.equal(status.dailyLimitUsd, 10);
assert.equal(status.weeklyLimitUsd, 20);
assert.equal(status.dailyResetAtIso, "2026-06-20T03:00:00.000Z");
assert.equal(status.weeklyWindowStartIso, "2026-06-18T20:00:00.000Z");
assert.equal(status.weeklyResetAtIso, weeklyResetAt);
assert.equal(status.dailyExceeded, false);
assert.equal(status.weeklyExceeded, false);
});
test("buildApiKeyUsageLimitText returns only the quota and spent USD lines", async () => {
const text = usageLimits.buildApiKeyUsageLimitText({
enabled: true,
dailyLimitUsd: 10,
weeklyLimitUsd: 50,
dailySpentUsd: 2,
weeklySpentUsd: 5.25,
dailyWindowStartIso: "2026-06-19T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-12T20:00:00.000Z",
weeklyResetAtIso: "2026-06-19T20:00:00.000Z",
dailyExceeded: false,
weeklyExceeded: false,
test("getApiKeyUsageLimitStatus cuts weekly USD spend at observed provider quota reset", async () => {
await localDb.updatePricing({
claude: {
"claude-opus-4-8": {
input: 1,
cached: 1,
output: 1,
reasoning: 1,
cache_creation: 1,
},
},
});
const created = await apiKeysDb.createApiKey("Reset Cut Key", "machine-limit-reset");
await apiKeysDb.updateApiKeyPermissions(created.id, {
usageLimitEnabled: true,
dailyUsageLimitUsd: 10,
weeklyUsageLimitUsd: 20,
});
await usageHistory.saveRequestUsage({
provider: "claude",
model: "claude-opus-4-8",
apiKeyId: created.id,
apiKeyName: "Reset Cut Key",
tokens: { input: 7_000_000, output: 0 },
success: true,
timestamp: "2026-06-19T23:30:00.000Z",
});
await usageHistory.saveRequestUsage({
provider: "claude",
model: "claude-opus-4-8",
apiKeyId: created.id,
apiKeyName: "Reset Cut Key",
tokens: { input: 2_000_000, output: 0 },
success: true,
timestamp: "2026-06-20T02:00:00.000Z",
});
const db = core.getDbInstance();
const insertSnapshot = db.prepare(`
INSERT INTO quota_snapshots (
provider,
connection_id,
window_key,
remaining_percentage,
is_exhausted,
next_reset_at,
window_duration_ms,
raw_data,
created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
insertSnapshot.run(
"claude",
"conn-claude",
"weekly (7d)",
72,
0,
"2026-06-25T23:00:00.000Z",
null,
null,
"2026-06-19T23:55:00.000Z"
);
insertSnapshot.run(
"claude",
"conn-claude",
"weekly (7d)",
100,
0,
"2026-06-25T23:00:00.000Z",
null,
null,
"2026-06-20T01:42:52.590Z"
);
insertSnapshot.run(
"claude",
"conn-claude",
"weekly (7d)",
99,
0,
"2026-06-25T23:00:00.000Z",
null,
null,
"2026-06-20T02:10:00.000Z"
);
const metadata = await apiKeysDb.getApiKeyMetadata(created.key);
assert.ok(metadata);
const weeklyResetAt = "2026-06-25T23:00:00.000Z";
const status = await usageLimits.getApiKeyUsageLimitStatus(
{ ...metadata, allowedConnections: ["conn-claude"] },
{
now: () => Date.parse("2026-06-20T14:30:00.000Z"),
getProviderConnectionById: async () => ({
id: "conn-claude",
provider: "claude",
isActive: true,
}),
getProviderConnections: async () => [],
getProviderLimitsCache: () => ({
plan: "Claude Max",
quotas: {
"weekly (7d)": {
used: 5,
total: 100,
resetAt: weeklyResetAt,
},
},
message: null,
fetchedAt: "2026-06-20T14:30:00.000Z",
}),
getAllProviderLimitsCache: () => ({}),
}
);
assert.equal(status.weeklyWindowStartIso, "2026-06-20T01:42:52.590Z");
assert.equal(status.weeklySpentUsd, 2);
});
test("buildApiKeyUsageLimitText returns API-key quota spend percentage and reset lines", async () => {
const text = usageLimits.buildApiKeyUsageLimitText(
{
enabled: true,
dailyLimitUsd: 10,
weeklyLimitUsd: 50,
dailySpentUsd: 2,
weeklySpentUsd: 5.25,
dailyWindowStartIso: "2026-06-19T03:00:00.000Z",
dailyResetAtIso: "2026-06-20T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-12T20:00:00.000Z",
weeklyResetAtIso: "2026-06-25T20:00:00.000Z",
dailyExceeded: false,
weeklyExceeded: false,
},
Date.parse("2026-06-19T20:00:00.000Z")
);
assert.equal(
text,
[
@@ -165,15 +291,50 @@ test("buildApiKeyUsageLimitText returns only the quota and spent USD lines", asy
"$10.00",
"Gasto diario",
"$2.00",
"Uso diario",
"20%",
"Resets in 7h",
"",
"Cota semanal",
"$50.00",
"Gasto semanal",
"$5.25",
"Uso semanal",
"11%",
"Resets in 6d",
].join("\n")
);
});
test("buildApiKeyUsageLimitRejection includes over-quota percentage and reset hint", async () => {
const response = usageLimits.buildApiKeyUsageLimitRejection(
new Request("http://localhost/v1/messages", {
headers: { "anthropic-version": "2023-06-01" },
}),
{
enabled: true,
dailyLimitUsd: 10,
weeklyLimitUsd: 1,
dailySpentUsd: 0.25,
weeklySpentUsd: 1.09,
dailyWindowStartIso: "2026-06-19T03:00:00.000Z",
dailyResetAtIso: "2026-06-20T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-12T20:00:00.000Z",
weeklyResetAtIso: "2026-06-25T20:00:00.000Z",
dailyExceeded: false,
weeklyExceeded: true,
},
Date.parse("2026-06-19T20:00:00.000Z")
);
assert.equal(response.status, 400);
const body = (await response.json()) as { error: { message: string } };
assert.equal(
body.error.message,
"This API key reached its weekly USD usage quota ($1.09 of $1.00, 109%). Resets in 6d. Choose another allowed model after reset."
);
});
test("buildApiKeyUsageLimitRejection uses 400 so Claude Code does not trigger login", () => {
const response = usageLimits.buildApiKeyUsageLimitRejection(
new Request("http://localhost/v1/messages", {
@@ -186,6 +347,7 @@ test("buildApiKeyUsageLimitRejection uses 400 so Claude Code does not trigger lo
dailySpentUsd: 12,
weeklySpentUsd: 20,
dailyWindowStartIso: "2026-06-19T03:00:00.000Z",
dailyResetAtIso: "2026-06-20T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-12T20:00:00.000Z",
weeklyResetAtIso: "2026-06-19T20:00:00.000Z",
dailyExceeded: true,

View File

@@ -0,0 +1,51 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
maskKey,
toggleKeyVisibility,
} from "../../src/app/(dashboard)/dashboard/api-manager/apiManagerPageUtils.js";
describe("maskKey", () => {
it("returns empty string when key is missing or empty", () => {
assert.equal(maskKey(""), "");
assert.equal(maskKey(null), "");
assert.equal(maskKey(undefined), "");
});
it("returns the key untouched when it fits the visible budget (<=8 chars)", () => {
assert.equal(maskKey("sk"), "sk");
assert.equal(maskKey("sk-12345"), "sk-12345");
});
it("keeps the first 8 chars and appends an ellipsis when the key is longer", () => {
const full = "sk-or-1234567890abcdef";
const masked = maskKey(full);
assert.equal(masked.startsWith("sk-or-12"), true);
assert.equal(masked.endsWith("..."), true);
// Must not leak the tail
assert.equal(masked.includes("90abcdef"), false);
});
});
describe("toggleKeyVisibility", () => {
it("adds an id when it is not present", () => {
const next = toggleKeyVisibility(new Set<string>(), "k1");
assert.equal(next.has("k1"), true);
assert.equal(next.size, 1);
});
it("removes an id when it is already present", () => {
const next = toggleKeyVisibility(new Set<string>(["k1", "k2"]), "k1");
assert.equal(next.has("k1"), false);
assert.equal(next.has("k2"), true);
assert.equal(next.size, 1);
});
it("returns a NEW Set (does not mutate the input — React state safety)", () => {
const input = new Set<string>(["k1"]);
const output = toggleKeyVisibility(input, "k2");
assert.notEqual(output, input);
assert.equal(input.has("k2"), false);
assert.equal(output.has("k2"), true);
});
});

View File

@@ -158,11 +158,7 @@ describe("settings/compression route — engines + activeComboId", () => {
assert.equal(getRes.status, 200);
const body = await getRes.json();
assert.equal(
body.engines?.rtk?.enabled,
true,
"engines.rtk.enabled should be true after PUT"
);
assert.equal(body.engines?.rtk?.enabled, true, "engines.rtk.enabled should be true after PUT");
assert.equal(
body.engines?.rtk?.level,
"standard",
@@ -199,15 +195,29 @@ describe("settings/compression route — engines + activeComboId", () => {
it("PUT with invalid engines shape is rejected by schema validation (400)", async () => {
// engines values must have an `enabled` boolean — passing a string should fail the schema.
const putRes = await route.PUT(
makeRequest("PUT", { engines: { rtk: { enabled: "yes" } } })
);
const putRes = await route.PUT(makeRequest("PUT", { engines: { rtk: { enabled: "yes" } } }));
assert.equal(putRes.status, 400);
const body = await putRes.json();
// Validation failures use { error: { message, details } } via validateBody helper.
assert.ok(body.error !== null && typeof body.error === "object", "error should be an object");
const errorMessage: string =
typeof body.error === "string" ? body.error : (body.error?.message ?? JSON.stringify(body.error));
typeof body.error === "string"
? body.error
: (body.error?.message ?? JSON.stringify(body.error));
assert.ok(!errorMessage.includes("at /"), "error must not contain a stack trace");
});
it("PUT accepts enginesExplicit (round-tripped from GET response)", async () => {
// Regression: the GET handler injects `enginesExplicit` (compression.ts:632) so the
// hub/panel can round-trip the full settings object. The previous .strict() PUT schema
// rejected it with 400 ("Unrecognized key: enginesExplicit"), causing every toggle on
// the Compression Hub / Panel to revert. Allow it through.
const putRes = await route.PUT(makeRequest("PUT", { enabled: true, enginesExplicit: true }));
assert.equal(putRes.status, 200);
core.resetDbInstance();
const putRes2 = await route.PUT(makeRequest("PUT", { enabled: false, enginesExplicit: false }));
assert.equal(putRes2.status, 200);
});
});

View File

@@ -0,0 +1,81 @@
/**
* #4558 — Auto-combo must respect model visibility (isHidden).
*
* When an operator hides a model with the EYE/visibility toggle
* (`mergeModelCompatOverride(provider, model, { isHidden: true })`, written to
* the `modelCompatOverrides` key_value namespace), that model must be excluded
* from the AUTO-combo candidate pool. Both auto paths consume the same seam:
* - `open-sse/services/combo.ts::buildAutoCandidates` (combo.ts:322,520-521)
* - `open-sse/services/autoCombo/virtualFactory.ts` (virtualFactory.ts:239,256-257)
* via the new bulk `getHiddenModelsByProvider()` map (single query, not N+1).
*
* This guards that seam: a hidden model is present in the map's per-provider set
* while a visible sibling is not, and that toggling visibility back off
* (`isHidden: null`) removes it from the map again. Without the fix the map is
* empty and the auto pool would keep serving hidden models.
*/
import test, { before, after } from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import fs from "node:fs";
// Hermetic DB: this test writes overrides into the `modelCompatOverrides`
// key_value namespace. Without an isolated DATA_DIR it would leak that state
// into the shared dev/CI database. Point DATA_DIR at a throwaway dir before any
// import that opens the SQLite handle (CLAUDE.md "Database Handles in Tests").
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-hidden-4558-"));
process.env.DATA_DIR = tmpDir;
const { mergeModelCompatOverride, getHiddenModelsByProvider, getModelIsHidden } = await import(
"../../src/lib/localDb.ts"
);
const { resetDbInstance } = await import("../../src/lib/db/core.ts");
before(() => {
resetDbInstance();
});
after(() => {
resetDbInstance();
fs.rmSync(tmpDir, { recursive: true, force: true });
});
const PROVIDER = "openai";
const HIDDEN_MODEL = "gpt-hidden-preview";
const VISIBLE_MODEL = "gpt-visible-4o";
test("getHiddenModelsByProvider: empty before any model is hidden", () => {
const map = getHiddenModelsByProvider();
assert.equal(map.get(PROVIDER)?.has(HIDDEN_MODEL) ?? false, false);
});
test("a hidden model lands in the provider's hidden set; a visible sibling does not", () => {
// Hide one model, leave a sibling visible (overridden for an unrelated reason).
mergeModelCompatOverride(PROVIDER, HIDDEN_MODEL, { isHidden: true });
mergeModelCompatOverride(PROVIDER, VISIBLE_MODEL, { normalizeToolCallId: true });
// Sanity: the per-model read agrees.
assert.equal(getModelIsHidden(PROVIDER, HIDDEN_MODEL), true);
assert.equal(getModelIsHidden(PROVIDER, VISIBLE_MODEL), false);
const map = getHiddenModelsByProvider();
const hiddenForProvider = map.get(PROVIDER);
assert.ok(hiddenForProvider, "expected an entry for the provider");
assert.equal(hiddenForProvider.has(HIDDEN_MODEL), true, "hidden model must be in the set");
assert.equal(
hiddenForProvider.has(VISIBLE_MODEL),
false,
"visible model must NOT be in the hidden set"
);
});
test("un-hiding a model (isHidden: null) removes it from the map", () => {
mergeModelCompatOverride(PROVIDER, HIDDEN_MODEL, { isHidden: null });
const map = getHiddenModelsByProvider();
assert.equal(
map.get(PROVIDER)?.has(HIDDEN_MODEL) ?? false,
false,
"un-hidden model must drop out of the hidden set"
);
});

View File

@@ -0,0 +1,76 @@
import { describe, it } from "node:test";
import assert from "node:assert";
describe("bazaarlink provider entry in APIKEY_PROVIDERS", () => {
it("exists with required fields", async () => {
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
const p = (APIKEY_PROVIDERS as Record<string, Record<string, unknown>>)["bazaarlink"];
assert.ok(p, "bazaarlink must exist in APIKEY_PROVIDERS");
assert.equal(p.id, "bazaarlink");
assert.equal(p.alias, "bzl");
assert.equal(p.name, "BazaarLink");
assert.equal(p.icon, "storefront");
assert.equal(p.color, "#6366F1");
assert.equal(p.textIcon, "BZ");
assert.equal(p.website, "https://bazaarlink.ai");
});
it("advertises the free tier and includes authHint", async () => {
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
const p = (
APIKEY_PROVIDERS as Record<
string,
{ hasFree?: boolean; authHint?: string; freeNote?: string; apiHint?: string }
>
)["bazaarlink"];
assert.equal(p.hasFree, true, "bazaarlink should advertise a free tier");
assert.ok(p.authHint, "bazaarlink must have an authHint field");
assert.ok(p.authHint.includes("sk-bl-"), "authHint should mention the sk-bl- key prefix");
assert.ok(p.freeNote, "bazaarlink must have a freeNote describing the free tier");
assert.ok(p.apiHint, "bazaarlink must have an apiHint");
assert.ok(p.apiHint.includes("bazaarlink.ai"), "apiHint should reference bazaarlink.ai");
});
it("is registered in the execution registry", async () => {
const { getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts");
const entry = getRegistryEntry("bazaarlink");
assert.ok(entry, "bazaarlink must have a registry entry");
assert.equal(entry.format, "openai");
assert.equal(entry.authType, "apikey");
assert.ok(entry.baseUrl, "registry entry must have a baseUrl");
assert.ok(entry.modelsUrl, "registry entry must have a modelsUrl");
assert.ok(
entry.models && entry.models.length > 0,
"registry entry must list at least one model"
);
assert.ok(
entry.models.some((m: { id: string }) => m.id === "auto:free"),
"registry must include auto:free model"
);
assert.ok(
entry.models.some((m: { id: string }) => m.id === "mimo-v2.5-pro"),
"registry must include mimo-v2.5-pro"
);
});
it("is resolvable through the static catalog for managed connections", async () => {
const { resolveStaticProviderCatalogEntry } =
await import("../../src/lib/providers/catalog.ts");
const entry = resolveStaticProviderCatalogEntry("bazaarlink");
assert.ok(entry, "bazaarlink must resolve through the static provider catalog");
assert.ok(entry.id, "bazaarlink", "resolved entry id must match");
assert.equal(entry.category, "apikey", "bazaarlink must be in the apikey category");
});
it("passes isManagedProviderConnectionId check so connections can be created", async () => {
const { isManagedProviderConnectionId } = await import("../../src/lib/providers/catalog.ts");
const result = isManagedProviderConnectionId("bazaarlink");
assert.equal(result, true, "bazaarlink must pass isManagedProviderConnectionId");
});
it("supports bulk API key add (not excluded)", async () => {
const { supportsBulkApiKey } = await import("../../src/shared/constants/providers.ts");
const result = supportsBulkApiKey("bazaarlink");
assert.equal(result, true, "bazaarlink must support bulk API key add");
});
});

View File

@@ -149,3 +149,33 @@ test("Every Codex registry model resolves a non-zero pricing row (alias: cx)", a
);
}
});
test("Every Qwen registry model resolves a non-zero pricing row (alias: qw)", async () => {
const { getPricingForModel } = await import("../../src/shared/constants/pricing.ts");
const models = getModelsByProviderId("qwen");
assert.ok(models.length > 0, "qwen must expose models");
for (const model of models) {
// Qwen pricing lives under the "qw" alias (its DEFAULT_PRICING key).
const pricing = getPricingForModel("qw", model.id) as {
input?: number;
output?: number;
} | null;
assert.ok(pricing, `qw pricing must include qwen model "${model.id}"`);
assert.equal(
typeof pricing?.input === "number" && typeof pricing?.output === "number",
true,
`qw pricing for "${model.id}" must have numeric input/output`
);
}
// Regression guard: the "coder-model" id (Qwen3.5/3.6 Coder Model, ported from
// upstream 9router PR #156) must be priced like the other Qwen coder tier.
const coderModel = getPricingForModel("qw", "coder-model") as {
input: number;
output: number;
} | null;
assert.ok(coderModel, "qw pricing must include coder-model");
assert.equal(typeof coderModel?.input, "number");
assert.equal(typeof coderModel?.output, "number");
});

View File

@@ -247,6 +247,19 @@ test("checkPipelineGates reapplies runtime breaker settings to existing breakers
});
test("handleNoCredentials reports missing provider credentials and exhausted accounts", async () => {
// Ported from upstream decolua/9router#336 (Ibrahim Ryan): when a provider has
// zero usable connections (all disabled, or none configured at all), the
// historical 400 BAD_REQUEST classified the failure as non-fallbackable, so a
// combo like `antigravity/opus → github/opus` died on the first leg with a
// hard 400 even though the next combo target was perfectly healthy.
//
// The combo target loop (open-sse/services/combo.ts) deliberately breaks on
// 400 to prevent infinite fallback loops with body-specific 4xx errors
// (#4279/PR#4316). 404 NOT_FOUND, by contrast, flows through checkFallbackError
// as `shouldFallback: true` (generic-error catch-all path,
// open-sse/services/accountFallback.ts:1593-1599) so the next combo target is
// tried. We surface "no active credentials" as 404 so combo can skip past a
// disabled-credentials provider instead of failing the whole request.
const missing = handleNoCredentials(null, null, "openai", "gpt-4o-mini", null, null);
const exhausted = handleNoCredentials(
null,
@@ -260,8 +273,8 @@ test("handleNoCredentials reports missing provider credentials and exhausted acc
const missingJson = (await missing.json()) as any;
const exhaustedJson = (await exhausted.json()) as any;
assert.equal(missing.status, 400);
assert.match(missingJson.error.message, /No credentials for provider: openai/);
assert.equal(missing.status, 404);
assert.match(missingJson.error.message, /No active credentials for provider: openai/);
assert.equal(exhausted.status, 500);
assert.match(exhaustedJson.error.message, /Primary account failed/);
});

View File

@@ -300,7 +300,11 @@ test("handleChat keeps the combo error when the global fallback throws", async (
assert.match(json.error.message, /primary combo failed/i);
});
test("handleChat returns 400 when no provider credentials exist", async () => {
test("handleChat returns 404 when no provider credentials exist", async () => {
// Upstream port decolua/9router#336 (Ibrahim Ryan): the no-credentials branch
// of handleNoCredentials now surfaces 404 NOT_FOUND so combo routing can fall
// through to the next target instead of being killed by the combo 400-hard-stop
// guard (open-sse/services/combo.ts, PR #4316 / issue #4279).
const response = await handleChat(
buildRequest({
body: {
@@ -312,8 +316,8 @@ test("handleChat returns 400 when no provider credentials exist", async () => {
);
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /No credentials for provider: openai/);
assert.equal(response.status, 404);
assert.match(json.error.message, /No active credentials for provider: openai/);
});
test("handleChat returns 503 for cooled-down connections and 503 for open circuit breakers", async () => {

View File

@@ -0,0 +1,69 @@
// tests/unit/chatcore-background-redirect.test.ts
// Characterization of resolveBackgroundTaskRedirect — the Background Task Redirection (T41)
// decision extracted from handleChatCore (chatCore god-file decomposition, #3501). Decides whether
// a request should be downgraded to a cheaper model: only when the feature is enabled, the request
// looks like a background task, AND the model has a degradation mapping. The handler keeps the
// effects (log, model/body mutation, audit). Uses the real backgroundTaskDetector config via its
// setter so the decision is exercised end-to-end.
import { test, afterEach } from "node:test";
import assert from "node:assert/strict";
import { resolveBackgroundTaskRedirect } from "../../open-sse/handlers/chatCore/backgroundRedirect.ts";
import { setBackgroundDegradationConfig } from "../../open-sse/services/backgroundTaskDetector.ts";
afterEach(() => {
setBackgroundDegradationConfig({ enabled: false, degradationMap: {} });
});
test("disabled config → no detection, no redirect (even for a clear background task)", () => {
setBackgroundDegradationConfig({ enabled: false, degradationMap: { "gpt-5": "gpt-5-mini" } });
assert.deepEqual(
resolveBackgroundTaskRedirect({ body: { max_tokens: 10 }, headers: null, model: "gpt-5" }),
{ backgroundReason: null, redirect: null }
);
});
test("enabled + low-max-tokens background + mapped model → detection + redirect", () => {
setBackgroundDegradationConfig({ enabled: true, degradationMap: { "gpt-5": "gpt-5-mini" } });
const r = resolveBackgroundTaskRedirect({
body: { max_tokens: 10 },
headers: null,
model: "gpt-5",
});
assert.deepEqual(r, {
backgroundReason: "low_max_tokens",
redirect: { degradedModel: "gpt-5-mini", reason: "low_max_tokens" },
});
});
test("enabled + x-task-type:background header → reason header_background", () => {
setBackgroundDegradationConfig({ enabled: true, degradationMap: { "gpt-5": "gpt-5-mini" } });
const r = resolveBackgroundTaskRedirect({
body: { messages: [{ role: "user", content: "hi" }] },
headers: { "x-task-type": "background" },
model: "gpt-5",
});
assert.deepEqual(r, {
backgroundReason: "header_background",
redirect: { degradedModel: "gpt-5-mini", reason: "header_background" },
});
});
test("enabled but the request is not a background task → no detection, no redirect", () => {
setBackgroundDegradationConfig({ enabled: true, degradationMap: { "gpt-5": "gpt-5-mini" } });
assert.deepEqual(
resolveBackgroundTaskRedirect({
body: { max_tokens: 4096, messages: [{ role: "user", content: "write an essay" }] },
headers: null,
model: "gpt-5",
}),
{ backgroundReason: null, redirect: null }
);
});
test("enabled + background but the model has no degradation mapping → detection but no redirect", () => {
setBackgroundDegradationConfig({ enabled: true, degradationMap: { "gpt-5": "gpt-5-mini" } });
assert.deepEqual(
resolveBackgroundTaskRedirect({ body: { max_tokens: 10 }, headers: null, model: "claude-opus-4" }),
{ backgroundReason: "low_max_tokens", redirect: null }
);
});

View File

@@ -0,0 +1,107 @@
// tests/unit/chatcore-claude-effort-variant.test.ts
// Characterization of applyClaudeEffortVariant — the Claude effort-suffix normalization extracted
// from handleChatCore (chatCore god-file decomposition, #3501). The VS Code "Effort" slider
// advertises claude-...-{low,medium,high,xhigh,max}; Anthropic has no such model, so the suffix is
// stripped to the base id and surfaced as reasoning_effort. Locks: the provider gate (claude /
// claude-code-compatible only), the in-place body mutation (model + reasoning_effort), the
// sourceFormat==="claude" skip, the explicit-effort-wins rule, and the returned effectiveModel/log.
import { test } from "node:test";
import assert from "node:assert/strict";
import { applyClaudeEffortVariant } from "../../open-sse/handlers/chatCore/claudeEffortVariant.ts";
import { FORMATS } from "../../open-sse/translator/formats.ts";
test("claude provider + effort suffix → strips to base, mutates body model + reasoning_effort, returns log", () => {
const body: Record<string, unknown> = { model: "claude-sonnet-4-high", messages: [] };
const r = applyClaudeEffortVariant({
provider: "claude",
effectiveModel: "claude-sonnet-4-high",
body,
sourceFormat: FORMATS.OPENAI,
});
assert.equal(r.effectiveModel, "claude-sonnet-4");
assert.equal(body.model, "claude-sonnet-4");
assert.equal(body.reasoning_effort, "high");
assert.match(String(r.log), /stripped "-high" → claude-sonnet-4 \(reasoning_effort=high\)/);
});
test("claude-code-compatible provider triggers the same stripping", () => {
const body: Record<string, unknown> = { model: "claude-opus-4-xhigh", messages: [] };
const r = applyClaudeEffortVariant({
provider: "anthropic-compatible-cc-default",
effectiveModel: "claude-opus-4-xhigh",
body,
sourceFormat: FORMATS.OPENAI,
});
assert.equal(r.effectiveModel, "claude-opus-4");
assert.equal(body.model, "claude-opus-4");
assert.equal(body.reasoning_effort, "xhigh");
});
test("sourceFormat 'claude' strips the model but does NOT inject reasoning_effort", () => {
const body: Record<string, unknown> = { model: "claude-sonnet-4-medium", messages: [] };
const r = applyClaudeEffortVariant({
provider: "claude",
effectiveModel: "claude-sonnet-4-medium",
body,
sourceFormat: FORMATS.CLAUDE,
});
assert.equal(r.effectiveModel, "claude-sonnet-4");
assert.equal(body.model, "claude-sonnet-4");
assert.equal(body.reasoning_effort, undefined);
});
test("an explicit client reasoning_effort wins (not overwritten)", () => {
const body: Record<string, unknown> = { model: "claude-sonnet-4-low", reasoning_effort: "high", messages: [] };
const r = applyClaudeEffortVariant({
provider: "claude",
effectiveModel: "claude-sonnet-4-low",
body,
sourceFormat: FORMATS.OPENAI,
});
assert.equal(r.effectiveModel, "claude-sonnet-4");
assert.equal(body.reasoning_effort, "high"); // unchanged
});
test("explicit effort nested under reasoning.effort also wins", () => {
const body: Record<string, unknown> = {
model: "claude-sonnet-4-low",
reasoning: { effort: "medium" },
messages: [],
};
const r = applyClaudeEffortVariant({
provider: "claude",
effectiveModel: "claude-sonnet-4-low",
body,
sourceFormat: FORMATS.OPENAI,
});
assert.equal(body.reasoning_effort, undefined); // explicit reasoning.effort present → no injection
assert.equal(r.effectiveModel, "claude-sonnet-4");
});
test("no effort suffix → no change, no log", () => {
const body: Record<string, unknown> = { model: "claude-sonnet-4", messages: [] };
const r = applyClaudeEffortVariant({
provider: "claude",
effectiveModel: "claude-sonnet-4",
body,
sourceFormat: FORMATS.OPENAI,
});
assert.equal(r.effectiveModel, "claude-sonnet-4");
assert.equal(body.model, "claude-sonnet-4");
assert.equal(body.reasoning_effort, undefined);
assert.equal(r.log, null);
});
test("non-claude provider is a no-op even with an effort suffix", () => {
const body: Record<string, unknown> = { model: "gpt-5-high", messages: [] };
const r = applyClaudeEffortVariant({
provider: "openai",
effectiveModel: "gpt-5-high",
body,
sourceFormat: FORMATS.OPENAI,
});
assert.equal(r.effectiveModel, "gpt-5-high");
assert.equal(body.model, "gpt-5-high");
assert.equal(body.reasoning_effort, undefined);
assert.equal(r.log, null);
});

View File

@@ -0,0 +1,106 @@
// tests/unit/chatcore-codex-quota.test.ts
// Characterization of buildCodexQuotaPersistence — the pure core of handleChatCore's
// persistCodexQuotaState, extracted during the chatCore god-file decomposition (#3501). Locks the
// shape of the persisted providerSpecificData: the codexQuotaState snapshot, the existing-data
// passthrough, and the 429 dual-window exhaustion fields (codexScopeRateLimitedUntil /
// codexExhaustedWindow) plus the debug-log message. The handler keeps the DB write, the
// preflight-cache invalidation, and the log emission; this function only builds the data.
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildCodexQuotaPersistence } from "../../open-sse/handlers/chatCore/codexQuota.ts";
import { getCodexModelScope } from "../../open-sse/executors/codex.ts";
const MODEL = "gpt-5-codex";
const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
function quotaHeaders(over: Record<string, string> = {}) {
return {
"x-codex-5h-usage": "50",
"x-codex-5h-limit": "100",
"x-codex-5h-reset-at": "2999-01-01T00:00:00.000Z",
"x-codex-7d-usage": "10",
"x-codex-7d-limit": "100",
"x-codex-7d-reset-at": "2999-01-08T00:00:00.000Z",
...over,
};
}
test("returns null when the response carries no codex quota headers", () => {
assert.equal(
buildCodexQuotaPersistence({ headers: {}, existingProviderData: {}, modelForScope: MODEL, status: 200 }),
null
);
assert.equal(
buildCodexQuotaPersistence({ headers: { "content-type": "application/json" }, existingProviderData: {}, modelForScope: MODEL, status: 200 }),
null
);
});
test("builds codexQuotaState (parsed numbers + scope + updatedAt) and preserves existing provider data", () => {
const built = buildCodexQuotaPersistence({
headers: quotaHeaders(),
existingProviderData: { keepMe: "yes", apiKeyHealth: { primary: {} } },
modelForScope: MODEL,
status: 200,
});
assert.ok(built);
const qs = built.nextProviderData.codexQuotaState as Record<string, unknown>;
assert.equal(qs.usage5h, 50);
assert.equal(qs.limit5h, 100);
assert.equal(qs.usage7d, 10);
assert.equal(qs.limit7d, 100);
assert.equal(qs.scope, getCodexModelScope(MODEL));
assert.match(String(qs.updatedAt), ISO);
// existing keys passed through, not dropped
assert.equal(built.nextProviderData.keepMe, "yes");
assert.deepEqual(built.nextProviderData.apiKeyHealth, { primary: {} });
// non-429 → no exhaustion fields, no log
assert.equal(built.exhaustionLog, null);
assert.equal(built.nextProviderData.codexScopeRateLimitedUntil, undefined);
assert.equal(built.nextProviderData.codexExhaustedWindow, undefined);
});
test("429 with a near-exhausted 5h window records the per-scope cooldown + window + log", () => {
const built = buildCodexQuotaPersistence({
headers: quotaHeaders({ "x-codex-5h-usage": "100" }), // ratio 1.0 >= 0.95, reset far in the future
existingProviderData: {},
modelForScope: MODEL,
status: 429,
});
assert.ok(built);
assert.equal(built.nextProviderData.codexExhaustedWindow, "5h");
const scope = getCodexModelScope(MODEL);
const scopeMap = built.nextProviderData.codexScopeRateLimitedUntil as Record<string, string>;
assert.ok(scopeMap[scope]?.startsWith("2999-01-01T00:00:00"));
assert.match(
String(built.exhaustionLog),
/^Quota exhaustion on 5h window, cooldown until 2999-01-01T00:00:00/
);
});
test("429 merges into an existing codexScopeRateLimitedUntil map without dropping other scopes", () => {
const built = buildCodexQuotaPersistence({
headers: quotaHeaders({ "x-codex-5h-usage": "100" }),
existingProviderData: { codexScopeRateLimitedUntil: { "other-scope": "2999-12-31T00:00:00.000Z" } },
modelForScope: MODEL,
status: 429,
});
assert.ok(built);
const scopeMap = built.nextProviderData.codexScopeRateLimitedUntil as Record<string, string>;
assert.equal(scopeMap["other-scope"], "2999-12-31T00:00:00.000Z");
assert.ok(scopeMap[getCodexModelScope(MODEL)]);
});
test("429 below the exhaustion threshold builds the snapshot but no cooldown / no log", () => {
const built = buildCodexQuotaPersistence({
headers: quotaHeaders({ "x-codex-5h-usage": "1", "x-codex-7d-usage": "1" }), // ratios well under 0.95
existingProviderData: {},
modelForScope: MODEL,
status: 429,
});
assert.ok(built);
assert.ok(built.nextProviderData.codexQuotaState);
assert.equal(built.exhaustionLog, null);
assert.equal(built.nextProviderData.codexScopeRateLimitedUntil, undefined);
assert.equal(built.nextProviderData.codexExhaustedWindow, undefined);
});

View File

@@ -0,0 +1,79 @@
// tests/unit/chatcore-failure-usage.test.ts
// Characterization of buildFailureUsageRecord — the failed-request usage payload builder extracted
// from handleChatCore's persistFailureUsage closure (chatCore god-file decomposition, #3501). Pure:
// the handler keeps the fire-and-forget saveRequestUsage(...).catch() call and computes latencyMs;
// this builds the record. Locks the unknown/undefined fallbacks, the zeroed token/timing fields,
// the combo-strategy gate, and the ISO timestamp.
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildFailureUsageRecord } from "../../open-sse/handlers/chatCore/failureUsage.ts";
const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
test("maps a fully-populated failure into the usage record", () => {
const r = buildFailureUsageRecord({
provider: "openai",
model: "gpt-4o",
connectionId: "conn-1",
apiKeyInfo: { id: "key-1", name: "My Key" },
effectiveServiceTier: "priority",
isCombo: true,
comboStrategy: "weighted",
statusCode: 429,
errorCode: "rate_limited",
latencyMs: 1234,
});
assert.equal(r.provider, "openai");
assert.equal(r.model, "gpt-4o");
assert.deepEqual(r.tokens, { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 });
assert.equal(r.status, "429");
assert.equal(r.success, false);
assert.equal(r.latencyMs, 1234);
assert.equal(r.timeToFirstTokenMs, 0);
assert.equal(r.errorCode, "rate_limited");
assert.match(String(r.timestamp), ISO);
assert.equal(r.connectionId, "conn-1");
assert.equal(r.apiKeyId, "key-1");
assert.equal(r.apiKeyName, "My Key");
assert.equal(r.serviceTier, "priority");
assert.equal(r.comboStrategy, "weighted");
});
test("applies the unknown/undefined fallbacks", () => {
const r = buildFailureUsageRecord({
provider: null,
model: undefined,
connectionId: null,
apiKeyInfo: null,
effectiveServiceTier: "standard",
isCombo: false,
comboStrategy: "priority",
statusCode: 502,
errorCode: null,
latencyMs: 7,
});
assert.equal(r.provider, "unknown");
assert.equal(r.model, "unknown");
assert.equal(r.errorCode, "502"); // falls back to String(statusCode)
assert.equal(r.connectionId, undefined);
assert.equal(r.apiKeyId, undefined);
assert.equal(r.apiKeyName, undefined);
// isCombo false → comboStrategy is dropped even when supplied
assert.equal(r.comboStrategy, undefined);
});
test("combo strategy is included only for combo requests", () => {
const combo = buildFailureUsageRecord({
provider: "x", model: "y", connectionId: null, apiKeyInfo: null,
effectiveServiceTier: "standard", isCombo: true, comboStrategy: "round-robin",
statusCode: 500, errorCode: "boom", latencyMs: 1,
});
assert.equal(combo.comboStrategy, "round-robin");
const comboNoStrategy = buildFailureUsageRecord({
provider: "x", model: "y", connectionId: null, apiKeyInfo: null,
effectiveServiceTier: "standard", isCombo: true, comboStrategy: null,
statusCode: 500, errorCode: "boom", latencyMs: 1,
});
assert.equal(comboNoStrategy.comboStrategy, undefined);
});

View File

@@ -0,0 +1,70 @@
// tests/unit/chatcore-key-health.test.ts
// Characterization of recordKeyHealthStatus — the per-request API-key health updater extracted
// from handleChatCore (chatCore god-file decomposition, #3501). Locks the observable in-memory
// transitions driven through apiKeyRotator: 401 → failure (warning, then invalid at the threshold),
// 2xx → success/recovery, selectedKeyId scoping, and the no-op paths (missing connectionId, and
// non-401/non-2xx statuses). The DB persistence side effect (updateProviderConnection) is moved
// byte-identically and is fire-and-forget; these tests assert the synchronous health mutations.
import { test, afterEach } from "node:test";
import assert from "node:assert/strict";
import { recordKeyHealthStatus } from "../../open-sse/handlers/chatCore/keyHealth.ts";
import { getAllKeyHealth, removeConnectionHealth } from "../../open-sse/services/apiKeyRotator.ts";
const noopLog = { warn: () => {}, error: () => {} };
const touched: string[] = [];
function creds(connectionId: string, psd: Record<string, unknown> = {}) {
touched.push(connectionId);
return { connectionId, providerSpecificData: psd };
}
afterEach(() => {
for (const c of touched.splice(0)) removeConnectionHealth(c);
});
test("missing connectionId is a no-op (no health entry created)", () => {
const before = Object.keys(getAllKeyHealth()).length;
const r = recordKeyHealthStatus(200, { providerSpecificData: {} }, noopLog);
assert.equal(r, undefined);
assert.equal(Object.keys(getAllKeyHealth()).length, before);
});
test("401 marks the selected key as failed → warning after the first failure", () => {
const conn = "kh-401-warning";
recordKeyHealthStatus(401, creds(conn), noopLog);
const h = getAllKeyHealth()[`${conn}:primary`];
assert.equal(h?.failures, 1);
assert.equal(h?.status, "warning");
});
test("401 reaches invalid at the failure threshold (2 consecutive)", () => {
const conn = "kh-401-invalid";
recordKeyHealthStatus(401, creds(conn), noopLog);
recordKeyHealthStatus(401, creds(conn), noopLog);
const h = getAllKeyHealth()[`${conn}:primary`];
assert.equal(h?.failures, 2);
assert.equal(h?.status, "invalid");
});
test("2xx after a failure resets the key to active with 0 failures", () => {
const conn = "kh-2xx-recover";
recordKeyHealthStatus(401, creds(conn), noopLog);
recordKeyHealthStatus(204, creds(conn), noopLog);
const h = getAllKeyHealth()[`${conn}:primary`];
assert.equal(h?.failures, 0);
assert.equal(h?.status, "active");
});
test("honors selectedKeyId — scopes the update to the active extra key, not primary", () => {
const conn = "kh-selected-key";
recordKeyHealthStatus(401, creds(conn, { selectedKeyId: "extra_1" }), noopLog);
const all = getAllKeyHealth();
assert.equal(all[`${conn}:extra_1`]?.status, "warning");
assert.equal(all[`${conn}:primary`], undefined);
});
test("non-401 / non-2xx status does not touch key health", () => {
const conn = "kh-5xx-noop";
recordKeyHealthStatus(500, creds(conn), noopLog);
assert.equal(getAllKeyHealth()[`${conn}:primary`], undefined);
});

View File

@@ -0,0 +1,103 @@
// tests/unit/chatcore-request-format.test.ts
// Characterization of resolveChatCoreRequestFormat — the endpoint/format resolution slice extracted
// from the top of handleChatCore (chatCore god-file decomposition, #3501). Locks the endpointPath
// construction, the /responses detection, the nativeCodexPassthrough + isDroidCLI + copilot wiring,
// and the clientResponseFormat downgrade (OpenAI Responses shape off a non-/responses, non-Droid
// endpoint collapses to plain OpenAI).
import { test } from "node:test";
import assert from "node:assert/strict";
import { resolveChatCoreRequestFormat } from "../../open-sse/handlers/chatCore/requestFormat.ts";
import { shouldUseNativeCodexPassthrough } from "../../open-sse/handlers/chatCore/passthroughHelpers.ts";
import { FORMATS } from "../../open-sse/translator/formats.ts";
const base = { body: { messages: [{ role: "user", content: "hi" }] }, provider: "openai", userAgent: "unit-test" };
test("chat/completions endpoint → openai source, not a responses endpoint, no downgrade", () => {
const r = resolveChatCoreRequestFormat({
...base,
clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Headers() },
});
assert.equal(r.endpointPath, "/v1/chat/completions");
assert.equal(r.sourceFormat, FORMATS.OPENAI);
assert.equal(r.isResponsesEndpoint, false);
assert.equal(r.clientResponseFormat, FORMATS.OPENAI);
assert.equal(r.nativeCodexPassthrough, false); // provider !== codex
assert.equal(r.isDroidCLI, false);
assert.equal(r.copilotCompatibleReasoning, false);
});
test("/responses endpoint → openai-responses source + isResponsesEndpoint, kept (no downgrade)", () => {
const r = resolveChatCoreRequestFormat({
body: { input: "x" },
provider: "openai",
userAgent: "unit-test",
clientRawRequest: { endpoint: "/v1/responses", headers: new Headers() },
});
assert.equal(r.sourceFormat, FORMATS.OPENAI_RESPONSES);
assert.equal(r.isResponsesEndpoint, true);
assert.equal(r.clientResponseFormat, FORMATS.OPENAI_RESPONSES);
});
test("Responses-shaped body on a /chat/completions endpoint downgrades clientResponseFormat to openai", () => {
const r = resolveChatCoreRequestFormat({
body: { input: "describe" }, // input + no messages → openai-responses via body
provider: "openai",
userAgent: "unit-test",
clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Headers() },
});
assert.equal(r.sourceFormat, FORMATS.OPENAI_RESPONSES);
assert.equal(r.isResponsesEndpoint, false);
assert.equal(r.clientResponseFormat, FORMATS.OPENAI); // downgraded
});
test("Droid CLI suppresses the downgrade (clientResponseFormat stays openai-responses)", () => {
const r = resolveChatCoreRequestFormat({
body: { input: "describe" },
provider: "openai",
userAgent: "Droid/1.2 codex-cli",
clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Headers() },
});
assert.equal(r.isDroidCLI, true);
assert.equal(r.sourceFormat, FORMATS.OPENAI_RESPONSES);
assert.equal(r.clientResponseFormat, FORMATS.OPENAI_RESPONSES); // !isDroidCLI is false → no downgrade
});
test("copilotCompatibleReasoning detects copilot via header or user-agent", () => {
const viaUa = resolveChatCoreRequestFormat({
...base,
userAgent: "GitHubCopilotChat/0.1",
clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Headers() },
});
assert.equal(viaUa.copilotCompatibleReasoning, true);
const viaHeader = resolveChatCoreRequestFormat({
...base,
clientRawRequest: {
endpoint: "/v1/chat/completions",
headers: new Headers({ "x-client": "copilot-vscode" }),
},
});
assert.equal(viaHeader.copilotCompatibleReasoning, true);
});
test("nativeCodexPassthrough delegates to shouldUseNativeCodexPassthrough (codex + responses)", () => {
const r = resolveChatCoreRequestFormat({
body: { input: "x" },
provider: "codex",
userAgent: "unit-test",
clientRawRequest: { endpoint: "/v1/responses", headers: new Headers() },
});
assert.equal(
r.nativeCodexPassthrough,
shouldUseNativeCodexPassthrough({
provider: "codex",
sourceFormat: r.sourceFormat,
endpointPath: r.endpointPath,
})
);
});
test("missing clientRawRequest → empty endpointPath", () => {
const r = resolveChatCoreRequestFormat({ ...base, clientRawRequest: null });
assert.equal(r.endpointPath, "");
});

View File

@@ -0,0 +1,89 @@
// tests/unit/chatcore-target-format.test.ts
// Characterization of resolveChatCoreTargetFormat — the wire target-format resolution extracted
// from handleChatCore (chatCore god-file decomposition, #3501). Resolves the provider alias and the
// upstream target format: apiFormat==="responses" forces OpenAI Responses; otherwise the model's
// registry target format, then the custom-model override, then the provider default. Returns both
// `alias` (reused downstream when stripping the alias/ prefix off the upstream model) and
// `targetFormat`. Asserted against the inline composition so the delegation stays byte-identical.
import { test } from "node:test";
import assert from "node:assert/strict";
import { resolveChatCoreTargetFormat } from "../../open-sse/handlers/chatCore/targetFormat.ts";
import { PROVIDER_ID_TO_ALIAS, getModelTargetFormat } from "../../open-sse/config/providerModels.ts";
import { getTargetFormat } from "../../open-sse/services/provider.ts";
import { FORMATS } from "../../open-sse/translator/formats.ts";
function expected(
provider: string,
resolvedModel: string,
apiFormat: string | undefined,
customModelTargetFormat: string | undefined,
providerSpecificData: unknown
) {
const alias = PROVIDER_ID_TO_ALIAS[provider] || provider;
const modelTargetFormat = getModelTargetFormat(alias, resolvedModel);
const targetFormat =
apiFormat === "responses"
? FORMATS.OPENAI_RESPONSES
: modelTargetFormat || customModelTargetFormat || getTargetFormat(provider, providerSpecificData);
return { alias, targetFormat };
}
test("apiFormat='responses' short-circuits to OPENAI_RESPONSES (alias still resolved)", () => {
const r = resolveChatCoreTargetFormat({
provider: "openai",
resolvedModel: "gpt-4o",
apiFormat: "responses",
customModelTargetFormat: undefined,
providerSpecificData: undefined,
});
assert.equal(r.targetFormat, FORMATS.OPENAI_RESPONSES);
assert.equal(r.alias, PROVIDER_ID_TO_ALIAS["openai"] || "openai");
});
test("delegates byte-identically for a normal model (no apiFormat / no custom override)", () => {
const r = resolveChatCoreTargetFormat({
provider: "openai",
resolvedModel: "gpt-4o",
apiFormat: undefined,
customModelTargetFormat: undefined,
providerSpecificData: undefined,
});
assert.deepEqual(r, expected("openai", "gpt-4o", undefined, undefined, undefined));
});
test("customModelTargetFormat is used when the model has no registry target format", () => {
const customModel = "totally-unknown-custom-model-xyz";
// precondition: the registry has no target format for this unknown model
assert.ok(!getModelTargetFormat(PROVIDER_ID_TO_ALIAS["openai"] || "openai", customModel));
const r = resolveChatCoreTargetFormat({
provider: "openai",
resolvedModel: customModel,
apiFormat: undefined,
customModelTargetFormat: "claude",
providerSpecificData: undefined,
});
assert.equal(r.targetFormat, "claude");
});
test("falls back to getTargetFormat(provider) when neither model nor custom format apply", () => {
const customModel = "totally-unknown-custom-model-xyz";
const r = resolveChatCoreTargetFormat({
provider: "openai",
resolvedModel: customModel,
apiFormat: undefined,
customModelTargetFormat: undefined,
providerSpecificData: undefined,
});
assert.equal(r.targetFormat, getTargetFormat("openai", undefined));
});
test("unmapped provider → alias falls back to the provider id", () => {
const r = resolveChatCoreTargetFormat({
provider: "some-unmapped-provider",
resolvedModel: "x",
apiFormat: "responses",
customModelTargetFormat: undefined,
providerSpecificData: undefined,
});
assert.equal(r.alias, "some-unmapped-provider");
});

View File

@@ -0,0 +1,70 @@
// tests/unit/chatcore-upstream-execute-headers.test.ts
// Characterization of buildUpstreamHeadersForExecute — the per-model upstream extra-header builder
// extracted from handleChatCore (chatCore god-file decomposition, #3501). Locks: the
// connection custom User-Agent override, the Claude Fast Mode opt-in (claude provider + enabled
// settings + supported model only), and the modelToCall===effectiveModel vs alias branches.
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildUpstreamHeadersForExecute } from "../../open-sse/handlers/chatCore/upstreamExecuteHeaders.ts";
import { CPA_FORCE_FAST_MODE_HEADER } from "../../src/lib/providers/claudeFastMode.ts";
const base = {
modelToCall: "some-model",
effectiveModel: "some-model",
provider: "openai",
model: "some-model",
resolvedModel: "some-model",
sourceFormat: "openai",
connectionCustomUserAgent: "",
settings: {},
};
test("no custom UA / non-claude / no fast settings → no UA and no fast-mode header", () => {
const h = buildUpstreamHeadersForExecute({ ...base });
assert.equal(h["User-Agent"], undefined);
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined);
});
test("connection custom User-Agent overrides the upstream User-Agent", () => {
const h = buildUpstreamHeadersForExecute({ ...base, connectionCustomUserAgent: "MyAgent/1.0" });
assert.equal(h["User-Agent"], "MyAgent/1.0");
});
test("claude provider + enabled fast-mode settings + supported model → CPA header set", () => {
const h = buildUpstreamHeadersForExecute({
...base,
provider: "claude",
modelToCall: "claude-fast-x",
effectiveModel: "claude-fast-x",
settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } },
});
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], "1");
});
test("fast-mode header is NOT set when the model is not in the supported list", () => {
const h = buildUpstreamHeadersForExecute({
...base,
provider: "claude",
modelToCall: "claude-other",
effectiveModel: "claude-other",
settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } },
});
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined);
});
test("fast-mode header is NOT set for non-claude providers even with fast settings", () => {
const h = buildUpstreamHeadersForExecute({
...base,
provider: "openai",
modelToCall: "claude-fast-x",
effectiveModel: "claude-fast-x",
settings: { claudeFastMode: { enabled: true, supportedModels: ["claude-fast-x"] } },
});
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined);
});
test("returns a plain object (no per-model extra headers configured for unknown models)", () => {
const h = buildUpstreamHeadersForExecute({ ...base, modelToCall: "totally-unknown", effectiveModel: "x" });
assert.equal(typeof h, "object");
assert.equal(h[CPA_FORCE_FAST_MODE_HEADER], undefined);
});

View File

@@ -99,7 +99,6 @@ const TYPE_ONLY = new Set(["_rowTypes"]);
// They remain in INTENTIONALLY_INTERNAL for schema-reservation reasons.
// Flag them but do NOT fail — a separate decision is needed to remove them.
const DOCUMENTED_DEAD = new Set([
"compressionScheduler", // DEAD?: 0 production importers as of 2026-06-11
"discovery", // DEAD?: 0 importers; lib/discovery/index.ts is independent
"pluginMetrics", // DEAD? (production): write path not yet wired (self-documented)
"prompts", // DEAD? (production): zero production callers; integration test only verifies interface shape
@@ -122,7 +121,7 @@ test("INTENTIONALLY_INTERNAL is exported from check-db-rules.mjs", () => {
assert.ok(INTENTIONALLY_INTERNAL.size > 0, "INTENTIONALLY_INTERNAL must not be empty");
});
test("INTENTIONALLY_INTERNAL contains the expected 28 audited modules", () => {
test("INTENTIONALLY_INTERNAL contains the expected 29 audited modules", () => {
const expected = [
"_rowTypes",
"accessTokens",
@@ -133,7 +132,6 @@ test("INTENTIONALLY_INTERNAL contains the expected 28 audited modules", () => {
"comboForecast",
"commandCodeAuth",
"compression",
"compressionScheduler",
"detailedLogs",
"discovery",
"domainState",
@@ -145,6 +143,7 @@ test("INTENTIONALLY_INTERNAL contains the expected 28 audited modules", () => {
"obsidian",
"pluginMetrics",
"prompts",
"providerNodeSelect",
"providerStats",
"recovery",
"secrets",
@@ -152,6 +151,7 @@ test("INTENTIONALLY_INTERNAL contains the expected 28 audited modules", () => {
"stateReset",
"stats",
"tierConfig",
"vacuumScheduler",
];
for (const mod of expected) {
assert.ok(

View File

@@ -0,0 +1,36 @@
import test from "node:test";
import assert from "node:assert/strict";
const { applyFingerprint } = await import("../../open-sse/config/cliFingerprints.ts");
test("Codex CLI fingerprint orders prompt_cache_key before include", () => {
const body = {
model: "gpt-5.5-low",
stream: true,
input: [{ role: "user", content: "hello" }],
instructions: "You are Codex.",
store: false,
reasoning: { effort: "low" },
tools: [],
tool_choice: "auto",
include: ["reasoning.encrypted_content"],
prompt_cache_key: "conv-codex",
service_tier: "priority",
};
const result = applyFingerprint("codex", {}, body);
const orderedKeys = Object.keys(JSON.parse(result.bodyString));
assert.deepEqual(orderedKeys.slice(0, 10), [
"model",
"stream",
"input",
"instructions",
"store",
"reasoning",
"prompt_cache_key",
"tools",
"tool_choice",
"include",
]);
});

View File

@@ -2,6 +2,10 @@ import test from "node:test";
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
// #4425: the supervisor now waits for the listen port to free up before respawning.
// Point that probe at the no-op port 0 so the restart tests don't open real sockets.
process.env.PORT = "0";
// Stub para o processSupervisor: testa a lógica de restart/backoff/MITM sem processos reais.
class StubChild extends EventEmitter {
@@ -41,9 +45,13 @@ test("detectMitmCrash retorna false com menos de 2 sinais", async () => {
// --- ServerSupervisor: lógica de restart ---
test("ServerSupervisor.handleExit com code=0 chama process.exit(0)", async () => {
test("ServerSupervisor.handleExit com code=0 espontâneo reinicia em vez de sair (#4425)", async () => {
const { ServerSupervisor } = await import("../../bin/cli/runtime/processSupervisor.mjs");
// waitUntilPortFree no-ops on port 0, so the scheduled restart fires fast and leaks no timer.
const origPort = process.env.PORT;
process.env.PORT = "0";
const exits: number[] = [];
const origExit = process.exit.bind(process);
// @ts-ignore
@@ -56,11 +64,27 @@ test("ServerSupervisor.handleExit com code=0 chama process.exit(0)", async () =>
env: {},
maxRestarts: 2,
});
let started = 0;
// Stub start() so the scheduled restart never spawns the fake server.
supervisor.start = () => {
started++;
return null as any;
};
supervisor.handleExit(0);
// #4425: a spontaneous code-0 exit (e.g. a systemd MemoryMax cgroup kill, which reports
// a clean exit) must NOT terminate the supervisor — it schedules a restart instead.
assert.equal(exits.length, 0, "must not process.exit on a spontaneous code-0 exit");
assert.equal(supervisor.restartCount, 1);
// Let the scheduled restart fire so no timer leaks past the test.
await new Promise((r) => setTimeout(r, 1100));
assert.equal(started, 1, "restart should fire after the backoff delay");
// @ts-ignore
process.exit = origExit;
assert.equal(exits[0], 0);
if (origPort === undefined) delete process.env.PORT;
else process.env.PORT = origPort;
});
test("ServerSupervisor.handleExit com isShuttingDown=true chama process.exit imediato", async () => {
@@ -125,6 +149,7 @@ test("ServerSupervisor.handleExit exibe crash log ao reiniciar", async () => {
console.error = origErr;
assert.ok(logs.some((l) => l.includes("line1") || l.includes("crash log")));
await new Promise((r) => setTimeout(r, 1100)); // drain the scheduled restart timer
});
test("ServerSupervisor chama onCrashCallback após maxRestarts atingido", async () => {
@@ -179,7 +204,7 @@ test("ServerSupervisor retorna 'disable-mitm-and-retry' chama start() novamente"
assert.equal(supervisor.restartCount, 0); // foi resetado
});
test("ServerSupervisor reseta restartCount após processo viver >=30s", async () => {
test("ServerSupervisor reseta restartCount após viver >= RESTART_RESET_MS (#4425: 60s)", async () => {
const { ServerSupervisor } = await import("../../bin/cli/runtime/processSupervisor.mjs");
const supervisor = new ServerSupervisor({
@@ -189,10 +214,11 @@ test("ServerSupervisor reseta restartCount após processo viver >=30s", async ()
});
supervisor.start = () => null as any;
supervisor.restartCount = 2;
supervisor.startedAt = Date.now() - 31_000; // viveu 31s
supervisor.startedAt = Date.now() - 61_000; // #4425: reset window bumped 30s→60s
supervisor.handleExit(1);
assert.equal(supervisor.restartCount, 1); // reset p/ 0, depois incrementado p/ 1
await new Promise((r) => setTimeout(r, 1100)); // drain the scheduled restart timer
});
// --- Node.js v24 compat: process.exit() must receive a number (#3748) ---

View File

@@ -0,0 +1,123 @@
import test from "node:test";
import assert from "node:assert/strict";
const { isApplyDisabled, isResetDisabled } = await import(
"../../src/app/(dashboard)/dashboard/cli-code/components/codexButtonState.ts"
);
test("Codex tool card — Apply / Reset disabled state", async (t) => {
// ──────────────────────────── Apply ────────────────────────────
await t.test("Apply is disabled when no model is selected (regardless of key)", () => {
assert.equal(
isApplyDisabled({
selectedModel: "",
selectedApiKey: "key-1",
cloudEnabled: true,
apiKeys: ["key-1"],
}),
true,
);
assert.equal(
isApplyDisabled({
selectedModel: null,
selectedApiKey: "",
cloudEnabled: false,
apiKeys: [],
}),
true,
);
});
await t.test(
"Apply is ENABLED with model + no key when cloud is disabled (default sk_omniroute path)",
() => {
// This is the central case the upstream port fixes: in local-mode the
// sk_omniroute default kicks in, so an empty selectedApiKey must not
// disable Apply.
assert.equal(
isApplyDisabled({
selectedModel: "gpt-5.5",
selectedApiKey: "",
cloudEnabled: false,
apiKeys: ["key-1"], // even with keys configured, local mode wins
}),
false,
);
},
);
await t.test("Apply is ENABLED with model + no key when no keys exist at all", () => {
// Fresh install / cloud on but no keys created yet — the user should still
// be able to apply with the default key.
assert.equal(
isApplyDisabled({
selectedModel: "gpt-5.5",
selectedApiKey: "",
cloudEnabled: true,
apiKeys: [],
}),
false,
);
});
await t.test(
"Apply IS disabled when cloud is on AND keys exist AND none selected (user must pick one)",
() => {
assert.equal(
isApplyDisabled({
selectedModel: "gpt-5.5",
selectedApiKey: "",
cloudEnabled: true,
apiKeys: ["key-1", "key-2"],
}),
true,
);
},
);
await t.test("Apply is ENABLED when a model and a key are both selected", () => {
assert.equal(
isApplyDisabled({
selectedModel: "gpt-5.5",
selectedApiKey: "key-1",
cloudEnabled: true,
apiKeys: ["key-1"],
}),
false,
);
});
await t.test("Apply tolerates null/undefined apiKeys (treats as empty)", () => {
assert.equal(
isApplyDisabled({
selectedModel: "gpt-5.5",
selectedApiKey: "",
cloudEnabled: true,
apiKeys: null,
}),
false,
);
assert.equal(
isApplyDisabled({
selectedModel: "gpt-5.5",
selectedApiKey: "",
cloudEnabled: true,
apiKeys: undefined,
}),
false,
);
});
// ──────────────────────────── Reset ────────────────────────────
await t.test("Reset is ENABLED by default when the CLI is installed", () => {
// The card only renders this control in the installed branch, so the only
// thing that should ever block it is an in-flight reset.
assert.equal(isResetDisabled({ restoring: false }), false);
});
await t.test("Reset is DISABLED only while the reset request is in flight", () => {
assert.equal(isResetDisabled({ restoring: true }), true);
});
});

View File

@@ -0,0 +1,109 @@
/**
* Regression test for Codex review-quota plumbing.
*
* Upstream parity: decolua/9router PR #836 surfaces the SECONDARY window of
* `code_review_rate_limit` and supports descriptors that arrive via the
* `additional_rate_limits` array (some ChatGPT plans report the review limit
* there rather than in the dedicated `code_review_rate_limit` block).
*
* Before this fix:
* - `buildCodexUsageQuotas` only emitted `quotas.code_review` from the
* primary window of `code_review_rate_limit`, so the WEEKLY review window
* was invisible to the dashboard.
* - Review limits surfaced under `additional_rate_limits` (with
* `limit_name`/`metered_feature` containing "review") were dropped.
*
* After this fix:
* - The secondary window of `code_review_rate_limit` is emitted as
* `quotas.code_review_weekly` (parallel to `session`/`weekly`).
* - Review entries inside `additional_rate_limits` populate the same
* `code_review`/`code_review_weekly` keys when the dedicated block is
* absent.
* - The primary `code_review` key is preserved (backward-compat for the
* existing dashboard rendering & the usage-service-hardening regression).
*/
import test from "node:test";
import assert from "node:assert/strict";
import { buildCodexUsageQuotas } from "../../open-sse/services/codexUsageQuotas.ts";
test("buildCodexUsageQuotas surfaces the secondary code_review window", () => {
const { quotas } = buildCodexUsageQuotas({
rate_limit: {
primary_window: { used_percent: 25 },
secondary_window: { used_percent: 50 },
},
code_review_rate_limit: {
primary_window: { used_percent: 40, reset_after_seconds: 45 },
secondary_window: { used_percent: 70, reset_after_seconds: 6000 },
},
});
// Pre-existing primary window stays under `code_review` (back-compat).
assert.equal(quotas.code_review?.used, 40);
assert.equal(quotas.code_review?.remaining, 60);
// New: secondary window exposed as `code_review_weekly`.
assert.equal(quotas.code_review_weekly?.used, 70);
assert.equal(quotas.code_review_weekly?.remaining, 30);
assert.equal(quotas.code_review_weekly?.total, 100);
assert.equal(quotas.code_review_weekly?.unlimited, false);
});
test("buildCodexUsageQuotas reads review windows from additional_rate_limits", () => {
const { quotas } = buildCodexUsageQuotas({
rate_limit: {
primary_window: { used_percent: 10 },
},
additional_rate_limits: [
{
limit_name: "Code Review",
metered_feature: "code_review",
rate_limit: {
primary_window: { used_percent: 33 },
secondary_window: { used_percent: 55 },
},
},
],
});
assert.equal(quotas.code_review?.used, 33);
assert.equal(quotas.code_review?.remaining, 67);
assert.equal(quotas.code_review_weekly?.used, 55);
assert.equal(quotas.code_review_weekly?.remaining, 45);
});
test("buildCodexUsageQuotas leaves review windows undefined when payload is silent", () => {
const { quotas } = buildCodexUsageQuotas({
rate_limit: {
primary_window: { used_percent: 10 },
secondary_window: { used_percent: 20 },
},
});
assert.equal(quotas.session?.used, 10);
assert.equal(quotas.weekly?.used, 20);
assert.equal(quotas.code_review, undefined);
assert.equal(quotas.code_review_weekly, undefined);
});
test("buildCodexUsageQuotas prefers dedicated block over additional_rate_limits fallback", () => {
const { quotas } = buildCodexUsageQuotas({
code_review_rate_limit: {
primary_window: { used_percent: 11 },
secondary_window: { used_percent: 22 },
},
additional_rate_limits: [
{
limit_name: "review",
rate_limit: {
primary_window: { used_percent: 99 },
secondary_window: { used_percent: 99 },
},
},
],
});
assert.equal(quotas.code_review?.used, 11);
assert.equal(quotas.code_review_weekly?.used, 22);
});

View File

@@ -26,6 +26,7 @@ test("getDefaultComboConfig returns a fresh copy of the defaults", () => {
assert.equal(first.handoffThreshold, 0.85);
assert.equal(first.maxMessagesForSummary, 30);
assert.deepEqual(first.handoffProviders, ["codex"]);
assert.equal(first.nestedComboMode, "flatten");
assert.equal(first.failoverBeforeRetry, true);
assert.equal(first.maxSetRetries, 0);
assert.equal(first.setRetryDelayMs, 2000);
@@ -531,6 +532,30 @@ test("createComboSchema accepts failoverBeforeRetry, maxSetRetries and setRetryD
assert.equal(parsed.config.setRetryDelayMs, 1500);
});
test("createComboSchema accepts nestedComboMode and rejects invalid values", () => {
const parsed = createComboSchema.parse({
name: "nested-execute",
models: [{ kind: "combo-ref", comboName: "child" }],
strategy: "priority",
config: { nestedComboMode: "execute" },
});
assert.equal(parsed.config.nestedComboMode, "execute");
const flatten = createComboSchema.parse({
name: "nested-flatten",
models: ["openai/gpt-4o-mini"],
config: { nestedComboMode: "flatten" },
});
assert.equal(flatten.config.nestedComboMode, "flatten");
const invalid = createComboSchema.safeParse({
name: "nested-invalid",
models: ["openai/gpt-4o-mini"],
config: { nestedComboMode: "redirect" },
});
assert.equal(invalid.success, false);
});
test("createComboSchema accepts per-combo stickyRoundRobinLimit and rejects out-of-range", () => {
const parsed = createComboSchema.parse({
name: "sticky-override",
@@ -549,6 +574,24 @@ test("createComboSchema accepts per-combo stickyRoundRobinLimit and rejects out-
assert.equal(tooHigh.success, false);
});
test("createComboSchema accepts per-combo stickyWeightedLimit and rejects out-of-range", () => {
const parsed = createComboSchema.parse({
name: "sticky-weighted",
models: [{ model: "openai/gpt-4o-mini", weight: 100 }],
strategy: "weighted",
config: { stickyWeightedLimit: 2 },
});
assert.equal(parsed.config.stickyWeightedLimit, 2);
const tooHigh = createComboSchema.safeParse({
name: "sticky-weighted-too-high",
models: [{ model: "openai/gpt-4o-mini", weight: 100 }],
strategy: "weighted",
config: { stickyWeightedLimit: 1001 },
});
assert.equal(tooHigh.success, false);
});
test("createComboSchema coerces string numbers for maxSetRetries and setRetryDelayMs", () => {
const parsed = createComboSchema.parse({
name: "coerce-test",
@@ -600,6 +643,17 @@ test("createComboSchema rejects setRetryDelayMs out of range", () => {
assert.equal(negative.success, false);
});
test("resolveComboConfig cascades nestedComboMode", () => {
const result = resolveComboConfig(
{ config: { nestedComboMode: "execute" } },
{ comboDefaults: { nestedComboMode: "flatten" } }
);
assert.equal(result.nestedComboMode, "execute");
const defaulted = resolveComboConfig({ config: {} }, { comboDefaults: {} });
assert.equal(defaulted.nestedComboMode, "flatten");
});
test("resolveComboConfig cascades failoverBeforeRetry, maxSetRetries and setRetryDelayMs", () => {
const result = resolveComboConfig(
{

View File

@@ -0,0 +1,60 @@
import test from "node:test";
import assert from "node:assert/strict";
// #4519 — allow combo fallback on context-overflow / param-validation 400s and
// preserve upstream error semantics. Three regression guards:
// 1. accountFallback.checkFallbackError classifies param-validation 400s as
// fallback-worthy with zero cooldown.
// 2. combo guard predicates (isContextOverflow400 / isParamValidation400) let
// those 400s fall through to the next target instead of short-circuiting.
// 3. openai-responses.normalizeUpstreamFailure keeps context_length_exceeded as
// 400 and rate-limit as 429 (instead of rewriting everything to 502).
const { checkFallbackError } = await import("../../open-sse/services/accountFallback.ts");
const { isContextOverflow400, isParamValidation400 } = await import(
"../../open-sse/services/combo.ts"
);
const { normalizeUpstreamFailure } = await import(
"../../open-sse/translator/response/openai-responses.ts"
);
test("#4519 checkFallbackError treats per-model max_tokens 400 as fallback-worthy with zero cooldown", () => {
const res = checkFallbackError(
400,
"The max_tokens parameter is illegal.:限制数值范围[1,131072]"
);
assert.equal(res.shouldFallback, true);
assert.equal(res.cooldownMs, 0);
});
test("#4519 combo guard: context-overflow and param-validation 400 texts are recognized", () => {
assert.equal(isContextOverflow400("This model's context_length_exceeded for the input"), true);
assert.equal(isContextOverflow400("your input exceeds the allowed size"), true);
assert.equal(isParamValidation400("max_tokens must be in range [1,131072]"), true);
assert.equal(isParamValidation400("The parameter is illegal"), true);
});
test("#4519 combo guard: a genuinely body-specific 400 is NOT classified as overflow/param", () => {
const malformed = "Invalid JSON: unexpected token at position 12";
assert.equal(isContextOverflow400(malformed), false);
assert.equal(isParamValidation400(malformed), false);
});
test("#4519 normalizeUpstreamFailure preserves context_length_exceeded as 400", () => {
const out = normalizeUpstreamFailure({
error: { code: "context_length_exceeded", message: "too long" },
});
assert.equal(out.status, 400);
assert.equal(out.type, "invalid_request_error");
});
test("#4519 normalizeUpstreamFailure keeps rate-limit as 429 and unknown as 502", () => {
const rl = normalizeUpstreamFailure({
error: { code: "rate_limit_exceeded", message: "slow down" },
});
assert.equal(rl.status, 429);
assert.equal(rl.type, "rate_limit_error");
const unknown = normalizeUpstreamFailure({ error: { code: "weird_error", message: "boom" } });
assert.equal(unknown.status, 502);
});

View File

@@ -0,0 +1,348 @@
/**
* Unit tests for provider-wildcard combo expansion (#2562).
*
* Tests cover:
* - Detection of wildcard notation in combo models
* - Expansion against static providerRegistry models
* - Expansion against synced DB models (mocked)
* - Glob pattern filtering (`prefix*`)
* - Preservation of step metadata (weight, label, connectionId, allowedConnectionIds)
* - Graceful no-op when no models found (keeps original entry)
* - Non-wildcard entries pass through unchanged
* - Object-form `{ kind: "provider-wildcard", ... }` syntax
* - Collection-level expansion
*/
import test from "node:test";
import assert from "node:assert/strict";
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-provider-wildcard-"));
process.env.DATA_DIR = TEST_DATA_DIR;
// ── Imports ─────────────────────────────────────────────────────────────────
const {
isProviderWildcardEntry,
expandProviderWildcardsInCombo,
expandProviderWildcardsInCollection,
} = await import("../../open-sse/services/combo/providerWildcard.ts");
const { replaceSyncedAvailableModelsForConnection, getSyncedAvailableModels } =
await import("../../src/lib/db/models.ts");
const core = await import("../../src/lib/db/core.ts");
core.getDbInstance(); // initialise DB + run migrations
// ── Helpers ──────────────────────────────────────────────────────────────────
function makeCombo(models: unknown[], name = "test-combo") {
return { name, models, id: "test-id" };
}
// Seed synced models for a provider into the DB.
async function seedSyncedModels(providerId: string, connectionId: string, modelIds: string[]) {
await replaceSyncedAvailableModelsForConnection(
providerId,
connectionId,
modelIds.map((id) => ({ id, name: id, source: "imported" as const }))
);
}
// ── isProviderWildcardEntry ───────────────────────────────────────────────────
test("isProviderWildcardEntry: detects string wildcard `provider/*`", () => {
assert.equal(isProviderWildcardEntry("fta/*"), true);
assert.equal(isProviderWildcardEntry("openai/*"), true);
assert.equal(isProviderWildcardEntry("opc/deepseek*"), true);
assert.equal(isProviderWildcardEntry("openai/gpt-4*"), true);
});
test("isProviderWildcardEntry: rejects plain model strings", () => {
assert.equal(isProviderWildcardEntry("fta/some-model"), false);
assert.equal(isProviderWildcardEntry("openai/gpt-4o"), false);
assert.equal(isProviderWildcardEntry("openai"), false);
assert.equal(isProviderWildcardEntry(""), false);
assert.equal(isProviderWildcardEntry(null), false);
assert.equal(isProviderWildcardEntry(42), false);
});
test("isProviderWildcardEntry: detects object form `{ kind: 'provider-wildcard' }`", () => {
assert.equal(
isProviderWildcardEntry({ kind: "provider-wildcard", providerId: "fta", modelPattern: "*" }),
true
);
});
test("isProviderWildcardEntry: rejects object without kind=provider-wildcard", () => {
assert.equal(isProviderWildcardEntry({ kind: "model", model: "fta/some-model" }), false);
assert.equal(isProviderWildcardEntry({ kind: "provider-wildcard" }), false); // missing providerId
});
// ── expandProviderWildcardsInCombo — static registry ─────────────────────────
test("expandProviderWildcardsInCombo: expands `openai/*` against static registry", async () => {
// `openai` is a built-in provider with models in providerRegistry
const combo = makeCombo(["openai/*"]);
const result = await expandProviderWildcardsInCombo(combo);
// Should have expanded to at least 1 model
assert.ok(result.models.length > 0, "should have expanded to ≥1 model");
// Every expanded entry should be a model step object with `kind: "model"`
for (const entry of result.models) {
assert.equal(typeof entry, "object");
assert.equal((entry as any).kind, "model");
assert.ok(
typeof (entry as any).model === "string" && (entry as any).model.startsWith("openai/"),
`model should start with openai/, got: ${(entry as any).model}`
);
}
});
test("expandProviderWildcardsInCombo: `_expandedFromWildcard` tag is set on expanded entries", async () => {
const combo = makeCombo(["openai/*"]);
const result = await expandProviderWildcardsInCombo(combo);
for (const entry of result.models) {
assert.equal((entry as any)._expandedFromWildcard, "openai/*");
}
});
// ── expandProviderWildcardsInCombo — synced DB models ────────────────────────
test("expandProviderWildcardsInCombo: expands against synced DB models", async () => {
const providerId = "test-custom-provider-" + Date.now();
await seedSyncedModels(providerId, "conn-1", ["model-alpha", "model-beta", "model-gamma"]);
const combo = makeCombo([`${providerId}/*`]);
const result = await expandProviderWildcardsInCombo(combo);
assert.equal(result.models.length, 3);
const modelStrs = result.models.map((e) => (e as any).model);
assert.ok(modelStrs.includes(`${providerId}/model-alpha`));
assert.ok(modelStrs.includes(`${providerId}/model-beta`));
assert.ok(modelStrs.includes(`${providerId}/model-gamma`));
});
test("expandProviderWildcardsInCombo: glob prefix filter `provider/pre*`", async () => {
const providerId = "test-prefix-provider-" + Date.now();
await seedSyncedModels(providerId, "conn-1", [
"deepseek-v4-pro",
"deepseek-v4-flash",
"qwen3-free",
"minimax-m3",
]);
const combo = makeCombo([`${providerId}/deepseek*`]);
const result = await expandProviderWildcardsInCombo(combo);
assert.equal(result.models.length, 2);
const ids = result.models.map((e) => (e as any).model);
assert.ok(ids.includes(`${providerId}/deepseek-v4-pro`));
assert.ok(ids.includes(`${providerId}/deepseek-v4-flash`));
assert.ok(!ids.includes(`${providerId}/qwen3-free`));
assert.ok(!ids.includes(`${providerId}/minimax-m3`));
});
// ── Step metadata preservation ────────────────────────────────────────────────
test("expandProviderWildcardsInCombo: weight is inherited on expanded entries", async () => {
const providerId = "test-weight-provider-" + Date.now();
await seedSyncedModels(providerId, "conn-1", ["model-a", "model-b"]);
const combo = makeCombo([
{ kind: "provider-wildcard", providerId, modelPattern: "*", weight: 50, label: "fast" },
]);
const result = await expandProviderWildcardsInCombo(combo);
for (const entry of result.models) {
assert.equal((entry as any).weight, 50, "weight should be inherited");
assert.equal((entry as any).label, "fast", "label should be inherited");
}
});
test("expandProviderWildcardsInCombo: connectionId is inherited on expanded entries", async () => {
const providerId = "test-conn-provider-" + Date.now();
await seedSyncedModels(providerId, "conn-42", ["model-x"]);
const combo = makeCombo([
{ kind: "provider-wildcard", providerId, modelPattern: "*", connectionId: "conn-42" },
]);
const result = await expandProviderWildcardsInCombo(combo);
assert.equal(result.models.length, 1);
assert.equal((result.models[0] as any).connectionId, "conn-42");
});
test("expandProviderWildcardsInCombo: allowedConnectionIds is inherited", async () => {
const providerId = "test-acl-provider-" + Date.now();
await seedSyncedModels(providerId, "conn-a", ["model-m"]);
const combo = makeCombo([
{
kind: "provider-wildcard",
providerId,
modelPattern: "*",
allowedConnectionIds: ["conn-a", "conn-b"],
},
]);
const result = await expandProviderWildcardsInCombo(combo);
assert.equal(result.models.length, 1);
assert.deepEqual((result.models[0] as any).allowedConnectionIds, ["conn-a", "conn-b"]);
});
// ── Non-wildcard pass-through ─────────────────────────────────────────────────
test("expandProviderWildcardsInCombo: non-wildcard string entries pass through unchanged", async () => {
const combo = makeCombo(["anthropic/claude-opus-4", "openai/gpt-4o"]);
const result = await expandProviderWildcardsInCombo(combo);
assert.equal(result.models.length, 2);
assert.equal(result.models[0], "anthropic/claude-opus-4");
assert.equal(result.models[1], "openai/gpt-4o");
});
test("expandProviderWildcardsInCombo: mixed combo — wildcards expand, explicit entries preserved", async () => {
const providerId = "test-mixed-provider-" + Date.now();
await seedSyncedModels(providerId, "conn-1", ["model-1", "model-2"]);
const combo = makeCombo(["anthropic/claude-opus-4", `${providerId}/*`, "openai/gpt-4o"]);
const result = await expandProviderWildcardsInCombo(combo);
// anthropic/claude-opus-4, model-1, model-2, openai/gpt-4o
assert.equal(result.models.length, 4);
assert.equal(result.models[0], "anthropic/claude-opus-4");
assert.equal((result.models[1] as any).model, `${providerId}/model-1`);
assert.equal((result.models[2] as any).model, `${providerId}/model-2`);
assert.equal(result.models[3], "openai/gpt-4o");
});
// ── No models found — graceful fallback ──────────────────────────────────────
test("expandProviderWildcardsInCombo: keeps original entry when no models found for provider", async () => {
const combo = makeCombo(["nonexistent-provider-xyz/*"]);
const result = await expandProviderWildcardsInCombo(combo);
// Should not silently drop the step
assert.equal(result.models.length, 1);
assert.equal(result.models[0], "nonexistent-provider-xyz/*");
});
test("expandProviderWildcardsInCombo: keeps original entry when pattern matches nothing", async () => {
const providerId = "test-nomatch-provider-" + Date.now();
await seedSyncedModels(providerId, "conn-1", ["some-model"]);
const combo = makeCombo([`${providerId}/zzzz*`]);
const result = await expandProviderWildcardsInCombo(combo);
// Pattern matches nothing — original entry preserved
assert.equal(result.models.length, 1);
});
// ── Collection expansion ──────────────────────────────────────────────────────
test("expandProviderWildcardsInCollection: expands wildcards in every combo in the collection", async () => {
const p1 = "test-col-p1-" + Date.now();
const p2 = "test-col-p2-" + Date.now();
await seedSyncedModels(p1, "c1", ["m1", "m2"]);
await seedSyncedModels(p2, "c2", ["m3"]);
const combos = [
makeCombo([`${p1}/*`], "combo-a"),
makeCombo([`${p2}/*`, "openai/gpt-4o"], "combo-b"),
makeCombo(["anthropic/claude-opus-4"], "combo-c"), // no wildcard
];
const results = await expandProviderWildcardsInCollection(combos);
assert.equal(results[0].models.length, 2); // m1, m2
assert.equal(results[1].models.length, 2); // m3, gpt-4o
assert.equal(results[2].models.length, 1); // unchanged
assert.equal(results[2].models[0], "anthropic/claude-opus-4");
});
// ── Return identity when no wildcards ────────────────────────────────────────
test("expandProviderWildcardsInCombo: returns same object when no wildcards", async () => {
const combo = makeCombo(["openai/gpt-4o"]);
const result = await expandProviderWildcardsInCombo(combo);
// Same reference — no allocation when nothing to expand
assert.strictEqual(result, combo);
});
test("expandProviderWildcardsInCombo: returns same object for empty models array", async () => {
const combo = makeCombo([]);
const result = await expandProviderWildcardsInCombo(combo);
assert.strictEqual(result, combo);
});
// ── Multi-provider wildcard combo ─────────────────────────────────────────────
// Validates the primary use-case from issue #2562:
// a single combo that spans multiple providers, each expressed as a wildcard.
// e.g. freetheai/* + deepseek-web/* (or any other provider)
// The expanded model list is the ordered union of all providers' model catalogs.
test("expandProviderWildcardsInCombo: two provider wildcards expand independently and maintain order", async () => {
const p1 = "test-multi-p1-" + Date.now();
const p2 = "test-multi-p2-" + Date.now();
await seedSyncedModels(p1, "conn-1", ["fast-model", "smart-model"]);
await seedSyncedModels(p2, "conn-2", ["web-search-model", "reasoning-model"]);
// Simulate: freetheai/* + deepseek-web/*
const combo = makeCombo([`${p1}/*`, `${p2}/*`]);
const result = await expandProviderWildcardsInCombo(combo);
// Should have all 4 models, p1 first then p2 (insertion order preserved)
assert.equal(result.models.length, 4);
const models = result.models.map((e) => (e as any).model);
assert.ok(
models.indexOf(`${p1}/fast-model`) < models.indexOf(`${p2}/web-search-model`),
"p1 models should come before p2 models"
);
assert.ok(models.includes(`${p1}/fast-model`));
assert.ok(models.includes(`${p1}/smart-model`));
assert.ok(models.includes(`${p2}/web-search-model`));
assert.ok(models.includes(`${p2}/reasoning-model`));
});
test("expandProviderWildcardsInCombo: two providers with prefix filters", async () => {
const p1 = "test-prefix-p1-" + Date.now();
const p2 = "test-prefix-p2-" + Date.now();
await seedSyncedModels(p1, "conn-1", ["free-fast", "free-smart", "paid-pro"]);
await seedSyncedModels(p2, "conn-2", ["web-basic", "web-turbo", "local-model"]);
// Only free models from p1, only web models from p2
const combo = makeCombo([`${p1}/free*`, `${p2}/web*`]);
const result = await expandProviderWildcardsInCombo(combo);
assert.equal(result.models.length, 4); // free-fast, free-smart, web-basic, web-turbo
const models = result.models.map((e) => (e as any).model);
assert.ok(models.includes(`${p1}/free-fast`));
assert.ok(models.includes(`${p1}/free-smart`));
assert.ok(!models.includes(`${p1}/paid-pro`)); // filtered out
assert.ok(models.includes(`${p2}/web-basic`));
assert.ok(models.includes(`${p2}/web-turbo`));
assert.ok(!models.includes(`${p2}/local-model`)); // filtered out
});
test("expandProviderWildcardsInCombo: three providers mixed with explicit entries", async () => {
const p1 = "test-three-p1-" + Date.now();
const p2 = "test-three-p2-" + Date.now();
await seedSyncedModels(p1, "conn-1", ["m-a", "m-b"]);
await seedSyncedModels(p2, "conn-2", ["m-c"]);
// anchor explicit entry first, then two wildcards, then another explicit
const combo = makeCombo(["anthropic/claude-opus-4", `${p1}/*`, `${p2}/*`, "openai/gpt-4o"]);
const result = await expandProviderWildcardsInCombo(combo);
// anthropic + 2 (p1) + 1 (p2) + openai = 5
assert.equal(result.models.length, 5);
assert.equal(result.models[0], "anthropic/claude-opus-4");
assert.equal((result.models[1] as any).model, `${p1}/m-a`);
assert.equal((result.models[2] as any).model, `${p1}/m-b`);
assert.equal((result.models[3] as any).model, `${p2}/m-c`);
assert.equal(result.models[4], "openai/gpt-4o");
});

View File

@@ -0,0 +1,379 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import test, { describe, beforeEach } from "node:test";
import assert from "node:assert/strict";
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-combo-sel-conn-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
process.env.DATA_DIR = TEST_DATA_DIR;
const { handleComboChat } = await import("../../open-sse/services/combo.ts");
const {
recordModelLockoutFailure,
getModelLockoutInfo,
clearAllModelLockouts,
decayModelFailureCount,
} = await import("../../open-sse/services/accountFallback.ts");
const { recordProviderCooldown, isProviderInCooldown, recordProviderSuccess, clearCooldownState } =
await import("../../open-sse/services/providerCooldownTracker.ts");
const { resolveResilienceSettings } = await import("../../src/lib/resilience/settings.ts");
const core = await import("../../src/lib/db/core.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const settings = resolveResilienceSettings({
resilienceSettings: {
providerCooldown: {
enabled: true,
minRetryCooldownMs: 5000,
maxRetryCooldownMs: 300000,
},
},
});
function createLog() {
return {
info: (tag: any, msg: any) => console.log(`[INFO][${tag}] ${msg}`),
warn: (tag: any, msg: any) => console.log(`[WARN][${tag}] ${msg}`),
error: (tag: any, msg: any) => console.log(`[ERROR][${tag}] ${msg}`),
debug: (tag: any, msg: any) => console.log(`[DEBUG][${tag}] ${msg}`),
};
}
async function cleanupTestDataDir() {
let lastError;
for (let attempt = 0; attempt < 5; attempt += 1) {
try {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
return;
} catch (error: any) {
lastError = error;
await new Promise((resolve) => setTimeout(resolve, 25));
}
}
if (lastError) {
throw lastError;
}
}
test.after(async () => {
await cleanupTestDataDir();
process.env.DATA_DIR = ORIGINAL_DATA_DIR;
});
beforeEach(async () => {
clearAllModelLockouts();
clearCooldownState();
settingsDb.clearAllLKGP();
});
describe("combo selected connection success handling", () => {
test("priority strategy correctly extracts dynamic connection ID from success response headers and decays lockout, resets provider cooldown, and updates LKGP", async () => {
const comboName = "test-combo-priority";
const modelStr = "openai/gpt-4";
const provider = "openai";
const dynamicConnId = "conn-dynamic-123";
await providersDb.createProviderConnection({
provider,
authType: "apikey",
name: "OpenAI Test",
apiKey: "sk-test",
});
// 1. Populate lockout failure count = 4
recordModelLockoutFailure(
provider,
dynamicConnId,
"gpt-4",
"rate_limit_exceeded",
429,
120_000,
null,
{ exactCooldownMs: 60_000 }
);
for (let i = 0; i < 3; i++) {
recordModelLockoutFailure(
provider,
dynamicConnId,
"gpt-4",
"rate_limit_exceeded",
429,
120_000,
null,
{ exactCooldownMs: 60_000 }
);
}
// Verify initial failureCount is 4
const initialLockout = getModelLockoutInfo(provider, dynamicConnId, "gpt-4");
assert.equal(initialLockout?.failureCount, 4);
// 2. Record provider cooldown
recordProviderCooldown(provider, dynamicConnId, settings);
assert.ok(isProviderInCooldown(provider, dynamicConnId, settings));
// 3. Invoke handleComboChat
const result = await handleComboChat({
body: { stream: false },
combo: {
name: comboName,
strategy: "priority",
models: [modelStr],
config: { maxRetries: 0, concurrencyPerModel: 1, queueTimeoutMs: 1000 },
},
handleSingleModel: async () => {
return new Response(JSON.stringify({ choices: [{ message: { content: "ok" } }] }), {
status: 200,
headers: {
"content-type": "application/json",
"X-OmniRoute-Selected-Connection-Id": dynamicConnId,
},
});
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
});
assert.equal(result.ok, true);
// 4. Assertions
// A. Dynamic connection-level failure count decay (halved to 2)
const decayCheck = decayModelFailureCount(provider, dynamicConnId, "gpt-4");
assert.equal(
decayCheck.newFailureCount,
1,
"failure count should decay from 4 to 2, and now to 1"
);
// B. Dynamic connection-level provider success tracking (not in cooldown anymore)
assert.equal(
isProviderInCooldown(provider, dynamicConnId, settings),
false,
"provider cooldown should be cleared on success"
);
// C. Correct LKGP record updated with the dynamic connection ID (setLKGP is called)
let persisted: any = null;
for (let i = 0; i < 20; i++) {
persisted = await settingsDb.getLKGP(comboName, comboName);
if (persisted?.connectionId === dynamicConnId) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
assert.equal(persisted?.provider, provider);
assert.equal(
persisted?.connectionId,
dynamicConnId,
"LKGP connectionId must be the dynamic connection ID"
);
});
test("priority strategy with lowercase selected connection ID header correctly decays lockout, resets provider cooldown, and updates LKGP", async () => {
const comboName = "test-combo-priority-lc";
const modelStr = "openai/gpt-4";
const provider = "openai";
const dynamicConnId = "conn-dynamic-123-lc";
await providersDb.createProviderConnection({
provider,
authType: "apikey",
name: "OpenAI Test LC",
apiKey: "sk-test",
});
// 1. Populate lockout failure count = 4
recordModelLockoutFailure(
provider,
dynamicConnId,
"gpt-4",
"rate_limit_exceeded",
429,
120_000,
null,
{ exactCooldownMs: 60_000 }
);
for (let i = 0; i < 3; i++) {
recordModelLockoutFailure(
provider,
dynamicConnId,
"gpt-4",
"rate_limit_exceeded",
429,
120_000,
null,
{ exactCooldownMs: 60_000 }
);
}
// Verify initial failureCount is 4
const initialLockout = getModelLockoutInfo(provider, dynamicConnId, "gpt-4");
assert.equal(initialLockout?.failureCount, 4);
// 2. Record provider cooldown
recordProviderCooldown(provider, dynamicConnId, settings);
assert.ok(isProviderInCooldown(provider, dynamicConnId, settings));
// 3. Invoke handleComboChat
const result = await handleComboChat({
body: { stream: false },
combo: {
name: comboName,
strategy: "priority",
models: [modelStr],
config: { maxRetries: 0, concurrencyPerModel: 1, queueTimeoutMs: 1000 },
},
handleSingleModel: async () => {
return new Response(JSON.stringify({ choices: [{ message: { content: "ok" } }] }), {
status: 200,
headers: {
"content-type": "application/json",
"x-omniroute-selected-connection-id": dynamicConnId,
},
});
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
});
assert.equal(result.ok, true);
// 4. Assertions
const decayCheck = decayModelFailureCount(provider, dynamicConnId, "gpt-4");
assert.equal(
decayCheck.newFailureCount,
1,
"failure count should decay from 4 to 2, and now to 1"
);
assert.equal(
isProviderInCooldown(provider, dynamicConnId, settings),
false,
"provider cooldown should be cleared on success"
);
let persisted: any = null;
for (let i = 0; i < 20; i++) {
persisted = await settingsDb.getLKGP(comboName, comboName);
if (persisted?.connectionId === dynamicConnId) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
assert.equal(persisted?.provider, provider);
assert.equal(
persisted?.connectionId,
dynamicConnId,
"LKGP connectionId must be the dynamic connection ID"
);
});
test("round-robin strategy correctly extracts dynamic connection ID from success response headers and decays lockout, resets provider cooldown, and updates LKGP", async () => {
const comboName = "test-combo-rr";
const modelStr = "openai/gpt-4";
const provider = "openai";
const dynamicConnId = "conn-dynamic-123-rr";
await providersDb.createProviderConnection({
provider,
authType: "apikey",
name: "OpenAI Test RR",
apiKey: "sk-test",
});
// 1. Populate lockout failure count = 4
recordModelLockoutFailure(
provider,
dynamicConnId,
"gpt-4",
"rate_limit_exceeded",
429,
120_000,
null,
{ exactCooldownMs: 60_000 }
);
for (let i = 0; i < 3; i++) {
recordModelLockoutFailure(
provider,
dynamicConnId,
"gpt-4",
"rate_limit_exceeded",
429,
120_000,
null,
{ exactCooldownMs: 60_000 }
);
}
// Verify initial failureCount is 4
const initialLockout = getModelLockoutInfo(provider, dynamicConnId, "gpt-4");
assert.equal(initialLockout?.failureCount, 4);
// 2. Record provider cooldown
recordProviderCooldown(provider, dynamicConnId, settings);
assert.ok(isProviderInCooldown(provider, dynamicConnId, settings));
// 3. Invoke handleComboChat with round-robin strategy
const result = await handleComboChat({
body: { stream: false },
combo: {
name: comboName,
strategy: "round-robin",
models: [modelStr],
config: { maxRetries: 0, concurrencyPerModel: 1, queueTimeoutMs: 1000 },
},
handleSingleModel: async () => {
return new Response(JSON.stringify({ choices: [{ message: { content: "ok" } }] }), {
status: 200,
headers: {
"content-type": "application/json",
"X-OmniRoute-Selected-Connection-Id": dynamicConnId,
},
});
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
});
assert.equal(result.ok, true);
// 4. Assertions
const decayCheck = decayModelFailureCount(provider, dynamicConnId, "gpt-4");
assert.equal(
decayCheck.newFailureCount,
1,
"failure count should decay from 4 to 2, and now to 1"
);
assert.equal(
isProviderInCooldown(provider, dynamicConnId, settings),
false,
"provider cooldown should be cleared on success"
);
let persisted: any = null;
for (let i = 0; i < 20; i++) {
persisted = await settingsDb.getLKGP(comboName, comboName);
if (persisted?.connectionId === dynamicConnId) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
assert.equal(persisted?.provider, provider);
assert.equal(
persisted?.connectionId,
dynamicConnId,
"LKGP connectionId must be the dynamic connection ID"
);
});
});

View File

@@ -13,6 +13,8 @@ const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
process.env.DATA_DIR = TEST_DATA_DIR;
const { handleComboChat, preScreenTargets } = await import("../../open-sse/services/combo.ts");
const { weightedStickyTargets, rrStickyTargets } =
await import("../../open-sse/services/combo/rrState.ts");
const core = await import("../../src/lib/db/core.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const { resetAllComboMetrics } = await import("../../open-sse/services/comboMetrics.ts");
@@ -69,6 +71,8 @@ test.beforeEach(async () => {
resetAllSemaphores();
_resetAllDecks();
clearSessions();
weightedStickyTargets.clear();
rrStickyTargets.clear();
await cleanupTestDataDir();
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
await settingsDb.resetAllPricing();
@@ -80,6 +84,8 @@ test.after(async () => {
resetAllCircuitBreakers();
resetAllSemaphores();
_resetAllDecks();
weightedStickyTargets.clear();
rrStickyTargets.clear();
settingsDb.clearAllLKGP();
if (ORIGINAL_DATA_DIR === undefined) {
delete process.env.DATA_DIR;
@@ -385,6 +391,172 @@ test("round-robin sticky batching fallback success becomes sticky target", async
assert.deepEqual(calls, ["openai/a", "claude/b", "claude/b", "gemini/c", "gemini/c"]);
});
test("weighted sticky limit batches successful weighted selections", async () => {
const calls: string[] = [];
const secureRandom = await import("../../src/shared/utils/secureRandom.ts");
// First draw targets openai/a (cumulative weight < 0.5), second targets claude/b
// (cumulative weight >= 0.5). With sticky=2, openai/a runs for 2 calls then claude/b for 2.
secureRandom._setSecureRandomFloatSource(() => (calls.length < 2 ? 0.1 : 0.9));
try {
const combo = {
name: "weighted-sticky-batches",
strategy: "weighted",
models: [
{ model: "openai/a", weight: 50 },
{ model: "claude/b", weight: 50 },
],
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0, stickyWeightedLimit: 2 },
};
for (let i = 0; i < 4; 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: null,
allCombos: null,
});
assert.equal(result.ok, true);
}
} finally {
secureRandom._setSecureRandomFloatSource(null);
}
assert.deepEqual(calls, ["openai/a", "openai/a", "claude/b", "claude/b"]);
});
test("weighted sticky limit clears unavailable sticky steps before sampling", async () => {
const calls: string[] = [];
const step0Key = "weighted-sticky-clears-model-1-openai-a";
const step1Key = "weighted-sticky-clears-model-2-claude-b";
weightedStickyTargets.set("weighted-sticky-clears", { executionKey: step0Key, successCount: 1 });
const combo = {
name: "weighted-sticky-clears",
strategy: "weighted",
models: [
{ model: "openai/a", weight: 50 },
{ model: "claude/b", weight: 50 },
],
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0, stickyWeightedLimit: 10 },
};
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async (modelStr: string) => modelStr !== "openai/a",
log: createLog(),
settings: null,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["claude/b"]);
assert.equal(weightedStickyTargets.get("weighted-sticky-clears")?.executionKey, step1Key);
});
test("weighted sticky limit follows fallback success", async () => {
const calls: string[] = [];
const secureRandom = await import("../../src/shared/utils/secureRandom.ts");
// Always draw claude/b (the failing target). The combo retries within claude/b,
// exhausts its leaves, falls through to openai/a, succeeds, and sticky migrates.
secureRandom._setSecureRandomFloatSource(() => 0.9);
try {
const combo = {
name: "weighted-sticky-fallback-success",
strategy: "weighted",
models: [
{ model: "openai/a", weight: 50 },
{ model: "claude/b", weight: 50 },
],
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0, stickyWeightedLimit: 2 },
};
for (let i = 0; i < 4; i += 1) {
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return modelStr === "claude/b" ? errorResponse(503, "b is down") : okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: null,
});
assert.equal(result.ok, true);
}
} finally {
secureRandom._setSecureRandomFloatSource(null);
}
assert.deepEqual(calls, ["claude/b", "openai/a", "openai/a", "claude/b", "openai/a", "openai/a"]);
});
test("weighted sticky keeps a top-level step if one nested leaf remains available", async () => {
const calls: string[] = [];
const step0Key = "weighted-nested-sticky-ref-1-minimax";
weightedStickyTargets.set("weighted-nested-sticky", { executionKey: step0Key, successCount: 1 });
const combo = {
name: "weighted-nested-sticky",
strategy: "weighted",
models: [{ kind: "combo-ref", comboName: "minimax", weight: 100 }],
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0, stickyWeightedLimit: 10 },
};
const allCombos = [
combo,
{ name: "minimax", strategy: "priority", models: ["ollama/m3", "oc/m3"] },
];
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async (modelStr: string) => modelStr !== "ollama/m3",
log: createLog(),
settings: null,
allCombos,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["oc/m3"]);
assert.equal(weightedStickyTargets.get("weighted-nested-sticky")?.executionKey, step0Key);
});
test("round-robin sticky clears unavailable sticky target before rotation", async () => {
const calls: string[] = [];
const step0Key = "rr-sticky-clears-unavailable-model-1-openai-a";
rrStickyTargets.set("rr-sticky-clears-unavailable", { executionKey: step0Key, successCount: 1 });
const combo = {
name: "rr-sticky-clears-unavailable",
strategy: "round-robin",
models: ["openai/a", "claude/b", "gemini/c"],
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0, stickyRoundRobinLimit: 10 },
};
const result = await handleComboChat({
body: {},
combo,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async (modelStr: string) => modelStr !== "openai/a",
log: createLog(),
settings: null,
allCombos: null,
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["claude/b"]);
assert.equal(
rrStickyTargets.get("rr-sticky-clears-unavailable")?.executionKey,
"rr-sticky-clears-unavailable-model-2-claude-b"
);
});
test("strict-random survives a stale deck entry after a target is removed", async () => {
const comboTwoTargets = {
name: "strict-random-stale",
@@ -428,6 +600,164 @@ test("strict-random survives a stale deck entry after a target is removed", asyn
assert.deepEqual(calls, ["claude/sonnet"]);
});
test("nested execute mode treats combo refs as black-box priority targets", async () => {
const calls: string[] = [];
const outer = {
name: "outer-execute-priority",
strategy: "priority",
models: [{ kind: "combo-ref", comboName: "child-priority" }, "gemini/fallback"],
config: { nestedComboMode: "execute", maxRetries: 0, retryDelayMs: 0 },
};
const child = {
name: "child-priority",
strategy: "priority",
models: ["openai/a", "claude/b"],
config: { maxRetries: 0, retryDelayMs: 0 },
};
const result = await handleComboChat({
body: {},
combo: outer,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: [outer, child],
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/a"]);
});
test("nested execute mode falls back to the next parent target after a child combo fails", async () => {
const calls: string[] = [];
const outer = {
name: "outer-execute-fallback",
strategy: "priority",
models: [{ kind: "combo-ref", comboName: "child-down" }, "gemini/fallback"],
config: { nestedComboMode: "execute", maxRetries: 0, retryDelayMs: 0 },
};
const child = {
name: "child-down",
strategy: "priority",
models: ["openai/a", "claude/b"],
config: { maxRetries: 0, retryDelayMs: 0 },
};
const result = await handleComboChat({
body: {},
combo: outer,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return modelStr === "gemini/fallback" ? okResponse() : errorResponse(503);
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: [outer, child],
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/a", "claude/b", "gemini/fallback"]);
});
test("nested execute mode supports 3-level nesting", async () => {
const calls: string[] = [];
const a = {
name: "level-a",
strategy: "priority",
models: [{ kind: "combo-ref", comboName: "level-b" }],
config: { nestedComboMode: "execute", maxRetries: 0 },
};
const b = {
name: "level-b",
strategy: "priority",
models: [{ kind: "combo-ref", comboName: "level-c" }],
config: { nestedComboMode: "execute", maxRetries: 0 },
};
const c = {
name: "level-c",
strategy: "priority",
models: ["openai/leaf"],
config: { maxRetries: 0 },
};
const result = await handleComboChat({
body: {},
combo: a,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: [a, b, c],
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/leaf"]);
});
test("nested execute mode fails cleanly on a circular combo reference", async () => {
const a = {
name: "cycle-a",
strategy: "priority",
models: [{ kind: "combo-ref", comboName: "cycle-b" }],
config: { nestedComboMode: "execute", maxRetries: 0 },
};
const b = {
name: "cycle-b",
strategy: "priority",
models: [{ kind: "combo-ref", comboName: "cycle-a" }],
config: { nestedComboMode: "execute", maxRetries: 0 },
};
await assert.rejects(
async () =>
handleComboChat({
body: {},
combo: a,
handleSingleModel: async () => okResponse(),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: [a, b],
}),
/[Cc]ircular/
);
});
test("flatten mode (default) expands nested combo refs into leaf targets", async () => {
const calls: string[] = [];
const outer = {
name: "outer-flatten-default",
strategy: "priority",
models: [{ kind: "combo-ref", comboName: "child-flat" }],
config: { maxRetries: 0 },
};
const child = {
name: "child-flat",
strategy: "priority",
models: ["openai/a", "claude/b"],
config: { maxRetries: 0 },
};
const result = await handleComboChat({
body: {},
combo: outer,
handleSingleModel: async (_body: any, modelStr: string) => {
calls.push(modelStr);
return modelStr === "openai/a" ? errorResponse(503) : okResponse();
},
isModelAvailable: async () => true,
log: createLog(),
settings: null,
allCombos: [outer, child],
});
assert.equal(result.ok, true);
assert.deepEqual(calls, ["openai/a", "claude/b"]);
});
test("unknown strategy value normalizes to priority order", async () => {
const calls: string[] = [];
const result = await handleComboChat({

View File

@@ -0,0 +1,123 @@
// tests/unit/combo/auto-quota-cutoff.test.ts
// Regression coverage for auto routing quota cutoff: hard-cutoff candidates must be
// removed before scoring/fallback so an exhausted account cannot win by latency/model fit.
import { test } from "node:test";
import assert from "node:assert/strict";
import { scoreAutoTargets } from "../../../open-sse/services/combo/autoStrategy.ts";
import type {
AutoProviderCandidate,
ResolvedComboTarget,
} from "../../../open-sse/services/combo/types.ts";
import type { ScoringWeights } from "../../../open-sse/services/autoCombo/scoring.ts";
const latencyOnlyWeights: ScoringWeights = {
quota: 0,
health: 0,
costInv: 0,
latencyInv: 1,
taskFit: 0,
stability: 0,
tierPriority: 0,
tierAffinity: 0,
specificityMatch: 0,
contextAffinity: 0,
resetWindowAffinity: 0,
connectionDensity: 0,
};
function target(provider: string, model: string, connectionId: string): ResolvedComboTarget {
return {
kind: "model",
stepId: `${provider}-${model}-${connectionId}`,
executionKey: `${provider}/${model}@${connectionId}`,
modelStr: `${provider}/${model}`,
provider,
providerId: null,
connectionId,
} as ResolvedComboTarget;
}
function candidate(
provider: string,
model: string,
connectionId: string,
overrides: Partial<AutoProviderCandidate> = {}
): AutoProviderCandidate {
return {
provider,
model,
stepId: `${provider}-${model}-${connectionId}`,
executionKey: `${provider}/${model}@${connectionId}`,
modelStr: `${provider}/${model}`,
connectionId,
quotaRemaining: 100,
quotaTotal: 100,
circuitBreakerState: "CLOSED",
costPer1MTokens: 1,
p95LatencyMs: 1000,
latencyStdDev: 10,
errorRate: 0,
resetWindowAffinity: 0.5,
connectionPoolSize: 1,
...overrides,
} as AutoProviderCandidate;
}
test("auto scoring skips GLM when its 2% remaining quota hit the hard cutoff", () => {
const targets = [target("glm", "glm-5.2", "glm-empty"), target("mcode", "mimo-auto", "mcode-ok")];
const ranked = scoreAutoTargets(
targets,
[
candidate("glm", "glm-5.2", "glm-empty", {
quotaRemaining: 2,
p95LatencyMs: 10,
quotaCutoffBlocked: true,
quotaCutoffReason: "quota_exhausted",
}),
candidate("mcode", "mimo-auto", "mcode-ok", {
quotaRemaining: 100,
p95LatencyMs: 5000,
}),
],
"coding",
latencyOnlyWeights
);
assert.equal(ranked.length, 1);
assert.equal(ranked[0]?.target.provider, "mcode");
assert.equal(ranked[0]?.target.connectionId, "mcode-ok");
});
test("blocked quota candidates are not included in the scoring pool", () => {
const targets = [
target("fast", "healthy", "fast-ok"),
target("slow", "healthy", "slow-ok"),
target("glm", "glm-5.2", "glm-empty"),
];
const ranked = scoreAutoTargets(
targets,
[
candidate("fast", "healthy", "fast-ok", { p95LatencyMs: 100 }),
candidate("slow", "healthy", "slow-ok", { p95LatencyMs: 1000 }),
candidate("glm", "glm-5.2", "glm-empty", {
quotaRemaining: 0,
p95LatencyMs: 10000,
quotaCutoffBlocked: true,
quotaCutoffReason: "quota_exhausted",
}),
],
"coding",
latencyOnlyWeights
);
assert.deepEqual(
ranked.map((entry) => entry.target.provider),
["fast", "slow"]
);
const slowScore = ranked.find((entry) => entry.target.provider === "slow")?.score;
assert.equal(
slowScore,
0,
"the blocked GLM latency must not inflate surviving candidates' scores"
);
});

View File

@@ -294,6 +294,58 @@ test("Command Code executor surfaces upstream and streamed errors", async () =>
}, /boom/);
});
test("Command Code executor caps max_tokens to the registered per-model limit (GLM-5.x)", async () => {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
// GLM-5 and GLM-5.1 are registered with maxOutputTokens: 131072.
// Without per-model capping, the upstream rejects with
// "限制数值范围[1,131072]".
await getExecutor("command-code").execute({
model: "zai-org/GLM-5.1",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
assert.equal(calls[0].body.params.max_tokens, 131072);
});
test("Command Code executor caps max_tokens to the registered per-model limit (DeepSeek v4)", async () => {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
// DeepSeek v4 pro is registered with maxOutputTokens: 384000.
await getExecutor("command-code").execute({
model: "deepseek/deepseek-v4-pro",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }] },
});
assert.equal(calls[0].body.params.max_tokens, 384000);
});
test("Command Code executor honors a smaller client-provided max_tokens under the per-model cap", async () => {
const calls: FetchCall[] = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
};
await getExecutor("command-code").execute({
model: "zai-org/GLM-5.1",
stream: false,
credentials: { apiKey: "cc_test_key" },
body: { messages: [{ role: "user", content: "Hi" }], max_tokens: 2048 },
});
assert.equal(calls[0].body.params.max_tokens, 2048);
});
test("Command Code non-stream aggregation throws when the final error event lacks a trailing newline", async () => {
globalThis.fetch = async () =>
new Response(

View File

@@ -0,0 +1,50 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
selectCompressionStrategy,
selectCompressionPlan,
activeComboResolves,
} from "../../../open-sse/services/compression/strategySelector.ts";
import {
DEFAULT_COMPRESSION_CONFIG,
type CompressionConfig,
} from "../../../open-sse/services/compression/types.ts";
const combos = { c1: [{ engine: "rtk", intensity: "standard" }, { engine: "caveman", intensity: "full" }] };
function cfg(overrides: Partial<CompressionConfig> = {}): CompressionConfig {
return { ...DEFAULT_COMPRESSION_CONFIG, enabled: true, ...overrides };
}
describe("active named combo resolution (Phase 2)", () => {
it("activeComboId + combo present => that combo's stacked pipeline (regardless of enginesExplicit)", () => {
const config = cfg({ activeComboId: "c1", enginesExplicit: false });
const plan = selectCompressionPlan(config, null, 0, undefined, undefined, combos);
assert.equal(plan.mode, "stacked");
assert.deepEqual(plan.stackedPipeline, combos.c1);
});
it("activeComboId null => falls through to derived default (not the combo)", () => {
const config = cfg({ activeComboId: null, enginesExplicit: true, engines: { rtk: { enabled: true } } });
assert.equal(selectCompressionStrategy(config, null, 0, undefined, undefined, combos), "rtk");
});
it("activeComboId set but combo missing => graceful fall-through to default", () => {
const config = cfg({ activeComboId: "ghost", defaultMode: "lite", enginesExplicit: false });
assert.equal(selectCompressionStrategy(config, null, 0, undefined, undefined, combos), "lite");
});
it("routing-combo override wins over the active profile", () => {
const config = cfg({ activeComboId: "c1", comboOverrides: { "my-combo": "off" } });
assert.equal(selectCompressionStrategy(config, "my-combo", 0, undefined, undefined, combos), "off");
});
it("active profile wins over auto-trigger", () => {
const config = cfg({ activeComboId: "c1", autoTriggerTokens: 1000, autoTriggerMode: "aggressive" });
assert.equal(selectCompressionStrategy(config, null, 5000, undefined, undefined, combos), "stacked");
});
});
describe("activeComboResolves", () => {
it("true only when activeComboId is set AND present in combos", () => {
assert.equal(activeComboResolves(cfg({ activeComboId: "c1" }), combos), true);
assert.equal(activeComboResolves(cfg({ activeComboId: "ghost" }), combos), false);
assert.equal(activeComboResolves(cfg({ activeComboId: null }), combos), false);
});
});

View File

@@ -0,0 +1,43 @@
import { test, after } from "node:test";
import assert from "node:assert/strict";
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-combo-"));
const ORIGINAL = process.env.DATA_DIR;
process.env.DATA_DIR = TEST_DATA_DIR;
const { getDbInstance, resetDbInstance } = await import("../../../src/lib/db/core.ts");
const combosDb = await import("../../../src/lib/db/compressionCombos.ts");
const { updateCompressionSettings } = await import("../../../src/lib/db/compression.ts");
const { selectCompressionPlan } = await import("../../../open-sse/services/compression/strategySelector.ts");
const { DEFAULT_COMPRESSION_CONFIG } = await import("../../../open-sse/services/compression/types.ts");
after(() => {
resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
if (ORIGINAL === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = ORIGINAL;
});
test("an active named combo's pipeline is what selectCompressionPlan resolves, fed from the DB combos map", async () => {
resetDbInstance();
getDbInstance();
const created = combosDb.createCompressionCombo({
name: "RTK only",
pipeline: [{ engine: "rtk", intensity: "standard" }],
});
await updateCompressionSettings({ enabled: true, activeComboId: created.id });
// Mirror chatCore's load: build the combos map from the DB.
const combos = Object.fromEntries(combosDb.listCompressionCombos().map((c) => [c.id, c.pipeline]));
const config = { ...DEFAULT_COMPRESSION_CONFIG, enabled: true, activeComboId: created.id };
const plan = selectCompressionPlan(config, null, 5000, undefined, undefined, combos);
assert.equal(plan.mode, "stacked");
assert.deepEqual(plan.stackedPipeline, [{ engine: "rtk", intensity: "standard" }]);
// Setting activeComboId did NOT change which combo is is_default (legacy untouched).
const def = combosDb.getDefaultCompressionCombo();
assert.notEqual(def?.id, created.id);
});

View File

@@ -0,0 +1,96 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { applyRtkCompression } from "../../../open-sse/services/compression/engines/rtk/index.ts";
// Regression: a tool_result block the client marked with `cache_control` is an explicit
// prompt-cache breakpoint — the upstream caches the prefix up to and INCLUDING that block.
// RTK (since the Anthropic tool_result support added in v3.8.32) rewrote the block's inner
// content, so the cached prefix no longer matched byte-for-byte → guaranteed cache miss at
// that breakpoint (reported as "provider cache again broken" after upgrading). Compression
// must never alter a block carrying `cache_control`. Mirrors #3936's invariant: under
// caching, only ever preserve more of the prefix — never rewrite a declared breakpoint.
const GIT_STATUS = `On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/app.ts
modified: src/lib/util.ts
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/new.ts
no changes added to commit (use "git add" and/or "git commit -a")`;
function anthropicBody(toolResultBlock: Record<string, unknown>) {
return {
model: "anthropic/claude-sonnet-4",
messages: [
{ role: "user", content: "run git status" },
{
role: "assistant",
content: [
{ type: "tool_use", id: "toolu_1", name: "bash", input: { command: "git status" } },
],
},
{ role: "user", content: [toolResultBlock] },
],
} as Record<string, unknown>;
}
// Pull the single tool_result block out of message[2].content[0] with a typed shape (no `any`).
function toolResultBlock(body: Record<string, unknown>): Record<string, unknown> {
const messages = body.messages as Array<{ content: Array<Record<string, unknown>> }>;
return messages[2].content[0];
}
test("RTK preserves a tool_result block carrying cache_control byte-for-byte (string content)", () => {
const block = {
type: "tool_result",
tool_use_id: "toolu_1",
content: GIT_STATUS,
cache_control: { type: "ephemeral" },
};
const body = anthropicBody(block);
const before = JSON.stringify(toolResultBlock(body));
const res = applyRtkCompression(body, { config: { enabled: true, applyToToolResults: true } });
const after = JSON.stringify(toolResultBlock(res.body as Record<string, unknown>));
// The marked breakpoint block must be untouched — content identical, marker intact.
assert.equal(after, before, "cache_control-marked tool_result must not be rewritten");
assert.deepEqual(toolResultBlock(res.body as Record<string, unknown>).cache_control, { type: "ephemeral" });
});
test("RTK preserves an inner text sub-block carrying cache_control (array content)", () => {
const block = {
type: "tool_result",
tool_use_id: "toolu_1",
content: [{ type: "text", text: GIT_STATUS, cache_control: { type: "ephemeral" } }],
};
const body = anthropicBody(block);
const before = JSON.stringify(toolResultBlock(body));
const res = applyRtkCompression(body, { config: { enabled: true, applyToToolResults: true } });
const after = JSON.stringify(toolResultBlock(res.body as Record<string, unknown>));
assert.equal(after, before, "cache_control-marked text sub-block must not be rewritten");
});
test("RTK still compresses tool_result blocks WITHOUT cache_control (no over-protection)", () => {
const block = {
type: "tool_result",
tool_use_id: "toolu_1",
content: GIT_STATUS,
};
const body = anthropicBody(block);
const res = applyRtkCompression(body, { config: { enabled: true, applyToToolResults: true } });
const after = toolResultBlock(res.body as Record<string, unknown>).content as string;
assert.equal(res.compressed, true, "unmarked tool_result should still compress");
assert.ok(!after.includes('(use "git add'), "hint lines should be dropped when uncached");
});

View File

@@ -0,0 +1,158 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
newStreamCtx,
processFrame,
isComposerModel,
visibleComposerContentFromThinking,
composerReasoningRemainder,
type StreamCtx,
} from "../../open-sse/executors/cursor";
// ─── Wire-format helpers (mirror cursor-streaming.test.ts) ────────────────────
function v(n: number): Buffer {
const out: number[] = [];
while (n > 0x7f) {
out.push((n & 0x7f) | 0x80);
n >>>= 7;
}
out.push(n);
return Buffer.from(out);
}
function tag(field: number, wireType: number): Buffer {
return v((field << 3) | wireType);
}
function lenPrefixed(field: number, payload: Buffer): Buffer {
return Buffer.concat([tag(field, 2), v(payload.length), payload]);
}
// AgentServerMessage { interaction_update (1): { thinking_delta (4): { text (1): str } } }
function buildThinkingDeltaPayload(text: string): Buffer {
const tdu = lenPrefixed(1, Buffer.from(text, "utf8"));
const iu = lenPrefixed(4, tdu);
return lenPrefixed(1, iu);
}
function parseSSE(text: string): Array<Record<string, unknown>> {
return text
.split("\n\n")
.filter((c) => c.startsWith("data: "))
.map((c) => c.slice("data: ".length))
.filter((d) => d !== "[DONE]")
.map((d) => JSON.parse(d));
}
// ─── Pure helpers ────────────────────────────────────────────────────────────
test("isComposerModel matches composer + composer-* (case-insensitive, vendor prefix tolerated)", () => {
assert.equal(isComposerModel("composer"), true);
assert.equal(isComposerModel("composer-2.5"), true);
assert.equal(isComposerModel("composer-2.5-fast"), true);
assert.equal(isComposerModel("cu/composer-2.5"), true);
assert.equal(isComposerModel("CURSOR/Composer-2.5"), true);
assert.equal(isComposerModel("gpt-5.3-codex"), false);
assert.equal(isComposerModel("claude-4-sonnet"), false);
assert.equal(isComposerModel("composer2"), false);
assert.equal(isComposerModel(""), false);
});
test("visibleComposerContentFromThinking returns suffix after last </think> (trim-start)", () => {
assert.equal(
visibleComposerContentFromThinking("private reasoning</think>OK"),
"OK"
);
assert.equal(
visibleComposerContentFromThinking("a</think>b</think> final"),
"final"
);
assert.equal(visibleComposerContentFromThinking("no marker yet"), "");
assert.equal(visibleComposerContentFromThinking(""), "");
assert.equal(visibleComposerContentFromThinking("ends with</think>"), "");
});
test("composerReasoningRemainder returns only the hidden portion before last </think>", () => {
assert.equal(
composerReasoningRemainder("private reasoning</think>OK"),
"private reasoning"
);
assert.equal(
composerReasoningRemainder("just hidden, no marker"),
"just hidden, no marker"
);
assert.equal(composerReasoningRemainder(""), "");
});
// ─── Composer thinking handling via processFrame ─────────────────────────────
test("Composer streaming: emits visible suffix after </think> as content deltas; hidden never leaks as content", () => {
const chunks: string[] = [];
const ctx: StreamCtx = newStreamCtx("composer-2.5-fast", (c) => chunks.push(c));
processFrame(buildThinkingDeltaPayload("private reasoning"), ctx, new Set());
processFrame(
buildThinkingDeltaPayload(" that must not leak</think>O"),
ctx,
new Set()
);
processFrame(buildThinkingDeltaPayload("K"), ctx, new Set());
const sseText = chunks.join("");
const events = parseSSE(sseText);
const content = events
.map((e) => {
const choices = (e as { choices?: Array<{ delta?: { content?: string } }> }).choices;
return choices?.[0]?.delta?.content ?? "";
})
.join("");
assert.equal(content, "OK");
// Aggregated ctx.totalText must mirror the visible content so the
// non-streaming aggregator surfaces it as message.content unchanged.
assert.equal(ctx.totalText, "OK");
// Composer must NOT emit reasoning_content for the visible suffix portion
// — the hidden reasoning may still appear as reasoning_content deltas, but
// the literal post-</think> text must never appear as reasoning_content.
const reasoningStream = events
.map((e) => {
const choices = (e as { choices?: Array<{ delta?: { reasoning_content?: string } }> })
.choices;
return choices?.[0]?.delta?.reasoning_content ?? "";
})
.join("");
assert.ok(
!reasoningStream.includes("OK"),
"visible suffix must not be duplicated into reasoning_content"
);
});
test("Composer non-streaming aggregation: thinking with </think> populates totalText with visible suffix", () => {
const chunks: string[] = [];
const ctx: StreamCtx = newStreamCtx("cu/composer-2.5", (c) => chunks.push(c));
processFrame(
buildThinkingDeltaPayload("private reasoning that must not leak</think>OK"),
ctx,
new Set()
);
assert.equal(ctx.totalText, "OK");
});
test("Non-Composer model: thinking field stays in reasoning_content (unchanged contract)", () => {
const chunks: string[] = [];
const ctx: StreamCtx = newStreamCtx("gpt-5.3-codex", (c) => chunks.push(c));
processFrame(
buildThinkingDeltaPayload("hidden</think>SHOULD_NOT_APPEAR"),
ctx,
new Set()
);
const sseText = chunks.join("");
assert.ok(sseText.includes("reasoning_content"), "reasoning_content delta missing");
const events = parseSSE(sseText);
const content = events
.map((e) => {
const choices = (e as { choices?: Array<{ delta?: { content?: string } }> }).choices;
return choices?.[0]?.delta?.content ?? "";
})
.join("");
assert.equal(content, "", "non-Composer must not surface thinking as content");
assert.equal(ctx.totalText, "", "non-Composer must not populate totalText from thinking");
});

View File

@@ -415,7 +415,10 @@ test("local sqlite configuration enables WAL and sane pragmas", serial, async ()
const db = core.getDbInstance();
assert.equal(db.pragma("journal_mode", { simple: true }), "wal");
assert.equal(db.pragma("busy_timeout", { simple: true }), 5000);
// v3.8.32 intentionally capped busy_timeout at 2s (was 5s) so a contended
// synchronous write cannot park the Node event loop past the host watchdog's
// 6s liveness probe — see src/lib/db/core.ts.
assert.equal(db.pragma("busy_timeout", { simple: true }), 2000);
assert.equal(db.pragma("synchronous", { simple: true }), 1);
assert.equal(core.closeDbInstance({ checkpointMode: null }), true);
});

View File

@@ -115,6 +115,21 @@ test("#3500 buildUnifiedSource — raw-only branch when sinceIso is recent", ()
assert.ok("since" in result.unifiedParams, "unifiedParams has since key");
});
test("#3500 buildUnifiedSource — raw-only branch for same cutoff date ISO", () => {
const rawCutoffDate = "2026-06-21";
const result = mod.buildUnifiedSource({
sinceIso: "2026-06-21T01:00:00.000Z",
untilIso: null,
rawCutoffDate,
apiKeyWhere: "",
apiKeyParams: {},
});
assert.ok(!result.unifiedSource.includes("daily_usage_summary"), "no same-day agg table");
assert.equal(result.unifiedParams.since, "2026-06-21T01:00:00.000Z");
assert.ok(!("rawCutoffDate" in result.unifiedParams), "rawCutoffDate not needed");
});
// ---------------------------------------------------------------------------
// buildUnifiedSource — UNION branch (agg needed)
// ---------------------------------------------------------------------------
@@ -154,7 +169,10 @@ test("#3500 buildUnifiedSource — api_key filter suppresses daily_usage_summary
apiKeyParams: { apiKey0: "key-abc" },
});
assert.ok(!result.unifiedSource.includes("daily_usage_summary"), "agg leg suppressed with api_key filter");
assert.ok(
!result.unifiedSource.includes("daily_usage_summary"),
"agg leg suppressed with api_key filter"
);
assert.ok(result.unifiedSource.includes("usage_history"), "raw leg still present");
assert.equal(result.unifiedParams.apiKey0, "key-abc", "apiKey0 propagated");
});
@@ -167,8 +185,20 @@ test("#3500 getUsageSummary — returns correct scalar aggregations", () => {
const rawCutoffDate = "2020-01-01";
const ts = new Date().toISOString();
insertUsageHistory({ tokens_input: 50, tokens_output: 80, success: 1, latency_ms: 200, timestamp: ts });
insertUsageHistory({ tokens_input: 30, tokens_output: 40, success: 0, latency_ms: 400, timestamp: ts });
insertUsageHistory({
tokens_input: 50,
tokens_output: 80,
success: 1,
latency_ms: 200,
timestamp: ts,
});
insertUsageHistory({
tokens_input: 30,
tokens_output: 40,
success: 0,
latency_ms: 400,
timestamp: ts,
});
const { unifiedSource, unifiedParams } = mod.buildUnifiedSource({
sinceIso: null,
@@ -233,8 +263,22 @@ test("#3500 getDailyCostRows — groups by date+provider+model+serviceTier", ()
const rawCutoffDate = "2020-01-01";
const ts = "2025-04-01T12:00:00.000Z";
insertUsageHistory({ timestamp: ts, provider: "anthropic", model: "claude-3-5-sonnet", tokens_input: 100, tokens_output: 200, service_tier: "priority" });
insertUsageHistory({ timestamp: ts, provider: "anthropic", model: "claude-3-5-sonnet", tokens_input: 50, tokens_output: 100, service_tier: "priority" });
insertUsageHistory({
timestamp: ts,
provider: "anthropic",
model: "claude-3-5-sonnet",
tokens_input: 100,
tokens_output: 200,
service_tier: "priority",
});
insertUsageHistory({
timestamp: ts,
provider: "anthropic",
model: "claude-3-5-sonnet",
tokens_input: 50,
tokens_output: 100,
service_tier: "priority",
});
const { unifiedSource, unifiedParams } = mod.buildUnifiedSource({
sinceIso: "2025-04-01T00:00:00.000Z",
@@ -262,10 +306,9 @@ test("#3500 getHeatmapRows — groups by date, respects conditions", () => {
const ts = "2025-05-15T10:00:00.000Z";
insertUsageHistory({ timestamp: ts, tokens_input: 40, tokens_output: 60 });
const rows = mod.getHeatmapRows(
["timestamp >= @heatmapStart"],
{ heatmapStart: "2025-05-15T00:00:00.000Z" }
);
const rows = mod.getHeatmapRows(["timestamp >= @heatmapStart"], {
heatmapStart: "2025-05-15T00:00:00.000Z",
});
const may15 = rows.find((r) => r.date === "2025-05-15");
assert.ok(may15, "2025-05-15 row present");
@@ -280,8 +323,24 @@ test("#3500 getModelUsageRows — returns per-model aggregates", () => {
const rawCutoffDate = "2020-01-01";
const ts = "2025-06-01T10:00:00.000Z";
insertUsageHistory({ timestamp: ts, provider: "openai", model: "gpt-5", tokens_input: 100, tokens_output: 200, success: 1, latency_ms: 150 });
insertUsageHistory({ timestamp: ts, provider: "openai", model: "gpt-5", tokens_input: 50, tokens_output: 100, success: 1, latency_ms: 250 });
insertUsageHistory({
timestamp: ts,
provider: "openai",
model: "gpt-5",
tokens_input: 100,
tokens_output: 200,
success: 1,
latency_ms: 150,
});
insertUsageHistory({
timestamp: ts,
provider: "openai",
model: "gpt-5",
tokens_input: 50,
tokens_output: 100,
success: 1,
latency_ms: 250,
});
const { unifiedSource, unifiedParams } = mod.buildUnifiedSource({
sinceIso: "2025-06-01T00:00:00.000Z",
@@ -310,7 +369,14 @@ test("#3500 getProviderCostRows — groups by provider+model+serviceTier", () =>
const rawCutoffDate = "2020-01-01";
const ts = "2025-07-01T12:00:00.000Z";
insertUsageHistory({ timestamp: ts, provider: "gemini", model: "gemini-2.5-flash", tokens_input: 200, tokens_output: 300, tokens_cache_read: 50 });
insertUsageHistory({
timestamp: ts,
provider: "gemini",
model: "gemini-2.5-flash",
tokens_input: 200,
tokens_output: 300,
tokens_cache_read: 50,
});
const { unifiedSource, unifiedParams } = mod.buildUnifiedSource({
sinceIso: "2025-07-01T00:00:00.000Z",
@@ -335,8 +401,22 @@ test("#3500 getProviderUsageRows — aggregates per provider", () => {
const rawCutoffDate = "2020-01-01";
const ts = "2025-08-01T12:00:00.000Z";
insertUsageHistory({ timestamp: ts, provider: "mistral", tokens_input: 100, tokens_output: 100, success: 1, latency_ms: 200 });
insertUsageHistory({ timestamp: ts, provider: "mistral", tokens_input: 100, tokens_output: 100, success: 0, latency_ms: 400 });
insertUsageHistory({
timestamp: ts,
provider: "mistral",
tokens_input: 100,
tokens_output: 100,
success: 1,
latency_ms: 200,
});
insertUsageHistory({
timestamp: ts,
provider: "mistral",
tokens_input: 100,
tokens_output: 100,
success: 0,
latency_ms: 400,
});
const { unifiedSource, unifiedParams } = mod.buildUnifiedSource({
sinceIso: "2025-08-01T00:00:00.000Z",
@@ -365,8 +445,22 @@ test("#3500 getApiKeyUsageRows — groups by api_key identity", () => {
const apiKeyWhereClause =
"WHERE (api_key_id IS NOT NULL AND api_key_id != '') OR (api_key_name IS NOT NULL AND api_key_name != '')";
insertUsageHistory({ timestamp: ts, provider: "openai", model: "gpt-4.1", api_key_id: "key-xyz", tokens_input: 50, tokens_output: 80 });
insertUsageHistory({ timestamp: ts, provider: "openai", model: "gpt-4.1", api_key_id: "key-xyz", tokens_input: 30, tokens_output: 40 });
insertUsageHistory({
timestamp: ts,
provider: "openai",
model: "gpt-4.1",
api_key_id: "key-xyz",
tokens_input: 50,
tokens_output: 80,
});
insertUsageHistory({
timestamp: ts,
provider: "openai",
model: "gpt-4.1",
api_key_id: "key-xyz",
tokens_input: 30,
tokens_output: 40,
});
const rows = mod.getApiKeyUsageRows(apiKeyWhereClause, {});
const row = rows.find((r) => r.apiKeyId === "key-xyz");
@@ -399,9 +493,30 @@ test("#3500 getServiceTierUsageRows — groups by serviceTier+provider+model", (
const rawCutoffDate = "2020-01-01";
const ts = "2025-09-01T12:00:00.000Z";
insertUsageHistory({ timestamp: ts, provider: "openai", model: "gpt-5", service_tier: "flex", tokens_input: 100, tokens_output: 150 });
insertUsageHistory({ timestamp: ts, provider: "openai", model: "gpt-5", service_tier: "flex", tokens_input: 50, tokens_output: 75 });
insertUsageHistory({ timestamp: ts, provider: "openai", model: "gpt-5", service_tier: "standard", tokens_input: 200, tokens_output: 300 });
insertUsageHistory({
timestamp: ts,
provider: "openai",
model: "gpt-5",
service_tier: "flex",
tokens_input: 100,
tokens_output: 150,
});
insertUsageHistory({
timestamp: ts,
provider: "openai",
model: "gpt-5",
service_tier: "flex",
tokens_input: 50,
tokens_output: 75,
});
insertUsageHistory({
timestamp: ts,
provider: "openai",
model: "gpt-5",
service_tier: "standard",
tokens_input: 200,
tokens_output: 300,
});
const { unifiedSource, unifiedParams } = mod.buildUnifiedSource({
sinceIso: "2025-09-01T00:00:00.000Z",
@@ -430,7 +545,11 @@ test("#3500 getServiceTierUsageRows — groups by serviceTier+provider+model", (
test("#3500 getWeeklyPatternRows — groups by day of week, ascending", () => {
const rawCutoffDate = "2020-01-01";
// Monday 2025-06-02
insertUsageHistory({ timestamp: "2025-06-02T10:00:00.000Z", tokens_input: 10, tokens_output: 20 });
insertUsageHistory({
timestamp: "2025-06-02T10:00:00.000Z",
tokens_input: 10,
tokens_output: 20,
});
insertUsageHistory({ timestamp: "2025-06-02T11:00:00.000Z", tokens_input: 5, tokens_output: 10 });
const { unifiedSource, unifiedParams } = mod.buildUnifiedSource({
@@ -493,7 +612,16 @@ test("#3500 getPresetCostModelRows — groups by model+provider+serviceTier", ()
const rawCutoffDate = "2020-01-01";
const ts = "2025-10-01T12:00:00.000Z";
insertUsageHistory({ timestamp: ts, provider: "cohere", model: "command-r-plus", tokens_input: 200, tokens_output: 300, tokens_cache_read: 10, tokens_cache_creation: 5, tokens_reasoning: 0 });
insertUsageHistory({
timestamp: ts,
provider: "cohere",
model: "command-r-plus",
tokens_input: 200,
tokens_output: 300,
tokens_cache_read: 10,
tokens_cache_creation: 5,
tokens_reasoning: 0,
});
const { unifiedSource, unifiedParams } = mod.buildPresetUnifiedSource({
sinceIso: "2025-10-01T00:00:00.000Z",

View File

@@ -0,0 +1,138 @@
/**
* Tests for src/lib/db/vacuumScheduler.ts (#4437 / PR #4480)
*
* Covers:
* 1. Module exports the expected public API.
* 2. getState() returns the documented shape before any init/run.
* 3. init() is idempotent (safe to call from instrumentation-node.ts).
* 4. stop() is safe before init() and idempotent.
* 5. runNow() succeeds on a healthy DB, persists lastRunAt, clears isRunning.
* 6. runNow() called twice concurrently yields exactly one success and one
* "already_running".
* 7. lastRunAt survives a simulated restart (__resetForTests + init reloads the
* persisted state from key_value).
*
* Rebuild note (PR #4480): the original PR test was authored against the Vitest
* API and a stale scheduler interface (`state.initialized` / `state.running`),
* which never matched the shipped module (`isRunning`, no `initialized`) and was
* placed under tests/unit/db/** where the Node native runner — not Vitest —
* picks it up. Rewritten for node:test against the real VacuumSchedulerState.
*
* DB isolation pattern mirrors tests/unit/db/default-combo-toggle.test.ts:
* temp DATA_DIR, resetDbInstance() before each test, cleanup in test.after().
*/
import test from "node:test";
import assert from "node:assert/strict";
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-vacuum-scheduler-"));
const originalDataDir = process.env.DATA_DIR;
const originalEnabled = process.env.OMNIROUTE_VACUUM_ENABLED;
process.env.DATA_DIR = TEST_DATA_DIR;
// Keep the scheduler from arming a real interval timer during unit tests.
process.env.OMNIROUTE_VACUUM_ENABLED = "0";
const core = await import("../../../src/lib/db/core.ts");
core.resetDbInstance();
const scheduler = await import("../../../src/lib/db/vacuumScheduler.ts");
test.beforeEach(() => {
scheduler.__resetForTests();
});
test.after(() => {
scheduler.__resetForTests();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
if (originalDataDir === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = originalDataDir;
if (originalEnabled === undefined) delete process.env.OMNIROUTE_VACUUM_ENABLED;
else process.env.OMNIROUTE_VACUUM_ENABLED = originalEnabled;
});
test("module loads and exports the expected public API", () => {
assert.equal(typeof scheduler.init, "function");
assert.equal(typeof scheduler.stop, "function");
assert.equal(typeof scheduler.runNow, "function");
assert.equal(typeof scheduler.getState, "function");
});
test("getState() returns the documented shape before any init/run", () => {
const state = scheduler.getState();
assert.equal(typeof state.enabled, "boolean");
assert.equal(typeof state.isRunning, "boolean");
assert.equal(state.isRunning, false);
assert.equal(state.lastRunAt, null);
assert.equal(state.lastDurationMs, null);
assert.equal(state.lastError, null);
assert.equal(state.nextRunAt, null);
});
test("init() is idempotent — calling it twice does not throw", () => {
assert.doesNotThrow(() => scheduler.init());
assert.doesNotThrow(() => scheduler.init());
scheduler.stop();
});
test("stop() is safe to call before init() and is idempotent", () => {
assert.doesNotThrow(() => scheduler.stop());
scheduler.init();
assert.doesNotThrow(() => scheduler.stop());
assert.doesNotThrow(() => scheduler.stop());
});
test("runNow() succeeds on a healthy DB and persists lastRunAt", async () => {
scheduler.init();
try {
const result = await scheduler.runNow();
assert.equal(result.success, true);
assert.equal(typeof result.durationMs, "number");
assert.ok(result.durationMs >= 0);
const state = scheduler.getState();
assert.equal(state.isRunning, false);
assert.notEqual(state.lastRunAt, null);
assert.equal(state.lastError, null);
} finally {
scheduler.stop();
}
});
test("runNow() can be called repeatedly; each run succeeds and refreshes lastRunAt", async () => {
// better-sqlite3 is synchronous, so VACUUM blocks the event loop for the whole
// run — the isRunning guard (which returns "already_running") cannot be
// triggered by overlapping awaits in-process. The realistic contract is that
// sequential runs each succeed and update lastRunAt.
scheduler.init();
try {
const first = await scheduler.runNow();
assert.equal(first.success, true);
const second = await scheduler.runNow();
assert.equal(second.success, true);
assert.equal(scheduler.getState().isRunning, false);
assert.notEqual(scheduler.getState().lastRunAt, null);
} finally {
scheduler.stop();
}
});
test("lastRunAt survives a simulated restart (state reloaded from key_value)", async () => {
scheduler.init();
await scheduler.runNow();
const beforeRestart = scheduler.getState().lastRunAt;
assert.notEqual(beforeRestart, null);
// Simulate a process restart: wipe in-memory state, then init() reloads the
// persisted blob from key_value.
scheduler.__resetForTests();
assert.equal(scheduler.getState().lastRunAt, null);
scheduler.init();
const afterRestart = scheduler.getState().lastRunAt;
assert.equal(afterRestart, beforeRestart);
scheduler.stop();
});

View File

@@ -23,7 +23,8 @@ test("convertOpenAIContentToParts maps input_audio (mp3) and strips a data: pref
const parts = gemini.convertOpenAIContentToParts([
{ type: "input_audio", input_audio: { data: "data:audio/mp3;base64,QUJDRA==", format: "mp3" } },
]);
assert.deepEqual(parts, [{ inlineData: { mimeType: "audio/mp3", data: "QUJDRA==" } }]);
// mp3 normalizes to the canonical audio/mpeg (matches the #913 translator test).
assert.deepEqual(parts, [{ inlineData: { mimeType: "audio/mpeg", data: "QUJDRA==" } }]);
});
test("convertOpenAIContentToParts supports the { type: 'audio', audio: {...} } shape", () => {

View File

@@ -0,0 +1,36 @@
import test from "node:test";
import assert from "node:assert/strict";
import i18nConfig from "../../config/i18n.json" with { type: "json" };
import {
DEFAULT_LOCALE,
LANGUAGES,
LOCALES,
LOCALE_COOKIE,
RTL_LOCALES,
} from "../../src/i18n/config.ts";
test("i18n config adapter reflects the JSON source of truth", () => {
assert.deepEqual(
LOCALES,
i18nConfig.locales.map((locale) => locale.code)
);
assert.equal(DEFAULT_LOCALE, i18nConfig.default);
assert.deepEqual(RTL_LOCALES, i18nConfig.rtl);
assert.equal(LOCALE_COOKIE, "NEXT_LOCALE");
});
test("i18n language metadata preserves native and English names", () => {
assert.equal(LANGUAGES.length, i18nConfig.locales.length);
const english = LANGUAGES.find((language) => language.code === "en");
const englishConfig = i18nConfig.locales.find((language) => language.code === "en");
assert.deepEqual(english, {
code: "en",
label: englishConfig?.label,
name: englishConfig?.name,
native: englishConfig?.native,
english: englishConfig?.english,
flag: englishConfig?.flag,
});
});

View File

@@ -132,8 +132,9 @@ test("buildUsageCommandText formats API key USD limits when fair usage is enable
dailySpentUsd: 2,
weeklySpentUsd: 5.25,
dailyWindowStartIso: "2026-06-16T03:00:00.000Z",
dailyResetAtIso: "2026-06-17T03:00:00.000Z",
weeklyWindowStartIso: "2026-06-09T12:00:00.000Z",
weeklyResetAtIso: "2026-06-16T12:00:00.000Z",
weeklyResetAtIso: "2026-06-23T12:00:00.000Z",
dailyExceeded: false,
weeklyExceeded: false,
}),
@@ -159,11 +160,17 @@ test("buildUsageCommandText formats API key USD limits when fair usage is enable
"$10.00",
"Gasto diario",
"$2.00",
"Uso diario",
"20%",
"Resets in 15h",
"",
"Cota semanal",
"$50.00",
"Gasto semanal",
"$5.25",
"Uso semanal",
"11%",
"Resets in 7d",
].join("\n")
);
});

View File

@@ -2,10 +2,13 @@ import test from "node:test";
import assert from "node:assert/strict";
import {
__testing,
buildKiroUsageResult,
discoverKiroProfileArn,
} from "@omniroute/open-sse/services/usage.ts";
const { getKiroUsage } = __testing;
// Real-world shape returned by GetUsageLimits for an AWS IAM Identity Center ("KIRO POWER")
// account — the usage is reported under resourceType "CREDIT" (not "AGENTIC_REQUEST").
const IAM_CREDIT_RESPONSE = {
@@ -98,3 +101,69 @@ test("discoverKiroProfileArn returns undefined for empty profiles or non-ok resp
globalThis.fetch = originalFetch;
}
});
// Regression: when a Kiro account added via Google/GitHub social-auth (authMethod "imported"
// with provider "Google" or "Github" — set by /api/oauth/kiro/social-exchange/route.ts) has its
// token rejected by the AWS CodeWhisperer quota API (401/403), surface a clear "auth expired,
// chat may still work" message instead of throwing a generic upstream-error blob.
test("getKiroUsage returns a friendly auth-expired message for social-auth Kiro on 401/403", async () => {
const originalFetch = globalThis.fetch;
// First call (ListAvailableProfiles for ARN discovery) succeeds with an ARN so we proceed
// to GetUsageLimits, which then returns 401. The friendly branch only applies when the
// GetUsageLimits call returned an auth-shaped error.
let callIdx = 0;
globalThis.fetch = (async (_url: string, init?: RequestInit) => {
callIdx += 1;
const target = String((init?.headers as Record<string, string> | undefined)?.["x-amz-target"] || "");
if (target.endsWith("ListAvailableProfiles")) {
return new Response(
JSON.stringify({ profiles: [{ arn: "arn:aws:codewhisperer:us-east-1:1:profile/SOCIAL" }] }),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
// GetUsageLimits → simulate the social-auth token rejection
return new Response(JSON.stringify({ __type: "AccessDeniedException" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}) as typeof fetch;
try {
const result = (await getKiroUsage("social-tok", {
authMethod: "imported",
provider: "Google",
})) as { message?: string; quotas?: Record<string, unknown> };
assert.ok(result, "should resolve, not throw");
assert.ok(
typeof result.message === "string" && /authentication expired/i.test(result.message),
`expected an auth-expired message, got: ${JSON.stringify(result)}`
);
assert.deepEqual(result.quotas ?? {}, {});
assert.ok(callIdx >= 2, "GetUsageLimits should have been called after profile discovery");
} finally {
globalThis.fetch = originalFetch;
}
});
// IAM Identity Center / Builder-ID accounts must keep the existing throw-on-failure behavior so
// transient upstream errors don't get silently masked as "auth expired".
test("getKiroUsage still throws on 401/403 for non-social Kiro accounts (Builder-ID/IDC)", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (_url: string, init?: RequestInit) => {
const target = String((init?.headers as Record<string, string> | undefined)?.["x-amz-target"] || "");
if (target.endsWith("ListAvailableProfiles")) {
return new Response(
JSON.stringify({ profiles: [{ arn: "arn:aws:codewhisperer:us-east-1:1:profile/BID" }] }),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
return new Response("denied", { status: 401 });
}) as typeof fetch;
try {
await assert.rejects(
() => getKiroUsage("builder-tok", { authMethod: "builder-id" }),
/Failed to fetch Kiro usage/i
);
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -0,0 +1,59 @@
/**
* Source-guard test: ManualConfigModal must use the shared useCopyToClipboard
* hook (which delegates to src/shared/utils/clipboard.ts for HTTP/HTTPS fallback)
* rather than re-implementing the navigator.clipboard + execCommand fallback
* inline.
*
* Rationale: duplicated inline fallbacks drift from the canonical helper.
* Two known divergences in the previous inline copy:
* 1. `window.isSecureContext` gate skipped navigator.clipboard on some
* browsers that allow it in non-secure contexts.
* 2. No `finally` cleanup if execCommand threw after appendChild succeeded,
* leaking a hidden textarea in the DOM.
*
* The shared helper handles both correctly. This test pins the migration.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const FILE = resolve(__dirname, "../../src/shared/components/ManualConfigModal.tsx");
describe("ManualConfigModal — clipboard migration to shared hook", () => {
const src = readFileSync(FILE, "utf8");
it("imports useCopyToClipboard from the shared hooks barrel", () => {
assert.match(
src,
/useCopyToClipboard/,
"expected ManualConfigModal to consume the shared useCopyToClipboard hook"
);
});
it("does not call navigator.clipboard directly (delegated to the hook)", () => {
assert.doesNotMatch(
src,
/navigator\.clipboard/,
"ManualConfigModal must not call navigator.clipboard directly; use the shared hook"
);
});
it("does not call document.execCommand('copy') inline (delegated to the hook)", () => {
assert.doesNotMatch(
src,
/document\.execCommand\(\s*["']copy["']\s*\)/,
"ManualConfigModal must not inline the execCommand fallback; use the shared hook"
);
});
it("does not gate on window.isSecureContext (the shared helper does the right thing)", () => {
assert.doesNotMatch(
src,
/isSecureContext/,
"ManualConfigModal must not gate on isSecureContext; the shared helper handles fallback correctly"
);
});
});

View File

@@ -0,0 +1,146 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
webFetchInput,
webFetchOutput,
webFetchTool,
MCP_TOOLS,
MCP_TOOL_MAP,
} from "../../open-sse/mcp-server/schemas/tools.ts";
import { MCP_TOOL_SCOPES } from "../../src/shared/constants/mcpScopes.ts";
// ── Tool definition shape ──
test("webFetchTool has the required McpToolDefinition shape", () => {
assert.equal(webFetchTool.name, "omniroute_web_fetch");
assert.equal(typeof webFetchTool.description, "string");
assert.ok(webFetchTool.description.length > 0);
assert.ok(webFetchTool.inputSchema != null);
assert.ok(webFetchTool.outputSchema != null);
assert.equal(typeof webFetchTool.inputSchema.parse, "function");
assert.deepEqual(webFetchTool.scopes, ["execute:search"]);
assert.equal(webFetchTool.auditLevel, "basic");
assert.equal(webFetchTool.phase, 1);
assert.ok(webFetchTool.sourceEndpoints.includes("/v1/web/fetch"));
});
test("webFetchTool is registered in MCP_TOOLS and MCP_TOOL_MAP", () => {
const toolNames = MCP_TOOLS.map((t) => t.name);
assert.ok(
toolNames.includes("omniroute_web_fetch"),
"webFetchTool must be in MCP_TOOLS array"
);
assert.ok(
"omniroute_web_fetch" in MCP_TOOL_MAP,
"webFetchTool must be in MCP_TOOL_MAP"
);
});
// ── Scope mapping ──
test("omniroute_web_fetch is mapped in MCP_TOOL_SCOPES with execute:search", () => {
const scopes = MCP_TOOL_SCOPES["omniroute_web_fetch"];
assert.ok(scopes != null, "omniroute_web_fetch must have a scope mapping");
assert.ok(
scopes.includes("execute:search"),
"omniroute_web_fetch must require execute:search scope"
);
});
// ── Input schema validation ──
test("webFetchInput accepts a valid minimal request (URL only)", () => {
const parsed = webFetchInput.parse({ url: "https://example.com" });
assert.equal(parsed.url, "https://example.com");
assert.equal(parsed.format, "markdown"); // default
assert.equal(parsed.include_metadata, false); // default
});
test("webFetchInput accepts all optional fields", () => {
const parsed = webFetchInput.parse({
url: "https://example.com",
provider: "firecrawl",
format: "html",
include_metadata: true,
depth: 1,
wait_for_selector: "#content",
});
assert.equal(parsed.provider, "firecrawl");
assert.equal(parsed.format, "html");
assert.equal(parsed.include_metadata, true);
assert.equal(parsed.depth, 1);
assert.equal(parsed.wait_for_selector, "#content");
});
test("webFetchInput rejects missing URL", () => {
assert.throws(
() => webFetchInput.parse({}),
/URL is required/,
"Missing url should fail validation"
);
});
test("webFetchInput rejects empty URL", () => {
assert.throws(
() => webFetchInput.parse({ url: "" }),
/URL is required/,
"Empty url should fail validation"
);
});
test("webFetchInput rejects depth > 2 (matches WebFetchRequest type constraint)", () => {
assert.throws(
() => webFetchInput.parse({ url: "https://example.com", depth: 3 }),
"depth > 2 should fail validation to match the 0 | 1 | 2 type in WebFetchRequest"
);
});
test("webFetchInput accepts depth values 0, 1, 2", () => {
for (const depth of [0, 1, 2]) {
const parsed = webFetchInput.parse({ url: "https://example.com", depth });
assert.equal(parsed.depth, depth);
}
});
test("webFetchInput rejects invalid provider", () => {
assert.throws(
() => webFetchInput.parse({ url: "https://example.com", provider: "unknown-provider" }),
"Unknown provider should fail validation"
);
});
test("webFetchInput rejects invalid format", () => {
assert.throws(
() => webFetchInput.parse({ url: "https://example.com", format: "xml" }),
"Invalid format should fail validation"
);
});
// ── Output schema validation ──
test("webFetchOutput validates a typical scrape response", () => {
const result = webFetchOutput.parse({
provider: "firecrawl",
url: "https://example.com",
content: "# Example Domain\n\nThis domain is for use in documentation examples.",
links: ["https://iana.org/domains/example"],
metadata: { title: "Example Domain", description: "Example site" },
screenshot_url: null,
});
assert.equal(result.provider, "firecrawl");
assert.equal(result.links.length, 1);
assert.equal(result.metadata?.title, "Example Domain");
});
test("webFetchOutput validates a response with null metadata", () => {
const result = webFetchOutput.parse({
provider: "jina-reader",
url: "https://example.com",
content: "Some content",
links: [],
metadata: null,
screenshot_url: null,
});
assert.equal(result.metadata, null);
});

View File

@@ -119,7 +119,7 @@ test("memory search respects a configured zero token budget", async () => {
assert.equal(result.data.totalTokens, 0);
});
test("memory search keeps globally disabled memory disabled with explicit maxTokens", async () => {
test("memory search runs explicitly even when global memory injection is disabled", async () => {
await settingsDb.updateSettings({ memoryEnabled: false, memoryMaxTokens: 2000 });
invalidateMemorySettingsCache();
@@ -137,9 +137,10 @@ test("memory search keeps globally disabled memory disabled with explicit maxTok
});
assert.equal(result.success, true);
assert.equal(result.data.count, 0);
assert.deepEqual(result.data.memories, []);
assert.equal(result.data.totalTokens, 0);
assert.equal(result.data.count, 1);
assert.equal(result.data.memories.length, 1);
assert.match(result.data.memories[0].content, /TypeScript/i);
assert.ok(result.data.totalTokens > 0);
});
test("memory clear deletes only older filtered entries and reports the deleted count", async () => {

View File

@@ -0,0 +1,88 @@
/**
* PR #822: gate sudo prompts on the server platform.
*
* The MITM control surface previously decided whether to prompt for a sudo
* password using the *browser's* `navigator.userAgent` and a non-Windows
* unconditional gate on the API route. That broke two real cases:
*
* 1. Windows browser hitting a Linux server (no prompt → request 400s).
* 2. Linux server running as root or under NOPASSWD sudoers (unnecessary
* modal blocks the user even though sudo would never ask).
*
* The fix:
* - `dnsConfig.ts` exposes `canRunSudoWithoutPassword()` /
* `isSudoPasswordRequired()` that probe the actual server state.
* - The route surfaces `isWin` + `needsSudoPassword` so the UI can decide
* based on the server's platform, not the browser's.
*
* These tests pin the *pure* helper contract — no real `sudo` is invoked
* because every probe path is short-circuited before it tries `sudo -n true`
* (Windows / root / no-sudo-on-PATH).
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
canRunSudoWithoutPassword,
isSudoAvailable,
isSudoPasswordRequired,
} from "../../src/mitm/dns/dnsConfig.ts";
test("isSudoAvailable returns a boolean on the current platform", () => {
const result = isSudoAvailable();
assert.equal(typeof result, "boolean");
// Windows reports true unconditionally (no sudo concept).
if (process.platform === "win32") {
assert.equal(result, true);
}
});
test("canRunSudoWithoutPassword short-circuits to true on Windows and root", () => {
const result = canRunSudoWithoutPassword();
assert.equal(typeof result, "boolean");
if (process.platform === "win32") {
assert.equal(result, true, "Windows uses UAC, never needs sudo password");
return;
}
// Linux/macOS: root user always passes without a password.
const isRootUser = !!(process.getuid && process.getuid() === 0);
if (isRootUser) {
assert.equal(result, true, "root user never needs sudo password");
}
});
test("isSudoPasswordRequired returns false on Windows", () => {
if (process.platform !== "win32") {
// Can't simulate Windows from a non-Windows test runner; assert the
// contract holds on the native platform.
const result = isSudoPasswordRequired();
assert.equal(typeof result, "boolean");
return;
}
assert.equal(isSudoPasswordRequired(), false);
});
test("isSudoPasswordRequired returns false when running as root on POSIX", () => {
if (process.platform === "win32") return;
const isRootUser = !!(process.getuid && process.getuid() === 0);
if (!isRootUser) {
// Skip — we can't elevate from the test runner. This branch is covered
// by the contract: isSudoPasswordRequired === !IS_WIN && isSudoAvailable
// && !canRunSudoWithoutPassword, and canRunSudoWithoutPassword returns
// early when isRoot() is true.
return;
}
assert.equal(isSudoPasswordRequired(), false);
});
test("isSudoPasswordRequired is consistent with canRunSudoWithoutPassword on POSIX", () => {
if (process.platform === "win32") return;
if (!isSudoAvailable()) {
// No sudo binary → never required.
assert.equal(isSudoPasswordRequired(), false);
return;
}
// When sudo *is* available, requirement is the inverse of "can run without".
assert.equal(isSudoPasswordRequired(), !canRunSudoWithoutPassword());
});

View File

@@ -0,0 +1,101 @@
/**
* Regression test for MITM cert install in Docker `USER node` (non-root, no sudo).
*
* OmniRoute's runtime Docker image (`Dockerfile`) runs as `USER node` (UID 1000)
* and the slim base (`node:24-trixie-slim`) does NOT ship `sudo`. The previous
* `execFileWithPassword("sudo", ["-S", ...])` would spawn `sudo` unconditionally
* when not root, producing `spawn sudo ENOENT` and breaking `installCert` /
* `addDNSEntries` for any MITM operation triggered from inside the container.
*
* The fix:
* - `isSudoAvailable()` probes `PATH` for `sudo`.
* - `execFileWithPassword` when invoked with `command === "sudo"` and sudo is
* NOT available and the process is NOT root, strips the leading `sudo -S`
* flags and runs the underlying command directly (same user, no elevation).
* - Callers that absolutely require elevation (e.g. installing a CA to the
* system trust store) can pre-check with `isSudoAvailable()` and skip with
* a clear log instead of throwing.
*
* This mirrors upstream behavior (decolua/9router) extended to OmniRoute's TS
* fork and OmniRoute's existing root-detection (`isRoot()`).
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import {
execFileWithPassword,
isRoot,
isSudoAvailable,
} from "../../src/mitm/systemCommands.ts";
test("isSudoAvailable: returns a boolean (probe shape)", () => {
const result = isSudoAvailable();
assert.equal(typeof result, "boolean");
});
test(
"execFileWithPassword: when command is 'sudo' but sudo is missing, falls back to direct exec (no ENOENT)",
async () => {
if (isRoot()) {
// Root path is already handled by an earlier branch — skip.
return;
}
if (isSudoAvailable()) {
// We cannot exercise the no-sudo path on a host that has sudo on PATH.
// The dedicated unit test below covers it by isolating PATH.
return;
}
// Use a no-op binary that succeeds without requiring privileges.
// After stripping `sudo -S`, the underlying command must be `true` (or any
// executable in PATH that exits 0).
const result = await execFileWithPassword(
"sudo",
["-S", "true"],
"fake-password"
);
assert.equal(typeof result, "string");
}
);
test(
"execFileWithPassword: with empty PATH so sudo is undiscoverable, falls back gracefully",
async () => {
if (isRoot()) {
// Root branch strips sudo upstream — covered separately.
return;
}
// Force the discovery probe to find no sudo by clearing PATH temporarily.
// We do this only around the probe call inside `execFileWithPassword`,
// by stubbing the env. Restore on test exit.
const originalPath = process.env.PATH;
process.env.PATH = "/__no_such_dir__";
try {
// After fallback, the bare command must be a valid executable. We use
// the current Node binary with `process.exit(0)` so it is portable
// regardless of which utilities (`true`, `:`) exist in the path.
const result = await execFileWithPassword(
"sudo",
["-S", process.execPath, "-e", "process.exit(0)"],
"fake-password"
);
assert.equal(typeof result, "string");
} finally {
process.env.PATH = originalPath;
}
}
);
test(
"execFileWithPassword: a non-sudo command is unaffected by the fallback path",
async () => {
// Direct invocation of a known-good binary must still work end-to-end.
const result = await execFileWithPassword(
process.execPath,
["-e", "process.exit(0)"],
"" // no password needed
);
assert.equal(typeof result, "string");
}
);

View File

@@ -0,0 +1,80 @@
import test from "node:test";
import assert from "node:assert/strict";
// Pure function — importable without DB setup.
const { disambiguateCatalogModelNames } = await import("../../src/lib/modelMetadataRegistry.ts");
type CatalogEntry = { id: string; name?: string; owned_by?: string; [key: string]: unknown };
test("leaves names untouched when each name appears under only one provider", () => {
const models: CatalogEntry[] = [
{ id: "gh/gpt-5.5", name: "GPT-5.5", owned_by: "github" },
{ id: "cc/claude-sonnet-4-6", name: "Claude Sonnet 4.6", owned_by: "claude" },
{ id: "ds-web/deepseek-r2", name: "DeepSeek R2", owned_by: "deepseek-web" },
];
const result = disambiguateCatalogModelNames(models);
assert.equal(result[0].name, "GPT-5.5", "unique name should be unchanged");
assert.equal(result[1].name, "Claude Sonnet 4.6", "unique name should be unchanged");
assert.equal(result[2].name, "DeepSeek R2", "unique name should be unchanged");
});
test("qualifies names shared across multiple providers with their prefix", () => {
const models: CatalogEntry[] = [
{ id: "gh/gpt-5.5", name: "GPT-5.5", owned_by: "github" },
{ id: "cx/gpt-5.5", name: "GPT-5.5", owned_by: "codex" },
{ id: "opencode-zen/gpt-5.5", name: "GPT-5.5", owned_by: "opencode-zen" },
{ id: "cc/claude-sonnet-4-6", name: "Claude Sonnet 4.6", owned_by: "claude" },
];
const result = disambiguateCatalogModelNames(models);
assert.equal(result[0].name, "gh/GPT-5.5", "ambiguous name should gain gh/ prefix");
assert.equal(result[1].name, "cx/GPT-5.5", "ambiguous name should gain cx/ prefix");
assert.equal(
result[2].name,
"opencode-zen/GPT-5.5",
"ambiguous name should gain opencode-zen/ prefix"
);
assert.equal(result[3].name, "Claude Sonnet 4.6", "unique name should remain unqualified");
});
test("skips entries with no name field", () => {
const models: CatalogEntry[] = [
{ id: "gh/gpt-5.5", owned_by: "github" },
{ id: "cx/gpt-5.5", owned_by: "codex" },
];
const result = disambiguateCatalogModelNames(models);
assert.equal(result[0].name, undefined, "nameless entry should stay nameless");
assert.equal(result[1].name, undefined, "nameless entry should stay nameless");
});
test("skips entries with no provider prefix in id", () => {
const models: CatalogEntry[] = [
{ id: "gpt-5.5", name: "GPT-5.5" },
{ id: "gpt-5.5-mini", name: "GPT-5.5" },
];
const result = disambiguateCatalogModelNames(models);
// No prefix extractable → counts as same prefix bucket, treated as same source
// Both names should remain because disambiguation needs 2+ distinct prefixes.
assert.equal(result[0].name, "GPT-5.5");
assert.equal(result[1].name, "GPT-5.5");
});
test("does not mutate the original model objects", () => {
const original: CatalogEntry = { id: "gh/gpt-5.5", name: "GPT-5.5", owned_by: "github" };
const dup: CatalogEntry = { id: "cx/gpt-5.5", name: "GPT-5.5", owned_by: "codex" };
const models = [original, dup];
disambiguateCatalogModelNames(models);
assert.equal(original.name, "GPT-5.5", "original object must not be mutated");
assert.equal(dup.name, "GPT-5.5", "original object must not be mutated");
});
test("handles models with no name-conflicts among combo/unprefixed entries gracefully", () => {
const models: CatalogEntry[] = [
{ id: "auto/best", owned_by: "combo" },
{ id: "my-combo", owned_by: "combo" },
{ id: "gh/gpt-4o", name: "GPT-4o", owned_by: "github" },
{ id: "cx/gpt-4o", name: "GPT-4o", owned_by: "codex" },
];
const result = disambiguateCatalogModelNames(models);
assert.equal(result[2].name, "gh/GPT-4o");
assert.equal(result[3].name, "cx/GPT-4o");
});

View File

@@ -0,0 +1,222 @@
import test from "node:test";
import assert from "node:assert/strict";
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(), "omr-lockout-max-cooldown-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = "test-lockout-max-cooldown-secret";
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const auth = await import("../../src/sse/services/auth.ts");
const { handleComboChat } = await import("../../open-sse/services/combo.ts");
const { recordModelLockoutFailure, getModelLockoutInfo, clearAllModelLockouts } =
await import("../../open-sse/services/accountFallback.ts");
function createLog() {
return {
info: () => {},
warn: () => {},
error: () => {},
debug: () => {},
};
}
function errorResponse(status: number, message: string = `Error ${status}`) {
return new Response(JSON.stringify({ error: { message } }), {
status,
headers: { "content-type": "application/json" },
});
}
async function seedConnection(provider: string, overrides: any = {}): Promise<any> {
return providersDb.createProviderConnection({
provider,
authType: "apikey",
name: overrides.name || `${provider}-${Math.random().toString(16).slice(2, 8)}`,
apiKey: overrides.apiKey || `sk-test-${Math.random().toString(16).slice(2, 8)}`,
isActive: true,
testStatus: "active",
rateLimitedUntil: null,
backoffLevel: overrides.backoffLevel || 0,
providerSpecificData: overrides.providerSpecificData || {},
});
}
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(async () => {
clearAllModelLockouts();
await resetStorage();
});
test.after(async () => {
clearAllModelLockouts();
try {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
} catch {}
});
test("recordModelLockoutFailure honors maxCooldownMs option parameter", () => {
const provider = "openai";
const connectionId = "conn-s1";
const model = "gpt-4";
const first = recordModelLockoutFailure(
provider,
connectionId,
model,
"rate_limited",
429,
500,
null,
{
maxCooldownMs: 1500,
}
);
assert.equal(first.cooldownMs, 500);
const originalNow = Date.now;
try {
let fakeNow = Date.now();
Date.now = () => fakeNow;
fakeNow += 600;
const second = recordModelLockoutFailure(
provider,
connectionId,
model,
"rate_limited",
429,
500,
null,
{
maxCooldownMs: 1500,
}
);
assert.equal(second.cooldownMs, 1000);
fakeNow += 1100;
const third = recordModelLockoutFailure(
provider,
connectionId,
model,
"rate_limited",
429,
500,
null,
{
maxCooldownMs: 1500,
}
);
assert.equal(third.cooldownMs, 1500);
} finally {
Date.now = originalNow;
}
});
test("handleComboChat quality failure model lockout honors maxCooldownMs settings", async () => {
const provider = "openai";
const model = "gpt-4";
const customSettings = {
modelLockout: {
enabled: true,
errorCodes: [502],
baseCooldownMs: 3000,
maxCooldownMs: 5000,
maxBackoffSteps: 10,
useExponentialBackoff: true,
},
};
const logs = createLog();
const executeComboWithFailure = async () => {
return handleComboChat({
body: {},
combo: {
name: "test-combo",
strategy: "priority",
models: [`${provider}/${model}`],
config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0 },
},
handleSingleModel: async () => {
return errorResponse(502);
},
isModelAvailable: async () => true,
log: logs as any,
settings: customSettings,
allCombos: null,
});
};
const originalNow = Date.now;
try {
let fakeNow = Date.now();
Date.now = () => fakeNow;
await executeComboWithFailure();
let info = getModelLockoutInfo(provider, "", model);
assert.ok(info && info.remainingMs <= 3000);
fakeNow += 3100;
await executeComboWithFailure();
info = getModelLockoutInfo(provider, "", model);
assert.ok(info);
assert.ok(
info!.remainingMs <= 5000,
`Expected remainingMs to be capped at 5000, but got ${info!.remainingMs}`
);
} finally {
Date.now = originalNow;
}
});
test("markAccountUnavailable local 404 lockout honors maxCooldownMs settings", async () => {
const provider = "openai";
const model = "local-gpt-4";
const connection = (await seedConnection(provider, {
providerSpecificData: { baseUrl: "http://127.0.0.1:8080/v1" },
})) as any;
await settingsDb.updateSettings({
modelLockout: {
enabled: true,
errorCodes: [404],
baseCooldownMs: 3000,
maxCooldownMs: 5000,
maxBackoffSteps: 10,
useExponentialBackoff: true,
},
});
const originalNow = Date.now;
try {
let fakeNow = Date.now();
Date.now = () => fakeNow;
await auth.markAccountUnavailable(connection.id as string, 404, "not found", provider, model);
let info = getModelLockoutInfo(provider, connection.id as string, model);
assert.ok(info && info.remainingMs <= 3000);
fakeNow += 3100;
await providersDb.updateProviderConnection(connection.id as string, { rateLimitedUntil: null });
await auth.markAccountUnavailable(connection.id as string, 404, "not found", provider, model);
info = getModelLockoutInfo(provider, connection.id as string, model);
assert.ok(info);
assert.ok(
info!.remainingMs <= 5000,
`Expected remainingMs to be capped at 5000, but got ${info!.remainingMs}`
);
} finally {
Date.now = originalNow;
}
});

View File

@@ -0,0 +1,107 @@
import test from "node:test";
import assert from "node:assert/strict";
// #4424 follow-up — `/v1/models` must not emit the same id twice (OpenAI clients key
// by id and break on exact-duplicate ids). The reporter observed `codex/gpt-5.5`,
// `veo-free/seedance`, `veo-free/veo` each listed twice. A final dedupe keyed by the
// model's listing identity `(id, type, subtype)` collapses true exact dupes (keep-first)
// while preserving the ONE intentional same-id case: audio models that list both a
// transcription and a speech entry under the same id (distinguished by `subtype`).
import { dedupeExactCatalogIds } from "../../src/app/api/v1/models/catalogDedupe.ts";
test("collapses an exact-duplicate id to a single entry (keep first)", () => {
const input = [
{ id: "codex/gpt-5.5", owned_by: "codex", root: "gpt-5.5", context_length: 200000 },
{ id: "codex/gpt-5.5", owned_by: "codex", root: "gpt-5.5", context_length: 200000 },
];
const out = dedupeExactCatalogIds(input);
assert.equal(out.length, 1);
assert.equal(out[0].id, "codex/gpt-5.5");
assert.equal(out[0].context_length, 200000);
});
test("collapses the reporter's two distinct duplicated ids to two unique entries", () => {
const input = [
{ id: "veo-free/seedance", owned_by: "veo-free", root: "seedance", type: "video" },
{ id: "veo-free/veo", owned_by: "veo-free", root: "veo", type: "video" },
{ id: "veo-free/seedance", owned_by: "veo-free", root: "seedance", type: "video" },
{ id: "veo-free/veo", owned_by: "veo-free", root: "veo", type: "video" },
];
const out = dedupeExactCatalogIds(input);
assert.equal(out.length, 2);
assert.deepEqual(
out.map((m) => m.id),
["veo-free/seedance", "veo-free/veo"]
);
});
test("preserves intentional same-id audio variants (transcription vs speech)", () => {
const input = [
{ id: "prov/whisper", owned_by: "prov", root: "whisper", type: "audio", subtype: "transcription" },
{ id: "prov/whisper", owned_by: "prov", root: "whisper", type: "audio", subtype: "speech" },
];
const out = dedupeExactCatalogIds(input);
assert.equal(out.length, 2);
assert.deepEqual(
out.map((m) => m.subtype).sort(),
["speech", "transcription"]
);
});
test("keeps distinct ids untouched", () => {
const input = [
{ id: "a/m1", type: "chat" },
{ id: "b/m2", type: "chat" },
{ id: "c/m3", type: "chat" },
];
const out = dedupeExactCatalogIds(input);
assert.equal(out.length, 3);
});
test("keeps the FIRST occurrence's metadata, drops the later dupe", () => {
const input = [
{ id: "x/dup", name: "First", capabilities: { vision: true } },
{ id: "x/dup", name: "Second" },
];
const out = dedupeExactCatalogIds(input);
assert.equal(out.length, 1);
assert.equal(out[0].name, "First");
assert.deepEqual(out[0].capabilities, { vision: true });
});
test("a dup that differs only by type is NOT collapsed (distinct listing identity)", () => {
const input = [
{ id: "p/m", type: "chat" },
{ id: "p/m", type: "embedding" },
];
const out = dedupeExactCatalogIds(input);
assert.equal(out.length, 2);
});
test("preserves relative order of kept entries", () => {
const input = [
{ id: "first/a", type: "chat" },
{ id: "dup/x", type: "chat" },
{ id: "second/b", type: "chat" },
{ id: "dup/x", type: "chat" },
{ id: "third/c", type: "chat" },
];
const out = dedupeExactCatalogIds(input);
assert.deepEqual(
out.map((m) => m.id),
["first/a", "dup/x", "second/b", "third/c"]
);
});
test("empty and single-element inputs pass through", () => {
assert.deepEqual(dedupeExactCatalogIds([]), []);
const one = [{ id: "only/one" }];
assert.equal(dedupeExactCatalogIds(one).length, 1);
});
test("entries missing an id are passed through unchanged (never grouped)", () => {
const input = [{ foo: 1 } as { id?: string }, { foo: 2 } as { id?: string }];
const out = dedupeExactCatalogIds(input);
assert.equal(out.length, 2);
});

View File

@@ -75,18 +75,29 @@ test("applyNoThinkingAlias strips reasoning fields without a thinking block (Ope
});
test("applyNoThinkingAlias is a no-op for plain models", () => {
const body: Record<string, unknown> = { model: "anthropic/claude-opus-4-5", thinking: { type: "enabled" } };
const body: Record<string, unknown> = {
model: "anthropic/claude-opus-4-5",
thinking: { type: "enabled" },
};
const res = applyNoThinkingAlias(body, { claudeFormat: true });
assert.equal(res.applied, false);
assert.equal(body.model, "anthropic/claude-opus-4-5");
assert.deepEqual(body.thinking, { type: "enabled" }, "thinking is left untouched when not an alias");
assert.deepEqual(
body.thinking,
{ type: "enabled" },
"thinking is left untouched when not an alias"
);
});
test("applyNoThinkingAlias ignores a malformed prefix-only model", () => {
const body: Record<string, unknown> = { model: "claude-3-omniroute-no-thinking/" };
const res = applyNoThinkingAlias(body, { claudeFormat: true });
assert.equal(res.applied, false);
assert.equal(body.model, "claude-3-omniroute-no-thinking/", "left untouched when nothing follows the prefix");
assert.equal(
body.model,
"claude-3-omniroute-no-thinking/",
"left untouched when nothing follows the prefix"
);
});
// ── catalog gating ───────────────────────────────────────────────────────────
@@ -113,16 +124,21 @@ test("shouldExposeNoThinkingAlias rejects models where suppression is meaningles
});
test("appendNoThinkingVariants adds one variant per eligible model and preserves the rest", () => {
const models = [
entry("claude-opus-4-5"),
entry("gpt-4o", "openai"),
entry("claude-fable-5"),
];
const models = [entry("claude-opus-4-5"), entry("gpt-4o", "openai"), entry("claude-fable-5")];
const out = appendNoThinkingVariants(models);
const ids = out.map((m) => m.id);
assert.ok(ids.includes("claude-3-omniroute-no-thinking/claude-opus-4-5"), "eligible model gets a variant");
assert.ok(!ids.includes("claude-3-omniroute-no-thinking/gpt-4o"), "non-thinking model has no variant");
assert.ok(!ids.includes("claude-3-omniroute-no-thinking/claude-fable-5"), "reject-disabled model has no variant");
assert.ok(
ids.includes("claude-3-omniroute-no-thinking/claude-opus-4-5"),
"eligible model gets a variant"
);
assert.ok(
!ids.includes("claude-3-omniroute-no-thinking/gpt-4o"),
"non-thinking model has no variant"
);
assert.ok(
!ids.includes("claude-3-omniroute-no-thinking/claude-fable-5"),
"reject-disabled model has no variant"
);
assert.equal(out.length, models.length + 1, "exactly one variant appended");
// originals preserved up front
assert.deepEqual(out.slice(0, 3), models);
@@ -132,3 +148,28 @@ test("appendNoThinkingVariants returns the same array reference when nothing is
const models = [entry("gpt-4o", "openai")];
assert.equal(appendNoThinkingVariants(models), models);
});
test("appendNoThinkingVariants normalizes alias prefix to canonical when aliasToCanonical map is provided", () => {
const models = [entry("cc/claude-opus-4-5")];
const aliasToCanonical = { cc: "claude" };
const out = appendNoThinkingVariants(models, aliasToCanonical);
const ids = out.map((m) => m.id);
assert.ok(
ids.includes("claude-3-omniroute-no-thinking/claude/claude-opus-4-5"),
"uses canonical prefix"
);
assert.ok(
!ids.includes("claude-3-omniroute-no-thinking/cc/claude-opus-4-5"),
"alias prefix not used"
);
});
test("appendNoThinkingVariants keeps alias prefix when no map is provided", () => {
const models = [entry("cc/claude-opus-4-5")];
const out = appendNoThinkingVariants(models);
const ids = out.map((m) => m.id);
assert.ok(
ids.includes("claude-3-omniroute-no-thinking/cc/claude-opus-4-5"),
"alias prefix preserved"
);
});

View File

@@ -0,0 +1,130 @@
import test from "node:test";
import assert from "node:assert/strict";
import { testOAuthConnection } from "../../src/app/api/providers/[id]/test/route";
// Port of decolua/9router#347 (author: Ibrahim Ryan).
//
// Prior to this fix, the Codex OAuth test only validated `checkExpiry: true` — i.e.
// it inspected the local token's `expiresAt` and returned valid=true if the
// timestamp wasn't in the past. A token that the server has already revoked or that
// belongs to a deactivated account would still report as valid. The new test
// actually probes ChatGPT's `/backend-api/codex/responses` endpoint with a minimal
// invalid body. The endpoint returns 400 (bad request) when auth is accepted and
// 401/403 when the token is bad — exactly the signal the test should be using.
//
// Important OmniRoute-specific constraint: codex is a `rotating` provider (shares
// an Auth0 family with openai — see `rotationGroupFor`). The probe path must NOT
// burn a single-use refresh_token from a connection test (precedent: openai/codex
// #9648, see comment in route.ts above the rotating-provider guard). The probe
// only validates the access token as-is.
const CODEX_TEST_URL = "https://chatgpt.com/backend-api/codex/responses";
function futureExpiresAt(): string {
return new Date(Date.now() + 60 * 60 * 1000).toISOString();
}
function mockFetch(handler: (url: string, init?: RequestInit) => Response) {
const calls: Array<{ url: string; init?: RequestInit }> = [];
const fn = (async (url: RequestInfo | URL, init?: RequestInit) => {
const u = typeof url === "string" ? url : url instanceof URL ? url.toString() : String(url);
calls.push({ url: u, init });
return handler(u, init);
}) as typeof fetch;
return { fn, calls };
}
test("codex test probes the real /responses endpoint and treats 400 as 'auth ok' (port PR#347)", async (t) => {
const original = globalThis.fetch;
const { fn, calls } = mockFetch(
() =>
new Response(JSON.stringify({ error: { message: "Bad request" } }), {
status: 400,
headers: { "content-type": "application/json" },
})
);
globalThis.fetch = fn;
t.after(() => {
globalThis.fetch = original;
});
const result = await testOAuthConnection(
{
provider: "codex",
authType: "oauth",
accessToken: "fake-codex-token",
refreshToken: "fake-refresh",
expiresAt: futureExpiresAt(),
},
5000
);
assert.equal(result.valid, true, "400 from the real endpoint must be treated as auth ok");
assert.equal(calls.length, 1, "exactly one upstream probe");
assert.equal(calls[0].url, CODEX_TEST_URL, "must probe the actual codex /responses endpoint");
assert.equal(calls[0].init?.method, "POST");
const headers = (calls[0].init?.headers ?? {}) as Record<string, string>;
assert.equal(headers.Authorization, "Bearer fake-codex-token");
assert.ok(calls[0].init?.body, "must send a minimal body so the endpoint returns 400 (not 405)");
});
test("codex test reports invalid when the endpoint returns 401 (port PR#347)", async (t) => {
const original = globalThis.fetch;
const { fn, calls } = mockFetch(
() =>
new Response(JSON.stringify({ error: { message: "Unauthorized" } }), {
status: 401,
headers: { "content-type": "application/json" },
})
);
globalThis.fetch = fn;
t.after(() => {
globalThis.fetch = original;
});
const result = await testOAuthConnection(
{
provider: "codex",
authType: "oauth",
accessToken: "revoked-token",
// Token NOT expired locally — that's the whole point: the local check would
// have lied; the real endpoint surfaces the revocation.
refreshToken: "fake-refresh",
expiresAt: futureExpiresAt(),
},
5000
);
assert.equal(result.valid, false, "401 from the real endpoint must be reported as invalid");
assert.equal(calls.length, 1, "must NOT burn the refresh_token from a connection test (codex is a rotating provider — openai/codex#9648)");
});
test("codex test reports invalid when the endpoint returns 403 (port PR#347)", async (t) => {
const original = globalThis.fetch;
const { fn } = mockFetch(
() =>
new Response("Forbidden", {
status: 403,
headers: { "content-type": "text/plain" },
})
);
globalThis.fetch = fn;
t.after(() => {
globalThis.fetch = original;
});
const result = await testOAuthConnection(
{
provider: "codex",
authType: "oauth",
accessToken: "fake-token",
refreshToken: "fake-refresh",
expiresAt: futureExpiresAt(),
},
5000
);
assert.equal(result.valid, false);
assert.equal(result.statusCode, 403);
});

View File

@@ -0,0 +1,140 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
normalizeVscDbValue,
extractCursorTokensFromRows,
fuzzyExtractCursorTokensFromRows,
cursorDbCandidatePaths,
} from "../../src/app/api/oauth/cursor/auto-import/route";
describe("normalizeVscDbValue", () => {
it("unwraps a JSON-encoded string", () => {
assert.equal(normalizeVscDbValue('"abc"'), "abc");
});
it("returns the raw string when JSON parse fails", () => {
assert.equal(normalizeVscDbValue("not-json"), "not-json");
});
it("returns the raw string when JSON parses to non-string", () => {
assert.equal(normalizeVscDbValue("123"), "123");
assert.equal(normalizeVscDbValue("{}"), "{}");
});
it("passes non-strings through unchanged", () => {
assert.equal(normalizeVscDbValue(42 as unknown as string), 42);
assert.equal(normalizeVscDbValue(null as unknown as string), null);
});
});
describe("extractCursorTokensFromRows", () => {
it("extracts tokens using exact primary keys", () => {
const tokens = extractCursorTokensFromRows([
{ key: "cursorAuth/accessToken", value: "tok-1" },
{ key: "storage.serviceMachineId", value: "machine-1" },
]);
assert.equal(tokens.accessToken, "tok-1");
assert.equal(tokens.machineId, "machine-1");
});
it("accepts the alternative `cursorAuth/token` key", () => {
const tokens = extractCursorTokensFromRows([
{ key: "cursorAuth/token", value: "tok-2" },
{ key: "storage.machineId", value: "machine-2" },
]);
assert.equal(tokens.accessToken, "tok-2");
assert.equal(tokens.machineId, "machine-2");
});
it("accepts the alternative `telemetry.machineId` key", () => {
const tokens = extractCursorTokensFromRows([
{ key: "cursorAuth/accessToken", value: "tok-3" },
{ key: "telemetry.machineId", value: "machine-3" },
]);
assert.equal(tokens.machineId, "machine-3");
});
it("prefers the first match and ignores duplicates", () => {
const tokens = extractCursorTokensFromRows([
{ key: "cursorAuth/accessToken", value: "first" },
{ key: "cursorAuth/token", value: "second" },
]);
assert.equal(tokens.accessToken, "first");
});
it("normalizes JSON-encoded values", () => {
const tokens = extractCursorTokensFromRows([
{ key: "cursorAuth/accessToken", value: '"json-token"' },
{ key: "storage.serviceMachineId", value: '"json-machine"' },
]);
assert.equal(tokens.accessToken, "json-token");
assert.equal(tokens.machineId, "json-machine");
});
it("returns empty on no matches", () => {
const tokens = extractCursorTokensFromRows([{ key: "irrelevant", value: "x" }]);
assert.equal(tokens.accessToken, undefined);
assert.equal(tokens.machineId, undefined);
});
});
describe("fuzzyExtractCursorTokensFromRows", () => {
it("matches keys by substring containing `accesstoken` and `machineid`", () => {
const tokens = fuzzyExtractCursorTokensFromRows([
{ key: "cursorAuth/someOtherAccessTokenKey", value: "fallback-token" },
{ key: "storage.someMachineId", value: "fallback-machine" },
]);
assert.equal(tokens.accessToken, "fallback-token");
assert.equal(tokens.machineId, "fallback-machine");
});
it("preserves already-found tokens (passes existing through)", () => {
const tokens = fuzzyExtractCursorTokensFromRows(
[
{ key: "cursorAuth/someOtherAccessTokenKey", value: "fallback-token" },
{ key: "storage.someMachineId", value: "fallback-machine" },
],
{ accessToken: "already-have-it" }
);
assert.equal(tokens.accessToken, "already-have-it");
assert.equal(tokens.machineId, "fallback-machine");
});
it("is case-insensitive on the key match", () => {
const tokens = fuzzyExtractCursorTokensFromRows([
{ key: "Some.ACCESSTOKEN.suffix", value: "tok" },
{ key: "Some.MACHINEID.suffix", value: "mid" },
]);
assert.equal(tokens.accessToken, "tok");
assert.equal(tokens.machineId, "mid");
});
});
describe("cursorDbCandidatePaths", () => {
it("returns standard + Insiders paths on macOS", () => {
const paths = cursorDbCandidatePaths("darwin", { home: "/Users/test" });
assert.equal(paths.length, 2);
assert.ok(paths[0].includes("Cursor/User/globalStorage/state.vscdb"));
assert.ok(paths[1].includes("Cursor - Insiders/User/globalStorage/state.vscdb"));
});
it("returns a single path on Linux", () => {
const paths = cursorDbCandidatePaths("linux", { home: "/home/test" });
assert.deepEqual(paths, [
"/home/test/.config/Cursor/User/globalStorage/state.vscdb",
]);
});
it("returns a single path on Windows using APPDATA", () => {
const paths = cursorDbCandidatePaths("win32", {
home: "C:/Users/test",
appdata: "C:/Users/test/AppData/Roaming",
});
assert.equal(paths.length, 1);
assert.ok(paths[0].includes("Cursor/User/globalStorage/state.vscdb"));
});
it("returns empty array for unsupported platforms", () => {
assert.deepEqual(cursorDbCandidatePaths("freebsd" as NodeJS.Platform, { home: "/x" }), []);
});
});

View File

@@ -0,0 +1,42 @@
import test from "node:test";
import assert from "node:assert/strict";
import { getDefaultPricing } from "../../src/shared/constants/pricing.ts";
// Antigravity exposes Gemini 3.5 Flash via three public client IDs in
// ANTIGRAVITY_PUBLIC_MODELS (`open-sse/config/antigravityModelAliases.ts`):
// - gemini-3-flash-agent → "Gemini 3.5 Flash (High)" — upstream High tier
// - gemini-3.5-flash-low → "Gemini 3.5 Flash (Medium)" — upstream Medium tier
// - gemini-pro-agent → "Gemini 3.1 Pro (High)" — upstream Pro High alias
// All three were missing pricing rows in `ag` (DEFAULT_PRICING.ag), so
// getPricingForModel("ag", id) returned null and downstream cost / quota
// calculations silently fell back to $0. The same pricing schedule used for
// the legacy `gemini-3-flash` and `gemini-3.1-pro-high` rows applies (same
// per-MTok rates as the upstream quota tier they map to).
test("ag/gemini-3-flash-agent matches the Gemini 3.5 Flash (High) tier", () => {
const p = getDefaultPricing().ag["gemini-3-flash-agent"];
assert.equal(p.input, 0.5);
assert.equal(p.output, 3.0);
assert.equal(p.cached, 0.03);
assert.equal(p.reasoning, 4.5);
assert.equal(p.cache_creation, 0.5);
});
test("ag/gemini-3.5-flash-low matches the Gemini 3.5 Flash (Medium) tier", () => {
const p = getDefaultPricing().ag["gemini-3.5-flash-low"];
assert.equal(p.input, 0.5);
assert.equal(p.output, 3.0);
assert.equal(p.cached, 0.03);
assert.equal(p.reasoning, 4.5);
assert.equal(p.cache_creation, 0.5);
});
test("ag/gemini-pro-agent matches the Gemini 3.1 Pro (High) tier", () => {
const p = getDefaultPricing().ag["gemini-pro-agent"];
assert.equal(p.input, 4.0);
assert.equal(p.output, 18.0);
assert.equal(p.cached, 0.5);
assert.equal(p.reasoning, 27.0);
assert.equal(p.cache_creation, 4.0);
});

View File

@@ -0,0 +1,87 @@
// Port of decolua/9router PR #769 — provider dropdown filter for the quota
// dashboard. The upstream PR added the dropdown plus an "Expiring first" sort
// toggle; OmniRoute already always sorts by soonest reset within each status
// group (see ProviderLimits/index.tsx `visibleConnections`), so only the
// dropdown is genuinely new here. These tests guard the pure helpers that
// back the dropdown so regressions in the inline `useMemo` predicates fail
// loudly instead of silently widening / breaking the filter.
import test from "node:test";
import assert from "node:assert/strict";
import {
buildProviderOptions,
matchesProviderFilter,
} from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx";
test("PR#769: matchesProviderFilter — 'all' lets every connection through", () => {
assert.equal(matchesProviderFilter({ provider: "openai" }, "all"), true);
assert.equal(matchesProviderFilter({ provider: "anthropic" }, "all"), true);
// Defensive: even a malformed connection still matches the "all" sentinel
// so the dropdown's default never collapses the list to zero.
assert.equal(matchesProviderFilter({}, "all"), true);
assert.equal(matchesProviderFilter(null, "all"), true);
});
test("PR#769: matchesProviderFilter — exact match on a specific provider key", () => {
assert.equal(matchesProviderFilter({ provider: "openai" }, "openai"), true);
assert.equal(matchesProviderFilter({ provider: "openai" }, "anthropic"), false);
// Case-sensitive: provider keys are normalized upstream, so the filter must
// not match "OpenAI" against "openai" — that would silently union two
// distinct registry rows.
assert.equal(matchesProviderFilter({ provider: "OpenAI" }, "openai"), false);
});
test("PR#769: matchesProviderFilter — non-string provider is excluded under a specific filter", () => {
// Connections with a missing or non-string provider field cannot match a
// specific provider; only the "all" sentinel accepts them. This keeps
// dropdown filtering deterministic when the API returns partial rows.
assert.equal(matchesProviderFilter({}, "openai"), false);
assert.equal(matchesProviderFilter({ provider: undefined }, "openai"), false);
assert.equal(matchesProviderFilter({ provider: 42 as unknown }, "openai"), false);
assert.equal(matchesProviderFilter(null, "openai"), false);
});
test("PR#769: matchesProviderFilter — empty filter string is treated like 'all'", () => {
// Defensive: if the persisted localStorage value is somehow blanked the
// dropdown should keep listing everything instead of hiding all rows.
assert.equal(matchesProviderFilter({ provider: "openai" }, ""), true);
});
test("PR#769: buildProviderOptions — de-duplicates and sorts alphabetically", () => {
const connections = [
{ provider: "openai" },
{ provider: "anthropic" },
{ provider: "openai" },
{ provider: "gemini" },
{ provider: "anthropic" },
];
assert.deepEqual(buildProviderOptions(connections), ["anthropic", "gemini", "openai"]);
});
test("PR#769: buildProviderOptions — drops missing/empty/non-string provider keys", () => {
const connections = [
{ provider: "openai" },
{ provider: "" },
{ provider: undefined },
{ provider: null as unknown },
{ provider: 42 as unknown },
{},
{ provider: "anthropic" },
];
assert.deepEqual(buildProviderOptions(connections), ["anthropic", "openai"]);
});
test("PR#769: buildProviderOptions — honors a custom comparator (e.g. Turkish-aware)", () => {
// The production call site passes `compareTr` so locale-aware ordering
// applies. The helper must thread the comparator through Array#sort
// unchanged — verify with a reverse comparator instead of pulling in the
// real i18n helper.
const reverse = (a: string, b: string) => b.localeCompare(a);
const out = buildProviderOptions([{ provider: "a" }, { provider: "c" }, { provider: "b" }], reverse);
assert.deepEqual(out, ["c", "b", "a"]);
});
test("PR#769: buildProviderOptions — returns [] for an empty connection list", () => {
assert.deepEqual(buildProviderOptions([]), []);
});

View File

@@ -146,6 +146,7 @@ test("quota labels normalize session and weekly windows while preserving readabl
assert.equal(providerLimitUtils.formatQuotaLabel("weekly (7d)"), "Weekly");
assert.equal(providerLimitUtils.formatQuotaLabel("weekly sonnet (7d)"), "Weekly Sonnet");
assert.equal(providerLimitUtils.formatQuotaLabel("code_review"), "Code Review");
assert.equal(providerLimitUtils.formatQuotaLabel("code_review_weekly"), "Code Review Weekly");
assert.equal(providerLimitUtils.formatQuotaLabel("mcp_monthly"), "Monthly");
});

View File

@@ -0,0 +1,53 @@
import test from "node:test";
import assert from "node:assert/strict";
// #4421: creating a connection with `provider: "openai-compatible-responses"` (the bare
// derived type) returned 404 "OpenAI Compatible node not found" even though a node of
// that type existed — the handler did an exact-id lookup, but node ids carry a UUID
// suffix ("openai-compatible-responses-<uuid>"). selectProviderNodeForConnection now
// also resolves the bare type to the sole matching node.
const { selectProviderNodeForConnection, nodeTypeFromId } = await import(
"../../src/lib/db/providerNodeSelect.ts"
);
const UUID = "1715ed0f-1111-2222-3333-444455556666";
const UUID2 = "2222aaaa-1111-2222-3333-444455556666";
test("#4421 resolves by exact node id (dashboard path, unchanged)", () => {
const nodes = [{ id: `openai-compatible-responses-${UUID}`, name: "fox" }];
const r = selectProviderNodeForConnection(`openai-compatible-responses-${UUID}`, nodes);
assert.equal(r?.name, "fox");
});
test("#4421 resolves the bare base type to the sole matching node (direct-API path)", () => {
// Before the fix the handler called getProviderNodeById("openai-compatible-responses")
// — an exact-id lookup that returned null → 404.
const nodes = [{ id: `openai-compatible-responses-${UUID}`, name: "fox" }];
const r = selectProviderNodeForConnection("openai-compatible-responses", nodes);
assert.equal(r?.name, "fox");
});
test("#4421 returns null when the base type is ambiguous (more than one node)", () => {
const nodes = [
{ id: `openai-compatible-responses-${UUID}`, name: "a" },
{ id: `openai-compatible-responses-${UUID2}`, name: "b" },
];
assert.equal(selectProviderNodeForConnection("openai-compatible-responses", nodes), null);
});
test("#4421 a base type does not match a more-specific node type", () => {
// "openai-compatible" (chat) must NOT resolve an "openai-compatible-responses" node.
const nodes = [{ id: `openai-compatible-responses-${UUID}`, name: "fox" }];
assert.equal(selectProviderNodeForConnection("openai-compatible", nodes), null);
});
test("#4421 nodeTypeFromId strips a trailing UUID", () => {
assert.equal(nodeTypeFromId(`openai-compatible-responses-${UUID}`), "openai-compatible-responses");
assert.equal(nodeTypeFromId(`openai-compatible-${UUID}`), "openai-compatible");
assert.equal(nodeTypeFromId("no-uuid-here"), "no-uuid-here");
});
test("#4421 returns null when no node matches", () => {
assert.equal(selectProviderNodeForConnection("anthropic-compatible", []), null);
});

View File

@@ -0,0 +1,45 @@
/**
* Port of 9router PR #98 — add GPT-4o to GitHub Copilot (`github`/alias `gh`).
*
* Copilot still serves the original `gpt-4o` chat model via its chat/completions
* endpoint. The OmniRoute registry only ships the GPT-5.x family, so apps that
* explicitly request `gpt-4o` against the `gh` alias get an "unknown model"
* error. Adding the entry restores parity with the upstream Copilot catalog
* without disturbing the GPT-5.x / Claude / Gemini lineups already curated.
*
* GPT-4o is a chat/completions model — it must NOT use `openai-responses`.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
const { getModelsByProviderId } = await import("../../open-sse/config/providerModels.ts");
type ModelEntry = { id: string; name?: string; targetFormat?: string; [k: string]: unknown };
function githubModel(id: string): ModelEntry | undefined {
const provider = (REGISTRY as Record<string, { models?: ModelEntry[] }>)["github"];
return provider?.models?.find((m) => m.id === id);
}
test("9router#98 github/gpt-4o is registered under the gh provider", () => {
const model = githubModel("gpt-4o");
assert.ok(model, "gpt-4o must be registered under the github (gh) provider");
assert.equal(typeof model?.name, "string");
});
test("9router#98 github/gpt-4o routes via chat/completions (no openai-responses)", () => {
const model = githubModel("gpt-4o");
assert.ok(model);
assert.notEqual(
model.targetFormat,
"openai-responses",
"GPT-4o on GitHub Copilot is a chat/completions model — Responses API would reject it"
);
});
test("9router#98 getModelsByProviderId(github) exposes gpt-4o", () => {
const models = getModelsByProviderId("github") as ModelEntry[];
const gpt4o = models.find((m) => m.id === "gpt-4o");
assert.ok(gpt4o, "gpt-4o resolvable via getModelsByProviderId(github)");
});

View File

@@ -372,12 +372,12 @@ test("web-cookie provider validators surface auth and subscription failures", as
__setPplxTlsFetchOverride(async () => {
return { status: 403, headers: new Headers(), text: null, body: null };
});
__setGrokTlsFetchOverride(async () => {
return { status: 401, headers: new Headers(), text: "Unauthorized", body: null };
});
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target.includes("grok.com/rest/app-chat/conversations/new")) {
return new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 });
}
if (target.includes("app.blackbox.ai/api/auth/session")) {
const cookie = (init.headers as Record<string, string>)?.Cookie || "";
if (cookie.includes("expired-cookie")) {

View File

@@ -0,0 +1,35 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { quotaSnapshotChanged } from "@/domain/quotaCache";
// Regression guard for #4438: quota_snapshots generated 400K+ rows/day because
// setQuotaCache wrote a snapshot row for EVERY window of EVERY connection on each
// 60s background refresh, even for idle connections whose quota never changed.
// quotaSnapshotChanged() gates the write so unchanged idle connections stop
// generating rows, while the first observation and every real change still persist.
test("#4438 writes when there is no prior cache entry (baseline row)", () => {
assert.equal(quotaSnapshotChanged(null, "daily", 100, false), true);
assert.equal(quotaSnapshotChanged(undefined, "daily", 42, false), true);
});
test("#4438 writes when the window was never seen before", () => {
const prior = { quotas: { weekly: { remainingPercentage: 80 } }, exhausted: false };
assert.equal(quotaSnapshotChanged(prior, "daily", 80, false), true);
});
test("#4438 skips when remaining_percentage and is_exhausted are unchanged (idle connection)", () => {
const prior = { quotas: { daily: { remainingPercentage: 73 } }, exhausted: false };
assert.equal(quotaSnapshotChanged(prior, "daily", 73, false), false);
});
test("#4438 writes when remaining_percentage changed", () => {
const prior = { quotas: { daily: { remainingPercentage: 73 } }, exhausted: false };
assert.equal(quotaSnapshotChanged(prior, "daily", 72, false), true);
});
test("#4438 writes when is_exhausted flipped even if percentage matches", () => {
const prior = { quotas: { daily: { remainingPercentage: 0 } }, exhausted: false };
assert.equal(quotaSnapshotChanged(prior, "daily", 0, true), true);
});

View File

@@ -108,6 +108,29 @@ test("providerWindowDefaults: out-of-range values are clamped, garbage is pruned
);
});
test("#4483: auto-routing quota cutoff is OFF by default (opt-in)", () => {
// The hard cutoff overlaps the existing soft penalty + cooldown, so it must not
// change auto-routing behavior unless an operator explicitly turns it on.
const settings = cloneDefaults();
assert.equal(settings.quotaPreflight.enabled, false);
assert.equal(resolveResilienceSettings({}).quotaPreflight.enabled, false);
});
test("#4483: enabling the quota cutoff round-trips and preserves the other thresholds", () => {
const next = mergeResilienceSettings(cloneDefaults(), {
quotaPreflight: { enabled: true },
});
assert.equal(next.quotaPreflight.enabled, true);
// Toggling the switch must not disturb the threshold defaults.
assert.equal(next.quotaPreflight.defaultThresholdPercent, 2);
assert.equal(next.quotaPreflight.warnThresholdPercent, 20);
const resolved = resolveResilienceSettings({
resilienceSettings: { quotaPreflight: { enabled: true } },
});
assert.equal(resolved.quotaPreflight.enabled, true);
});
test("resolveResilienceSettings round-trips a stored providerWindowDefaults map", () => {
const stored = {
resilienceSettings: {

View File

@@ -0,0 +1,54 @@
import test from "node:test";
import assert from "node:assert/strict";
import net from "node:net";
// #4425: the supervisor treated a clean exit (code 0) as intentional and exited instead
// of restarting — but a systemd MemoryMax cgroup kill reports code 0, so the OOM'd gateway
// stayed dead. And it restarted immediately after a crash, hitting EADDRINUSE before the
// OS released the port. supervisorPolicy centralizes the restart decision + a port-wait.
const {
RESTART_RESET_MS,
DEFAULT_MAX_RESTARTS,
shouldExitInsteadOfRestart,
computeRestartDelayMs,
isPortFree,
waitUntilPortFree,
} = await import("../../bin/cli/runtime/supervisorPolicy.mjs");
test("#4425 spontaneous code-0 exit restarts (only shutdown exits)", () => {
assert.equal(shouldExitInsteadOfRestart(false), false); // OOM cgroup code-0 → restart
assert.equal(shouldExitInsteadOfRestart(true), true); // operator stop() → exit
});
test("#4425 tuned recovery constants", () => {
assert.equal(RESTART_RESET_MS, 60_000);
assert.equal(DEFAULT_MAX_RESTARTS, 3);
});
test("#4425 restart backoff is 1s,2s,4s… capped at 10s", () => {
assert.equal(computeRestartDelayMs(1), 1000);
assert.equal(computeRestartDelayMs(2), 2000);
assert.equal(computeRestartDelayMs(3), 4000);
assert.equal(computeRestartDelayMs(10), 10_000);
});
test("#4425 isPortFree detects a bound port and waitUntilPortFree resolves once released", async () => {
const server = net.createServer();
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve()));
const port = (server.address() as net.AddressInfo).port;
assert.equal(await isPortFree(port), false, "bound port is not free");
// waitUntilPortFree should time out (return false) while the port stays bound.
assert.equal(await waitUntilPortFree(port, 300, 50), false);
await new Promise<void>((resolve) => server.close(() => resolve()));
assert.equal(await isPortFree(port), true, "released port is free");
assert.equal(await waitUntilPortFree(port, 1000, 50), true);
});
test("#4425 waitUntilPortFree no-ops on an invalid port", async () => {
assert.equal(await waitUntilPortFree(undefined), true);
assert.equal(await waitUntilPortFree(0), true);
});

View File

@@ -0,0 +1,84 @@
// Regression for upstream PR decolua/9router#913 — OpenAI `input_audio` and
// `audio_url` content parts must be forwarded as Gemini `inlineData` audio parts
// (instead of being silently dropped) so that callers can send audio (WAV/MP3/etc.)
// to Gemini models via the Antigravity / Gemini / Gemini-CLI translation paths.
import test from "node:test";
import assert from "node:assert/strict";
const { convertOpenAIContentToParts } = await import(
"../../open-sse/translator/helpers/geminiHelper.ts"
);
const { VALID_OPENAI_CONTENT_TYPES, filterToOpenAIFormat } = await import(
"../../open-sse/translator/helpers/openaiHelper.ts"
);
type Part = { inlineData?: { mimeType: string; data: string } };
test("convertOpenAIContentToParts handles input_audio with explicit wav format (#913)", () => {
const parts = convertOpenAIContentToParts([
{ type: "text", text: "Transcribe this" },
{ type: "input_audio", input_audio: { data: "UklGRiQ", format: "wav" } },
]) as Part[];
const inline = parts.find((p) => p.inlineData);
assert.ok(inline, "input_audio must produce an inlineData part");
assert.equal(inline!.inlineData!.mimeType, "audio/wav");
assert.equal(inline!.inlineData!.data, "UklGRiQ");
});
test("convertOpenAIContentToParts maps input_audio mp3 -> audio/mpeg mime (#913)", () => {
const parts = convertOpenAIContentToParts([
{ type: "input_audio", input_audio: { data: "SUQzBAA", format: "mp3" } },
]) as Part[];
const inline = parts.find((p) => p.inlineData);
assert.ok(inline, "input_audio mp3 must produce an inlineData part");
assert.equal(
inline!.inlineData!.mimeType,
"audio/mpeg",
"mp3 must canonicalize to audio/mpeg per RFC 3003"
);
});
test("convertOpenAIContentToParts defaults input_audio without format to audio/wav (#913)", () => {
const parts = convertOpenAIContentToParts([
{ type: "input_audio", input_audio: { data: "AAAA" } },
]) as Part[];
const inline = parts.find((p) => p.inlineData);
assert.ok(inline, "input_audio without format must still produce an inlineData part");
assert.equal(inline!.inlineData!.mimeType, "audio/wav");
});
test("convertOpenAIContentToParts handles audio_url data URI (#913)", () => {
const parts = convertOpenAIContentToParts([
{ type: "audio_url", audio_url: { url: "data:audio/wav;base64,UklGRiQ" } },
]) as Part[];
const inline = parts.find((p) => p.inlineData);
assert.ok(inline, "audio_url data URI must produce an inlineData part");
assert.equal(inline!.inlineData!.mimeType, "audio/wav");
assert.equal(inline!.inlineData!.data, "UklGRiQ");
});
test("input_audio and audio_url are preserved by filterToOpenAIFormat (#913)", () => {
// For OpenAI-target routes (passthrough), audio parts must not be stripped
// out by the content-type allowlist.
assert.ok(VALID_OPENAI_CONTENT_TYPES.includes("input_audio"));
assert.ok(VALID_OPENAI_CONTENT_TYPES.includes("audio_url"));
const body = {
messages: [
{
role: "user",
content: [
{ type: "text", text: "What is this?" },
{ type: "input_audio", input_audio: { data: "AAAA", format: "wav" } },
{ type: "audio_url", audio_url: { url: "data:audio/wav;base64,AAAA" } },
],
},
],
};
const filtered = filterToOpenAIFormat(body);
const content = filtered.messages[0].content as Array<Record<string, unknown>>;
const types = content.map((c) => c.type);
assert.ok(types.includes("input_audio"), "input_audio survives passthrough filter");
assert.ok(types.includes("audio_url"), "audio_url survives passthrough filter");
});

View File

@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
// Source-level parity check (#241): AntigravityToolCard must surface
// API-Key-compatible / custom OpenAI-compatible providers in its model picker.
// Those provider groups in <ModelSelectModal> are derived from `modelAliases`
// — without the prop, custom-keyed providers are silently hidden even when
// active. The fix mirrors the pattern already used by every sibling CLI tool
// card (Codex, Claude, Cline, Kilo, Droid, OpenClaw, HermesAgent).
const __dirname = dirname(fileURLToPath(import.meta.url));
const CARD_PATH = resolve(
__dirname,
"../../../src/app/(dashboard)/dashboard/cli-code/components/AntigravityToolCard.tsx"
);
describe("AntigravityToolCard model alias wiring", () => {
const source = readFileSync(CARD_PATH, "utf8");
it("declares modelAliases state", () => {
expect(source).toMatch(/useState\(\{\}\)/);
expect(source).toMatch(/setModelAliases/);
});
it("fetches /api/models/alias when expanded", () => {
expect(source).toContain('fetch("/api/models/alias")');
expect(source).toMatch(/fetchModelAliases\s*\(\s*\)/);
});
it("passes modelAliases prop to ModelSelectModal", () => {
// Regression guard for upstream parity: the prop is what unlocks the
// API-Key-compatible / passthrough provider groups in the picker.
expect(source).toMatch(/modelAliases=\{modelAliases\}/);
});
});

View File

@@ -0,0 +1,66 @@
// Pure unit test for the deselect filter semantics used by ComboFormModal in
// src/app/(dashboard)/dashboard/combos/page.tsx — ported from upstream PR
// decolua/9router#889 (Fajar Hidayat).
//
// The page-level handler removes every step whose qualified `model` matches
// the value sent from the ModelSelectModal, matching upstream JS behavior
// (`setModels(models.filter((m) => m !== model.value))`). Duplicates with
// different providerId/weight pointing at the same model id are all stripped.
import { describe, expect, it } from "vitest";
type Step = { model: string; providerId?: string; weight?: number };
// Mirror of src/app/(dashboard)/dashboard/combos/page.tsx::handleDeselectModel.
// Kept in lockstep so the same filter behavior is checked here, leaf-only.
function deselectModel(models: Step[], model: { value?: string } | string): Step[] {
const value =
typeof (model as any)?.value === "string"
? (model as any).value
: typeof model === "string"
? model
: "";
if (!value) return models;
return models.filter((m) => m.model !== value);
}
describe("ComboFormModal deselect handler (upstream PR #889)", () => {
it("removes the single matching step when called with { value }", () => {
const models: Step[] = [
{ model: "openai/gpt-4o", weight: 50 },
{ model: "anthropic/claude-3-5-sonnet", weight: 50 },
];
const next = deselectModel(models, { value: "openai/gpt-4o" });
expect(next).toEqual([{ model: "anthropic/claude-3-5-sonnet", weight: 50 }]);
});
it("strips every duplicate of the same qualified model", () => {
const models: Step[] = [
{ model: "openai/gpt-4o", providerId: "openai-a", weight: 30 },
{ model: "openai/gpt-4o", providerId: "openai-b", weight: 70 },
{ model: "anthropic/claude-3-5-sonnet", weight: 0 },
];
const next = deselectModel(models, { value: "openai/gpt-4o" });
expect(next).toEqual([{ model: "anthropic/claude-3-5-sonnet", weight: 0 }]);
});
it("is a no-op when the value is not in the list", () => {
const models: Step[] = [{ model: "openai/gpt-4o", weight: 100 }];
const next = deselectModel(models, { value: "openai/gpt-3.5" });
expect(next).toEqual(models);
});
it("is a no-op for empty/missing value (defensive guard)", () => {
const models: Step[] = [{ model: "openai/gpt-4o", weight: 100 }];
expect(deselectModel(models, { value: "" })).toEqual(models);
expect(deselectModel(models, {})).toEqual(models);
});
it("accepts a raw string model identifier (legacy upstream call shape)", () => {
const models: Step[] = [
{ model: "openai/gpt-4o", weight: 50 },
{ model: "anthropic/claude-3-5-sonnet", weight: 50 },
];
const next = deselectModel(models, "openai/gpt-4o");
expect(next).toEqual([{ model: "anthropic/claude-3-5-sonnet", weight: 50 }]);
});
});

View File

@@ -0,0 +1,148 @@
// @vitest-environment jsdom
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
const containers: HTMLElement[] = [];
const roots: Array<{ unmount: () => void }> = [];
function mount(ui: React.ReactElement): HTMLElement {
const container = document.createElement("div");
document.body.appendChild(container);
containers.push(container);
const root = createRoot(container);
roots.push(root);
act(() => {
root.render(ui);
});
return container;
}
beforeEach(() => {
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
});
afterEach(async () => {
vi.restoreAllMocks();
await act(async () => {
while (roots.length > 0) roots.pop()?.unmount();
});
for (let i = 0; i < 10; i++) await Promise.resolve();
while (containers.length > 0) containers.pop()?.remove();
document.body.innerHTML = "";
});
async function flush() {
await act(async () => {
for (let i = 0; i < 10; i++) await Promise.resolve();
});
}
interface CapturedPut {
url: string;
body: Record<string, unknown>;
}
function setupFetchMock(): { puts: CapturedPut[] } {
const puts: CapturedPut[] = [];
const json = (body: unknown, status = 200) =>
new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
const initialConfig = {
enabled: true,
defaultMode: "off",
autoTriggerTokens: 0,
cacheMinutes: 5,
preserveSystemPrompt: true,
comboOverrides: {},
activeComboId: null,
contextEditing: { enabled: false },
};
const combos = [{ id: "c1", name: "RTK only", pipeline: [{ engine: "rtk" }] }];
vi.spyOn(globalThis, "fetch").mockImplementation(
async (input: RequestInfo | URL, init?: RequestInit) => {
const url = input.toString();
const method = (init?.method ?? "GET").toUpperCase();
if (url.includes("/api/context/combos/default")) return json({}, 404);
if (url.includes("/api/context/combos")) return json({ combos });
if (url.includes("/api/compression/engines")) return json({ engines: [] });
if (url.includes("/api/settings/compression")) {
if (method === "PUT") {
const body = JSON.parse(String(init?.body ?? "{}"));
puts.push({ url, body });
return json({ ...initialConfig, ...body });
}
return json(initialConfig);
}
return json({}, 404);
}
);
return { puts };
}
function setSelectValue(select: HTMLSelectElement, value: string) {
const setter = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, "value")!.set!;
setter.call(select, value);
select.dispatchEvent(new Event("change", { bubbles: true }));
}
describe("CompressionHub — active-profile selector", () => {
async function render() {
const { default: CompressionHub } = await import(
"../../../src/app/(dashboard)/dashboard/context/combos/CompressionHub"
);
let container!: HTMLElement;
await act(async () => {
container = mount(<CompressionHub />);
});
await flush();
return container;
}
it("renders the active-profile select with Default + each named combo", async () => {
setupFetchMock();
const container = await render();
const select = container.querySelector('[data-testid="active-profile-select"]') as HTMLSelectElement | null;
expect(select).toBeTruthy();
expect(container.textContent).toContain("Default (from panel)");
expect(container.textContent).toContain("RTK only");
});
it("changing the select to a combo PUTs activeComboId === that id", async () => {
const { puts } = setupFetchMock();
const container = await render();
const select = container.querySelector('[data-testid="active-profile-select"]') as HTMLSelectElement;
await act(async () => {
setSelectValue(select, "c1");
});
await flush();
const settingsPuts = puts.filter((p) => p.url.includes("/api/settings/compression"));
expect(settingsPuts.length).toBeGreaterThan(0);
expect(settingsPuts.pop()!.body.activeComboId).toBe("c1");
});
it("preview shows the Default fallback initially, and the combo engines once a combo is active", async () => {
setupFetchMock();
const container = await render();
const preview = () => container.querySelector('[data-testid="active-profile-preview"]');
expect(preview()).toBeTruthy();
expect(preview()!.textContent).toContain("Default");
const select = container.querySelector('[data-testid="active-profile-select"]') as HTMLSelectElement;
await act(async () => {
setSelectValue(select, "c1");
});
await flush();
expect(preview()!.textContent).toContain("rtk");
});
it("no longer renders the master Token Saver toggle, the mode selector, or reorder buttons", async () => {
setupFetchMock();
const container = await render();
expect(container.querySelector('[aria-label="Toggle Token Saver"]')).toBeNull();
expect(container.querySelector('[aria-label="Move up"]')).toBeNull();
expect(container.querySelector('[aria-label="Move down"]')).toBeNull();
// The Aggressive mode button's hint text is gone with the mode selector.
expect(container.textContent).not.toContain("Summary plus aging");
});
});

View File

@@ -0,0 +1,202 @@
// @vitest-environment jsdom
//
// Port of decolua/9router PR #889 (Fajar Hidayat <fajarhide@gmail.com>):
// "Add model deselection functionality in ComboFormModal and ComboDetailPage".
//
// Upstream UX: clicking an already-added (highlighted) model in ModelSelectModal
// should TOGGLE — invoke onDeselect instead of onSelect — and the modal must stay
// open when keepOpenOnSelect so the user can add/remove several models in one
// session. OmniRoute already had the visual highlight (addedModelValues) but no
// deselect callback nor an opt-out for the auto-close — added here.
import React from "react";
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => key,
}));
const { default: ModelSelectModal } = await import("@/shared/components/ModelSelectModal");
const containers: HTMLElement[] = [];
async function renderModal(props: Partial<React.ComponentProps<typeof ModelSelectModal>> = {}) {
const container = document.createElement("div");
document.body.appendChild(container);
containers.push(container);
const root = createRoot(container);
await act(async () => {
root.render(
<ModelSelectModal
isOpen={true}
onClose={() => {}}
onSelect={() => {}}
showCombos={false}
activeProviders={[{ provider: "openai" }]}
{...props}
/>
);
});
await act(async () => {});
return { container, root };
}
function findModelButton(container: HTMLElement, label: string): HTMLButtonElement | null {
const buttons = Array.from(container.querySelectorAll("button"));
return (buttons.find((b) => (b.textContent || "").includes(label)) as HTMLButtonElement) || null;
}
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
vi.stubGlobal(
"fetch",
vi.fn(async (url: string) => {
if (url === "/api/provider-nodes") {
return { ok: true, json: async () => ({ nodes: [] }) };
}
if (url === "/api/provider-models") {
return { ok: true, json: async () => ({ models: {} }) };
}
if (url === "/api/combos") {
return { ok: true, json: async () => ({ combos: [] }) };
}
return { ok: true, json: async () => ({}) };
})
);
});
afterEach(() => {
while (containers.length > 0) {
containers.pop()?.remove();
}
document.body.innerHTML = "";
vi.unstubAllGlobals();
});
describe("ModelSelectModal — deselect / toggle behavior (upstream PR #889)", () => {
it("calls onDeselect (not onSelect) when clicking a model already in addedModelValues", async () => {
const onSelect = vi.fn();
const onDeselect = vi.fn();
// We don't know exactly which OpenAI model labels the system catalog ships at
// any given time, so probe the rendered DOM for one and use its value.
const { container } = await renderModal({
onSelect,
onDeselect,
addedModelValues: [],
});
// Find the first OpenAI model rendered (it must exist — openai is an active provider).
const firstModelButton = container.querySelector(
"button[class*='hover:border-primary']"
) as HTMLButtonElement | null;
expect(firstModelButton, "expected at least one openai model button to render").not.toBeNull();
const modelName = (firstModelButton!.textContent || "").trim();
expect(modelName.length).toBeGreaterThan(0);
// First click — model is NOT yet added → must call onSelect, not onDeselect.
await act(async () => {
firstModelButton!.click();
});
expect(onSelect).toHaveBeenCalledTimes(1);
expect(onDeselect).not.toHaveBeenCalled();
// The handler we got back from onSelect should be a model object with .value.
const selectedArg = onSelect.mock.calls[0][0];
expect(selectedArg).toMatchObject({ value: expect.any(String) });
});
it("toggles to onDeselect when the model is already in addedModelValues", async () => {
const onSelect = vi.fn();
const onDeselect = vi.fn();
// Render once to discover the model value
const probe = await renderModal({ onSelect: vi.fn(), onDeselect: vi.fn() });
const probeButton = probe.container.querySelector(
"button[class*='hover:border-primary']"
) as HTMLButtonElement | null;
expect(probeButton).not.toBeNull();
// The on-click handler embeds the model value; trigger once to capture it.
const tempSelect = vi.fn();
const probe2 = await renderModal({ onSelect: tempSelect, addedModelValues: [] });
const probeButton2 = probe2.container.querySelector(
"button[class*='hover:border-primary']"
) as HTMLButtonElement | null;
await act(async () => {
probeButton2!.click();
});
const capturedValue = tempSelect.mock.calls[0][0].value as string;
expect(typeof capturedValue).toBe("string");
expect(capturedValue.length).toBeGreaterThan(0);
// Now render with that value pre-added and click again — must invoke onDeselect.
const { container } = await renderModal({
onSelect,
onDeselect,
addedModelValues: [capturedValue],
keepOpenOnSelect: true,
});
// The already-added model now renders with the emerald/added class — look for ✓.
const addedButton = Array.from(container.querySelectorAll("button")).find((b) =>
(b.textContent || "").includes("✓")
) as HTMLButtonElement | undefined;
expect(addedButton, "expected an added (✓) button to render").toBeDefined();
await act(async () => {
addedButton!.click();
});
expect(onDeselect).toHaveBeenCalledTimes(1);
expect(onSelect).not.toHaveBeenCalled();
expect(onDeselect.mock.calls[0][0]).toMatchObject({ value: capturedValue });
});
it("does NOT auto-close the modal when keepOpenOnSelect is true", async () => {
const onSelect = vi.fn();
const onClose = vi.fn();
const { container } = await renderModal({
onSelect,
onClose,
keepOpenOnSelect: true,
});
const firstModelButton = container.querySelector(
"button[class*='hover:border-primary']"
) as HTMLButtonElement | null;
expect(firstModelButton).not.toBeNull();
await act(async () => {
firstModelButton!.click();
});
expect(onSelect).toHaveBeenCalledTimes(1);
expect(onClose).not.toHaveBeenCalled();
});
it("still auto-closes by default (keepOpenOnSelect defaults to false, backward-compat)", async () => {
const onSelect = vi.fn();
const onClose = vi.fn();
const { container } = await renderModal({ onSelect, onClose });
const firstModelButton = container.querySelector(
"button[class*='hover:border-primary']"
) as HTMLButtonElement | null;
expect(firstModelButton).not.toBeNull();
await act(async () => {
firstModelButton!.click();
});
expect(onSelect).toHaveBeenCalledTimes(1);
expect(onClose).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,119 @@
// @vitest-environment jsdom
//
// Regression coverage for the "keepOpenOnSelect" prop added in feat/port-pr-1031.
// Mirrors the UX of upstream PR decolua/9router#1031: when a caller (e.g. combo
// creation) opts out of the auto-close-on-select behaviour, the modal renders a
// "Done" button in the footer so the user has a clear way to confirm they are
// finished adding entries.
import React from "react";
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => key,
}));
const { default: ModelSelectModal } = await import("@/shared/components/ModelSelectModal");
const containers: HTMLElement[] = [];
async function renderModal(props: Partial<React.ComponentProps<typeof ModelSelectModal>> = {}) {
const container = document.createElement("div");
document.body.appendChild(container);
containers.push(container);
const root = createRoot(container);
await act(async () => {
root.render(
<ModelSelectModal
isOpen={true}
onClose={() => {}}
onSelect={() => {}}
showCombos={false}
activeProviders={[]}
{...props}
/>
);
});
await act(async () => {});
return container;
}
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
vi.stubGlobal(
"fetch",
vi.fn(async (url: string) => {
if (url === "/api/provider-nodes") {
return { ok: true, json: async () => ({ nodes: [] }) };
}
if (url === "/api/provider-models") {
return { ok: true, json: async () => ({ models: {} }) };
}
if (url === "/api/combos") {
return { ok: true, json: async () => ({ combos: [] }) };
}
return { ok: true, json: async () => ({}) };
})
);
});
afterEach(() => {
while (containers.length > 0) {
containers.pop()?.remove();
}
document.body.innerHTML = "";
vi.unstubAllGlobals();
});
describe("ModelSelectModal keepOpenOnSelect", () => {
it("does not render the Done button by default (auto-close behaviour preserved)", async () => {
const container = await renderModal();
const doneButton = Array.from(container.querySelectorAll("button")).find(
(b) => b.textContent?.trim() === "done"
);
expect(doneButton).toBeUndefined();
});
it("renders the Done button when keepOpenOnSelect is true", async () => {
const container = await renderModal({ keepOpenOnSelect: true });
const doneButton = Array.from(container.querySelectorAll("button")).find(
(b) => b.textContent?.trim() === "done"
);
expect(doneButton).toBeDefined();
});
it("clicking Done triggers onClose without invoking onSelect again", async () => {
const onClose = vi.fn();
const onSelect = vi.fn();
const container = await renderModal({ keepOpenOnSelect: true, onClose, onSelect });
const doneButton = Array.from(container.querySelectorAll("button")).find(
(b) => b.textContent?.trim() === "done"
);
expect(doneButton).toBeDefined();
await act(async () => {
doneButton!.click();
});
expect(onClose).toHaveBeenCalledTimes(1);
expect(onSelect).not.toHaveBeenCalled();
});
it("does not render the Done button when multiSelect is true (multiSelect owns its own footer)", async () => {
// multiSelect already ships a Clear + Done footer driven by selectedModels.
// keepOpenOnSelect must defer to it to avoid two competing Done buttons.
const container = await renderModal({ keepOpenOnSelect: true, multiSelect: true });
const doneButtons = Array.from(container.querySelectorAll("button")).filter(
(b) => b.textContent?.trim() === "done"
);
// Exactly one Done button — the one inside the multiSelect footer, not a duplicate.
expect(doneButtons.length).toBe(1);
});
});

View File

@@ -0,0 +1,95 @@
// @vitest-environment jsdom
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
const containers: HTMLElement[] = [];
const roots: Array<{ unmount: () => void }> = [];
function mount(ui: React.ReactElement): HTMLElement {
const container = document.createElement("div");
document.body.appendChild(container);
containers.push(container);
const root = createRoot(container);
roots.push(root);
act(() => {
root.render(ui);
});
return container;
}
beforeEach(() => {
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
});
afterEach(async () => {
vi.restoreAllMocks();
await act(async () => {
while (roots.length > 0) roots.pop()?.unmount();
});
for (let i = 0; i < 10; i++) await Promise.resolve();
while (containers.length > 0) containers.pop()?.remove();
document.body.innerHTML = "";
});
async function flush() {
await act(async () => {
for (let i = 0; i < 10; i++) await Promise.resolve();
});
}
function combo(id: string, name: string) {
return {
id,
name,
description: `${name} desc`,
pipeline: [{ engine: "rtk", intensity: "standard" }],
languagePacks: ["en"],
outputMode: false,
outputModeIntensity: "full",
isDefault: false,
};
}
function setupFetchMock() {
const json = (body: unknown, status = 200) =>
new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
const combos = [combo("c1", "Alpha"), combo("c2", "Bravo")];
vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = input.toString();
void init;
if (url.includes("/api/context/combos/") && url.includes("/assignments")) return json({ assignments: [] });
if (url.includes("/api/context/combos")) return json({ combos });
if (url.includes("/api/combos")) return json({ combos: [] });
if (url.includes("/api/compression/language-packs")) return json({ packs: [] });
if (url.includes("/api/settings/compression")) return json({ activeComboId: "c2", enabled: true });
return json({}, 404);
});
}
describe("NamedCombosManager — active badge, no set-as-default", () => {
async function render() {
const { default: CompressionCombosPageClient } = await import(
"../../../src/app/(dashboard)/dashboard/context/combos/CompressionCombosPageClient"
);
let container!: HTMLElement;
await act(async () => {
container = mount(<CompressionCombosPageClient />);
});
await flush();
return container;
}
it("renders no 'Set as default' button", async () => {
setupFetchMock();
const container = await render();
expect(container.textContent).not.toContain("Set as default");
});
it("shows the '● Active' badge only on the combo whose id === activeComboId", async () => {
setupFetchMock();
const container = await render();
expect(container.querySelector('[data-testid="active-badge-c2"]')).toBeTruthy();
expect(container.querySelector('[data-testid="active-badge-c1"]')).toBeNull();
});
});

View File

@@ -0,0 +1,130 @@
// @vitest-environment jsdom
import React, { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useProviderBreakerHealth } from "../../../src/hooks/useProviderBreakerHealth";
type Deferred<T> = {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: unknown) => void;
};
function deferred<T>(): Deferred<T> {
let resolve!: (value: T) => void;
let reject!: (error: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
function okResponse(body: unknown): Response {
return {
ok: true,
json: async () => body,
} as Response;
}
describe("useProviderBreakerHealth", () => {
let root: Root | null = null;
let container: HTMLDivElement | null = null;
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
vi.useFakeTimers();
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
if (root) {
act(() => root?.unmount());
}
root = null;
container?.remove();
container = null;
document.body.innerHTML = "";
vi.useRealTimers();
vi.unstubAllGlobals();
});
it("skips overlapping health polls while a request is in flight", async () => {
const first = deferred<Response>();
const second = deferred<Response>();
const fetchMock = vi
.fn<typeof fetch>()
.mockReturnValueOnce(first.promise)
.mockReturnValueOnce(second.promise);
vi.stubGlobal("fetch", fetchMock);
function Probe() {
const snapshot = useProviderBreakerHealth(5000);
return <output>{Object.keys(snapshot.providerHealth).join(",")}</output>;
}
await act(async () => {
root = createRoot(container!);
root.render(<Probe />);
});
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0]?.[1]).toMatchObject({ cache: "no-store" });
await act(async () => {
vi.advanceTimersByTime(5000);
});
expect(fetchMock).toHaveBeenCalledTimes(1);
await act(async () => {
first.resolve(
okResponse({
providerHealth: { openai: { state: "closed" } },
connectionHealth: {},
})
);
await first.promise;
});
expect(container!.textContent).toBe("openai");
await act(async () => {
vi.advanceTimersByTime(5000);
});
expect(fetchMock).toHaveBeenCalledTimes(2);
second.resolve(okResponse({ providerHealth: {}, connectionHealth: {} }));
});
it("aborts an active health poll on unmount", async () => {
let signal: AbortSignal | undefined;
const fetchMock = vi.fn<typeof fetch>((_input, init) => {
signal = init?.signal ?? undefined;
return new Promise<Response>(() => {});
});
vi.stubGlobal("fetch", fetchMock);
function Probe() {
useProviderBreakerHealth(5000);
return null;
}
await act(async () => {
root = createRoot(container!);
root.render(<Probe />);
});
expect(signal?.aborted).toBe(false);
act(() => {
root?.unmount();
root = null;
});
expect(signal?.aborted).toBe(true);
});
});

View File

@@ -60,6 +60,21 @@ describe("parseResetTime", () => {
it("returns null for invalid date strings", () => {
assert.equal(__testing.parseResetTime("not-a-date"), null);
});
// Inspired-by upstream decolua/9router#768 — provider APIs sometimes return
// the reset timestamp as a numeric string. Without explicit detection,
// `new Date("1700000000")` returns Invalid Date and the value is lost.
it("parses a numeric string in seconds (value < 1e12)", () => {
const sec = "1700000000";
const out = __testing.parseResetTime(sec);
assert.equal(out, new Date(Number(sec) * 1000).toISOString());
});
it("parses a numeric string already in milliseconds (value >= 1e12)", () => {
const ms = "1700000000000";
const out = __testing.parseResetTime(ms);
assert.equal(out, new Date(Number(ms)).toISOString());
});
});
/* ------------------------------------------------------------------ */

View File

@@ -1115,9 +1115,12 @@ test("vscode tokenized /chat/completions route applies the path token and codex
);
const body = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(body.error?.code, "bad_request");
assert.equal(body.error?.message, "No credentials for provider: codex");
// Upstream port decolua/9router#336: zero-active-credentials now surfaces as
// 404 (combo-fallbackable) instead of 400 (combo hard-stop). The 404 OpenAI
// error code mapping is "model_not_found" (open-sse/config/errorConfig.ts:29).
assert.equal(response.status, 404);
assert.equal(body.error?.code, "model_not_found");
assert.equal(body.error?.message, "No active credentials for provider: codex");
});
test("vscode tokenized /responses route applies the path token and codex tier rewrite", async () => {
@@ -1142,9 +1145,10 @@ test("vscode tokenized /responses route applies the path token and codex tier re
);
const body = (await response.json()) as any;
assert.equal(response.status, 400);
assert.equal(body.error?.code, "bad_request");
assert.equal(body.error?.message, "No credentials for provider: codex");
// Upstream port decolua/9router#336: see chat/completions sibling test above.
assert.equal(response.status, 404);
assert.equal(body.error?.code, "model_not_found");
assert.equal(body.error?.message, "No active credentials for provider: codex");
});
test("vscode tokenized api/show route preserves the selected reasoning effort for codex variants", async () => {

View File

@@ -0,0 +1,151 @@
import test from "node:test";
import assert from "node:assert/strict";
// #4481 layer 2 — CCR-style `Router.webSearch`. When a request carries a NATIVE
// web-search server tool (`web_search`, `web_search_preview`, or Anthropic's versioned
// `web_search_20250305`) and an operator configured `webSearchRouteModel`, route the
// whole request to that model instead of the default — so a provider that doesn't
// implement the server tool (e.g. MiniMax) isn't asked to run a tool it 400s on.
// Pure helpers, no DB.
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import {
hasNativeWebSearchTool,
resolveWebSearchRouteOverride,
} from "../../open-sse/services/webSearchRouting.ts";
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
// ── Wiring source-guard (RED on base: chat.ts doesn't call the router yet) ─
test("chat.ts wires the web-search router at the request entrypoint", () => {
const chat = readFileSync(join(REPO_ROOT, "src/sse/handlers/chat.ts"), "utf8");
assert.match(chat, /from "@omniroute\/open-sse\/services\/webSearchRouting\.ts"/);
assert.match(chat, /hasNativeWebSearchTool\(body\)/);
assert.match(chat, /resolveWebSearchRouteOverride\(/);
});
test("webSearchRouteModel is registered in the settings Zod schema", () => {
const schema = readFileSync(join(REPO_ROOT, "src/shared/validation/settingsSchemas.ts"), "utf8");
assert.match(schema, /webSearchRouteModel:\s*z\.string\(\)/);
});
test("the Routing settings tab exposes a webSearchRouteModel field", () => {
const tab = readFileSync(
join(REPO_ROOT, "src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx"),
"utf8"
);
assert.match(tab, /settings\.webSearchRouteModel/);
assert.match(tab, /updateSetting\(\{ webSearchRouteModel:/);
assert.match(tab, /webSearchRouteTitle/);
});
test("en.json defines the web-search-routing UI strings", () => {
const en = JSON.parse(readFileSync(join(REPO_ROOT, "src/i18n/messages/en.json"), "utf8"));
const s = en.settings || {};
assert.equal(typeof s.webSearchRouteTitle, "string");
assert.equal(typeof s.webSearchRouteDesc, "string");
assert.equal(typeof s.webSearchRoutePlaceholder, "string");
});
// ── hasNativeWebSearchTool ───────────────────────────────────────────────
test("detects the plain and preview native web-search tool types", () => {
assert.equal(hasNativeWebSearchTool({ tools: [{ type: "web_search" }] }), true);
assert.equal(hasNativeWebSearchTool({ tools: [{ type: "web_search_preview" }] }), true);
});
test("detects Anthropic's versioned web_search_20250305 (and future dated names)", () => {
assert.equal(
hasNativeWebSearchTool({ tools: [{ type: "web_search_20250305", name: "web_search" }] }),
true
);
assert.equal(hasNativeWebSearchTool({ tools: [{ type: "web_search_20251201" }] }), true);
});
test("detects a native web-search tool anywhere in a mixed tools array", () => {
assert.equal(
hasNativeWebSearchTool({
tools: [{ type: "function", function: { name: "x" } }, { type: "web_search_20250305" }],
}),
true
);
});
test("ignores a custom FUNCTION tool merely named web_search (has a function field)", () => {
assert.equal(
hasNativeWebSearchTool({
tools: [{ type: "function", function: { name: "web_search", parameters: {} } }],
}),
false
);
// A bare web_search type WITH a function field is also not the native server tool.
assert.equal(
hasNativeWebSearchTool({ tools: [{ type: "web_search", function: { name: "x" } }] }),
false
);
});
test("returns false for no tools / non-web-search tools / malformed input", () => {
assert.equal(hasNativeWebSearchTool({ tools: [] }), false);
assert.equal(hasNativeWebSearchTool({ tools: [{ type: "code_interpreter" }] }), false);
assert.equal(hasNativeWebSearchTool({}), false);
assert.equal(hasNativeWebSearchTool({ tools: "nope" }), false);
assert.equal(hasNativeWebSearchTool(null), false);
assert.equal(hasNativeWebSearchTool(undefined), false);
});
// ── resolveWebSearchRouteOverride ────────────────────────────────────────
const bodyWithSearch = { tools: [{ type: "web_search_20250305", name: "web_search" }] };
test("routes to the configured model when a native web-search tool is present", () => {
const r = resolveWebSearchRouteOverride("minimax,MiniMax-M3", bodyWithSearch, {
webSearchRouteModel: "openrouter,anthropic/claude-3.5-sonnet",
});
assert.deepEqual(r, { wasRouted: true, model: "openrouter,anthropic/claude-3.5-sonnet" });
});
test("does NOT route when the request has no native web-search tool", () => {
const r = resolveWebSearchRouteOverride("minimax,MiniMax-M3", { tools: [{ type: "function" }] }, {
webSearchRouteModel: "openrouter,anthropic/claude-3.5-sonnet",
});
assert.deepEqual(r, { wasRouted: false, model: "minimax,MiniMax-M3" });
});
test("does NOT route when no route model is configured (or it is blank)", () => {
assert.deepEqual(resolveWebSearchRouteOverride("minimax,MiniMax-M3", bodyWithSearch, {}), {
wasRouted: false,
model: "minimax,MiniMax-M3",
});
assert.deepEqual(
resolveWebSearchRouteOverride("minimax,MiniMax-M3", bodyWithSearch, {
webSearchRouteModel: " ",
}),
{ wasRouted: false, model: "minimax,MiniMax-M3" }
);
});
test("does NOT route (no-op) when the configured model equals the current model", () => {
const r = resolveWebSearchRouteOverride("openrouter,claude", bodyWithSearch, {
webSearchRouteModel: " openrouter,claude ",
});
assert.deepEqual(r, { wasRouted: false, model: "openrouter,claude" });
});
test("trims the configured route model", () => {
const r = resolveWebSearchRouteOverride("minimax,M3", bodyWithSearch, {
webSearchRouteModel: " anthropic/claude-opus-4-8 ",
});
assert.deepEqual(r, { wasRouted: true, model: "anthropic/claude-opus-4-8" });
});
test("ignores a non-string route config", () => {
const r = resolveWebSearchRouteOverride("minimax,M3", bodyWithSearch, {
webSearchRouteModel: 123 as unknown as string,
});
assert.deepEqual(r, { wasRouted: false, model: "minimax,M3" });
});

View File

@@ -36,6 +36,41 @@ test("buildTelegramPayload — request.failed includes model and event label", (
assert.equal(payload.parse_mode, "Markdown");
});
test("buildTelegramPayload — request.completed includes provider, account, combo, and metrics", () => {
const payload = buildTelegramPayload(
"request.completed",
{
model: "codex/gpt-5.5",
provider: "codex",
account: "Workspace Principal",
combo: "auto-fallback",
latencyMs: 1421,
fallbackCount: 2,
},
"-100123"
);
assert.ok(payload.text.includes("Model: `codex/gpt-5.5`"));
assert.ok(payload.text.includes("Provider: `codex`"));
assert.ok(payload.text.includes("Account: `Workspace Principal`"));
assert.ok(payload.text.includes("Combo: `auto-fallback`"));
assert.ok(payload.text.includes("Latency: `1421ms`"));
assert.ok(payload.text.includes("Fallbacks: `2`"));
});
test("buildTelegramPayload — accountId falls back to short account label", () => {
const payload = buildTelegramPayload(
"request.completed",
{
provider: "codex",
accountId: "12345678-abcd-efgh-ijkl-1234567890ab",
},
"-100123"
);
assert.ok(payload.text.includes("Account: `Account #123456`"));
});
test("buildTelegramPayload — chat_id matches provided value for groups", () => {
const payload = buildTelegramPayload("test.ping", { message: "ping" }, "-1001234567890");
assert.equal(payload.chat_id, "-1001234567890");