diff --git a/CHANGELOG.md b/CHANGELOG.md index c445ca8c2d9..3667eaa6404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai - Browser/existing-session: support `browser.profiles..userDataDir` so Chrome DevTools MCP can attach to Brave, Edge, and other Chromium-based browsers through their own user data directories. (#48170) Thanks @velvet-shark. - Skills/prompt budget: preserve all registered skills via a compact catalog fallback before dropping entries when the full prompt format exceeds `maxSkillsPromptChars`. (#47553) Thanks @snese. - Models/OpenAI: add native forward-compat support for `gpt-5.4-mini` and `gpt-5.4-nano` in the OpenAI provider catalog, runtime resolution, and reasoning capability gates. Thanks @vincentkoc. +- Models/OpenAI Codex: add `gpt-5.4-mini` support for the `openai-codex` OAuth provider — forward-compat resolution, catalog augmentation, xhigh thinking, and modern model filtering. (`gpt-5.4-nano` is API-only and not available via Codex OAuth.) Thanks @LittleMeHere. - Plugins/bundles: make enabled bundle MCP servers expose runnable tools in embedded Pi, and default relative bundle MCP launches to the bundle root so marketplace bundles like Context7 work through Pi instead of stopping at config import. - Scope message SecretRef resolution and harden doctor/status paths. (#48728) Thanks @joshavant. - Plugins/testing: add a public `openclaw/plugin-sdk/testing` surface for plugin-author test helpers, and move bundled-extension-only test bridges out of `extensions/` into private repo test helpers. diff --git a/extensions/openai/openai-codex-provider.ts b/extensions/openai/openai-codex-provider.ts index 36af1146758..a99aa3b9f3d 100644 --- a/extensions/openai/openai-codex-provider.ts +++ b/extensions/openai/openai-codex-provider.ts @@ -30,6 +30,7 @@ import { const PROVIDER_ID = "openai-codex"; const OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api"; const OPENAI_CODEX_GPT_54_MODEL_ID = "gpt-5.4"; +const OPENAI_CODEX_GPT_54_MINI_MODEL_ID = "gpt-5.4-mini"; const OPENAI_CODEX_GPT_54_CONTEXT_TOKENS = 1_050_000; const OPENAI_CODEX_GPT_54_MAX_TOKENS = 128_000; const OPENAI_CODEX_GPT_54_TEMPLATE_MODEL_IDS = ["gpt-5.3-codex", "gpt-5.2-codex"] as const; @@ -41,6 +42,7 @@ const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"] as const; const OPENAI_CODEX_DEFAULT_MODEL = `${PROVIDER_ID}/${OPENAI_CODEX_GPT_54_MODEL_ID}`; const OPENAI_CODEX_XHIGH_MODEL_IDS = [ OPENAI_CODEX_GPT_54_MODEL_ID, + OPENAI_CODEX_GPT_54_MINI_MODEL_ID, OPENAI_CODEX_GPT_53_MODEL_ID, OPENAI_CODEX_GPT_53_SPARK_MODEL_ID, "gpt-5.2-codex", @@ -48,6 +50,7 @@ const OPENAI_CODEX_XHIGH_MODEL_IDS = [ ] as const; const OPENAI_CODEX_MODERN_MODEL_IDS = [ OPENAI_CODEX_GPT_54_MODEL_ID, + OPENAI_CODEX_GPT_54_MINI_MODEL_ID, "gpt-5.2", "gpt-5.2-codex", OPENAI_CODEX_GPT_53_MODEL_ID, @@ -98,6 +101,8 @@ function resolveCodexForwardCompatModel( contextWindow: OPENAI_CODEX_GPT_54_CONTEXT_TOKENS, maxTokens: OPENAI_CODEX_GPT_54_MAX_TOKENS, }; + } else if (lower === OPENAI_CODEX_GPT_54_MINI_MODEL_ID) { + templateIds = OPENAI_CODEX_GPT_54_TEMPLATE_MODEL_IDS; } else if (lower === OPENAI_CODEX_GPT_53_SPARK_MODEL_ID) { templateIds = [OPENAI_CODEX_GPT_53_MODEL_ID, ...OPENAI_CODEX_TEMPLATE_MODEL_IDS]; patch = { @@ -279,6 +284,13 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin { name: OPENAI_CODEX_GPT_54_MODEL_ID, } : undefined, + gpt54Template + ? { + ...gpt54Template, + id: OPENAI_CODEX_GPT_54_MINI_MODEL_ID, + name: OPENAI_CODEX_GPT_54_MINI_MODEL_ID, + } + : undefined, sparkTemplate ? { ...sparkTemplate, diff --git a/src/agents/model-catalog.test.ts b/src/agents/model-catalog.test.ts index 8d56da2389a..8cffcca2a38 100644 --- a/src/agents/model-catalog.test.ts +++ b/src/agents/model-catalog.test.ts @@ -244,6 +244,13 @@ describe("loadModelCatalog", () => { name: "gpt-5.4", }), ); + expect(result).toContainEqual( + expect.objectContaining({ + provider: "openai-codex", + id: "gpt-5.4-mini", + name: "gpt-5.4-mini", + }), + ); }); it("merges configured models for opted-in non-pi-native providers", async () => { diff --git a/src/agents/model-compat.test.ts b/src/agents/model-compat.test.ts index c1e79f4757a..b4bab9efeb9 100644 --- a/src/agents/model-compat.test.ts +++ b/src/agents/model-compat.test.ts @@ -350,7 +350,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 @@ -364,6 +364,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); diff --git a/src/agents/pi-embedded-runner/model.test.ts b/src/agents/pi-embedded-runner/model.test.ts index b733e3a3f5f..db6e66b2155 100644 --- a/src/agents/pi-embedded-runner/model.test.ts +++ b/src/agents/pi-embedded-runner/model.test.ts @@ -666,6 +666,15 @@ describe("resolveModel", () => { expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4")); }); + it("builds an openai-codex fallback for gpt-5.4-mini", () => { + mockOpenAICodexTemplateModel(); + + const result = resolveModel("openai-codex", "gpt-5.4-mini", "/tmp/agent"); + + expect(result.error).toBeUndefined(); + expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4-mini")); + }); + it("builds an openai-codex fallback for gpt-5.3-codex-spark", () => { mockOpenAICodexTemplateModel(); diff --git a/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.test.ts b/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.test.ts index c3c1c073e24..c6d67fa378f 100644 --- a/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.test.ts @@ -244,7 +244,7 @@ describe("directive behavior", () => { const unsupportedModelTexts = await runThinkingDirective(home, "openai/gpt-4.1-mini"); expect(unsupportedModelTexts).toContain( - 'Thinking level "xhigh" is only supported for openai/gpt-5.4, openai/gpt-5.4-pro, openai/gpt-5.4-mini, openai/gpt-5.4-nano, openai/gpt-5.2, openai-codex/gpt-5.4, openai-codex/gpt-5.3-codex, openai-codex/gpt-5.3-codex-spark, openai-codex/gpt-5.2-codex, openai-codex/gpt-5.1-codex, github-copilot/gpt-5.2-codex or github-copilot/gpt-5.2.', + 'Thinking level "xhigh" is only supported for openai/gpt-5.4, openai/gpt-5.4-pro, openai/gpt-5.4-mini, openai/gpt-5.4-nano, openai/gpt-5.2, openai-codex/gpt-5.4, openai-codex/gpt-5.4-mini, openai-codex/gpt-5.3-codex, openai-codex/gpt-5.3-codex-spark, openai-codex/gpt-5.2-codex, openai-codex/gpt-5.1-codex, github-copilot/gpt-5.2-codex or github-copilot/gpt-5.2.', ); expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); }); diff --git a/src/plugins/contracts/runtime.contract.test.ts b/src/plugins/contracts/runtime.contract.test.ts index 551361d1bdd..6815022a348 100644 --- a/src/plugins/contracts/runtime.contract.test.ts +++ b/src/plugins/contracts/runtime.contract.test.ts @@ -583,6 +583,31 @@ describe("provider runtime contract", () => { }); }); + it("owns openai-codex gpt-5.4 mini forward-compat resolution", () => { + const provider = requireProviderContractProvider("openai-codex"); + const model = provider.resolveDynamicModel?.({ + provider: "openai-codex", + modelId: "gpt-5.4-mini", + modelRegistry: { + find: (_provider: string, id: string) => + id === "gpt-5.2-codex" + ? createModel({ + id, + api: "openai-codex-responses", + provider: "openai-codex", + baseUrl: "https://chatgpt.com/backend-api", + }) + : null, + } as never, + }); + + expect(model).toMatchObject({ + id: "gpt-5.4-mini", + provider: "openai-codex", + api: "openai-codex-responses", + }); + }); + it("owns codex transport defaults", () => { const provider = requireProviderContractProvider("openai-codex"); expect( diff --git a/src/plugins/provider-catalog-metadata.ts b/src/plugins/provider-catalog-metadata.ts index 1347fe00629..9d5d31cf50f 100644 --- a/src/plugins/provider-catalog-metadata.ts +++ b/src/plugins/provider-catalog-metadata.ts @@ -95,6 +95,13 @@ export function augmentBundledProviderCatalog( name: "gpt-5.4", } : undefined, + openAiCodexGpt54Template + ? { + ...openAiCodexGpt54Template, + id: "gpt-5.4-mini", + name: "gpt-5.4-mini", + } + : undefined, openAiCodexSparkTemplate ? { ...openAiCodexSparkTemplate, diff --git a/src/plugins/provider-runtime.test-support.ts b/src/plugins/provider-runtime.test-support.ts index 9e9fb0bb877..601f2053d6f 100644 --- a/src/plugins/provider-runtime.test-support.ts +++ b/src/plugins/provider-runtime.test-support.ts @@ -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", diff --git a/src/plugins/provider-runtime.test.ts b/src/plugins/provider-runtime.test.ts index 5db15a259bb..365faa8e094 100644 --- a/src/plugins/provider-runtime.test.ts +++ b/src/plugins/provider-runtime.test.ts @@ -219,6 +219,7 @@ describe("provider-runtime", () => { { provider: "openai", id: "gpt-5.4", name: "gpt-5.4" }, { provider: "openai", id: "gpt-5.4-pro", name: "gpt-5.4-pro" }, { 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", @@ -485,6 +486,7 @@ describe("provider-runtime", () => { { 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",