fix(api): probe provider alias in models.dev reverse capability lookup (#8429) (#8506)

reverseModelsDevProviders() only matched MODELS_DEV_PROVIDER_MAP entries
by the canonical OmniRoute provider id, but the map's RHS for the OAuth
CLI providers (codex/claude) only lists their alias (cx/cc), never the
canonical id itself. Since the models.dev sync job writes
model_capabilities rows under openai/cx and anthropic/cc (never
codex/claude), and the auto-combo gate canonicalizes a codex/... or
claude/... target's provider to "codex"/"claude" before the lookup,
the synced capability row was unreachable for those two providers.

Also probe the provider's alias (via the already-imported
PROVIDER_ID_TO_ALIAS) when scanning the map, so a canonical id like
"codex"/"claude" still matches entries keyed only by their alias.

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-25 04:57:15 -03:00
committed by GitHub
parent 61277813b2
commit 6cad24eec0
3 changed files with 88 additions and 1 deletions

View File

@@ -0,0 +1 @@
- fix(api): reach synced model_capabilities rows for canonical provider ids that only appear as an alias in MODELS_DEV_PROVIDER_MAP, e.g. codex/claude (#8429)

View File

@@ -281,9 +281,21 @@ function reverseModelsDevProviders(provider: string): string[] {
// models.dev may store capabilities under a different OmniRoute provider id
// that also maps from the same upstream models.dev provider. Build reverse
// candidates from MODELS_DEV_PROVIDER_MAP (e.g. openai ↔ cx).
//
// MODELS_DEV_PROVIDER_MAP's RHS is inconsistent: most providers list their
// canonical id directly, but the OAuth CLI providers (codex/claude) only
// list their alias (cx/cc), never the canonical id. Also probe the
// provider's alias so a canonical id like "codex"/"claude" still matches
// the map entries keyed only by "cx"/"cc" (#8429).
const out = new Set<string>();
const providerAlias = PROVIDER_ID_TO_ALIAS[provider] || provider;
for (const [modelsDevId, omniIds] of Object.entries(MODELS_DEV_PROVIDER_MAP)) {
if (omniIds.includes(provider) || modelsDevId === provider) {
if (
omniIds.includes(provider) ||
omniIds.includes(providerAlias) ||
modelsDevId === provider ||
modelsDevId === providerAlias
) {
out.add(modelsDevId);
for (const id of omniIds) out.add(id);
}

View File

@@ -0,0 +1,74 @@
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-repro-8429-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const modelsDevSync = await import("../../src/lib/modelsDevSync.ts");
const modelCapabilities = await import("../../src/lib/modelCapabilities.ts");
function buildCapability(overrides: Record<string, unknown> = {}) {
return {
tool_call: null, reasoning: null, attachment: null, structured_output: null,
temperature: null, modalities_input: "[]", modalities_output: "[]",
knowledge_cutoff: null, release_date: null, last_updated: null, status: null,
family: null, open_weights: null, limit_context: null, limit_input: null,
limit_output: null, interleaved_field: null, ...overrides,
};
}
function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(() => { resetStorage(); });
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("#8429: synced model_capabilities row written under models.dev mapping is unreachable via the canonical 'codex' provider id", () => {
modelsDevSync.saveModelsDevCapabilities({
openai: { "gpt-6-codex-preview": buildCapability({ tool_call: false }) },
cx: { "gpt-6-codex-preview": buildCapability({ tool_call: false }) },
});
const viaAlias = modelsDevSync.getSyncedCapability("cx", "gpt-6-codex-preview");
assert.equal(viaAlias?.tool_call, false, "row must exist under the alias 'cx'");
const resolved = modelCapabilities.getResolvedModelCapabilities({
provider: "codex",
model: "gpt-6-codex-preview",
});
assert.equal(
resolved.supportsTools,
false,
`expected synced tool_call=false to be resolved for provider "codex" (it was ${resolved.supportsTools})`
);
});
test("#8429: synced model_capabilities row is unreachable via the canonical 'claude' provider id (same class, alias 'cc')", () => {
modelsDevSync.saveModelsDevCapabilities({
anthropic: { "claude-preview-9-9": buildCapability({ tool_call: false }) },
cc: { "claude-preview-9-9": buildCapability({ tool_call: false }) },
});
const viaAlias = modelsDevSync.getSyncedCapability("cc", "claude-preview-9-9");
assert.equal(viaAlias?.tool_call, false, "row must exist under the alias 'cc'");
const resolved = modelCapabilities.getResolvedModelCapabilities({
provider: "claude",
model: "claude-preview-9-9",
});
assert.equal(
resolved.supportsTools,
false,
`expected synced tool_call=false to be resolved for provider "claude" (it was ${resolved.supportsTools})`
);
});