Skip to content

Commit 3766bbb

Browse files
committed
fix(models): restore codex mini oauth route
1 parent 0f120c0 commit 3766bbb

11 files changed

Lines changed: 132 additions & 49 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
88

99
- Agents/subagents: bound automatic orphan recovery with persisted recovery attempts and a wedged-session tombstone, and teach task maintenance/doctor to reconcile those sessions so restart loops no longer require manual `sessions.json` surgery. Fixes #74864. Thanks @solosage1.
1010
- CLI/progress: suppress nested progress spinners and line clears while TUI input owns raw stdin, so Crestodian `/status` no longer disturbs the active input row. (#75003) Thanks @velvet-shark.
11+
- Models/OpenAI Codex: restore `openai-codex/gpt-5.4-mini` for ChatGPT/Codex OAuth PI runs after live OAuth proof, and align the manifest, forward-compat metadata, docs, and regression tests so stale cron and heartbeat configs resolve again. Fixes #74451. Thanks @0xCyda, @hclsys, and @Marvae.
1112
- Telegram: use durable message edits for streaming previews instead of native draft state, so generated replies no longer flicker through draft-to-message transitions that look like duplicates. (#75073) Thanks @obviyus.
1213

1314
## 2026.4.29

docs/providers/openai.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ Choose your preferred auth method and follow the setup steps.
208208
| Model ref | Runtime config | Route | Auth |
209209
|-----------|----------------|-------|------|
210210
| `openai-codex/gpt-5.5` | omitted / `runtime: "pi"` | ChatGPT/Codex OAuth through PI | Codex sign-in |
211+
| `openai-codex/gpt-5.4-mini` | omitted / `runtime: "pi"` | ChatGPT/Codex OAuth through PI | Codex sign-in |
211212
| `openai-codex/gpt-5.5` | `runtime: "auto"` | Still PI unless a plugin explicitly claims `openai-codex` | Codex sign-in |
212213
| `openai/gpt-5.5` | `agentRuntime.id: "codex"` | Codex app-server harness | Codex app-server auth |
213214

@@ -217,12 +218,6 @@ Choose your preferred auth method and follow the setup steps.
217218
It does not select or auto-enable the bundled Codex app-server harness.
218219
</Note>
219220

220-
<Warning>
221-
`openai-codex/gpt-5.4-mini` is not a supported Codex OAuth route. Use
222-
`openai/gpt-5.4-mini` with an OpenAI API key, or use
223-
`openai-codex/gpt-5.5` with Codex OAuth.
224-
</Warning>
225-
226221
### Config example
227222

228223
```json5

extensions/openai/openai-codex-provider.test.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -439,22 +439,33 @@ describe("openai codex provider", () => {
439439
});
440440
});
441441

442-
it("does not resolve gpt-5.4-mini through the Codex OAuth route", () => {
442+
it("resolves gpt-5.4-mini through the Codex OAuth route", () => {
443443
const provider = buildOpenAICodexProviderPlugin();
444444

445445
const model = provider.resolveDynamicModel?.({
446446
provider: "openai-codex",
447447
modelId: "gpt-5.4-mini",
448448
modelRegistry: createSingleModelRegistry(
449449
createCodexTemplate({
450-
id: "gpt-5.1-codex-mini",
451-
cost: { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0 },
450+
id: "gpt-5.4",
451+
cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
452+
contextWindow: 1_050_000,
453+
contextTokens: 272_000,
452454
}),
453455
null,
454456
) as never,
455457
} as never);
456458

457-
expect(model).toBeUndefined();
459+
expect(model).toMatchObject({
460+
id: "gpt-5.4-mini",
461+
name: "gpt-5.4-mini",
462+
api: "openai-codex-responses",
463+
baseUrl: "https://chatgpt.com/backend-api",
464+
contextWindow: 400_000,
465+
contextTokens: 272_000,
466+
maxTokens: 128_000,
467+
cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
468+
});
458469
});
459470

460471
it("augments catalog with gpt-5.5-pro and gpt-5.4 native metadata", () => {
@@ -503,9 +514,12 @@ describe("openai codex provider", () => {
503514
cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 },
504515
}),
505516
);
506-
expect(entries).not.toContainEqual(
517+
expect(entries).toContainEqual(
507518
expect.objectContaining({
508519
id: "gpt-5.4-mini",
520+
contextWindow: 400_000,
521+
contextTokens: 272_000,
522+
cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
509523
}),
510524
);
511525
});

extensions/openai/openai-codex-provider.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ const OPENAI_CODEX_GPT_55_MODEL_ID = "gpt-5.5";
5252
const OPENAI_CODEX_GPT_55_PRO_MODEL_ID = "gpt-5.5-pro";
5353
const OPENAI_CODEX_GPT_54_MODEL_ID = "gpt-5.4";
5454
const OPENAI_CODEX_GPT_54_LEGACY_MODEL_ID = "gpt-5.4-codex";
55+
const OPENAI_CODEX_GPT_54_MINI_MODEL_ID = "gpt-5.4-mini";
5556
const OPENAI_CODEX_GPT_54_PRO_MODEL_ID = "gpt-5.4-pro";
5657
const OPENAI_CODEX_GPT_55_CODEX_CONTEXT_TOKENS = 400_000;
5758
const OPENAI_CODEX_GPT_55_DEFAULT_RUNTIME_CONTEXT_TOKENS = 272_000;
5859
const OPENAI_CODEX_GPT_55_PRO_NATIVE_CONTEXT_TOKENS = 1_000_000;
5960
const OPENAI_CODEX_GPT_55_PRO_DEFAULT_CONTEXT_TOKENS = 272_000;
6061
const OPENAI_CODEX_GPT_54_NATIVE_CONTEXT_TOKENS = 1_050_000;
6162
const OPENAI_CODEX_GPT_54_DEFAULT_CONTEXT_TOKENS = 272_000;
63+
const OPENAI_CODEX_GPT_54_MINI_NATIVE_CONTEXT_TOKENS = 400_000;
6264
const OPENAI_CODEX_GPT_54_MAX_TOKENS = 128_000;
6365
const OPENAI_CODEX_GPT_55_PRO_COST = {
6466
input: 30,
@@ -78,6 +80,12 @@ const OPENAI_CODEX_GPT_54_PRO_COST = {
7880
cacheRead: 0,
7981
cacheWrite: 0,
8082
} as const;
83+
const OPENAI_CODEX_GPT_54_MINI_COST = {
84+
input: 0.75,
85+
output: 4.5,
86+
cacheRead: 0.075,
87+
cacheWrite: 0,
88+
} as const;
8189
const OPENAI_CODEX_GPT_54_TEMPLATE_MODEL_IDS = ["gpt-5.3-codex", "gpt-5.2-codex"] as const;
8290
/** Legacy codex rows first; fall back to catalog `gpt-5.4` when the API omits 5.3/5.2. */
8391
const OPENAI_CODEX_GPT_54_CATALOG_SYNTH_TEMPLATE_MODEL_IDS = [
@@ -105,6 +113,7 @@ const OPENAI_CODEX_MODERN_MODEL_IDS = [
105113
OPENAI_CODEX_GPT_55_PRO_MODEL_ID,
106114
OPENAI_CODEX_GPT_54_MODEL_ID,
107115
OPENAI_CODEX_GPT_54_PRO_MODEL_ID,
116+
OPENAI_CODEX_GPT_54_MINI_MODEL_ID,
108117
"gpt-5.2",
109118
"gpt-5.2-codex",
110119
OPENAI_CODEX_GPT_53_MODEL_ID,
@@ -227,6 +236,14 @@ function resolveCodexForwardCompatModel(ctx: ProviderResolveDynamicModelContext)
227236
maxTokens: OPENAI_CODEX_GPT_54_MAX_TOKENS,
228237
cost: OPENAI_CODEX_GPT_54_PRO_COST,
229238
};
239+
} else if (lower === OPENAI_CODEX_GPT_54_MINI_MODEL_ID) {
240+
templateIds = OPENAI_CODEX_GPT_54_CATALOG_SYNTH_TEMPLATE_MODEL_IDS;
241+
patch = {
242+
contextWindow: OPENAI_CODEX_GPT_54_MINI_NATIVE_CONTEXT_TOKENS,
243+
contextTokens: OPENAI_CODEX_GPT_54_DEFAULT_CONTEXT_TOKENS,
244+
maxTokens: OPENAI_CODEX_GPT_54_MAX_TOKENS,
245+
cost: OPENAI_CODEX_GPT_54_MINI_COST,
246+
};
230247
} else if (lower === OPENAI_CODEX_GPT_53_MODEL_ID) {
231248
templateIds = OPENAI_CODEX_TEMPLATE_MODEL_IDS;
232249
} else {
@@ -495,6 +512,7 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
495512
OPENAI_CODEX_GPT_55_PRO_MODEL_ID,
496513
OPENAI_CODEX_GPT_54_MODEL_ID,
497514
OPENAI_CODEX_GPT_54_PRO_MODEL_ID,
515+
OPENAI_CODEX_GPT_54_MINI_MODEL_ID,
498516
].includes(id);
499517
},
500518
...buildOpenAIResponsesProviderHooks(),
@@ -555,6 +573,14 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
555573
contextTokens: OPENAI_CODEX_GPT_54_DEFAULT_CONTEXT_TOKENS,
556574
cost: OPENAI_CODEX_GPT_54_PRO_COST,
557575
}),
576+
buildOpenAISyntheticCatalogEntry(gpt54Template, {
577+
id: OPENAI_CODEX_GPT_54_MINI_MODEL_ID,
578+
reasoning: true,
579+
input: ["text", "image"],
580+
contextWindow: OPENAI_CODEX_GPT_54_MINI_NATIVE_CONTEXT_TOKENS,
581+
contextTokens: OPENAI_CODEX_GPT_54_DEFAULT_CONTEXT_TOKENS,
582+
cost: OPENAI_CODEX_GPT_54_MINI_COST,
583+
}),
558584
].filter((entry): entry is NonNullable<typeof entry> => entry !== undefined);
559585
},
560586
};

extensions/openai/openclaw.plugin.json

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,21 @@
645645
"cacheWrite": 0
646646
}
647647
},
648+
{
649+
"id": "gpt-5.4-mini",
650+
"name": "gpt-5.4-mini",
651+
"reasoning": true,
652+
"input": ["text", "image"],
653+
"contextWindow": 400000,
654+
"contextTokens": 272000,
655+
"maxTokens": 128000,
656+
"cost": {
657+
"input": 0.75,
658+
"output": 4.5,
659+
"cacheRead": 0.075,
660+
"cacheWrite": 0
661+
}
662+
},
648663
{
649664
"id": "gpt-5.5-pro",
650665
"name": "gpt-5.5-pro",
@@ -688,11 +703,6 @@
688703
"provider": "openai-codex",
689704
"model": "gpt-5.3-codex-spark",
690705
"reason": "gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5."
691-
},
692-
{
693-
"provider": "openai-codex",
694-
"model": "gpt-5.4-mini",
695-
"reason": "gpt-5.4-mini is not supported by the OpenAI Codex OAuth route. Use openai/gpt-5.4-mini with an OpenAI API key or openai-codex/gpt-5.5 with Codex OAuth."
696706
}
697707
]
698708
},

src/agents/pi-embedded-runner/model.provider-runtime.test-support.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,10 @@ function buildDynamicModel(
266266
const template =
267267
lower === "gpt-5.5-pro"
268268
? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.4-pro", "gpt-5.3-codex"])
269-
: lower === "gpt-5.4" || isLegacyGpt54Alias || lower === "gpt-5.4-pro"
269+
: lower === "gpt-5.4" ||
270+
isLegacyGpt54Alias ||
271+
lower === "gpt-5.4-pro" ||
272+
lower === "gpt-5.4-mini"
270273
? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex"])
271274
: lower === "gpt-5.3-codex-spark"
272275
? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex"])
@@ -329,6 +332,22 @@ function buildDynamicModel(
329332
fallback,
330333
);
331334
}
335+
if (lower === "gpt-5.4-mini") {
336+
return cloneTemplate(
337+
template,
338+
modelId,
339+
{
340+
provider: "openai-codex",
341+
api: "openai-codex-responses",
342+
baseUrl: OPENAI_CODEX_BASE_URL,
343+
cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
344+
contextWindow: 400_000,
345+
contextTokens: 272_000,
346+
maxTokens: 128_000,
347+
},
348+
fallback,
349+
);
350+
}
332351
if (lower === "gpt-5.3-codex-spark") {
333352
return cloneTemplate(
334353
template,

src/agents/pi-embedded-runner/model.test-harness.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,14 @@ export function buildOpenAICodexForwardCompatExpectation(
7575
: isGpt54Mini
7676
? { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }
7777
: OPENAI_CODEX_TEMPLATE_MODEL.cost,
78-
contextWindow: isGpt54 ? 1_050_000 : isGpt55 ? 400_000 : isSpark ? 128_000 : 272000,
79-
...(isGpt54 || isGpt55 ? { contextTokens: 272_000 } : {}),
78+
contextWindow: isGpt54
79+
? 1_050_000
80+
: isGpt55 || isGpt54Mini
81+
? 400_000
82+
: isSpark
83+
? 128_000
84+
: 272000,
85+
...(isGpt54 || isGpt55 || isGpt54Mini ? { contextTokens: 272_000 } : {}),
8086
maxTokens: 128000,
8187
};
8288
}

src/agents/pi-embedded-runner/model.test.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,6 @@ vi.mock("../model-suppression.js", () => {
6060
) {
6161
return true;
6262
}
63-
if (provider === "openai-codex" && id?.trim().toLowerCase() === "gpt-5.4-mini") {
64-
return true;
65-
}
6663
return (
6764
(provider === "qwen" || provider === "modelstudio") &&
6865
id?.trim().toLowerCase() === "qwen3.6-plus" &&
@@ -78,9 +75,6 @@ vi.mock("../model-suppression.js", () => {
7875
) {
7976
return true;
8077
}
81-
if (provider === "openai-codex" && id?.trim().toLowerCase() === "gpt-5.4-mini") {
82-
return true;
83-
}
8478
return false;
8579
},
8680
buildSuppressedBuiltInModelError: ({
@@ -99,9 +93,6 @@ vi.mock("../model-suppression.js", () => {
9993
) {
10094
return "Unknown model: qwen/qwen3.6-plus. qwen3.6-plus is not supported on the Qwen Coding Plan endpoint; use a Standard pay-as-you-go Qwen endpoint or choose qwen/qwen3.5-plus.";
10195
}
102-
if (provider === "openai-codex" && id?.trim().toLowerCase() === "gpt-5.4-mini") {
103-
return "Unknown model: openai-codex/gpt-5.4-mini. gpt-5.4-mini is not supported by the OpenAI Codex OAuth route. Use openai/gpt-5.4-mini with an OpenAI API key or openai-codex/gpt-5.5 with Codex OAuth.";
104-
}
10596
if (
10697
(provider === "openai" ||
10798
provider === "azure-openai-responses" ||
@@ -369,7 +360,7 @@ describe("resolveModel", () => {
369360
);
370361
});
371362

372-
it("#74451: suppresses explicitly configured openai-codex/gpt-5.4-mini despite inline entry", () => {
363+
it("#74451: resolves explicitly configured openai-codex/gpt-5.4-mini inline entries", () => {
373364
const cfg = {
374365
models: {
375366
providers: {
@@ -391,10 +382,14 @@ describe("resolveModel", () => {
391382

392383
const result = resolveModelForTest("openai-codex", "gpt-5.4-mini", "/tmp/agent", cfg);
393384

394-
expect(result.model).toBeUndefined();
395-
expect(result.error).toBe(
396-
"Unknown model: openai-codex/gpt-5.4-mini. gpt-5.4-mini is not supported by the OpenAI Codex OAuth route. Use openai/gpt-5.4-mini with an OpenAI API key or openai-codex/gpt-5.5 with Codex OAuth.",
397-
);
385+
expect(result.error).toBeUndefined();
386+
expect(result.model).toMatchObject({
387+
provider: "openai-codex",
388+
id: "gpt-5.4-mini",
389+
api: "openai-codex-responses",
390+
contextWindow: 400_000,
391+
maxTokens: 128_000,
392+
});
398393
});
399394

400395
it("normalizes Google fallback baseUrls for custom providers", () => {
@@ -1542,15 +1537,17 @@ describe("resolveModel", () => {
15421537
});
15431538
});
15441539

1545-
it("does not build an openai-codex fallback for unsupported gpt-5.4-mini", () => {
1540+
it("builds an openai-codex fallback for gpt-5.4-mini", () => {
15461541
mockOpenAICodexTemplateModel(discoverModels);
15471542

15481543
const result = resolveModelForTest("openai-codex", "gpt-5.4-mini", "/tmp/agent");
15491544

1550-
expect(result.model).toBeUndefined();
1551-
expect(result.error).toBe(
1552-
"Unknown model: openai-codex/gpt-5.4-mini. gpt-5.4-mini is not supported by the OpenAI Codex OAuth route. Use openai/gpt-5.4-mini with an OpenAI API key or openai-codex/gpt-5.5 with Codex OAuth.",
1553-
);
1545+
expect(result.error).toBeUndefined();
1546+
expect(result.model).toMatchObject({
1547+
...buildOpenAICodexForwardCompatExpectation("gpt-5.4-mini"),
1548+
contextWindow: 400_000,
1549+
contextTokens: 272_000,
1550+
});
15541551
});
15551552

15561553
it("does not build an openai-codex fallback for removed gpt-5.3-codex-spark", () => {
@@ -1944,7 +1941,7 @@ describe("resolveModel", () => {
19441941
});
19451942
});
19461943

1947-
it("rejects stale discovered openai-codex gpt-5.4-mini rows", () => {
1944+
it("resolves discovered openai-codex gpt-5.4-mini rows", () => {
19481945
mockDiscoveredModel(discoverModels, {
19491946
provider: "openai-codex",
19501947
modelId: "gpt-5.4-mini",
@@ -1958,10 +1955,14 @@ describe("resolveModel", () => {
19581955

19591956
const result = resolveModelForTest("openai-codex", "gpt-5.4-mini", "/tmp/agent");
19601957

1961-
expect(result.model).toBeUndefined();
1962-
expect(result.error).toBe(
1963-
"Unknown model: openai-codex/gpt-5.4-mini. gpt-5.4-mini is not supported by the OpenAI Codex OAuth route. Use openai/gpt-5.4-mini with an OpenAI API key or openai-codex/gpt-5.5 with Codex OAuth.",
1964-
);
1958+
expect(result.error).toBeUndefined();
1959+
expect(result.model).toMatchObject({
1960+
provider: "openai-codex",
1961+
id: "gpt-5.4-mini",
1962+
name: "GPT-5.4 Mini",
1963+
contextWindow: 64_000,
1964+
input: ["text"],
1965+
});
19651966
});
19661967

19671968
it("rejects stale direct openai gpt-5.3-codex-spark discovery rows", () => {

src/plugin-sdk/test-helpers/provider-runtime-contract.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -605,25 +605,36 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr
605605
});
606606
});
607607

608-
it("does not claim unsupported codex mini models", () => {
608+
it("claims codex mini models through the Codex OAuth route", () => {
609609
const provider = requireProviderContractProvider("openai-codex");
610610
const model = provider.resolveDynamicModel?.({
611611
provider: "openai-codex",
612612
modelId: "gpt-5.4-mini",
613613
modelRegistry: {
614614
find: (_provider: string, id: string) =>
615-
id === "gpt-5.1-codex-mini"
615+
id === "gpt-5.4"
616616
? createModel({
617617
id,
618618
api: "openai-codex-responses",
619619
provider: "openai-codex",
620620
baseUrl: "https://chatgpt.com/backend-api",
621+
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
622+
contextWindow: 272_000,
623+
maxTokens: 128_000,
621624
})
622625
: null,
623626
} as never,
624627
});
625628

626-
expect(model).toBeUndefined();
629+
expect(model).toMatchObject({
630+
id: "gpt-5.4-mini",
631+
provider: "openai-codex",
632+
api: "openai-codex-responses",
633+
contextWindow: 400_000,
634+
contextTokens: 272_000,
635+
maxTokens: 128_000,
636+
cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
637+
});
627638
});
628639

629640
it("owns codex transport defaults", () => {

0 commit comments

Comments
 (0)