fix(models): correct Codex context and combo limit resolution (#10533)

* fix(models): honor Codex combo context overrides

* test(codex): align discovery context expectation

* test(models): align Codex route limits

* test(models): align remaining Codex route limits
This commit is contained in:
Ke Jin
2026-08-18 21:51:42 +08:00
committed by GitHub
parent ebf0bf913a
commit 9a433775e7
10 changed files with 84 additions and 70 deletions

View File

@@ -0,0 +1 @@
- **fix(models):** align Codex GPT-5.6 context limits with the Codex catalog and honor model context overrides when advertising combos ([#10530](https://github.com/diegosouzapw/OmniRoute/issues/10530))

View File

@@ -279,8 +279,8 @@ export const GPT_5_6_CODEX_CAPABILITIES = {
supportsReasoning: true,
supportsVision: true,
supportsXHighEffort: true,
contextLength: 1050000,
maxInputTokens: 922000,
contextLength: 272000,
maxInputTokens: 272000,
maxOutputTokens: 128000,
} as const;

View File

@@ -431,27 +431,12 @@ async function buildUnifiedModelsResponseCore(
const syncedInputModalities = parseJsonStringArray(synced?.modalities_input);
const syncedOutputModalities = parseJsonStringArray(synced?.modalities_output);
const syncedContext = isPositiveFiniteNumber(synced?.limit_context)
? synced.limit_context
: undefined;
const registryContext = isPositiveFiniteNumber(registryModel?.contextLength)
? registryModel.contextLength
: undefined;
const specContext = isPositiveFiniteNumber(spec?.contextWindow)
? spec.contextWindow
: undefined;
const contextLength =
syncedContext ??
registryContext ??
specContext ??
(getTokenLimit(providerId, modelId) || undefined);
const registryInputLimit = isPositiveFiniteNumber(registryModel?.maxInputTokens)
? registryModel.maxInputTokens
: undefined;
const syncedInputLimit = isPositiveFiniteNumber(synced?.limit_input)
? synced.limit_input
: undefined;
const maxInputTokens = registryInputLimit ?? syncedInputLimit ?? contextLength;
const contextLength = isPositiveFiniteNumber(canonical.limits.contextWindow)
? canonical.limits.contextWindow
: getTokenLimit(providerId, modelId) || undefined;
const maxInputTokens = isPositiveFiniteNumber(canonical.limits.maxInputTokens)
? canonical.limits.maxInputTokens
: contextLength;
const maxOutputTokens = isPositiveFiniteNumber(synced?.limit_output)
? synced.limit_output
: isPositiveFiniteNumber(spec?.maxOutputTokens)

View File

@@ -1,9 +1,8 @@
/**
* Combo context-length computation.
*
* Computes the effective context_window for a combo using the same resolution
* chain as the catalog's `getComboTargetCatalogMetadata`:
* synced → registry → spec → getTokenLimit
* Computes the effective context_window for a combo from canonical model
* metadata, which already applies persisted overrides and source precedence.
*
* Only models that are registered in at least one data source (provider registry,
* static specs, or synced capabilities) contribute to the result — matching the
@@ -12,9 +11,6 @@
import { resolveNestedComboTargets } from "@omniroute/open-sse/services/combo";
import { getCanonicalModelMetadata } from "@/lib/modelMetadataRegistry";
import { getSyncedCapability } from "@/lib/modelsDevSync";
import { getModelSpec } from "@/shared/constants/modelSpecs";
import { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models";
import { getTokenLimit } from "@omniroute/open-sse/services/contextManager";
import { buildAliasMaps, getComboTargetModelId } from "@/app/api/v1/models/catalogProviderMaps";
@@ -30,17 +26,6 @@ function minKnownNumber(values: Array<number | undefined>): number | undefined {
return known.length > 0 ? Math.min(...known) : undefined;
}
/** Look up a model in the provider-registry model list. */
function getRegistryModel(
providerId: string,
modelId: string
): { contextLength?: number; id?: string; name?: string } | null {
const alias = PROVIDER_ID_TO_ALIAS[providerId] || providerId;
const providerModels: Array<{ id?: string; contextLength?: number }> =
PROVIDER_MODELS[alias] || PROVIDER_MODELS[providerId] || [];
return providerModels.find((m) => m?.id === modelId) ?? null;
}
/* ─── public API ────────────────────────────────────────────── */
/**
@@ -48,9 +33,9 @@ function getRegistryModel(
*
* Resolution order:
* 1. Explicit `context_length` on the combo record itself.
* 2. Minimum of member-model context windows — each member resolved via
* synced → registry → spec → getTokenLimit, only counting members that
* exist in at least one known data source (matching the catalog behavior).
* 2. Minimum of member-model effective context windows from canonical metadata,
* only counting members that exist in at least one known data source
* (matching the catalog behavior).
*
* Returns `undefined` when no known context window can be determined.
*/
@@ -75,8 +60,8 @@ export function computeComboContextLength(
if (!Array.isArray(targets) || targets.length === 0) return undefined;
// 3. Per-target context resolution — same logic as the catalog's
// `getComboTargetCatalogMetadata`.
// 3. Per-target context resolution from canonical metadata, matching the
// catalog's `getComboTargetCatalogMetadata`.
const contextValues: number[] = [];
const aliasMaps = buildAliasMaps();
@@ -111,20 +96,10 @@ export function computeComboContextLength(
const providerId = canonicalMeta.provider || resolvedTarget.providerId;
const modelId = canonicalMeta.model || resolvedTarget.modelId;
// 3c. Resolve window: synced → registry → spec → getTokenLimit
const synced = getSyncedCapability(providerId, modelId);
const spec = getModelSpec(modelId);
const registryModel = getRegistryModel(providerId, modelId);
const syncedCtx = isPositiveFiniteNumber(synced?.limit_context)
? (synced.limit_context as number)
: undefined;
const registryCtx = isPositiveFiniteNumber(registryModel?.contextLength)
? registryModel.contextLength
: undefined;
const specCtx = isPositiveFiniteNumber(spec?.contextWindow) ? spec.contextWindow : undefined;
const targetCtx = syncedCtx ?? registryCtx ?? specCtx ?? getTokenLimit(providerId, modelId);
const targetCtx =
(isPositiveFiniteNumber(canonicalMeta.limits.contextWindow)
? canonicalMeta.limits.contextWindow
: undefined) ?? getTokenLimit(providerId, modelId);
if (isPositiveFiniteNumber(targetCtx)) {
contextValues.push(targetCtx);

View File

@@ -36,8 +36,8 @@ test("Codex catalog exposes the GPT-5.6 lineup in configured priority order", ()
for (const modelId of expectedIds) {
const model = models.find((entry) => entry.id === modelId);
assert.ok(model, `codex must expose ${modelId}`);
assert.equal(model.contextLength, 1050000);
assert.equal(model.maxInputTokens, 922000);
assert.equal(model.contextLength, 272000);
assert.equal(model.maxInputTokens, 272000);
assert.equal(model.maxOutputTokens, 128000);
assert.equal(model.targetFormat, "openai-responses");
assert.equal(model.toolCalling, true);

View File

@@ -33,6 +33,8 @@ process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const { computeComboContextLength } = await import("../../src/lib/combos/comboContext.ts");
const { setModelContextOverride, removeModelContextOverride } =
await import("../../src/lib/db/modelContextOverrides.ts");
test.after(() => {
core.resetDbInstance();
@@ -72,3 +74,17 @@ test("computeComboContextLength takes the minimum across multiple prefixed, regi
"matching the catalog's minKnownNumber semantics"
);
});
test("computeComboContextLength honors a larger persisted Codex GPT-5.6 window", () => {
const modelId = "gpt-5.6-terra";
assert.equal(setModelContextOverride("codex", modelId, 500000, "manual"), true);
try {
assert.equal(
computeComboContextLength({ models: [`codex/${modelId}`] }, []),
500000,
"the combo aggregate must use the effective override instead of the Codex registry default"
);
} finally {
removeModelContextOverride("codex", modelId);
}
});

View File

@@ -11,6 +11,7 @@ process.env.API_KEY_SECRET ||= "combo-metadata-test-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 contextOverrides = await import("../../src/lib/db/modelContextOverrides.ts");
const catalog = await import("../../src/app/api/v1/models/catalog.ts");
test.after(() => {
@@ -56,6 +57,43 @@ test("single-target combo preserves its direct model metadata", async () => {
}
});
test("single-target Codex combo advertises a larger model context override", async () => {
const modelId = "gpt-5.6-terra";
const contextWindow = 500000;
assert.equal(contextOverrides.setModelContextOverride("codex", modelId, contextWindow), true);
try {
await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
name: "codex-gpt-5.6-context-override-combo",
accessToken: "codex-test-token",
isActive: true,
testStatus: "active",
providerSpecificData: {},
});
await combosDb.createCombo({
name: "gpt-5.6-context-override-combo",
strategy: "auto",
models: [`codex/${modelId}`],
});
const response = await catalog.getUnifiedModelsResponse(
new Request("http://localhost/api/v1/models")
);
const body = (await response.json()) as { data: Array<Record<string, unknown>> };
const direct = body.data.find((item) => item.id === `cx/${modelId}`);
const combo = body.data.find((item) => item.id === "gpt-5.6-context-override-combo");
assert.equal(response.status, 200);
assert.equal(direct?.context_length, contextWindow);
assert.equal(combo?.context_length, contextWindow);
assert.equal(combo?.max_input_tokens, 272000);
} finally {
contextOverrides.removeModelContextOverride("codex", modelId);
}
});
test("single-target combo respects registry reasoning overrides before specs", async () => {
await providersDb.createProviderConnection({
provider: "command-code",

View File

@@ -181,11 +181,10 @@ test("provider models route merges live Codex models with the local catalog then
// merge conservatively — the smaller of live vs. pinned wins, never the
// larger, so a stale/inflated live number can never make OmniRoute promise
// more context than the account can actually serve (#7012). Here the pinned
// GPT-5.6 Codex contract (922000/128000, see GPT_5_6_CODEX_CAPABILITIES
// raised from 272000 in #9432) is smaller than the live payload's
// 999999/999999, so the pinned value wins.
// GPT-5.6 Codex contract (272000/128000, see GPT_5_6_CODEX_CAPABILITIES)
// is smaller than the live payload's 999999/999999, so the pinned value wins.
assert.equal(liveModel?.name, "GPT 5.6 Sol Live");
assert.equal(liveModel?.inputTokenLimit, 922000);
assert.equal(liveModel?.inputTokenLimit, 272000);
assert.equal(liveModel?.outputTokenLimit, 128000);
assert.equal(liveModel?.apiFormat, "responses");
assert.deepEqual(liveModel?.supportedEndpoints, ["responses"]);

View File

@@ -128,9 +128,9 @@ test("vscode raw models route exposes native GPT-5.6 IDs and effort tiers", asyn
assert.equal(typeof defaultModel.created, "number");
assert.equal(defaultModel.owned_by, "codex");
assert.equal(defaultModel.name, "Codex GPT 5.6 Sol");
assert.equal(defaultModel.context_length, 1050000);
assert.equal(defaultModel.context_length, 272000);
assert.equal(defaultModel.max_output_tokens, 128000);
assert.equal(defaultModel.max_input_tokens, 922000);
assert.equal(defaultModel.max_input_tokens, 272000);
assert.deepEqual(defaultModel.capabilities, {
vision: true,
tool_calling: true,

View File

@@ -255,7 +255,7 @@ test("vscode combos route resolves combo names through Ollama api/show", async (
assert.equal(body.model, "show-combo");
assert.equal(body.modelfile, "FROM show-combo");
assert.equal(body.details.family, "show-combo");
assert.equal(body.model_info.context_length, 1050000);
assert.equal(body.model_info.context_length, 272000);
assert.deepEqual(body.supportsReasoningEffort, ["none", "low", "medium", "high", "xhigh"]);
assert.equal(body.model_info.capabilities.reasoning, true);
});
@@ -290,7 +290,7 @@ test("vscode tokenized combos root route exposes importable combo metadata", asy
assert.equal(response.status, 200);
assert.ok(combo, "expected balanced-load in combo root response");
assert.equal(combo.url.includes("/responses#models.ai.azure.com"), true);
assert.equal(combo.maxInputTokens, 922000);
assert.equal(combo.maxInputTokens, 272000);
assert.equal(combo.toolCalling, true);
assert.deepEqual(combo.supportsReasoningEffort, ["none", "low", "medium", "high", "xhigh"]);
});
@@ -1073,7 +1073,7 @@ test("vscode tokenized api/show route exposes explicit reasoning effort metadata
assert.equal(body.configurationSchema?.properties?.reasoningEffort?.default, "low");
assert.equal(body.model_info["general.basename"], "Codex GPT 5.6 Sol (Default)");
assert.equal(body.model_info["general.architecture"], "codex");
assert.equal(body.model_info["codex.context_length"], 1050000);
assert.equal(body.model_info["codex.context_length"], 272000);
assert.deepEqual(body.model_info.supports_reasoning_effort, [
"low",
"medium",