feat(openai): default new setups to GPT-5.6 (#103581)

* feat(openai): default fresh setup to GPT-5.6

* test(crestodian): expect GPT-5.6 Codex defaults

* test(crestodian): expect GPT-5.6 bootstrap default
This commit is contained in:
Peter Steinberger
2026-07-10 10:22:58 +01:00
committed by GitHub
parent fc60a8e66b
commit ab5d143d59
39 changed files with 652 additions and 179 deletions

View File

@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
### Changes
- **OpenAI GPT-5.6 defaults:** use `openai/gpt-5.6` (Sol alias) for fresh API-key setup and exact `openai/gpt-5.6-sol` for fresh Codex/OAuth setup, while preserving existing primaries, fallbacks, aliases, and explicit GPT-5.5 selections. (#103234)
- **Meta provider:** add bundled `muse-spark-1.1` model support with Responses API streaming, tool calls, encrypted reasoning replay, onboarding, and standalone npm/ClawHub distribution. (#102873) Thanks @HamidShojanazeri.
- **Android chat agent selector:** switch the active agent directly from the live chat screen while keeping chat, Talk mode, and home canvas on the same canonical session. (#80422) Thanks @bcperry.
- **Gateway host status:** show the connected Gateway's host, network address, OS, runtime, uptime, CPU, memory, and disk details in Control UI Settings. (#100478)

View File

@@ -50,8 +50,8 @@ openclaw crestodian
openclaw crestodian --json
openclaw crestodian --message "models"
openclaw crestodian --message "validate config"
openclaw crestodian --message "setup workspace ~/Projects/work model openai/gpt-5.5" --yes
openclaw crestodian --message "set default model openai/gpt-5.5" --yes
openclaw crestodian --message "setup workspace ~/Projects/work model openai/gpt-5.6" --yes
openclaw crestodian --message "set default model openai/gpt-5.6" --yes
openclaw onboard --modern
```
@@ -64,7 +64,7 @@ doctor
doctor fix
validate config
setup
setup workspace ~/Projects/work model openai/gpt-5.5
setup workspace ~/Projects/work model openai/gpt-5.6
config set gateway.port 19001
config set-ref gateway.auth.token env OPENCLAW_GATEWAY_TOKEN
gateway status
@@ -73,7 +73,7 @@ agents
create agent work workspace ~/Projects/work
models
configure model provider
set default model openai/gpt-5.5
set default model openai/gpt-5.6
channels
channel info slack
connect slack
@@ -138,18 +138,22 @@ gateway/app chat that supports sensitive replies hosts the same steps inline.
```text
setup
setup workspace ~/Projects/work
setup workspace ~/Projects/work model openai/gpt-5.5
setup workspace ~/Projects/work model openai/gpt-5.6
```
When no model is configured, setup picks the first usable backend in this order and tells you what it chose:
1. Existing explicit model, if already configured.
2. `OPENAI_API_KEY` -> `openai/gpt-5.5`
2. `OPENAI_API_KEY` -> `openai/gpt-5.6` (the bare direct-API id resolves to Sol)
3. `ANTHROPIC_API_KEY` -> `anthropic/claude-opus-4-8`
4. Claude Code CLI -> `claude-cli/claude-opus-4-8`
5. Codex -> `openai/gpt-5.5` through the Codex app-server harness
5. Codex -> `openai/gpt-5.6-sol` through the Codex app-server harness
6. Gemini CLI -> `google-gemini-cli/gemini-3.1-pro-preview`
The first existing explicit model always wins, so setup preserves an existing
`openai/gpt-5.5` primary. If an OpenAI account does not expose GPT-5.6, set
`openai/gpt-5.5` explicitly instead; Crestodian does not silently downgrade.
If none are available, setup still writes the workspace and Gateway configuration, then asks whether to configure a model provider. Accepting opens the normal onboarding provider/auth and default-model steps. Declining leaves Crestodian in deterministic mode; exact setup and repair commands still work, but the normal agent cannot answer until a provider and default model are configured. Run `configure model provider` later to reopen the provider flow.
The macOS app drives the same ladder through the `crestodian.setup.detect` and `crestodian.setup.activate` gateway methods: detect lists every reusable backend it finds, activate live-tests one candidate (a real "reply with OK" completion) and only persists the model, workspace, and gateway defaults after the test passes. A failing candidate never changes config; the app automatically walks down the ladder and finally offers a manual key/token step populated from the Gateway's active text-inference provider plugins. The selected provider owns its starter model and config, and the credential is verified the same way before it is saved.
@@ -159,7 +163,7 @@ The macOS app drives the same ladder through the `crestodian.setup.detect` and `
Interactive Crestodian is AI-only: every message — including ones that look like typed commands — runs through the same embedded agent loop as regular OpenClaw agents, restricted to one ring-zero `crestodian` tool that wraps the typed operations. Read actions run freely, mutations require your conversational approval for that exact operation (see Operations and approval), and every applied write is audited and re-validated. The agent session persists, so the custodian has real multi-turn memory. It first uses the configured OpenClaw model; with no usable model it falls back to a local runtime already present on the machine, in setup-ladder order:
- Claude Code CLI: `claude-cli/claude-opus-4-8` (agent loop; the ring-zero tool is served over MCP, see the trust model below)
- Codex app-server harness: `openai/gpt-5.5` (agent loop with an enforced single-tool allow-list)
- Codex app-server harness: `openai/gpt-5.6-sol` (agent loop with an enforced single-tool allow-list)
- Gemini CLI: `google-gemini-cli/gemini-3.1-pro-preview` (agent loop; ring-zero tool over MCP)
When the agent loop is unavailable, Crestodian degrades to a bounded single-turn planner, and only without any usable model at all to deterministic typed commands. The planner cannot mutate config directly; it must translate the request into one of Crestodian's typed commands, and normal approval/audit rules apply. Crestodian prints the model it used and the interpreted command before running anything. Fallback planner turns are temporary, tool-disabled where the runtime supports it, and use a temporary workspace/session.

View File

@@ -37,7 +37,13 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
Plugin auto-enable follows the same boundary: `openai/*` agent refs enable the Codex plugin for the default route, and explicit provider/model `agentRuntime.id: "codex"` or legacy `codex/<model>` refs also require it.
GPT-5.5 is available through the native Codex app-server harness by default on `openai/gpt-5.5`, and through the OpenClaw runtime when provider/model runtime policy explicitly selects `openclaw`.
Fresh OpenAI setup uses a route-specific GPT-5.6 ref: API-key setup selects
`openai/gpt-5.6` (the bare direct-API id resolves to Sol), while
ChatGPT/Codex OAuth selects exact `openai/gpt-5.6-sol` for the native Codex
catalog. Existing explicit primaries, including `openai/gpt-5.5`, are
preserved when OpenAI auth is added or refreshed. GPT-5.5 remains available
through either runtime as an explicit recovery choice for accounts without
GPT-5.6 access.
</Accordion>
<Accordion title="CLI runtimes">
@@ -89,7 +95,8 @@ Official provider plugins publish their own model catalog rows. These providers
- Provider: `openai`
- Auth: `OPENAI_API_KEY`
- Optional rotation: `OPENAI_API_KEYS`, `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`, plus `OPENCLAW_LIVE_OPENAI_KEY` (single override)
- Example models: `openai/gpt-5.5`, `openai/gpt-5.4-mini`
- Fresh setup default: `openai/gpt-5.6`; on the direct API, the bare id resolves to Sol.
- Example models: `openai/gpt-5.6`, `openai/gpt-5.6-terra`, `openai/gpt-5.6-luna`, `openai/gpt-5.5`
- Verify account/model availability with `openclaw models list --provider openai` if a specific install or API key behaves differently.
- CLI: `openclaw onboard --auth-choice openai-api-key`
- Default transport is `auto`; OpenClaw passes the transport choice to the shared model runtime.
@@ -103,10 +110,15 @@ Official provider plugins publish their own model catalog rows. These providers
```json5
{
agents: { defaults: { model: { primary: "openai/gpt-5.5" } } },
agents: { defaults: { model: { primary: "openai/gpt-5.6" } } },
}
```
If the API organization does not expose GPT-5.6, set
`openai/gpt-5.5` explicitly. Normal onboarding and reauthentication preserve an
existing explicit primary model; `models auth login --set-default` and
`models set` are the intentional replacement paths.
### Anthropic
- Provider: `anthropic`
@@ -134,7 +146,7 @@ Claude CLI reuse (`claude -p`) is a sanctioned OpenClaw integration path. Anthro
- Provider: `openai`
- Auth: OAuth (ChatGPT)
- Native Codex app-server harness ref: `openai/gpt-5.5`
- Fresh native Codex app-server harness ref: `openai/gpt-5.6-sol`
- Native Codex app-server harness docs: [Codex harness](/plugins/codex-harness)
- Legacy model refs: `codex/gpt-*`
- Plugin boundary: `openai/*` loads the OpenAI plugin; the native Codex app-server plugin is selected by the Codex harness runtime.
@@ -144,17 +156,18 @@ Claude CLI reuse (`claude -p`) is a sanctioned OpenClaw integration path. Anthro
- `params.serviceTier` is also forwarded on native Codex Responses requests (`chatgpt.com/backend-api`)
- Hidden OpenClaw attribution headers (`originator`, `version`, `User-Agent`) are only attached on native Codex traffic to `chatgpt.com/backend-api`, not generic OpenAI-compatible proxies
- Shares the same `/fast` toggle and `params.fastMode` config as direct `openai/*`; OpenClaw maps that to `service_tier=priority`
- The native Codex catalog can expose exact `openai/gpt-5.6-sol`, `openai/gpt-5.6-terra`, and `openai/gpt-5.6-luna` refs according to account access. It does not apply the direct API's bare `gpt-5.6` alias client-side.
- `openai/gpt-5.5` uses the Codex catalog native `contextWindow = 400000` and default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai.models[].contextTokens`
- Sign in with `openai` auth and configure `openai/gpt-5.5` for the standard subscription plus native Codex runtime route; OpenAI agent turns select Codex by default.
- Use provider/model `agentRuntime.id: "openclaw"` only when you want the built-in OpenClaw route; otherwise keep `openai/gpt-5.5` on the default Codex harness.
- Legacy Codex GPT refs are legacy state, not a live provider route. Use `openai/gpt-5.5` on the native Codex runtime for new agent config, and run `openclaw doctor --fix` to migrate old legacy Codex model refs to canonical `openai/*` refs.
- Sign in with `openai` auth and use `openai/gpt-5.6-sol` for a fresh subscription-backed setup. Select `openai/gpt-5.5` explicitly if that Codex workspace does not expose GPT-5.6.
- Use provider/model `agentRuntime.id: "openclaw"` only when you want the built-in OpenClaw route; otherwise keep the selected `openai/*` model on the default Codex harness.
- Legacy Codex GPT refs are legacy state, not a live provider route. Use canonical `openai/*` refs for new agent config, and run `openclaw doctor --fix` to migrate old legacy Codex model refs without upgrading an existing explicit `openai/gpt-5.5` selection.
```json5
{
plugins: { entries: { codex: { enabled: true } } },
agents: {
defaults: {
model: { primary: "openai/gpt-5.5" },
model: { primary: "openai/gpt-5.6-sol" },
},
},
}

View File

@@ -23,7 +23,7 @@ sidebarTitle: "Models CLI"
</Card>
</CardGroup>
A model ref (`provider/model`) chooses a provider and model. It does not usually choose the low-level agent runtime. OpenAI is the main exception: `openai/gpt-5.5` runs through the Codex app-server runtime by default on the official OpenAI provider. Subscription Copilot refs (`github-copilot/*`) can be opted into the external GitHub Copilot agent runtime plugin, but that path is always explicit (never selected by `auto`). Runtime overrides belong on provider/model policy, not on the whole agent or session. In Codex runtime mode, `openai/gpt-*` does not imply API-key billing; auth can come from a Codex account or an `openai` OAuth profile. See [Agent runtimes](/concepts/agent-runtimes) and [GitHub Copilot agent runtime](/plugins/copilot).
A model ref (`provider/model`) chooses a provider and model. It does not usually choose the low-level agent runtime. OpenAI is the main exception: official `openai/gpt-*` agent refs run through the Codex app-server runtime by default. Subscription Copilot refs (`github-copilot/*`) can be opted into the external GitHub Copilot agent runtime plugin, but that path is always explicit (never selected by `auto`). Runtime overrides belong on provider/model policy, not on the whole agent or session. In Codex runtime mode, `openai/gpt-*` does not imply API-key billing; auth can come from a Codex account or an `openai` OAuth profile. See [Agent runtimes](/concepts/agent-runtimes) and [GitHub Copilot agent runtime](/plugins/copilot).
## Selection order
@@ -83,6 +83,13 @@ openclaw onboard
Sets up model and auth for common providers without hand-editing config, including OpenAI Codex subscription OAuth and Anthropic (API key or Claude CLI reuse).
With no primary model configured, fresh OpenAI API-key setup selects
`openai/gpt-5.6`; the bare direct-API id resolves to the Sol tier. Fresh
ChatGPT/Codex OAuth setup selects the exact `openai/gpt-5.6-sol` catalog ref.
Reauthentication preserves an existing explicit primary model, including
`openai/gpt-5.5`. If GPT-5.6 is unavailable to the account, select
`openai/gpt-5.5` explicitly; OpenClaw does not silently downgrade it.
## "Model is not allowed" (and why replies stop)
If `agents.defaults.models` is set, it becomes the allowlist for `/model` and session overrides. Selecting a model outside that allowlist returns, before any normal reply is generated:

View File

@@ -558,12 +558,17 @@ and troubleshooting see the main [FAQ](/help/faq).
</Accordion>
<Accordion title="How does Codex auth work?">
OpenClaw supports **OpenAI Codex** via OAuth (ChatGPT sign-in). Use `openai/gpt-5.5`
for the default setup: ChatGPT/Codex subscription auth plus native Codex app-server
execution. Legacy Codex-prefixed model refs are legacy config repaired by
`openclaw doctor --fix`. Direct OpenAI API-key access remains available for non-agent
OpenAI API surfaces and, through an ordered `openai` API-key profile, for agent models
too. See [Model providers](/concepts/model-providers) and [Onboarding (CLI)](/start/wizard).
OpenClaw supports **OpenAI Codex** via OAuth (ChatGPT sign-in). A fresh
setup with no primary model uses exact `openai/gpt-5.6-sol` for
ChatGPT/Codex subscription auth plus native Codex app-server execution.
Reauthentication preserves an existing explicit model, including
`openai/gpt-5.5`. If the Codex workspace does not expose GPT-5.6, select
`openai/gpt-5.5` explicitly; OpenClaw does not silently downgrade. Legacy
Codex-prefixed model refs are legacy config repaired by `openclaw doctor
--fix`. Direct OpenAI API-key access remains available for non-agent OpenAI
API surfaces and, through an ordered `openai` API-key profile, for agent
models too. See [Model providers](/concepts/model-providers) and
[Onboarding (CLI)](/start/wizard).
</Accordion>
<Accordion title="Why does OpenClaw still mention legacy OpenAI Codex prefix?">
@@ -571,14 +576,17 @@ and troubleshooting see the main [FAQ](/help/faq).
ChatGPT/Codex OAuth - OpenAI Codex is folded into it. You may still see a legacy
`openai-codex` prefix in older config and migration warnings:
- `openai/gpt-5.5` = ChatGPT/Codex subscription auth with native Codex runtime for agent turns.
- `openai/gpt-5.6-sol` = fresh ChatGPT/Codex subscription setup with the native Codex runtime for agent turns.
- `openai/gpt-5.5` = explicit supported selection for existing config or accounts without GPT-5.6 access.
- Legacy `openai-codex/*` model refs = legacy route repaired by `openclaw doctor --fix`.
- `openai/gpt-5.5` plus an ordered `openai` API-key profile = API-key auth for an OpenAI agent model.
- Legacy `openai-codex` auth profile ids = legacy ids migrated by `openclaw doctor --fix`.
Want direct OpenAI Platform billing? Set `OPENAI_API_KEY`. Want ChatGPT/Codex
subscription auth? Run `openclaw models auth login --provider openai`. Keep the model
ref as `openai/gpt-5.5`; legacy Codex-prefixed refs are what `openclaw doctor --fix` rewrites.
subscription auth? Run `openclaw models auth login --provider openai`. Keep
model refs under the canonical `openai/*` provider. Fresh subscription
setup uses exact `openai/gpt-5.6-sol`; doctor repairs legacy Codex-prefixed
refs without upgrading an explicit `openai/gpt-5.5` selection.
</Accordion>

View File

@@ -44,15 +44,16 @@ changing config.
## Quick choice
| Goal | Use | Notes |
| ------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| ChatGPT/Codex subscription, native Codex runtime | `openai/gpt-5.5` | Default setup. Sign in with Codex auth. |
| GPT-5.6 limited preview | `openai/gpt-5.6-sol`, `-terra`, or `-luna` | Needs an OpenAI-approved API org or Codex workspace allowlist entry. |
| Direct API-key billing for agent turns | `openai/gpt-5.5` plus an ordered API-key auth profile | Set `auth.order.openai` to put the key profile after subscription auth. |
| Direct API-key billing, explicit OpenClaw runtime | `openai/gpt-5.5` plus provider/model `agentRuntime.id: "openclaw"` | Select a normal `openai` API-key profile. |
| Latest ChatGPT Instant model alias | `openai/chat-latest` | Direct API-key only; moving alias, not the stable default. |
| Image generation or editing | `openai/gpt-image-2` | Works with `OPENAI_API_KEY` or Codex OAuth. |
| Transparent-background images | `openai/gpt-image-1.5` | Set `outputFormat` to `png` or `webp` and `background=transparent`. |
| Goal | Use | Notes |
| ------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------- |
| ChatGPT/Codex subscription, native Codex runtime | `openai/gpt-5.6-sol` | Fresh subscription setup; sign in with Codex auth. |
| Direct API-key billing for agent turns | `openai/gpt-5.6` plus an ordered API-key auth profile | Fresh API-key setup; the bare direct-API id resolves to Sol. |
| Choose an exact GPT-5.6 tier | `openai/gpt-5.6-sol`, `-terra`, or `-luna` | Check `models list` for the tiers available to this account. |
| Account without GPT-5.6 access | `openai/gpt-5.5` | Explicit recovery choice; OpenClaw does not silently downgrade. |
| Direct API-key billing, explicit OpenClaw runtime | `openai/gpt-5.6` plus provider/model `agentRuntime.id: "openclaw"` | Select a normal `openai` API-key profile. |
| Latest ChatGPT Instant model alias | `openai/chat-latest` | Direct API-key only; moving alias, not the stable default. |
| Image generation or editing | `openai/gpt-image-2` | Works with `OPENAI_API_KEY` or Codex OAuth. |
| Transparent-background images | `openai/gpt-image-1.5` | Set `outputFormat` to `png` or `webp` and `background=transparent`. |
## Naming map
@@ -69,27 +70,42 @@ profile ids, and legacy Codex auth-order entries to the canonical `openai`
route. Use `auth.order.openai` for new auth-order config.
<Note>
GPT-5.5 is available through both direct OpenAI Platform API-key access and
subscription/OAuth routes. For ChatGPT/Codex subscription with native Codex
execution, use `openai/gpt-5.5` and leave runtime config unset; that already
selects the Codex harness. Use an API-key auth profile only when you want
direct API-key auth for an agent model.
Fresh OpenAI setup applies a GPT-5.6 primary only when no primary model is
configured. Adding or refreshing OpenAI auth preserves an existing explicit
selection, including `openai/gpt-5.5`, unless you explicitly use
`models auth login --set-default` or `models set`. Use an API-key auth profile
only when you want API-key auth for an agent model.
</Note>
## GPT-5.6 limited preview
OpenClaw recognizes three public GPT-5.6 model ids: `openai/gpt-5.6-sol`,
`openai/gpt-5.6-terra`, and `openai/gpt-5.6-luna`. All three expose `xhigh` and
`max` reasoning in the current catalog. OpenAI describes Sol as the flagship
tier, Terra as the balanced tier, and Luna as the fast, lower-cost tier. See
the [GPT-5.6 launch announcement](https://openai.com/index/previewing-gpt-5-6-sol/)
and [preview access guide](https://help.openai.com/en/articles/20001325-a-preview-of-gpt-5-6-sol-terra-and-luna).
OpenClaw recognizes the exact `openai/gpt-5.6-sol`,
`openai/gpt-5.6-terra`, and `openai/gpt-5.6-luna` model ids. All three expose
`xhigh` and `max` reasoning in the current catalog. OpenAI describes Sol as
the flagship tier, Terra as the balanced tier, and Luna as the fast,
lower-cost tier. See the
[GPT-5.6 launch announcement](https://openai.com/index/previewing-gpt-5-6-sol/)
and [access guide](https://help.openai.com/en/articles/20001325-a-preview-of-gpt-5-6-sol-terra-and-luna).
Access is allowlisted during the preview and can be granted separately for the
API and Codex; a paid ChatGPT plan alone does not grant access. OpenClaw keeps
`openai/gpt-5.5` as the default and does not special-case the access error, so
selecting a GPT-5.6 ref without access surfaces the upstream error directly
instead of falling back silently.
With direct OpenAI API-key auth, the bare `openai/gpt-5.6` id is an alias for
Sol and is the fresh setup default. The native Codex catalog does not apply
that direct-API alias client-side; depending on workspace access, it can show
the exact Sol, Terra, and Luna ids. Fresh ChatGPT/Codex OAuth setup therefore
uses `openai/gpt-5.6-sol`. Check the current account with:
```bash
openclaw models list --provider openai
```
API organization and Codex workspace access can differ. If GPT-5.6 is not
available, select GPT-5.5 explicitly:
```bash
openclaw models set openai/gpt-5.5
```
OpenClaw surfaces the upstream access error and does not silently replace a
GPT-5.6 selection with GPT-5.5.
<Note>
Agent model turns on `openai/*` require the bundled Codex app-server plugin by
@@ -190,11 +206,12 @@ for the full example.
### Route summary
| Model ref | Runtime config | Route | Auth |
| ----------------------- | ------------------------------------------------------ | --------------------------- | ------------------------------------ |
| `openai/gpt-5.5` | unset, or provider/model `agentRuntime.id: "codex"` | Codex app-server harness | Ordered API-key auth profile |
| `openai/gpt-5.4-mini` | unset, or provider/model `agentRuntime.id: "codex"` | Codex app-server harness | Ordered API-key auth profile |
| `openai/gpt-5.5` | provider/model `agentRuntime.id: "openclaw"` | OpenClaw embedded runtime | Selected `openai` API-key profile |
| Model ref | Runtime config | Route | Auth |
| --------------------- | --------------------------------------------------- | ------------------------ | -------------------------------- |
| `openai/gpt-5.6` | unset, or provider/model `agentRuntime.id: "codex"` | Codex app-server harness | Ordered API-key auth profile |
| `openai/gpt-5.6` | provider/model `agentRuntime.id: "openclaw"` | OpenClaw embedded runtime | Selected `openai` API-key profile |
| `openai/gpt-5.5` | either runtime | Selected agent runtime | Selected OpenAI API-key profile |
| `openai/gpt-5.4-mini` | unset, or provider/model `agentRuntime.id: "codex"` | Codex app-server harness | Ordered API-key auth profile |
<Note>
Agent turns on `openai/*` use the Codex app-server harness by default. For
@@ -209,10 +226,14 @@ for the full example.
```json5
{
env: { OPENAI_API_KEY: "example-openai-key-not-real" },
agents: { defaults: { model: { primary: "openai/gpt-5.5" } } },
agents: { defaults: { model: { primary: "openai/gpt-5.6" } } },
}
```
The bare direct-API `gpt-5.6` id resolves to the Sol tier. If this API
organization does not expose GPT-5.6, set the primary to
`openai/gpt-5.5` explicitly.
To try ChatGPT's current Instant model from the OpenAI API, set the model
to `openai/chat-latest`:
@@ -223,10 +244,11 @@ for the full example.
}
```
`chat-latest` is a moving alias. OpenAI recommends `gpt-5.5` for production
API usage, so keep `openai/gpt-5.5` as the stable default unless you want
that alias behavior. The alias only accepts `medium` text verbosity;
OpenClaw forces any other requested verbosity to `medium` for this model.
`chat-latest` is a moving alias. Fresh OpenAI API-key setup instead uses
`openai/gpt-5.6`, whose bare direct-API id resolves to Sol. Existing
explicit primaries, including `openai/gpt-5.5`, remain unchanged. The
`chat-latest` alias only accepts `medium` text verbosity; OpenClaw forces
any other requested verbosity to `medium` for this model.
<Warning>
OpenClaw does **not** expose `gpt-5.3-codex-spark` on the direct OpenAI
@@ -263,7 +285,7 @@ for the full example.
</Step>
<Step title="Use the canonical OpenAI model route">
```bash
openclaw config set agents.defaults.model.primary openai/gpt-5.5
openclaw config set agents.defaults.model.primary openai/gpt-5.6-sol
```
No runtime config is required for the default path. OpenAI agent
@@ -283,19 +305,25 @@ for the full example.
### Route summary
| Model ref | Runtime config | Route | Auth |
| -------------------------- | ------------------------------------------------ | --------------------------------------------------------- | -------------------------------------------------- |
| `openai/gpt-5.5` | unset, or provider/model `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in, or an ordered `openai` auth profile |
| `openai/gpt-5.5` | provider/model `agentRuntime.id: "openclaw"` | OpenClaw embedded runtime, internal Codex-auth transport | Selected `openai` OAuth profile |
| Legacy Codex GPT-5.5 ref | repaired by doctor | Rewritten to `openai/gpt-5.5` | Migrated OpenAI OAuth profile |
| `codex-cli/gpt-5.5` | repaired by doctor | Rewritten to `openai/gpt-5.5` | Codex app-server auth |
| Model ref | Runtime config | Route | Auth |
| ------------------------- | --------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------- |
| `openai/gpt-5.6-sol` | unset, or provider/model `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in, or an ordered `openai` auth profile |
| `openai/gpt-5.6-terra` | unset, or provider/model `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in when the catalog exposes Terra |
| `openai/gpt-5.6-luna` | unset, or provider/model `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in when the catalog exposes Luna |
| `openai/gpt-5.6-sol` | provider/model `agentRuntime.id: "openclaw"` | OpenClaw embedded runtime, internal Codex-auth transport | Selected `openai` OAuth profile |
| `openai/gpt-5.5` | either runtime | Selected agent runtime | Selected OpenAI auth profile |
| Legacy Codex GPT-5.5 ref | repaired by doctor | Rewritten to `openai/gpt-5.5` | Migrated OpenAI OAuth profile |
| `codex-cli/gpt-5.5` | repaired by doctor | Rewritten to `openai/gpt-5.5` | Codex app-server auth |
<Warning>
Prefer `openai/gpt-5.5` for new subscription-backed agent config. Older
Fresh subscription-backed setup uses exact `openai/gpt-5.6-sol`; the
native Codex catalog may also expose exact Terra or Luna refs. If the
account does not expose GPT-5.6, select `openai/gpt-5.5` explicitly. Older
Codex GPT refs are legacy OpenClaw routes, not the native Codex runtime
path; run `openclaw doctor --fix` to migrate them. `gpt-5.3-codex-spark`
stays limited to accounts whose Codex subscription catalog advertises it;
direct OpenAI API-key and Azure refs for it stay suppressed.
path; run `openclaw doctor --fix` to migrate them without upgrading an
existing explicit GPT-5.5 selection. `gpt-5.3-codex-spark` stays limited
to accounts whose Codex subscription catalog advertises it; direct OpenAI
API-key and Azure refs for it stay suppressed.
</Warning>
<Note>
@@ -310,22 +338,22 @@ for the full example.
plugins: { entries: { codex: { enabled: true } } },
agents: {
defaults: {
model: { primary: "openai/gpt-5.5" },
model: { primary: "openai/gpt-5.6-sol" },
},
},
}
```
With an API-key backup, keep the model on `openai/gpt-5.5` and put the
auth order under `openai`. OpenClaw tries the subscription first, then the
API key, while staying on the Codex harness:
With an API-key backup, keep the selected model under `openai/*` and put
the auth order under `openai`. OpenClaw tries the subscription first, then
the API key, while staying on the Codex harness:
```json5
{
plugins: { entries: { codex: { enabled: true } } },
agents: {
defaults: {
model: { primary: "openai/gpt-5.5" },
model: { primary: "openai/gpt-5.6-sol" },
},
},
auth: {

View File

@@ -40,11 +40,12 @@ behavior and outputs, see [CLI setup reference](/start/wizard-cli-reference).
- **Anthropic API key**: uses `ANTHROPIC_API_KEY` if present or prompts for a key, then saves it for daemon use.
- **Anthropic Claude CLI**: preferred local path when a Claude CLI sign-in already exists; OpenClaw still supports Anthropic setup-token auth as an alternative.
- **OpenAI Code (Codex) subscription (OAuth)**: browser flow; paste the `code#state`.
- Sets `agents.defaults.model` to `openai/gpt-5.5` through the Codex runtime when model is unset or already OpenAI-family.
- On a fresh setup with no primary model, sets `agents.defaults.model` to `openai/gpt-5.6-sol` through the Codex runtime.
- **OpenAI Code (Codex) subscription (device pairing)**: browser pairing flow with a short-lived device code.
- Sets `agents.defaults.model` to `openai/gpt-5.5` through the Codex runtime when model is unset or already OpenAI-family.
- On a fresh setup with no primary model, sets `agents.defaults.model` to `openai/gpt-5.6-sol` through the Codex runtime.
- **OpenAI API key**: uses `OPENAI_API_KEY` if present or prompts for a key, then stores it in auth profiles.
- Sets `agents.defaults.model` to `openai/gpt-5.5` when model is unset, `openai/*`, or legacy Codex model refs.
- On a fresh setup with no primary model, sets `agents.defaults.model` to `openai/gpt-5.6`; the bare direct-API model id resolves to the Sol tier.
- Adding or reauthenticating OpenAI preserves an existing explicit primary model, including `openai/gpt-5.5`. If the account does not expose GPT-5.6, select `openai/gpt-5.5` explicitly; OpenClaw does not silently downgrade the model.
- **xAI OAuth**: device-code browser sign-in with no localhost callback required, so it works over SSH/Docker/VPS too (`--auth-choice xai-oauth`).
- **xAI API key**: prompts for `XAI_API_KEY` (`--auth-choice xai-api-key`).
- `--auth-choice xai-device-code` still works as a manual-only compatibility alias for the same xAI OAuth device-code flow; use `xai-oauth` for new scripts.

View File

@@ -171,19 +171,26 @@ instead of exiting. Explicit `--auth-choice` runs still fail fast for automation
<Accordion title="OpenAI Code subscription (OAuth)">
Browser flow; paste `code#state`.
Sets `agents.defaults.model` to `openai/gpt-5.5` through the Codex runtime when model is unset or already OpenAI-family.
On a fresh setup with no primary model, sets `agents.defaults.model` to
`openai/gpt-5.6-sol` through the Codex runtime.
</Accordion>
<Accordion title="OpenAI Code subscription (device pairing)">
Browser pairing flow with a short-lived device code.
Sets `agents.defaults.model` to `openai/gpt-5.5` through the Codex runtime when model is unset or already OpenAI-family.
On a fresh setup with no primary model, sets `agents.defaults.model` to
`openai/gpt-5.6-sol` through the Codex runtime.
</Accordion>
<Accordion title="OpenAI API key">
Uses `OPENAI_API_KEY` if present or prompts for a key, then stores the credential in auth profiles.
Sets `agents.defaults.model` to `openai/gpt-5.5` when model is unset, `openai/*`, or legacy Codex model refs.
On a fresh setup with no primary model, sets `agents.defaults.model` to
`openai/gpt-5.6`; the bare direct-API model id resolves to the Sol tier.
Adding or reauthenticating OpenAI preserves an existing explicit primary
model, including `openai/gpt-5.5`. If the account does not expose GPT-5.6,
select `openai/gpt-5.5` explicitly; OpenClaw does not silently downgrade it.
</Accordion>
<Accordion title="xAI (Grok) OAuth">

View File

@@ -124,6 +124,11 @@ Local mode (default) walks through these steps:
provider-specific manual auth), including Custom Provider
(OpenAI-compatible, OpenAI Responses-compatible, Anthropic-compatible, or
Unknown auto-detect). Pick a default model.
Fresh OpenAI API-key setup defaults to `openai/gpt-5.6` (the bare direct-API
id resolves to Sol); fresh ChatGPT/Codex setup defaults to
`openai/gpt-5.6-sol`. Re-running setup preserves an existing explicit model,
including `openai/gpt-5.5`. Select `openai/gpt-5.5` explicitly if the
account does not expose GPT-5.6.
Security note: if this agent will run tools or process webhook/hook
content, prefer the strongest latest-generation model available and keep
tool policy strict - weaker or older tiers are easier to prompt-inject.

View File

@@ -30,7 +30,7 @@ import type { CodexSource } from "./source.js";
import type { resolveCodexMigrationTargets } from "./targets.js";
const OPENAI_PROVIDER_ID = "openai";
const OPENAI_CODEX_DEFAULT_MODEL = "openai/gpt-5.5";
const OPENAI_CODEX_DEFAULT_MODEL = "openai/gpt-5.6-sol";
const CODEX_IMPORT_DISPLAY_NAME = "Codex import";
const CODEX_REASON_AUTH_NOT_SELECTED = "auth credential migration not selected";
const CODEX_REASON_AUTH_PROFILE_EXISTS = "auth profile exists";

View File

@@ -405,9 +405,11 @@ describe("buildCodexMigrationProvider", () => {
}),
);
expect(configState.agents?.defaults?.models?.["openai/gpt-5.4-mini"]).toEqual({});
expect(configState.agents?.defaults?.models?.["openai/gpt-5.5"]).toEqual({});
expect(configState.agents?.defaults?.models?.["openai/gpt-5.6-sol"]).toEqual({});
expect(configState.agents?.defaults?.model).toEqual({
fallbacks: [],
primary: "openai/gpt-5.5",
primary: "openai/gpt-5.6-sol",
});
});
@@ -820,9 +822,11 @@ describe("buildCodexMigrationProvider", () => {
details: expect.objectContaining({
path: ["agents", "defaults"],
value: expect.objectContaining({
model: { primary: "openai/gpt-5.5" },
model: { primary: "openai/gpt-5.6-sol" },
models: expect.objectContaining({
"openai/gpt-5.4-mini": {},
"openai/gpt-5.5": {},
"openai/gpt-5.6-sol": {},
}),
}),
}),

View File

@@ -24,6 +24,10 @@ import {
type OpenClawConfig,
type ProviderAuthResult,
} from "openclaw/plugin-sdk/provider-auth";
import {
applyAgentDefaultModelPrimary,
resolveAgentModelPrimaryValue,
} from "openclaw/plugin-sdk/provider-onboard";
import {
applyAuthProfileConfigWithConflictCheck,
hasAuthProfileConfigConflict,
@@ -43,7 +47,7 @@ import type { HermesSource } from "./source.js";
import type { PlannedTargets } from "./targets.js";
const OPENAI_PROVIDER_ID = "openai";
const OPENAI_DEFAULT_MODEL = "openai/gpt-5.5";
const OPENAI_CODEX_DEFAULT_MODEL = "openai/gpt-5.6-sol";
const HERMES_AUTH_DISPLAY_NAME = "Hermes import";
type AgentDefaultModelConfigs = NonNullable<
@@ -172,7 +176,7 @@ function buildAuthResult(
});
return buildOauthProviderAuthResult({
providerId: OPENAI_PROVIDER_ID,
defaultModel: OPENAI_DEFAULT_MODEL,
defaultModel: OPENAI_CODEX_DEFAULT_MODEL,
access: candidate.access,
refresh: candidate.refresh,
expires: resolveOpenAICodexAccessTokenExpiry(candidate.access),
@@ -188,7 +192,7 @@ function readProviderAuthModelConfigs(result: ProviderAuthResult): AgentDefaultM
if (isRecord(models)) {
return { ...models };
}
const defaultModel = readString(result.defaultModel) ?? OPENAI_DEFAULT_MODEL;
const defaultModel = readString(result.defaultModel) ?? OPENAI_CODEX_DEFAULT_MODEL;
return { [defaultModel]: {} };
}
@@ -503,7 +507,10 @@ export async function applyAuthItem(
ctx,
profile: configProfile,
applyConfigPatch(config) {
return applyOAuthModelConfigsToConfig(config, profile.result);
const next = applyOAuthModelConfigsToConfig(config, profile.result);
return resolveAgentModelPrimaryValue(next.agents?.defaults?.model) === undefined
? applyAgentDefaultModelPrimary(next, OPENAI_CODEX_DEFAULT_MODEL)
: next;
},
});
if (configResult === "conflict") {

View File

@@ -807,6 +807,10 @@ describe("Hermes migration secret items", () => {
refresh: "openai-refresh-token",
}),
);
expect(config.agents?.defaults?.model).toEqual({
primary: "openai/gpt-5.6-sol",
});
expect(config.agents?.defaults?.models?.["openai/gpt-5.6-sol"]).toEqual({});
});
it("does not apply a planned OpenCode OpenAI OAuth credential after the source token changes", async () => {
@@ -957,6 +961,10 @@ describe("Hermes migration secret items", () => {
agents: {
defaults: {
workspace: workspaceDir,
model: {
primary: "anthropic/claude-opus-4-8",
fallbacks: ["openai/gpt-5.5"],
},
},
},
} as OpenClawConfig;
@@ -1026,5 +1034,9 @@ describe("Hermes migration secret items", () => {
email: sharedEmail,
}),
);
expect(config.agents?.defaults?.model).toEqual({
primary: "anthropic/claude-opus-4-8",
fallbacks: ["openai/gpt-5.5"],
});
});
});

View File

@@ -23,15 +23,71 @@ describe("openai default models", () => {
expect(next.agents?.defaults?.models?.[OPENAI_DEFAULT_MODEL]?.alias).toBe("My GPT");
});
it("does not move the GPT alias from an existing model", () => {
const next = applyOpenAIProviderConfig({
agents: {
defaults: {
models: {
"openai/gpt-5.5": { alias: "GPT" },
"custom/model": { alias: "Custom" },
},
},
},
});
expect(next.agents?.defaults?.models).toEqual({
"openai/gpt-5.5": { alias: "GPT" },
"custom/model": { alias: "Custom" },
[OPENAI_DEFAULT_MODEL]: {},
});
});
it("does not duplicate a case-insensitive custom GPT alias", () => {
const next = applyOpenAIProviderConfig({
agents: {
defaults: {
models: {
"custom/model": { alias: "gPt" },
},
},
},
});
expect(next.agents?.defaults?.models?.["custom/model"]?.alias).toBe("gPt");
expect(next.agents?.defaults?.models?.[OPENAI_DEFAULT_MODEL]).toEqual({});
});
it("sets the default model when it is unset", () => {
const next = applyOpenAIConfig({});
expect(next.agents?.defaults?.model).toEqual({ primary: OPENAI_DEFAULT_MODEL });
});
it("overrides model.primary while preserving fallbacks", () => {
it("preserves an explicit primary and its fallbacks", () => {
const next = applyOpenAIConfig({
agents: { defaults: { model: { primary: "anthropic/claude-opus-4-6", fallbacks: [] } } },
} as OpenClawConfig);
expect(next.agents?.defaults?.model).toEqual({ primary: OPENAI_DEFAULT_MODEL, fallbacks: [] });
expect(next.agents?.defaults?.model).toEqual({
primary: "anthropic/claude-opus-4-6",
fallbacks: [],
});
expect(next.agents?.defaults?.models).toEqual({
"anthropic/claude-opus-4-6": {},
[OPENAI_DEFAULT_MODEL]: { alias: "GPT" },
});
});
it("fills a missing object-form primary while preserving fallbacks", () => {
const next = applyOpenAIConfig({
agents: { defaults: { model: { fallbacks: ["anthropic/claude-opus-4-6"] } } },
} as OpenClawConfig);
expect(next.agents?.defaults?.model).toEqual({
primary: OPENAI_DEFAULT_MODEL,
fallbacks: ["anthropic/claude-opus-4-6"],
});
expect(next.agents?.defaults?.models).toEqual({
"anthropic/claude-opus-4-6": {},
[OPENAI_DEFAULT_MODEL]: { alias: "GPT" },
});
});
});

View File

@@ -2,11 +2,12 @@
import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard";
import {
applyAgentDefaultModelPrimary,
resolveAgentModelPrimaryValue,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
export const OPENAI_DEFAULT_MODEL = "openai/gpt-5.5";
export const OPENAI_CODEX_DEFAULT_MODEL = OPENAI_DEFAULT_MODEL;
export const OPENAI_DEFAULT_MODEL = "openai/gpt-5.6";
export const OPENAI_CODEX_DEFAULT_MODEL = "openai/gpt-5.6-sol";
export const OPENAI_DEFAULT_IMAGE_MODEL = "gpt-image-2";
export const OPENAI_DEFAULT_TTS_MODEL = "gpt-4o-mini-tts";
export const OPENAI_DEFAULT_TTS_VOICE = "alloy";
@@ -14,14 +15,29 @@ export const OPENAI_DEFAULT_AUDIO_TRANSCRIPTION_MODEL = "gpt-4o-transcribe";
export const OPENAI_DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
export function applyOpenAIProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const next = ensureModelAllowlistEntry({
const configuredModel = cfg.agents?.defaults?.model;
const configuredRefs = [
resolveAgentModelPrimaryValue(configuredModel),
...(typeof configuredModel === "object" ? (configuredModel.fallbacks ?? []) : []),
].filter((ref): ref is string => typeof ref === "string" && ref.trim().length > 0);
const withConfiguredRefs = configuredRefs.reduce(
(next, modelRef) => ensureModelAllowlistEntry({ cfg: next, modelRef }),
cfg,
);
const next = ensureModelAllowlistEntry({
cfg: withConfiguredRefs,
modelRef: OPENAI_DEFAULT_MODEL,
});
const models = { ...next.agents?.defaults?.models };
const gptAliasClaimed = Object.entries(models).some(
([modelRef, model]) =>
modelRef !== OPENAI_DEFAULT_MODEL && model?.alias?.trim().toLowerCase() === "gpt",
);
models[OPENAI_DEFAULT_MODEL] = {
...models[OPENAI_DEFAULT_MODEL],
alias: models[OPENAI_DEFAULT_MODEL]?.alias ?? "GPT",
...(models[OPENAI_DEFAULT_MODEL]?.alias === undefined && !gptAliasClaimed
? { alias: "GPT" }
: {}),
};
return {
@@ -37,5 +53,8 @@ export function applyOpenAIProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
}
export function applyOpenAIConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(applyOpenAIProviderConfig(cfg), OPENAI_DEFAULT_MODEL);
const next = applyOpenAIProviderConfig(cfg);
return resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model) === undefined
? applyAgentDefaultModelPrimary(next, OPENAI_DEFAULT_MODEL)
: next;
}

View File

@@ -69,6 +69,10 @@ describe("OpenAI provider Codex transport hooks", () => {
refresh: "refresh-token",
},
});
expect(result?.defaultModel).toBe("openai/gpt-5.6-sol");
expect(result?.configPatch?.agents?.defaults?.models).toEqual({
"openai/gpt-5.6-sol": {},
});
});
it("routes Codex-backed OpenAI models through the Codex Responses transport", () => {
@@ -115,6 +119,19 @@ describe("OpenAI provider Codex transport hooks", () => {
},
);
it("does not invent a bare GPT-5.6 alias for the Codex transport", () => {
const provider = buildOpenAIProvider();
const model = provider.resolveDynamicModel?.({
provider: "openai",
modelId: "gpt-5.6",
authProfileMode: "oauth",
modelRegistry: { find: () => null },
} as never);
expect(model).toBeUndefined();
});
it.each([
{ name: "fills a missing map", thinkingLevelMap: undefined, expectedOff: null },
{ name: "preserves explicit overrides", thinkingLevelMap: { off: "low" }, expectedOff: "low" },

View File

@@ -1,6 +1,7 @@
import { afterEach, describe, expect, it, vi } from "vitest";
const FUTURE_DEFAULT_MODEL = "openai/gpt-next-default";
const FUTURE_API_DEFAULT_MODEL = "openai/gpt-next-default";
const FUTURE_CODEX_DEFAULT_MODEL = "openai/gpt-next-codex-default";
describe("OpenAI missing auth message", () => {
afterEach(() => {
@@ -14,8 +15,8 @@ describe("OpenAI missing auth message", () => {
const actual = await importOriginal<typeof import("./default-models.js")>();
return {
...actual,
OPENAI_DEFAULT_MODEL: FUTURE_DEFAULT_MODEL,
OPENAI_CODEX_DEFAULT_MODEL: FUTURE_DEFAULT_MODEL,
OPENAI_DEFAULT_MODEL: FUTURE_API_DEFAULT_MODEL,
OPENAI_CODEX_DEFAULT_MODEL: FUTURE_CODEX_DEFAULT_MODEL,
};
});
@@ -27,6 +28,9 @@ describe("OpenAI missing auth message", () => {
listProfileIds: (providerId: string) => (providerId === "openai" ? ["openai:codex"] : []),
} as never);
expect(message).toContain(`Use ${FUTURE_DEFAULT_MODEL} with the ChatGPT/Codex OAuth profile`);
expect(message).toContain(
`Use ${FUTURE_CODEX_DEFAULT_MODEL} with the ChatGPT/Codex OAuth profile`,
);
expect(message).not.toContain(FUTURE_API_DEFAULT_MODEL);
});
});

View File

@@ -6,7 +6,7 @@ import {
type LiveModelCatalogFetchGuard,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { OPENAI_DEFAULT_MODEL } from "./default-models.js";
import { OPENAI_CODEX_DEFAULT_MODEL, OPENAI_DEFAULT_MODEL } from "./default-models.js";
import {
buildOpenAICodexLiveProviderConfig,
buildOpenAILiveProviderConfig,
@@ -162,6 +162,46 @@ describe("buildOpenAIProvider", () => {
groupLabel: "OpenAI",
groupHint: "ChatGPT/Codex sign-in or API key",
});
expect(apiKey?.starterModel).toBe("openai/gpt-5.6");
});
it("preserves existing model selection during non-interactive API key setup", async () => {
const provider = buildOpenAIProvider();
const apiKey = provider.auth.find((method) => method.id === "api-key");
if (!apiKey?.runNonInteractive) {
throw new Error("expected OpenAI API key non-interactive auth");
}
const primaryModel = "anthropic/claude-opus-4-6";
const fallbackModel = "openai/gpt-5.5";
const next = await apiKey.runNonInteractive({
config: {
agents: {
defaults: {
model: { primary: primaryModel, fallbacks: [fallbackModel] },
models: {
[primaryModel]: { alias: "Primary" },
[fallbackModel]: { alias: "Fallback" },
},
},
},
},
opts: {},
env: {},
runtime: {},
resolveApiKey: async () => ({ key: "sk-test", source: "profile" }),
toApiKeyCredential: () => null,
} as never);
expect(next?.agents?.defaults?.model).toEqual({
primary: primaryModel,
fallbacks: [fallbackModel],
});
expect(next?.agents?.defaults?.models).toMatchObject({
[primaryModel]: { alias: "Primary" },
[fallbackModel]: { alias: "Fallback" },
[OPENAI_DEFAULT_MODEL]: { alias: "GPT" },
});
});
it("marks the OpenAI manifest catalog as runtime-discovered", () => {
@@ -197,18 +237,28 @@ describe("buildOpenAIProvider", () => {
}
const gpt55 = result.providers.openai?.models.find((model) => model.id === "gpt-5.5");
const gpt56Models = result.providers.openai?.models.filter((model) =>
model.id.startsWith("gpt-5.6-"),
model.id.startsWith("gpt-5.6"),
);
expect(gpt55?.mediaInput).toEqual({
image: { maxSidePx: 6000, preferredSidePx: 2048, tokenMode: "detail" },
});
expect(gpt56Models?.map((model) => model.id)).toEqual([
"gpt-5.6",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
]);
expect(gpt56Models?.map((model) => model.thinkingLevelMap?.off)).toEqual([null, null, null]);
expect(OPENAI_DEFAULT_MODEL).toBe("openai/gpt-5.5");
expect(gpt56Models?.map((model) => model.contextWindow)).toEqual([
1_050_000, 1_050_000, 1_050_000, 1_050_000,
]);
expect(gpt56Models?.map((model) => model.thinkingLevelMap?.off)).toEqual([
"none",
"none",
"none",
"none",
]);
expect(OPENAI_DEFAULT_MODEL).toBe("openai/gpt-5.6");
expect(OPENAI_CODEX_DEFAULT_MODEL).toBe("openai/gpt-5.6-sol");
});
it("scopes the OpenAI API-key catalog to the OpenAI provider id", async () => {
@@ -295,6 +345,7 @@ describe("buildOpenAIProvider", () => {
const fetchGuard: LiveModelCatalogFetchGuard = vi.fn(async () => ({
response: Response.json({
data: [
{ id: "gpt-5.6", object: "model" },
{ id: "gpt-5.5", object: "model" },
{ id: "not-in-manifest", object: "model" },
],
@@ -309,6 +360,7 @@ describe("buildOpenAIProvider", () => {
});
expect(provider.apiKey).toBe("sk-openai");
expect(provider.models.map((model) => model.id)).toContain("gpt-5.6");
expect(provider.models.map((model) => model.id)).toContain("gpt-5.5");
expect(provider.models.map((model) => model.id)).not.toContain("not-in-manifest");
const fetchParams = vi.mocked(fetchGuard).mock.calls[0]?.[0];
@@ -358,8 +410,8 @@ describe("buildOpenAIProvider", () => {
Response.json({
models: [
{
slug: "gpt-5.6",
display_name: "GPT-5.6",
slug: "gpt-5.6-sol",
display_name: "GPT-5.6 Sol",
visibility: "list",
supported_reasoning_levels: [
{ effort: "low", description: "low" },
@@ -386,6 +438,12 @@ describe("buildOpenAIProvider", () => {
max_context_window: 1_000_000,
max_output_tokens: 128_000,
},
{
slug: "gpt-5.6-terra",
display_name: "GPT-5.6 Terra",
visibility: "list",
supported_reasoning_levels: [{ effort: "medium", description: "medium" }],
},
{
slug: "gpt-5.3-codex-spark",
display_name: "GPT-5.3 Codex Spark",
@@ -443,17 +501,22 @@ describe("buildOpenAIProvider", () => {
expect(openai?.auth).toBe("oauth");
expect(openai?.baseUrl).toBe("https://chatgpt.com/backend-api/codex");
expect(openai?.models.map((model) => model.id)).toEqual([
"gpt-5.6",
"gpt-5.6-sol",
"gpt-5.5",
"gpt-5.6-terra",
"gpt-5.3-codex-spark",
]);
expect(openai?.models.find((model) => model.id === "gpt-5.6")).toMatchObject({
expect(openai?.models.find((model) => model.id === "gpt-5.6-sol")).toMatchObject({
contextWindow: 372_000,
compat: {
supportedReasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
},
thinkingLevelMap: { off: null, xhigh: "xhigh", max: "max" },
});
expect(openai?.models.find((model) => model.id === "gpt-5.6-terra")).toMatchObject({
contextWindow: 372_000,
contextTokens: 372_000,
});
expect(openai?.models.find((model) => model.id === "gpt-5.3-codex-spark")).toMatchObject({
name: "GPT-5.3 Codex Spark",
reasoning: true,
@@ -615,6 +678,21 @@ describe("buildOpenAIProvider", () => {
expect(provider.auth).toBe("oauth");
expect(provider.baseUrl).toBe("https://chatgpt.com/backend-api/codex");
expect(provider.models.length).toBeGreaterThan(0);
expect(provider.models.map((model) => model.id)).not.toContain("gpt-5.6");
expect(provider.models.find((model) => model.id === "gpt-5.6-sol")).toMatchObject({
contextWindow: 372_000,
contextTokens: 372_000,
thinkingLevelMap: { off: null },
compat: { supportedReasoningEfforts: ["low", "medium", "high", "xhigh", "max"] },
});
expect(provider.models.find((model) => model.id === "gpt-5.6-terra")).toMatchObject({
contextWindow: 372_000,
contextTokens: 372_000,
});
expect(provider.models.find((model) => model.id === "gpt-5.6-luna")).toMatchObject({
contextWindow: 372_000,
contextTokens: 372_000,
});
expect(provider.models.map((model) => model.id)).toContain("gpt-5.5");
expect(release).toHaveBeenCalledOnce();
});
@@ -1029,22 +1107,27 @@ describe("buildOpenAIProvider", () => {
});
it.each([
{
id: "gpt-5.6",
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
thinkingLevelMap: { off: "none", xhigh: "xhigh", max: "max" },
},
{
id: "gpt-5.6-sol",
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
thinkingLevelMap: { off: null, xhigh: "xhigh", max: "max" },
thinkingLevelMap: { off: "none", xhigh: "xhigh", max: "max" },
},
{
id: "gpt-5.6-terra",
cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 3.125 },
thinkingLevelMap: { off: null, xhigh: "xhigh", max: "max" },
thinkingLevelMap: { off: "none", xhigh: "xhigh", max: "max" },
},
{
id: "gpt-5.6-luna",
cost: { input: 1, output: 6, cacheRead: 0.1, cacheWrite: 1.25 },
thinkingLevelMap: { off: null, xhigh: "xhigh", max: "max" },
thinkingLevelMap: { off: "none", xhigh: "xhigh", max: "max" },
},
])("resolves $id locally with preview metadata", ({ id, cost, thinkingLevelMap }) => {
])("resolves $id locally with direct API metadata", ({ id, cost, thinkingLevelMap }) => {
const provider = buildOpenAIProvider();
const model = provider.resolveDynamicModel?.({
@@ -1075,8 +1158,8 @@ describe("buildOpenAIProvider", () => {
provider: "openai",
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
contextWindow: 372_000,
contextTokens: 372_000,
contextWindow: 1_050_000,
contextTokens: 1_050_000,
maxTokens: 128_000,
cost,
thinkingLevelMap,

View File

@@ -29,7 +29,11 @@ import {
isOpenAICodexBaseUrl,
resolveOpenAIDefaultBaseUrl,
} from "./base-url.js";
import { applyOpenAIConfig, OPENAI_DEFAULT_MODEL } from "./default-models.js";
import {
applyOpenAIConfig,
OPENAI_CODEX_DEFAULT_MODEL,
OPENAI_DEFAULT_MODEL,
} from "./default-models.js";
import {
buildOpenAIChatGPTAuthMethods,
buildOpenAICodexProviderHooks,
@@ -50,6 +54,7 @@ const OPENAI_CODEX_MODELS_ENDPOINT = `${OPENAI_CODEX_RESPONSES_BASE_URL}/models?
const OPENAI_MODELS_CACHE_TTL_MS = 60_000;
const OPENAI_CODEX_MODELS_CACHE_TTL_MS = 60_000;
const OPENAI_CHAT_LATEST_MODEL_ID = "chat-latest";
const OPENAI_GPT_56_MODEL_ID = "gpt-5.6";
const OPENAI_GPT_56_SOL_MODEL_ID = "gpt-5.6-sol";
const OPENAI_GPT_56_TERRA_MODEL_ID = "gpt-5.6-terra";
const OPENAI_GPT_56_LUNA_MODEL_ID = "gpt-5.6-luna";
@@ -60,7 +65,8 @@ const OPENAI_GPT_54_PRO_MODEL_ID = "gpt-5.4-pro";
const OPENAI_GPT_54_MINI_MODEL_ID = "gpt-5.4-mini";
const OPENAI_GPT_54_NANO_MODEL_ID = "gpt-5.4-nano";
const OPENAI_GPT_53_CODEX_SPARK_MODEL_ID = "gpt-5.3-codex-spark";
const OPENAI_GPT_56_CONTEXT_TOKENS = 372_000;
const OPENAI_GPT_56_DIRECT_CONTEXT_TOKENS = 1_050_000;
const OPENAI_CODEX_GPT_56_CONTEXT_TOKENS = 372_000;
const OPENAI_GPT_55_CONTEXT_WINDOW = 1_000_000;
const OPENAI_GPT_55_CONTEXT_TOKENS = 272_000;
const OPENAI_GPT_55_PRO_CONTEXT_TOKENS = 1_000_000;
@@ -121,12 +127,13 @@ const OPENAI_CHAT_LATEST_TEMPLATE_MODEL_IDS = [
] as const;
const OPENAI_GPT_56_TEMPLATE_MODEL_IDS = [OPENAI_GPT_55_MODEL_ID] as const;
const OPENAI_GPT_56_THINKING_LEVEL_MAP = {
off: null,
off: "none",
xhigh: "xhigh",
max: "max",
} as const;
const OPENAI_MODERN_MODEL_IDS = [
OPENAI_CHAT_LATEST_MODEL_ID,
OPENAI_GPT_56_MODEL_ID,
OPENAI_GPT_56_SOL_MODEL_ID,
OPENAI_GPT_56_TERRA_MODEL_ID,
OPENAI_GPT_56_LUNA_MODEL_ID,
@@ -315,11 +322,45 @@ function resolveCodexModelInput(
return input.size > 0 ? [...input] : (fallback?.input ?? ["text", "image"]);
}
function normalizeOpenAICodexCatalogModel(
model: ModelDefinitionConfig,
): ModelDefinitionConfig | undefined {
const modelId = normalizeLowercaseStringOrEmpty(model.id);
if (modelId === OPENAI_GPT_56_MODEL_ID) {
return undefined;
}
if (
modelId === OPENAI_GPT_56_SOL_MODEL_ID ||
modelId === OPENAI_GPT_56_TERRA_MODEL_ID ||
modelId === OPENAI_GPT_56_LUNA_MODEL_ID
) {
const supportedReasoningEfforts = model.compat?.supportedReasoningEfforts?.filter(
(effort) => effort !== "none",
);
return {
...model,
contextWindow: OPENAI_CODEX_GPT_56_CONTEXT_TOKENS,
contextTokens: OPENAI_CODEX_GPT_56_CONTEXT_TOKENS,
thinkingLevelMap: { ...model.thinkingLevelMap, off: null },
...(model.compat
? {
compat: {
...model.compat,
...(supportedReasoningEfforts ? { supportedReasoningEfforts } : {}),
},
}
: {}),
};
}
return model;
}
function resolveCodexModelFallback(modelId: string): ModelDefinitionConfig | undefined {
return OPENAI_MANIFEST_PROVIDER.models.find(
(model) =>
normalizeLowercaseStringOrEmpty(model.id) === normalizeLowercaseStringOrEmpty(modelId),
const fallbackModel = OPENAI_MANIFEST_PROVIDER.models.find(
(candidate) =>
normalizeLowercaseStringOrEmpty(candidate.id) === normalizeLowercaseStringOrEmpty(modelId),
);
return fallbackModel ? normalizeOpenAICodexCatalogModel(fallbackModel) : undefined;
}
function buildOpenAICodexModelFromLiveRow(row: unknown): ModelDefinitionConfig | undefined {
@@ -386,7 +427,10 @@ function buildOpenAICodexStaticProviderConfig(): ModelProviderConfig {
baseUrl: OPENAI_CODEX_RESPONSES_BASE_URL,
api: "openai-chatgpt-responses",
auth: "oauth",
models: OPENAI_MANIFEST_PROVIDER.models,
models: OPENAI_MANIFEST_PROVIDER.models.flatMap((model) => {
const normalized = normalizeOpenAICodexCatalogModel(model);
return normalized ? [normalized] : [];
}),
};
}
@@ -593,13 +637,14 @@ function resolveOpenAIGptForwardCompatModel(ctx: ProviderResolveDynamicModelCont
maxTokens: OPENAI_GPT_54_MAX_TOKENS,
};
} else if (
lower === OPENAI_GPT_56_MODEL_ID ||
lower === OPENAI_GPT_56_SOL_MODEL_ID ||
lower === OPENAI_GPT_56_TERRA_MODEL_ID ||
lower === OPENAI_GPT_56_LUNA_MODEL_ID
) {
templateIds = OPENAI_GPT_56_TEMPLATE_MODEL_IDS;
const cost =
lower === OPENAI_GPT_56_SOL_MODEL_ID
lower === OPENAI_GPT_56_MODEL_ID || lower === OPENAI_GPT_56_SOL_MODEL_ID
? OPENAI_GPT_56_SOL_COST
: lower === OPENAI_GPT_56_TERRA_MODEL_ID
? OPENAI_GPT_56_TERRA_COST
@@ -611,8 +656,8 @@ function resolveOpenAIGptForwardCompatModel(ctx: ProviderResolveDynamicModelCont
reasoning: true,
input: ["text", "image"],
cost,
contextWindow: OPENAI_GPT_56_CONTEXT_TOKENS,
contextTokens: OPENAI_GPT_56_CONTEXT_TOKENS,
contextWindow: OPENAI_GPT_56_DIRECT_CONTEXT_TOKENS,
contextTokens: OPENAI_GPT_56_DIRECT_CONTEXT_TOKENS,
maxTokens: OPENAI_GPT_54_MAX_TOKENS,
thinkingLevelMap: OPENAI_GPT_56_THINKING_LEVEL_MAP,
};
@@ -736,6 +781,7 @@ export function buildOpenAIProvider(): ProviderPlugin {
promptMessage: "Enter OpenAI API key",
profileId: "openai:api-key",
defaultModel: OPENAI_DEFAULT_MODEL,
preserveExistingPrimary: true,
expectedProviders: ["openai"],
applyConfig: (cfg) => applyOpenAIConfig(cfg),
wizard: {
@@ -871,7 +917,7 @@ export function buildOpenAIProvider(): ProviderPlugin {
if (ctx.listProfileIds(PROVIDER_ID).length === 0) {
return undefined;
}
return `No API key found for provider "openai". You are authenticated with OpenAI ChatGPT/Codex OAuth. Use ${OPENAI_DEFAULT_MODEL} with the ChatGPT/Codex OAuth profile, or set OPENAI_API_KEY for direct OpenAI API access.`;
return `No API key found for provider "openai". You are authenticated with OpenAI ChatGPT/Codex OAuth. Use ${OPENAI_CODEX_DEFAULT_MODEL} with the ChatGPT/Codex OAuth profile, or set OPENAI_API_KEY for direct OpenAI API access.`;
},
matchesContextOverflowError: ({ errorMessage }) =>
/content_filter.*(?:prompt|input).*(?:too long|exceed)/i.test(errorMessage),

View File

@@ -105,18 +105,32 @@
"maxTokens": 128000,
"cost": { "input": 30, "output": 180, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gpt-5.6",
"name": "GPT-5.6",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 1050000,
"maxTokens": 128000,
"cost": { "input": 5, "output": 30, "cacheRead": 0.5, "cacheWrite": 6.25 },
"thinkingLevelMap": { "off": "none", "xhigh": "xhigh", "max": "max" },
"compat": {
"supportsReasoningEffort": true,
"supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh", "max"]
}
},
{
"id": "gpt-5.6-sol",
"name": "GPT-5.6 Sol",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 372000,
"contextWindow": 1050000,
"maxTokens": 128000,
"cost": { "input": 5, "output": 30, "cacheRead": 0.5, "cacheWrite": 6.25 },
"thinkingLevelMap": { "off": null, "xhigh": "xhigh", "max": "max" },
"thinkingLevelMap": { "off": "none", "xhigh": "xhigh", "max": "max" },
"compat": {
"supportsReasoningEffort": true,
"supportedReasoningEfforts": ["low", "medium", "high", "xhigh", "max"]
"supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh", "max"]
}
},
{
@@ -124,13 +138,13 @@
"name": "GPT-5.6 Terra",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 372000,
"contextWindow": 1050000,
"maxTokens": 128000,
"cost": { "input": 2.5, "output": 15, "cacheRead": 0.25, "cacheWrite": 3.125 },
"thinkingLevelMap": { "off": null, "xhigh": "xhigh", "max": "max" },
"thinkingLevelMap": { "off": "none", "xhigh": "xhigh", "max": "max" },
"compat": {
"supportsReasoningEffort": true,
"supportedReasoningEfforts": ["low", "medium", "high", "xhigh", "max"]
"supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh", "max"]
}
},
{
@@ -138,13 +152,13 @@
"name": "GPT-5.6 Luna",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 372000,
"contextWindow": 1050000,
"maxTokens": 128000,
"cost": { "input": 1, "output": 6, "cacheRead": 0.1, "cacheWrite": 1.25 },
"thinkingLevelMap": { "off": null, "xhigh": "xhigh", "max": "max" },
"thinkingLevelMap": { "off": "none", "xhigh": "xhigh", "max": "max" },
"compat": {
"supportsReasoningEffort": true,
"supportedReasoningEfforts": ["low", "medium", "high", "xhigh", "max"]
"supportedReasoningEfforts": ["none", "low", "medium", "high", "xhigh", "max"]
}
},
{

View File

@@ -118,7 +118,7 @@ export function describeOpenAIProviderCatalogContract() {
const { openaiProvider } = await contractDepsPromise;
expectCodexMissingAuthHint(
(params) => openaiProvider.buildMissingAuthMessage?.(params.context) ?? undefined,
"openai/gpt-5.5",
"openai/gpt-5.6-sol",
);
});

View File

@@ -37,7 +37,7 @@ describe("qa model selection runtime", () => {
resolveEnvApiKey.mockReturnValue({ apiKey: "sk-test" });
expect(resolveQaPreferredLiveModel()).toBeUndefined();
expect(defaultQaRuntimeModelForMode("live-frontier")).toBe("openai/gpt-5.5");
expect(defaultQaRuntimeModelForMode("live-frontier")).toBe("openai/gpt-5.6");
expect(loadAuthProfileStoreForRuntime).not.toHaveBeenCalled();
});
@@ -71,7 +71,7 @@ describe("qa model selection runtime", () => {
});
expect(resolveQaPreferredLiveModel()).toBeUndefined();
expect(defaultQaRuntimeModelForMode("live-frontier")).toBe("openai/gpt-5.5");
expect(defaultQaRuntimeModelForMode("live-frontier")).toBe("openai/gpt-5.6");
});
it("leaves mock defaults unchanged", () => {

View File

@@ -31,7 +31,7 @@ function isClaudeOpusModel(modelRef: string) {
export const liveFrontierProviderDefinition: QaProviderDefinition = {
mode: "live-frontier",
kind: "live",
defaultModel: (options) => options?.preferredLiveModel ?? "openai/gpt-5.5",
defaultModel: (options) => options?.preferredLiveModel ?? "openai/gpt-5.6",
defaultImageGenerationProviderIds: ["openai"],
defaultImageGenerationModel: ({ modelProviderIds }) =>
modelProviderIds.includes("openai") ? "openai/gpt-image-1" : null,

View File

@@ -7,7 +7,7 @@ import {
} from "./openai-reasoning-effort.js";
describe("OpenAI reasoning effort support", () => {
it("preserves max for the GPT-5.6 series", () => {
it("preserves disabled and max effort for the GPT-5.6 series", () => {
const sol = { provider: "openai", id: "gpt-5.6-sol" };
const terra = { provider: "openai", id: "gpt-5.6-terra" };
const luna = { provider: "openai", id: "gpt-5.6-luna" };
@@ -15,6 +15,9 @@ describe("OpenAI reasoning effort support", () => {
expect(resolveOpenAIReasoningEffortForModel({ model: sol, effort: "max" })).toBe("max");
expect(resolveOpenAIReasoningEffortForModel({ model: terra, effort: "max" })).toBe("max");
expect(resolveOpenAIReasoningEffortForModel({ model: luna, effort: "max" })).toBe("max");
expect(resolveOpenAIReasoningEffortForModel({ model: sol, effort: "off" })).toBe("none");
expect(resolveOpenAIReasoningEffortForModel({ model: terra, effort: "none" })).toBe("none");
expect(resolveOpenAIReasoningEffortForModel({ model: luna, effort: "off" })).toBe("none");
});
it.each([

View File

@@ -32,7 +32,7 @@ type OpenAIReasoningModel = {
const GPT_5_REASONING_EFFORTS = ["minimal", "low", "medium", "high"] as const;
const GPT_51_REASONING_EFFORTS = ["none", "low", "medium", "high"] as const;
const GPT_52_REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh"] as const;
const GPT_56_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const;
const GPT_56_REASONING_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"] as const;
const GPT_CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"] as const;
const GPT_PRO_REASONING_EFFORTS = ["medium", "high", "xhigh"] as const;
const GPT_5_PRO_REASONING_EFFORTS = ["high"] as const;
@@ -167,6 +167,9 @@ export function resolveOpenAIReasoningEffortForModel(params: {
if (supported.includes(normalized as OpenAIApiReasoningEffort)) {
return normalized as OpenAIApiReasoningEffort;
}
if (requested === "off" && supported.includes("none")) {
return "none";
}
if (isDisabledReasoningEffort(requested) || isDisabledReasoningEffort(normalized)) {
return undefined;
}

View File

@@ -91,8 +91,8 @@ if (!hasHarness) {
}
const primaryModel = cfg.agents?.defaults?.model?.primary;
if (primaryModel !== "openai/gpt-5.5") {
throw new Error(`expected OpenAI onboarding model openai/gpt-5.5, got ${primaryModel}`);
if (primaryModel !== "openai/gpt-5.6") {
throw new Error(`expected OpenAI onboarding model openai/gpt-5.6, got ${primaryModel}`);
}
const providerRuntime = cfg.models?.providers?.openai?.agentRuntime?.id;
if (providerRuntime && providerRuntime !== "codex") {

View File

@@ -28,7 +28,7 @@ const HIGH_SIGNAL_LIVE_MODEL_PRIORITY = [
"deepseek/deepseek-v4-flash",
"deepseek/deepseek-v4-pro",
"minimax/minimax-m3",
"openai/gpt-5.5",
"openai/gpt-5.6",
"openrouter/openai/gpt-5.2-chat",
"openrouter/minimax/minimax-m2.7",
"opencode-go/glm-5",
@@ -56,6 +56,12 @@ export const DEFAULT_HIGH_SIGNAL_LIVE_MODEL_LIMIT = HIGH_SIGNAL_LIVE_MODEL_PRIOR
/** Default cap for the small-model live smoke lane. */
export const DEFAULT_SMALL_LIVE_MODEL_LIMIT = SMALL_LIVE_MODEL_PRIORITY.length;
const DEFAULT_HIGH_SIGNAL_LIVE_EXCLUDED_PROVIDERS = new Set(["codex", "codex-cli"]);
const DIRECT_OPENAI_HIGH_SIGNAL_LIVE_MODEL_IDS = new Set([
"gpt-5.6",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
]);
const CURATED_ONLY_HIGH_SIGNAL_LIVE_PROVIDERS = new Set([
"fireworks",
"google",
@@ -157,7 +163,7 @@ function isUnsupportedOpenAiLiveModelRef(provider: string, id: string): boolean
}
const modelName = normalizeLowercaseStringOrEmpty(id).split("/").pop() ?? "";
if (provider === "openai") {
return modelName !== "gpt-5.5";
return !DIRECT_OPENAI_HIGH_SIGNAL_LIVE_MODEL_IDS.has(modelName);
}
return !modelName.startsWith("gpt-5.2");
}

View File

@@ -4,7 +4,10 @@
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveVisibleModelCatalog } from "./model-catalog-visibility.js";
import {
isCodexRoutableOpenAIPlatformCatalogEntry,
resolveVisibleModelCatalog,
} from "./model-catalog-visibility.js";
import type { ModelCatalogEntry } from "./model-catalog.types.js";
const normalizeProviderModelIdWithRuntimeMock = vi.hoisted(() => vi.fn());
@@ -19,6 +22,20 @@ describe("resolveVisibleModelCatalog", () => {
normalizeProviderModelIdWithRuntimeMock.mockReset();
});
it("recognizes exact GPT-5.6 Codex ids without treating the API alias as routable", () => {
const entry = (id: string): ModelCatalogEntry => ({
provider: "openai",
id,
name: id,
api: "openai-responses",
});
expect(isCodexRoutableOpenAIPlatformCatalogEntry(entry("gpt-5.6"))).toBe(false);
expect(isCodexRoutableOpenAIPlatformCatalogEntry(entry("gpt-5.6-sol"))).toBe(true);
expect(isCodexRoutableOpenAIPlatformCatalogEntry(entry("gpt-5.6-terra"))).toBe(true);
expect(isCodexRoutableOpenAIPlatformCatalogEntry(entry("gpt-5.6-luna"))).toBe(true);
});
it("can use static auth checks for gateway read-only model lists", async () => {
const authChecker = vi.fn((provider: string) => provider === "openai");
const catalog: ModelCatalogEntry[] = [

View File

@@ -20,6 +20,9 @@ type ProviderAuthChecker = (provider: string, modelApi?: string) => boolean | Pr
const OPENAI_PROVIDER_ID = "openai";
const OPENAI_CODEX_RESPONSES_API = "openai-chatgpt-responses";
const OPENAI_CODEX_ROUTABLE_MODEL_IDS = new Set([
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gpt-5.5",
"gpt-5.5-pro",
"gpt-5.4",

View File

@@ -393,9 +393,18 @@ describe("isModernModelRef", () => {
it("includes plugin-advertised modern models", () => {
providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation(({ provider, context }) =>
provider === "openai" &&
["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini", "gpt-5.4-nano"].includes(
context.modelId,
)
[
"gpt-5.6",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gpt-5.5",
"gpt-5.5-pro",
"gpt-5.4",
"gpt-5.4-pro",
"gpt-5.4-mini",
"gpt-5.4-nano",
].includes(context.modelId)
? true
: provider === "openai" &&
["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini"].includes(
@@ -409,6 +418,8 @@ describe("isModernModelRef", () => {
: undefined,
);
expect(isModernModelRef({ provider: "openai", id: "gpt-5.6" })).toBe(true);
expect(isModernModelRef({ provider: "openai", id: "gpt-5.6-sol" })).toBe(true);
expect(isModernModelRef({ provider: "openai", id: "gpt-5.5" })).toBe(true);
expect(isModernModelRef({ provider: "openai", id: "gpt-5.5-pro" })).toBe(true);
expect(isModernModelRef({ provider: "openai", id: "gpt-5.4" })).toBe(true);
@@ -495,7 +506,7 @@ describe("isHighSignalLiveModelRef", () => {
);
});
it("keeps only the current direct OpenAI-family model in the default live matrix", () => {
it("keeps only the current direct OpenAI-family models in the default live matrix", () => {
providerRuntimeMocks.resolveProviderModernModelRef.mockReturnValue(true);
expect(isHighSignalLiveModelRef({ provider: "openrouter", id: "openai/gpt-3.5-turbo" })).toBe(
@@ -510,7 +521,10 @@ describe("isHighSignalLiveModelRef", () => {
expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5" })).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5.1" })).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5.4" })).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5.5" })).toBe(true);
expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5.5" })).toBe(false);
for (const id of ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]) {
expect(isHighSignalLiveModelRef({ provider: "openai", id })).toBe(true);
}
expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5.2-codex" })).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5.2-chat-latest" })).toBe(false);
expect(isHighSignalLiveModelRef({ provider: "openrouter", id: "openai/gpt-5.1-chat" })).toBe(
@@ -680,7 +694,7 @@ describe("isPrioritizedHighSignalLiveModelRef", () => {
{ provider: "deepseek", id: "deepseek-v4-flash" },
{ provider: "deepseek", id: "deepseek-v4-pro" },
{ provider: "minimax", id: "minimax-m3" },
{ provider: "openai", id: "gpt-5.5" },
{ provider: "openai", id: "gpt-5.6" },
{ provider: "openrouter", id: "openai/gpt-5.2-chat" },
{ provider: "openrouter", id: "minimax/minimax-m2.7" },
{ provider: "opencode-go", id: "glm-5" },

View File

@@ -17,6 +17,11 @@ function probeDeps(found: Record<string, boolean>) {
}
describe("detectInferenceBackends", () => {
it("uses route-specific GPT-5.6 defaults for direct API and Codex", () => {
expect(OPENAI_API_DEFAULT_MODEL_REF).toBe("openai/gpt-5.6");
expect(CODEX_APP_SERVER_DEFAULT_MODEL_REF).toBe("openai/gpt-5.6-sol");
});
it("returns nothing when no backend exists", async () => {
const candidates = await detectInferenceBackends({
env: {},

View File

@@ -5,7 +5,6 @@ import {
readGeminiCliCredentialsCached,
} from "../agents/cli-credentials.js";
// Inference backend detection shared by onboarding bootstrap and Crestodian setup.
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { probeLocalCommand, type LocalCommandProbe } from "../crestodian/probes.js";
@@ -16,10 +15,10 @@ import { probeLocalCommand, type LocalCommandProbe } from "../crestodian/probes.
* asking the user anything. The ladder order is a documented contract
* (docs/cli/crestodian.md "Setup bootstrap") — change docs when changing it.
*/
export const OPENAI_API_DEFAULT_MODEL_REF = `${DEFAULT_PROVIDER}/${DEFAULT_MODEL}`;
export const OPENAI_API_DEFAULT_MODEL_REF = "openai/gpt-5.6";
export const ANTHROPIC_API_DEFAULT_MODEL_REF = "anthropic/claude-opus-4-8";
export const CLAUDE_CLI_DEFAULT_MODEL_REF = "claude-cli/claude-opus-4-8";
export const CODEX_APP_SERVER_DEFAULT_MODEL_REF = OPENAI_API_DEFAULT_MODEL_REF;
export const CODEX_APP_SERVER_DEFAULT_MODEL_REF = "openai/gpt-5.6-sol";
export const GEMINI_CLI_DEFAULT_MODEL_REF = "google-gemini-cli/gemini-3.1-pro-preview";
export type InferenceBackendKind =

View File

@@ -211,7 +211,7 @@ describe("Crestodian assistant", () => {
const codexAppServerEntries = requireRecord(codexAppServerPlugins.entries);
const codexAppServerCodexEntry = requireRecord(codexAppServerEntries.codex);
expect(codexAppServerDefaults.workspace).toBe("/tmp/workspace");
expect(codexAppServerModel.primary).toBe("openai/gpt-5.5");
expect(codexAppServerModel.primary).toBe("openai/gpt-5.6-sol");
expect(codexAppServerCodexEntry.enabled).toBe(true);
});
@@ -246,12 +246,12 @@ describe("Crestodian assistant", () => {
}
expect(result.command).toBe("gateway status");
expect(result.reply).toBe("Codex planner online.");
expect(result.modelLabel).toBe("openai/gpt-5.5 via codex");
expect(result.modelLabel).toBe("openai/gpt-5.6-sol via codex");
expect(runEmbeddedAgent).toHaveBeenCalledTimes(1);
const firstEmbeddedCall = firstMockArg(runEmbeddedAgent);
expect(firstEmbeddedCall.provider).toBe("openai");
expect(firstEmbeddedCall.model).toBe("gpt-5.5");
expect(firstEmbeddedCall.model).toBe("gpt-5.6-sol");
expect(firstEmbeddedCall.agentHarnessId).toBe("codex");
expect(firstEmbeddedCall.disableTools).toBe(true);
expect(firstEmbeddedCall.toolsAllow).toEqual([]);
@@ -262,7 +262,7 @@ describe("Crestodian assistant", () => {
const embeddedPlugins = requireRecord(embeddedConfig.plugins);
const embeddedEntries = requireRecord(embeddedPlugins.entries);
const embeddedCodexEntry = requireRecord(embeddedEntries.codex);
expect(embeddedModel.primary).toBe("openai/gpt-5.5");
expect(embeddedModel.primary).toBe("openai/gpt-5.6-sol");
expect(embeddedCodexEntry.enabled).toBe(true);
});

View File

@@ -737,7 +737,7 @@ describe("parseCrestodianOperation", () => {
const { runtime, lines } = createCrestodianTestRuntime();
const applySetup = vi.fn(async () => ({
configPath: path.join(tempDir, "openclaw.json"),
lines: ["Workspace: /tmp/work", "Default model: openai/gpt-5.5"],
lines: ["Workspace: /tmp/work", "Default model: openai/gpt-5.6"],
}));
const plan = await executeCrestodianOperation(
@@ -748,7 +748,7 @@ describe("parseCrestodianOperation", () => {
expectRecordFields(plan as unknown as Record<string, unknown>, {
applied: false,
});
expect(lines.join("\n")).toContain("Model choice: openai/gpt-5.5 (OPENAI_API_KEY).");
expect(lines.join("\n")).toContain("Model choice: openai/gpt-5.6 (OPENAI_API_KEY).");
expect(applySetup).not.toHaveBeenCalled();
const result = await executeCrestodianOperation(
@@ -765,7 +765,7 @@ describe("parseCrestodianOperation", () => {
expect(lines.join("\n")).toContain("[crestodian] done: crestodian.setup");
expect(applySetup).toHaveBeenCalledWith({
workspace: "/tmp/work",
model: "openai/gpt-5.5",
model: "openai/gpt-5.6",
surface: "cli",
runtime,
});
@@ -775,12 +775,12 @@ describe("parseCrestodianOperation", () => {
audit,
{
operation: "crestodian.setup",
summary: "Bootstrapped setup with openai/gpt-5.5",
summary: "Bootstrapped setup with openai/gpt-5.6",
},
{
rescue: true,
workspace: "/tmp/work",
model: "openai/gpt-5.5",
model: "openai/gpt-5.6",
modelSource: "OPENAI_API_KEY",
},
);

View File

@@ -751,9 +751,10 @@ describe("activateSetupInference", () => {
async (params: { transform: (config: OpenClawConfig) => { nextConfig: OpenClawConfig } }) => {
const transformed = params.transform(persistedConfig).nextConfig;
const configuredRuntime =
transformed.agents?.defaults?.models?.["openai/gpt-5.5"]?.agentRuntime?.id ??
transformed.agents?.list?.find((agent) => agent.id === "ops")?.models?.["openai/gpt-5.5"]
?.agentRuntime?.id;
transformed.agents?.defaults?.models?.["openai/gpt-5.6-sol"]?.agentRuntime?.id ??
transformed.agents?.list?.find((agent) => agent.id === "ops")?.models?.[
"openai/gpt-5.6-sol"
]?.agentRuntime?.id;
events.push(
configuredRuntime === "codex" ? "persist-plugin-config" : "persist-plugin-install",
);
@@ -798,10 +799,13 @@ describe("activateSetupInference", () => {
expect.objectContaining({
id: "ops",
model: {
primary: "openai/gpt-5.5",
primary: "openai/gpt-5.6-sol",
fallbacks: ["google/gemini-3.1-pro-preview"],
},
models: { "openai/gpt-5.5": { agentRuntime: { id: "codex" } } },
models: {
"openai/gpt-5.5": { agentRuntime: { id: "openclaw" } },
"openai/gpt-5.6-sol": { agentRuntime: { id: "codex" } },
},
}),
],
},
@@ -811,7 +815,7 @@ describe("activateSetupInference", () => {
},
},
}),
model: "openai/gpt-5.5",
model: "openai/gpt-5.6-sol",
agentId: "ops",
}),
);
@@ -852,10 +856,13 @@ describe("activateSetupInference", () => {
expect.objectContaining({
id: "ops",
model: {
primary: "openai/gpt-5.5",
primary: "openai/gpt-5.6-sol",
fallbacks: ["google/gemini-3.1-pro-preview"],
},
models: { "openai/gpt-5.5": { agentRuntime: { id: "codex" } } },
models: {
"openai/gpt-5.5": { agentRuntime: { id: "openclaw" } },
"openai/gpt-5.6-sol": { agentRuntime: { id: "codex" } },
},
}),
],
},
@@ -879,10 +886,13 @@ describe("activateSetupInference", () => {
expect.objectContaining({
id: "ops",
model: {
primary: "openai/gpt-5.5",
primary: "openai/gpt-5.6-sol",
fallbacks: ["google/gemini-3.1-pro-preview"],
},
models: { "openai/gpt-5.5": { agentRuntime: { id: "codex" } } },
models: {
"openai/gpt-5.5": { agentRuntime: { id: "openclaw" } },
"openai/gpt-5.6-sol": { agentRuntime: { id: "codex" } },
},
}),
],
},

View File

@@ -2,6 +2,7 @@
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { normalizeUniqueStringEntries } from "@openclaw/normalization-core/string-normalization";
import { upsertAuthProfileWithLock } from "../agents/auth-profiles/profiles.js";
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { SecretInput } from "../config/types.secrets.js";
import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js";
@@ -26,6 +27,7 @@ type ProviderApiKeyAuthMethodOptions = {
profileIds?: string[];
allowProfile?: boolean;
defaultModel?: string;
preserveExistingPrimary?: boolean;
expectedProviders?: string[];
metadata?: Record<string, string>;
noteMessage?: string;
@@ -74,6 +76,7 @@ async function applyApiKeyConfig(params: {
providerId: string;
profileIds: string[];
defaultModel?: string;
preserveExistingPrimary?: boolean;
applyConfig?: (cfg: OpenClawConfig) => OpenClawConfig;
}) {
const { applyAuthProfileConfig, applyPrimaryModel } = await loadProviderApiKeyAuthRuntime();
@@ -88,7 +91,16 @@ async function applyApiKeyConfig(params: {
if (params.applyConfig) {
next = params.applyConfig(next);
}
return params.defaultModel ? applyPrimaryModel(next, params.defaultModel) : next;
if (!params.defaultModel) {
return next;
}
if (
params.preserveExistingPrimary === true &&
resolveAgentModelPrimaryValue(next.agents?.defaults?.model) !== undefined
) {
return next;
}
return applyPrimaryModel(next, params.defaultModel);
}
/** Creates a provider auth method that captures, stores, and configures API-key credentials. */
@@ -204,6 +216,7 @@ export function createProviderApiKeyAuthMethod(
providerId: params.providerId,
profileIds,
defaultModel: params.defaultModel,
preserveExistingPrimary: params.preserveExistingPrimary,
applyConfig: params.applyConfig,
});
},

View File

@@ -31,6 +31,28 @@ describe("applyProviderAuthConfigPatch", () => {
expect(next.agents?.defaults?.model).toEqual(base.agents.defaults.model);
});
it("keeps configured primary and fallback refs in a newly introduced allowlist", () => {
const next = applyProviderAuthConfigPatch(
{
agents: {
defaults: {
model: {
primary: "openai/gpt-5.5",
fallbacks: ["anthropic/claude-opus-4-6"],
},
},
},
},
{ agents: { defaults: { models: { "openai/gpt-5.6-sol": {} } } } },
);
expect(next.agents?.defaults?.models).toEqual({
"openai/gpt-5.6-sol": {},
"openai/gpt-5.5": {},
"anthropic/claude-opus-4-6": {},
});
});
it("replaces the allowlist only when replaceDefaultModels is set", () => {
const patch = {
agents: {
@@ -176,6 +198,7 @@ describe("applyProviderAuthConfigPatch", () => {
alias: "gemini",
params: { thinking: "high", maxTokens: 12_000 },
},
"openai/gpt-5.5": {},
});
});
@@ -327,6 +350,10 @@ describe("applyDefaultModel", () => {
expect(next.agents?.defaults?.model).toEqual({
primary: "anthropic/claude-opus-4-6",
});
expect(next.agents?.defaults?.models).toEqual({
"openrouter/auto": {},
"anthropic/claude-opus-4-6": {},
});
});
it("normalizes a preserved retired Google Gemini primary", () => {
@@ -363,6 +390,11 @@ describe("applyDefaultModel", () => {
primary: "anthropic/claude-opus-4-6",
fallbacks: ["openai/gpt-5.4"],
});
expect(next.agents?.defaults?.models).toEqual({
"openrouter/auto": {},
"anthropic/claude-opus-4-6": {},
"openai/gpt-5.4": {},
});
});
it("adds the model to the allowlist", () => {

View File

@@ -276,6 +276,38 @@ function normalizeConfigModelRefsForWrite(
};
}
/** Keep a restrictive model allowlist consistent with the configured primary and fallbacks. */
function ensureConfiguredDefaultModelsAllowed(cfg: OpenClawConfig): OpenClawConfig {
const defaults = cfg.agents?.defaults;
if (!defaults?.models) {
return cfg;
}
const model = defaults.model;
const refs = [
typeof model === "string" ? model : model?.primary,
...(typeof model === "object" ? (model.fallbacks ?? []) : []),
].filter((ref): ref is string => typeof ref === "string" && ref.trim().length > 0);
const models = normalizeAgentModelMapForConfig(defaults.models);
let changed = false;
for (const ref of refs) {
const normalizedRef = normalizeAgentModelRefForConfig(ref);
if (!models[normalizedRef]) {
models[normalizedRef] = {};
changed = true;
}
}
if (!changed) {
return cfg;
}
return {
...cfg,
agents: {
...cfg.agents,
defaults: { ...defaults, models },
},
};
}
export function applyProviderAuthConfigPatch(
cfg: OpenClawConfig,
patch: unknown,
@@ -291,7 +323,7 @@ export function applyProviderAuthConfigPatch(
providerConfigNormalizer,
);
if (!options?.replaceDefaultModels || !isPlainRecord(patch)) {
return merged;
return ensureConfiguredDefaultModelsAllowed(merged);
}
const patchModels = (patch.agents as { defaults?: { models?: unknown } } | undefined)?.defaults
@@ -369,7 +401,7 @@ export function applyDefaultModel(
normalizeAgentModelRefForConfig(fallback),
)
: undefined;
return {
return ensureConfiguredDefaultModelsAllowed({
...cfg,
agents: {
...cfg.agents,
@@ -385,5 +417,5 @@ export function applyDefaultModel(
},
},
},
};
});
}

View File

@@ -222,7 +222,7 @@ function createCodexInstallFixture(root: string) {
writeFileSync(codexBin, "#!/usr/bin/env node\n", { mode: 0o755 });
chmodSync(codexBin, 0o755);
writeJson(path.join(stateDir, "openclaw.json"), {
agents: { defaults: { model: { primary: "openai/gpt-5.5" } } },
agents: { defaults: { model: { primary: "openai/gpt-5.6" } } },
models: { providers: { openai: { agentRuntime: { id: "codex" } } } },
plugins: {
installs: {