mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Fix bare GPT-5.5 routing for Codex-only installations (#2054)
Integrated into release/v3.8.0
This commit is contained in:
@@ -407,6 +407,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
tokenUrl: "https://auth.openai.com/oauth/token",
|
||||
},
|
||||
models: [
|
||||
{ id: "gpt-5.5", name: "GPT 5.5", ...GPT_5_5_CODEX_CAPABILITIES },
|
||||
{ id: "gpt-5.5-xhigh", name: "GPT 5.5 (xHigh)", ...GPT_5_5_CODEX_CAPABILITIES },
|
||||
{ id: "gpt-5.5-high", name: "GPT 5.5 (High)", ...GPT_5_5_CODEX_CAPABILITIES },
|
||||
{ id: "gpt-5.5-medium", name: "GPT 5.5 (Medium)", ...GPT_5_5_CODEX_CAPABILITIES },
|
||||
|
||||
@@ -74,8 +74,15 @@ for (const [aliasOrId, models] of Object.entries(PROVIDER_MODELS)) {
|
||||
}
|
||||
const KNOWN_MODEL_IDS = new Set(MODEL_TO_PROVIDERS.keys());
|
||||
const CODEX_PREFERRED_UNPREFIXED_MODELS = new Set(["gpt-5.5"]);
|
||||
const CODEX_PREFERRED_UNPREFIXED_MODEL_ALIASES = new Map([["gpt-5.5", "gpt-5.5-medium"]]);
|
||||
export const CODEX_NATIVE_UNPREFIXED_MODELS = new Set(["codex-auto-review"]);
|
||||
|
||||
interface ProviderConnectionLike {
|
||||
provider?: unknown;
|
||||
isActive?: unknown;
|
||||
is_active?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve provider alias to provider ID
|
||||
*/
|
||||
@@ -127,6 +134,68 @@ function hasKnownProviderModel(providerOrAlias, modelId) {
|
||||
return canonicalModel !== modelId && models.some((entry) => entry?.id === canonicalModel);
|
||||
}
|
||||
|
||||
function hasCodexPreferredUnprefixedModel(modelId) {
|
||||
const canonicalModel = CODEX_PREFERRED_UNPREFIXED_MODEL_ALIASES.get(modelId);
|
||||
if (!canonicalModel) return false;
|
||||
|
||||
const providerAlias = PROVIDER_ID_TO_ALIAS.codex || "codex";
|
||||
const models = PROVIDER_MODELS[providerAlias] || PROVIDER_MODELS.codex || [];
|
||||
return models.some((entry) => entry?.id === canonicalModel);
|
||||
}
|
||||
|
||||
function resolveInferredProviderModel(provider, modelId) {
|
||||
const codexPreferredModel = CODEX_PREFERRED_UNPREFIXED_MODEL_ALIASES.get(modelId);
|
||||
if (provider === "codex" && codexPreferredModel) {
|
||||
return codexPreferredModel;
|
||||
}
|
||||
return resolveProviderModelAlias(provider, modelId);
|
||||
}
|
||||
|
||||
function getInferredProvidersForModel(modelId) {
|
||||
const providers = [...(MODEL_TO_PROVIDERS.get(modelId) || [])];
|
||||
|
||||
if (
|
||||
CODEX_PREFERRED_UNPREFIXED_MODELS.has(modelId) &&
|
||||
hasCodexPreferredUnprefixedModel(modelId) &&
|
||||
!providers.includes("codex")
|
||||
) {
|
||||
providers.push("codex");
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
||||
function isProviderConnectionActive(connection: ProviderConnectionLike) {
|
||||
if (connection.isActive !== undefined) {
|
||||
return connection.isActive !== false && connection.isActive !== 0;
|
||||
}
|
||||
if (connection.is_active !== undefined) {
|
||||
return connection.is_active !== false && connection.is_active !== 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getProviderIdFromConnection(connection: unknown) {
|
||||
if (!connection || typeof connection !== "object") return null;
|
||||
const record = connection as ProviderConnectionLike;
|
||||
if (typeof record.provider !== "string" || !record.provider) return null;
|
||||
if (!isProviderConnectionActive(record)) return null;
|
||||
return resolveProviderAlias(record.provider);
|
||||
}
|
||||
|
||||
async function getActiveProviderSet() {
|
||||
try {
|
||||
const { getProviderConnections } = await import("@/lib/localDb");
|
||||
const conns = (await getProviderConnections()) as unknown[];
|
||||
const providers = conns
|
||||
.map(getProviderIdFromConnection)
|
||||
.filter((provider): provider is string => Boolean(provider));
|
||||
return new Set(providers);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldTreatAsExactModelId(modelStr) {
|
||||
if (!modelStr || typeof modelStr !== "string" || !modelStr.includes("/")) return false;
|
||||
if (!KNOWN_MODEL_IDS.has(modelStr)) return false;
|
||||
@@ -276,7 +345,7 @@ function parseAliasTarget(target) {
|
||||
}
|
||||
|
||||
async function resolveModelByProviderInference(modelId, extendedContext) {
|
||||
const providers = MODEL_TO_PROVIDERS.get(modelId) || [];
|
||||
const providers = getInferredProvidersForModel(modelId);
|
||||
|
||||
const nonOpenAIProviders = providers.filter((p) => p !== "openai");
|
||||
|
||||
@@ -288,10 +357,24 @@ async function resolveModelByProviderInference(modelId, extendedContext) {
|
||||
};
|
||||
}
|
||||
|
||||
if (providers.includes("codex") && CODEX_PREFERRED_UNPREFIXED_MODELS.has(modelId)) {
|
||||
const activeProviders = await getActiveProviderSet();
|
||||
|
||||
const activeCandidates = activeProviders ? providers.filter((p) => activeProviders.has(p)) : [];
|
||||
|
||||
if (activeCandidates.length === 1) {
|
||||
const provider = activeCandidates[0];
|
||||
const canonicalModel = resolveInferredProviderModel(provider, modelId);
|
||||
return { provider, model: canonicalModel, extendedContext };
|
||||
}
|
||||
|
||||
if (
|
||||
activeProviders?.has("codex") &&
|
||||
providers.includes("codex") &&
|
||||
CODEX_PREFERRED_UNPREFIXED_MODELS.has(modelId)
|
||||
) {
|
||||
return {
|
||||
provider: "codex",
|
||||
model: modelId,
|
||||
model: resolveInferredProviderModel("codex", modelId),
|
||||
extendedContext,
|
||||
};
|
||||
}
|
||||
@@ -305,24 +388,15 @@ async function resolveModelByProviderInference(modelId, extendedContext) {
|
||||
};
|
||||
}
|
||||
|
||||
let activeProviders: Set<string> | null = null;
|
||||
try {
|
||||
const { getProviderConnections } = await import("@/lib/localDb");
|
||||
const conns = await getProviderConnections();
|
||||
activeProviders = new Set(conns.filter((c: any) => c.is_active).map((c: any) => c.provider));
|
||||
} catch {
|
||||
// DB unavailable
|
||||
}
|
||||
|
||||
const eligibleProviders = activeProviders
|
||||
? nonOpenAIProviders.filter((p) => activeProviders!.has(p))
|
||||
? nonOpenAIProviders.filter((p) => activeProviders.has(p))
|
||||
: nonOpenAIProviders;
|
||||
|
||||
const candidatesToUse = eligibleProviders.length > 0 ? eligibleProviders : nonOpenAIProviders;
|
||||
|
||||
if (candidatesToUse.length === 1) {
|
||||
const provider = candidatesToUse[0];
|
||||
const canonicalModel = resolveProviderModelAlias(provider, modelId);
|
||||
const canonicalModel = resolveInferredProviderModel(provider, modelId);
|
||||
return { provider, model: canonicalModel, extendedContext };
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,35 @@ test("resolveModelOrError keeps non-Codex gpt-5.5 Responses requests on OpenAI",
|
||||
assert.equal(result.model, "gpt-5.5");
|
||||
});
|
||||
|
||||
test("resolveModelOrError routes bare gpt-5.5 to Codex medium when Codex is the only active account", async () => {
|
||||
await seedConnection("codex");
|
||||
|
||||
const result = await resolveModelOrError(
|
||||
"gpt-5.5",
|
||||
{ model: "gpt-5.5", input: "hello" },
|
||||
"/v1/responses",
|
||||
{ "user-agent": "OpenAI/Node" }
|
||||
);
|
||||
|
||||
assert.equal(result.provider, "codex");
|
||||
assert.equal(result.model, "gpt-5.5-medium");
|
||||
assert.equal(result.targetFormat, "openai-responses");
|
||||
});
|
||||
|
||||
test("resolveModelOrError keeps bare gpt-5.5 on OpenAI when OpenAI is the only active account", async () => {
|
||||
await seedConnection("openai");
|
||||
|
||||
const result = await resolveModelOrError(
|
||||
"gpt-5.5",
|
||||
{ model: "gpt-5.5", input: "hello" },
|
||||
"/v1/responses",
|
||||
{ "user-agent": "OpenAI/Node" }
|
||||
);
|
||||
|
||||
assert.equal(result.provider, "openai");
|
||||
assert.equal(result.model, "gpt-5.5");
|
||||
});
|
||||
|
||||
test("checkPipelineGates blocks providers with an open circuit breaker", async () => {
|
||||
const breaker = getCircuitBreaker("openai");
|
||||
breaker.state = STATE.OPEN;
|
||||
|
||||
Reference in New Issue
Block a user