fix(codex): remove ghost models, add gpt-daybreak/reserve, route gpt-5.x/6.x bare slugs to codex (#4418)

This commit is contained in:
semihisikman committed 2026-09-28 12:43:12 +07:00
1 parent 60e890d7f1
commit 8f9ff44f27
3 files changed
+153 -8

No files matched your search

+6 -7
View File
@@ -63,12 +63,11 @@ export default {
{ id: "gpt-5.6-luna-review", name: "GPT 5.6 Luna Review", upstreamModelId: "gpt-5.6-luna", quotaFamily: "review" },
{ id: "gpt-5.5", name: "GPT 5.5" },
{ id: "gpt-5.5-review", name: "GPT 5.5 Review", upstreamModelId: "gpt-5.5", quotaFamily: "review" },
{ id: "gpt-5.4", name: "GPT 5.4" },
{ id: "gpt-5.4-review", name: "GPT 5.4 Review", upstreamModelId: "gpt-5.4", quotaFamily: "review" },
{ id: "gpt-5.4-mini", name: "GPT 5.4 Mini" },
{ id: "gpt-5.4-mini-review", name: "GPT 5.4 Mini Review", upstreamModelId: "gpt-5.4-mini", quotaFamily: "review" },
{ id: "gpt-5.3-codex-spark", name: "GPT 5.3 Codex Spark" },
{ id: "gpt-5.3-codex-spark-review", name: "GPT 5.3 Codex Spark Review", upstreamModelId: "gpt-5.3-codex-spark", quotaFamily: "review" },
// gpt-5.4 / gpt-5.4-mini / gpt-5.3-codex-spark removed: absent from backend-api/codex/models
// for ChatGPT Plus/Pro accounts and return HTTP 400 "model is not supported" (#4202).
// gpt-daybreak-blue-latest and gpt-reserve added: confirmed live via backend-api/codex/models (#4202).
{ id: "gpt-daybreak-blue-latest", name: "GPT Daybreak Blue" },
{ id: "gpt-reserve", name: "GPT Reserve" },
// Codex CLI's auto-review virtual model. Unlike the "-review" variants above it is not derived
// from a base model, so it is forwarded verbatim instead of having "-review" stripped (#1398).
{ id: "codex-auto-review", name: "Codex Auto Review", upstreamModelId: "codex-auto-review", quotaFamily: "review" },
@@ -81,7 +80,7 @@ export default {
{ id: "gpt-5.6-terra-image", name: "GPT 5.6 Terra Image", capabilities: ["text2img","edit"], params: ["size","quality","background","image_detail","output_format"], kind: "image" },
{ id: "gpt-5.6-luna-image", name: "GPT 5.6 Luna Image", capabilities: ["text2img","edit"], params: ["size","quality","background","image_detail","output_format"], kind: "image" },
{ id: "gpt-5.5-image", name: "GPT 5.5 Image", capabilities: ["text2img","edit"], params: ["size","quality","background","image_detail","output_format"], kind: "image" },
{ id: "gpt-5.4-image", name: "GPT 5.4 Image", capabilities: ["text2img","edit"], params: ["size","quality","background","image_detail","output_format"], kind: "image" },
// gpt-5.4-image removed alongside gpt-5.4 (both are dead on the backend) (#4202).
{ id: "gpt-5.3-image", name: "GPT 5.3 Image", capabilities: ["text2img","edit"], params: ["size","quality","background","image_detail","output_format"], kind: "image" },
],
serviceKinds: ["llm","image"],
+12 -1
View File
@@ -124,8 +124,19 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) {
// Config-driven prefix → provider inference (first match wins, fallback "openai").
const MODEL_PREFIX_PROVIDERS = [
// Codex CLI sends this bare virtual model for auto-review — keep it on OAuth Codex (#1398).
// Codex CLI sends this bare virtual model for auto-review - keep it on OAuth Codex (#1398).
[/^codex-auto-review$/, "codex"],
// Codex-only GPT model slugs: present in backend-api/codex/models but not on the
// OpenAI API. Without these rules a bare model id (e.g. "gpt-5.6-terra" from the
// Codex CLI /model picker) resolves to provider "openai", which 404s for users that
// only have a Codex OAuth account and no OpenAI API key (#4405).
// Ranges covered: gpt-5.x, gpt-6.x, gpt-daybreak-*, gpt-reserve* — all are
// Codex-backend models. Plain "gpt-4*" / "gpt-3.5*" / "gpt-4o*" fall through to
// the generic gpt-* → openai rule below.
[/^gpt-[56]\./, "codex"],
[/^gpt-6-/, "codex"],
[/^gpt-daybreak-/, "codex"],
[/^gpt-reserve/, "codex"],
[/^claude-/, "anthropic"],
[/^gemini-/, "gemini"],
[/^gpt-/, "openai"],
@@ -0,0 +1,135 @@
/**
* Tests for #4202 and #4405
*
* #4202 — Codex registry has ghost models (always HTTP 400) and is missing
* gpt-daybreak-blue-latest / gpt-reserve.
* Fix: remove gpt-5.4/mini/spark entries + gpt-5.4-image; add gpt-daybreak-blue-latest and gpt-reserve.
*
* #4405 — Bare Codex model slugs (e.g. gpt-5.6-terra from the CLI /model picker)
* routed to provider "openai" instead of "codex", causing 404 for users without
* an OpenAI API key connection.
* Fix: add codex-specific gpt-5.x / gpt-6.x / gpt-daybreak-* / gpt-reserve* rules
* to MODEL_PREFIX_PROVIDERS before the generic gpt-* → openai rule.
*/
import { describe, it, expect } from "vitest";
import fs from "fs";
import path from "path";
// ── #4202 Registry checks ────────────────────────────────────────────────────
const codexSrc = fs.readFileSync(
path.resolve("../open-sse/providers/registry/codex.js"),
"utf-8"
);
describe("Codex registry — ghost models removed (#4202)", () => {
it("gpt-5.4 is removed", () => {
// Match only a standalone entry, not inside gpt-5.4-mini or gpt-5.45 etc.
expect(codexSrc).not.toMatch(/id:\s*"gpt-5\.4"/);
});
it("gpt-5.4-mini is removed", () => {
expect(codexSrc).not.toMatch(/id:\s*"gpt-5\.4-mini"/);
});
it("gpt-5.3-codex-spark is removed", () => {
expect(codexSrc).not.toMatch(/id:\s*"gpt-5\.3-codex-spark"/);
});
it("gpt-5.4-image is removed", () => {
expect(codexSrc).not.toMatch(/id:\s*"gpt-5\.4-image"/);
});
});
describe("Codex registry — new models added (#4202)", () => {
it("gpt-daybreak-blue-latest is present", () => {
expect(codexSrc).toContain('"gpt-daybreak-blue-latest"');
});
it("gpt-reserve is present", () => {
expect(codexSrc).toContain('"gpt-reserve"');
});
});
describe("Codex registry — still-live models kept (#4202 regression guard)", () => {
it("gpt-5.5 still present", () => {
expect(codexSrc).toContain('"gpt-5.5"');
});
it("gpt-5.6-terra still present", () => {
expect(codexSrc).toContain('"gpt-5.6-terra"');
});
it("gpt-6-astra still present", () => {
expect(codexSrc).toContain('"gpt-6-astra"');
});
});
// ── #4405 Model prefix routing ───────────────────────────────────────────────
// Replicate MODEL_PREFIX_PROVIDERS logic from open-sse/services/model.js
const MODEL_PREFIX_PROVIDERS = [
[/^codex-auto-review$/, "codex"],
[/^gpt-[56]\./, "codex"],
[/^gpt-6-/, "codex"],
[/^gpt-daybreak-/, "codex"],
[/^gpt-reserve/, "codex"],
[/^claude-/, "anthropic"],
[/^gemini-/, "gemini"],
[/^gpt-/, "openai"],
[/^o[134]/, "openai"],
[/^deepseek-/, "openrouter"],
];
function inferProvider(modelName) {
if (!modelName) return "openai";
const m = modelName.toLowerCase();
return MODEL_PREFIX_PROVIDERS.find(([re]) => re.test(m))?.[1] || "openai";
}
describe("inferProviderFromModelName — Codex gpt-* models route to codex (#4405)", () => {
it("gpt-5.6-terra → codex", () => {
expect(inferProvider("gpt-5.6-terra")).toBe("codex");
});
it("gpt-5.6-sol → codex", () => {
expect(inferProvider("gpt-5.6-sol")).toBe("codex");
});
it("gpt-5.5 → codex", () => {
expect(inferProvider("gpt-5.5")).toBe("codex");
});
it("gpt-6-astra → codex", () => {
expect(inferProvider("gpt-6-astra")).toBe("codex");
});
it("gpt-daybreak-blue-latest → codex", () => {
expect(inferProvider("gpt-daybreak-blue-latest")).toBe("codex");
});
it("gpt-reserve → codex", () => {
expect(inferProvider("gpt-reserve")).toBe("codex");
});
it("gpt-4o → openai (standard model not affected)", () => {
expect(inferProvider("gpt-4o")).toBe("openai");
});
it("gpt-4-turbo → openai (standard model not affected)", () => {
expect(inferProvider("gpt-4-turbo")).toBe("openai");
});
it("gpt-3.5-turbo → openai (standard model not affected)", () => {
expect(inferProvider("gpt-3.5-turbo")).toBe("openai");
});
it("claude-opus-5 → anthropic (regression guard)", () => {
expect(inferProvider("claude-opus-5")).toBe("anthropic");
});
it("codex-auto-review → codex (regression guard)", () => {
expect(inferProvider("codex-auto-review")).toBe("codex");
});
});