mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-19 07:42:04 +00:00
Remove codex-cli backend and migrate to Codex runtime
Remove the bundled codex-cli backend, migrate legacy codex-cli refs and runtime pins to the Codex app-server runtime, and update live/backend workflow coverage for the supported CLI lanes.
This commit is contained in:
parent
66b98b7294
commit
a0f35574d0
38 changed files with 531 additions and 377 deletions
|
|
@ -2154,27 +2154,11 @@ jobs:
|
|||
fi
|
||||
case "${{ matrix.suite_id }}" in
|
||||
live-cli-backend-docker)
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
|
||||
# Keep the release-blocking CI lane on Codex API-key auth. The
|
||||
# staged auth-file path remains supported for local maintainer
|
||||
# reruns, but it can hang on stale subscription/session state in
|
||||
# an otherwise healthy release run.
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV"
|
||||
# Replace the staged config.toml with a minimal CI-safe config so
|
||||
# the repo stays trusted for MCP/tool use without inheriting
|
||||
# maintainer-local provider/profile overrides that do not exist
|
||||
# inside CI.
|
||||
# Codex's workspace-write sandbox relies on user namespaces that
|
||||
# this Docker lane does not provide, so run Codex unsandboxed
|
||||
# inside the already-isolated container to keep MCP cron/tool
|
||||
# execution representative instead of failing on nested sandbox
|
||||
# setup.
|
||||
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
|
||||
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV"
|
||||
;;
|
||||
live-codex-harness-docker)
|
||||
# Keep CI on the API-key path for now. The staged Codex auth secret
|
||||
|
|
@ -2395,14 +2379,11 @@ jobs:
|
|||
fi
|
||||
case "${{ matrix.suite_id }}" in
|
||||
live-cli-backend-docker)
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV"
|
||||
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
|
||||
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV"
|
||||
;;
|
||||
live-codex-harness-docker)
|
||||
echo "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key" >> "$GITHUB_ENV"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Maintainer tooling: fail CI when pull requests add package patch files or pnpm patched dependencies, preserving the upstream-and-bump dependency workflow.
|
||||
- Amazon Bedrock: externalize the Bedrock and Bedrock Mantle provider packages so core installs no longer pull AWS SDK dependencies unless those providers are installed.
|
||||
- Plugins: externalize Slack, OpenShell sandbox, and Anthropic Vertex so their runtime dependency cones install only when those plugins are installed.
|
||||
- Codex migration: remove the bundled `codex-cli` backend and repair legacy `codex-cli/*` model refs to the Codex app-server route on `openai/*`.
|
||||
- Control UI/WebChat: add a persisted auto-scroll mode selector so users can keep the current near-bottom behavior, always follow streaming output, or turn automatic streaming scroll off and use the New messages button manually. Fixes #7648 and #81287. Thanks @BunsDev.
|
||||
- ACP: add `acp.fallbacks` so ACP turns can try configured backup runtime backends when the primary backend is unavailable before any output is emitted. (#69542) Thanks @kaseonedge.
|
||||
- Gateway/startup: add owner-level startup trace attribution for auth, plugin loading, lookup counts, and plugin sidecar services. (#81738) Thanks @samzong.
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ order and tells you what it chose:
|
|||
- `OPENAI_API_KEY` -> `openai/gpt-5.5`
|
||||
- `ANTHROPIC_API_KEY` -> `anthropic/claude-opus-4-7`
|
||||
- Claude Code CLI -> `claude-cli/claude-opus-4-7`
|
||||
- Codex CLI -> `codex-cli/gpt-5.5`
|
||||
- Codex -> `openai/gpt-5.5` through the Codex app-server harness
|
||||
|
||||
If none are available, setup still writes the default workspace and leaves the
|
||||
model unset. Install or log into Codex/Claude Code, or expose
|
||||
|
|
@ -171,7 +171,6 @@ back to local runtimes already present on the machine:
|
|||
|
||||
- Claude Code CLI: `claude-cli/claude-opus-4-7`
|
||||
- Codex app-server harness: `openai/gpt-5.5`
|
||||
- Codex CLI: `codex-cli/gpt-5.5`
|
||||
|
||||
The model-assisted planner cannot mutate config directly. It must translate the
|
||||
request into one of Crestodian's typed commands, then the normal approval and
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ This is the agent-facing decision tree:
|
|||
`openai/<model>` with `openclaw doctor --fix`; doctor keeps the Codex auth
|
||||
route by adding provider/model-scoped `agentRuntime.id: "codex"` where the
|
||||
old model ref implied it.
|
||||
Legacy **`codex-cli/*` model refs** repair to the same `openai/<model>` Codex
|
||||
app-server route; OpenClaw no longer keeps a bundled Codex CLI backend.
|
||||
5. If the user explicitly says **ACP**, **acpx**, or **Codex ACP adapter**, use
|
||||
ACP with `runtime: "acp"` and `agentId: "codex"`.
|
||||
6. If the request is for **Claude Code, Gemini CLI, OpenCode, Cursor, Droid, or
|
||||
|
|
@ -180,6 +182,10 @@ Legacy refs such as `claude-cli/claude-opus-4-7` remain supported for
|
|||
compatibility, but new config should keep the provider/model canonical and put
|
||||
the execution backend in provider/model runtime policy.
|
||||
|
||||
Legacy `codex-cli/*` refs are different: doctor migrates them to `openai/*` so
|
||||
they run through the Codex app-server harness instead of preserving a Codex CLI
|
||||
backend.
|
||||
|
||||
`auto` mode is intentionally conservative for most providers. OpenAI agent
|
||||
models are the exception: unset runtime and `auto` both resolve to the Codex
|
||||
harness. Explicit PI runtime config remains an opt-in compatibility route for
|
||||
|
|
|
|||
|
|
@ -41,9 +41,9 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
|
|||
|
||||
</Accordion>
|
||||
<Accordion title="CLI runtimes">
|
||||
CLI runtimes use the same split: choose canonical model refs such as `anthropic/claude-*`, `google/gemini-*`, or `openai/gpt-*`, then set provider/model runtime policy to `claude-cli`, `google-gemini-cli`, or `codex-cli` when you want a local CLI backend.
|
||||
CLI runtimes use the same split: choose canonical model refs such as `anthropic/claude-*` or `google/gemini-*`, then set provider/model runtime policy to `claude-cli` or `google-gemini-cli` when you want a local CLI backend.
|
||||
|
||||
Legacy `claude-cli/*`, `google-gemini-cli/*`, and `codex-cli/*` refs migrate back to canonical provider refs with the runtime recorded separately.
|
||||
Legacy `claude-cli/*` and `google-gemini-cli/*` refs migrate back to canonical provider refs with the runtime recorded separately. Legacy `codex-cli/*` refs migrate to `openai/*` and use the Codex app-server route; OpenClaw no longer keeps a bundled Codex CLI backend.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
summary: "CLI backends: local AI CLI fallback with optional MCP tool bridge"
|
||||
read_when:
|
||||
- You want a reliable fallback when API providers fail
|
||||
- You are running Codex CLI or other local AI CLIs and want to reuse them
|
||||
- You are running local AI CLIs and want to reuse them
|
||||
- You want to understand the MCP loopback bridge for CLI backend tool access
|
||||
title: "CLI backends"
|
||||
---
|
||||
|
|
@ -31,11 +31,11 @@ thread/conversation binding, and persistent external coding sessions, use
|
|||
|
||||
## Beginner-friendly quick start
|
||||
|
||||
You can use Codex CLI **without any config** (the bundled OpenAI plugin
|
||||
You can use Claude Code CLI **without any config** (the bundled Anthropic plugin
|
||||
registers a default backend):
|
||||
|
||||
```bash
|
||||
openclaw agent --message "hi" --model codex-cli/gpt-5.5
|
||||
openclaw agent --message "hi" --model claude-cli/claude-sonnet-4-6
|
||||
```
|
||||
|
||||
If your gateway runs under launchd/systemd and PATH is minimal, add just the
|
||||
|
|
@ -46,8 +46,8 @@ command path:
|
|||
agents: {
|
||||
defaults: {
|
||||
cliBackends: {
|
||||
"codex-cli": {
|
||||
command: "/opt/homebrew/bin/codex",
|
||||
"claude-cli": {
|
||||
command: "/opt/homebrew/bin/claude",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -72,11 +72,11 @@ Add a CLI backend to your fallback list so it only runs when primary models fail
|
|||
defaults: {
|
||||
model: {
|
||||
primary: "anthropic/claude-opus-4-6",
|
||||
fallbacks: ["codex-cli/gpt-5.5"],
|
||||
fallbacks: ["claude-cli/claude-sonnet-4-6"],
|
||||
},
|
||||
models: {
|
||||
"anthropic/claude-opus-4-6": { alias: "Opus" },
|
||||
"codex-cli/gpt-5.5": {},
|
||||
"claude-cli/claude-sonnet-4-6": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -97,7 +97,7 @@ All CLI backends live under:
|
|||
agents.defaults.cliBackends
|
||||
```
|
||||
|
||||
Each entry is keyed by a **provider id** (e.g. `codex-cli`, `my-cli`).
|
||||
Each entry is keyed by a **provider id** (e.g. `claude-cli`, `my-cli`).
|
||||
The provider id becomes the left side of your model ref:
|
||||
|
||||
```
|
||||
|
|
@ -111,9 +111,6 @@ The provider id becomes the left side of your model ref:
|
|||
agents: {
|
||||
defaults: {
|
||||
cliBackends: {
|
||||
"codex-cli": {
|
||||
command: "/opt/homebrew/bin/codex",
|
||||
},
|
||||
"my-cli": {
|
||||
command: "my-cli",
|
||||
args: ["--json"],
|
||||
|
|
@ -149,7 +146,7 @@ The provider id becomes the left side of your model ref:
|
|||
|
||||
## How it works
|
||||
|
||||
1. **Selects a backend** based on the provider prefix (`codex-cli/...`).
|
||||
1. **Selects a backend** based on the provider prefix (`claude-cli/...`).
|
||||
2. **Builds a system prompt** using the same OpenClaw prompt + workspace context.
|
||||
3. **Executes the CLI** with a session id (if supported) so history stays consistent.
|
||||
The bundled `claude-cli` backend keeps a Claude stdio process alive per
|
||||
|
|
@ -164,12 +161,6 @@ told us OpenClaw-style Claude CLI usage is allowed again, so OpenClaw treats
|
|||
a new policy.
|
||||
</Note>
|
||||
|
||||
The bundled OpenAI `codex-cli` backend passes OpenClaw's system prompt through
|
||||
Codex's `model_instructions_file` config override (`-c
|
||||
model_instructions_file="..."`). Codex does not expose a Claude-style
|
||||
`--append-system-prompt` flag, so OpenClaw writes the assembled prompt to a
|
||||
temporary file for each fresh Codex CLI session.
|
||||
|
||||
The bundled Anthropic `claude-cli` backend receives the OpenClaw skills snapshot
|
||||
two ways: the compact OpenClaw skills catalog in the appended system prompt, and
|
||||
a temporary Claude Code plugin passed with `--plugin-dir`. The plugin contains
|
||||
|
|
@ -292,7 +283,7 @@ load local files from plain paths.
|
|||
- `output: "json"` (default) tries to parse JSON and extract text + session id.
|
||||
- For Gemini CLI JSON output, OpenClaw reads reply text from `response` and
|
||||
usage from `stats` when `usage` is missing or empty.
|
||||
- `output: "jsonl"` parses JSONL streams (for example Codex CLI `--json`) and extracts the final agent message plus session
|
||||
- `output: "jsonl"` parses JSONL streams and extracts the final agent message plus session
|
||||
identifiers when present.
|
||||
- `output: "text"` treats stdout as the final response.
|
||||
|
||||
|
|
@ -304,16 +295,19 @@ Input modes:
|
|||
|
||||
## Defaults (plugin-owned)
|
||||
|
||||
The bundled OpenAI plugin also registers a default for `codex-cli`:
|
||||
Bundled CLI backend defaults live with their owning plugin. For example,
|
||||
Anthropic owns `claude-cli` and Google owns `google-gemini-cli`. OpenAI Codex
|
||||
agent runs use the Codex app-server harness through `openai/*`; OpenClaw no
|
||||
longer registers a bundled `codex-cli` backend.
|
||||
|
||||
- `command: "codex"`
|
||||
- `args: ["exec","--json","--color","never","--sandbox","workspace-write","--skip-git-repo-check"]`
|
||||
- `resumeArgs: ["exec","resume","{sessionId}","-c","sandbox_mode=\"workspace-write\"","--skip-git-repo-check"]`
|
||||
The bundled Anthropic plugin registers a default for `claude-cli`:
|
||||
|
||||
- `command: "claude"`
|
||||
- `args: ["-p","--output-format","stream-json","--include-partial-messages","--verbose", ...]`
|
||||
- `output: "jsonl"`
|
||||
- `resumeOutput: "text"`
|
||||
- `input: "stdin"`
|
||||
- `modelArg: "--model"`
|
||||
- `imageArg: "--image"`
|
||||
- `sessionMode: "existing"`
|
||||
- `sessionMode: "always"`
|
||||
|
||||
The bundled Google plugin also registers a default for `google-gemini-cli`:
|
||||
|
||||
|
|
@ -383,9 +377,6 @@ opt into a generated MCP config overlay with `bundleMcp: true`.
|
|||
Current bundled behavior:
|
||||
|
||||
- `claude-cli`: generated strict MCP config file
|
||||
- `codex-cli`: inline config overrides for `mcp_servers`; the generated
|
||||
OpenClaw loopback server is marked with Codex's per-server tool approval mode
|
||||
so MCP calls cannot stall on local approval prompts
|
||||
- `google-gemini-cli`: generated Gemini system settings file
|
||||
|
||||
When bundle MCP is enabled, OpenClaw:
|
||||
|
|
@ -414,16 +405,13 @@ children and Streamable HTTP/SSE streams do not outlive the run.
|
|||
- **Streaming is backend-specific.** Some backends stream JSONL; others buffer
|
||||
until exit.
|
||||
- **Structured outputs** depend on the CLI's JSON format.
|
||||
- **Codex CLI sessions** resume via text output (no JSONL), which is less
|
||||
structured than the initial `--json` run. OpenClaw sessions still work
|
||||
normally.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **CLI not found**: set `command` to a full path.
|
||||
- **Wrong model name**: use `modelAliases` to map `provider/model` → CLI model.
|
||||
- **No session continuity**: ensure `sessionArg` is set and `sessionMode` is not
|
||||
`none` (Codex CLI currently cannot resume with JSON output).
|
||||
`none`.
|
||||
- **Images ignored**: set `imageArg` (and verify CLI supports file paths).
|
||||
|
||||
## Related
|
||||
|
|
|
|||
|
|
@ -461,8 +461,8 @@ Optional CLI backends for text-only fallback runs (no tool calls). Useful as a b
|
|||
agents: {
|
||||
defaults: {
|
||||
cliBackends: {
|
||||
"codex-cli": {
|
||||
command: "/opt/homebrew/bin/codex",
|
||||
"claude-cli": {
|
||||
command: "/opt/homebrew/bin/claude",
|
||||
},
|
||||
"my-cli": {
|
||||
command: "my-cli",
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ openclaw models list --json
|
|||
|
||||
</Tip>
|
||||
|
||||
## Live: CLI backend smoke (Claude, Codex, Gemini, or other local CLIs)
|
||||
## Live: CLI backend smoke (Claude, Gemini, or other local CLIs)
|
||||
|
||||
- Test: `src/gateway/gateway-cli-backend.live.test.ts`
|
||||
- Goal: validate the Gateway + agent pipeline using a local CLI backend, without touching your default config.
|
||||
|
|
@ -145,9 +145,9 @@ openclaw models list --json
|
|||
- Default provider/model: `claude-cli/claude-sonnet-4-6`
|
||||
- Command/args/image behavior come from the owning CLI backend plugin metadata.
|
||||
- Overrides (optional):
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.5"`
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_COMMAND="/full/path/to/codex"`
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_ARGS='["exec","--json","--color","never","--sandbox","read-only","--skip-git-repo-check"]'`
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-sonnet-4-6"`
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_COMMAND="/full/path/to/claude"`
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_ARGS='["-p","--output-format","json"]'`
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=1` to send a real image attachment (paths are injected into the prompt). Docker recipes default this off unless explicitly requested.
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_ARG="--image"` to pass image file paths as CLI args instead of prompt injection.
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE="repeat"` (or `"list"`) to control how image args are passed when `IMAGE_ARG` is set.
|
||||
|
|
@ -158,8 +158,8 @@ openclaw models list --json
|
|||
Example:
|
||||
|
||||
```bash
|
||||
OPENCLAW_LIVE_CLI_BACKEND=1 \
|
||||
OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.5" \
|
||||
OPENCLAW_LIVE_CLI_BACKEND=1 \
|
||||
OPENCLAW_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-sonnet-4-6" \
|
||||
pnpm test:live src/gateway/gateway-cli-backend.live.test.ts
|
||||
```
|
||||
|
||||
|
|
@ -186,7 +186,6 @@ Single-provider Docker recipes:
|
|||
```bash
|
||||
pnpm test:docker:live-cli-backend:claude
|
||||
pnpm test:docker:live-cli-backend:claude-subscription
|
||||
pnpm test:docker:live-cli-backend:codex
|
||||
pnpm test:docker:live-cli-backend:gemini
|
||||
```
|
||||
|
||||
|
|
@ -194,9 +193,9 @@ Notes:
|
|||
|
||||
- The Docker runner lives at `scripts/test-live-cli-backend-docker.sh`.
|
||||
- It runs the live CLI-backend smoke inside the repo Docker image as the non-root `node` user.
|
||||
- It resolves CLI smoke metadata from the owning extension, then installs the matching Linux CLI package (`@anthropic-ai/claude-code`, `@openai/codex`, or `@google/gemini-cli`) into a cached writable prefix at `OPENCLAW_DOCKER_CLI_TOOLS_DIR` (default: `~/.cache/openclaw/docker-cli-tools`).
|
||||
- It resolves CLI smoke metadata from the owning extension, then installs the matching Linux CLI package (`@anthropic-ai/claude-code` or `@google/gemini-cli`) into a cached writable prefix at `OPENCLAW_DOCKER_CLI_TOOLS_DIR` (default: `~/.cache/openclaw/docker-cli-tools`).
|
||||
- `pnpm test:docker:live-cli-backend:claude-subscription` requires portable Claude Code subscription OAuth through either `~/.claude/.credentials.json` with `claudeAiOauth.subscriptionType` or `CLAUDE_CODE_OAUTH_TOKEN` from `claude setup-token`. It first proves direct `claude -p` in Docker, then runs two Gateway CLI-backend turns without preserving Anthropic API-key env vars. This subscription lane disables the Claude MCP/tool and image probes by default because Claude currently routes third-party app usage through extra-usage billing instead of normal subscription plan limits.
|
||||
- The live CLI-backend smoke now exercises the same end-to-end flow for Claude, Codex, and Gemini: text turn, image classification turn, then MCP `cron` tool call verified through the gateway CLI.
|
||||
- The live CLI-backend smoke now exercises the same end-to-end flow for Claude and Gemini: text turn, image classification turn, then MCP `cron` tool call verified through the gateway CLI.
|
||||
- Claude's default smoke also patches the session from Sonnet to Opus and verifies the resumed session still remembers an earlier note.
|
||||
|
||||
## Live: APNs HTTP/2 proxy reachability
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ Prefer the narrowest metadata that already describes ownership. Use
|
|||
when those fields express the relationship. Use `activation` for extra planner
|
||||
hints that cannot be represented by those ownership fields.
|
||||
Use top-level `cliBackends` for CLI runtime aliases such as `claude-cli`,
|
||||
`codex-cli`, or `google-gemini-cli`; `activation.onAgentHarnesses` is only for
|
||||
`my-cli`, or `google-gemini-cli`; `activation.onAgentHarnesses` is only for
|
||||
embedded agent harness ids that do not already have an ownership field.
|
||||
|
||||
This block is metadata only. It does not register runtime behavior, and it does
|
||||
|
|
|
|||
|
|
@ -320,9 +320,9 @@ descriptor-backed placeholders for parse-time lazy loading.
|
|||
### CLI backend registration
|
||||
|
||||
`api.registerCliBackend(...)` lets a plugin own the default config for a local
|
||||
AI CLI backend such as `codex-cli`.
|
||||
AI CLI backend such as `claude-cli` or `my-cli`.
|
||||
|
||||
- The backend `id` becomes the provider prefix in model refs like `codex-cli/gpt-5`.
|
||||
- The backend `id` becomes the provider prefix in model refs like `my-cli/gpt-5`.
|
||||
- The backend `config` uses the same shape as `agents.defaults.cliBackends.<id>`.
|
||||
- User config still wins. OpenClaw merges `agents.defaults.cliBackends.<id>` over the
|
||||
plugin default before running the CLI.
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ PI runtime config remains available as an opt-in compatibility route. When PI is
|
|||
explicitly selected with an `openai-codex` auth profile, OpenClaw keeps the
|
||||
public model ref as `openai/*` and routes PI internally through the legacy
|
||||
Codex-auth transport. Run `openclaw doctor --fix` to repair stale
|
||||
`openai-codex/*` model refs or old PI session pins that do not come from
|
||||
`openai-codex/*`, `codex-cli/*`, or old PI session pins that do not come from
|
||||
explicit runtime config.
|
||||
</Note>
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ explicit runtime config.
|
|||
| ------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| Chat / Responses | `openai/<model>` model provider | Yes |
|
||||
| Codex subscription models | `openai/<model>` with `openai-codex` OAuth | Yes |
|
||||
| Legacy Codex model refs | `openai-codex/<model>` | Repaired by doctor to `openai/<model>` |
|
||||
| Legacy Codex model refs | `openai-codex/<model>` or `codex-cli/<model>` | Repaired by doctor to `openai/<model>` |
|
||||
| Codex app-server harness | `openai/<model>` with omitted runtime or provider/model `agentRuntime.id: codex` | Yes |
|
||||
| Server-side web search | Native OpenAI Responses tool | Yes, when web search is enabled and no provider pinned |
|
||||
| Images | `image_generate` | Yes |
|
||||
|
|
@ -245,6 +245,7 @@ Choose your preferred auth method and follow the setup steps.
|
|||
| `openai/gpt-5.5` | omitted / provider/model `agentRuntime.id: "codex"` | Native Codex app-server harness | Codex sign-in or ordered `openai` auth profile |
|
||||
| `openai/gpt-5.5` | provider/model `agentRuntime.id: "pi"` | PI embedded runtime with internal Codex-auth transport | Selected `openai-codex` profile |
|
||||
| `openai-codex/gpt-5.5` | repaired by doctor | Legacy route rewritten to `openai/gpt-5.5` | Existing `openai-codex` profile |
|
||||
| `codex-cli/gpt-5.5` | repaired by doctor | Legacy CLI route rewritten to `openai/gpt-5.5` | Codex app-server auth |
|
||||
|
||||
<Warning>
|
||||
Do not configure older `openai-codex/gpt-5.1*`, `openai-codex/gpt-5.2*`, or
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ title: "Tests"
|
|||
- `pnpm test:docker:all`: Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into `/app`, then runs Docker smoke lanes with `OPENCLAW_SKIP_DOCKER_BUILD=1` through a weighted scheduler. The bare image (`OPENCLAW_DOCKER_E2E_BARE_IMAGE`) is used for installer/update/plugin-dependency lanes; those lanes mount the prebuilt tarball instead of using copied repo sources. The functional image (`OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`) is used for normal built-app functionality lanes. `scripts/package-openclaw-for-docker.mjs` is the single local/CI package packer and validates the tarball plus `dist/postinstall-inventory.json` before Docker consumes it. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in `scripts/lib/docker-e2e-plan.mjs`; `scripts/test-docker-all.mjs` executes the selected plan. `node scripts/test-docker-all.mjs --plan-json` emits the scheduler-owned CI plan for selected lanes, image kinds, package/live-image needs, state scenarios, and credential checks without building or running Docker. `OPENCLAW_DOCKER_ALL_PARALLELISM=<n>` controls process slots and defaults to 10; `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM=<n>` controls the provider-sensitive tail pool and defaults to 10. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; provider caps default to one heavy lane per provider via `OPENCLAW_DOCKER_ALL_LIVE_CLAUDE_LIMIT=4`, `OPENCLAW_DOCKER_ALL_LIVE_CODEX_LIMIT=4`, and `OPENCLAW_DOCKER_ALL_LIVE_GEMINI_LIMIT=4`. Use `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` for larger hosts. If one lane exceeds the effective weight or resource cap on a low-parallelism host, it can still start from an empty pool and will run alone until it releases capacity. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=<ms>`. The runner preflights Docker by default, cleans stale OpenClaw E2E containers, emits active-lane status every 30 seconds, shares provider CLI tool caches between compatible lanes, retries transient live-provider failures once by default (`OPENCLAW_DOCKER_ALL_LIVE_RETRIES=<n>`), and stores lane timings in `.artifacts/docker-tests/lane-timings.json` for longest-first ordering on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the lane manifest without running Docker, `OPENCLAW_DOCKER_ALL_STATUS_INTERVAL_MS=<ms>` to tune status output, or `OPENCLAW_DOCKER_ALL_TIMINGS=0` to disable timing reuse. Use `OPENCLAW_DOCKER_ALL_LIVE_MODE=skip` for deterministic/local lanes only or `OPENCLAW_DOCKER_ALL_LIVE_MODE=only` for live-provider lanes only; package aliases are `pnpm test:docker:local:all` and `pnpm test:docker:live:all`. Live-only mode merges main and tail live lanes into one longest-first pool so provider buckets can pack Claude, Codex, and Gemini work together. The runner stops scheduling new pooled lanes after the first failure unless `OPENCLAW_DOCKER_ALL_FAIL_FAST=0` is set, and each lane has a 120-minute fallback timeout overrideable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. CLI backend Docker setup commands have their own timeout via `OPENCLAW_LIVE_CLI_BACKEND_SETUP_TIMEOUT_SECONDS` (default 180). Per-lane logs, `summary.json`, `failures.json`, and phase timings are written under `.artifacts/docker-tests/<run-id>/`; use `pnpm test:docker:timings <summary.json>` to inspect slow lanes and `pnpm test:docker:rerun <run-id|summary.json|failures.json>` to print cheap targeted rerun commands.
|
||||
- `pnpm test:docker:browser-cdp-snapshot`: Builds a Chromium-backed source E2E container, starts raw CDP plus an isolated Gateway, runs `browser doctor --deep`, and verifies CDP role snapshots include link URLs, cursor-promoted clickables, iframe refs, and frame metadata.
|
||||
- `pnpm test:docker:skill-install`: Installs the packed OpenClaw tarball in a bare Docker runner, disables `skills.install.allowUploadedArchives`, resolves a current skill slug from live ClawHub search, installs it through `openclaw skills install`, and verifies `SKILL.md`, `.clawhub/origin.json`, `.clawhub/lock.json`, and `skills info --json`.
|
||||
- CLI backend live Docker probes can be run as focused lanes, for example `pnpm test:docker:live-cli-backend:codex`, `pnpm test:docker:live-cli-backend:codex:resume`, or `pnpm test:docker:live-cli-backend:codex:mcp`. Claude and Gemini have matching `:resume` and `:mcp` aliases.
|
||||
- CLI backend live Docker probes can be run as focused lanes, for example `pnpm test:docker:live-cli-backend:claude`, `pnpm test:docker:live-cli-backend:claude:resume`, or `pnpm test:docker:live-cli-backend:claude:mcp`. Gemini has matching `:resume` and `:mcp` aliases.
|
||||
- `pnpm test:docker:openwebui`: Starts Dockerized OpenClaw + Open WebUI, signs in through Open WebUI, checks `/api/models`, then runs a real proxied chat through `/api/chat/completions`. Requires a usable live model key, pulls an external Open WebUI image, and is not expected to be CI-stable like the normal unit/e2e suites.
|
||||
- `pnpm test:docker:mcp-channels`: Starts a seeded Gateway container and a second client container that spawns `openclaw mcp serve`, then verifies routed conversation discovery, transcript reads, attachment metadata, live event queue behavior, outbound send routing, and Claude-style channel + permission notifications over the real stdio bridge. The Claude notification assertion reads the raw stdio MCP frames directly so the smoke reflects what the bridge actually emits.
|
||||
- `pnpm test:docker:upgrade-survivor`: Installs the packed OpenClaw tarball over a dirty old-user fixture, runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks that agents, channel config, plugin allowlists, workspace/session files, stale legacy plugin dependency state, startup, and RPC status survive.
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
import type { CliBackendPlugin } from "openclaw/plugin-sdk/cli-backend";
|
||||
import {
|
||||
CLI_FRESH_WATCHDOG_DEFAULTS,
|
||||
CLI_RESUME_WATCHDOG_DEFAULTS,
|
||||
} from "openclaw/plugin-sdk/cli-backend";
|
||||
|
||||
const CODEX_CLI_DEFAULT_MODEL_REF = "codex-cli/gpt-5.5";
|
||||
// Keep this in sync with MANAGED_CODEX_APP_SERVER_PACKAGE_VERSION in the Codex plugin.
|
||||
const CODEX_CLI_NPM_PACKAGE = "@openai/codex@0.130.0";
|
||||
|
||||
export function buildOpenAICodexCliBackend(): CliBackendPlugin {
|
||||
return {
|
||||
id: "codex-cli",
|
||||
liveTest: {
|
||||
defaultModelRef: CODEX_CLI_DEFAULT_MODEL_REF,
|
||||
defaultImageProbe: true,
|
||||
defaultMcpProbe: true,
|
||||
docker: {
|
||||
npmPackage: CODEX_CLI_NPM_PACKAGE,
|
||||
binaryName: "codex",
|
||||
},
|
||||
},
|
||||
bundleMcp: true,
|
||||
bundleMcpMode: "codex-config-overrides",
|
||||
nativeToolMode: "always-on",
|
||||
config: {
|
||||
command: "codex",
|
||||
args: [
|
||||
"exec",
|
||||
"--json",
|
||||
"--color",
|
||||
"never",
|
||||
"--sandbox",
|
||||
"workspace-write",
|
||||
"-c",
|
||||
'service_tier="fast"',
|
||||
"--skip-git-repo-check",
|
||||
],
|
||||
resumeArgs: [
|
||||
"exec",
|
||||
"resume",
|
||||
"{sessionId}",
|
||||
"-c",
|
||||
'sandbox_mode="workspace-write"',
|
||||
"-c",
|
||||
'service_tier="fast"',
|
||||
"--skip-git-repo-check",
|
||||
],
|
||||
output: "jsonl",
|
||||
resumeOutput: "text",
|
||||
input: "arg",
|
||||
modelArg: "--model",
|
||||
sessionIdFields: ["thread_id"],
|
||||
sessionMode: "existing",
|
||||
systemPromptFileConfigArg: "-c",
|
||||
systemPromptFileConfigKey: "model_instructions_file",
|
||||
systemPromptWhen: "first",
|
||||
imageArg: "--image",
|
||||
imageMode: "repeat",
|
||||
imagePathScope: "workspace",
|
||||
reliability: {
|
||||
watchdog: {
|
||||
fresh: { ...CLI_FRESH_WATCHDOG_DEFAULTS },
|
||||
resume: { ...CLI_RESUME_WATCHDOG_DEFAULTS },
|
||||
},
|
||||
},
|
||||
serialize: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools";
|
||||
import { buildOpenAICodexCliBackend } from "./cli-backend.js";
|
||||
import { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js";
|
||||
import {
|
||||
openaiCodexMediaUnderstandingProvider,
|
||||
|
|
@ -45,7 +44,6 @@ export default definePluginEntry({
|
|||
});
|
||||
},
|
||||
});
|
||||
api.registerCliBackend(buildOpenAICodexCliBackend());
|
||||
api.registerProvider(buildProviderWithPromptContribution(buildOpenAIProvider()));
|
||||
api.registerProvider(buildProviderWithPromptContribution(buildOpenAICodexProviderPlugin()));
|
||||
api.registerMemoryEmbeddingProvider(openAiMemoryEmbeddingProviderAdapter);
|
||||
|
|
|
|||
|
|
@ -756,7 +756,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"cliBackends": ["codex-cli"],
|
||||
"providerAuthEnvVars": {
|
||||
"openai": ["OPENAI_API_KEY"]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
export { buildOpenAICodexCliBackend } from "./cli-backend.js";
|
||||
export { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js";
|
||||
export {
|
||||
openaiCodexMediaUnderstandingProvider,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import {
|
|||
OPENAI_CODEX_LOGIN_LABEL,
|
||||
OPENAI_CODEX_WIZARD_GROUP,
|
||||
} from "./auth-choice-copy.js";
|
||||
import { buildOpenAICodexCliBackend } from "./cli-backend.js";
|
||||
|
||||
async function runOpenAIProviderAuthMethod(
|
||||
methodId: string,
|
||||
|
|
@ -159,6 +158,5 @@ export default definePluginEntry({
|
|||
register(api) {
|
||||
api.registerProvider(buildOpenAISetupProvider());
|
||||
api.registerProvider(buildOpenAICodexSetupProvider());
|
||||
api.registerCliBackend(buildOpenAICodexCliBackend());
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
export { buildOpenAICodexCliBackend } from "./cli-backend.js";
|
||||
export { buildOpenAIImageGenerationProvider } from "./image-generation-provider.js";
|
||||
export {
|
||||
openaiCodexMediaUnderstandingProvider,
|
||||
|
|
|
|||
|
|
@ -1608,9 +1608,6 @@
|
|||
"test:docker:live-cli-backend:claude-subscription": "OPENCLAW_LIVE_CLI_BACKEND_AUTH=subscription OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6 OPENCLAW_LIVE_CLI_BACKEND_DISABLE_MCP_CONFIG=1 OPENCLAW_LIVE_CLI_BACKEND_MODEL_SWITCH_PROBE=0 OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1 OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=0 OPENCLAW_LIVE_CLI_BACKEND_MCP_PROBE=0 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:claude:mcp": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6 OPENCLAW_LIVE_CLI_BACKEND_MCP_PROBE=1 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:claude:resume": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6 OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:codex": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:codex:mcp": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4 OPENCLAW_LIVE_CLI_BACKEND_MCP_PROBE=1 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:codex:resume": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4 OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:gemini": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=google-gemini-cli/gemini-3-flash-preview bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:gemini:mcp": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=google-gemini-cli/gemini-3-flash-preview OPENCLAW_LIVE_CLI_BACKEND_MCP_PROBE=1 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:gemini:resume": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=google-gemini-cli/gemini-3-flash-preview OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE=1 bash scripts/test-live-cli-backend-docker.sh",
|
||||
|
|
@ -1621,11 +1618,9 @@
|
|||
"test:docker:live-subagent-announce": "bash scripts/test-live-subagent-announce-docker.sh",
|
||||
"test:docker:live-gateway": "bash scripts/test-live-gateway-models-docker.sh",
|
||||
"test:docker:live-gateway:claude": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=claude-cli OPENCLAW_LIVE_GATEWAY_MODELS=claude-cli/claude-sonnet-4-6 bash scripts/test-live-gateway-models-docker.sh",
|
||||
"test:docker:live-gateway:codex": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=codex-cli OPENCLAW_LIVE_GATEWAY_MODELS=codex-cli/gpt-5.5 bash scripts/test-live-gateway-models-docker.sh",
|
||||
"test:docker:live-gateway:gemini": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=google-gemini-cli OPENCLAW_LIVE_GATEWAY_MODELS=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-gateway-models-docker.sh",
|
||||
"test:docker:live-models": "bash scripts/test-live-models-docker.sh",
|
||||
"test:docker:live-models:claude": "OPENCLAW_LIVE_PROVIDERS=claude-cli OPENCLAW_LIVE_MODELS=claude-cli/claude-sonnet-4-6 bash scripts/test-live-models-docker.sh",
|
||||
"test:docker:live-models:codex": "OPENCLAW_LIVE_PROVIDERS=codex-cli OPENCLAW_LIVE_MODELS=codex-cli/gpt-5.5 bash scripts/test-live-models-docker.sh",
|
||||
"test:docker:live-models:gemini": "OPENCLAW_LIVE_PROVIDERS=google-gemini-cli OPENCLAW_LIVE_MODELS=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-models-docker.sh",
|
||||
"test:docker:live:all": "OPENCLAW_DOCKER_ALL_LIVE_MODE=only node scripts/test-docker-all.mjs",
|
||||
"test:docker:local:all": "OPENCLAW_DOCKER_ALL_LIVE_MODE=skip node scripts/test-docker-all.mjs",
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ function liveOpenAiChatToolsLane() {
|
|||
|
||||
export const mainLanes = [
|
||||
liveLane("live-models", liveDockerScriptCommand("test-live-models-docker.sh"), {
|
||||
providers: ["claude-cli", "codex-cli", "google-gemini-cli"],
|
||||
providers: ["claude-cli", "google-gemini-cli"],
|
||||
timeoutMs: LIVE_PROFILE_TIMEOUT_MS,
|
||||
weight: 4,
|
||||
}),
|
||||
|
|
@ -169,11 +169,11 @@ export const mainLanes = [
|
|||
"live-gateway",
|
||||
liveDockerScriptCommand(
|
||||
"test-live-gateway-models-docker.sh",
|
||||
"OPENCLAW_IMAGE=openclaw:local-live-gateway OPENCLAW_DOCKER_BUILD_EXTENSIONS=matrix OPENCLAW_LIVE_GATEWAY_PROVIDERS=claude-cli,codex-cli,google-gemini-cli",
|
||||
"OPENCLAW_IMAGE=openclaw:local-live-gateway OPENCLAW_DOCKER_BUILD_EXTENSIONS=matrix OPENCLAW_LIVE_GATEWAY_PROVIDERS=claude-cli,google-gemini-cli",
|
||||
{ skipBuild: false },
|
||||
),
|
||||
{
|
||||
providers: ["claude-cli", "codex-cli", "google-gemini-cli"],
|
||||
providers: ["claude-cli", "google-gemini-cli"],
|
||||
timeoutMs: LIVE_PROFILE_TIMEOUT_MS,
|
||||
weight: 4,
|
||||
},
|
||||
|
|
@ -431,7 +431,7 @@ export const tailLanes = [
|
|||
),
|
||||
liveLane("live-codex-harness", liveDockerScriptCommand("test-live-codex-harness-docker.sh"), {
|
||||
cacheKey: "codex-harness",
|
||||
provider: "codex-cli",
|
||||
provider: "openai",
|
||||
resources: ["npm"],
|
||||
timeoutMs: LIVE_ACP_TIMEOUT_MS,
|
||||
weight: 3,
|
||||
|
|
@ -455,7 +455,7 @@ export const tailLanes = [
|
|||
),
|
||||
{
|
||||
cacheKey: "codex-harness",
|
||||
provider: "codex-cli",
|
||||
provider: "openai",
|
||||
resources: ["npm"],
|
||||
timeoutMs: LIVE_ACP_TIMEOUT_MS,
|
||||
weight: 3,
|
||||
|
|
@ -475,20 +475,6 @@ export const tailLanes = [
|
|||
},
|
||||
),
|
||||
livePluginToolLane(),
|
||||
liveLane(
|
||||
"live-cli-backend-codex",
|
||||
liveDockerScriptCommand(
|
||||
"test-live-cli-backend-docker.sh",
|
||||
"OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4",
|
||||
),
|
||||
{
|
||||
cacheKey: "cli-backend-codex",
|
||||
provider: "codex-cli",
|
||||
resources: ["npm"],
|
||||
timeoutMs: LIVE_CLI_TIMEOUT_MS,
|
||||
weight: 3,
|
||||
},
|
||||
),
|
||||
liveLane(
|
||||
"live-acp-bind-claude",
|
||||
liveDockerScriptCommand("test-live-acp-bind-docker.sh", "OPENCLAW_LIVE_ACP_BIND_AGENT=claude"),
|
||||
|
|
@ -505,7 +491,7 @@ export const tailLanes = [
|
|||
liveDockerScriptCommand("test-live-acp-bind-docker.sh", "OPENCLAW_LIVE_ACP_BIND_AGENT=codex"),
|
||||
{
|
||||
cacheKey: "acp-bind-codex",
|
||||
provider: "codex-cli",
|
||||
provider: "openai",
|
||||
resources: ["npm"],
|
||||
timeoutMs: LIVE_ACP_TIMEOUT_MS,
|
||||
weight: 3,
|
||||
|
|
|
|||
|
|
@ -7,16 +7,28 @@ if (!provider) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
if (provider === "codex-cli") {
|
||||
process.stdout.write(
|
||||
JSON.stringify(
|
||||
{
|
||||
provider,
|
||||
unsupported: true,
|
||||
reason:
|
||||
"codex-cli is no longer a bundled CLI backend. Use openai/* with the Codex app-server runtime instead.",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function loadFallbackBackend(id: string) {
|
||||
switch (id) {
|
||||
case "claude-cli": {
|
||||
const mod = await import("../extensions/anthropic/cli-backend.ts");
|
||||
return mod.buildAnthropicCliBackend();
|
||||
}
|
||||
case "codex-cli": {
|
||||
const mod = await import("../extensions/openai/cli-backend.ts");
|
||||
return mod.buildOpenAICodexCliBackend();
|
||||
}
|
||||
case "google-gemini-cli": {
|
||||
const mod = await import("../extensions/google/cli-backend.ts");
|
||||
return mod.buildGoogleGeminiCliBackend();
|
||||
|
|
|
|||
|
|
@ -33,15 +33,6 @@ DOCKER_TRUSTED_HARNESS_MOUNT=(-v "$TRUSTED_HARNESS_DIR":"$DOCKER_TRUSTED_HARNESS
|
|||
if [[ -z "$CLI_PROVIDER" || "$CLI_PROVIDER" == "$CLI_MODEL" ]]; then
|
||||
CLI_PROVIDER="$DEFAULT_PROVIDER"
|
||||
fi
|
||||
CLI_USE_CI_SAFE_CODEX_CONFIG="${OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG:-}"
|
||||
if [[ -z "$CLI_USE_CI_SAFE_CODEX_CONFIG" ]]; then
|
||||
if [[ "$CLI_PROVIDER" == "codex-cli" ]]; then
|
||||
CLI_USE_CI_SAFE_CODEX_CONFIG="1"
|
||||
else
|
||||
CLI_USE_CI_SAFE_CODEX_CONFIG="0"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$PROFILE_FILE" && -r "$PROFILE_FILE" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
|
|
@ -63,11 +54,9 @@ if [[ "$CLI_AUTH_MODE" == "subscription" && "$CLI_PROVIDER" != "claude-cli" ]];
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$CLI_AUTH_MODE" == "api-key" && "$CLI_PROVIDER" == "codex-cli" ]]; then
|
||||
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
||||
echo "ERROR: OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key for codex-cli requires OPENAI_API_KEY." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$CLI_PROVIDER" == "codex-cli" ]]; then
|
||||
echo "ERROR: codex-cli is no longer a bundled CLI backend. Use openai/* with the Codex app-server runtime instead." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CLI_METADATA_JSON="$(node --import tsx "$ROOT_DIR/scripts/print-cli-backend-live-metadata.ts" "$CLI_PROVIDER")"
|
||||
|
|
@ -187,9 +176,7 @@ fi
|
|||
|
||||
AUTH_DIRS=()
|
||||
AUTH_FILES=()
|
||||
if [[ "$CLI_AUTH_MODE" == "api-key" && "$CLI_PROVIDER" == "codex-cli" ]]; then
|
||||
AUTH_FILES+=(".codex/config.toml")
|
||||
elif [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then
|
||||
if [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then
|
||||
while IFS= read -r auth_dir; do
|
||||
[[ -n "$auth_dir" ]] || continue
|
||||
AUTH_DIRS+=("$auth_dir")
|
||||
|
|
@ -285,10 +272,6 @@ provider="${OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER:-claude-cli}"
|
|||
default_command="${OPENCLAW_DOCKER_CLI_BACKEND_COMMAND_DEFAULT:-}"
|
||||
docker_package="${OPENCLAW_DOCKER_CLI_BACKEND_NPM_PACKAGE:-}"
|
||||
binary_name="${OPENCLAW_DOCKER_CLI_BACKEND_BINARY_NAME:-}"
|
||||
if [ "$provider" = "codex-cli" ] && [ "${OPENCLAW_LIVE_CLI_BACKEND_AUTH:-auto}" != "api-key" ]; then
|
||||
unset OPENAI_API_KEY
|
||||
unset OPENAI_BASE_URL
|
||||
fi
|
||||
if [ -z "$binary_name" ] && [ -n "$default_command" ]; then
|
||||
binary_name="$(basename "$default_command")"
|
||||
fi
|
||||
|
|
@ -310,13 +293,6 @@ if [ -n "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ] && [ ! -x "${OPENCLAW_LIVE_CL
|
|||
elif [ -n "$docker_package" ] && package_has_explicit_version "$docker_package"; then
|
||||
run_setup_command npm install -g "$docker_package"
|
||||
fi
|
||||
if [ "$provider" = "codex-cli" ] && [ "${OPENCLAW_LIVE_CLI_BACKEND_AUTH:-auto}" = "api-key" ]; then
|
||||
codex_login_command="${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-$NPM_CONFIG_PREFIX/bin/codex}"
|
||||
if [ ! -x "$codex_login_command" ] && [ -x "$NPM_CONFIG_PREFIX/bin/codex" ]; then
|
||||
codex_login_command="$NPM_CONFIG_PREFIX/bin/codex"
|
||||
fi
|
||||
printf '%s\n' "$OPENAI_API_KEY" | "$codex_login_command" login --with-api-key >/dev/null
|
||||
fi
|
||||
if [ -n "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ] && [ -x "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" ]; then
|
||||
echo "==> CLI backend binary: ${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}"
|
||||
"${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" -V || "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" --version || true
|
||||
|
|
@ -403,42 +379,6 @@ openclaw_live_link_runtime_tree "$tmp_dir"
|
|||
openclaw_live_stage_state_dir "$tmp_dir/.openclaw-state"
|
||||
openclaw_live_prepare_staged_config
|
||||
cd "$tmp_dir"
|
||||
if [ "${OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG:-0}" = "1" ]; then
|
||||
node --import tsx "$trusted_scripts_dir/prepare-codex-ci-config.ts" "$HOME/.codex/config.toml" "$tmp_dir"
|
||||
fi
|
||||
if [ "$provider" = "codex-cli" ] && [ "${OPENCLAW_LIVE_CLI_BACKEND_AUTH:-auto}" = "api-key" ]; then
|
||||
codex_probe_model="${OPENCLAW_LIVE_CLI_BACKEND_MODEL#*/}"
|
||||
codex_probe_token="OPENCLAW-CODEX-DIRECT-PROBE"
|
||||
codex_probe_stdout="$tmp_dir/codex-direct-probe.stdout"
|
||||
codex_probe_stderr="$tmp_dir/codex-direct-probe.stderr"
|
||||
if ! timeout --foreground --kill-after=10s 180s \
|
||||
"${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-codex}" \
|
||||
exec \
|
||||
--json \
|
||||
--color \
|
||||
never \
|
||||
--sandbox \
|
||||
danger-full-access \
|
||||
-c \
|
||||
'service_tier="fast"' \
|
||||
--skip-git-repo-check \
|
||||
--model \
|
||||
"$codex_probe_model" \
|
||||
"Reply exactly: $codex_probe_token" \
|
||||
>"$codex_probe_stdout" 2>"$codex_probe_stderr" </dev/null; then
|
||||
echo "ERROR: direct Codex CLI probe failed before OpenClaw gateway smoke." >&2
|
||||
sed -n '1,120p' "$codex_probe_stdout" >&2 || true
|
||||
sed -n '1,120p' "$codex_probe_stderr" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q "$codex_probe_token" "$codex_probe_stdout"; then
|
||||
echo "ERROR: direct Codex CLI probe did not return expected token." >&2
|
||||
sed -n '1,120p' "$codex_probe_stdout" >&2 || true
|
||||
sed -n '1,120p' "$codex_probe_stderr" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
echo "==> Direct Codex CLI probe ok"
|
||||
fi
|
||||
node scripts/test-live.mjs -- src/gateway/gateway-cli-backend.live.test.ts
|
||||
EOF
|
||||
|
||||
|
|
@ -450,9 +390,6 @@ echo "==> Provider: $CLI_PROVIDER"
|
|||
echo "==> Auth mode: $CLI_AUTH_MODE"
|
||||
echo "==> Setup timeout: ${CLI_SETUP_TIMEOUT_SECONDS}s"
|
||||
echo "==> Profile file: $PROFILE_STATUS"
|
||||
if [[ "$CLI_PROVIDER" == "codex-cli" ]]; then
|
||||
echo "==> CI-safe Codex config: $CLI_USE_CI_SAFE_CODEX_CONFIG"
|
||||
fi
|
||||
if [[ "$CLI_PROVIDER" == "claude-cli" && "$CLI_AUTH_MODE" == "subscription" ]]; then
|
||||
echo "==> Claude subscription: $CLAUDE_SUBSCRIPTION_TYPE"
|
||||
echo "==> Claude subscription source: $CLAUDE_SUBSCRIPTION_AUTH_SOURCE"
|
||||
|
|
@ -462,18 +399,7 @@ echo "==> External auth files: ${AUTH_FILES_CSV:-none}"
|
|||
DOCKER_AUTH_ENV=(
|
||||
-e OPENCLAW_LIVE_CLI_BACKEND_AUTH="$CLI_AUTH_MODE"
|
||||
)
|
||||
if [[ "$CLI_PROVIDER" == "codex-cli" && "$CLI_AUTH_MODE" == "api-key" ]]; then
|
||||
docker_env_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-cli-backend-env.XXXXXX")"
|
||||
TEMP_DIRS+=("$docker_env_dir")
|
||||
docker_env_file="$docker_env_dir/openai.env"
|
||||
{
|
||||
printf 'OPENAI_API_KEY=%s\n' "${OPENAI_API_KEY}"
|
||||
if [[ -n "${OPENAI_BASE_URL:-}" ]]; then
|
||||
printf 'OPENAI_BASE_URL=%s\n' "${OPENAI_BASE_URL}"
|
||||
fi
|
||||
} >"$docker_env_file"
|
||||
DOCKER_EXTRA_ENV_FILES+=(--env-file "$docker_env_file")
|
||||
elif [[ "$CLI_PROVIDER" == "claude-cli" && "$CLI_AUTH_MODE" == "subscription" ]]; then
|
||||
if [[ "$CLI_PROVIDER" == "claude-cli" && "$CLI_AUTH_MODE" == "subscription" ]]; then
|
||||
DOCKER_AUTH_ENV+=(
|
||||
-e CLAUDE_CODE_OAUTH_TOKEN="${CLAUDE_CODE_OAUTH_TOKEN:-}"
|
||||
-e OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV="$OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV"
|
||||
|
|
@ -501,7 +427,6 @@ DOCKER_RUN_ARGS=(docker run --rm -t \
|
|||
-e OPENCLAW_DOCKER_AUTH_FILES_RESOLVED="$AUTH_FILES_CSV" \
|
||||
-e OPENCLAW_LIVE_DOCKER_SCRIPTS_DIR="${DOCKER_TRUSTED_HARNESS_CONTAINER_DIR}/scripts" \
|
||||
-e OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE="${OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE:-copy}" \
|
||||
-e OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG="$CLI_USE_CI_SAFE_CODEX_CONFIG" \
|
||||
-e OPENCLAW_LIVE_CLI_BACKEND_SETUP_TIMEOUT_SECONDS="$CLI_SETUP_TIMEOUT_SECONDS" \
|
||||
-e OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER="$CLI_PROVIDER" \
|
||||
-e OPENCLAW_DOCKER_CLI_BACKEND_COMMAND_DEFAULT="$CLI_DEFAULT_COMMAND" \
|
||||
|
|
|
|||
|
|
@ -8,24 +8,53 @@ type LegacyRuntimeModelProviderAlias = {
|
|||
legacyProvider: string;
|
||||
/** Canonical provider id that should own model selection. */
|
||||
provider: string;
|
||||
/** Runtime/backend id that preserves the old execution behavior. */
|
||||
/** Runtime/backend id selected for the migrated ref. */
|
||||
runtime: string;
|
||||
/** True when the runtime is a CLI backend rather than an embedded harness. */
|
||||
cli: boolean;
|
||||
/** True when doctor must write a runtime policy even if the target runtime is the default. */
|
||||
requiresRuntimePolicy: boolean;
|
||||
};
|
||||
|
||||
const LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES = [
|
||||
{ legacyProvider: "codex", provider: "openai", runtime: "codex", cli: false },
|
||||
{ legacyProvider: "codex-cli", provider: "openai", runtime: "codex-cli", cli: true },
|
||||
{ legacyProvider: "claude-cli", provider: "anthropic", runtime: "claude-cli", cli: true },
|
||||
{
|
||||
legacyProvider: "codex",
|
||||
provider: "openai",
|
||||
runtime: "codex",
|
||||
cli: false,
|
||||
requiresRuntimePolicy: false,
|
||||
},
|
||||
{
|
||||
legacyProvider: "codex-cli",
|
||||
provider: "openai",
|
||||
runtime: "codex",
|
||||
cli: false,
|
||||
requiresRuntimePolicy: true,
|
||||
},
|
||||
{
|
||||
legacyProvider: "claude-cli",
|
||||
provider: "anthropic",
|
||||
runtime: "claude-cli",
|
||||
cli: true,
|
||||
requiresRuntimePolicy: true,
|
||||
},
|
||||
{
|
||||
legacyProvider: "google-gemini-cli",
|
||||
provider: "google",
|
||||
runtime: "google-gemini-cli",
|
||||
cli: true,
|
||||
requiresRuntimePolicy: true,
|
||||
},
|
||||
] as const satisfies readonly LegacyRuntimeModelProviderAlias[];
|
||||
|
||||
export function legacyRuntimeModelAliasRequiresRuntimePolicy(provider: string): boolean {
|
||||
return (
|
||||
LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.find(
|
||||
(entry) => normalizeProviderId(entry.legacyProvider) === normalizeProviderId(provider),
|
||||
)?.requiresRuntimePolicy === true
|
||||
);
|
||||
}
|
||||
|
||||
const LEGACY_ALIAS_BY_PROVIDER = new Map(
|
||||
LEGACY_RUNTIME_MODEL_PROVIDER_ALIASES.map((entry) => [
|
||||
normalizeProviderId(entry.legacyProvider),
|
||||
|
|
|
|||
|
|
@ -241,10 +241,9 @@ describe("handleModelsCommand", () => {
|
|||
expect(result?.reply?.text).not.toContain("- anthropic");
|
||||
});
|
||||
|
||||
it("hides bare backwards-compat aliases but surfaces CLI runtime providers in /models lists", async () => {
|
||||
it("hides bare backwards-compat aliases but surfaces supported CLI runtime providers in /models lists", async () => {
|
||||
modelCatalogMocks.loadModelCatalog.mockResolvedValueOnce([
|
||||
{ provider: "codex", id: "gpt-5.5", name: "GPT-5.5" },
|
||||
{ provider: "codex-cli", id: "gpt-5.5", name: "GPT-5.5" },
|
||||
{ provider: "claude-cli", id: "claude-opus-4-7", name: "Claude Opus" },
|
||||
{ provider: "google-gemini-cli", id: "gemini-3.1-pro-preview", name: "Gemini Pro" },
|
||||
{ provider: "anthropic", id: "claude-opus-4-7", name: "Claude Opus" },
|
||||
|
|
@ -256,7 +255,6 @@ describe("handleModelsCommand", () => {
|
|||
"google",
|
||||
"openai",
|
||||
"claude-cli",
|
||||
"codex-cli",
|
||||
"google-gemini-cli",
|
||||
]);
|
||||
|
||||
|
|
@ -271,9 +269,9 @@ describe("handleModelsCommand", () => {
|
|||
expect(result?.reply?.text).toContain("- google (1)");
|
||||
expect(result?.reply?.text).toContain("- openai (1)");
|
||||
expect(result?.reply?.text).toContain("- claude-cli (1)");
|
||||
expect(result?.reply?.text).toContain("- codex-cli (1)");
|
||||
expect(result?.reply?.text).toContain("- google-gemini-cli (1)");
|
||||
expect(result?.reply?.text).not.toMatch(/^- codex \(/m);
|
||||
expect(result?.reply?.text).not.toMatch(/^- codex-cli \(/m);
|
||||
});
|
||||
|
||||
it("sources CLI runtime provider model lists from the catalog, not user agents.defaults.models", async () => {
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ vi.mock("../config/config.js", () => ({
|
|||
}));
|
||||
|
||||
vi.mock("../config/paths.js", () => ({
|
||||
resolveIsNixMode: () => false,
|
||||
resolveStateDir: () => resolveStateDir(),
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -629,7 +629,7 @@ describe("normalizeCompatibilityConfigValues", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("migrates legacy Codex CLI primary refs to OpenAI refs plus model runtime", () => {
|
||||
it("migrates legacy Codex CLI primary refs to the Codex app-server route", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
agents: {
|
||||
defaults: {
|
||||
|
|
@ -652,16 +652,159 @@ describe("normalizeCompatibilityConfigValues", () => {
|
|||
expect(res.config.agents?.defaults?.agentRuntime).toBeUndefined();
|
||||
expect(res.config.agents?.defaults?.models).toEqual({
|
||||
"codex-cli/gpt-5.5": { alias: "Codex CLI" },
|
||||
"openai/gpt-5.5": {
|
||||
alias: "OpenAI GPT",
|
||||
agentRuntime: { id: "codex-cli" },
|
||||
"openai/gpt-5.5": { alias: "OpenAI GPT", agentRuntime: { id: "codex" } },
|
||||
"openai/gpt-5.4-mini": { agentRuntime: { id: "codex" } },
|
||||
});
|
||||
});
|
||||
|
||||
it("migrates legacy Codex CLI fallback refs when the primary is already canonical", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "openai/gpt-5.5",
|
||||
fallbacks: ["codex-cli/gpt-5.4"],
|
||||
},
|
||||
models: {
|
||||
"codex-cli/gpt-5.4": { alias: "Legacy CLI fallback" },
|
||||
},
|
||||
},
|
||||
},
|
||||
"openai/gpt-5.4-mini": {
|
||||
agentRuntime: { id: "codex-cli" },
|
||||
} as unknown as OpenClawConfig);
|
||||
|
||||
expect(res.config.agents?.defaults?.model).toEqual({
|
||||
primary: "openai/gpt-5.5",
|
||||
fallbacks: ["openai/gpt-5.4"],
|
||||
});
|
||||
expect(res.config.agents?.defaults?.models).toEqual({
|
||||
"codex-cli/gpt-5.4": { alias: "Legacy CLI fallback" },
|
||||
"openai/gpt-5.4": {
|
||||
alias: "Legacy CLI fallback",
|
||||
agentRuntime: { id: "codex" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("migrates standalone legacy Codex CLI allowlist keys", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"codex-cli/gpt-5.4": { alias: "Legacy CLI fallback" },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig);
|
||||
|
||||
expect(res.config.agents?.defaults?.models).toEqual({
|
||||
"codex-cli/gpt-5.4": { alias: "Legacy CLI fallback" },
|
||||
"openai/gpt-5.4": {
|
||||
alias: "Legacy CLI fallback",
|
||||
agentRuntime: { id: "codex" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("pins migrated Codex CLI refs to Codex when OpenAI uses a custom base URL", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "codex-cli/gpt-5.5",
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://proxy.example/v1",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig);
|
||||
|
||||
expect(res.config.agents?.defaults?.model).toBe("openai/gpt-5.5");
|
||||
expect(res.config.agents?.defaults?.models?.["openai/gpt-5.5"]?.agentRuntime).toEqual({
|
||||
id: "codex",
|
||||
});
|
||||
});
|
||||
|
||||
it("migrates existing Codex CLI runtime pins to the Codex app-server runtime", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"openai/gpt-5.5": {
|
||||
agentRuntime: { id: "codex-cli", mode: "strict" },
|
||||
},
|
||||
},
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "reviewer",
|
||||
models: {
|
||||
"openai/gpt-5.4-mini": {
|
||||
agentRuntime: { id: "codex-cli" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
agentRuntime: { id: "codex-cli" },
|
||||
models: [
|
||||
{
|
||||
id: "gpt-5.5",
|
||||
agentRuntime: { id: "codex-cli" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig);
|
||||
|
||||
expect(res.config.agents?.defaults?.models?.["openai/gpt-5.5"]?.agentRuntime).toEqual({
|
||||
id: "codex",
|
||||
mode: "strict",
|
||||
});
|
||||
expect(res.config.agents?.list?.[0]?.models?.["openai/gpt-5.4-mini"]?.agentRuntime).toEqual({
|
||||
id: "codex",
|
||||
});
|
||||
expect(res.config.models?.providers?.openai?.agentRuntime).toEqual({ id: "codex" });
|
||||
expect(res.config.models?.providers?.openai?.models?.[0]?.agentRuntime).toEqual({
|
||||
id: "codex",
|
||||
});
|
||||
expect(res.changes).toContain(
|
||||
"Moved agents.defaults.models.openai/gpt-5.5 agentRuntime.id from codex-cli to codex.",
|
||||
);
|
||||
expect(res.changes).toContain(
|
||||
"Moved agents.list.reviewer.models.openai/gpt-5.4-mini agentRuntime.id from codex-cli to codex.",
|
||||
);
|
||||
expect(res.changes).toContain(
|
||||
"Moved models.providers.openai agentRuntime.id from codex-cli to codex.",
|
||||
);
|
||||
expect(res.changes).toContain(
|
||||
"Moved models.providers.openai.models.gpt-5.5 agentRuntime.id from codex-cli to codex.",
|
||||
);
|
||||
});
|
||||
|
||||
it("migrates provider-scoped Codex CLI runtime pins without agents config", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
agentRuntime: { id: "codex-cli" },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig);
|
||||
|
||||
expect(res.config.models?.providers?.openai?.agentRuntime).toEqual({ id: "codex" });
|
||||
expect(res.changes).toContain(
|
||||
"Moved models.providers.openai agentRuntime.id from codex-cli to codex.",
|
||||
);
|
||||
});
|
||||
|
||||
it("migrates legacy Gemini CLI primary refs to Google refs plus model runtime", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
agents: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import { migrateLegacyRuntimeModelRef } from "../../../agents/model-runtime-aliases.js";
|
||||
import {
|
||||
legacyRuntimeModelAliasRequiresRuntimePolicy,
|
||||
migrateLegacyRuntimeModelRef,
|
||||
} from "../../../agents/model-runtime-aliases.js";
|
||||
import { normalizeProviderId } from "../../../agents/provider-id.js";
|
||||
import { resolveSingleAccountKeysToMove } from "../../../channels/plugins/setup-promotion-helpers.js";
|
||||
import { resolveNormalizedProviderModelMaxTokens } from "../../../config/defaults.js";
|
||||
|
|
@ -229,6 +232,18 @@ type ModelProviderEntry = Partial<
|
|||
>;
|
||||
type ModelsConfigPatch = Partial<NonNullable<OpenClawConfig["models"]>>;
|
||||
type ModelDefinitionEntry = NonNullable<ModelProviderEntry["models"]>[number];
|
||||
type SelectedRuntimeRef = {
|
||||
ref: string;
|
||||
runtime: string;
|
||||
requiresRuntimePolicy: boolean;
|
||||
};
|
||||
|
||||
const LEGACY_CODEX_CLI_RUNTIME_ID = "codex-cli";
|
||||
const CODEX_APP_SERVER_RUNTIME_ID = "codex";
|
||||
|
||||
function migratedRuntimeRequiresPolicy(legacyProvider: string): boolean {
|
||||
return legacyRuntimeModelAliasRequiresRuntimePolicy(legacyProvider);
|
||||
}
|
||||
|
||||
function mergeModelEntry(legacyEntry: unknown, currentEntry: unknown): unknown {
|
||||
if (!isRecord(legacyEntry) || !isRecord(currentEntry)) {
|
||||
|
|
@ -237,11 +252,28 @@ function mergeModelEntry(legacyEntry: unknown, currentEntry: unknown): unknown {
|
|||
return { ...legacyEntry, ...currentEntry };
|
||||
}
|
||||
|
||||
function normalizeLegacyCodexCliAgentRuntimePolicy(raw: unknown): {
|
||||
value?: unknown;
|
||||
changed: boolean;
|
||||
} {
|
||||
if (!isRecord(raw)) {
|
||||
return { value: raw, changed: false };
|
||||
}
|
||||
if (normalizeOptionalLowercaseString(raw.id) !== LEGACY_CODEX_CLI_RUNTIME_ID) {
|
||||
return { value: raw, changed: false };
|
||||
}
|
||||
return {
|
||||
value: { ...raw, id: CODEX_APP_SERVER_RUNTIME_ID },
|
||||
changed: true,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeLegacyRuntimeAgentModelConfig(raw: unknown): {
|
||||
value?: unknown;
|
||||
changed: boolean;
|
||||
selectedRuntime?: string;
|
||||
selectedRefs: string[];
|
||||
selectedRuntimeRequiresPolicy: boolean;
|
||||
selectedRefs: SelectedRuntimeRef[];
|
||||
} {
|
||||
if (typeof raw === "string") {
|
||||
const migrated = migrateLegacyRuntimeModelRef(raw);
|
||||
|
|
@ -250,39 +282,72 @@ function normalizeLegacyRuntimeAgentModelConfig(raw: unknown): {
|
|||
value: migrated.ref,
|
||||
changed: true,
|
||||
selectedRuntime: migrated.runtime,
|
||||
selectedRefs: [migrated.ref],
|
||||
selectedRuntimeRequiresPolicy: migratedRuntimeRequiresPolicy(migrated.legacyProvider),
|
||||
selectedRefs: [
|
||||
{
|
||||
ref: migrated.ref,
|
||||
runtime: migrated.runtime,
|
||||
requiresRuntimePolicy: migratedRuntimeRequiresPolicy(migrated.legacyProvider),
|
||||
},
|
||||
],
|
||||
}
|
||||
: { value: raw, changed: false, selectedRefs: [] };
|
||||
: { value: raw, changed: false, selectedRuntimeRequiresPolicy: false, selectedRefs: [] };
|
||||
}
|
||||
if (!isRecord(raw)) {
|
||||
return { value: raw, changed: false, selectedRefs: [] };
|
||||
return { value: raw, changed: false, selectedRuntimeRequiresPolicy: false, selectedRefs: [] };
|
||||
}
|
||||
|
||||
const migratedPrimary =
|
||||
typeof raw.primary === "string" ? migrateLegacyRuntimeModelRef(raw.primary) : null;
|
||||
if (!migratedPrimary) {
|
||||
return { value: raw, changed: false, selectedRefs: [] };
|
||||
let changed = false;
|
||||
const next: Record<string, unknown> = { ...raw };
|
||||
const selectedRefs: SelectedRuntimeRef[] = [];
|
||||
let selectedRuntime = migratedPrimary?.runtime;
|
||||
let selectedRuntimeRequiresPolicy =
|
||||
migratedPrimary !== null && migratedRuntimeRequiresPolicy(migratedPrimary.legacyProvider);
|
||||
if (migratedPrimary) {
|
||||
next.primary = migratedPrimary.ref;
|
||||
selectedRefs.push({
|
||||
ref: migratedPrimary.ref,
|
||||
runtime: migratedPrimary.runtime,
|
||||
requiresRuntimePolicy: migratedRuntimeRequiresPolicy(migratedPrimary.legacyProvider),
|
||||
});
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const next: Record<string, unknown> = { ...raw, primary: migratedPrimary.ref };
|
||||
const selectedRefs = [migratedPrimary.ref];
|
||||
if (Array.isArray(raw.fallbacks)) {
|
||||
next.fallbacks = raw.fallbacks.map((fallback) => {
|
||||
if (typeof fallback !== "string") {
|
||||
return fallback;
|
||||
}
|
||||
const migratedFallback = migrateLegacyRuntimeModelRef(fallback);
|
||||
if (migratedFallback?.runtime === migratedPrimary.runtime) {
|
||||
selectedRefs.push(migratedFallback.ref);
|
||||
if (
|
||||
migratedFallback &&
|
||||
(migratedFallback.runtime === selectedRuntime ||
|
||||
migratedFallback.legacyProvider === LEGACY_CODEX_CLI_RUNTIME_ID)
|
||||
) {
|
||||
selectedRuntime ??= migratedFallback.runtime;
|
||||
selectedRuntimeRequiresPolicy ||= migratedRuntimeRequiresPolicy(
|
||||
migratedFallback.legacyProvider,
|
||||
);
|
||||
selectedRefs.push({
|
||||
ref: migratedFallback.ref,
|
||||
runtime: migratedFallback.runtime,
|
||||
requiresRuntimePolicy: migratedRuntimeRequiresPolicy(migratedFallback.legacyProvider),
|
||||
});
|
||||
changed = true;
|
||||
return migratedFallback.ref;
|
||||
}
|
||||
return fallback;
|
||||
});
|
||||
}
|
||||
if (!changed) {
|
||||
return { value: raw, changed: false, selectedRuntimeRequiresPolicy: false, selectedRefs: [] };
|
||||
}
|
||||
return {
|
||||
value: next,
|
||||
changed: true,
|
||||
selectedRuntime: migratedPrimary.runtime,
|
||||
selectedRuntime,
|
||||
selectedRuntimeRequiresPolicy,
|
||||
selectedRefs,
|
||||
};
|
||||
}
|
||||
|
|
@ -309,56 +374,77 @@ function mergeModelEntryWithRuntimePolicy(
|
|||
legacyEntry: unknown,
|
||||
currentEntry: unknown,
|
||||
runtime: string | undefined,
|
||||
requiresRuntimePolicy = runtimeNeedsExplicitModelPolicy(runtime),
|
||||
): unknown {
|
||||
const merged = mergeModelEntry(legacyEntry, currentEntry);
|
||||
return runtimeNeedsExplicitModelPolicy(runtime)
|
||||
? modelEntryWithRuntimePolicy(merged, runtime)
|
||||
: merged;
|
||||
return runtime && requiresRuntimePolicy ? modelEntryWithRuntimePolicy(merged, runtime) : merged;
|
||||
}
|
||||
|
||||
function normalizeLegacyRuntimeAllowlistModels(
|
||||
rawModels: unknown,
|
||||
selectedRuntime: string | undefined,
|
||||
selectedRuntimeRequiresPolicy: boolean,
|
||||
): {
|
||||
value?: unknown;
|
||||
changed: boolean;
|
||||
} {
|
||||
if (!selectedRuntime || !isRecord(rawModels)) {
|
||||
if (!isRecord(rawModels)) {
|
||||
return { value: rawModels, changed: false };
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
const next: Record<string, unknown> = {};
|
||||
const legacyEntries: Array<[string, unknown]> = [];
|
||||
const legacyEntries: Array<{
|
||||
migratedKey: string;
|
||||
entry: unknown;
|
||||
runtime: string;
|
||||
requiresRuntimePolicy: boolean;
|
||||
}> = [];
|
||||
for (const [rawKey, entry] of Object.entries(rawModels)) {
|
||||
const migrated = migrateLegacyRuntimeModelRef(rawKey);
|
||||
if (migrated?.runtime === selectedRuntime) {
|
||||
if (
|
||||
migrated &&
|
||||
(migrated.runtime === selectedRuntime ||
|
||||
migrated.legacyProvider === LEGACY_CODEX_CLI_RUNTIME_ID)
|
||||
) {
|
||||
changed = true;
|
||||
next[rawKey] = mergeModelEntry(entry, next[rawKey]);
|
||||
legacyEntries.push([migrated.ref, entry]);
|
||||
legacyEntries.push({
|
||||
migratedKey: migrated.ref,
|
||||
entry,
|
||||
runtime: migrated.runtime,
|
||||
requiresRuntimePolicy: migratedRuntimeRequiresPolicy(migrated.legacyProvider),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
next[rawKey] = mergeModelEntry(entry, next[rawKey]);
|
||||
}
|
||||
for (const [migratedKey, entry] of legacyEntries) {
|
||||
next[migratedKey] = mergeModelEntryWithRuntimePolicy(entry, next[migratedKey], selectedRuntime);
|
||||
for (const { migratedKey, entry, runtime, requiresRuntimePolicy } of legacyEntries) {
|
||||
next[migratedKey] = mergeModelEntryWithRuntimePolicy(
|
||||
entry,
|
||||
next[migratedKey],
|
||||
runtime,
|
||||
requiresRuntimePolicy || (runtime === selectedRuntime && selectedRuntimeRequiresPolicy),
|
||||
);
|
||||
}
|
||||
return { value: next, changed };
|
||||
}
|
||||
|
||||
function ensureSelectedModelRuntimePolicies(
|
||||
rawModels: unknown,
|
||||
selectedRefs: readonly string[],
|
||||
selectedRuntime: string | undefined,
|
||||
selectedRefs: readonly SelectedRuntimeRef[],
|
||||
): { value?: unknown; changed: boolean } {
|
||||
if (!runtimeNeedsExplicitModelPolicy(selectedRuntime) || selectedRefs.length === 0) {
|
||||
if (selectedRefs.length === 0) {
|
||||
return { value: rawModels, changed: false };
|
||||
}
|
||||
const next: Record<string, unknown> = isRecord(rawModels) ? { ...rawModels } : {};
|
||||
let changed = false;
|
||||
for (const ref of selectedRefs) {
|
||||
for (const { ref, runtime, requiresRuntimePolicy } of selectedRefs) {
|
||||
if (!requiresRuntimePolicy) {
|
||||
continue;
|
||||
}
|
||||
const current = next[ref];
|
||||
const updated = modelEntryWithRuntimePolicy(current, selectedRuntime);
|
||||
const updated = modelEntryWithRuntimePolicy(current, runtime);
|
||||
if (JSON.stringify(updated) !== JSON.stringify(current ?? {})) {
|
||||
next[ref] = updated;
|
||||
changed = true;
|
||||
|
|
@ -367,6 +453,33 @@ function ensureSelectedModelRuntimePolicies(
|
|||
return { value: next, changed };
|
||||
}
|
||||
|
||||
function normalizeLegacyCodexCliRuntimePinsInModels(
|
||||
rawModels: unknown,
|
||||
path: string,
|
||||
changes: string[],
|
||||
): { value?: unknown; changed: boolean } {
|
||||
if (!isRecord(rawModels)) {
|
||||
return { value: rawModels, changed: false };
|
||||
}
|
||||
let changed = false;
|
||||
const next: Record<string, unknown> = { ...rawModels };
|
||||
for (const [modelRef, rawEntry] of Object.entries(rawModels)) {
|
||||
if (!isRecord(rawEntry)) {
|
||||
continue;
|
||||
}
|
||||
const runtime = normalizeLegacyCodexCliAgentRuntimePolicy(rawEntry.agentRuntime);
|
||||
if (!runtime.changed) {
|
||||
continue;
|
||||
}
|
||||
next[modelRef] = { ...rawEntry, agentRuntime: runtime.value };
|
||||
changed = true;
|
||||
changes.push(
|
||||
`Moved ${path}.${sanitizeForLog(modelRef)} agentRuntime.id from codex-cli to codex.`,
|
||||
);
|
||||
}
|
||||
return { value: next, changed };
|
||||
}
|
||||
|
||||
function normalizeLegacyRuntimeAgentContainer(
|
||||
raw: Record<string, unknown>,
|
||||
path: string,
|
||||
|
|
@ -387,7 +500,11 @@ function normalizeLegacyRuntimeAgentContainer(
|
|||
);
|
||||
}
|
||||
|
||||
const models = normalizeLegacyRuntimeAllowlistModels(raw.models, model.selectedRuntime);
|
||||
const models = normalizeLegacyRuntimeAllowlistModels(
|
||||
raw.models,
|
||||
model.selectedRuntime,
|
||||
model.selectedRuntimeRequiresPolicy,
|
||||
);
|
||||
if (models.changed) {
|
||||
next.models = models.value;
|
||||
changed = true;
|
||||
|
|
@ -395,11 +512,7 @@ function normalizeLegacyRuntimeAgentContainer(
|
|||
}
|
||||
|
||||
if (model.selectedRuntime) {
|
||||
const modelRuntimes = ensureSelectedModelRuntimePolicies(
|
||||
next.models,
|
||||
model.selectedRefs,
|
||||
model.selectedRuntime,
|
||||
);
|
||||
const modelRuntimes = ensureSelectedModelRuntimePolicies(next.models, model.selectedRefs);
|
||||
if (modelRuntimes.changed) {
|
||||
next.models = modelRuntimes.value;
|
||||
changed = true;
|
||||
|
|
@ -407,16 +520,95 @@ function normalizeLegacyRuntimeAgentContainer(
|
|||
}
|
||||
}
|
||||
|
||||
const codexCliRuntimePins = normalizeLegacyCodexCliRuntimePinsInModels(
|
||||
next.models,
|
||||
`${path}.models`,
|
||||
changes,
|
||||
);
|
||||
if (codexCliRuntimePins.changed) {
|
||||
next.models = codexCliRuntimePins.value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return { value: next, changed };
|
||||
}
|
||||
|
||||
function normalizeLegacyCodexCliProviderRuntimePins(
|
||||
cfg: OpenClawConfig,
|
||||
changes: string[],
|
||||
): { config: OpenClawConfig; changed: boolean } {
|
||||
const rawModels = cfg.models;
|
||||
if (!isRecord(rawModels) || !isRecord(rawModels.providers)) {
|
||||
return { config: cfg, changed: false };
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
const nextProviders: Record<string, unknown> = { ...rawModels.providers };
|
||||
for (const [providerId, rawProvider] of Object.entries(rawModels.providers)) {
|
||||
if (!isRecord(rawProvider)) {
|
||||
continue;
|
||||
}
|
||||
let providerChanged = false;
|
||||
const nextProvider: Record<string, unknown> = { ...rawProvider };
|
||||
const providerRuntime = normalizeLegacyCodexCliAgentRuntimePolicy(rawProvider.agentRuntime);
|
||||
if (providerRuntime.changed) {
|
||||
nextProvider.agentRuntime = providerRuntime.value;
|
||||
providerChanged = true;
|
||||
changes.push(
|
||||
`Moved models.providers.${sanitizeForLog(providerId)} agentRuntime.id from codex-cli to codex.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(rawProvider.models)) {
|
||||
const nextProviderModels = rawProvider.models.map((entry, index) => {
|
||||
if (!isRecord(entry)) {
|
||||
return entry;
|
||||
}
|
||||
const runtime = normalizeLegacyCodexCliAgentRuntimePolicy(entry.agentRuntime);
|
||||
if (!runtime.changed) {
|
||||
return entry;
|
||||
}
|
||||
providerChanged = true;
|
||||
const modelId = normalizeOptionalString(entry.id) ?? `[${index}]`;
|
||||
changes.push(
|
||||
`Moved models.providers.${sanitizeForLog(providerId)}.models.${sanitizeForLog(modelId)} agentRuntime.id from codex-cli to codex.`,
|
||||
);
|
||||
return Object.assign({}, entry, { agentRuntime: runtime.value });
|
||||
});
|
||||
if (providerChanged) {
|
||||
nextProvider.models = nextProviderModels;
|
||||
}
|
||||
}
|
||||
|
||||
if (providerChanged) {
|
||||
nextProviders[providerId] = nextProvider;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
? {
|
||||
config: {
|
||||
...cfg,
|
||||
models: {
|
||||
...rawModels,
|
||||
providers: nextProviders as NonNullable<OpenClawConfig["models"]>["providers"],
|
||||
},
|
||||
},
|
||||
changed: true,
|
||||
}
|
||||
: { config: cfg, changed: false };
|
||||
}
|
||||
|
||||
export function normalizeLegacyRuntimeModelRefs(
|
||||
cfg: OpenClawConfig,
|
||||
changes: string[],
|
||||
): OpenClawConfig {
|
||||
const rawAgents = cfg.agents;
|
||||
const providerPinned = normalizeLegacyCodexCliProviderRuntimePins(cfg, changes);
|
||||
const cfgWithProviders = providerPinned.config;
|
||||
const rawAgents = cfgWithProviders.agents;
|
||||
if (!isRecord(rawAgents)) {
|
||||
return cfg;
|
||||
return cfgWithProviders;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
|
|
@ -452,12 +644,13 @@ export function normalizeLegacyRuntimeModelRefs(
|
|||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
const nextCfg = changed
|
||||
? {
|
||||
...cfg,
|
||||
...cfgWithProviders,
|
||||
agents: nextAgents as OpenClawConfig["agents"],
|
||||
}
|
||||
: cfg;
|
||||
: cfgWithProviders;
|
||||
return nextCfg;
|
||||
}
|
||||
|
||||
export function normalizeLegacyOpenAICodexModelsAddMetadata(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const CRESTODIAN_CLAUDE_CLI_MODEL = "claude-opus-4-7";
|
|||
const CRESTODIAN_CODEX_MODEL = "gpt-5.5";
|
||||
|
||||
type CrestodianLocalPlannerBackend = {
|
||||
kind: "claude-cli" | "codex-app-server" | "codex-cli";
|
||||
kind: "claude-cli" | "codex-app-server";
|
||||
label: string;
|
||||
runner: "cli" | "embedded";
|
||||
provider: string;
|
||||
|
|
@ -32,16 +32,6 @@ const CODEX_APP_SERVER_BACKEND: CrestodianLocalPlannerBackend = {
|
|||
buildConfig: buildCodexAppServerPlannerConfig,
|
||||
};
|
||||
|
||||
const CODEX_CLI_BACKEND: CrestodianLocalPlannerBackend = {
|
||||
kind: "codex-cli",
|
||||
label: `codex-cli/${CRESTODIAN_CODEX_MODEL}`,
|
||||
runner: "cli",
|
||||
provider: "codex-cli",
|
||||
model: CRESTODIAN_CODEX_MODEL,
|
||||
buildConfig: (workspaceDir) =>
|
||||
buildCliPlannerConfig(workspaceDir, `codex-cli/${CRESTODIAN_CODEX_MODEL}`),
|
||||
};
|
||||
|
||||
export function selectCrestodianLocalPlannerBackends(
|
||||
overview: CrestodianOverview,
|
||||
): CrestodianLocalPlannerBackend[] {
|
||||
|
|
@ -50,7 +40,7 @@ export function selectCrestodianLocalPlannerBackends(
|
|||
backends.push(CLAUDE_CLI_BACKEND);
|
||||
}
|
||||
if (overview.tools.codex.found) {
|
||||
backends.push(CODEX_APP_SERVER_BACKEND, CODEX_CLI_BACKEND);
|
||||
backends.push(CODEX_APP_SERVER_BACKEND);
|
||||
}
|
||||
return backends;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export function buildCrestodianAssistantUserPrompt(params: {
|
|||
`Default model: ${params.overview.defaultModel ?? "not configured"}`,
|
||||
`Config valid: ${params.overview.config.valid}`,
|
||||
`Gateway reachable: ${params.overview.gateway.reachable}`,
|
||||
`Codex CLI: ${params.overview.tools.codex.found ? "found" : "not found"}`,
|
||||
`Codex binary: ${params.overview.tools.codex.found ? "found" : "not found"}`,
|
||||
`Claude Code CLI: ${params.overview.tools.claude.found ? "found" : "not found"}`,
|
||||
`OpenAI API key: ${params.overview.tools.apiKeys.openai ? "found" : "not found"}`,
|
||||
`Anthropic API key: ${params.overview.tools.apiKeys.anthropic ? "found" : "not found"}`,
|
||||
|
|
|
|||
|
|
@ -165,9 +165,9 @@ describe("Crestodian assistant", () => {
|
|||
codex: { command: "codex", found: true },
|
||||
}),
|
||||
).map((backend) => backend.kind),
|
||||
).toEqual(["claude-cli", "codex-app-server", "codex-cli"]);
|
||||
).toEqual(["claude-cli", "codex-app-server"]);
|
||||
|
||||
const [codexAppServer, codexCli] = selectCrestodianLocalPlannerBackends(
|
||||
const [codexAppServer] = selectCrestodianLocalPlannerBackends(
|
||||
overview({
|
||||
codex: { command: "codex", found: true },
|
||||
}),
|
||||
|
|
@ -182,13 +182,6 @@ describe("Crestodian assistant", () => {
|
|||
expect(codexAppServerDefaults.workspace).toBe("/tmp/workspace");
|
||||
expect(codexAppServerModel.primary).toBe("openai/gpt-5.5");
|
||||
expect(codexAppServerCodexEntry.enabled).toBe(true);
|
||||
|
||||
const codexCliConfig = requireRecord(codexCli?.buildConfig("/tmp/workspace"));
|
||||
const codexCliAgents = requireRecord(codexCliConfig.agents);
|
||||
const codexCliDefaults = requireRecord(codexCliAgents.defaults);
|
||||
const codexCliModel = requireRecord(codexCliDefaults.model);
|
||||
expect(codexCliDefaults.workspace).toBe("/tmp/workspace");
|
||||
expect(codexCliModel.primary).toBe("codex-cli/gpt-5.5");
|
||||
});
|
||||
|
||||
it("falls back to Codex app-server when Claude CLI planning fails", async () => {
|
||||
|
|
@ -242,14 +235,8 @@ describe("Crestodian assistant", () => {
|
|||
expect(embeddedCodexEntry.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("uses Codex CLI if the app-server planner is not usable", async () => {
|
||||
const runCliAgent = vi.fn(async (params: RunCliAgentParams): Promise<EmbeddedPiRunResult> => {
|
||||
if (params.provider === "codex-cli") {
|
||||
return {
|
||||
payloads: [{ text: '{"reply":"CLI fallback.","command":"models"}' }],
|
||||
meta: { durationMs: 0 },
|
||||
};
|
||||
}
|
||||
it("does not fall back to Codex CLI if the app-server planner is not usable", async () => {
|
||||
const runCliAgent = vi.fn(async (): Promise<EmbeddedPiRunResult> => {
|
||||
throw new Error("unexpected cli provider");
|
||||
});
|
||||
const runEmbeddedPiAgent = vi.fn(async () => {
|
||||
|
|
@ -268,18 +255,9 @@ describe("Crestodian assistant", () => {
|
|||
removeTempDir: async () => {},
|
||||
},
|
||||
});
|
||||
if (result === null) {
|
||||
throw new Error("Expected planner result");
|
||||
}
|
||||
expect(result.command).toBe("models");
|
||||
expect(result.reply).toBe("CLI fallback.");
|
||||
expect(result.modelLabel).toBe("codex-cli/gpt-5.5");
|
||||
expect(result).toBeNull();
|
||||
|
||||
expect(runEmbeddedPiAgent).toHaveBeenCalledTimes(1);
|
||||
expect(runCliAgent).toHaveBeenCalledTimes(1);
|
||||
const firstCliCall = firstMockArg(runCliAgent);
|
||||
expect(firstCliCall.provider).toBe("codex-cli");
|
||||
expect(firstCliCall.model).toBe("gpt-5.5");
|
||||
expect(firstCliCall.cleanupCliLiveSessionOnRunEnd).toBe(true);
|
||||
expect(runCliAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ const PLUGIN_UNINSTALL_RE =
|
|||
const OPENAI_API_DEFAULT_MODEL_REF = `${DEFAULT_PROVIDER}/${DEFAULT_MODEL}`;
|
||||
const ANTHROPIC_API_DEFAULT_MODEL_REF = "anthropic/claude-opus-4-7";
|
||||
const CLAUDE_CLI_DEFAULT_MODEL_REF = "claude-cli/claude-opus-4-7";
|
||||
const CODEX_CLI_DEFAULT_MODEL_REF = "codex-cli/gpt-5.5";
|
||||
const CODEX_APP_SERVER_DEFAULT_MODEL_REF = "openai/gpt-5.5";
|
||||
|
||||
export function parseCrestodianOperation(input: string): CrestodianOperation {
|
||||
const trimmed = input.trim();
|
||||
|
|
@ -385,7 +385,7 @@ function chooseSetupModel(
|
|||
return { model: CLAUDE_CLI_DEFAULT_MODEL_REF, source: "Claude Code CLI" };
|
||||
}
|
||||
if (overview.tools.codex.found) {
|
||||
return { model: CODEX_CLI_DEFAULT_MODEL_REF, source: "Codex CLI" };
|
||||
return { model: CODEX_APP_SERVER_DEFAULT_MODEL_REF, source: "Codex app-server" };
|
||||
}
|
||||
return { source: "none" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,22 +114,21 @@ describe("gateway cli backend live helpers", () => {
|
|||
expect(shouldRunCliModelSwitchProbe("codex-cli", "codex-cli/gpt-5.5")).toBe(false);
|
||||
});
|
||||
|
||||
it("configures legacy CLI model refs as canonical provider models plus CLI runtime", async () => {
|
||||
it("rejects removed Codex CLI refs for live CLI backend selection", async () => {
|
||||
const { resolveCliBackendLiveModelSelection } =
|
||||
await import("./gateway-cli-backend.live-helpers.js");
|
||||
|
||||
expect(
|
||||
expect(() =>
|
||||
resolveCliBackendLiveModelSelection({
|
||||
rawModel: "codex-cli/gpt-5.4",
|
||||
defaultProvider: "claude-cli",
|
||||
}),
|
||||
).toEqual({
|
||||
providerId: "codex-cli",
|
||||
cliModelKey: "codex-cli/gpt-5.4",
|
||||
configModelKey: "openai/gpt-5.4",
|
||||
configModelSwitchTarget: undefined,
|
||||
agentRuntime: { id: "codex-cli" },
|
||||
});
|
||||
).toThrow(/codex-cli\/\.\.\. is no longer supported/u);
|
||||
});
|
||||
|
||||
it("configures legacy CLI model refs as canonical provider models plus CLI runtime", async () => {
|
||||
const { resolveCliBackendLiveModelSelection } =
|
||||
await import("./gateway-cli-backend.live-helpers.js");
|
||||
|
||||
expect(
|
||||
resolveCliBackendLiveModelSelection({
|
||||
|
|
|
|||
|
|
@ -73,6 +73,11 @@ export function resolveCliBackendLiveModelSelection(params: {
|
|||
}
|
||||
|
||||
const migrated = migrateLegacyRuntimeModelRef(params.rawModel);
|
||||
if (migrated?.legacyProvider === "codex-cli") {
|
||||
throw new Error(
|
||||
"OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/... is no longer supported. Use a supported CLI backend such as claude-cli or google-gemini-cli.",
|
||||
);
|
||||
}
|
||||
if (migrated?.cli) {
|
||||
return {
|
||||
providerId: migrated.runtime,
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ function createManifestRegistryFixture(): PluginManifestRegistry {
|
|||
origin: "bundled",
|
||||
enabledByDefault: true,
|
||||
providers: ["openai", "openai-codex"],
|
||||
cliBackends: ["codex-cli"],
|
||||
cliBackends: [],
|
||||
contracts: {
|
||||
imageGenerationProviders: ["openai"],
|
||||
videoGenerationProviders: ["openai"],
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ describe("loadPluginLookUpTable", () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
cliBackends: ["codex-cli"],
|
||||
cliBackends: [],
|
||||
setup: {
|
||||
providers: [{ id: "openai" }],
|
||||
},
|
||||
|
|
@ -248,7 +248,7 @@ describe("loadPluginLookUpTable", () => {
|
|||
expect(table.owners.providers.get("openai")).toEqual(["openai"]);
|
||||
expect(table.owners.modelCatalogProviders.get("openai")).toEqual(["openai"]);
|
||||
expect(table.owners.modelCatalogProviders.get("azure-openai-responses")).toEqual(["openai"]);
|
||||
expect(table.owners.cliBackends.get("codex-cli")).toEqual(["openai"]);
|
||||
expect(table.owners.cliBackends.get("codex-cli")).toBeUndefined();
|
||||
expect(table.owners.setupProviders.get("openai")).toEqual(["openai"]);
|
||||
expect(table.owners.commandAliases.get("telegram-send")).toEqual(["telegram"]);
|
||||
expect(table.owners.contracts.get("tools")).toEqual(["telegram"]);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,6 @@ function setOwningProviderManifestPlugins() {
|
|||
createManifestProviderPlugin({
|
||||
id: "openai",
|
||||
providerIds: ["openai", "openai-codex"],
|
||||
cliBackends: ["codex-cli"],
|
||||
modelSupport: {
|
||||
modelPrefixes: ["gpt-", "o1", "o3", "o4"],
|
||||
},
|
||||
|
|
@ -111,7 +110,6 @@ function setOwningProviderManifestPluginsWithWorkspace() {
|
|||
createManifestProviderPlugin({
|
||||
id: "openai",
|
||||
providerIds: ["openai", "openai-codex"],
|
||||
cliBackends: ["codex-cli"],
|
||||
modelSupport: {
|
||||
modelPrefixes: ["gpt-", "o1", "o3", "o4"],
|
||||
},
|
||||
|
|
@ -516,7 +514,7 @@ describe("resolvePluginProviders", () => {
|
|||
setOwningProviderManifestPlugins();
|
||||
|
||||
expectOwningPluginIds("claude-cli", ["anthropic"]);
|
||||
expectOwningPluginIds("codex-cli", ["openai"]);
|
||||
expectOwningPluginIds("codex-cli");
|
||||
});
|
||||
|
||||
it("reflects provider ownership manifest changes on the next lookup", () => {
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ describe("docker build helper", () => {
|
|||
expect(liveCliBackend).toContain(
|
||||
'OPENCLAW_LIVE_DOCKER_REPO_ROOT="$ROOT_DIR" "$TRUSTED_HARNESS_DIR/scripts/test-live-build-docker.sh"',
|
||||
);
|
||||
expect(liveCliBackend).toContain("direct Codex CLI probe failed before OpenClaw gateway smoke");
|
||||
expect(liveCliBackend).toContain("==> Direct Codex CLI probe ok");
|
||||
expect(liveCliBackend).toContain("codex-cli is no longer a bundled CLI backend");
|
||||
expect(liveCliBackend).not.toContain("==> Direct Codex CLI probe ok");
|
||||
expect(liveCliBackend).not.toContain(
|
||||
'echo "==> Reuse live-test image: $LIVE_IMAGE_NAME (OPENCLAW_SKIP_DOCKER_BUILD=1)"',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -115,7 +115,9 @@ describe("package acceptance workflow", () => {
|
|||
expect(workflow).toContain("update-channel-switch skill-install update-corrupt-plugin");
|
||||
expect(workflow).toContain("update-corrupt-plugin upgrade-survivor");
|
||||
expect(workflow).toContain("published-upgrade-survivor");
|
||||
expect(workflow).toContain("published-upgrade-survivor root-managed-vps-upgrade update-restart-auth");
|
||||
expect(workflow).toContain(
|
||||
"published-upgrade-survivor root-managed-vps-upgrade update-restart-auth",
|
||||
);
|
||||
expect(workflow).toContain("plugins-offline plugin-update");
|
||||
expect(workflow).toContain("include_release_path_suites=true");
|
||||
expect(workflow).not.toContain("telegram_mode requires source=npm");
|
||||
|
|
@ -382,10 +384,12 @@ describe("package artifact reuse", () => {
|
|||
expect(workflow).toContain("OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter");
|
||||
expect(workflow).toContain("OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai");
|
||||
expect(workflow).toContain("inputs.live_suite_filter == 'live-gateway-advisory-docker'");
|
||||
expect(workflow).toContain("OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4");
|
||||
expect(workflow).toContain("OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6");
|
||||
expect(workflow).toContain("OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key");
|
||||
expect(workflow).toContain("OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1");
|
||||
expect((workflow.match(/service_tier=\\"fast\\"/g) ?? []).length).toBeGreaterThanOrEqual(2);
|
||||
expect(workflow).not.toContain("OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1");
|
||||
expect(workflow).not.toContain('service_tier=\\"fast\\"');
|
||||
expect(workflow).not.toContain("OPENCLAW_LIVE_CLI_BACKEND_ARGS=");
|
||||
expect(workflow).not.toContain("OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=");
|
||||
expect(workflow).not.toContain(
|
||||
'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","--skip-git-repo-check"]',
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue