Files
OmniRoute/tests/integration/combo-matrix/context-relay-codex.test.ts
Diego Rodrigues de Sa e Souza dc40911583 Release v3.8.39 (#5164)
* chore(release): open v3.8.39 development cycle

* docs(changelog): backfill 5 v3.8.38 bullets merged after release finalize

These PRs squash-merged into release/v3.8.38 between the CHANGELOG finalize
(ff57be32f) and the merge-to-main (ae6e2342d), so they shipped in the v3.8.38
tag but had no bullet:

- feat(compression): Ionizer engine (lossy JSON-array sampling + CCR) (#5148)
- fix(sse): preserve non-stream reasoning fields (#5155, @rdself)
- fix(i18n): add missing English UI labels (#5153, @rdself)
- test(combo): gated live smoke (#5151) + release-expectations refresh (#5150, @KooshaPari)

(#5129 exact-host Anthropic baseUrl is already covered by the #5130 bullet — same CodeQL #674.)
Synced 41 i18n CHANGELOG mirrors.

* feat(compression): TOON best-of-N candidate encoder + encoder A/B table (#5163)

Integrated into release/v3.8.39. TOON best-of-N candidate encoder (GCF default, fail-open). 17/17 unit tests pass on merge result; CI reds were base-stale + Quality Ratchet DRIFT.

* fix(zenmux): normalize vendor-prefixed GLM system roles (#5158)

Integrated into release/v3.8.39. ZenMux vendor-prefixed GLM system-role normalization; 12/12 role-normalizer tests pass on merge result. CI reds base-stale.

* [codex] fix xAI OAuth test and reasoning effort (#5157)

Integrated into release/v3.8.39. xAI reasoning-effort normalization (max/xhigh→high) + OAuth test config; 46/46 xai-translator tests pass on merge result. CI reds base-stale.

* docs(i18n): add Traditional Chinese (zh-TW) README and update zh-CN to latest (#5162)

Integrated into release/v3.8.39. Traditional Chinese (zh-TW) README + zh-CN refresh; docs-only.

* test(security): guard PII redaction stays opt-in (default off) + Hard Rule #20 (#5159)

Integrated into release/v3.8.39. PII opt-in regression guard + Hard Rule #20; rebased to strip base-drift (+81/-1). 5/5 guard tests pass; flip-proof verified.

* test(combo): deterministic context-relay universal-handoff coverage (closes phase-2 TODO) (#5168)

Integrated into release/v3.8.39. Deterministic context-relay universal-handoff coverage (3 tests); 3/3 pass on merge result.

* docs(i18n): full sync zh-TW and zh-CN README with canonical English v3.8.39 (#5171)

Integrated into release/v3.8.39. Full zh-TW docs tree + zh-CN sync with canonical English v3.8.39; docs-only.

* fix(serve): honour HOSTNAME from .env instead of hardcoding 0.0.0.0 (#5134) (#5170)

Integrated into release/v3.8.39. HOSTNAME env override in serve (#5134) + regression test (4/4, TDD flip-proof verified).

* fix(sse): resolve nameless deepseek-web tool blocks via parameter-schema match (#5154) (#5173)

Integrated into release/v3.8.39. Schema-based nameless deepseek-web tool-block resolution (#5154); 6/6 tests pass on merge result (incl. ambiguous/no-match negatives + named-tag no-regression).

* fix(sse): normalize array user content for Command Code to avoid upstream 400 (#5166) (#5174)

Integrated into release/v3.8.39. Normalize array user content for Command Code (#5166, user-array/400 symptom); 4/4 tests pass on merge result.

* fix(sse): defer </think> close so it never leaks before tool_calls (#5123) (#5175)

Integrated into release/v3.8.39. Defer </think> close so it never leaks before tool_calls (#5123); 4/4 tests pass (incl. #4633 no-regression). CHANGELOG synced to keep all 3 v3.8.39 fixes.

* fix(dashboard): use amber for home update-step warning icon (#5176)

Integrated into release/v3.8.39. Amber for home update-step warning icon; 1/1 UI test.

* fix(api): LAN/Tailscale dashboard — host-aware CSP + GET-exempt version route + combo field errors (#5083) (#5177)

Integrated into release/v3.8.39. Host-aware CSP (ReDoS/injection-safe host validation) + GET-exempt /api/system/version (POST/spawn stays LOCAL_ONLY, exact-match safe-methods-only) + COMBO_002 firstField. 44/44 tests + route-guard membership gate green. CHANGELOG synced to keep all 4 v3.8.39 fixes.

* fix(api): replace #5083 global middleware CSP with declarative ws: scheme (#5083)

Follow-up to PR #5177 (merged): that version implemented the LAN-CSP fix (Bug 1)
with a new global `src/middleware.ts` + `src/server/csp.ts`, which contradicts the
project's documented architecture — 'No global Next.js middleware — interception is
route-specific' (CLAUDE.md / AGENTS.md) — and was merged unverified (middleware vs
next.config header precedence was never confirmed in a real build).

This replaces that approach with the minimal, declarative equivalent:
  • next.config.mjs: connect-src now permits the bare `ws:` scheme (symmetric with the
    bare `wss:` already allowed) so the dashboard can reach its own Live WS server from
    a LAN/Tailscale host. No middleware.
  • Removes src/middleware.ts, src/server/csp.ts, and tests/unit/csp-host-aware.test.ts.
  • Adds tests/unit/csp-lan-ws-5083.test.ts (incl. a guard asserting src/middleware.ts
    does NOT exist, so the global-middleware approach cannot silently return).

Bugs 2 (GET-exempt /api/system/version) and 3 (COMBO_002 field surfacing) from #5177
are unaffected and remain in place.

Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>

* test(combo): end-to-end quota-share DRR routing-decision coverage (matrix parity) (#5179)

Integrated into release/v3.8.39. Quota-share DRR routing-decision coverage (matrix parity); 2/2 pass on merge result.

* feat(agent-bridge): graceful cert-install fallback with manual guide for containers (#4546) (#5178)

Integrated into release/v3.8.39. Agent-bridge graceful cert-install fallback + manual guide (#4546); 6/6 tests pass on merge result.

* fix(antigravity): family-scoped quota lockout (gemini/claude buckets) (#5180)

Integrated into release/v3.8.39 — family-scoped antigravity quota lockout. Rebased from v3.8.37 + validated (vitest 5/5, typecheck clean, full combo-matrix green, model-lockout 99/0). Same-model cross-account retry (chat.ts) deferred pending live antigravity VPS validation.

* fix(cli): force NODE_ENV to match dev/start run mode in custom Next server (#5189)

Integrated into release/v3.8.39. Force NODE_ENV to match dev/start run mode in custom Next server; 2/2 source-scan+ordering tests pass on merge result.

* feat(compression): CCR ranged/grep/stats retrieval (ReDoS-safe, backward-compat) (#5187)

Integrated into release/v3.8.39. CCR ranged/grep/stats retrieval (safe-regex ReDoS guard + length/match caps); 17/17 tests pass on merge result.

* docs(combo): sync all combo/routing-strategy docs to current state + document test coverage (#5185)

Integrated into release/v3.8.39. Combo/routing-strategy docs sync; docs-only.

* fix(mcp): return 404 (not 400) for unknown Streamable HTTP session id (#5169) (#5191)

* fix(api): respect blocked Auto (Zero-Config) provider in /v1/models catalog (#5192) (#5194)

* test(combo): deterministic context-relay codex quota-handoff coverage (closes last gap) (#5195)

* test(ci): wire antigravity-quota-family under test:vitest (fix test-discovery orphan) (#5196)

* fix(oauth): antigravity login no longer hangs — fire-and-forget onboarding + bounded post-exchange (#5193)

Antigravity OAuth hang fix (no-PKCE/no-openid + bounded post-exchange + exchange-500 fix). Includes #5200 (Koosha) revert + owner rebaseline to keep documented comments. Integrated into release/v3.8.39.

* feat(oauth): remote Antigravity login via local helper + paste-credentials (#5203)

Remote Antigravity login: local helper (omniroute login antigravity) + paste-credentials. Integrated into release/v3.8.39.

* fix(translator): accept Claude Messages shape in non-stream malformed-200 guard (#5156)

Integrated into release/v3.8.39

* fix(cli): default dev bundler to Turbopack (16.2.x panic no longer reproduces) (#5206)

Integrated into release/v3.8.39

* fix(cli): auto-calibrate server V8 heap from physical RAM (#5172) (#5213)

The server was spawned with a fixed --max-old-space-size=512 (omniroute serve)
or no heap flag at all (Electron), so RAM-rich boxes still OOM-crashed under
load (Ineffective mark-compacts near heap limit ~500MB) with many providers/
accounts and large model catalogs. New calibrateHeapFallbackMb(os.totalmem())
defaults the heap to ~35% of RAM clamped [512,4096], wired into serve.mjs and
electron/main.js. Explicit OMNIROUTE_MEMORY_MB still wins (#2939 unchanged).

Also addresses #5160 (same OOM root); #5152 (docker) benefits via the same knob.

Closes #5172

* fix(proxy): coalesce fast-fail health probes (#5208)

Integrated into release/v3.8.39

* fix(proxy): close dispatchers when clearing cache (#5202)

Integrated into release/v3.8.39

* fix(cli): raise dev server Node heap limit to 8GB to prevent OOM (#5198)

Integrated into release/v3.8.39

* fix(auth): allow synthetic no-auth fallback for mimocode (#5205)

Integrated into release/v3.8.39

* fix(oauth): preserve Antigravity refresh_token on empty/omitted upstream response (#3850) (#5214)

Google's OAuth refresh tokens are non-rotating: the refresh response usually
omits refresh_token and occasionally returns it as an empty string. The
Antigravity executor used `typeof tokens.refresh_token === "string" ? ... `
which accepts "" (typeof "" === "string") and overwrote the stored token with
empty, nulling it on first refresh. Now treats non-string OR empty as absent and
preserves credentials.refreshToken, matching refreshGoogleToken semantics.

Closes #3850

* fix(responses): normalize non-array input (#5204)

Integrated into release/v3.8.39

* fix(stream): normalize safety finish reasons via shared helper (#5197)

Integrated into release/v3.8.39

* fix(request-logger): never render negative '(-100%)' compression badge (#5201)

Integrated into release/v3.8.39

* fix(combo): reject empty responses api output (#5207)

Integrated into release/v3.8.39 — combo failover now rejects empty Responses API output (validateQuality). Baseline rebaseline dropped (main-measured drift; maintainer rebaselines at release).

* fix(pwa): prefer cached navigation before offline page (#5209)

Integrated into release/v3.8.39 — PWA service worker prefers cached navigation before offline page (#5165).

* chore(release): v3.8.39 — 2026-06-28

* chore(release): rebaseline openapi+i18n coverage ratchet drift for v3.8.39

---------

Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: Nguyen Minh <lop123thcs@gmail.com>
Co-authored-by: lunkerchen <labanchen@gmail.com>
Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com>
Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
Co-authored-by: Ardem2025 <ardemb22@gmail.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: Anton <39598727+NomenAK@users.noreply.github.com>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: Wilson <pedbookmed@gmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
2026-06-28 06:58:29 -03:00

368 lines
16 KiB
TypeScript

// tests/integration/combo-matrix/context-relay-codex.test.ts
//
// Deterministic in-process test for context-relay CODEX-SPECIFIC HANDOFF
// (combo.ts ~2143-2183): the quota-aware handoff that fires after a successful
// codex response when the quota exceeds the threshold.
//
// Code path:
// if (strategy === "context-relay" && relayOptions?.sessionId && relayConfig &&
// relayConfig.handoffProviders.includes(provider) && provider === "codex") {
// const connectionId = getSessionConnection(relayOptions.sessionId);
// if (connectionId) {
// const quotaInfo = await fetchCodexQuota(connectionId).catch(() => null);
// if (quotaInfo) { ... maybeGenerateHandoff({ ..., expiresAt: resetCandidates[0] }); }
// }
// }
//
// PRIMARY observable: getHandoff(sessionId, comboName).expiresAt === session-window
// reset time from the codex quota response. This is the CODEX-SPECIFIC proof:
// the universal handoff path does NOT set expiresAt from a codex quota fetch.
//
// SECONDARY observables:
// - The codex usage URL was fetched (proves the quota path ran).
// - The handoff record has a non-empty .summary.
//
// CONTROL: combo targeting openai (not codex) → codex block NEVER runs →
// codex usage URL is never fetched → expiresAt from quota doesn't appear.
//
// Seams used:
// - registerCodexConnection / unregisterCodexConnection (codexQuotaFetcher.ts)
// - clearSessions (sessionManager.ts) — cleanup between tests
// - buildCodexResponsesSse helper (mirrors codex-stream-false.test.ts)
// - Custom fetch mock distinguishes quota URL / summary dispatch / main dispatch
import test from "node:test";
import assert from "node:assert/strict";
import { createComboRoutingHarness } from "../_comboRoutingHarness.ts";
import {
registerCodexConnection,
unregisterCodexConnection,
} from "../../../open-sse/services/codexQuotaFetcher.ts";
import { clearSessions } from "../../../open-sse/services/sessionManager.ts";
// ── Harness setup ─────────────────────────────────────────────────────────────
// Each harness owns an isolated DATA_DIR so DB handles don't clash.
const h = await createComboRoutingHarness("combo-relay-codex");
const {
BaseExecutor,
combosDb,
handleChat,
buildRequest,
seedConnection,
resetStorage,
buildOpenAIResponse,
waitFor,
toPlainHeaders,
} = h;
// Import DB helpers AFTER harness creation so they share the same DB instance
// (DATA_DIR is set by the harness before any import triggers DB init).
const { getHandoff } = await import("../../../src/lib/db/contextHandoffs.ts");
// ── Constants ─────────────────────────────────────────────────────────────────
const CODEX_COMBO_NAME = "m-relay-codex-quota";
const SESSION_HEADER_VALUE = "relay-codex-quota-001";
const SESSION_ID = `ext:${SESSION_HEADER_VALUE}`;
// Codex endpoint URLs (must match codexQuotaFetcher.ts and the executor config).
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
const CODEX_RESPONSES_HOST = "chatgpt.com/backend-api/codex/responses";
// Summary JSON that parseHandoffJSON will successfully parse.
const CODEX_SUMMARY_JSON = JSON.stringify({
summary:
"User is implementing a TypeScript context-relay codex quota-handoff test using TDD.",
keyDecisions: ["codex provider selected", "quota threshold at 90%"],
taskProgress: "writing deterministic integration test for codex handoff",
activeEntities: ["combo.ts", "codexQuotaFetcher.ts", "contextHandoff.ts"],
});
// ── SSE builder (mirrors codex-stream-false.test.ts) ─────────────────────────
function buildCodexResponsesSse(text = "codex assistant reply") {
return new Response(
[
"event: response.created",
'data: {"type":"response.created","response":{"id":"resp_codex_1","model":"gpt-5.3-codex","status":"in_progress","output":[]}}',
"",
"event: response.output_text.delta",
`data: ${JSON.stringify({
type: "response.output_text.delta",
output_index: 0,
delta: text,
})}`,
"",
"event: response.completed",
`data: ${JSON.stringify({
type: "response.completed",
response: {
id: "resp_codex_1",
object: "response",
model: "gpt-5.3-codex",
status: "completed",
output: [
{
type: "message",
role: "assistant",
content: [{ type: "output_text", text }],
},
],
usage: { input_tokens: 6, output_tokens: 4 },
},
})}`,
"",
"data: [DONE]",
"",
].join("\n"),
{
status: 200,
headers: { "Content-Type": "text/event-stream" },
}
);
}
// ── Quota response builder ────────────────────────────────────────────────────
// Produces a codex usage API response that parseCodexUsageResponse accepts.
// primary_window → session → percentUsed = 0.90 (90%) — triggers handoff (threshold 0.85)
// secondary_window → weekly → percentUsed = 0.85 (85%) — below threshold for weekly
// Both are below HANDOFF_EXHAUSTION_THRESHOLD (0.95) so maybeGenerateHandoff proceeds.
function buildCodexUsageBody(
sessionResetAtUnix: number,
weeklyResetAtUnix: number
): Record<string, unknown> {
return {
rate_limit: {
primary_window: { used_percent: 90, reset_at: sessionResetAtUnix },
secondary_window: { used_percent: 85, reset_at: weeklyResetAtUnix },
},
};
}
// ── Request builder ───────────────────────────────────────────────────────────
function codexRequest(withSessionId = true) {
return buildRequest({
headers: withSessionId ? { "x-session-id": SESSION_HEADER_VALUE } : {},
body: {
model: CODEX_COMBO_NAME,
stream: false,
messages: [{ role: "user", content: "Write a TypeScript hello world." }],
},
});
}
// ── Fetch mock ────────────────────────────────────────────────────────────────
// Handles three URL classes:
// 1. CODEX_USAGE_URL → codex quota JSON
// 2. CODEX_RESPONSES_HOST (call 1) → main request Responses API SSE
// 3. CODEX_RESPONSES_HOST (call 2) → summary request Responses API SSE (with CODEX_SUMMARY_JSON)
//
// The second codex-responses call is the handoff summary generation, identified by
// call count (the first is always the main request).
function installCodexHandoffFetch(
sessionResetAtUnix: number,
weeklyResetAtUnix: number,
seenUrls: string[]
) {
let codexResponsesCallCount = 0;
globalThis.fetch = async (url: unknown, init: unknown = {}) => {
const u = String(url);
seenUrls.push(u);
const headers =
init != null && typeof init === "object"
? toPlainHeaders((init as Record<string, unknown>).headers)
: {};
void headers; // recorded for debugging; not needed in assertions here
if (u === CODEX_USAGE_URL) {
// Codex quota endpoint — return structured usage JSON.
return new Response(
JSON.stringify(buildCodexUsageBody(sessionResetAtUnix, weeklyResetAtUnix)),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
if (u.includes(CODEX_RESPONSES_HOST)) {
codexResponsesCallCount++;
if (codexResponsesCallCount === 1) {
// Main codex request — return a successful Responses API SSE.
return buildCodexResponsesSse("codex assistant reply ok");
}
// Subsequent call(s) are from the handoff summary generation.
// Return Responses API SSE whose output_text is CODEX_SUMMARY_JSON so that
// generateHandoffAsync → parseHandoffJSON succeeds.
return buildCodexResponsesSse(CODEX_SUMMARY_JSON);
}
// Fallback — should not be reached in these tests.
return new Response(JSON.stringify({ error: { message: "unexpected URL in test" } }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
};
}
// ── Lifecycle hooks ───────────────────────────────────────────────────────────
test.beforeEach(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = 0;
await resetStorage();
});
test.afterEach(async () => {
BaseExecutor.RETRY_CONFIG.delayMs = h.originalRetryDelayMs;
// Clear in-memory session state so test 2 doesn't see stale connections from test 1.
clearSessions();
await resetStorage();
});
test.after(async () => {
await h.cleanup();
});
// ── Test 1 (PRIMARY): codex quota handoff fires ───────────────────────────────
//
// Flow:
// 1. Seed a codex DB connection + register with accessToken.
// 2. Create context-relay combo → codex.
// 3. Send request with x-session-id header.
// 4. During request processing, chat.ts calls touchSession(sessionId, codexConnId).
// 5. After the successful codex response, combo.ts enters the codex block:
// - getSessionConnection → codexConnId (set in step 4).
// - fetchCodexQuota(codexConnId) → hits our mock → returns 90% usage.
// - resetCandidates[0] = session-window resetAt ISO string.
// - maybeGenerateHandoff({ ..., expiresAt: sessionResetISO }) fires via setImmediate.
// 6. generateHandoffAsync dispatches a second codex fetch for the summary.
// 7. upsertHandoff writes to DB with expiresAt = sessionResetISO.
//
// Assertion: handoff.expiresAt === sessionResetISO (codex-specific, not TTL-derived).
test("context-relay codex quota handoff: fires and expiresAt matches session-window reset from quota", async () => {
// 1. Seed codex connection.
const conn = await seedConnection("codex", { apiKey: "sk-codex-handoff-test-1" });
const codexConnId = conn.id;
// 2. Register connection meta so fetchCodexQuota can find credentials.
// chat.ts also calls registerCodexConnection during request processing for OAuth
// tokens; for API-key connections the accessToken may not be set there, so we
// pre-register with the API key as the token. This is the documented test seam.
registerCodexConnection(codexConnId, { accessToken: "sk-codex-handoff-test-1" });
// 3. Create a context-relay combo targeting codex.
// Default handoffProviders: ["codex"] and handoffThreshold: 0.85 apply.
await combosDb.createCombo({
name: CODEX_COMBO_NAME,
strategy: "context-relay",
config: { maxRetries: 0, retryDelayMs: 0, stickyRoundRobinLimit: 1 },
models: [
{ id: "rc-codex-1", kind: "model", providerId: "codex", model: "gpt-5.3-codex" },
],
});
// 4. Compute quota reset times (future timestamps).
const sessionResetAtUnix = Math.floor((Date.now() + 5 * 60 * 60 * 1000) / 1000); // +5h
const weeklyResetAtUnix = Math.floor((Date.now() + 7 * 24 * 60 * 60 * 1000) / 1000); // +7d
// Expected expiresAt: parseWindowReset converts reset_at (unix seconds) → ISO string.
// resetCandidates[0] = earliest ISO string = session window reset (5h < 7d).
const expectedExpiresAt = new Date(sessionResetAtUnix * 1000).toISOString();
// 5. Install fetch mock and send request.
const seenUrls: string[] = [];
installCodexHandoffFetch(sessionResetAtUnix, weeklyResetAtUnix, seenUrls);
const r = await handleChat(codexRequest(true));
assert.equal(r.status, 200, "main codex request must return 200");
// 6. Wait for setImmediate → generateHandoffAsync → upsertHandoff (up to 3s).
const handoff = await waitFor(() => getHandoff(SESSION_ID, CODEX_COMBO_NAME), 3000);
// ── Primary assertion: handoff record exists ──────────────────────────────
assert.ok(
handoff !== null,
"codex quota handoff record must be written to DB when quota ≥ threshold"
);
// ── CODEX-SPECIFIC proof: expiresAt == session-window reset from quota ────
// The universal handoff path never calls fetchCodexQuota, so it cannot produce
// this specific expiresAt value. The value below can only come from the codex block.
assert.equal(
handoff!.expiresAt,
expectedExpiresAt,
`handoff.expiresAt must equal the session-window reset from codex quota (${expectedExpiresAt}); ` +
`got ${handoff!.expiresAt}`
);
// ── Secondary assertion: codex usage URL was fetched ─────────────────────
assert.ok(
seenUrls.includes(CODEX_USAGE_URL),
`codex usage URL must have been fetched to produce the expiresAt; seen URLs: ${JSON.stringify(seenUrls)}`
);
// ── Secondary assertion: summary was generated (non-empty) ───────────────
assert.ok(
typeof handoff!.summary === "string" && handoff!.summary.length > 0,
`handoff.summary must be non-empty; got ${JSON.stringify(handoff!.summary)}`
);
// ── Cleanup ───────────────────────────────────────────────────────────────
unregisterCodexConnection(codexConnId);
});
// ── Test 2 (CONTROL): non-codex provider never triggers the codex block ───────
//
// The codex block gates on `provider === "codex"`. A combo targeting openai
// will never enter it, so the codex usage URL is never fetched and there is no
// quota-derived expiresAt in the DB.
//
// This proves the primary test is not trivially green (i.e., the block is
// actually gated on `provider === "codex"`).
test("context-relay codex quota handoff: does NOT fire when provider is openai (control)", async () => {
// Seed openai (not codex).
await seedConnection("openai", { apiKey: "sk-openai-control-no-codex-block" });
await combosDb.createCombo({
name: CODEX_COMBO_NAME,
strategy: "context-relay",
config: { maxRetries: 0, retryDelayMs: 0, stickyRoundRobinLimit: 1 },
models: [
{ id: "rc-openai-ctrl", kind: "model", providerId: "openai", model: "gpt-4o-mini" },
],
});
const seenUrls: string[] = [];
globalThis.fetch = async (url: unknown, _init: unknown = {}) => {
seenUrls.push(String(url));
return buildOpenAIResponse("assistant reply ok");
};
const r = await handleChat(codexRequest(true));
assert.equal(r.status, 200, "openai request must return 200");
// Give setImmediate time to fire if the block were incorrectly entered.
await new Promise((res) => setTimeout(res, 400));
// The codex block requires provider === "codex", so it never runs for openai.
// The codex usage URL must NOT have been fetched.
assert.ok(
!seenUrls.includes(CODEX_USAGE_URL),
`codex usage URL must NOT be fetched for openai provider; seen: ${JSON.stringify(seenUrls)}`
);
// No codex quota handoff record in DB.
// (The universal handoff also does not fire because no prior model is seeded,
// so getLastSessionModel returns null → no model switch detected.)
const handoff = getHandoff(SESSION_ID, CODEX_COMBO_NAME);
assert.equal(
handoff,
null,
"no handoff record must exist when provider is openai (codex block never entered)"
);
});