Files
OmniRoute/tests/unit/codex-synced-bare-model-routing.test.ts
guanbear 62415e5e67 fix: infer bare models from active synced catalogs (#7028)
* fix: infer bare models from active synced catalogs

Bare Codex model IDs from Codex CLI can be newer than the static registry even though synchronized connection catalogs advertise and route them with an explicit prefix. Merge exact active synced-provider candidates into bare-model inference and prefer Codex when that active subscription supports the model, replacing the GPT-5.5-specific preference set from #2054.

Constraint: Explicit provider prefixes remain authoritative and unknown GPT models are not guessed as Codex.
Rejected: Add gpt-5.6-sol to the hardcoded preference set | repeats #2054 and fails on the next model release.
Confidence: high
Scope-risk: moderate
Directive: Keep bare-model inference aligned with active synchronized connection catalogs.
Tested: Prettier; typecheck:core; ESLint; 31 focused routing/database tests; focused c8 run.
Not-tested: Full unit suite is blocked locally by DuckDuckGo network timeout and a pre-existing WebDAV path-space URL encoding failure.
Related: https://github.com/diegosouzapw/OmniRoute/pull/2054

* fix: preserve stable overlap routing

Synchronized catalog discovery should repair unambiguous Codex-only model routing without turning provider inference into a global quota preference. Restore the historical OpenAI default when both providers support a bare model, while retaining automatic Codex routing when only its active catalog advertises a future model.

Constraint: Explicit provider prefixes remain authoritative and bare-model inference must remain backward compatible.
Rejected: Always prefer Codex when connected | quota optimization belongs in auto routing or an explicit setting, not provider inference.
Confidence: high
Scope-risk: narrow
Directive: Do not change overlapping bare-model precedence without an explicit routing-policy setting.
Tested: TDD red run with 3 expected overlap failures; 32 focused tests; typecheck:core; ESLint; Prettier; git diff --check.
Related: https://github.com/diegosouzapw/OmniRoute/pull/2054
Related: https://github.com/diegosouzapw/OmniRoute/pull/7028

* test: prove routing across released and future catalogs

Exercise the v3.8.48 GPT-5.6 dual-provider catalog directly and add a non-GPT Anthropic model that exists only in synchronized connection data. This documents that the fix covers the released Codex regression and future uniquely attributable models without claiming to resolve intentional multi-provider ambiguity.

Constraint: GPT-5.6 remains OpenAI-default when both providers are active.
Rejected: Describe the fix as universal model mapping | provider aliases and intentional same-ID ambiguity are separate concerns.
Confidence: high
Scope-risk: narrow
Directive: Keep one non-GPT synchronized-only case so the resolver remains data-driven rather than GPT-specific.
Tested: 37 focused routing/catalog/database tests; typecheck:core; ESLint; Prettier; git diff --check.
Related: https://github.com/diegosouzapw/OmniRoute/releases/tag/v3.8.48
Related: https://github.com/diegosouzapw/OmniRoute/pull/7028

* Keep PR validation deterministic across shallow checkouts

The routing change added one export line to a frozen barrel, so reclaim an existing separator instead of expanding its size. The #6634 regression test now uses in-memory base/head sources that prove both tautology counts grow without assuming origin/main exists in pull-request checkouts.

Constraint: GitHub PR jobs use fetch-depth 1 and do not create origin/main.
Rejected: Fetch full history in every unit shard | adds repeated network cost and still lets the fixture go stale
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep test-masking unit fixtures independent of remote Git refs.
Tested: npm run lint; npm run check:file-size; npm run typecheck:core; 57 focused test-masking tests
Not-tested: Fresh GitHub Actions run pending; full macOS shard has 13 unrelated environment-sensitive failures

* Preserve improved branch coverage in the quality gate

The now-unblocked coverage pipeline reports 78.11% branch coverage, more than five points above the frozen baseline. Tighten the baseline to the measured value so the ratchet retains that improvement instead of rejecting the PR.

Constraint: The blocking quality gate requires baseline tightening when an improvement exceeds tightenSlack.
Rejected: Increase the slack or bypass the gate | would discard a verified coverage improvement
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Lower this baseline only when a reviewed coverage regression is intentionally accepted.
Tested: quality ratchet with the CI-reported 78.11 branch metric; Prettier; git diff --check
Not-tested: Fresh GitHub Actions run pending

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 21:17:53 -03:00

147 lines
5.1 KiB
TypeScript

import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-codex-synced-routing-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const modelsDb = await import("../../src/lib/db/models.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const { getModelInfoCore } = await import("../../open-sse/services/model.ts");
type TestProvider = "anthropic" | "codex" | "openai";
const GPT_56_CODEX_MODEL = "gpt-5.6-sol";
const FUTURE_CODEX_MODEL = "codex-next-preview";
const FUTURE_NON_GPT_MODEL = "orion-preview-2027";
async function seedConnection(provider: TestProvider, isActive = true) {
return providersDb.createProviderConnection({
provider,
authType: provider === "codex" ? "oauth" : "apikey",
name: `${provider}-routing-test`,
email: provider === "codex" ? `${provider}@example.com` : undefined,
apiKey: provider !== "codex" ? `sk-${provider}-routing-test` : undefined,
isActive,
providerSpecificData: provider === "codex" ? { workspaceId: "ws-routing-test" } : undefined,
});
}
async function seedSyncedModel(provider: TestProvider, modelId: string, isActive = true) {
const connection = await seedConnection(provider, isActive);
assert.ok(connection?.id, `${provider} connection must be created`);
await modelsDb.replaceSyncedAvailableModelsForConnection(provider, String(connection.id), [
{
id: modelId,
name: modelId,
apiFormat: "openai-responses",
supportedEndpoints: ["chat"],
},
]);
return connection;
}
test.beforeEach(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
});
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("bare GPT-5.6 model routes through Codex when it is the only active provider", async () => {
await seedSyncedModel("codex", GPT_56_CODEX_MODEL);
const info = await getModelInfoCore(GPT_56_CODEX_MODEL, null);
assert.equal(info.provider, "codex");
assert.equal(info.model, GPT_56_CODEX_MODEL);
});
test("OpenAI remains the historical default when both providers advertise the bare model", async () => {
await seedSyncedModel("codex", GPT_56_CODEX_MODEL);
await seedSyncedModel("openai", GPT_56_CODEX_MODEL);
const info = await getModelInfoCore(GPT_56_CODEX_MODEL, null);
assert.equal(info.provider, "openai");
assert.equal(info.model, GPT_56_CODEX_MODEL);
});
test("Codex is inferred when only its active catalog advertises the model", async () => {
await seedSyncedModel("codex", FUTURE_CODEX_MODEL);
await seedConnection("openai");
const info = await getModelInfoCore(FUTURE_CODEX_MODEL, null);
assert.equal(info.provider, "codex");
assert.equal(info.model, FUTURE_CODEX_MODEL);
});
test("non-GPT models use the same active synchronized-catalog inference", async () => {
await seedSyncedModel("anthropic", FUTURE_NON_GPT_MODEL);
const info = await getModelInfoCore(FUTURE_NON_GPT_MODEL, null);
assert.equal(info.provider, "anthropic");
assert.equal(info.model, FUTURE_NON_GPT_MODEL);
});
test("OpenAI remains selected when it is the only active provider advertising the model", async () => {
await seedSyncedModel("openai", GPT_56_CODEX_MODEL);
const info = await getModelInfoCore(GPT_56_CODEX_MODEL, null);
assert.equal(info.provider, "openai");
assert.equal(info.model, GPT_56_CODEX_MODEL);
});
test("inactive Codex synchronized models do not influence bare-model routing", async () => {
await seedSyncedModel("codex", GPT_56_CODEX_MODEL, false);
await seedSyncedModel("openai", GPT_56_CODEX_MODEL);
const info = await getModelInfoCore(GPT_56_CODEX_MODEL, null);
assert.equal(info.provider, "openai");
assert.equal(info.model, GPT_56_CODEX_MODEL);
});
test("OpenAI remains the historical default for overlapping static models", async () => {
await seedConnection("codex");
await seedConnection("openai");
const info = await getModelInfoCore("gpt-5.5", null);
assert.equal(info.provider, "openai");
assert.equal(info.model, "gpt-5.5");
});
test("OpenAI remains selected for an overlapping static model when Codex is inactive", async () => {
await seedConnection("codex", false);
await seedConnection("openai");
const info = await getModelInfoCore("gpt-5.5", null);
assert.equal(info.provider, "openai");
assert.equal(info.model, "gpt-5.5");
});
test("explicit Codex and OpenAI prefixes remain authoritative", async () => {
await seedSyncedModel("codex", GPT_56_CODEX_MODEL);
await seedSyncedModel("openai", GPT_56_CODEX_MODEL);
const codexAlias = await getModelInfoCore(`cx/${GPT_56_CODEX_MODEL}`, null);
const codexCanonical = await getModelInfoCore(`codex/${GPT_56_CODEX_MODEL}`, null);
const openai = await getModelInfoCore(`openai/${GPT_56_CODEX_MODEL}`, null);
assert.equal(codexAlias.provider, "codex");
assert.equal(codexCanonical.provider, "codex");
assert.equal(openai.provider, "openai");
});