Files
OmniRoute/tests/unit/codex-gpt55-routing-5887.test.ts
Diego Rodrigues de Sa e Souza 4a3dcf6b0b fix(routing): only let Codex-native bare ids preempt a provider when codex is active (#9447)
* fix(routing): only let Codex-native bare ids preempt a provider when codex is active

#9275 widened CODEX_NATIVE_UNPREFIXED_MODELS from a single id to gpt-5.5 plus the
gpt-5.6-sol/terra/luna tiers, so bare Codex CLI ids would reach the ChatGPT
subscription instead of fanning out to whichever provider won the inference race.
The early return it added never consulted the active-provider set, which made the
codex-only guard 30 lines below unreachable for every id in the set:

  if (CODEX_NATIVE_UNPREFIXED_MODELS.has(modelId)) return { provider: "codex", ... }

An OpenAI-only install therefore had bare gpt-5.5 routed to codex and failed with
'no active credentials for provider: codex' on a model OpenAI serves, and an install
whose codex connection was merely inactive failed identically. This also silently
reverted #5887's compatibility boundary.

The preference now only PREEMPTS another provider when a codex connection is active.
Ids that no other provider catalogs (codex-auto-review) still resolve to codex with no
connection at all — there is nothing to preempt and 'no codex credentials' is the
honest error. With codex active the preference still beats OpenAI, which is the point
of #9275, and an explicit openai/ prefix overrides it either way.

Tests: the three assertions that encode the intended #9275 change now expect codex
(plus a new one pinning the explicit-prefix override); the rest were already correct
and pass again untouched. Adds a regression test for the OpenAI-only case.

* docs(changelog): correct fragment id to #9447

* test(routing): seed an active codex connection in the bare-precedence guards

The two files #9275 added assert that bare gpt-5.5 / gpt-5.6-sol reach codex, but
they ran against an empty database — so they also pinned 'codex wins with no codex
connection at all', which is the regression #9447 removes. That put them in direct
contradiction with plan3-p0 / chat-helpers / codex-gpt55-routing-5887, which assert
openai for the very same input: no implementation could satisfy both, which is why
the release could not go green.

Seeding an active codex connection keeps the contract these files were written to
guard (codex beats openai for a Codex-native bare id) while dropping the accidental
'even with no codex configured' half. Cases that need no connection are left as they
were: the tier-only ids and codex-auto-review have no alternative provider to preempt,
and the explicit-prefix overrides are unaffected.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-04 17:08:08 -03:00

89 lines
3.9 KiB
TypeScript

/**
* Issue #5887 — regression: an unprefixed `gpt-5.5` request from a codex-only
* setup (no OpenAI connection) stopped auto-routing to the `codex` provider.
*
* Root cause: `gpt-5.5` was added to the OpenAI static catalog
* (`open-sse/config/providers/registry/openai/index.ts`), so the
* `if (providers.includes("openai"))` short-circuit in
* `resolveModelByProviderInference` (open-sse/services/model.ts) started
* firing BEFORE the codex-preference block — making that block unreachable for
* `gpt-5.5`. Result: a codex-only user (no OpenAI connection) had `gpt-5.5`
* routed to `openai`, and Codex-only hosted image generation failed.
*
* Catalog-driven inference generalizes the original GPT-5.5-specific fix while
* preserving its compatibility boundary: Codex-only users route through Codex,
* but OpenAI remains the historical default when both providers are active.
*/
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-gpt55-5887-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const { getModelInfoCore } = await import("../../open-sse/services/model.ts");
let openaiConnectionId: number | string | undefined;
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
// (a) Codex active, OpenAI NOT active → bare gpt-5.5 must infer codex.
// FAILS before the fix (OpenAI static-catalog short-circuit wins).
test("#5887(a) codex-only setup infers codex for unprefixed gpt-5.5", async () => {
await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: "codex@example.com",
providerSpecificData: { workspaceId: "ws-1" },
});
const info = await getModelInfoCore("gpt-5.5", null);
assert.equal(info.provider, "codex", "gpt-5.5 must infer codex when only codex is active");
// #2877: the bare id must be preserved — no `-medium` effort suffix baked in.
assert.equal(info.model, "gpt-5.5", "codex inference keeps the bare gpt-5.5 id");
});
// (b) Codex + OpenAI active → Codex wins for a Codex-native bare id.
// Reversed by #9275: `gpt-5.5` joined CODEX_NATIVE_UNPREFIXED_MODELS, so the
// ChatGPT subscription is now the deliberate destination for bare Codex CLI ids
// even with OpenAI active. The compatibility boundary this file documented moved
// from "OpenAI wins the overlap" to "an explicit prefix wins the overlap" —
// asserted in (b2) below so the override is not silently lost.
test("#5887(b) active Codex and OpenAI connections route bare gpt-5.5 to Codex", async () => {
const conn = await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
apiKey: "sk-test",
});
openaiConnectionId = (conn as { id?: number | string })?.id;
const info = await getModelInfoCore("gpt-5.5", null);
assert.equal(
info.provider,
"codex",
"bare gpt-5.5 prefers the Codex subscription once both providers are active (#9275)"
);
assert.equal(info.model, "gpt-5.5");
});
// (b2) …but the explicit prefix stays authoritative — the documented escape hatch.
test("#5887(b2) an explicit openai/ prefix still overrides the Codex preference", async () => {
const info = await getModelInfoCore("openai/gpt-5.5", null);
assert.equal(info.provider, "openai", "explicit provider prefix beats the Codex-native set");
assert.equal(info.model, "gpt-5.5");
});
// (c) Non-regression: a normal OpenAI model still routes to openai.
test("#5887(c) gpt-4o routes to openai with openai active", async () => {
assert.ok(openaiConnectionId !== undefined, "openai connection created in (b)");
const info = await getModelInfoCore("gpt-4o", null);
assert.equal(info.provider, "openai");
});