diff --git a/CHANGELOG.md b/CHANGELOG.md index aa76166bf0d..3e6769146b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,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 cb8d6d2519c..ecaf689cfb7 100644 --- a/extensions/openai/openai-codex-provider.ts +++ b/extensions/openai/openai-codex-provider.ts @@ -31,9 +31,11 @@ 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; +const OPENAI_CODEX_GPT_54_MINI_TEMPLATE_MODEL_IDS = ["gpt-5.3-codex", "gpt-5.2-codex"] as const; const OPENAI_CODEX_GPT_53_MODEL_ID = "gpt-5.3-codex"; const OPENAI_CODEX_GPT_53_SPARK_MODEL_ID = "gpt-5.3-codex-spark"; const OPENAI_CODEX_GPT_53_SPARK_CONTEXT_TOKENS = 128_000; @@ -42,6 +44,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", @@ -49,6 +52,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, @@ -99,6 +103,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_MINI_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 = { @@ -266,6 +272,11 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin { providerId: PROVIDER_ID, templateIds: OPENAI_CODEX_GPT_54_TEMPLATE_MODEL_IDS, }); + const gpt54MiniTemplate = findCatalogTemplate({ + entries: ctx.entries, + providerId: PROVIDER_ID, + templateIds: OPENAI_CODEX_GPT_54_MINI_TEMPLATE_MODEL_IDS, + }); const sparkTemplate = findCatalogTemplate({ entries: ctx.entries, providerId: PROVIDER_ID, @@ -279,6 +290,13 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin { name: OPENAI_CODEX_GPT_54_MODEL_ID, } : undefined, + gpt54MiniTemplate + ? { + ...gpt54MiniTemplate, + 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 e576bc621b3..11fe6b8b937 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 ba6e7df1187..393c90d368c 100644 --- a/src/plugins/contracts/runtime.contract.test.ts +++ b/src/plugins/contracts/runtime.contract.test.ts @@ -587,6 +587,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..d343fa508b8 100644 --- a/src/plugins/provider-catalog-metadata.ts +++ b/src/plugins/provider-catalog-metadata.ts @@ -53,6 +53,11 @@ export function augmentBundledProviderCatalog( providerId: OPENAI_CODEX_PROVIDER_ID, templateIds: ["gpt-5.3-codex", "gpt-5.2-codex"], }); + const openAiCodexGpt54MiniTemplate = findCatalogTemplate({ + entries: context.entries, + providerId: OPENAI_CODEX_PROVIDER_ID, + templateIds: ["gpt-5.3-codex", "gpt-5.2-codex"], + }); const openAiCodexSparkTemplate = findCatalogTemplate({ entries: context.entries, providerId: OPENAI_CODEX_PROVIDER_ID, @@ -95,6 +100,13 @@ export function augmentBundledProviderCatalog( name: "gpt-5.4", } : undefined, + openAiCodexGpt54MiniTemplate + ? { + ...openAiCodexGpt54MiniTemplate, + 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 2c1cc1e2d57..bec95b2268e 100644 --- a/src/plugins/provider-runtime.test.ts +++ b/src/plugins/provider-runtime.test.ts @@ -217,6 +217,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", @@ -483,6 +484,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",