feat(openai): add codex gpt-5.4-mini support

This commit is contained in:
Peter Steinberger
2026-04-04 11:51:57 +09:00
parent 7df763b04d
commit 6b3ff0dd4f
12 changed files with 181 additions and 8 deletions

View File

@@ -247,6 +247,12 @@ describe("loadModelCatalog", () => {
id: "gpt-5.4",
}),
);
expect(result).toContainEqual(
expect.objectContaining({
provider: "openai-codex",
id: "gpt-5.4-mini",
}),
);
});
it("merges configured models for opted-in non-pi-native providers", async () => {

View File

@@ -363,7 +363,7 @@ describe("isModernModelRef", () => {
provider === "openai" &&
["gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini", "gpt-5.4-nano"].includes(context.modelId)
? true
: provider === "openai-codex" && context.modelId === "gpt-5.4"
: provider === "openai-codex" && ["gpt-5.4", "gpt-5.4-mini"].includes(context.modelId)
? true
: provider === "opencode" && ["claude-opus-4-6", "gemini-3-pro"].includes(context.modelId)
? true
@@ -377,6 +377,7 @@ describe("isModernModelRef", () => {
expect(isModernModelRef({ provider: "openai", id: "gpt-5.4-mini" })).toBe(true);
expect(isModernModelRef({ provider: "openai", id: "gpt-5.4-nano" })).toBe(true);
expect(isModernModelRef({ provider: "openai-codex", id: "gpt-5.4" })).toBe(true);
expect(isModernModelRef({ provider: "openai-codex", id: "gpt-5.4-mini" })).toBe(true);
expect(isModernModelRef({ provider: "opencode", id: "claude-opus-4-6" })).toBe(true);
expect(isModernModelRef({ provider: "opencode", id: "gemini-3-pro" })).toBe(true);
expect(isModernModelRef({ provider: "opencode-go", id: "kimi-k2.5" })).toBe(true);

View File

@@ -183,9 +183,16 @@ function buildDynamicModel(
const template =
lower === "gpt-5.4"
? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.2-codex"])
: lower === "gpt-5.3-codex-spark"
? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.2-codex"])
: findTemplate(params, "openai-codex", ["gpt-5.2-codex"]);
: lower === "gpt-5.4-mini"
? findTemplate(params, "openai-codex", [
"gpt-5.4",
"gpt-5.1-codex-mini",
"gpt-5.3-codex",
"gpt-5.2-codex",
])
: lower === "gpt-5.3-codex-spark"
? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.2-codex"])
: findTemplate(params, "openai-codex", ["gpt-5.2-codex"]);
const fallback = {
provider: "openai-codex",
api: "openai-codex-responses",
@@ -212,6 +219,21 @@ function buildDynamicModel(
fallback,
);
}
if (lower === "gpt-5.4-mini") {
return cloneTemplate(
template,
modelId,
{
provider: "openai-codex",
api: "openai-codex-responses",
baseUrl: OPENAI_CODEX_BASE_URL,
cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
contextWindow: 272_000,
maxTokens: 128_000,
},
fallback,
);
}
if (lower === "gpt-5.3-codex-spark") {
return cloneTemplate(
template,

View File

@@ -57,6 +57,7 @@ export function buildOpenAICodexForwardCompatExpectation(
baseUrl: string;
} {
const isGpt54 = id === "gpt-5.4";
const isGpt54Mini = id === "gpt-5.4-mini";
const isSpark = id === "gpt-5.3-codex-spark";
return {
provider: "openai-codex",
@@ -69,7 +70,9 @@ export function buildOpenAICodexForwardCompatExpectation(
? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
: isGpt54
? { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }
: OPENAI_CODEX_TEMPLATE_MODEL.cost,
: isGpt54Mini
? { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }
: OPENAI_CODEX_TEMPLATE_MODEL.cost,
contextWindow: isGpt54 ? 1_050_000 : isSpark ? 128_000 : 272000,
...(isGpt54 ? { contextTokens: 272_000 } : {}),
maxTokens: 128000,

View File

@@ -847,13 +847,13 @@ describe("resolveModel", () => {
expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4"));
});
it("builds an openai-codex fallback for gpt-5.4", () => {
it("builds an openai-codex fallback for gpt-5.4-mini", () => {
mockOpenAICodexTemplateModel(discoverModels);
const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent");
const result = resolveModelForTest("openai-codex", "gpt-5.4-mini", "/tmp/agent");
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4"));
expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4-mini"));
});
it("builds an openai-codex fallback for gpt-5.3-codex-spark", () => {

View File

@@ -12,6 +12,13 @@ const OPENAI_CODEX_MODEL = {
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
};
const OPENAI_CODEX_MINI_MODEL = {
...OPENAI_CODEX_MODEL,
id: "gpt-5.4-mini",
name: "GPT-5.4 Mini",
contextWindow: 272_000,
};
const OPENAI_CODEX_53_MODEL = {
...OPENAI_CODEX_MODEL,
id: "gpt-5.4",
@@ -170,6 +177,35 @@ describe("modelsListCommand forward-compat", () => {
expect(codex?.tags).not.toContain("missing");
});
it("does not mark configured codex mini as missing when forward-compat can build a fallback", async () => {
mocks.resolveConfiguredEntries.mockReturnValueOnce({
entries: [
{
key: "openai-codex/gpt-5.4-mini",
ref: { provider: "openai-codex", model: "gpt-5.4-mini" },
tags: new Set(["configured"]),
aliases: [],
},
],
});
mocks.resolveModelWithRegistry.mockReturnValueOnce({ ...OPENAI_CODEX_MINI_MODEL });
const runtime = createRuntime();
await modelsListCommand({ json: true }, runtime as never);
expect(mocks.printModelTable).toHaveBeenCalled();
const rows = lastPrintedRows<{
key: string;
tags: string[];
missing: boolean;
}>();
const codexMini = rows.find((row) => row.key === "openai-codex/gpt-5.4-mini");
expect(codexMini).toBeTruthy();
expect(codexMini?.missing).toBe(false);
expect(codexMini?.tags).not.toContain("missing");
});
it("passes source config to model registry loading for persistence safety", async () => {
const runtime = createRuntime();

View File

@@ -14,6 +14,7 @@ export const expectedAugmentedOpenaiCodexCatalogEntries = [
{ provider: "openai", id: "gpt-5.4-mini", name: "gpt-5.4-mini" },
{ provider: "openai", id: "gpt-5.4-nano", name: "gpt-5.4-nano" },
{ provider: "openai-codex", id: "gpt-5.4", name: "gpt-5.4" },
{ provider: "openai-codex", id: "gpt-5.4-mini", name: "gpt-5.4-mini" },
{
provider: "openai-codex",
id: "gpt-5.3-codex-spark",

View File

@@ -127,6 +127,7 @@ function createOpenAiCatalogProviderPlugin(
{ provider: "openai", id: "gpt-5.4-mini", name: "gpt-5.4-mini" },
{ provider: "openai", id: "gpt-5.4-nano", name: "gpt-5.4-nano" },
{ provider: "openai-codex", id: "gpt-5.4", name: "gpt-5.4" },
{ provider: "openai-codex", id: "gpt-5.4-mini", name: "gpt-5.4-mini" },
{
provider: "openai-codex",
id: "gpt-5.3-codex-spark",