Files
OmniRoute/tests/integration/chat-pipeline.test.ts
Diego Rodrigues de Sa e Souza 8180b49ce1 fix(quality): 2 production bugs + 24 unit base-reds + measured gate ceilings (#9529)
* fix(quality): resolve net-new lint errors and allowlist #9343 assert rewrite

Two `no-explicit-any` errors landed with #9407 and #9320 after the
suppressions inventory was generated. Project policy is to fix new
violations rather than freeze them, so both are typed instead:
  - #9407: `executor as unknown as Record<string, unknown>`
  - #9320: `(k: { name?: string })`

Also allowlists the net-assert reduction in web-tools-translation-2820
(39->35). #9343 inverted the contract — bare JSON must no longer be
promoted to tool_calls without an explicit <tool> envelope — so the
tests were rewritten to assert non-promotion, which costs fewer asserts
than validating a promoted object. More restrictive, not weaker.

* fix(quality): raise integration ceiling to 40min and unpin codex-cli version in test

The integration gate's 20min ceiling killed a healthy run: measured 22m08s
hermetic on an idle 16-core box (935 tests across 112 files, strictly serial
at --test-concurrency=1 because ~16 of them bind a port or share a DB). The
"~3-10min" estimate in the code was stale by ~3x. 40min keeps the ceiling's
real purpose — turning a genuine hang into a visible failure — without
failing a long-but-healthy suite.

Also fixes a base-red in chat-pipeline: 564c204efe bumped
DEFAULT_CODEX_CLIENT_VERSION to 0.146.0 but the User-Agent assertion still
pinned 0.144.1. The line two above already read the constant via
getCodexClientVersion(); this one duplicated the literal. Deriving it from
the same source stops the next bump from breaking the test again.

* fix(ratelimit): re-arm Bottleneck reservoir heartbeat after updateSettings

Bottleneck 2.19.5 (frozen upstream dependency, no release since 2019) has a
bug in LocalDatastore#_startHeartbeat() (node_modules/bottleneck/lib/
LocalDatastore.js:29,56): the guard `if (this.heartbeat == null && ...)`
only (re)creates the periodic reservoir-refresh interval the first time it
runs. Every later call -- including the one updateSettings() itself
triggers internally -- falls into the else branch and does
clearInterval(this.heartbeat) WITHOUT resetting the reference back to
null. Because the stale reference sticks around, every future
_startHeartbeat() call keeps taking the same dead else branch: the
periodic reservoir refresh is gone forever after the first manual
updateSettings() call on a limiter.

Every limiter created by this file starts with a live heartbeat
(buildLimiterDefaults() always sets reservoirRefreshInterval/
reservoirRefreshAmount), so the very first updateFromHeaders() /
updateFromResponseBody() / applyRequestQueueSettings() call against a
limiter permanently kills its refresh. In production this wedges the
request queue once the reservoir hits 0: an auto-enrolled apikey
connection accumulates its default 60 requests, the reservoir zeroes, the
queue freezes for ~120s, the watchdog fires a synthetic 502
(RATE_LIMIT_QUEUE_WEDGED), the connection cools down and gets excluded
from weighted combo pools -- turning a configured 70/30 split into
~50/50.

Add applyLimiterSettings(), a module-local wrapper around
limiter.updateSettings() that nulls the stale heartbeat reference and
re-invokes _startHeartbeat() afterward so it takes the "start a fresh
interval" branch again. Route all 5 updateSettings() call sites through
it (updateAllLimiterSettings, both updateFromHeaders() branches,
loadPersistedLimits(), and updateFromResponseBody()). updateAllLimiterSettings
is now async and awaited by its two callers (initializeRateLimits,
applyRequestQueueSettings); the sync call sites use the existing
trackAsyncOperation() fire-and-forget tracking pattern.

tests/integration/combo-matrix/weighted.test.ts is the E2E proof: the
"weighted: 70/30" case now passes with zero WEDGED/RATE_LIMIT_QUEUE/502
log lines across 200 sequential requests (previously the wedge/recovery
cycle inflated its runtime and skewed the distribution toward ~50/50).

Refs #8213

* fix(tests): remove stray TDD probes committed by accident in f4e93f339d

Three TDD repro/probe test files landed on the release tip via
f4e93f339d (docs: add management authentication terminology guide,
files from a worktree. Each file is a pre-fix TDD probe that belongs
to a *different*, still-in-flight fix branch/PR and duplicates a file
path that PR already owns and will properly update on merge:

- tests/unit/authz/probe-9033-repro.test.ts: probe for #9033 (IP
  blacklist direct-connection bypass). 3/4 asserts fail against this
  tree (D1, D2, Bonus — all assert the not-yet-implemented target
  behavior); D3 passes (pre-existing behavior). Owned by PR #9385
  (open, unmerged), which modifies this exact path.
- tests/unit/repro-8522.test.ts: probe for #8522 (absolute file-size
  baseline reds innocent PRs on inherited drift). First test fails
  against this tree's evaluateFileSizes (still absolute-only); second
  (sanity: real growth still flags) passes. #8522 is actually CLOSED
  upstream — PR #9355 merged the real fix into release/v3.8.50 today
  (2026-08-05T15:53Z) modifying this exact path — but this branch's
  merge-base with release/v3.8.50 (6b0e11e378) predates that merge,
  so the fix has not synced into this tree yet.
- tests/unit/repro-8956.test.ts: probe for #8956 (resolveProjectRoot
  stops at synthetic Next.js standalone package.json). First test
  fails against this tree; second (sanity: named package.json still
  resolves) passes. Owned by PR #9354 (open, unmerged), which
  modifies this exact path.

Each deleted file's real implementation + passing version already
exists in its owning PR and will land normally through that PR's own
merge — deleting the premature copy here does not lose any coverage.

No config/quality/test-masking-allowlist.json entry was added: the
_deletedWithReplacement schema only supports `replacement` (a test
file that must already exist in this tree's HEAD — none does, the
real versions live in the unmerged sibling PRs above) or `sourceRemoved`
(production files that must be absent from HEAD — they are not, none
of the three issues are implemented in this tree). Neither shape fits
an "owned by an in-flight sibling PR" deletion, so the CI test-masking
gate will flag these 3 deletions for mandatory human review on this
branch's next PR diff against release/v3.8.50 — flagged for the owner
rather than inventing a new allowlist shape.

Refs #9033, #8522, #8956, #7786

* fix(tests): align 8189-classifier-compat with #9276 always-mode semantics

tests/unit/8189-classifier-compat-auto-narrow.test.ts was a test-sibling
forgotten when #9276 (commit 6b531fbacd) removed the unconditional
`if (mode === "always") return true` branch from
shouldDefaultAllowClassifier(). tests/unit/claude-classifier-compat.test.ts
was updated in that same commit; this file was not.

Old contract: 'always' mode short-circuited every Claude-format request
unconditionally (operator opt-in was treated as sufficient on its own).
New contract: 'always' now requires the same SECURITY_MONITOR_MARKER
system-prompt text as 'auto' — the marker-optional behavior let a normal
chat request through /v1/messages be silently swallowed by an operator's
'always' opt-in.

The single 'always' test (1 assert, no-marker body expecting true) is
replaced by two tests mirroring the depth already used for 'auto' mode
in the same file: no-marker/false and marker-present/true. Net effect is
+1 assert, not a reduction — the new pair verifies both directions of
the narrowed contract instead of only the now-incorrect unconditional
case.

Before: 3/4 pass (the 'always' test failed: expected true, got false).
After: 5/5 pass.

Refs #9276

* fix(tests): align deepseek-web-tools-execute with #9343 tool envelope contract

tests/unit/deepseek-web-tools-execute-2820.test.ts (executor level) was a
test-sibling forgotten when #9343 (commit d969555417) hardened tool-call
parsing: bare JSON with no explicit <tool>/<tool_call> envelope is never
promoted to tool_calls anymore (previously it was, whenever a tools[] set
was requested — a security gap allowing prose/code-fenced JSON echoed
back by the model, or a copy-attack, to trigger real tool execution).

Three siblings were updated in the same commit: web-tools-translation.test.ts
and web-tools-translation-2820.test.ts (parseToolCallsFromText, the shared
translator), and deepseek-web-tools-variants.test.ts (parseDeepSeekToolCalls,
deepseek-specific parser) — all inverted their bare-JSON assertions to
`toolCalls === null` + `content === text` (preserved verbatim, not stripped).
This file calls the executor's execute() (full HTTP round trip through
buildToolAwareResult), so it was not touched by that diff and kept
asserting the old contract (finish_reason: "tool_calls", content: null).

Verified against source (open-sse/executors/deepseek-web.ts
buildToolAwareResult): when parseDeepSeekToolCalls returns toolCalls=null,
hasCalls is false, so finish_reason is "stop", message.tool_calls is never
set, and message.content is the parser's returned content — which for text
with no <tool>/<tool_call> tag at all is the original string, unchanged
(parseToolCallsFromText's early-return branch). The test now asserts
exactly that shape, at the same executor level as the rest of the file's
tool_calls that make sense at that level as the rest of the file's tool_calls
Refs #9343

* fix(tests): align visionBridge tests with #8430 contract (partial — see note)

Two test-siblings were forgotten when #8430 (commit 7e55abbc41) hardened
Vision Bridge's vision-model selection: getBestVisionModel() now validates
that a candidate has a usable active connection (hasUsableCredentialsForModel,
DB-backed) before returning it, instead of unconditionally returning the
fixedModel or a hardcoded "openai/gpt-4o-mini" default. Three siblings were
updated in the same commit (visionBridgeRouter.test.ts, the new
repro-8430.test.ts, vision-bridge-preserve-on-failure-4012.test.ts); these two
were not.

tests/unit/guardrails/visionBridgeHelpers.callVisionModel.test.ts (8 failures,
all "No vision-capable provider connected"): callVisionModel()'s `routerConfig`
param only merges into getBestVisionModel's CONFIG argument, never its `deps`
argument, so there is no way to inject a credentials stub through this
function's public signature (unlike the guardrail class and getBestVisionModel
itself, which do accept an injectable `hasUsableCredentials`). These tests
exercise callVisionModel's own request/response handling, not credential
routing (already covered elsewhere), so the fix seeds one real usable
`provider_connections` row per provider the file exercises (openai, anthropic)
via createProviderConnection in a test.before() hook, with resetDbInstance()
in test.after() per the DB-handle-cleanup convention. All 8 now pass.

tests/unit/guardrails/visionBridge.test.ts (7 failures): 1 of the 7 (VB-S03)
is a genuine forgotten-contract case, fixed here — same semantic flip already
applied to vision-bridge-preserve-on-failure-4012.test.ts: in the combo
describe path, when EVERY describe call fails, the raw image is now replaced
with an "(unavailable)" stub instead of preserved, because that path is only
reached for confirmed non-vision targets. Assertions inverted to match
(imagePart undefined, unavailable-stub present), same assert count, no
weakening.

*** THE OTHER 6 (VB-S12, VB-S12b, VB-S01, VB-S13, VB-S07, VB-S10) ARE
DELIBERATELY LEFT FAILING. *** These are NOT a #8430 contract change — root-
caused to what looks like a separate, unintentional regression: the ONE call
to getBestVisionModel() in visionBridge.ts's whole-request-reroute path
(line 244, `getBestVisionModel({ fixedModel: configuredModel })`) does not
pass a `deps` second argument, so it always uses the real DB-backed
hasUsableCredentialsForModel instead of this.deps.hasUsableCredentials — even
though the two adjacent checks in the very same function (`checkCreds(model)`
at line 226, `checkCreds(bestModel)` at line 246) DO honor the injectable
override. In this suite's empty-but-readable isolated test DB, that real
check deterministically returns `false` (not the indeterminate `null` the
file's own createGuardrail() comment says these tests rely on: "Fail-open
(null) so classic VB-S01/S07/S10 reroute tests keep working without a live
credential DB"), so getBestVisionModel silently returns null, the reroute
branch's `if (bestModel && ...)` guard never fires, and every test that
expects a reroute observes a silent no-op instead.

Evidence this is a source gap, not a test that needs updating:
- The file's own pre-existing comment names VB-S01/S07/S10 as tests the
  `null` fail-open default is SUPPOSED to keep green.
- VB-CRED-01/02 (the file's only two tests that actually inject a non-default
  hasUsableCredentials mock) both pass today, but neither one's assertions
  distinguish "mock honored" from "mock ignored, real check also says no" —
  they don't prove the threading works, they just don't happen to notice it's
  missing.
- visionBridgeRouter.test.ts, repro-8430.test.ts, and
  vision-bridge-preserve-on-failure-4012.test.ts (22 tests, all green) all
  either call getBestVisionModel directly with explicit deps, or mock
  callVisionModel wholesale (bypassing getBestVisionModel entirely) — none of
  them exercises this exact call site through the guardrail's own deps.

Per instructions, this was intentionally NOT "fixed" by weakening these 6
tests' assertions (that would mask the gap) or by seeding fake DB credentials
to route around it (that would hide a real production DI inconsistency behind
a test-only workaround) or by touching src/lib/guardrails/visionBridge.ts
(a production behavior change outside a test-alignment task's scope, and
Hard Rule #18 requires its own TDD/validation cycle). Flagging for the owner:
the likely one-line fix is threading `{ hasUsableCredentials:
this.deps.hasUsableCredentials }` as getBestVisionModel's second argument at
visionBridge.ts:244, mirroring the two adjacent call sites in the same
function.

Before: 15 failures (7 + 8). After: 9 pass added (1 + 8), 6 still fail
(unchanged, by design).

Refs #8430

* feat(quality): add strayFromCommit deletion allowlist form to test-masking gate

The deletion allowlist supported two shapes: replacement (test rewritten
elsewhere) and sourceRemoved (feature deleted). Neither fits a third
legitimate case surfaced today: test files that entered the repo BY
ACCIDENT — commit f4e93f339d (#7786 docs) swept another session's
worktree artifacts into the release, including TDD probes owned by open
fix PRs (probe-9033-repro -> PR #9385, repro-8956 -> PR #9354,
repro-8522 -> PR #9355). Those probes fail by design until their owning
PR merges, so every unit run on the release tip broke on them.

The new strayFromCommit form is verified, not trusted: the gate asks git
which commit actually ADDED the file (git log --diff-filter=A) and only
exempts the deletion when it matches the declared hash; a non-empty
reason naming the owning PR/issue is mandatory. Also allowlists the
deepseek-web-tools-execute assert reduction (23->21) from ed661f2126 —
same #9343 contract-inversion class as the existing
web-tools-translation entry.

Gate unit tests: 55/55 pass. Full gate vs main: OK.

* fix(guardrails): pass credential deps to getBestVisionModel at reroute call site

The individual-model reroute path in VisionBridgeGuardrail.preCall() calls
getBestVisionModel({ fixedModel: configuredModel }) without its second
`deps` argument, so the router always falls back to the real DB-backed
hasUsableCredentialsForModel instead of an injected
`deps.hasUsableCredentials` override. The two adjacent credential checks in
the same function (the original-model check and the best-model check,
both via the local `checkCreds` binding) already thread deps correctly —
only this middle call, added in #8430, was left out.

Pass the same resolved `checkCreds` used by those two adjacent checks as
`getBestVisionModel`'s deps argument so all three credential checks in this
reroute path stay consistent.

Fixes 6 tests in tests/unit/guardrails/visionBridge.test.ts that depended
on the injected hasUsableCredentials mock being honored on this path:
VB-S12, VB-S12b, VB-S01, VB-S13, VB-S07, VB-S10.

Refs #8430

* fix(quality): raise unit ceiling to 100min and align 2 more forgotten sibling tests

Unit ceiling 45->100min: a hermetic-env measurement on the loaded devbox
(load 7-26) was still inside invocation 1 of 3 at 76min when killed;
contention factor 2-3x measured, no idle measurement exists. The
pre-flight's real condition is exactly that contended one (unit runs in
Promise.all with integration+vitest), and there 45min provably killed a
healthy suite and fabricated a false base-red. The 45min value came from
v3.8.43 as an estimate never validated by measurement. TODO in-code:
re-tighten after an idle run on the .113 box.

Also aligns the 6th and 7th occurrences of the same systemic pattern
(behavior change merged updating only part of the sibling tests):
- issue-7859-gemini-web-redirect-valid: #9407 refined ServiceLogin
  redirects to mean expired session; the #7859 regression coverage is
  preserved via a non-ServiceLogin public redirect variant.
- provider-validation-specialty claude-web 429: #9406 inverted the
  contract (rate-limited session is unhealthy); the dedicated repro file
  owns the full contract, this sibling now matches it.

Also carries the file-size rebaseline for #9323's base.ts growth
(1578->1623, WAF retry + burst guard) and the eslintWarnings baseline
tightened 5000->0 (real measured value with the TS7 suppressions in
place — 5000 left the ratchet inert).

Refs #9407, #9406, #9323

* fix(tests): restore the 3 TDD probes now owned by merged fixes and drop their stray allowlist entries

The base advanced while this PR was open: the real fixes for the three
issues behind the stray probes all merged into release/v3.8.50 —
#9385 (issue 9033), #9355 (issue 8522) and #9354 (issue 8956).

- probe-9033-repro / repro-8522: the base rewrote both probes into the
  regression tests of their merged fixes, so the delete side of the
  rebase conflict was dropped and the base versions kept.
- repro-8956: #9354 only realigned one fixture line in
  auto-update.test.ts (package.json marker now needs a name field) and
  added no test for the new skip-synthetic behavior — the probe is the
  ONLY regression coverage of that merged fix (2/2 green on the base),
  so deleting it would remove real coverage. Restored.

With no test-file deletions left in the PR diff, the three
strayFromCommit allowlist entries are stale and removed. The
strayFromCommit form support in check-test-masking.mjs stays (covered
by its own fixtures).

* fix(quality): rebaseline file-size for PR #9529 own growth

The base sits exactly at the old frozen values, so the base-relative
mode (#8522) does not cover this growth — it is this PR's own:

- open-sse/services/rateLimitManager.ts 1060->1105: the
  applyLimiterSettings() helper that re-arms the reservoir heartbeat
  after updateSettings (Bottleneck 2.19.5 fix, TDD in
  ratelimit-reservoir-refresh.test.ts).
- tests/integration/chat-pipeline.test.ts 1592->1598: codex User-Agent
  derived from getCodexClientVersion() instead of a pinned literal.
- tests/unit/provider-validation-specialty.test.ts 2980->2985: new
  claude-web 429 -> valid:false coverage (#9406).

* fix(docs): sync provider count to 291 in README and CLAUDE

The live catalog counts 291 providers but README.md/CLAUDE.md still
said 290, so the STRICT 'Docs Gates (fast-path)' check reds EVERY open
PR against release/v3.8.50 (verified on #9537/#9539 as well — inherited
base-red, not introduced by this PR). Updated all provider-count
mentions including the section anchor.

* fix(tests): align launch-codex 6312 guard with the async #9454 spawn contract

#9454 made resolveCodexSpawn async (PATH-probes a native codex.exe before
the .cmd shim) and updated its own tests, but left this older sibling
calling the function synchronously — destructuring the Promise yields
undefined and reds Unit fast-path (1/4) for EVERY open PR against the
release (verified on #9537/#9539; inherited base-red). Realigned to the
async contract with an injected probe; keeps the original #6312 fallback
guard plus the only non-Windows codex coverage (now also asserting the
probe never runs off Windows).

* fix(translator): move state-mutating reasoning summary helper out of the pure leaf

#9500 added buildResponsesReasoningSummaryDelta(state, ...) to
pureHelpers.ts, but the function reads AND mutates stream state
(reasoningSummaryIndex map) — violating the leaf contract declared in
the file header ('no host imports, no stream state') and guarded by
response-openai-responses-purehelpers-split.test.ts, which reds Unit
fast-path (4/4) for every open PR (inherited base-red, verified on
#9537/#9539). Moved verbatim to the host next to the other stream-state
helpers (markResponsesReasoningDeltaEmitted); the host was its only
consumer. Behavior unchanged: repro-9500-reasoning-separator 3/3 green,
leaf/host architecture tests green.

* fix(quality): rebaseline openai-responses.ts for the leaf-state relocation

The #9500 helper moved from pureHelpers.ts into the host (previous
commit) grows the host file 1174->1204 while the leaf shrinks by the
same amount — net-zero LOC across the pair, but the per-file frozen
ratchet only sees the growing side.

* fix(tests): let the 9442 cert-mode test see past the harness trust-store guard

tests/_setup/isolateDataDir.ts sets OMNIROUTE_SKIP_SYSTEM_TRUST=1
globally, which makes installCert() return before issuing any command —
so the #9442 install-gap test captured nothing and could NEVER pass
under npm run test:unit (it only passed invoked directly, harness-less;
inherited base-red on Unit fast-path 3/4, verified on #9537/#9539).
Clear the flag for this file only (restored in test.after): safe because
every spawned command is a logging stub on PATH and OMNIROUTE_NO_SUDO=1
strips sudo, so nothing touches the real trust store. 6/6 under the CI
harness including system-trust-test-guard.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-05 22:52:50 -03:00

1598 lines
49 KiB
TypeScript

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-chat-pipeline-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.REQUIRE_API_KEY = "false";
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-chat-pipeline-secret";
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const combosDb = await import("../../src/lib/db/combos.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const apiKeysDb = await import("../../src/lib/db/apiKeys.ts");
const readCacheDb = await import("../../src/lib/db/readCache.ts");
const { getLatestCallLog, getResponsesCallLogs } = await import("./_chatPipelineCallLogs.ts");
const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/settings.ts");
const { skillRegistry } = await import("../../src/lib/skills/registry.ts");
const { skillExecutor } = await import("../../src/lib/skills/executor.ts");
const { handleChat } = await import("../../src/sse/handlers/chat.ts");
const { initTranslators } = await import("../../open-sse/translator/index.ts");
const { clearInflight } = await import("../../open-sse/services/requestDedup.ts");
const { setCliCompatProviders } = await import("../../open-sse/config/cliFingerprints.ts");
const { BaseExecutor } = await import("../../open-sse/executors/base.ts");
const { getCodexClientVersion } = await import("../../open-sse/config/codexClient.ts");
const { getCircuitBreaker, resetAllCircuitBreakers } =
await import("../../src/shared/utils/circuitBreaker.ts");
const { clearProviderFailure } = await import("../../open-sse/services/accountFallback.ts");
const originalFetch = globalThis.fetch;
const originalRetryDelayMs = BaseExecutor.RETRY_CONFIG.delayMs;
type SeedConnectionOverrides = {
name?: string;
authType?: string;
apiKey?: string;
accessToken?: string;
refreshToken?: string;
tokenType?: string;
expiresAt?: string;
tokenExpiresAt?: string;
isActive?: boolean;
testStatus?: string;
priority?: number;
rateLimitedUntil?: string | number | null;
providerSpecificData?: Record<string, unknown>;
};
type FetchCall = {
url: string;
method?: string;
headers: Record<string, string>;
body: Record<string, any> | null;
};
type SeedApiKeyOptions = {
name?: string;
noLog?: boolean;
allowedConnections?: string[];
allowedCombos?: string[];
allowedModels?: string[];
};
function toPlainHeaders(headers: HeadersInit | undefined | null) {
if (!headers) return {};
if (headers instanceof Headers) return Object.fromEntries(headers.entries());
if (Array.isArray(headers)) return Object.fromEntries(headers);
return Object.fromEntries(
Object.entries(headers).map(([key, value]) => [key, value == null ? "" : String(value)])
);
}
function buildRequest({
url = "http://localhost/v1/chat/completions",
body,
authKey = null,
headers = {},
}: {
url?: string;
body?: unknown;
authKey?: string | null;
headers?: Record<string, string>;
} = {}) {
const requestHeaders: Record<string, string> = {
"Content-Type": "application/json",
...headers,
};
if (authKey) {
requestHeaders.Authorization = `Bearer ${authKey}`;
}
return new Request(url, {
method: "POST",
headers: requestHeaders,
body: typeof body === "string" ? body : JSON.stringify(body),
});
}
function buildOpenAIResponse(text = "ok", model = "gpt-4o-mini", usage = null) {
return new Response(
JSON.stringify({
id: "chatcmpl_json",
object: "chat.completion",
model,
choices: [
{
index: 0,
message: { role: "assistant", content: text },
finish_reason: "stop",
},
],
usage: usage || {
prompt_tokens: 4,
completion_tokens: 2,
total_tokens: 6,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
function buildOpenAIToolCallResponse({
model = "gpt-4o-mini",
toolName = "lookupWeather@1.0.0",
toolCallId = "call_weather",
argumentsObject = { location: "Sao Paulo" },
} = {}) {
return new Response(
JSON.stringify({
id: "chatcmpl_tool",
object: "chat.completion",
model,
choices: [
{
index: 0,
message: {
role: "assistant",
content: "",
tool_calls: [
{
id: toolCallId,
type: "function",
function: {
name: toolName,
arguments: JSON.stringify(argumentsObject),
},
},
],
},
finish_reason: "tool_calls",
},
],
usage: {
prompt_tokens: 6,
completion_tokens: 4,
total_tokens: 10,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
function buildClaudeResponse(text = "ok", model = "claude-3-5-sonnet-20241022") {
return new Response(
JSON.stringify({
id: "msg_json",
type: "message",
role: "assistant",
model,
content: [{ type: "text", text }],
stop_reason: "end_turn",
usage: {
input_tokens: 10,
output_tokens: 4,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
function buildClaudeStreamResponse(text = "streamed from claude", model = "claude-sonnet-4-6") {
return new Response(
[
"event: message_start",
`data: ${JSON.stringify({
type: "message_start",
message: {
id: "msg_stream",
type: "message",
role: "assistant",
model,
usage: { input_tokens: 12, output_tokens: 0 },
},
})}`,
"",
"event: content_block_start",
`data: ${JSON.stringify({
type: "content_block_start",
index: 0,
content_block: { type: "text", text: "" },
})}`,
"",
"event: content_block_delta",
`data: ${JSON.stringify({
type: "content_block_delta",
index: 0,
delta: { type: "text_delta", text },
})}`,
"",
"event: message_delta",
`data: ${JSON.stringify({
type: "message_delta",
delta: { stop_reason: "end_turn" },
usage: { output_tokens: 3 },
})}`,
"",
"event: message_stop",
`data: ${JSON.stringify({ type: "message_stop" })}`,
"",
].join("\n"),
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
}
function buildGeminiResponse(text = "ok", model = "gemini-2.5-flash") {
return new Response(
JSON.stringify({
responseId: "resp_gemini",
modelVersion: model,
createTime: "2026-04-05T12:00:00.000Z",
candidates: [
{
content: {
parts: [{ text }],
},
finishReason: "STOP",
},
],
usageMetadata: {
promptTokenCount: 5,
candidatesTokenCount: 7,
totalTokenCount: 12,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
function buildOpenAIStreamResponse(text = "streamed from openai") {
return new Response(
[
`data: ${JSON.stringify({
id: "chatcmpl_stream",
object: "chat.completion.chunk",
choices: [{ index: 0, delta: { role: "assistant", content: text } }],
})}`,
"",
"data: [DONE]",
"",
].join("\n"),
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
}
function buildOpenAIResponsesSSE({
text = "responses streamed from codex",
model = "gpt-5.1-codex",
usage = null,
} = {}) {
return new Response(
[
`data: ${JSON.stringify({
type: "response.completed",
response: {
id: "resp_stream",
object: "response",
status: "completed",
model,
output: [
{
id: "msg_stream",
type: "message",
role: "assistant",
content: [{ type: "output_text", text, annotations: [] }],
},
],
usage: usage || {
input_tokens: 120,
output_tokens: 30,
prompt_tokens_details: {
cached_tokens: 40,
},
cache_creation_input_tokens: 11,
completion_tokens_details: {
reasoning_tokens: 13,
},
},
},
})}`,
"",
"data: [DONE]",
"",
].join("\n"),
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
}
function buildOpenAIResponsesJson({
text = "responses compacted from codex",
model = "gpt-5.5",
usage = null,
} = {}) {
return new Response(
JSON.stringify({
id: "resp_compact",
object: "response",
status: "completed",
model,
output: [
{
id: "msg_compact",
type: "message",
role: "assistant",
content: [{ type: "output_text", text, annotations: [] }],
},
],
output_text: text,
usage: usage || {
input_tokens: 90,
output_tokens: 15,
total_tokens: 105,
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
async function resetStorage() {
globalThis.fetch = originalFetch;
process.env.REQUIRE_API_KEY = "false";
clearInflight();
resetAllCircuitBreakers();
apiKeysDb.resetApiKeyState();
readCacheDb.invalidateDbCache();
invalidateMemorySettingsCache();
await new Promise((resolve) => setTimeout(resolve, 20));
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
initTranslators();
}
async function seedConnection(provider, overrides: SeedConnectionOverrides = {}) {
return providersDb.createProviderConnection({
provider,
authType: overrides.authType || "apikey",
name: overrides.name || `${provider}-primary`,
email: overrides.email,
apiKey: overrides.apiKey || `sk-${provider}-${Math.random().toString(16).slice(2, 10)}`,
accessToken: overrides.accessToken,
refreshToken: overrides.refreshToken,
tokenType: overrides.tokenType,
expiresAt: overrides.expiresAt,
tokenExpiresAt: overrides.tokenExpiresAt,
isActive: overrides.isActive ?? true,
testStatus: overrides.testStatus || "active",
priority: overrides.priority,
rateLimitedUntil: overrides.rateLimitedUntil,
providerSpecificData: overrides.providerSpecificData || {},
});
}
async function seedApiKey({
name = "chat-pipeline-key",
noLog = false,
allowedConnections,
allowedCombos,
allowedModels,
}: SeedApiKeyOptions = {}) {
const key = await apiKeysDb.createApiKey(name, "machine-test");
const updates: Record<string, unknown> = {};
if (noLog) updates.noLog = true;
if (allowedConnections) updates.allowedConnections = allowedConnections;
if (allowedCombos) updates.allowedCombos = allowedCombos;
if (allowedModels) updates.allowedModels = allowedModels;
if (Object.keys(updates).length > 0) {
await apiKeysDb.updateApiKeyPermissions(key.id, updates);
}
return key;
}
function ensureLegacyMemoryTable() {
const db = core.getDbInstance();
db.exec(`
CREATE TABLE IF NOT EXISTS memory (
id TEXT PRIMARY KEY,
apiKeyId TEXT NOT NULL,
sessionId TEXT,
type TEXT NOT NULL,
key TEXT,
content TEXT NOT NULL,
metadata TEXT,
createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL,
expiresAt TEXT
)
`);
}
function insertLegacyMemory(apiKeyId, content) {
const db = core.getDbInstance();
const now = new Date().toISOString();
const hasModernTable = Boolean(
db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'memories'").get()
);
if (hasModernTable) {
db.prepare(
`
INSERT INTO memories (
id, api_key_id, session_id, type, key, content, metadata, created_at, updated_at, expires_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
).run(
`mem_${Math.random().toString(16).slice(2, 10)}`,
apiKeyId,
"",
"factual",
"pref",
content,
"{}",
now,
now,
null
);
return;
}
ensureLegacyMemoryTable();
db.prepare(
`
INSERT INTO memory (
id, apiKeyId, sessionId, type, key, content, metadata, createdAt, updatedAt, expiresAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
).run(
`mem_${Math.random().toString(16).slice(2, 10)}`,
apiKeyId,
"",
"factual",
"pref",
content,
"{}",
now,
now,
null
);
}
async function waitFor(fn, timeoutMs = 1500) {
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
const result = await fn();
if (result) return result;
await new Promise((resolve) => setTimeout(resolve, 25));
}
return null;
}
test.beforeEach(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = 0;
await resetStorage();
});
test.afterEach(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = originalRetryDelayMs;
setCliCompatProviders([]);
await resetStorage();
});
test.after(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = originalRetryDelayMs;
globalThis.fetch = originalFetch;
clearInflight();
resetAllCircuitBreakers();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("chat pipeline handles OpenAI passthrough with valid API key auth", async () => {
await seedConnection("openai", { apiKey: "sk-openai-primary" });
const apiKey = await seedApiKey();
const fetchCalls: FetchCall[] = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
method: init.method || "GET",
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponse("OpenAI passthrough");
};
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Hello OpenAI" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\/chat\/completions$/);
assert.equal(fetchCalls[0].headers.Authorization, "Bearer sk-openai-primary");
assert.equal(fetchCalls[0].body.messages[0].content, "Hello OpenAI");
assert.equal(json.choices[0].message.content, "OpenAI passthrough");
});
test("chat pipeline persists Codex responses cache and reasoning tokens to call logs", async () => {
await seedConnection("codex", { apiKey: "sk-codex-primary" });
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponsesSSE();
};
const response = await handleChat(
buildRequest({
url: "http://localhost/v1/responses",
body: {
model: "codex/gpt-5.1-codex",
stream: false,
input: "Persist cache + reasoning usage",
},
})
);
const json = (await response.json()) as any;
const callLog = await waitFor(() => getLatestCallLog());
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\/responses$/);
assert.equal(fetchCalls[0].headers.Authorization, "Bearer sk-codex-primary");
assert.equal(json.object, "response");
assert.equal(json.output[0].type, "message");
assert.equal(json.output[0].content[0].text, "responses streamed from codex");
assert.equal(json.output_text, "responses streamed from codex");
assert.equal(json.usage.input_tokens_details.cached_tokens, 40);
assert.equal(json.usage.output_tokens_details.reasoning_tokens, 13);
assert.ok(callLog, "expected a call log row to be created");
assert.equal(callLog.provider, "codex");
assert.equal(callLog.path, "/v1/responses");
assert.equal(callLog.tokens.cacheRead, 40);
assert.equal(callLog.tokens.cacheWrite, 11);
assert.equal(callLog.tokens.reasoning, 13);
});
test("chat pipeline applies global Codex priority service tier inside combos", async () => {
await seedConnection("codex", { apiKey: "sk-codex-combo-priority" });
await settingsDb.updateSettings({
codexServiceTier: { enabled: true, tier: "priority" },
});
await combosDb.createCombo({
name: "codex-priority-combo",
strategy: "priority",
config: { maxRetries: 0, retryDelayMs: 0 },
models: ["codex/gpt-5.5"],
});
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponsesSSE({ text: "combo priority ok", model: "gpt-5.5" });
};
const response = await handleChat(
buildRequest({
body: {
model: "codex-priority-combo",
stream: false,
messages: [{ role: "user", content: "Use Codex combo priority" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\/responses$/);
assert.equal(fetchCalls[0].headers.Authorization, "Bearer sk-codex-combo-priority");
assert.equal(fetchCalls[0].body.service_tier, "priority");
assert.equal(json.choices[0].message.content, "combo priority ok");
});
test("chat pipeline applies Codex CLI fingerprint to OAuth responses requests", async () => {
setCliCompatProviders(["codex"]);
await seedConnection("codex", {
apiKey: "unused-for-oauth",
authType: "oauth",
accessToken: "codex-oauth-token",
providerSpecificData: {
openaiStoreEnabled: false,
requestDefaults: { reasoningEffort: "high" },
codexInstallationId: "11111111-1111-4111-a111-111111111111",
},
});
const fetchCalls = [];
globalThis.fetch = async (url, init = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
bodyString: String(init.body || ""),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponsesSSE({ text: "fingerprint ok" });
};
const response = await handleChat(
buildRequest({
url: "http://localhost/v1/responses",
body: {
model: "codex/gpt-5.5-low",
stream: false,
conversation_id: "conv_codex_fingerprint",
input: [
{
type: "message",
role: "user",
content: [{ type: "input_text", text: "Reply with fingerprint ok" }],
},
],
},
})
);
await response.json();
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
const call = fetchCalls[0];
assert.match(call.url, /chatgpt\.com\/backend-api\/codex\/responses$/);
assert.equal(call.headers.Authorization, "Bearer codex-oauth-token");
assert.equal(call.headers.Accept, "text/event-stream");
assert.equal(call.headers.Version, getCodexClientVersion());
assert.equal(call.headers["Openai-Beta"], "responses=experimental");
assert.equal(call.headers["X-Codex-Beta-Features"], "responses_websockets");
// Derive from the same source the code reads (see getCodexClientVersion() two
// lines above) instead of pinning the literal — #9323's version bump to 0.146.0
// broke this assertion while the rest of the test kept passing.
assert.equal(
call.headers["User-Agent"],
`codex-cli/${getCodexClientVersion()} (Windows 10.0.26200; x64)`
);
assert.equal(call.headers["x-codex-window-id"], "conv_codex_fingerprint:0");
assert.ok(call.headers["x-client-request-id"], "expected Codex request id header");
assert.ok(call.headers["x-codex-turn-metadata"], "expected Codex turn metadata header");
const headerOrder = Object.keys(call.headers);
assert.ok(headerOrder.indexOf("Content-Type") < headerOrder.indexOf("Authorization"));
assert.ok(headerOrder.indexOf("Authorization") < headerOrder.indexOf("Accept"));
assert.ok(headerOrder.indexOf("Accept") < headerOrder.indexOf("User-Agent"));
const bodyOrder = Object.keys(JSON.parse(call.bodyString));
// Order must match the canonical Codex fingerprint bodyFieldOrder (cliFingerprints.ts):
// …reasoning, prompt_cache_key, …, include — i.e. prompt_cache_key precedes include.
// (#4584 inadvertently flipped these two; fast-gates skip integration tests so it only
// surfaced on the release PR full CI.)
assert.deepEqual(
bodyOrder.slice(0, 8),
"model stream input instructions store reasoning prompt_cache_key include".split(" ")
);
assert.equal(call.body.model, "gpt-5.5");
assert.equal(call.body.store, false);
assert.equal(
call.body.client_metadata["x-codex-installation-id"],
"11111111-1111-4111-a111-111111111111"
);
});
test("chat pipeline strips previous_response_id from stateless Codex responses by default", async () => {
await seedConnection("codex", {
apiKey: "sk-codex-stateless-responses",
providerSpecificData: { openaiStoreEnabled: false },
});
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponsesSSE({ text: "stateless responses ok", model: "gpt-5.5" });
};
const response = await handleChat(
buildRequest({
url: "http://localhost/v1/responses",
body: {
model: "codex/gpt-5.5",
stream: false,
previous_response_id: "resp_vs_code_prev",
input: [
{
type: "message",
role: "user",
content: [{ type: "input_text", text: "Second VS Code turn" }],
},
],
},
})
);
await response.json();
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\/responses$/);
assert.equal(fetchCalls[0].body.previous_response_id, undefined);
assert.equal(fetchCalls[0].body.store, false);
});
test("chat pipeline preserve mode forwards previous_response_id for responses requests", async () => {
await settingsDb.updateSettings({ responsesPreviousResponseIdMode: "preserve" });
await seedConnection("codex", {
apiKey: "sk-codex-preserve-responses",
providerSpecificData: { openaiStoreEnabled: false },
});
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponsesSSE({ text: "preserve responses ok", model: "gpt-5.5" });
};
const response = await handleChat(
buildRequest({
url: "http://localhost/v1/responses",
body: {
model: "codex/gpt-5.5",
stream: false,
previous_response_id: "resp_preserved_prev",
input: "Second stateful turn",
},
})
);
await response.json();
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].body.previous_response_id, "resp_preserved_prev");
});
test("chat pipeline treats Codex /responses/compact as non-streaming JSON", async () => {
await seedConnection("codex", { apiKey: "sk-codex-compact" });
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponsesJson();
};
const response = await handleChat(
buildRequest({
url: "http://localhost/v1/responses/compact",
headers: { Accept: "text/event-stream" },
body: {
model: "codex/gpt-5.5",
input: "Compact this session",
},
})
);
const json = (await response.json()) as { object?: string; output_text?: string };
const callLog = await waitFor(() => getLatestCallLog());
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\/responses\/compact$/);
assert.equal(fetchCalls[0].headers.Accept, "application/json");
assert.equal(fetchCalls[0].body.stream, undefined);
assert.equal(fetchCalls[0].body.store, undefined);
assert.equal(json.object, "response");
assert.equal(json.output_text, "responses compacted from codex");
assert.ok(callLog, "expected a compact call log row to be created");
assert.equal(callLog.provider, "codex");
assert.equal(callLog.path, "/v1/responses/compact");
assert.equal(callLog.status, 200);
});
test("chat pipeline serves repeated /v1/responses requests as MISS then HIT and logs cache hits separately", async () => {
await seedConnection("codex", { apiKey: "sk-codex-cache-seq" });
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponsesSSE({
text: "cached semantic response",
usage: {
input_tokens: 21,
output_tokens: 7,
prompt_tokens_details: {
cached_tokens: 5,
},
cache_creation_input_tokens: 2,
completion_tokens_details: {
reasoning_tokens: 3,
},
},
});
};
const uniquePrompt = `semantic-cache-seq-${Math.random().toString(16).slice(2)}`;
const requestBody = {
model: "codex/gpt-5.3-codex",
stream: false,
temperature: 0,
input: [{ role: "user", content: [{ type: "input_text", text: uniquePrompt }] }],
};
const beforeCount = (await getResponsesCallLogs()).length;
const firstResponse = await handleChat(
buildRequest({
url: "http://localhost/v1/responses",
body: requestBody,
})
);
const secondResponse = await handleChat(
buildRequest({
url: "http://localhost/v1/responses",
body: requestBody,
})
);
const thirdResponse = await handleChat(
buildRequest({
url: "http://localhost/v1/responses",
body: requestBody,
})
);
await firstResponse.json();
await secondResponse.json();
await thirdResponse.json();
assert.equal(firstResponse.status, 200);
assert.equal(secondResponse.status, 200);
assert.equal(thirdResponse.status, 200);
assert.equal(firstResponse.headers.get("X-OmniRoute-Cache"), "MISS");
assert.equal(secondResponse.headers.get("X-OmniRoute-Cache"), "HIT");
assert.equal(thirdResponse.headers.get("X-OmniRoute-Cache"), "HIT");
assert.equal(fetchCalls.length, 1, "expected upstream to be called only once for MISS");
assert.match(fetchCalls[0].url, /\/responses$/);
const callLogs = await waitFor(async () => {
const rows = await getResponsesCallLogs();
return rows.length === beforeCount + 3 ? rows : null;
}, 2000);
assert.ok(callLogs, "expected /v1/responses call logs to be recorded");
assert.equal(callLogs.length, beforeCount + 3, "expected MISS plus two HIT call logs");
const newLogs = callLogs.slice(0, 3);
assert.equal(newLogs.filter((row) => row.cacheSource === "upstream").length, 1);
assert.equal(newLogs.filter((row) => row.cacheSource === "semantic").length, 2);
const callLog = await waitFor(() => getLatestCallLog());
assert.ok(callLog, "expected a call log row to exist");
assert.equal(callLog.path, "/v1/responses");
assert.equal(callLog.status, 200);
});
test("chat pipeline translates OpenAI requests to Claude and returns OpenAI-shaped responses", async () => {
await seedConnection("claude", { apiKey: "sk-claude-primary" });
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildClaudeResponse("Claude translated reply");
};
const response = await handleChat(
buildRequest({
body: {
model: "claude/claude-3-5-sonnet-20241022",
stream: false,
messages: [{ role: "user", content: "Hello Claude" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\?beta=true$/);
assert.equal(fetchCalls[0].headers["x-api-key"], "sk-claude-primary");
assert.equal(fetchCalls[0].body.messages[0].role, "user");
assert.equal(fetchCalls[0].body.messages[0].content[0].text, "Hello Claude");
assert.equal(json.object, "chat.completion");
assert.equal(json.choices[0].message.content, "Claude translated reply");
});
test("chat pipeline translates OpenAI requests to Gemini and returns OpenAI-shaped responses", async () => {
await seedConnection("gemini", { apiKey: "sk-gemini-primary" });
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildGeminiResponse("Gemini translated reply");
};
const response = await handleChat(
buildRequest({
body: {
model: "gemini/gemini-2.5-flash",
stream: false,
messages: [{ role: "user", content: "Hello Gemini" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /generateContent$/);
assert.equal(fetchCalls[0].headers["x-goog-api-key"], "sk-gemini-primary");
assert.equal(fetchCalls[0].body.contents[0].role, "user");
assert.equal(fetchCalls[0].body.contents[0].parts[0].text, "Hello Gemini");
assert.equal(json.object, "chat.completion");
assert.equal(json.choices[0].message.content, "Gemini translated reply");
});
test("chat pipeline translates Claude-format requests into OpenAI upstream and back to Claude", async () => {
await seedConnection("openai", { apiKey: "sk-openai-claude-route" });
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponse("OpenAI answered Claude client");
};
const response = await handleChat(
buildRequest({
url: "http://localhost/v1/messages",
body: {
model: "openai/gpt-4o-mini",
stream: false,
max_tokens: 128,
system: [{ text: "Be brief" }],
messages: [{ role: "user", content: [{ type: "text", text: "Hello from Claude client" }] }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.match(fetchCalls[0].url, /\/chat\/completions$/);
assert.equal(fetchCalls[0].body.messages[0].role, "system");
assert.equal(fetchCalls[0].body.messages[0].content, "Be brief");
assert.equal(fetchCalls[0].body.messages[1].content, "Hello from Claude client");
assert.equal(json.type, "message");
assert.equal(json.role, "assistant");
assert.equal(json.content[0].text, "OpenAI answered Claude client");
});
test("chat pipeline converts Claude SSE streams into OpenAI SSE output", async () => {
await seedConnection("claude", { apiKey: "sk-claude-stream" });
globalThis.fetch = async () => buildClaudeStreamResponse("Streamed Claude chunk");
const response = await handleChat(
buildRequest({
body: {
model: "claude/claude-sonnet-4-6",
stream: true,
messages: [{ role: "user", content: "Stream this" }],
},
})
);
const raw = await response.text();
assert.equal(response.status, 200);
assert.equal(response.headers.get("Content-Type"), "text/event-stream");
assert.match(raw, /chat\.completion\.chunk/);
assert.match(raw, /Streamed Claude chunk/);
assert.match(raw, /\[DONE\]/);
});
test("chat pipeline rejects invalid API keys and malformed JSON bodies", async () => {
await seedConnection("openai", { apiKey: "sk-openai-invalid-key-path" });
const invalidKeyResponse = await handleChat(
buildRequest({
authKey: "does-not-exist",
body: {
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: "Hello" }],
},
})
);
const invalidKeyJson = (await invalidKeyResponse.json()) as any;
const invalidJsonResponse = await handleChat(
new Request("http://localhost/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: "{bad-json",
})
);
const invalidJson = (await invalidJsonResponse.json()) as any;
assert.equal(invalidKeyResponse.status, 401);
assert.match(invalidKeyJson.error.message, /Invalid API key|Incorrect API key/i);
assert.equal(invalidJsonResponse.status, 400);
assert.match(invalidJson.error.message, /Invalid JSON body/i);
});
test("chat pipeline allows unauthenticated requests through to provider resolution when called directly (authz pipeline enforces REQUIRE_API_KEY at route level)", async () => {
process.env.REQUIRE_API_KEY = "true";
const response = await handleChat(
buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Missing auth" }],
},
})
);
const json = (await response.json()) as any;
// 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.
// 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 () => {
const response = await handleChat(
buildRequest({
body: {
stream: false,
messages: [{ role: "user", content: "No model selected" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(json.error.message, /Missing model/i);
});
test("chat pipeline treats Accept text/event-stream as streaming mode and returns a session header", async () => {
await seedConnection("openai", { apiKey: "sk-openai-accept-stream" });
globalThis.fetch = async () => buildOpenAIStreamResponse("Accept header stream");
// #5305/#5309: only a PURE `text/event-stream` Accept (without application/json)
// forces SSE when `stream` is omitted. A mixed `application/json, text/event-stream`
// Accept is the Vercel/OpenAI SDK non-stream signature and now resolves to JSON, so
// this SSE-opt-in test must send the pure-SSE Accept header.
const response = await handleChat(
buildRequest({
headers: { Accept: "text/event-stream" },
body: {
model: "openai/gpt-4o-mini",
messages: [{ role: "user", content: "Stream via Accept" }],
},
})
);
const raw = await response.text();
assert.equal(response.status, 200);
assert.equal(response.headers.get("Content-Type"), "text/event-stream");
assert.ok(response.headers.get("X-OmniRoute-Session-Id"));
assert.match(raw, /Accept header stream/);
assert.match(raw, /\[DONE\]/);
});
test("chat pipeline supports local mode without Authorization on explicit combos", async () => {
await seedConnection("openai", { apiKey: "sk-openai-local-combo" });
await combosDb.createCombo({
name: "local-router",
strategy: "priority",
models: ["openai/gpt-4o-mini"],
});
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
headers: toPlainHeaders(init.headers),
});
return buildOpenAIResponse("Local combo route");
};
const response = await handleChat(
buildRequest({
body: {
model: "local-router",
stream: false,
messages: [{ role: "user", content: "No auth header here" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.equal(json.choices[0].message.content, "Local combo route");
});
test("chat pipeline honors noLog by redacting persisted call log payloads", async () => {
await seedConnection("openai", { apiKey: "sk-openai-no-log" });
const apiKey = await seedApiKey({ noLog: true });
globalThis.fetch = async () => buildOpenAIResponse("No-log reply");
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Do not persist payloads" }],
},
})
);
assert.equal(response.status, 200);
const callLog = await waitFor(() => getLatestCallLog());
assert.ok(callLog, "expected a call log row to be created");
assert.equal(callLog.apiKeyId, apiKey.id);
assert.equal(callLog.requestBody, null);
assert.equal(callLog.responseBody, null);
assert.equal(callLog.artifactRelPath, null);
});
test("chat pipeline returns current no-credentials contract when no provider connection exists", async () => {
const response = await handleChat(
buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Hello" }],
},
})
);
const json = (await response.json()) as any;
// 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 () => {
await seedConnection("openai", { apiKey: "sk-openai-500" });
globalThis.fetch = async () =>
new Response(JSON.stringify({ error: { message: "provider exploded" } }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
const response = await handleChat(
buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Trigger 500" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 500);
assert.match(json.error.message, /\[500\]: provider exploded/);
});
test("chat pipeline returns 429 with Retry-After when the upstream rate-limits the only account", async () => {
await seedConnection("openai", { apiKey: "sk-openai-429" });
await settingsDb.updateSettings({
requestRetry: 0,
maxRetryIntervalSec: 0,
});
let attempts = 0;
globalThis.fetch = async () => {
attempts += 1;
return new Response(
JSON.stringify({
error: {
message: "Rate limit exceeded. Your quota will reset after 30s.",
},
}),
{
status: 429,
headers: { "Content-Type": "application/json" },
}
);
};
const response = await handleChat(
buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Trigger 429" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 429);
assert.ok(attempts >= 1, "expected at least one upstream attempt");
assert.ok(Number(response.headers.get("Retry-After")) >= 1);
assert.match(json.error.message, /\[openai\/gpt-4o-mini\]/);
});
test("chat pipeline keeps provider breaker closed for repeated connection-scoped 429s", async () => {
await seedConnection("openai", { apiKey: "sk-openai-429-breaker" });
await settingsDb.updateSettings({
requestRetry: 0,
maxRetryIntervalSec: 0,
});
globalThis.fetch = async () =>
new Response(
JSON.stringify({
error: {
message: "Rate limit exceeded. Your quota will reset after 30s.",
},
}),
{
status: 429,
headers: { "Content-Type": "application/json" },
}
);
for (let i = 0; i < 3; i += 1) {
const response = await handleChat(
buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: `Trigger 429 #${i + 1}` }],
},
})
);
assert.equal(response.status, 429);
}
const breaker = getCircuitBreaker("openai");
const status = breaker.getStatus();
assert.equal(status.state, "CLOSED");
assert.equal(status.failureCount, 0);
});
test("chat pipeline maps upstream timeouts to 504 responses", async () => {
await seedConnection("openai", { apiKey: "sk-openai-timeout" });
globalThis.fetch = async () => {
const error = new Error("upstream timed out");
error.name = "TimeoutError";
throw error;
};
const response = await handleChat(
buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Trigger timeout" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 504);
assert.match(json.error.message, /\[504\]: upstream timed out/);
});
test("chat pipeline injects memory context before sending the upstream request", async () => {
// Reset provider failure state to avoid circuit breaker interference
clearProviderFailure("openai");
await seedConnection("openai", { apiKey: "sk-openai-memory" });
const apiKey = await seedApiKey();
await settingsDb.updateSettings({
memoryEnabled: true,
memoryMaxTokens: 400,
memoryRetentionDays: 30,
memoryStrategy: "recent",
});
invalidateMemorySettingsCache();
insertLegacyMemory(apiKey.id, "User prefers concise answers.");
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIResponse("Memory-aware reply");
};
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Summarize my preference" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.equal(fetchCalls[0].body.messages[0].role, "system");
assert.match(fetchCalls[0].body.messages[0].content, /User prefers concise answers/);
assert.equal(json.choices[0].message.content, "Memory-aware reply");
});
test("chat pipeline injects skills into tools and intercepts tool calls with skill output", async () => {
// Reset provider failure state to avoid circuit breaker interference
clearProviderFailure("openai");
await seedConnection("openai", { apiKey: "sk-openai-skills" });
const apiKey = await seedApiKey();
await settingsDb.updateSettings({ skillsEnabled: true });
invalidateMemorySettingsCache();
const handlerName = `weather-handler-${Date.now()}`;
skillExecutor.registerHandler(handlerName, async (input) => ({
forecast: `Sunny in ${input.location}`,
}));
await skillRegistry.register({
apiKeyId: apiKey.id,
name: "lookupWeather",
version: "1.0.0",
description: "Return a canned forecast",
schema: {
input: {
type: "object",
properties: {
location: { type: "string" },
},
},
output: {
type: "object",
},
},
handler: handlerName,
enabled: true,
});
const fetchCalls = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
fetchCalls.push({
url: String(url),
body: init.body ? JSON.parse(String(init.body)) : null,
});
return buildOpenAIToolCallResponse();
};
const response = await handleChat(
buildRequest({
authKey: apiKey.key,
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Check the weather" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(fetchCalls.length, 1);
assert.ok(Array.isArray(fetchCalls[0].body.tools));
assert.equal(fetchCalls[0].body.tools[0].function.name, "lookupWeather@1.0.0");
assert.equal(json.choices[0].finish_reason, "tool_calls");
assert.equal(json.tool_results[0].tool_call_id, "call_weather");
assert.equal(JSON.parse(json.tool_results[0].output).forecast, "Sunny in Sao Paulo");
});
test("chat pipeline falls back to the next account after a provider failure", async () => {
// Reset provider failure state to avoid circuit breaker interference
clearProviderFailure("openai");
await seedConnection("openai", {
name: "openai-primary",
apiKey: "sk-openai-primary-fallback",
priority: 1,
});
await seedConnection("openai", {
name: "openai-secondary",
apiKey: "sk-openai-secondary-fallback",
priority: 2,
});
const seenAuthHeaders = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
const headers = toPlainHeaders(init.headers);
seenAuthHeaders.push(headers.Authorization);
if (seenAuthHeaders.length === 1) {
return new Response(JSON.stringify({ error: { message: "first account failed" } }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
return buildOpenAIResponse("Second account succeeded");
};
const response = await handleChat(
buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
messages: [{ role: "user", content: "Use account fallback" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.deepEqual(seenAuthHeaders, [
"Bearer sk-openai-primary-fallback",
"Bearer sk-openai-secondary-fallback",
]);
assert.equal(json.choices[0].message.content, "Second account succeeded");
});
test("chat pipeline falls back across combo models when the first provider fails", async () => {
// Reset provider failure state to avoid circuit breaker interference
clearProviderFailure("openai");
clearProviderFailure("claude");
await seedConnection("openai", { apiKey: "sk-openai-combo-fail" });
await seedConnection("claude", { apiKey: "sk-claude-combo-fail" });
await combosDb.createCombo({
name: "combo-fallback",
strategy: "priority",
config: { maxRetries: 0, retryDelayMs: 0 },
models: ["openai/gpt-4o-mini", "claude/claude-3-5-sonnet-20241022"],
});
const attempts = [];
globalThis.fetch = async (url, init: RequestInit = {}) => {
const call = {
url: String(url),
headers: toPlainHeaders(init.headers),
};
attempts.push(call);
if (attempts.length === 1) {
return new Response(JSON.stringify({ error: { message: "openai combo miss" } }), {
status: 503,
headers: { "Content-Type": "application/json" },
});
}
return buildClaudeResponse("Claude combo fallback");
};
const response = await handleChat(
buildRequest({
body: {
model: "combo-fallback",
stream: false,
messages: [{ role: "user", content: "Use combo fallback" }],
},
})
);
const json = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(attempts.length, 2);
assert.match(attempts[0].url, /\/chat\/completions$/);
assert.match(attempts[1].url, /\?beta=true$/);
assert.equal(json.choices[0].message.content, "Claude combo fallback");
});
test("chat pipeline deduplicates concurrent identical non-stream requests", async () => {
// Reset provider failure state to avoid circuit breaker interference
clearProviderFailure("openai");
await seedConnection("openai", { apiKey: "sk-openai-dedup" });
let fetchCount = 0;
globalThis.fetch = async () => {
fetchCount += 1;
await new Promise((resolve) => setTimeout(resolve, 25));
return buildOpenAIResponse("Deduplicated response");
};
const requestA = buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
temperature: 0,
messages: [{ role: "user", content: "Deduplicate this request" }],
},
});
const requestB = buildRequest({
body: {
model: "openai/gpt-4o-mini",
stream: false,
temperature: 0,
messages: [{ role: "user", content: "Deduplicate this request" }],
},
});
const [responseA, responseB] = await Promise.all([handleChat(requestA), handleChat(requestB)]);
const [jsonA, jsonB] = await Promise.all([responseA.json(), responseB.json()]);
assert.equal(responseA.status, 200);
assert.equal(responseB.status, 200);
assert.equal(fetchCount, 1);
assert.equal(jsonA.choices[0].message.content, "Deduplicated response");
assert.equal(jsonB.choices[0].message.content, "Deduplicated response");
});