mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor: unify OpenAI provider identity (#88451)
* refactor: unify OpenAI provider identity * refactor: move legacy oauth sidecar doctor helpers * test: align OpenAI fixtures after rebase * test: clean OpenAI provider unification * fix: finish OpenAI provider cleanup * fix: finish OpenAI cleanup follow-through * fix: finish OpenAI CI cleanup
This commit is contained in:
committed by
GitHub
parent
8d6a6e9d03
commit
4c33aaa86c
@@ -2871,7 +2871,7 @@ fun providerDisplayName(provider: String): String =
|
||||
when (provider.trim().lowercase()) {
|
||||
"openai" -> "OpenAI"
|
||||
"openrouter" -> "OpenRouter"
|
||||
"openai-codex", "codex" -> "Codex"
|
||||
"codex" -> "Codex"
|
||||
"ollama", "ollama-local" -> "Ollama Local"
|
||||
else ->
|
||||
provider
|
||||
|
||||
@@ -270,7 +270,7 @@ private fun providerPriority(provider: String): Int =
|
||||
"google" -> 2
|
||||
"openrouter" -> 3
|
||||
"ollama", "ollama-local" -> 4
|
||||
"codex", "openai-codex" -> 5
|
||||
"codex" -> 5
|
||||
else -> 100
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ struct MenuSessionsInjectorTests {
|
||||
plan: "Pro",
|
||||
error: nil),
|
||||
GatewayUsageProvider(
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
displayName: "Codex",
|
||||
windows: [GatewayUsageWindow(label: "day", usedPercent: 3, resetAt: nil)],
|
||||
plan: nil,
|
||||
|
||||
@@ -2192,7 +2192,7 @@ extension TestChatTransportState {
|
||||
path: nil,
|
||||
count: 1,
|
||||
defaults: OpenClawChatSessionsDefaults(
|
||||
modelProvider: "openai-codex",
|
||||
modelProvider: "openai",
|
||||
model: "gpt-5.5",
|
||||
contextTokens: nil,
|
||||
thinkingLevels: [
|
||||
|
||||
+1
-1
@@ -236,7 +236,7 @@ The bundled Hermes provider detects state at `~/.hermes` by default. Use `--from
|
||||
- Memory config defaults for OpenClaw file memory, plus archive or manual-review items for external memory providers such as Honcho.
|
||||
- Skills that include a `SKILL.md` file under `skills/<name>/`.
|
||||
- Per-skill config values from `skills.config`.
|
||||
- Supported OAuth credentials from Hermes `auth.json` and OpenCode OpenAI OAuth credentials from OpenCode `auth.json` when interactive credential migration is accepted, or when `--include-secrets` is set.
|
||||
- OpenCode OpenAI OAuth credentials from OpenCode `auth.json` when interactive credential migration is accepted, or when `--include-secrets` is set. Hermes `auth.json` OAuth entries are legacy state reported for manual OpenAI reauth or doctor repair.
|
||||
- Supported API keys and tokens from Hermes `.env` and OpenCode `auth.json` when interactive credential migration is accepted, or when `--include-secrets` is set.
|
||||
|
||||
### Supported `.env` keys
|
||||
|
||||
+1
-1
@@ -193,7 +193,7 @@ specific configured agent store. The parent `--agent` flag is honored by
|
||||
For OpenAI models, `--provider openai` defaults to ChatGPT/Codex account login.
|
||||
Use `--method api-key` only when you want to add an OpenAI API-key profile,
|
||||
usually as a backup for Codex subscription limits. Run `openclaw doctor --fix`
|
||||
to migrate older `openai-codex` auth/profile state to `openai`.
|
||||
to migrate older legacy OpenAI Codex prefix auth/profile state to `openai`.
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ Most confusion comes from several different surfaces sharing the Codex name:
|
||||
|
||||
Those surfaces are intentionally independent. Enabling the `codex` plugin makes
|
||||
the native app-server features available; `openclaw doctor --fix` owns legacy
|
||||
`openai-codex/*` route repair and stale session pin cleanup. Selecting
|
||||
legacy Codex route repair and stale session pin cleanup. Selecting
|
||||
`openai/*` for an agent model now means "run this through Codex" unless a
|
||||
non-agent OpenAI API surface is being used.
|
||||
|
||||
@@ -97,7 +97,7 @@ This is the agent-facing decision tree:
|
||||
as `openai/<model>` and set provider/model runtime policy to
|
||||
`agentRuntime.id: "openclaw"`. A selected `openai` OAuth profile is routed
|
||||
internally through OpenClaw's Codex-auth transport.
|
||||
4. If legacy config still contains **`openai-codex/*` model refs**, repair it to
|
||||
4. If legacy config still contains **legacy Codex model refs**, repair it to
|
||||
`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.
|
||||
@@ -202,7 +202,7 @@ keeping the public model ref as `openai/*`. Stale OpenAI runtime session pins ar
|
||||
ignored by runtime selection and can be cleaned with `openclaw doctor --fix`.
|
||||
|
||||
If `openclaw doctor` warns that the `codex` plugin is enabled while
|
||||
`openai-codex/*` remains in config, treat that as legacy route state. Run
|
||||
legacy Codex model refs remain in config, treat that as legacy route state. Run
|
||||
`openclaw doctor --fix` to rewrite it to `openai/*` with the Codex runtime.
|
||||
|
||||
## GitHub Copilot agent runtime
|
||||
|
||||
@@ -30,7 +30,7 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
|
||||
OpenAI-family routes are prefix-specific:
|
||||
|
||||
- `openai/<model>` uses the native Codex app-server harness for agent turns by default. This is the usual ChatGPT/Codex subscription setup.
|
||||
- `openai-codex/<model>` is legacy config that doctor rewrites to `openai/<model>`.
|
||||
- legacy Codex model refs are legacy config that doctor rewrites to `openai/<model>`.
|
||||
- `openai/<model>` plus provider/model `agentRuntime.id: "openclaw"` uses OpenClaw's built-in runtime for explicit API-key or compatibility routes.
|
||||
|
||||
See [OpenAI](/providers/openai) and [Codex harness](/plugins/codex-harness). If the provider/runtime split is confusing, read [Agent runtimes](/concepts/agent-runtimes) first.
|
||||
@@ -149,7 +149,7 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
|
||||
- Policy note: OpenAI Codex OAuth is explicitly supported for external tools/workflows like OpenClaw.
|
||||
- For the common subscription plus native Codex runtime route, sign in with `openai` auth and configure `openai/gpt-5.5`; 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.
|
||||
- `openai-codex/gpt-*` refs remain a legacy OpenAI Codex route. Prefer `openai/gpt-5.5` on the native Codex runtime for new agent config, and run `openclaw doctor --fix` when you want to migrate old `openai-codex/*` refs to canonical `openai/*` refs.
|
||||
- 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.
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -414,7 +414,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
- short cooldowns (rate limits/timeouts/auth failures)
|
||||
- longer disables (billing/credit failures)
|
||||
|
||||
Legacy Codex OAuth profiles whose tokens live in macOS Keychain (older onboarding before the file-based sidecar layout) are not picked up by the embedded runtime path — that path runs with `allowKeychainPrompt: false` and cannot trigger a Keychain prompt. Affected users will see a one-shot `log.warn` from the legacy sidecar loader naming `openclaw doctor --fix` and macOS Keychain (instead of the credential silently falling through to a downstream `No API key found for provider "openai-codex"`). Run `openclaw doctor --fix` once from an interactive terminal to migrate Keychain-backed legacy tokens inline into `auth-profiles.json`; after that, embedded turns (Telegram, cron, sub-agent dispatch) resolve them like any other inline OAuth profile.
|
||||
Legacy Codex OAuth profiles whose tokens live in macOS Keychain (older onboarding before the file-based sidecar layout) are repaired only by doctor. Run `openclaw doctor --fix` once from an interactive terminal to migrate Keychain-backed legacy tokens inline into `auth-profiles.json`; after that, embedded turns (Telegram, cron, sub-agent dispatch) resolve them as canonical OpenAI OAuth profiles.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="6. Hooks model validation">
|
||||
|
||||
@@ -596,28 +596,28 @@ and troubleshooting see the main [FAQ](/help/faq).
|
||||
<Accordion title="How does Codex auth work?">
|
||||
OpenClaw supports **OpenAI Code (Codex)** via OAuth (ChatGPT sign-in). Use
|
||||
`openai/gpt-5.5` for the common setup: ChatGPT/Codex subscription auth plus
|
||||
native Codex app-server execution. `openai-codex/gpt-*` model refs are
|
||||
native Codex app-server execution. Legacy Codex GPT refs are
|
||||
legacy config repaired by `openclaw doctor --fix`. Direct OpenAI API-key
|
||||
access remains available for non-agent OpenAI API surfaces and for agent
|
||||
models through an ordered `openai` API-key profile.
|
||||
See [Model providers](/concepts/model-providers) and [Onboarding (CLI)](/start/wizard).
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Why does OpenClaw still mention openai-codex?">
|
||||
<Accordion title="Why does OpenClaw still mention legacy OpenAI Codex prefix?">
|
||||
`openai` is the provider and auth-profile id for both OpenAI API keys and
|
||||
ChatGPT/Codex OAuth. You may still see `openai-codex` in legacy config and
|
||||
ChatGPT/Codex OAuth. You may still see legacy OpenAI Codex prefix in legacy config and
|
||||
migration warnings.
|
||||
Older configs also used it as a model prefix:
|
||||
|
||||
- `openai/gpt-5.5` = ChatGPT/Codex subscription auth with native Codex runtime for agent turns
|
||||
- `openai-codex/gpt-5.5` = legacy model route repaired by `openclaw doctor --fix`
|
||||
- legacy Codex GPT-5.5 ref = legacy model 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
|
||||
- `openai-codex:...` = legacy auth profile id migrated by `openclaw doctor --fix`
|
||||
- legacy Codex auth profile ids = legacy auth profile id migrated by `openclaw doctor --fix`
|
||||
|
||||
If you want the direct OpenAI Platform billing/limit path, set
|
||||
`OPENAI_API_KEY`. If you want ChatGPT/Codex subscription auth, sign in with
|
||||
`openclaw models auth login --provider openai`. Keep the model ref as
|
||||
`openai/gpt-5.5`; `openai-codex/*` model refs are legacy config that
|
||||
`openai/gpt-5.5`; legacy Codex model refs are legacy config that
|
||||
`openclaw doctor --fix` rewrites.
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -66,7 +66,7 @@ Imports require a fresh OpenClaw setup. If you already have local OpenClaw state
|
||||
Skills with a `SKILL.md` file under `skills/<name>/` are copied, along with per-skill config values from `skills.config`.
|
||||
</Accordion>
|
||||
<Accordion title="Auth credentials">
|
||||
Interactive `openclaw migrate` asks before importing auth credentials, with yes selected by default. Accepted imports include supported OAuth credentials from Hermes `auth.json`, OpenCode OpenAI OAuth credentials from OpenCode `auth.json`, OpenCode and GitHub Copilot entries from OpenCode `auth.json`, and the [supported `.env` keys](/cli/migrate#supported-env-keys). Use `--include-secrets` for non-interactive `openclaw migrate` credential import, `--no-auth-credentials` to skip it, or onboarding `--import-secrets` when importing from the onboarding wizard.
|
||||
Interactive `openclaw migrate` asks before importing auth credentials, with yes selected by default. Accepted imports include OpenCode OpenAI OAuth credentials from OpenCode `auth.json`, OpenCode and GitHub Copilot entries from OpenCode `auth.json`, and the [supported `.env` keys](/cli/migrate#supported-env-keys). Hermes `auth.json` OAuth entries are legacy state and are surfaced as manual reauth/doctor work instead of imported into live auth. Use `--include-secrets` for non-interactive `openclaw migrate` credential import, `--no-auth-credentials` to skip it, or onboarding `--import-secrets` when importing from the onboarding wizard.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -137,7 +137,7 @@ If a conflict surfaces mid-apply (for example, an unexpected race on a config fi
|
||||
|
||||
Interactive `openclaw migrate` asks whether to import detected auth credentials, with yes selected by default.
|
||||
|
||||
- Accepting the prompt imports supported OAuth credentials from Hermes `auth.json`, OpenCode OpenAI OAuth credentials from OpenCode `auth.json`, OpenCode and GitHub Copilot entries from OpenCode `auth.json`, and the [supported `.env` keys](/cli/migrate#supported-env-keys).
|
||||
- Accepting the prompt imports OpenCode OpenAI OAuth credentials from OpenCode `auth.json`, OpenCode and GitHub Copilot entries from OpenCode `auth.json`, and the [supported `.env` keys](/cli/migrate#supported-env-keys). Hermes `auth.json` OAuth entries are reported for manual OpenAI reauth or doctor repair.
|
||||
- Use `--no-auth-credentials` or choose no at the prompt to import non-secret state only.
|
||||
- Use `--include-secrets` when running unattended with `--yes`.
|
||||
- Use onboarding `--import-secrets` when importing credentials from the onboarding wizard.
|
||||
|
||||
@@ -17,9 +17,10 @@ selection, OpenClaw dynamic tools, approvals, media delivery, and the visible
|
||||
transcript mirror.
|
||||
|
||||
The normal setup uses canonical OpenAI model refs such as `openai/gpt-5.5`.
|
||||
Do not configure `openai-codex/gpt-*` model refs. Put OpenAI agent auth order
|
||||
under `auth.order.openai`; older `openai-codex:*` profiles and
|
||||
`auth.order.openai-codex` entries remain supported for existing installs.
|
||||
Do not configure legacy Codex GPT refs. Put OpenAI agent auth order
|
||||
under `auth.order.openai`; older legacy Codex auth profile ids and
|
||||
legacy Codex auth order entries are legacy state repaired by
|
||||
`openclaw doctor --fix`.
|
||||
|
||||
When no OpenClaw sandbox is active, OpenClaw starts Codex app-server threads
|
||||
with Codex native code mode enabled while leaving code-mode-only off by default.
|
||||
@@ -122,8 +123,8 @@ harness options in OpenClaw config, and use the CLI only for Codex auth:
|
||||
|
||||
Use `openai/gpt-*` model refs for Codex-backed OpenAI agent turns. Prefer
|
||||
`auth.order.openai` for subscription-first/API-key-backup ordering. Existing
|
||||
`openai-codex:*` auth profiles and `auth.order.openai-codex` remain valid, but
|
||||
do not write new `openai-codex/gpt-*` model refs.
|
||||
legacy Codex auth profile ids and legacy Codex auth order are doctor-only
|
||||
legacy state; do not write new legacy Codex GPT refs.
|
||||
|
||||
Do not set `compaction.model` or `compaction.provider` on Codex-backed agents.
|
||||
Codex compacts through its native app-server thread state, so OpenClaw ignores
|
||||
@@ -195,7 +196,7 @@ the harness and account. If `/status` is surprising, see
|
||||
Keep provider refs and runtime policy separate:
|
||||
|
||||
- Use `openai/gpt-*` for OpenAI agent turns through Codex.
|
||||
- Do not use `openai-codex/gpt-*` in config. Run `openclaw doctor --fix` to
|
||||
- Do not use legacy Codex GPT refs in config. Run `openclaw doctor --fix` to
|
||||
repair legacy refs and stale session route pins.
|
||||
- `agentRuntime.id: "codex"` is optional for normal OpenAI auto mode, but useful
|
||||
when a deployment should fail closed if Codex is unavailable.
|
||||
@@ -223,13 +224,13 @@ Common command routing:
|
||||
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-*` plus enabled `codex` plugin | `/status` shows `Runtime: OpenAI Codex` | Recommended path |
|
||||
| Fail closed if Codex is unavailable | Provider or model `agentRuntime.id: "codex"` | Turn fails instead of embedded fallback | Use for Codex-only deployments |
|
||||
| Direct OpenAI API-key traffic through OpenClaw | Provider or model `agentRuntime.id: "openclaw"` and normal OpenAI auth | `/status` shows OpenClaw runtime | Use only when OpenClaw is intentional |
|
||||
| Legacy config | `openai-codex/gpt-*` | `openclaw doctor --fix` rewrites it | Do not write new config this way |
|
||||
| Legacy config | legacy Codex GPT refs | `openclaw doctor --fix` rewrites it | Do not write new config this way |
|
||||
| ACP/acpx Codex adapter | ACP `sessions_spawn({ runtime: "acp" })` | ACP task/session status | Separate from native Codex harness |
|
||||
|
||||
`agents.defaults.imageModel` follows the same prefix split. Use `openai/gpt-*`
|
||||
for the normal OpenAI route and `codex/gpt-*` only when image understanding
|
||||
should run through a bounded Codex app-server turn. Do not use
|
||||
`openai-codex/gpt-*`; doctor rewrites that legacy prefix to `openai/gpt-*`.
|
||||
legacy Codex GPT refs; doctor rewrites that legacy prefix to `openai/gpt-*`.
|
||||
|
||||
## Deployment patterns
|
||||
|
||||
@@ -445,7 +446,7 @@ Auth is selected in this order:
|
||||
|
||||
1. Ordered OpenAI auth profiles for the agent, preferably under
|
||||
`auth.order.openai`. Run `openclaw doctor --fix` to migrate older
|
||||
`openai-codex:*` profile ids and `auth.order.openai-codex`.
|
||||
legacy Codex auth profile ids and legacy Codex auth order.
|
||||
2. The app-server's existing account in that agent's Codex home.
|
||||
3. For local stdio app-server launches only, `CODEX_API_KEY`, then
|
||||
`OPENAI_API_KEY`, when no app-server account is present and OpenAI auth is
|
||||
@@ -705,7 +706,7 @@ Ask affected collaborators to run this read-only command on their OpenClaw host:
|
||||
|
||||
```bash
|
||||
(
|
||||
pattern='openai/gpt-5\.[45]|agentRuntime(\.id)?|harnessRuntime|Runtime: OpenAI Codex|openai-codex|resolveSelectedOpenAIRuntimeProvider|candidateProvider[": ]+openai|status[": ]+401|Incorrect API key|No API key|api-key path|API-key path|OAuth'
|
||||
pattern='openai/gpt-5\.[45]|openai[-]codex|agentRuntime(\.id)?|harnessRuntime|Runtime: OpenAI Codex|legacy OpenAI Codex prefix|resolveSelectedOpenAIRuntimeProvider|candidateProvider[": ]+openai|status[": ]+401|Incorrect API key|No API key|api-key path|API-key path|OAuth'
|
||||
|
||||
if ls /tmp/openclaw/openclaw-*.log >/dev/null 2>&1; then
|
||||
grep -E -i -n "$pattern" /tmp/openclaw/openclaw-*.log 2>/dev/null || true
|
||||
@@ -729,7 +730,7 @@ Useful excerpts usually include `openai/gpt-5.5` or `openai/gpt-5.4`,
|
||||
`No API key` result. A corrected run should show the OpenAI OAuth
|
||||
path instead of a plain OpenAI API-key failure.
|
||||
|
||||
**Legacy `openai-codex/*` config remains:** run `openclaw doctor --fix`.
|
||||
**Legacy Codex model refs config remains:** run `openclaw doctor --fix`.
|
||||
Doctor rewrites legacy model refs to `openai/*`, removes stale session and
|
||||
whole-agent runtime pins, and preserves existing auth-profile overrides.
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ The bundled `codex` harness is the native Codex mode for embedded OpenClaw
|
||||
agent turns. Enable the bundled `codex` plugin first, and include `codex` in
|
||||
`plugins.allow` if your config uses a restrictive allowlist. Native app-server
|
||||
configs should use `openai/gpt-*`; OpenAI agent turns select the Codex harness
|
||||
by default. Legacy `openai-codex/*` routes should be repaired with
|
||||
by default. Legacy Codex model refs routes should be repaired with
|
||||
`openclaw doctor --fix`, and legacy `codex/*` model refs remain compatibility
|
||||
aliases for the native harness.
|
||||
|
||||
|
||||
+16
-16
@@ -21,7 +21,7 @@ surfaces such as images, embeddings, speech, and realtime.
|
||||
OpenAI API-key backup when you intentionally want API-key auth.
|
||||
- **Non-agent OpenAI APIs** - direct OpenAI Platform access with usage-based
|
||||
billing through `OPENAI_API_KEY` or OpenAI API-key onboarding.
|
||||
- **Legacy config** - `openai-codex/*` model refs are repaired by
|
||||
- **Legacy config** - legacy Codex model refs are repaired by
|
||||
`openclaw doctor --fix` to `openai/*` plus the Codex runtime.
|
||||
|
||||
OpenAI explicitly supports subscription OAuth usage in external tools and workflows like OpenClaw.
|
||||
@@ -49,7 +49,7 @@ The names are similar but not interchangeable:
|
||||
| Name you see | Layer | Meaning |
|
||||
| --------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `openai` | Provider prefix | Canonical OpenAI model route; agent turns use the Codex runtime. |
|
||||
| `openai-codex` | Legacy prefix | Older model/profile namespace. `openclaw doctor --fix` migrates it to `openai`. |
|
||||
| legacy OpenAI Codex prefix | Legacy prefix | Older model/profile namespace. `openclaw doctor --fix` migrates it to `openai`. |
|
||||
| `codex` plugin | Plugin | Bundled OpenClaw plugin that provides native Codex app-server runtime and `/codex` chat controls. |
|
||||
| provider/model `agentRuntime.id: codex` | Agent runtime | Force the native Codex app-server harness for matching embedded turns. |
|
||||
| `/codex ...` | Chat command set | Bind/control Codex app-server threads from a conversation. |
|
||||
@@ -58,8 +58,8 @@ The names are similar but not interchangeable:
|
||||
This means a config can intentionally contain `openai/*` model refs while auth
|
||||
profiles point at either API-key or ChatGPT/Codex OAuth credentials. Use
|
||||
`auth.order.openai` for config; `openclaw doctor --fix` rewrites legacy
|
||||
`openai-codex/*` model refs, `openai-codex:*` profile ids, and
|
||||
`auth.order.openai-codex` to the canonical OpenAI route.
|
||||
legacy Codex model refs, legacy Codex auth profile ids, and
|
||||
legacy Codex auth order to the canonical OpenAI route.
|
||||
|
||||
<Note>
|
||||
GPT-5.5 is available through both direct OpenAI Platform API-key access and
|
||||
@@ -75,7 +75,7 @@ OpenClaw runtime config remains available as an opt-in compatibility route. When
|
||||
explicitly selected with an `openai` OAuth profile, OpenClaw keeps the
|
||||
public model ref as `openai/*` and routes internally through the Codex-auth
|
||||
transport. Run `openclaw doctor --fix` to repair stale
|
||||
`openai-codex/*`, `codex-cli/*`, or old runtime session pins that do not come from
|
||||
legacy Codex model refs, `codex-cli/*`, or old runtime 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 OAuth | Yes |
|
||||
| Legacy Codex model refs | `openai-codex/<model>` or `codex-cli/<model>` | Repaired by doctor to `openai/<model>` |
|
||||
| Legacy Codex model refs | legacy Codex model refs 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 |
|
||||
@@ -185,7 +185,7 @@ Choose your preferred auth method and follow the setup steps.
|
||||
auth for an agent model, create a Codex-compatible API-key profile and order
|
||||
it with `auth.order.openai`; `OPENAI_API_KEY` remains the direct fallback for
|
||||
non-agent OpenAI API surfaces. Run `openclaw doctor --fix` to migrate older
|
||||
`auth.order.openai-codex` entries.
|
||||
legacy Codex auth order entries.
|
||||
</Note>
|
||||
|
||||
### Config example
|
||||
@@ -266,12 +266,12 @@ 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: "openclaw"` | OpenClaw embedded runtime with internal Codex-auth transport | Selected `openai` OAuth profile |
|
||||
| `openai-codex/gpt-5.5` | repaired by doctor | Legacy route rewritten to `openai/gpt-5.5` | Migrated OpenAI OAuth profile |
|
||||
| legacy Codex GPT-5.5 ref | repaired by doctor | Legacy route rewritten to `openai/gpt-5.5` | Migrated OpenAI OAuth profile |
|
||||
| `codex-cli/gpt-5.5` | repaired by doctor | Legacy CLI route rewritten to `openai/gpt-5.5` | Codex app-server auth |
|
||||
|
||||
<Warning>
|
||||
Prefer `openai/gpt-5.5` for new subscription-backed agent config. Older
|
||||
`openai-codex/gpt-*` refs are legacy OpenClaw routes, not the native Codex runtime
|
||||
legacy Codex GPT refs are legacy OpenClaw routes, not the native Codex runtime
|
||||
path; run `openclaw doctor --fix` when you want to migrate them to canonical
|
||||
`openai/*` refs. `gpt-5.3-codex-spark` remains limited to accounts whose
|
||||
Codex subscription catalog advertises that model; direct OpenAI API-key and
|
||||
@@ -279,11 +279,11 @@ Choose your preferred auth method and follow the setup steps.
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The `openai-codex/*` model prefix is legacy config repaired by doctor. For
|
||||
The legacy Codex model prefix is legacy config repaired by doctor. For
|
||||
the common subscription plus native runtime setup, sign in with Codex auth
|
||||
but keep the model ref as `openai/gpt-5.5`. New config should put OpenAI
|
||||
agent auth order under `auth.order.openai`; doctor migrates older
|
||||
`auth.order.openai-codex` entries.
|
||||
legacy Codex auth order entries.
|
||||
</Note>
|
||||
|
||||
### Config example
|
||||
@@ -345,7 +345,7 @@ Choose your preferred auth method and follow the setup steps.
|
||||
openclaw models auth list --agent <id> --provider openai
|
||||
```
|
||||
|
||||
If an older config still has `openai-codex/gpt-*` or a stale OpenAI runtime
|
||||
If an older config still has legacy Codex GPT refs or a stale OpenAI runtime
|
||||
session pin without explicit runtime config, repair it:
|
||||
|
||||
```bash
|
||||
@@ -370,7 +370,7 @@ Choose your preferred auth method and follow the setup steps.
|
||||
```
|
||||
|
||||
`openai/*` is the model route for OpenAI agent turns through Codex. Run
|
||||
`openclaw doctor --fix` to migrate older `openai-codex` profile ids and
|
||||
`openclaw doctor --fix` to migrate older legacy OpenAI Codex prefix profile ids and
|
||||
order entries before relying on profile ordering.
|
||||
|
||||
### Status indicator
|
||||
@@ -382,7 +382,7 @@ Choose your preferred auth method and follow the setup steps.
|
||||
|
||||
### Doctor warning
|
||||
|
||||
If `openai-codex/*` routes or stale OpenAI runtime pins remain in config or
|
||||
If legacy Codex model refs or stale OpenAI runtime pins remain in config or
|
||||
session state, `openclaw doctor --fix` rewrites them to `openai/*` with the
|
||||
Codex runtime unless OpenClaw is explicitly configured.
|
||||
|
||||
@@ -432,7 +432,7 @@ still account-based. OpenClaw selects auth in this order:
|
||||
|
||||
1. Ordered OpenAI auth profiles for the agent, preferably under
|
||||
`auth.order.openai`. Run `openclaw doctor --fix` to migrate older
|
||||
`openai-codex:*` profiles and `auth.order.openai-codex`.
|
||||
legacy Codex auth profile ids and legacy Codex auth order.
|
||||
2. The app-server's existing account, such as a local Codex CLI ChatGPT sign-in.
|
||||
3. For local stdio app-server launches only, `CODEX_API_KEY`, then
|
||||
`OPENAI_API_KEY`, when the app-server reports no account and still requires
|
||||
@@ -571,7 +571,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
|
||||
|
||||
## GPT-5 prompt contribution
|
||||
|
||||
OpenClaw adds a shared GPT-5 prompt contribution for GPT-5-family runs on OpenClaw-assembled prompt surfaces. It applies by model id, so OpenClaw/provider routes such as legacy pre-repair refs (`openai-codex/gpt-5.5`), `openrouter/openai/gpt-5.5`, `opencode/gpt-5.5`, and other compatible GPT-5 refs receive the same overlay. Older GPT-4.x models do not.
|
||||
OpenClaw adds a shared GPT-5 prompt contribution for GPT-5-family runs on OpenClaw-assembled prompt surfaces. It applies by model id, so OpenClaw/provider routes such as legacy pre-repair refs (legacy Codex GPT-5.5 ref), `openrouter/openai/gpt-5.5`, `opencode/gpt-5.5`, and other compatible GPT-5 refs receive the same overlay. Older GPT-4.x models do not.
|
||||
|
||||
The bundled native Codex harness does not receive this OpenClaw GPT-5 overlay through Codex app-server developer instructions. Native Codex keeps Codex-owned base, model, and project-doc behavior, while OpenClaw disables Codex's built-in personality for native threads so agent workspace personality files stay authoritative. OpenClaw contributes only runtime context such as channel delivery, OpenClaw dynamic tools, ACP delegation, workspace context, and OpenClaw skills.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard).
|
||||
- **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.
|
||||
- **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 `openai-codex/*`.
|
||||
- Sets `agents.defaults.model` to `openai/gpt-5.5` when model is unset, `openai/*`, or legacy Codex model refs.
|
||||
- **xAI (Grok) OAuth / API key**: signs in with xAI OAuth when chosen, or prompts for `XAI_API_KEY` on the API-key path, and configures xAI as a model provider.
|
||||
- **OpenCode**: prompts for `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`, get it at https://opencode.ai/auth) and lets you pick the Zen or Go catalog.
|
||||
- **Ollama**: offers **Cloud + Local**, **Cloud only**, or **Local only** first. `Cloud only` prompts for `OLLAMA_API_KEY` and uses `https://ollama.com`; the host-backed modes prompt for the Ollama base URL, discover available models, and auto-pull the selected local model when needed; `Cloud + Local` also checks whether that Ollama host is signed in for cloud access.
|
||||
|
||||
@@ -150,7 +150,7 @@ What you set:
|
||||
<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 `openai-codex/*`.
|
||||
Sets `agents.defaults.model` to `openai/gpt-5.5` when model is unset, `openai/*`, or legacy Codex model refs.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="xAI (Grok) OAuth">
|
||||
|
||||
@@ -183,7 +183,7 @@ Quick `/acp` flow from chat:
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Model / provider / runtime selection cheat sheet">
|
||||
- `openai-codex/*` - legacy Codex OAuth/subscription model route repaired by doctor.
|
||||
- legacy Codex model refs - legacy Codex OAuth/subscription model route repaired by doctor.
|
||||
- `openai/*` - native Codex app-server embedded runtime for OpenAI agent turns.
|
||||
- `/codex ...` - native Codex conversation control.
|
||||
- `/acp ...` or `runtime: "acp"` - explicit ACP/acpx control.
|
||||
|
||||
@@ -186,7 +186,7 @@ Key policy rules:
|
||||
surfaces, such as a provider/model ref, channel config, CLI backend, or agent
|
||||
harness runtime.
|
||||
- OpenAI-family Codex routing keeps provider and runtime plugin boundaries
|
||||
separate: `openai-codex/*` is legacy config repaired by doctor, while the bundled
|
||||
separate: legacy Codex model refs are legacy config repaired by doctor, while the bundled
|
||||
`codex` plugin owns Codex app-server runtime for canonical `openai/*` agent
|
||||
refs, explicit `agentRuntime.id: "codex"`, and legacy `codex/*` refs.
|
||||
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ Direct OpenAI Responses models use OpenAI's hosted `web_search` tool automatical
|
||||
Codex-capable models can optionally use the provider-native Responses `web_search` tool instead of OpenClaw's managed `web_search` function.
|
||||
|
||||
- Configure it under `tools.web.search.openaiCodex`
|
||||
- It only activates for Codex-capable OpenAI models (`openai/*` models using `api: "openai-codex-responses"`)
|
||||
- It only activates for Codex-capable OpenAI models (`openai/*` models using `api: "openai-chatgpt-responses"`)
|
||||
- Managed `web_search` still applies to non-Codex models
|
||||
- `mode: "cached"` is the default and recommended setting
|
||||
- `tools.web.search.enabled: false` disables both managed and native search
|
||||
|
||||
@@ -207,7 +207,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
model: "openai/gpt-5.4",
|
||||
});
|
||||
|
||||
expect(readFirstEnsureSessionInput(ensure)).toEqual({
|
||||
@@ -632,14 +632,14 @@ describe("AcpxRuntime fresh reset wrapper", () => {
|
||||
sessionKey: "agent:main:acp:test",
|
||||
agent: "main",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.5",
|
||||
model: "openai/gpt-5.5",
|
||||
});
|
||||
|
||||
expect(readFirstEnsureSessionInput(ensure)).toEqual({
|
||||
sessionKey: "agent:main:acp:test",
|
||||
agent: "main",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.5",
|
||||
model: "openai/gpt-5.5",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -678,7 +678,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.5",
|
||||
model: "openai/gpt-5.5",
|
||||
});
|
||||
|
||||
expect(readFirstEnsureSessionInput(ensure)).toEqual({
|
||||
@@ -710,7 +710,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
|
||||
sessionKey: "agent:codex:acp:test",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
model: "openai/gpt-5.4",
|
||||
thinking: "x-high",
|
||||
});
|
||||
|
||||
@@ -743,7 +743,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
|
||||
await runtime.setConfigOption({
|
||||
handle,
|
||||
key: "model",
|
||||
value: "openai-codex/gpt-5.4",
|
||||
value: "openai/gpt-5.4",
|
||||
});
|
||||
|
||||
expect(setConfigOption).toHaveBeenNthCalledWith(1, {
|
||||
@@ -774,7 +774,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
|
||||
await runtime.setConfigOption({
|
||||
handle,
|
||||
key: "model",
|
||||
value: "openai-codex/gpt-5.4/high",
|
||||
value: "openai/gpt-5.4/high",
|
||||
});
|
||||
|
||||
expect(setConfigOption).toHaveBeenNthCalledWith(1, {
|
||||
|
||||
@@ -292,7 +292,7 @@ function createResetAwareSessionStore(
|
||||
const OPENCLAW_BRIDGE_EXECUTABLE = "openclaw";
|
||||
const OPENCLAW_BRIDGE_SUBCOMMAND = "acp";
|
||||
const CODEX_ACP_AGENT_ID = "codex";
|
||||
const CODEX_ACP_OPENCLAW_PREFIX = "openai-codex/";
|
||||
const CODEX_ACP_OPENCLAW_PREFIX = "openai/";
|
||||
const CODEX_ACP_REASONING_EFFORTS = new Set(["low", "medium", "high", "xhigh"]);
|
||||
const CODEX_ACP_THINKING_ALIASES = new Map<string, string | undefined>([
|
||||
["off", undefined],
|
||||
@@ -437,7 +437,7 @@ function failUnsupportedCodexAcpModel(rawModel: string, detail?: string): never
|
||||
throw new AcpRuntimeError(
|
||||
"ACP_INVALID_RUNTIME_OPTION",
|
||||
detail ??
|
||||
`Codex ACP model "${rawModel}" is not supported. Use openai-codex/<model> or <model>/<reasoning-effort>.`,
|
||||
`Codex ACP model "${rawModel}" is not supported. Use openai/<model> or <model>/<reasoning-effort>.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ function normalizeCodexAcpModelOverride(
|
||||
if (parts.length > 2) {
|
||||
failUnsupportedCodexAcpModel(
|
||||
raw,
|
||||
`Codex ACP model "${raw}" is not supported. Use openai-codex/<model> or <model>/<reasoning-effort>.`,
|
||||
`Codex ACP model "${raw}" is not supported. Use openai/<model> or <model>/<reasoning-effort>.`,
|
||||
);
|
||||
}
|
||||
const model = (parts[0] ?? "").trim();
|
||||
@@ -506,7 +506,7 @@ function normalizeCodexAcpModelOverride(
|
||||
if (!model) {
|
||||
failUnsupportedCodexAcpModel(
|
||||
raw,
|
||||
`Codex ACP model "${raw}" is not supported. Use openai-codex/<model> or <model>/<reasoning-effort>.`,
|
||||
`Codex ACP model "${raw}" is not supported. Use openai/<model> or <model>/<reasoning-effort>.`,
|
||||
);
|
||||
}
|
||||
const reasoningEffort = thinkingReasoningEffort ?? modelReasoningEffort;
|
||||
|
||||
@@ -1875,7 +1875,7 @@ describe("active-memory plugin", () => {
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
openai: {
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
models: [
|
||||
{
|
||||
@@ -1907,7 +1907,7 @@ describe("active-memory plugin", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(lastEmbeddedRunParams().provider).toBe("openai-codex");
|
||||
expect(lastEmbeddedRunParams().provider).toBe("openai");
|
||||
expect(lastEmbeddedRunParams().model).toBe("gpt-5.5");
|
||||
});
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ describe("anthropic provider replay hooks", () => {
|
||||
|
||||
expect(
|
||||
provider.normalizeConfig?.({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
providerConfig,
|
||||
} as never),
|
||||
).toBe(providerConfig);
|
||||
|
||||
@@ -69,7 +69,7 @@ describe("anthropic provider policy public artifact", () => {
|
||||
|
||||
expect(
|
||||
normalizeConfig({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
providerConfig,
|
||||
}),
|
||||
).toBe(providerConfig);
|
||||
|
||||
@@ -11,8 +11,8 @@ describe("Codex agent harness supports()", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("supports openai-codex as the primary OpenClaw routing id", () => {
|
||||
expect(harness.supports({ provider: "openai-codex", requestedRuntime: "codex" })).toEqual({
|
||||
it("supports openai as the primary OpenClaw routing id", () => {
|
||||
expect(harness.supports({ provider: "openai", requestedRuntime: "codex" })).toEqual({
|
||||
supported: true,
|
||||
priority: 100,
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
CodexAppServerModelListResult,
|
||||
} from "./src/app-server/models.js";
|
||||
|
||||
const DEFAULT_CODEX_HARNESS_PROVIDER_IDS = new Set(["codex", "openai-codex", "openai"]);
|
||||
const DEFAULT_CODEX_HARNESS_PROVIDER_IDS = new Set(["codex", "openai"]);
|
||||
const CODEX_APP_SERVER_CONTEXT_ENGINE_HOST_CAPABILITIES = [
|
||||
"bootstrap",
|
||||
"assemble-before-prompt",
|
||||
|
||||
@@ -127,7 +127,7 @@ describe("codex plugin", () => {
|
||||
.supported,
|
||||
).toBe(true);
|
||||
const openAiCodex = harness.supports({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
requestedRuntime: "auto",
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ export function buildCodexModelDefinition(model: {
|
||||
return {
|
||||
id,
|
||||
name: model.displayName?.trim() || id,
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
reasoning: model.supportedReasoningEfforts.length > 0 || shouldDefaultToReasoningModel(id),
|
||||
input: model.inputModalities.includes("image") ? ["text", "image"] : ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
@@ -60,7 +60,7 @@ export function buildCodexProviderConfig(models: CodexAppServerModel[]): ModelPr
|
||||
baseUrl: CODEX_BASE_URL,
|
||||
apiKey: CODEX_APP_SERVER_AUTH_MARKER,
|
||||
auth: "token",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
models: models.map(buildCodexModelDefinition),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ describe("codex provider", () => {
|
||||
});
|
||||
expectRecordFields(result.provider, {
|
||||
auth: "token",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
});
|
||||
expect(result.provider.models).toHaveLength(1);
|
||||
expectRecordFields(result.provider.models[0], {
|
||||
@@ -308,7 +308,7 @@ describe("codex provider", () => {
|
||||
expectRecordFields(model, {
|
||||
id: "custom-model",
|
||||
provider: "codex",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
input: ["text"],
|
||||
});
|
||||
|
||||
@@ -51,11 +51,11 @@ describe("Codex app-server attempt diagnostics", () => {
|
||||
pluginAppCacheKey: buildCodexPluginAppCacheKey({
|
||||
appServer,
|
||||
agentDir: "/tmp/agent",
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
accountId: "account-work",
|
||||
envApiKeyFingerprint: "env-key",
|
||||
}),
|
||||
startupAuthProfileId: "openai-codex:work",
|
||||
startupAuthProfileId: "openai:work",
|
||||
appServer,
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ describe("Codex app-server attempt diagnostics", () => {
|
||||
pluginConfigKeys: ["google-calendar"],
|
||||
enabledPluginConfigKeys: ["google-calendar"],
|
||||
appCacheKeyFingerprint: expect.stringMatching(/^sha256:/),
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
appServerTransport: "websocket",
|
||||
appServerCommandSource: "config",
|
||||
}),
|
||||
|
||||
@@ -33,7 +33,7 @@ const providerRuntimeMocks = vi.hoisted(() => ({
|
||||
...params.context,
|
||||
...refreshed,
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
}
|
||||
: undefined;
|
||||
},
|
||||
@@ -268,10 +268,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:default",
|
||||
profileId: "openai:default",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
@@ -305,10 +305,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
@@ -320,7 +320,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
bridgeCodexAppServerStartOptions({
|
||||
startOptions,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
@@ -340,10 +340,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "token",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
token: "access-token",
|
||||
},
|
||||
});
|
||||
@@ -352,7 +352,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
bridgeCodexAppServerStartOptions({
|
||||
startOptions,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
@@ -372,10 +372,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
key: "explicit-api-key",
|
||||
},
|
||||
});
|
||||
@@ -384,7 +384,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
bridgeCodexAppServerStartOptions({
|
||||
startOptions,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
@@ -407,10 +407,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
@@ -422,7 +422,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
bridgeCodexAppServerStartOptions({
|
||||
startOptions,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toBe(startOptions);
|
||||
} finally {
|
||||
@@ -435,34 +435,34 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
key: "first-secret-key",
|
||||
},
|
||||
});
|
||||
const first = await resolveCodexAppServerAuthAccountCacheKey({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
key: "second-secret-key",
|
||||
},
|
||||
});
|
||||
const second = await resolveCodexAppServerAuthAccountCacheKey({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(first).toMatch(/^openai-codex:work:api_key:sha256:[a-f0-9]{64}$/);
|
||||
expect(second).toMatch(/^openai-codex:work:api_key:sha256:[a-f0-9]{64}$/);
|
||||
expect(first).toMatch(/^openai:work:api_key:sha256:[a-f0-9]{64}$/);
|
||||
expect(second).toMatch(/^openai:work:api_key:sha256:[a-f0-9]{64}$/);
|
||||
expect(second).not.toBe(first);
|
||||
expect(first).not.toContain("first-secret-key");
|
||||
expect(second).not.toContain("second-secret-key");
|
||||
@@ -476,27 +476,27 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
keyRef: { source: "env", provider: "default", id: "OPENAI_CODEX_TEST_KEY" },
|
||||
},
|
||||
});
|
||||
vi.stubEnv("OPENAI_CODEX_TEST_KEY", "first-ref-secret");
|
||||
const first = await resolveCodexAppServerAuthAccountCacheKey({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
vi.stubEnv("OPENAI_CODEX_TEST_KEY", "second-ref-secret");
|
||||
const second = await resolveCodexAppServerAuthAccountCacheKey({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(first).toMatch(/^openai-codex:work:api_key:sha256:[a-f0-9]{64}$/);
|
||||
expect(second).toMatch(/^openai-codex:work:api_key:sha256:[a-f0-9]{64}$/);
|
||||
expect(first).toMatch(/^openai:work:api_key:sha256:[a-f0-9]{64}$/);
|
||||
expect(second).toMatch(/^openai:work:api_key:sha256:[a-f0-9]{64}$/);
|
||||
expect(second).not.toBe(first);
|
||||
expect(first).not.toContain("first-ref-secret");
|
||||
expect(second).not.toContain("second-ref-secret");
|
||||
@@ -510,10 +510,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "token",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
tokenRef: { source: "env", provider: "default", id: "OPENAI_CODEX_TEST_TOKEN" },
|
||||
email: "codex@example.test",
|
||||
},
|
||||
@@ -521,13 +521,13 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
vi.stubEnv("OPENAI_CODEX_TEST_TOKEN", "first-ref-token");
|
||||
const first = await resolveCodexAppServerAuthAccountCacheKey({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
vi.stubEnv("OPENAI_CODEX_TEST_TOKEN", "second-ref-token");
|
||||
const second = await resolveCodexAppServerAuthAccountCacheKey({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(first).toMatch(/^codex@example\.test:token:sha256:[a-f0-9]{64}$/);
|
||||
@@ -546,10 +546,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
@@ -561,7 +561,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenCalledWith("account/login/start", {
|
||||
@@ -629,10 +629,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:default",
|
||||
profileId: "openai:default",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "default-access-token",
|
||||
refresh: "default-refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
@@ -663,9 +663,9 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
store: {
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:default": {
|
||||
"openai:default": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "",
|
||||
refresh: "",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -687,10 +687,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:default",
|
||||
profileId: "openai:default",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "ref-backed-access-token",
|
||||
refresh: "ref-backed-refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -732,7 +732,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
chatgptPlanType: null,
|
||||
});
|
||||
expect(loadAuthProfileStoreForSecretsRuntime(agentDir).profiles).not.toHaveProperty(
|
||||
"openai-codex:default",
|
||||
"openai:default",
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
@@ -792,10 +792,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:default",
|
||||
profileId: "openai:default",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "default-access-token",
|
||||
refresh: "default-refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
@@ -804,10 +804,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
});
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "work-access-token",
|
||||
refresh: "work-refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
@@ -821,7 +821,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
config: {
|
||||
auth: {
|
||||
order: {
|
||||
"openai-codex": ["openai-codex:work", "openai-codex:default"],
|
||||
openai: ["openai:work", "openai:default"],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -850,10 +850,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "expired-access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() - 60_000,
|
||||
@@ -865,7 +865,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(oauthMocks.refreshOpenAICodexToken).toHaveBeenCalledWith("refresh-token");
|
||||
@@ -887,10 +887,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
keyRef: { source: "env", provider: "default", id: "OPENAI_CODEX_API_KEY" },
|
||||
},
|
||||
});
|
||||
@@ -898,7 +898,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenCalledWith("account/login/start", {
|
||||
@@ -916,10 +916,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:aws",
|
||||
profileId: "openai:aws",
|
||||
credential: {
|
||||
type: "aws-sdk",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
} as never,
|
||||
});
|
||||
|
||||
@@ -927,10 +927,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:aws",
|
||||
authProfileId: "openai:aws",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
'Codex app-server auth profile "openai-codex:aws" does not contain usable credentials.',
|
||||
'Codex app-server auth profile "openai:aws" does not contain usable credentials.',
|
||||
);
|
||||
expect(oauthMocks.refreshOpenAICodexToken).not.toHaveBeenCalled();
|
||||
expect(request).not.toHaveBeenCalled();
|
||||
@@ -1191,10 +1191,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "token",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
tokenRef: { source: "env", provider: "default", id: "OPENAI_CODEX_TOKEN" },
|
||||
email: "codex@example.test",
|
||||
},
|
||||
@@ -1203,7 +1203,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenCalledWith("account/login/start", {
|
||||
@@ -1223,11 +1223,11 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "token",
|
||||
provider: "openai-codex",
|
||||
token: "sk-openai-codex-api-key-value",
|
||||
provider: "openai",
|
||||
token: "sk-openai-chatgpt-api-key-value",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1235,14 +1235,14 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(request).toHaveBeenCalledWith("account/login/start", {
|
||||
type: "chatgptAuthTokens",
|
||||
accessToken: "sk-openai-codex-api-key-value",
|
||||
chatgptAccountId: "openai-codex:work",
|
||||
accessToken: "sk-openai-chatgpt-api-key-value",
|
||||
chatgptAccountId: "openai:work",
|
||||
chatgptPlanType: null,
|
||||
});
|
||||
} finally {
|
||||
@@ -1257,10 +1257,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
key: tokenLikeKey,
|
||||
},
|
||||
});
|
||||
@@ -1269,7 +1269,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
@@ -1288,7 +1288,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "token",
|
||||
provider: "codex-cli",
|
||||
@@ -1300,7 +1300,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenCalledWith("account/login/start", {
|
||||
@@ -1325,10 +1325,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "stale-access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -1340,7 +1340,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await expect(
|
||||
refreshCodexAppServerAuthTokens({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
accessToken: "refreshed-access-token",
|
||||
@@ -1358,7 +1358,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
const currentExpiry = Date.now() + 60_000;
|
||||
oauthMocks.refreshOpenAICodexToken.mockImplementationOnce(async () => {
|
||||
const persistedProfile = expectOAuthProfile(
|
||||
loadAuthProfileStoreForSecretsRuntime(agentDir).profiles["openai-codex:work"],
|
||||
loadAuthProfileStoreForSecretsRuntime(agentDir).profiles["openai:work"],
|
||||
);
|
||||
expect(persistedProfile).toMatchObject({
|
||||
access: "current-access-token",
|
||||
@@ -1374,10 +1374,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "current-access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: currentExpiry,
|
||||
@@ -1389,7 +1389,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await expect(
|
||||
refreshCodexAppServerAuthTokens({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
accessToken: "refreshed-access-token",
|
||||
@@ -1398,7 +1398,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
});
|
||||
expect(oauthMocks.refreshOpenAICodexToken).toHaveBeenCalledWith("refresh-token");
|
||||
const refreshedProfile = expectOAuthProfile(
|
||||
loadAuthProfileStoreForSecretsRuntime(agentDir).profiles["openai-codex:work"],
|
||||
loadAuthProfileStoreForSecretsRuntime(agentDir).profiles["openai:work"],
|
||||
);
|
||||
expect(refreshedProfile?.access).toBe("refreshed-access-token");
|
||||
expect(refreshedProfile?.refresh).toBe("refreshed-refresh-token");
|
||||
@@ -1422,10 +1422,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
});
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "main-current-access-token",
|
||||
refresh: "main-refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -1437,7 +1437,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await expect(
|
||||
refreshCodexAppServerAuthTokens({
|
||||
agentDir: childAgentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
accessToken: "main-refreshed-access-token",
|
||||
@@ -1448,9 +1448,9 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
expect(oauthMocks.refreshOpenAICodexToken).toHaveBeenCalledWith("main-refresh-token");
|
||||
await expectPathMissing(childAuthPath);
|
||||
const mainProfile = expectOAuthProfile(
|
||||
loadAuthProfileStoreForSecretsRuntime().profiles["openai-codex:work"],
|
||||
loadAuthProfileStoreForSecretsRuntime().profiles["openai:work"],
|
||||
);
|
||||
expect(mainProfile?.provider).toBe("openai-codex");
|
||||
expect(mainProfile?.provider).toBe("openai");
|
||||
expect(mainProfile?.access).toBe("main-refreshed-access-token");
|
||||
expect(mainProfile?.refresh).toBe("main-refreshed-refresh-token");
|
||||
} finally {
|
||||
@@ -1473,10 +1473,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
});
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "main-current-access-token",
|
||||
refresh: "main-owner-refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -1490,9 +1490,9 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:work": {
|
||||
"openai:work": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "child-stale-access-token",
|
||||
refresh: "child-stale-refresh-token",
|
||||
expires: Date.now() - 60_000,
|
||||
@@ -1506,7 +1506,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await expect(
|
||||
refreshCodexAppServerAuthTokens({
|
||||
agentDir: childAgentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
accessToken: "main-refreshed-access-token",
|
||||
@@ -1516,13 +1516,13 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
|
||||
expect(oauthMocks.refreshOpenAICodexToken).toHaveBeenCalledWith("main-owner-refresh-token");
|
||||
const mainProfile = expectOAuthProfile(
|
||||
loadAuthProfileStoreForSecretsRuntime().profiles["openai-codex:work"],
|
||||
loadAuthProfileStoreForSecretsRuntime().profiles["openai:work"],
|
||||
);
|
||||
expect(mainProfile?.provider).toBe("openai-codex");
|
||||
expect(mainProfile?.provider).toBe("openai");
|
||||
expect(mainProfile?.access).toBe("main-refreshed-access-token");
|
||||
expect(mainProfile?.refresh).toBe("main-refreshed-refresh-token");
|
||||
const childProfile = expectOAuthProfile(
|
||||
loadAuthProfileStoreForSecretsRuntime(childAgentDir).profiles["openai-codex:work"],
|
||||
loadAuthProfileStoreForSecretsRuntime(childAgentDir).profiles["openai:work"],
|
||||
);
|
||||
expect(childProfile?.access).toBe("child-stale-access-token");
|
||||
expect(childProfile?.refresh).toBe("child-stale-refresh-token");
|
||||
@@ -1542,7 +1542,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "codex-cli",
|
||||
@@ -1557,7 +1557,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await expect(
|
||||
refreshCodexAppServerAuthTokens({
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
accessToken: "refreshed-alias-access-token",
|
||||
@@ -1576,10 +1576,10 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:work",
|
||||
profileId: "openai:work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 24 * 60 * 60_000,
|
||||
@@ -1592,7 +1592,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client: { request } as never,
|
||||
agentDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
|
||||
expect(request).toHaveBeenCalledWith("account/login/start", {
|
||||
|
||||
@@ -218,7 +218,7 @@ describe("Auth profile runtime contract - Codex app-server adapter", () => {
|
||||
await writeCodexAppServerBinding(sessionFile, {
|
||||
threadId: "thread-auth-contract",
|
||||
cwd: tmpDir,
|
||||
authProfileId: "openai-codex:stale",
|
||||
authProfileId: "openai:stale",
|
||||
dynamicToolsFingerprint: "[]",
|
||||
});
|
||||
const params = createParams(sessionFile, tmpDir);
|
||||
|
||||
@@ -251,11 +251,11 @@ describe("maybeCompactCodexAppServerSession", () => {
|
||||
seenAuthProfileId = authProfileId;
|
||||
return fake.client;
|
||||
});
|
||||
const sessionFile = await writeTestBinding({ authProfileId: "openai-codex:work" });
|
||||
const sessionFile = await writeTestBinding({ authProfileId: "openai:work" });
|
||||
|
||||
const result = requireCompactResult(await startCompaction(sessionFile));
|
||||
|
||||
expect(seenAuthProfileId).toBe("openai-codex:work");
|
||||
expect(seenAuthProfileId).toBe("openai:work");
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
@@ -278,7 +278,7 @@ describe("maybeCompactCodexAppServerSession", () => {
|
||||
fake.request.mockRejectedValueOnce(new Error("thread not found: thread-1"));
|
||||
setCodexAppServerClientFactoryForTest(async () => fake.client);
|
||||
const sessionFile = await writeTestBinding({
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
model: "gpt-5.5-mini",
|
||||
approvalPolicy: "on-request",
|
||||
sandbox: "workspace-write",
|
||||
@@ -292,7 +292,7 @@ describe("maybeCompactCodexAppServerSession", () => {
|
||||
expect(fake.request).toHaveBeenCalledWith("thread/compact/start", { threadId: "thread-1" });
|
||||
const preservedBinding = await readCodexAppServerBinding(sessionFile);
|
||||
expect(preservedBinding?.threadId).toBe("thread-1");
|
||||
expect(preservedBinding?.authProfileId).toBe("openai-codex:work");
|
||||
expect(preservedBinding?.authProfileId).toBe("openai:work");
|
||||
expect(preservedBinding?.model).toBe("gpt-5.5-mini");
|
||||
expect(preservedBinding?.approvalPolicy).toBe("on-request");
|
||||
expect(preservedBinding?.sandbox).toBe("workspace-write");
|
||||
@@ -568,7 +568,7 @@ describe("maybeCompactCodexAppServerSession", () => {
|
||||
await writeCodexAppServerBinding(sessionFile, {
|
||||
threadId: "thread-1",
|
||||
cwd: tempDir,
|
||||
authProfileId: "openai-codex:binding",
|
||||
authProfileId: "openai:binding",
|
||||
});
|
||||
|
||||
const result = await maybeCompactCodexAppServerSession({
|
||||
@@ -577,7 +577,7 @@ describe("maybeCompactCodexAppServerSession", () => {
|
||||
sessionFile,
|
||||
workspaceDir: tempDir,
|
||||
trigger: "manual",
|
||||
authProfileId: "openai-codex:runtime",
|
||||
authProfileId: "openai:runtime",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
|
||||
@@ -511,8 +511,8 @@ describe("Codex app-server dynamic tool build", () => {
|
||||
const transportAuthProfileStore = {
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:work": {
|
||||
provider: "openai-codex",
|
||||
"openai:work": {
|
||||
provider: "openai",
|
||||
type: "oauth",
|
||||
access: "transport-token",
|
||||
refresh: "transport-refresh",
|
||||
@@ -523,8 +523,8 @@ describe("Codex app-server dynamic tool build", () => {
|
||||
const toolAuthProfileStore = {
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:work": {
|
||||
provider: "openai-codex",
|
||||
"openai:work": {
|
||||
provider: "openai",
|
||||
type: "oauth",
|
||||
access: "transport-token",
|
||||
refresh: "transport-refresh",
|
||||
|
||||
@@ -42,8 +42,8 @@ function assistantMessage(text: string, timestamp: number) {
|
||||
return {
|
||||
role: "assistant" as const,
|
||||
content: [{ type: "text" as const, text }],
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
api: "openai-chatgpt-responses",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4-codex",
|
||||
usage: {
|
||||
input: 0,
|
||||
@@ -69,7 +69,7 @@ async function createParams(): Promise<EmbeddedRunAttemptParams> {
|
||||
sessionFile,
|
||||
workspaceDir: tempDir,
|
||||
runId: "run-1",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4-codex",
|
||||
model: createCodexTestModel(),
|
||||
thinkLevel: "medium",
|
||||
@@ -368,8 +368,8 @@ describe("CodexAppServerEventProjector", () => {
|
||||
|
||||
const result = projector.buildResult(buildEmptyToolTelemetry());
|
||||
|
||||
expect(result.lastAssistant?.provider).toBe("openai-codex");
|
||||
expect(result.lastAssistant?.api).toBe("openai-codex-responses");
|
||||
expect(result.lastAssistant?.provider).toBe("openai");
|
||||
expect(result.lastAssistant?.api).toBe("openai-chatgpt-responses");
|
||||
expect(result.lastAssistant?.model).toBe("gpt-5.5");
|
||||
});
|
||||
|
||||
@@ -390,7 +390,7 @@ describe("CodexAppServerEventProjector", () => {
|
||||
auth: {
|
||||
providerForAuth: "openai",
|
||||
authProfileProviderForAuth: "openai",
|
||||
harnessAuthProvider: "openai-codex",
|
||||
harnessAuthProvider: "openai",
|
||||
forwardedAuthProfileId: "openai:work",
|
||||
},
|
||||
observability: {
|
||||
@@ -1158,7 +1158,7 @@ describe("CodexAppServerEventProjector", () => {
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp",
|
||||
runId: "run-1",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4-codex",
|
||||
model: createCodexTestModel(),
|
||||
thinkLevel: "medium",
|
||||
|
||||
@@ -1562,7 +1562,7 @@ export class CodexAppServerEventProjector {
|
||||
return {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text }],
|
||||
api: attribution.api ?? "openai-codex-responses",
|
||||
api: attribution.api ?? "openai-chatgpt-responses",
|
||||
provider: attribution.provider,
|
||||
model: this.params.modelId,
|
||||
usage,
|
||||
@@ -1577,7 +1577,7 @@ export class CodexAppServerEventProjector {
|
||||
return {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: `${title}:\n${text}` }],
|
||||
api: attribution.api ?? "openai-codex-responses",
|
||||
api: attribution.api ?? "openai-chatgpt-responses",
|
||||
provider: attribution.provider,
|
||||
model: this.params.modelId,
|
||||
usage: ZERO_USAGE,
|
||||
@@ -1600,7 +1600,7 @@ export class CodexAppServerEventProjector {
|
||||
input: args,
|
||||
},
|
||||
],
|
||||
api: attribution.api ?? "openai-codex-responses",
|
||||
api: attribution.api ?? "openai-chatgpt-responses",
|
||||
provider: attribution.provider,
|
||||
model: this.params.modelId,
|
||||
usage: ZERO_USAGE,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness
|
||||
|
||||
const OPENAI_PROVIDER_ID = "openai";
|
||||
const OPENAI_RESPONSES_API = "openai-responses";
|
||||
const OPENAI_CODEX_RESPONSES_API = "openai-codex-responses";
|
||||
const OPENAI_CODEX_RESPONSES_API = "openai-chatgpt-responses";
|
||||
|
||||
export type CodexLocalRuntimeAttribution = {
|
||||
provider: string;
|
||||
|
||||
@@ -179,7 +179,7 @@ describe("Outcome/fallback runtime contract - Codex app-server adapter", () => {
|
||||
text: `Codex reasoning:\n${OUTCOME_FALLBACK_RUNTIME_CONTRACT.reasoningOnlyText}`,
|
||||
},
|
||||
]);
|
||||
expect(reasoningMessage.api).toBe("openai-codex-responses");
|
||||
expect(reasoningMessage.api).toBe("openai-chatgpt-responses");
|
||||
expect(reasoningMessage.provider).toBe("codex");
|
||||
expect(reasoningMessage.model).toBe(OUTCOME_FALLBACK_RUNTIME_CONTRACT.primaryModel);
|
||||
expect(reasoningMessage.usage).toStrictEqual({
|
||||
@@ -245,7 +245,7 @@ describe("Outcome/fallback runtime contract - Codex app-server adapter", () => {
|
||||
text: `Codex plan:\n${OUTCOME_FALLBACK_RUNTIME_CONTRACT.planningOnlyText}`,
|
||||
},
|
||||
]);
|
||||
expect(planMessage.api).toBe("openai-codex-responses");
|
||||
expect(planMessage.api).toBe("openai-chatgpt-responses");
|
||||
expect(planMessage.provider).toBe("codex");
|
||||
expect(planMessage.model).toBe(OUTCOME_FALLBACK_RUNTIME_CONTRACT.primaryModel);
|
||||
expect(planMessage.usage).toStrictEqual({
|
||||
|
||||
@@ -140,8 +140,8 @@ export function assistantMessage(text: string, timestamp: number) {
|
||||
return {
|
||||
role: "assistant" as const,
|
||||
content: [{ type: "text" as const, text }],
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
api: "openai-chatgpt-responses",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4-codex",
|
||||
usage: {
|
||||
input: 0,
|
||||
|
||||
@@ -66,8 +66,8 @@ function assistantMessage(text: string, timestamp: number): AgentMessage {
|
||||
return {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text }],
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
api: "openai-chatgpt-responses",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4-codex",
|
||||
usage: {
|
||||
input: 0,
|
||||
|
||||
@@ -3353,7 +3353,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const agentDir = path.join(tempDir, "agent");
|
||||
const authProfileId = "openai-codex:work";
|
||||
const authProfileId = "openai:work";
|
||||
const pluginConfig = {
|
||||
codexPlugins: {
|
||||
enabled: true,
|
||||
@@ -3468,7 +3468,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
profiles: {
|
||||
[authProfileId]: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -3667,11 +3667,11 @@ describe("runCodexAppServerAttempt", () => {
|
||||
path.join(tempDir, "session.jsonl"),
|
||||
path.join(tempDir, "workspace"),
|
||||
);
|
||||
params.authProfileId = "openai-codex:work";
|
||||
params.authProfileId = "openai:work";
|
||||
params.agentDir = path.join(tempDir, "agent");
|
||||
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
await vi.waitFor(() => expect(seenAuthProfileIds).toEqual(["openai-codex:work"]), {
|
||||
await vi.waitFor(() => expect(seenAuthProfileIds).toEqual(["openai:work"]), {
|
||||
interval: 1,
|
||||
});
|
||||
await waitForMethod("turn/start");
|
||||
@@ -3679,7 +3679,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
await completeTurn({ threadId: "thread-1", turnId: "turn-1" });
|
||||
await run;
|
||||
|
||||
expect(seenAuthProfileIds).toEqual(["openai-codex:work"]);
|
||||
expect(seenAuthProfileIds).toEqual(["openai:work"]);
|
||||
expect(seenAgentDirs).toEqual([path.join(tempDir, "agent")]);
|
||||
expect(requests.map((entry) => entry.method)).toContain("turn/start");
|
||||
});
|
||||
@@ -3871,7 +3871,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const agentDir = path.join(tempDir, "agent");
|
||||
await writeExistingBinding(sessionFile, workspaceDir, {
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
dynamicToolsFingerprint: "[]",
|
||||
});
|
||||
await fs.writeFile(
|
||||
@@ -3921,7 +3921,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const run = runCodexAppServerAttempt(params, {
|
||||
pluginConfig: { appServer: { mode: "yolo" } },
|
||||
});
|
||||
await vi.waitFor(() => expect(seenAuthProfileIds).toEqual(["openai-codex:work"]), {
|
||||
await vi.waitFor(() => expect(seenAuthProfileIds).toEqual(["openai:work"]), {
|
||||
interval: 1,
|
||||
});
|
||||
await waitForMethod("turn/start");
|
||||
@@ -3930,9 +3930,9 @@ describe("runCodexAppServerAttempt", () => {
|
||||
|
||||
expect(requests.map((entry) => entry.method)).toContain("thread/start");
|
||||
expect(requests.map((entry) => entry.method)).not.toContain("thread/resume");
|
||||
expect(seenAuthProfileIds).toEqual(["openai-codex:work"]);
|
||||
expect(seenAuthProfileIds).toEqual(["openai:work"]);
|
||||
const savedBinding = await readCodexAppServerBinding(sessionFile);
|
||||
expect(savedBinding?.authProfileId).toBe("openai-codex:work");
|
||||
expect(savedBinding?.authProfileId).toBe("openai:work");
|
||||
expect(savedBinding?.threadId).toBe("thread-1");
|
||||
});
|
||||
|
||||
@@ -4246,7 +4246,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
await writeExistingBinding(sessionFile, workspaceDir, {
|
||||
authProfileId: "openai-codex:bound",
|
||||
authProfileId: "openai:bound",
|
||||
dynamicToolsFingerprint: "[]",
|
||||
});
|
||||
const seenAuthProfileIds: Array<string | undefined> = [];
|
||||
@@ -4273,7 +4273,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
params.agentDir = path.join(tempDir, "agent");
|
||||
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
await vi.waitFor(() => expect(seenAuthProfileIds).toEqual(["openai-codex:bound"]), {
|
||||
await vi.waitFor(() => expect(seenAuthProfileIds).toEqual(["openai:bound"]), {
|
||||
interval: 1,
|
||||
});
|
||||
await waitForMethod("turn/start");
|
||||
@@ -4281,7 +4281,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
await completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
|
||||
await run;
|
||||
|
||||
expect(seenAuthProfileIds).toEqual(["openai-codex:bound"]);
|
||||
expect(seenAuthProfileIds).toEqual(["openai:bound"]);
|
||||
expect(seenAgentDirs).toEqual([path.join(tempDir, "agent")]);
|
||||
expect(requests.map((entry) => entry.method)).toContain("turn/start");
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ describe("runCodexAppServerAttempt usage limits", () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const resetsAt = Math.ceil(Date.now() / 1000) + 120;
|
||||
const authProfileId = "openai-codex:work";
|
||||
const authProfileId = "openai:work";
|
||||
const harnessRef: { current?: ReturnType<typeof createStartedThreadHarness> } = {};
|
||||
const harness = createStartedThreadHarness(async (method) => {
|
||||
if (method === "turn/start") {
|
||||
@@ -40,7 +40,7 @@ describe("runCodexAppServerAttempt usage limits", () => {
|
||||
profiles: {
|
||||
[authProfileId]: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access",
|
||||
refresh: "refresh",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -58,7 +58,7 @@ describe("runCodexAppServerAttempt usage limits", () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const resetsAt = Math.ceil(Date.now() / 1000) + 120;
|
||||
const authProfileId = "openai-codex:work";
|
||||
const authProfileId = "openai:work";
|
||||
rememberCodexRateLimits({
|
||||
rateLimits: {
|
||||
limitId: "codex",
|
||||
@@ -87,7 +87,7 @@ describe("runCodexAppServerAttempt usage limits", () => {
|
||||
profiles: {
|
||||
[authProfileId]: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access",
|
||||
refresh: "refresh",
|
||||
expires: Date.now() + 60_000,
|
||||
|
||||
@@ -19,7 +19,7 @@ const nativeAuthLookup: Pick<CodexAppServerAuthProfileLookup, "authProfileStore"
|
||||
profiles: {
|
||||
work: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -237,7 +237,7 @@ describe("codex app-server session binding", () => {
|
||||
{
|
||||
threadId: "thread-123",
|
||||
cwd: tempDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
model: "gpt-5.4-mini",
|
||||
modelProvider: "openai",
|
||||
},
|
||||
@@ -245,7 +245,7 @@ describe("codex app-server session binding", () => {
|
||||
authProfileStore: {
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:work": {
|
||||
"openai:work": {
|
||||
type: "api_key",
|
||||
provider: "openai",
|
||||
key: "sk-test",
|
||||
@@ -259,7 +259,7 @@ describe("codex app-server session binding", () => {
|
||||
authProfileStore: {
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:work": {
|
||||
"openai:work": {
|
||||
type: "api_key",
|
||||
provider: "openai",
|
||||
key: "sk-test",
|
||||
@@ -283,7 +283,7 @@ describe("codex app-server session binding", () => {
|
||||
{
|
||||
threadId: "thread-123",
|
||||
cwd: tempDir,
|
||||
authProfileId: "openai-codex:default",
|
||||
authProfileId: "openai:default",
|
||||
model: "gpt-5.4-mini",
|
||||
modelProvider: "openai",
|
||||
},
|
||||
@@ -294,7 +294,7 @@ describe("codex app-server session binding", () => {
|
||||
const binding = await readCodexAppServerBinding(sessionFile, { agentDir });
|
||||
|
||||
expect(raw).not.toContain('"modelProvider": "openai"');
|
||||
expect(binding?.authProfileId).toBe("openai-codex:default");
|
||||
expect(binding?.authProfileId).toBe("openai:default");
|
||||
expect(binding?.modelProvider).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
@@ -205,22 +205,22 @@ describe("shared Codex app-server client", () => {
|
||||
|
||||
const listPromise = listCodexAppServerModels({
|
||||
timeoutMs: 1000,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
await sendInitializeResult(harness, "openclaw/0.125.0 (macOS; test)");
|
||||
await sendEmptyModelList(harness);
|
||||
|
||||
await expect(listPromise).resolves.toEqual({ models: [] });
|
||||
const bridgeCall = bridgeStartOptionsCall();
|
||||
expect(bridgeCall?.authProfileId).toBe("openai-codex:work");
|
||||
expect(bridgeCall?.authProfileId).toBe("openai:work");
|
||||
const applyCall = applyAuthProfileCall();
|
||||
expect(applyCall?.authProfileId).toBe("openai-codex:work");
|
||||
expect(applyCall?.authProfileId).toBe("openai:work");
|
||||
});
|
||||
|
||||
it("skips target auth resolution when native source auth is requested", async () => {
|
||||
const harness = createClientHarness();
|
||||
vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
||||
const config = { auth: { order: { "openai-codex": ["openai-codex:target"] } } };
|
||||
const config = { auth: { order: { openai: ["openai:target"] } } };
|
||||
|
||||
const clientPromise = getSharedCodexAppServerClient({
|
||||
timeoutMs: 1000,
|
||||
@@ -245,8 +245,8 @@ describe("shared Codex app-server client", () => {
|
||||
it("resolves the configured implicit auth profile before sharing a client", async () => {
|
||||
const harness = createClientHarness();
|
||||
vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
||||
const config = { auth: { order: { "openai-codex": ["openai-codex:work"] } } };
|
||||
mocks.resolveCodexAppServerAuthProfileIdForAgent.mockReturnValue("openai-codex:work");
|
||||
const config = { auth: { order: { openai: ["openai:work"] } } };
|
||||
mocks.resolveCodexAppServerAuthProfileIdForAgent.mockReturnValue("openai:work");
|
||||
|
||||
const listPromise = listCodexAppServerModels({
|
||||
timeoutMs: 1000,
|
||||
@@ -263,10 +263,10 @@ describe("shared Codex app-server client", () => {
|
||||
config,
|
||||
});
|
||||
const bridgeCall = bridgeStartOptionsCall();
|
||||
expect(bridgeCall?.authProfileId).toBe("openai-codex:work");
|
||||
expect(bridgeCall?.authProfileId).toBe("openai:work");
|
||||
expect(bridgeCall?.config).toBe(config);
|
||||
const applyCall = applyAuthProfileCall();
|
||||
expect(applyCall?.authProfileId).toBe("openai-codex:work");
|
||||
expect(applyCall?.authProfileId).toBe("openai:work");
|
||||
expect(applyCall?.config).toBe(config);
|
||||
});
|
||||
|
||||
@@ -276,7 +276,7 @@ describe("shared Codex app-server client", () => {
|
||||
|
||||
const listPromise = listCodexAppServerModels({
|
||||
timeoutMs: 1000,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
agentDir: "/tmp/openclaw-agent-nova",
|
||||
});
|
||||
await sendInitializeResult(harness, "openclaw/0.125.0 (macOS; test)");
|
||||
@@ -285,10 +285,10 @@ describe("shared Codex app-server client", () => {
|
||||
await expect(listPromise).resolves.toEqual({ models: [] });
|
||||
const bridgeCall = bridgeStartOptionsCall();
|
||||
expect(bridgeCall?.agentDir).toBe("/tmp/openclaw-agent-nova");
|
||||
expect(bridgeCall?.authProfileId).toBe("openai-codex:work");
|
||||
expect(bridgeCall?.authProfileId).toBe("openai:work");
|
||||
const applyCall = applyAuthProfileCall();
|
||||
expect(applyCall?.agentDir).toBe("/tmp/openclaw-agent-nova");
|
||||
expect(applyCall?.authProfileId).toBe("openai-codex:work");
|
||||
expect(applyCall?.authProfileId).toBe("openai:work");
|
||||
});
|
||||
|
||||
it("migrates legacy singleton global state into the keyed registry", async () => {
|
||||
|
||||
@@ -308,7 +308,7 @@ function sideParams(overrides: Partial<Parameters<typeof runCodexAppServerSideQu
|
||||
sessionId: "session-1",
|
||||
sessionFile: "/tmp/session-1.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
authProfileIdSource: "user",
|
||||
...overrides,
|
||||
} satisfies Parameters<typeof runCodexAppServerSideQuestion>[0];
|
||||
@@ -342,7 +342,7 @@ describe("runCodexAppServerSideQuestion", () => {
|
||||
threadId: "parent-thread",
|
||||
sessionFile: "/tmp/session-1.jsonl",
|
||||
cwd: "/tmp/workspace",
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
model: "gpt-5.5",
|
||||
approvalPolicy: "on-request",
|
||||
sandbox: "workspace-write",
|
||||
@@ -737,7 +737,7 @@ describe("runCodexAppServerSideQuestion", () => {
|
||||
threadId: "parent-thread",
|
||||
sessionFile: "/tmp/session-1.jsonl",
|
||||
cwd: "/tmp/workspace",
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
model: "gpt-5.5",
|
||||
approvalPolicy: "never",
|
||||
sandbox: "workspace-write",
|
||||
@@ -1233,7 +1233,7 @@ describe("runCodexAppServerSideQuestion", () => {
|
||||
|
||||
expect(refreshCodexAppServerAuthTokensMock).toHaveBeenCalledWith({
|
||||
agentDir: "/tmp/agent",
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
config: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,12 +4,12 @@ import type { Api, Model } from "openclaw/plugin-sdk/llm";
|
||||
import { vi } from "vitest";
|
||||
import { CodexAppServerClient } from "./client.js";
|
||||
|
||||
export function createCodexTestModel(provider = "openai-codex", input = ["text"]): Model {
|
||||
export function createCodexTestModel(provider = "openai", input = ["text"]): Model {
|
||||
return {
|
||||
id: "gpt-5.4-codex",
|
||||
name: "gpt-5.4-codex",
|
||||
provider,
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
input,
|
||||
reasoning: true,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
|
||||
@@ -1336,7 +1336,7 @@ describe("Codex app-server thread lifecycle bindings", () => {
|
||||
cwd: workspaceDir,
|
||||
model: "gpt-5.4-codex",
|
||||
modelProvider: "openai",
|
||||
authProfileId: "openai-codex:bound",
|
||||
authProfileId: "openai:bound",
|
||||
});
|
||||
const params = createParams(sessionFile, workspaceDir);
|
||||
delete params.authProfileId;
|
||||
@@ -1370,6 +1370,6 @@ describe("Codex app-server thread lifecycle bindings", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(binding.authProfileId).toBe("openai-codex:bound");
|
||||
expect(binding.authProfileId).toBe("openai:bound");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ function createAttemptParams(params: {
|
||||
const authProfileProviders =
|
||||
params.authProfileProviders ??
|
||||
(params.authProfileId
|
||||
? { [params.authProfileId]: params.authProfileProvider ?? "openai-codex" }
|
||||
? { [params.authProfileId]: params.authProfileProvider ?? "openai" }
|
||||
: {});
|
||||
return {
|
||||
provider: params.provider,
|
||||
@@ -552,7 +552,7 @@ describe("Codex app-server turn params", () => {
|
||||
});
|
||||
|
||||
describe("Codex app-server model provider selection", () => {
|
||||
it.each(["openai", "openai-codex"])(
|
||||
it.each(["openai", "openai"])(
|
||||
"omits public %s modelProvider when forwarding native Codex auth on thread/start",
|
||||
(provider) => {
|
||||
const request = buildThreadStartParams(
|
||||
@@ -573,7 +573,7 @@ describe("Codex app-server model provider selection", () => {
|
||||
const request = buildThreadResumeParams(
|
||||
createAttemptParams({
|
||||
provider: "openai",
|
||||
authProfileProviders: { bound: "openai-codex" },
|
||||
authProfileProviders: { bound: "openai" },
|
||||
}),
|
||||
{
|
||||
threadId: "thread-1",
|
||||
@@ -590,7 +590,7 @@ describe("Codex app-server model provider selection", () => {
|
||||
const request = buildThreadStartParams(
|
||||
createAttemptParams({
|
||||
provider: "openai",
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
authProfileProvider: "openai",
|
||||
}),
|
||||
{
|
||||
|
||||
@@ -1223,16 +1223,13 @@ export function resolveCodexAppServerModelProvider(params: {
|
||||
// native provider/auth selection instead of forcing the legacy OpenAI path.
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
isCodexAppServerNativeAuthProfile(params) &&
|
||||
(normalizedLower === "openai" || normalizedLower === "openai-codex")
|
||||
) {
|
||||
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") {
|
||||
// When OpenClaw is forwarding ChatGPT/Codex OAuth, `openai` is Codex's
|
||||
// native provider id, not a public OpenAI API-key choice. Omit the override
|
||||
// so app-server keeps its configured provider/auth pair for this session.
|
||||
return undefined;
|
||||
}
|
||||
return normalizedLower === "openai-codex" ? "openai" : normalized;
|
||||
return normalizedLower === "openai" ? "openai" : normalized;
|
||||
}
|
||||
|
||||
// Modern Codex models (gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex-spark) use the
|
||||
|
||||
@@ -114,8 +114,8 @@ describe("Codex trajectory recorder", () => {
|
||||
const parsed = JSON.parse(
|
||||
fs.readFileSync(path.join(tmpDir, "session.trajectory.jsonl"), "utf8"),
|
||||
);
|
||||
expect(parsed.provider).toBe("openai-codex");
|
||||
expect(parsed.modelApi).toBe("openai-codex-responses");
|
||||
expect(parsed.provider).toBe("openai");
|
||||
expect(parsed.modelApi).toBe("openai-chatgpt-responses");
|
||||
expect(parsed.modelId).toBe("gpt-5.5");
|
||||
});
|
||||
|
||||
|
||||
@@ -735,7 +735,7 @@ describe("codex command", () => {
|
||||
});
|
||||
|
||||
it("shows model ids from Codex app-server", async () => {
|
||||
const config = { auth: { order: { "openai-codex": ["openai-codex:work"] } } };
|
||||
const config = { auth: { order: { openai: ["openai:work"] } } };
|
||||
const listCodexAppServerModels = vi.fn(async (_options?: { config?: unknown }) => ({
|
||||
models: [
|
||||
{
|
||||
@@ -825,7 +825,7 @@ describe("codex command", () => {
|
||||
});
|
||||
|
||||
it("reports status unavailable when every Codex probe fails", async () => {
|
||||
const config = { auth: { order: { "openai-codex": ["openai-codex:work"] } } };
|
||||
const config = { auth: { order: { openai: ["openai:work"] } } };
|
||||
const offline = { ok: false as const, error: "offline" };
|
||||
const deps = createDeps({
|
||||
readCodexStatusProbes: vi.fn(async () => ({
|
||||
@@ -1277,7 +1277,7 @@ describe("codex command", () => {
|
||||
profiles: {
|
||||
"openai:personal-email@gmail.com": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: now + 60 * 60 * 1000,
|
||||
@@ -1349,7 +1349,7 @@ describe("codex command", () => {
|
||||
profiles: {
|
||||
"openai:personal-email@gmail.com": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: now + 60 * 60 * 1000,
|
||||
@@ -1402,12 +1402,11 @@ describe("codex command", () => {
|
||||
expect(result.text).not.toContain("subscription unavailable");
|
||||
});
|
||||
|
||||
it("shows Codex auth order before OpenAI fallback order", async () => {
|
||||
it("shows OpenAI subscription auth before API-key fallback order", async () => {
|
||||
const config = {
|
||||
auth: {
|
||||
order: {
|
||||
openai: ["openai:api-key"],
|
||||
"openai-codex": ["openai-codex:personal-email@gmail.com"],
|
||||
openai: ["openai:personal-email@gmail.com", "openai:api-key"],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1421,9 +1420,9 @@ describe("codex command", () => {
|
||||
provider: "openai",
|
||||
key: "sk-test",
|
||||
},
|
||||
"openai-codex:personal-email@gmail.com": {
|
||||
"openai:personal-email@gmail.com": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: now + 60 * 60 * 1000,
|
||||
@@ -1431,7 +1430,7 @@ describe("codex command", () => {
|
||||
},
|
||||
},
|
||||
lastGood: {
|
||||
"openai-codex": "openai-codex:personal-email@gmail.com",
|
||||
openai: "openai:personal-email@gmail.com",
|
||||
},
|
||||
},
|
||||
config,
|
||||
@@ -1463,7 +1462,7 @@ describe("codex command", () => {
|
||||
expect(result.text).toContain(
|
||||
"\n 1. personal-email@gmail.com ChatGPT subscription — active now",
|
||||
);
|
||||
expect(result.text).not.toContain("api-key");
|
||||
expect(result.text).toContain("\n 2. api-key API key — available if needed");
|
||||
});
|
||||
|
||||
it("explains when an API-key backup is active because the subscription is paused", async () => {
|
||||
@@ -1477,7 +1476,7 @@ describe("codex command", () => {
|
||||
profiles: {
|
||||
"openai:personal-email@gmail.com": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: now + 60 * 60 * 1000,
|
||||
@@ -1490,7 +1489,7 @@ describe("codex command", () => {
|
||||
},
|
||||
"openai:work-email@gmail.com": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "work-access-token",
|
||||
refresh: "work-refresh-token",
|
||||
expires: now + 60 * 60 * 1000,
|
||||
@@ -1587,7 +1586,7 @@ describe("codex command", () => {
|
||||
profiles: {
|
||||
"openai:personal-email@gmail.com": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: now + 60 * 60 * 1000,
|
||||
@@ -1646,10 +1645,10 @@ describe("codex command", () => {
|
||||
expect(result.text).toContain("Now using: api-key-backup");
|
||||
expect(result.text).toContain("subscription rate-limited");
|
||||
expect(result.text).toContain(
|
||||
"\n 1. api-key-backup API key — active now \u00b7 billed per token",
|
||||
"\n 1. personal-email@gmail.com ChatGPT subscription — rate-limited",
|
||||
);
|
||||
expect(result.text).toContain(
|
||||
"\n 2. personal-email@gmail.com ChatGPT subscription — rate-limited",
|
||||
"\n 2. api-key-backup API key — active now \u00b7 billed per token",
|
||||
);
|
||||
expect(result.text).not.toContain(
|
||||
"personal-email@gmail.com ChatGPT subscription — active now",
|
||||
@@ -1663,17 +1662,17 @@ describe("codex command", () => {
|
||||
{
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:default": {
|
||||
"openai:default": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "stale-access-token",
|
||||
refresh: "stale-refresh-token",
|
||||
expires: now + 2 * 24 * 60 * 60 * 1000,
|
||||
email: "previous@example.com",
|
||||
},
|
||||
"openai-codex:fresh-email@example.com": {
|
||||
"openai:fresh-email@example.com": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "fresh-access-token",
|
||||
refresh: "fresh-refresh-token",
|
||||
expires: now + 9 * 24 * 60 * 60 * 1000,
|
||||
@@ -1681,10 +1680,10 @@ describe("codex command", () => {
|
||||
},
|
||||
},
|
||||
order: {
|
||||
"openai-codex": ["openai-codex:fresh-email@example.com", "openai-codex:default"],
|
||||
openai: ["openai:fresh-email@example.com", "openai:default"],
|
||||
},
|
||||
lastGood: {
|
||||
"openai-codex": "openai-codex:default",
|
||||
openai: "openai:default",
|
||||
},
|
||||
},
|
||||
config,
|
||||
@@ -1717,7 +1716,7 @@ describe("codex command", () => {
|
||||
"\n 2. previous@example.com ChatGPT subscription — available if needed",
|
||||
);
|
||||
expect(result.text).not.toContain("previous@example.com ChatGPT subscription — active now");
|
||||
expect(result.text).not.toContain("openai-codex:");
|
||||
expect(result.text).not.toContain("openai:");
|
||||
expect(safeCodexControlRequest).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -1779,26 +1778,26 @@ describe("codex command", () => {
|
||||
{
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:fresh@example.com": {
|
||||
"openai:fresh@example.com": {
|
||||
type: "token",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
token: "fresh-token",
|
||||
expires: now - 1000,
|
||||
email: "fresh@example.com",
|
||||
},
|
||||
"openai-codex:stale@example.com": {
|
||||
"openai:stale@example.com": {
|
||||
type: "token",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
token: "stale-token",
|
||||
expires: now - 2000,
|
||||
email: "stale@example.com",
|
||||
},
|
||||
},
|
||||
order: {
|
||||
"openai-codex": ["openai-codex:fresh@example.com", "openai-codex:stale@example.com"],
|
||||
openai: ["openai:fresh@example.com", "openai:stale@example.com"],
|
||||
},
|
||||
lastGood: {
|
||||
"openai-codex": "openai-codex:stale@example.com",
|
||||
openai: "openai:stale@example.com",
|
||||
},
|
||||
},
|
||||
config,
|
||||
@@ -3319,7 +3318,7 @@ describe("codex command", () => {
|
||||
schemaVersion: 1,
|
||||
threadId: "thread-123",
|
||||
cwd: "/repo",
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
modelProvider: "openai",
|
||||
}),
|
||||
);
|
||||
@@ -3371,7 +3370,7 @@ describe("codex command", () => {
|
||||
threadId: "thread-123",
|
||||
model: "gpt-5.4",
|
||||
modelProvider: "openai",
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
});
|
||||
expect(requestConversationBinding).toHaveBeenCalledWith({
|
||||
summary: "Codex app-server thread thread-123 in /repo",
|
||||
|
||||
@@ -127,20 +127,20 @@ describe("codex conversation binding", () => {
|
||||
it("uses the default Codex auth profile and omits the public OpenAI provider for new binds", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const config = {
|
||||
auth: { order: { "openai-codex": ["openai-codex:default"] } },
|
||||
auth: { order: { openai: ["openai:default"] } },
|
||||
};
|
||||
const requests: Array<{ method: string; params: Record<string, unknown> }> = [];
|
||||
agentRuntimeMocks.ensureAuthProfileStore.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai-codex:default": {
|
||||
"openai:default": {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
},
|
||||
},
|
||||
});
|
||||
agentRuntimeMocks.resolveAuthProfileOrder.mockReturnValue(["openai-codex:default"]);
|
||||
agentRuntimeMocks.resolveAuthProfileOrder.mockReturnValue(["openai:default"]);
|
||||
sharedClientMocks.getSharedCodexAppServerClient.mockResolvedValue({
|
||||
request: vi.fn(async (method: string, requestParams: Record<string, unknown>) => {
|
||||
requests.push({ method, params: requestParams });
|
||||
@@ -164,18 +164,18 @@ describe("codex conversation binding", () => {
|
||||
provider?: unknown;
|
||||
};
|
||||
expect(authOrderParams?.cfg).toBe(config);
|
||||
expect(authOrderParams?.provider).toBe("openai-codex");
|
||||
expect(authOrderParams?.provider).toBe("openai");
|
||||
const sharedClientParams = mockCallArg(sharedClientMocks.getSharedCodexAppServerClient) as {
|
||||
authProfileId?: unknown;
|
||||
};
|
||||
expect(sharedClientParams?.authProfileId).toBe("openai-codex:default");
|
||||
expect(sharedClientParams?.authProfileId).toBe("openai:default");
|
||||
expect(requests).toHaveLength(1);
|
||||
expect(requests[0]?.method).toBe("thread/start");
|
||||
expect(requests[0]?.params.model).toBe("gpt-5.4-mini");
|
||||
expect(requests[0]?.params.personality).toBe("none");
|
||||
expect(requests[0]?.params).not.toHaveProperty("modelProvider");
|
||||
await expect(fs.readFile(`${sessionFile}.codex-app-server.json`, "utf8")).resolves.toContain(
|
||||
'"authProfileId": "openai-codex:default"',
|
||||
'"authProfileId": "openai:default"',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -186,7 +186,7 @@ describe("codex conversation binding", () => {
|
||||
profiles: {
|
||||
work: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -622,7 +622,7 @@ describe("codex conversation binding", () => {
|
||||
profiles: {
|
||||
work: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
},
|
||||
},
|
||||
@@ -1045,7 +1045,7 @@ describe("codex conversation binding", () => {
|
||||
schemaVersion: 1,
|
||||
threadId: "thread-1",
|
||||
cwd: tempDir,
|
||||
authProfileId: "openai-codex:work",
|
||||
authProfileId: "openai:work",
|
||||
}),
|
||||
);
|
||||
const unhandledRejections: unknown[] = [];
|
||||
|
||||
@@ -714,13 +714,10 @@ function resolveThreadRequestModelProvider(params: {
|
||||
if (!modelProvider || modelProvider.toLowerCase() === "codex") {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
isCodexAppServerNativeAuthProfile(params) &&
|
||||
(modelProvider.toLowerCase() === "openai" || modelProvider.toLowerCase() === "openai-codex")
|
||||
) {
|
||||
if (isCodexAppServerNativeAuthProfile(params) && modelProvider.toLowerCase() === "openai") {
|
||||
return undefined;
|
||||
}
|
||||
return modelProvider.toLowerCase() === "openai-codex" ? "openai" : modelProvider;
|
||||
return modelProvider.toLowerCase() === "openai" ? "openai" : modelProvider;
|
||||
}
|
||||
|
||||
function buildAgentLookup(params: {
|
||||
|
||||
@@ -71,7 +71,7 @@ describe("codex conversation controls", () => {
|
||||
profileId: "work",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
|
||||
@@ -193,7 +193,7 @@ describe("discord config schema", () => {
|
||||
const cfg = expectValidDiscordConfig({
|
||||
voice: {
|
||||
mode: "agent-proxy",
|
||||
model: "openai-codex/gpt-5.5",
|
||||
model: "openai/gpt-5.5",
|
||||
followUsersEnabled: true,
|
||||
followUsers: ["58398277829140480"],
|
||||
realtime: {
|
||||
@@ -219,7 +219,7 @@ describe("discord config schema", () => {
|
||||
});
|
||||
|
||||
expect(cfg.voice?.mode).toBe("agent-proxy");
|
||||
expect(cfg.voice?.model).toBe("openai-codex/gpt-5.5");
|
||||
expect(cfg.voice?.model).toBe("openai/gpt-5.5");
|
||||
expect(cfg.voice?.followUsersEnabled).toBe(true);
|
||||
expect(cfg.voice?.followUsers).toEqual(["58398277829140480"]);
|
||||
expect(cfg.voice?.realtime?.provider).toBe("openai");
|
||||
|
||||
@@ -195,7 +195,7 @@ export const discordChannelConfigUiHints = {
|
||||
},
|
||||
"voice.model": {
|
||||
label: "Discord Voice Model",
|
||||
help: "Optional LLM model override for Discord voice channel responses and realtime agent consults (for example openai-codex/gpt-5.5). Leave unset to inherit the routed agent model.",
|
||||
help: "Optional LLM model override for Discord voice channel responses and realtime agent consults (for example openai/gpt-5.5). Leave unset to inherit the routed agent model.",
|
||||
},
|
||||
"voice.mode": {
|
||||
label: "Discord Voice Mode",
|
||||
|
||||
@@ -933,7 +933,7 @@ describe("Discord model picker interactions", () => {
|
||||
it("opens the first visible provider when the current model provider is filtered out", async () => {
|
||||
const context = createModelPickerContext();
|
||||
const pickerData = createModelsProviderData({
|
||||
"openai-codex": ["gpt-5.5-codex"],
|
||||
openai: ["gpt-5.5-codex"],
|
||||
vllm: ["qwen3-local"],
|
||||
});
|
||||
pickerData.resolvedDefault = {
|
||||
@@ -950,7 +950,7 @@ describe("Discord model picker interactions", () => {
|
||||
defaults: {
|
||||
model: { primary: "anthropic/claude-opus-4-5" },
|
||||
models: {
|
||||
"openai-codex/*": {},
|
||||
"openai/*": {},
|
||||
"vllm/*": {},
|
||||
},
|
||||
},
|
||||
@@ -970,7 +970,7 @@ describe("Discord model picker interactions", () => {
|
||||
|
||||
expect(loadSpy).toHaveBeenCalledWith(cfg, "main");
|
||||
const payload = JSON.stringify(firstMockArg(interaction.reply, "interaction.reply"));
|
||||
expect(payload).toContain("openai-codex");
|
||||
expect(payload).toContain("openai");
|
||||
expect(payload).toContain("gpt-5.5-codex");
|
||||
expect(payload).not.toContain("Provider not found");
|
||||
});
|
||||
|
||||
@@ -489,7 +489,7 @@ describe("Discord native plugin command dispatch", () => {
|
||||
const interaction = createInteraction();
|
||||
runtimeModuleMocks.getSessionEntry.mockReturnValue({
|
||||
sessionId: "discord-session",
|
||||
authProfileOverride: "openai-codex:owner@example.com",
|
||||
authProfileOverride: "openai:owner@example.com",
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
|
||||
@@ -516,7 +516,7 @@ describe("Discord native plugin command dispatch", () => {
|
||||
mock: executeSpy,
|
||||
commandName: "pair",
|
||||
expected: {
|
||||
authProfileId: "openai-codex:owner@example.com",
|
||||
authProfileId: "openai:owner@example.com",
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -541,7 +541,7 @@ describe("Discord native plugin command dispatch", () => {
|
||||
}));
|
||||
runtimeModuleMocks.getSessionEntry.mockReturnValue({
|
||||
sessionId: "codex-session",
|
||||
authProfileOverride: "openai-codex:owner@example.com",
|
||||
authProfileOverride: "openai:owner@example.com",
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
|
||||
@@ -570,7 +570,7 @@ describe("Discord native plugin command dispatch", () => {
|
||||
expected: {
|
||||
agentId: "codex",
|
||||
sessionKey: pluginSessionKey,
|
||||
authProfileId: "openai-codex:owner@example.com",
|
||||
authProfileId: "openai:owner@example.com",
|
||||
},
|
||||
});
|
||||
expect(runtimeModuleMocks.getSessionEntry).toHaveBeenCalledWith({
|
||||
|
||||
@@ -137,7 +137,7 @@ function installProviderThinkingRegistryForTest(): void {
|
||||
provider: {
|
||||
id: "discord-test-thinking",
|
||||
label: "Discord Test Thinking",
|
||||
aliases: ["anthropic", "openai-codex"],
|
||||
aliases: ["anthropic", "openai"],
|
||||
auth: [],
|
||||
isBinaryThinking: (context) =>
|
||||
providerThinkingMocks.resolveProviderBinaryThinking({
|
||||
@@ -181,7 +181,7 @@ describe("discord native /think autocomplete", () => {
|
||||
providerThinkingMocks.resolveProviderDefaultThinkingLevel.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderThinkingProfile.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) =>
|
||||
provider === "openai-codex" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId)
|
||||
provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId)
|
||||
? true
|
||||
: undefined,
|
||||
);
|
||||
@@ -212,7 +212,7 @@ describe("discord native /think autocomplete", () => {
|
||||
providerThinkingMocks.resolveProviderThinkingProfile.mockReturnValue(undefined);
|
||||
providerThinkingMocks.resolveProviderXHighThinking.mockReset();
|
||||
providerThinkingMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) =>
|
||||
provider === "openai-codex" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId)
|
||||
provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId)
|
||||
? true
|
||||
: undefined,
|
||||
);
|
||||
@@ -223,7 +223,7 @@ describe("discord native /think autocomplete", () => {
|
||||
JSON.stringify({
|
||||
[SESSION_KEY]: {
|
||||
updatedAt: Date.now(),
|
||||
providerOverride: "openai-codex",
|
||||
providerOverride: "openai",
|
||||
modelOverride: "gpt-5.4",
|
||||
},
|
||||
}),
|
||||
@@ -292,7 +292,7 @@ describe("discord native /think autocomplete", () => {
|
||||
threadBindings: createNoopThreadBindingManager("default"),
|
||||
});
|
||||
expect(context).toEqual({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4",
|
||||
});
|
||||
|
||||
|
||||
@@ -2212,7 +2212,7 @@ describe("DiscordVoiceManager", () => {
|
||||
groupPolicy: "open",
|
||||
voice: {
|
||||
enabled: true,
|
||||
model: "openai-codex/gpt-5.5",
|
||||
model: "openai/gpt-5.5",
|
||||
realtime: {
|
||||
provider: "openai",
|
||||
model: "gpt-realtime-2",
|
||||
@@ -2297,7 +2297,7 @@ describe("DiscordVoiceManager", () => {
|
||||
);
|
||||
|
||||
const commandArgs = lastAgentCommandArgs();
|
||||
expect(commandArgs.model).toBe("openai-codex/gpt-5.5");
|
||||
expect(commandArgs.model).toBe("openai/gpt-5.5");
|
||||
expect(commandArgs.messageProvider).toBe("discord-voice");
|
||||
expect(commandArgs.toolsAllow).toBeUndefined();
|
||||
expect(realtimeSessionMock.submitToolResult).toHaveBeenCalledTimes(1);
|
||||
@@ -4465,7 +4465,7 @@ describe("DiscordVoiceManager", () => {
|
||||
voice: {
|
||||
enabled: true,
|
||||
mode: "bidi",
|
||||
model: "openai-codex/gpt-5.5",
|
||||
model: "openai/gpt-5.5",
|
||||
realtime: {
|
||||
provider: "openai",
|
||||
model: "gpt-realtime-2",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Allowlist of provider/model keys like openai-codex/gpt-5.2."
|
||||
"description": "Allowlist of provider/model keys like openai/gpt-5.2."
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "integer",
|
||||
|
||||
+112
-145
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
||||
import { loadAuthProfileStoreWithoutExternalProfiles } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import {
|
||||
createMigrationItem,
|
||||
createMigrationManualItem,
|
||||
markMigrationItemConflict,
|
||||
markMigrationItemError,
|
||||
markMigrationItemSkipped,
|
||||
@@ -37,8 +38,8 @@ import {
|
||||
import type { HermesSource } from "./source.js";
|
||||
import type { PlannedTargets } from "./targets.js";
|
||||
|
||||
const HERMES_OPENAI_CODEX_SOURCE_PROVIDER_ID = "openai-codex";
|
||||
const OPENAI_PROVIDER_ID = "openai";
|
||||
const LEGACY_OPENAI_PROVIDER_ID = ["openai", "codex"].join("-");
|
||||
const OPENAI_DEFAULT_MODEL = "openai/gpt-5.5";
|
||||
const HERMES_AUTH_DISPLAY_NAME = "Hermes import";
|
||||
|
||||
@@ -51,7 +52,7 @@ type HermesCodexAuthCandidate = {
|
||||
access: string;
|
||||
accountId?: string;
|
||||
refresh: string;
|
||||
sourceKind: "hermes-auth-json" | "opencode-auth-json";
|
||||
sourceKind: "opencode-auth-json";
|
||||
sourceCredentialIndex?: number;
|
||||
sourceLabel: string;
|
||||
sourcePath: string;
|
||||
@@ -65,14 +66,6 @@ type HermesCodexAuthProfile = {
|
||||
sourceProfileId: string;
|
||||
};
|
||||
|
||||
function readTimestamp(value: unknown): number | undefined {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
function sourceCredentialFingerprint(candidate: HermesCodexAuthCandidate): string {
|
||||
const hash = createHash("sha256");
|
||||
for (const part of [
|
||||
@@ -87,86 +80,6 @@ function sourceCredentialFingerprint(candidate: HermesCodexAuthCandidate): strin
|
||||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
function readProviderTokens(
|
||||
auth: Record<string, unknown>,
|
||||
sourcePath: string,
|
||||
): HermesCodexAuthCandidate | undefined {
|
||||
const providers = isRecord(auth.providers) ? auth.providers : {};
|
||||
const provider = isRecord(providers[HERMES_OPENAI_CODEX_SOURCE_PROVIDER_ID])
|
||||
? providers[HERMES_OPENAI_CODEX_SOURCE_PROVIDER_ID]
|
||||
: undefined;
|
||||
const tokens = isRecord(provider?.tokens) ? provider.tokens : undefined;
|
||||
const access = readString(tokens?.access_token);
|
||||
const refresh = readString(tokens?.refresh_token);
|
||||
if (!access || !refresh) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
access,
|
||||
refresh,
|
||||
sourceKind: "hermes-auth-json",
|
||||
sourceLabel: "Hermes active OpenAI Codex provider",
|
||||
sourcePath,
|
||||
updatedAt: readTimestamp(provider?.last_refresh),
|
||||
};
|
||||
}
|
||||
|
||||
function readPoolTokens(
|
||||
auth: Record<string, unknown>,
|
||||
sourcePath: string,
|
||||
): HermesCodexAuthCandidate[] {
|
||||
const pool = isRecord(auth.credential_pool) ? auth.credential_pool : {};
|
||||
const entries = Array.isArray(pool[HERMES_OPENAI_CODEX_SOURCE_PROVIDER_ID])
|
||||
? pool[HERMES_OPENAI_CODEX_SOURCE_PROVIDER_ID]
|
||||
: [];
|
||||
const candidates: HermesCodexAuthCandidate[] = [];
|
||||
for (const entry of entries) {
|
||||
if (!isRecord(entry)) {
|
||||
continue;
|
||||
}
|
||||
const access = readString(entry.access_token);
|
||||
const refresh = readString(entry.refresh_token);
|
||||
if (!access || !refresh) {
|
||||
continue;
|
||||
}
|
||||
const label = readString(entry.label) ?? "Hermes OpenAI Codex credential pool";
|
||||
candidates.push({
|
||||
access,
|
||||
refresh,
|
||||
sourceKind: "hermes-auth-json",
|
||||
sourceLabel: label,
|
||||
sourcePath,
|
||||
updatedAt: readTimestamp(entry.last_refresh) ?? readTimestamp(entry.last_status_at),
|
||||
});
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
async function readHermesCodexAuthCandidates(
|
||||
authPath: string | undefined,
|
||||
): Promise<HermesCodexAuthCandidate[]> {
|
||||
const raw = await readText(authPath);
|
||||
if (!raw || !authPath) {
|
||||
return [];
|
||||
}
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
if (!isRecord(parsed)) {
|
||||
return [];
|
||||
}
|
||||
const candidates = [readProviderTokens(parsed, authPath), ...readPoolTokens(parsed, authPath)]
|
||||
.filter((candidate): candidate is HermesCodexAuthCandidate => candidate !== undefined)
|
||||
.toSorted((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0));
|
||||
candidates.forEach((candidate, index) => {
|
||||
candidate.sourceCredentialIndex = index;
|
||||
});
|
||||
return candidates;
|
||||
}
|
||||
|
||||
async function readOpenCodeOpenAICandidates(
|
||||
authPath: string | undefined,
|
||||
): Promise<HermesCodexAuthCandidate[]> {
|
||||
@@ -203,6 +116,48 @@ async function readOpenCodeOpenAICandidates(
|
||||
];
|
||||
}
|
||||
|
||||
async function hasLegacyHermesAuthJson(authPath: string | undefined): Promise<boolean> {
|
||||
const raw = await readText(authPath);
|
||||
if (!raw) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
return (
|
||||
isRecord(parsed) &&
|
||||
(hasLegacyOpenAIOAuthTokenFields(parsed.providers, "providers") ||
|
||||
hasLegacyOpenAIOAuthTokenFields(parsed.credential_pool, "credential_pool") ||
|
||||
hasLegacyOpenAIOAuthTokenFields(parsed.tokens, "tokens"))
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasLegacyOpenAIOAuthTokenFields(value: unknown, keyHint = ""): boolean {
|
||||
if (Array.isArray(value)) {
|
||||
return value.some((entry) => hasLegacyOpenAIOAuthTokenFields(entry, keyHint));
|
||||
}
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
const provider = readString(value.provider)?.toLowerCase();
|
||||
const normalizedKeyHint = keyHint.toLowerCase();
|
||||
const isOpenAIRecord =
|
||||
normalizedKeyHint.includes("openai") ||
|
||||
provider === OPENAI_PROVIDER_ID ||
|
||||
provider === LEGACY_OPENAI_PROVIDER_ID;
|
||||
const hasTokenPair =
|
||||
(readString(value.access) && readString(value.refresh)) ||
|
||||
(readString(value.access_token) && readString(value.refresh_token));
|
||||
if (isOpenAIRecord && hasTokenPair) {
|
||||
return true;
|
||||
}
|
||||
return Object.entries(value).some(([key, entry]) =>
|
||||
hasLegacyOpenAIOAuthTokenFields(entry, keyHint ? `${keyHint}.${key}` : key),
|
||||
);
|
||||
}
|
||||
|
||||
function buildAuthResult(
|
||||
candidate: HermesCodexAuthCandidate,
|
||||
fallbackProfileName = "hermes-import",
|
||||
@@ -282,10 +237,9 @@ function authProfileDedupeKey(profile: HermesCodexAuthProfile): string {
|
||||
async function readCodexAuthProfilesFromSource(
|
||||
source: HermesSource,
|
||||
): Promise<HermesCodexAuthProfile[]> {
|
||||
const candidates = [
|
||||
...(await readHermesCodexAuthCandidates(source.authPath)),
|
||||
...(await readOpenCodeOpenAICandidates(source.opencodeAuthPath)),
|
||||
].toSorted((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0));
|
||||
const candidates = (await readOpenCodeOpenAICandidates(source.opencodeAuthPath)).toSorted(
|
||||
(left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0),
|
||||
);
|
||||
const profiles: HermesCodexAuthProfile[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const [index, candidate] of candidates.entries()) {
|
||||
@@ -323,11 +277,7 @@ async function readCodexAuthProfilesFromPath(params: {
|
||||
...(params.sourcePath ? { opencodeAuthPath: params.sourcePath } : {}),
|
||||
});
|
||||
}
|
||||
return await readCodexAuthProfilesFromSource({
|
||||
root: "",
|
||||
archivePaths: [],
|
||||
...(params.sourcePath ? { authPath: params.sourcePath } : {}),
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
function findMatchingProfile(
|
||||
@@ -402,56 +352,73 @@ export async function buildAuthItems(params: {
|
||||
source: HermesSource;
|
||||
targets: PlannedTargets;
|
||||
}): Promise<MigrationItem[]> {
|
||||
const items: MigrationItem[] = [];
|
||||
if (await hasLegacyHermesAuthJson(params.source.authPath)) {
|
||||
items.push(
|
||||
createMigrationManualItem({
|
||||
id: "manual:legacy-hermes-auth-json",
|
||||
source: params.source.authPath ?? "auth.json",
|
||||
message:
|
||||
"Hermes auth.json contains legacy OAuth credentials. OpenClaw no longer imports those into live auth during Hermes migration.",
|
||||
recommendation:
|
||||
"Run openclaw models auth login --provider openai after migration, or run openclaw doctor --fix for existing OpenClaw legacy auth state.",
|
||||
}),
|
||||
);
|
||||
}
|
||||
const profiles = await readCodexAuthProfilesFromSource(params.source);
|
||||
if (profiles.length === 0) {
|
||||
return [];
|
||||
return items;
|
||||
}
|
||||
const store = loadAuthProfileStoreWithoutExternalProfiles(params.targets.agentDir);
|
||||
return profiles.map((profile) => {
|
||||
const matchedProfileId = findMatchingProfile(store, profile.credential);
|
||||
const profileId = matchedProfileId ?? profile.sourceProfileId;
|
||||
const targetExists = Boolean(store.profiles[profileId]);
|
||||
const skipped = !params.ctx.includeSecrets;
|
||||
const configConflict = hasAuthProfileConfigConflict(
|
||||
params.ctx.config,
|
||||
oauthAuthProfileConfig(profileId, profile.credential),
|
||||
Boolean(params.ctx.overwrite),
|
||||
);
|
||||
const conflict =
|
||||
((targetExists && !matchedProfileId && !params.ctx.overwrite) || configConflict) && !skipped;
|
||||
const itemId =
|
||||
profiles.length === 1
|
||||
? `auth:${OPENAI_PROVIDER_ID}`
|
||||
: `auth:${OPENAI_PROVIDER_ID}:${profile.sourceProfileId}`;
|
||||
return createMigrationItem({
|
||||
id: itemId,
|
||||
kind: "auth",
|
||||
action: skipped ? "skip" : "create",
|
||||
source: profile.candidate.sourcePath,
|
||||
target: `${params.targets.agentDir}/auth-profiles.json#${profileId}`,
|
||||
status: skipped ? "skipped" : conflict ? "conflict" : "planned",
|
||||
sensitive: true,
|
||||
reason: skipped
|
||||
? HERMES_REASON_INCLUDE_SECRETS
|
||||
: conflict
|
||||
? HERMES_REASON_AUTH_PROFILE_EXISTS
|
||||
: undefined,
|
||||
message: skipped
|
||||
? "OpenAI Codex OAuth credentials detected in Hermes."
|
||||
: "Import Hermes OpenAI Codex OAuth credentials and configure OpenAI Codex models.",
|
||||
details: {
|
||||
provider: OPENAI_PROVIDER_ID,
|
||||
profileId,
|
||||
...(typeof profile.candidate.sourceCredentialIndex === "number"
|
||||
? { sourceCredentialIndex: profile.candidate.sourceCredentialIndex }
|
||||
: {}),
|
||||
sourceCredentialFingerprint: sourceCredentialFingerprint(profile.candidate),
|
||||
sourceProfileId: profile.sourceProfileId,
|
||||
sourceKind: profile.candidate.sourceKind,
|
||||
sourceLabel: profile.candidate.sourceLabel,
|
||||
},
|
||||
});
|
||||
});
|
||||
items.push(
|
||||
...profiles.map((profile) => {
|
||||
const matchedProfileId = findMatchingProfile(store, profile.credential);
|
||||
const profileId = matchedProfileId ?? profile.sourceProfileId;
|
||||
const targetExists = Boolean(store.profiles[profileId]);
|
||||
const skipped = !params.ctx.includeSecrets;
|
||||
const configConflict = hasAuthProfileConfigConflict(
|
||||
params.ctx.config,
|
||||
oauthAuthProfileConfig(profileId, profile.credential),
|
||||
Boolean(params.ctx.overwrite),
|
||||
);
|
||||
const conflict =
|
||||
((targetExists && !matchedProfileId && !params.ctx.overwrite) || configConflict) &&
|
||||
!skipped;
|
||||
const itemId =
|
||||
profiles.length === 1
|
||||
? `auth:${OPENAI_PROVIDER_ID}`
|
||||
: `auth:${OPENAI_PROVIDER_ID}:${profile.sourceProfileId}`;
|
||||
return createMigrationItem({
|
||||
id: itemId,
|
||||
kind: "auth",
|
||||
action: skipped ? "skip" : "create",
|
||||
source: profile.candidate.sourcePath,
|
||||
target: `${params.targets.agentDir}/auth-profiles.json#${profileId}`,
|
||||
status: skipped ? "skipped" : conflict ? "conflict" : "planned",
|
||||
sensitive: true,
|
||||
reason: skipped
|
||||
? HERMES_REASON_INCLUDE_SECRETS
|
||||
: conflict
|
||||
? HERMES_REASON_AUTH_PROFILE_EXISTS
|
||||
: undefined,
|
||||
message: skipped
|
||||
? "OpenAI OAuth credentials detected in OpenCode."
|
||||
: "Import OpenAI OAuth credentials and configure OpenAI models.",
|
||||
details: {
|
||||
provider: OPENAI_PROVIDER_ID,
|
||||
profileId,
|
||||
...(typeof profile.candidate.sourceCredentialIndex === "number"
|
||||
? { sourceCredentialIndex: profile.candidate.sourceCredentialIndex }
|
||||
: {}),
|
||||
sourceCredentialFingerprint: sourceCredentialFingerprint(profile.candidate),
|
||||
sourceProfileId: profile.sourceProfileId,
|
||||
sourceKind: profile.candidate.sourceKind,
|
||||
sourceLabel: profile.candidate.sourceLabel,
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
export async function applyAuthItem(
|
||||
|
||||
@@ -182,4 +182,69 @@ describe("Hermes migration file and skill items", () => {
|
||||
await expectPathMissing(path.join(reportDir, "archive", "auth.json"));
|
||||
await expectPathMissing(path.join(workspaceDir, "logs", "session.log"));
|
||||
});
|
||||
|
||||
it("reports legacy Hermes auth.json OAuth state as manual reauth work", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, "hermes");
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
const stateDir = path.join(root, "state");
|
||||
const legacyOpenAIProvider = ["openai", "codex"].join("-");
|
||||
await writeFile(
|
||||
path.join(source, "auth.json"),
|
||||
JSON.stringify({
|
||||
providers: {
|
||||
[legacyOpenAIProvider]: {
|
||||
tokens: {
|
||||
access_token: "old-access",
|
||||
refresh_token: "old-refresh",
|
||||
},
|
||||
},
|
||||
},
|
||||
credential_pool: {
|
||||
[legacyOpenAIProvider]: [
|
||||
{
|
||||
access_token: "pool-access",
|
||||
refresh_token: "pool-refresh",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const provider = buildHermesMigrationProvider();
|
||||
const plan = await provider.plan(
|
||||
makeContext({ source, stateDir, workspaceDir, includeSecrets: true }),
|
||||
);
|
||||
|
||||
const manualAuth = itemById(plan.items, "manual:legacy-hermes-auth-json");
|
||||
expect(manualAuth?.kind).toBe("manual");
|
||||
expect(manualAuth?.status).toBe("skipped");
|
||||
expect(manualAuth?.message).toContain("no longer imports");
|
||||
expect(plan.items.some((item) => item.kind === "auth")).toBe(false);
|
||||
expect(plan.warnings).toContain(
|
||||
"Some Hermes settings require manual review before they can be activated safely.",
|
||||
);
|
||||
});
|
||||
|
||||
it("ignores empty Hermes auth.json credential containers", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, "hermes");
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
const stateDir = path.join(root, "state");
|
||||
await writeFile(
|
||||
path.join(source, "auth.json"),
|
||||
JSON.stringify({
|
||||
providers: {},
|
||||
credential_pool: {},
|
||||
tokens: { anthropic: { access: "other-access", refresh: "other-refresh" } },
|
||||
}),
|
||||
);
|
||||
|
||||
const provider = buildHermesMigrationProvider();
|
||||
const plan = await provider.plan(
|
||||
makeContext({ source, stateDir, workspaceDir, includeSecrets: true }),
|
||||
);
|
||||
|
||||
expect(plan.items.find((item) => item.id === "manual:legacy-hermes-auth-json")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,17 +125,15 @@ describe("Hermes migration provider secret write failures", () => {
|
||||
chatgpt_plan_type: "plus",
|
||||
},
|
||||
});
|
||||
await writeFile(path.join(source, "auth.json"), "{}");
|
||||
const opencodeAuthPath = path.join(root, ".local", "share", "opencode", "auth.json");
|
||||
await writeFile(
|
||||
path.join(source, "auth.json"),
|
||||
opencodeAuthPath,
|
||||
JSON.stringify({
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
last_refresh: new Date().toISOString(),
|
||||
tokens: {
|
||||
access_token: accessToken,
|
||||
refresh_token: "refresh-fail-token",
|
||||
},
|
||||
},
|
||||
openai: {
|
||||
type: "oauth",
|
||||
access: accessToken,
|
||||
refresh: "refresh-fail-token",
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -155,7 +153,7 @@ describe("Hermes migration provider secret write failures", () => {
|
||||
id: "auth:openai",
|
||||
kind: "auth",
|
||||
action: "create",
|
||||
source: path.join(source, "auth.json"),
|
||||
source: opencodeAuthPath,
|
||||
target: `${path.join(stateDir, "agents", "main", "agent")}/auth-profiles.json#openai:account-acct_fail`,
|
||||
status: "error",
|
||||
sensitive: true,
|
||||
|
||||
@@ -354,96 +354,6 @@ describe("Hermes migration secret items", () => {
|
||||
await expectMissingPath(path.join(agentDir, "auth-profiles.json"));
|
||||
});
|
||||
|
||||
it("imports Hermes auth.json OpenAI Codex OAuth and configures models", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, "hermes");
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
const stateDir = path.join(root, "state");
|
||||
const reportDir = path.join(root, "report");
|
||||
const agentDir = path.join(stateDir, "agents", "main", "agent");
|
||||
const accessToken = fakeJwt({
|
||||
exp: Math.floor(Date.now() / 1000) + 3600,
|
||||
"https://api.openai.com/profile": { email: "codex@example.test" },
|
||||
"https://api.openai.com/auth": {
|
||||
chatgpt_account_id: "acct_test",
|
||||
chatgpt_plan_type: "plus",
|
||||
},
|
||||
});
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
await writeFile(
|
||||
path.join(source, "auth.json"),
|
||||
JSON.stringify({
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
last_refresh: new Date().toISOString(),
|
||||
tokens: {
|
||||
access_token: accessToken,
|
||||
refresh_token: "refresh-test-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const provider = buildHermesMigrationProvider();
|
||||
const ctx = makeContext({
|
||||
source,
|
||||
stateDir,
|
||||
workspaceDir,
|
||||
config,
|
||||
includeSecrets: true,
|
||||
reportDir,
|
||||
runtime: makeConfigRuntime(config),
|
||||
});
|
||||
const plan = await provider.plan(ctx);
|
||||
|
||||
expect(plan.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "auth:openai",
|
||||
kind: "auth",
|
||||
status: "planned",
|
||||
sensitive: true,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const result = await provider.apply(ctx, plan);
|
||||
|
||||
expect(result.summary.errors).toBe(0);
|
||||
expect(result.summary.migrated).toBeGreaterThanOrEqual(1);
|
||||
const authStore = JSON.parse(
|
||||
await fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf8"),
|
||||
) as {
|
||||
profiles?: Record<
|
||||
string,
|
||||
{ access?: string; provider?: string; refresh?: string; type?: string }
|
||||
>;
|
||||
};
|
||||
const profile = authStore.profiles?.["openai:account-acct_test"];
|
||||
expect(profile).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "oauth",
|
||||
provider: "openai",
|
||||
access: accessToken,
|
||||
refresh: "refresh-test-token",
|
||||
}),
|
||||
);
|
||||
expect(config.auth?.profiles?.["openai:account-acct_test"]).toEqual(
|
||||
expect.objectContaining({
|
||||
provider: "openai",
|
||||
mode: "oauth",
|
||||
}),
|
||||
);
|
||||
expect(config.agents?.defaults?.models?.["openai/gpt-5.5"]).toEqual({});
|
||||
});
|
||||
|
||||
it("imports supported Hermes provider env credentials including OpenCode and GitHub Copilot", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, "hermes");
|
||||
@@ -823,7 +733,7 @@ describe("Hermes migration secret items", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("imports OpenCode OpenAI OAuth credentials as OpenAI Codex auth", async () => {
|
||||
it("imports OpenCode OpenAI OAuth credentials as OpenAI auth", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, ".hermes");
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
@@ -837,7 +747,7 @@ describe("Hermes migration secret items", () => {
|
||||
chatgpt_plan_type: "plus",
|
||||
},
|
||||
});
|
||||
await writeFile(path.join(source, "config.yaml"), "model: openai/gpt-5.5\n");
|
||||
await writeFile(path.join(source, "auth.json"), "{}");
|
||||
await writeFile(
|
||||
path.join(root, ".local", "share", "opencode", "auth.json"),
|
||||
JSON.stringify({
|
||||
@@ -908,87 +818,6 @@ describe("Hermes migration secret items", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("applies mixed Hermes and OpenCode OpenAI OAuth credentials with source-stable fingerprints", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, ".hermes");
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
const stateDir = path.join(root, "state");
|
||||
const reportDir = path.join(root, "report");
|
||||
const agentDir = path.join(stateDir, "agents", "main", "agent");
|
||||
await writeFile(
|
||||
path.join(source, "auth.json"),
|
||||
JSON.stringify({
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
tokens: {
|
||||
access_token: "opaque-hermes-access",
|
||||
refresh_token: "opaque-hermes-refresh",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(root, ".local", "share", "opencode", "auth.json"),
|
||||
JSON.stringify({
|
||||
openai: {
|
||||
type: "oauth",
|
||||
access: "opaque-opencode-access",
|
||||
refresh: "opaque-opencode-refresh",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const provider = buildHermesMigrationProvider();
|
||||
const ctx = makeContext({
|
||||
source,
|
||||
stateDir,
|
||||
workspaceDir,
|
||||
includeSecrets: true,
|
||||
reportDir,
|
||||
});
|
||||
const plan = await provider.plan(ctx);
|
||||
const authItems = plan.items.filter((item) => item.kind === "auth");
|
||||
|
||||
expect(authItems).toHaveLength(2);
|
||||
expect(authItems.map((item) => item.status)).toEqual(["planned", "planned"]);
|
||||
expect(authItems.map((item) => item.details?.sourceCredentialIndex)).toEqual([0, 0]);
|
||||
expect(authItems.map((item) => item.details?.sourceCredentialFingerprint)).toEqual([
|
||||
expect.any(String),
|
||||
expect.any(String),
|
||||
]);
|
||||
|
||||
const result = await provider.apply(ctx, plan);
|
||||
|
||||
expect(
|
||||
result.items
|
||||
.filter((item) => item.kind === "auth")
|
||||
.map((item) => ({ id: item.id, status: item.status })),
|
||||
).toEqual([
|
||||
{ id: "auth:openai:openai:hermes-import-1", status: "migrated" },
|
||||
{ id: "auth:openai:openai:hermes-import-2", status: "migrated" },
|
||||
]);
|
||||
const authStore = JSON.parse(
|
||||
await fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf8"),
|
||||
) as {
|
||||
profiles?: Record<string, { access?: string; provider?: string; refresh?: string }>;
|
||||
};
|
||||
expect(authStore.profiles?.["openai:hermes-import-1"]).toEqual(
|
||||
expect.objectContaining({
|
||||
provider: "openai",
|
||||
access: "opaque-hermes-access",
|
||||
refresh: "opaque-hermes-refresh",
|
||||
}),
|
||||
);
|
||||
expect(authStore.profiles?.["openai:hermes-import-2"]).toEqual(
|
||||
expect.objectContaining({
|
||||
provider: "openai",
|
||||
access: "opaque-opencode-access",
|
||||
refresh: "opaque-opencode-refresh",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not apply a planned OpenCode OpenAI OAuth credential after the source token changes", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, ".hermes");
|
||||
@@ -1054,7 +883,7 @@ describe("Hermes migration secret items", () => {
|
||||
await expectMissingPath(path.join(agentDir, "auth-profiles.json"));
|
||||
});
|
||||
|
||||
it("reports Hermes OAuth config auth profile conflicts during planning", async () => {
|
||||
it("reports OpenCode OpenAI OAuth config auth profile conflicts during planning", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, "hermes");
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
@@ -1082,17 +911,14 @@ describe("Hermes migration secret items", () => {
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
await writeFile(path.join(source, "auth.json"), "{}");
|
||||
await writeFile(
|
||||
path.join(source, "auth.json"),
|
||||
path.join(root, ".local", "share", "opencode", "auth.json"),
|
||||
JSON.stringify({
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
last_refresh: new Date().toISOString(),
|
||||
tokens: {
|
||||
access_token: accessToken,
|
||||
refresh_token: "refresh-test-token",
|
||||
},
|
||||
},
|
||||
openai: {
|
||||
type: "oauth",
|
||||
access: accessToken,
|
||||
refresh: "refresh-test-token",
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -1120,145 +946,7 @@ describe("Hermes migration secret items", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("imports every distinct Hermes auth.json OpenAI Codex OAuth credential", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, "hermes");
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
const stateDir = path.join(root, "state");
|
||||
const reportDir = path.join(root, "report");
|
||||
const agentDir = path.join(stateDir, "agents", "main", "agent");
|
||||
const activeAccessToken = fakeJwt({
|
||||
exp: Math.floor(Date.now() / 1000) + 3600,
|
||||
"https://api.openai.com/profile": { email: "active@example.test" },
|
||||
"https://api.openai.com/auth": {
|
||||
chatgpt_account_id: "acct_active",
|
||||
chatgpt_plan_type: "plus",
|
||||
},
|
||||
});
|
||||
const poolAccessToken = fakeJwt({
|
||||
exp: Math.floor(Date.now() / 1000) + 3600,
|
||||
"https://api.openai.com/profile": { email: "pool@example.test" },
|
||||
"https://api.openai.com/auth": {
|
||||
chatgpt_account_id: "acct_pool",
|
||||
chatgpt_plan_type: "team",
|
||||
},
|
||||
});
|
||||
const secondPoolAccessToken = fakeJwt({
|
||||
exp: Math.floor(Date.now() / 1000) + 3600,
|
||||
"https://api.openai.com/profile": { email: "second-pool@example.test" },
|
||||
"https://api.openai.com/auth": {
|
||||
chatgpt_account_id: "acct_second_pool",
|
||||
chatgpt_plan_type: "pro",
|
||||
},
|
||||
});
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
await writeFile(
|
||||
path.join(source, "auth.json"),
|
||||
JSON.stringify({
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
last_refresh: "2026-01-03T00:00:00.000Z",
|
||||
tokens: {
|
||||
access_token: activeAccessToken,
|
||||
refresh_token: "refresh-active-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
credential_pool: {
|
||||
"openai-codex": [
|
||||
{
|
||||
label: "Pool account",
|
||||
last_refresh: "2026-01-02T00:00:00.000Z",
|
||||
access_token: poolAccessToken,
|
||||
refresh_token: "refresh-pool-token",
|
||||
},
|
||||
{
|
||||
label: "Second pool account",
|
||||
last_refresh: "2026-01-01T00:00:00.000Z",
|
||||
access_token: secondPoolAccessToken,
|
||||
refresh_token: "refresh-second-pool-token",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const provider = buildHermesMigrationProvider();
|
||||
const ctx = makeContext({
|
||||
source,
|
||||
stateDir,
|
||||
workspaceDir,
|
||||
config,
|
||||
includeSecrets: true,
|
||||
reportDir,
|
||||
runtime: makeConfigRuntime(config),
|
||||
});
|
||||
const plan = await provider.plan(ctx);
|
||||
const authItems = plan.items.filter((item) => item.kind === "auth");
|
||||
|
||||
expect(authItems).toHaveLength(3);
|
||||
expect(
|
||||
authItems
|
||||
.map((item) => item.details?.profileId)
|
||||
.toSorted((left, right) => String(left).localeCompare(String(right))),
|
||||
).toEqual([
|
||||
"openai:account-acct_active",
|
||||
"openai:account-acct_pool",
|
||||
"openai:account-acct_second_pool",
|
||||
]);
|
||||
|
||||
const result = await provider.apply(ctx, plan);
|
||||
|
||||
expect(result.summary.errors).toBe(0);
|
||||
const authStore = JSON.parse(
|
||||
await fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf8"),
|
||||
) as {
|
||||
profiles?: Record<
|
||||
string,
|
||||
{ access?: string; provider?: string; refresh?: string; type?: string }
|
||||
>;
|
||||
};
|
||||
expect(authStore.profiles?.["openai:account-acct_active"]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "oauth",
|
||||
provider: "openai",
|
||||
access: activeAccessToken,
|
||||
refresh: "refresh-active-token",
|
||||
}),
|
||||
);
|
||||
expect(authStore.profiles?.["openai:account-acct_pool"]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "oauth",
|
||||
provider: "openai",
|
||||
access: poolAccessToken,
|
||||
refresh: "refresh-pool-token",
|
||||
}),
|
||||
);
|
||||
expect(authStore.profiles?.["openai:account-acct_second_pool"]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "oauth",
|
||||
provider: "openai",
|
||||
access: secondPoolAccessToken,
|
||||
refresh: "refresh-second-pool-token",
|
||||
}),
|
||||
);
|
||||
expect(
|
||||
Object.keys(config.auth?.profiles ?? {}).toSorted((left, right) => left.localeCompare(right)),
|
||||
).toEqual([
|
||||
"openai:account-acct_active",
|
||||
"openai:account-acct_pool",
|
||||
"openai:account-acct_second_pool",
|
||||
]);
|
||||
expect(config.agents?.defaults?.models?.["openai/gpt-5.5"]).toEqual({});
|
||||
});
|
||||
|
||||
it("does not collapse Hermes OAuth accounts that share an email", async () => {
|
||||
it("does not collapse OpenCode OpenAI OAuth accounts that share an email", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const source = path.join(root, "hermes");
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
@@ -1281,17 +969,14 @@ describe("Hermes migration secret items", () => {
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
await writeFile(path.join(source, "config.yaml"), "model: openai/gpt-5.5\n");
|
||||
await writeFile(
|
||||
path.join(source, "auth.json"),
|
||||
path.join(root, ".local", "share", "opencode", "auth.json"),
|
||||
JSON.stringify({
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
last_refresh: new Date().toISOString(),
|
||||
tokens: {
|
||||
access_token: accessToken,
|
||||
refresh_token: "refresh-new-token",
|
||||
},
|
||||
},
|
||||
openai: {
|
||||
type: "oauth",
|
||||
access: accessToken,
|
||||
refresh: "refresh-new-token",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -9,9 +9,9 @@ export {
|
||||
OPENAI_DEFAULT_TTS_MODEL,
|
||||
OPENAI_DEFAULT_TTS_VOICE,
|
||||
} from "./default-models.js";
|
||||
export { buildOpenAICodexProvider } from "./openai-codex-catalog.js";
|
||||
export { loginOpenAICodexOAuth } from "./openai-codex-oauth.runtime.js";
|
||||
export { refreshOpenAICodexToken } from "./openai-codex-provider.runtime.js";
|
||||
export { buildOpenAICodexProvider } from "./openai-chatgpt-catalog.js";
|
||||
export { loginOpenAICodexOAuth } from "./openai-chatgpt-oauth.runtime.js";
|
||||
export { refreshOpenAICodexToken } from "./openai-chatgpt-provider.runtime.js";
|
||||
export { buildOpenAICodexProviderPlugin, buildOpenAIProvider } from "./openai-provider.js";
|
||||
export { buildOpenAIRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js";
|
||||
export { buildOpenAIRealtimeVoiceProvider } from "./realtime-voice-provider.js";
|
||||
|
||||
@@ -18,10 +18,12 @@ const {
|
||||
(params: { provider: string; agentDir?: string }) => boolean
|
||||
>(() => false),
|
||||
listProfilesForProviderMock: vi.fn(
|
||||
(store: { profiles?: Record<string, { provider?: string }> }, provider: string) =>
|
||||
Object.entries(store.profiles ?? {})
|
||||
.filter(([, profile]) => profile.provider === provider)
|
||||
.map(([profileId]) => profileId),
|
||||
(store: { profiles?: Record<string, { provider?: string }> }, provider: string) => {
|
||||
const normalize = (raw?: string) => (raw === ["openai", "codex"].join("-") ? "openai" : raw);
|
||||
return Object.entries(store.profiles ?? {})
|
||||
.filter(([, profile]) => normalize(profile.provider) === provider)
|
||||
.map(([profileId]) => profileId);
|
||||
},
|
||||
),
|
||||
resolveApiKeyForProviderMock: vi.fn(
|
||||
async (_params?: {
|
||||
@@ -156,8 +158,8 @@ function mockCodexRawStream(body: string) {
|
||||
|
||||
function mockCodexAuthOnly() {
|
||||
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
|
||||
if (params?.provider === "openai-codex") {
|
||||
return { apiKey: "codex-key", source: "profile:openai-codex:default", mode: "oauth" };
|
||||
if (params?.provider === "openai") {
|
||||
return { apiKey: "codex-key", source: "profile:openai:default", mode: "oauth" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
@@ -167,9 +169,9 @@ function createCodexOAuthAuthStore() {
|
||||
return {
|
||||
version: 1 as const,
|
||||
profiles: {
|
||||
"openai-codex:default": {
|
||||
"openai:default": {
|
||||
type: "oauth" as const,
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
access: "codex-access",
|
||||
refresh: "codex-refresh",
|
||||
expires: Date.now() + 60_000,
|
||||
@@ -182,9 +184,9 @@ function createCodexApiKeyAuthStore() {
|
||||
return {
|
||||
version: 1 as const,
|
||||
profiles: {
|
||||
"openai-codex:manual": {
|
||||
"openai:manual": {
|
||||
type: "api_key" as const,
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
key: "codex-api-key",
|
||||
},
|
||||
},
|
||||
@@ -195,9 +197,9 @@ function createCodexTokenAuthStore() {
|
||||
return {
|
||||
version: 1 as const,
|
||||
profiles: {
|
||||
"openai-codex:token": {
|
||||
"openai:token": {
|
||||
type: "token" as const,
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
token: "codex-token",
|
||||
},
|
||||
},
|
||||
@@ -359,9 +361,7 @@ describe("openai image generation provider", () => {
|
||||
it("reports configured when either OpenAI API key auth or Codex OAuth auth is available", () => {
|
||||
const provider = buildOpenAIImageGenerationProvider();
|
||||
|
||||
isProviderApiKeyConfiguredMock.mockImplementation((params?: { provider?: string }) => {
|
||||
return params?.provider === "openai";
|
||||
});
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(true);
|
||||
expect(provider.isConfigured?.({ agentDir: "/tmp/agent" })).toBe(true);
|
||||
expect(isProviderApiKeyConfiguredMock).toHaveBeenCalledWith({
|
||||
provider: "openai",
|
||||
@@ -369,29 +369,46 @@ describe("openai image generation provider", () => {
|
||||
});
|
||||
|
||||
isProviderApiKeyConfiguredMock.mockClear();
|
||||
isProviderApiKeyConfiguredMock.mockImplementation((params?: { provider?: string }) => {
|
||||
return params?.provider === "openai-codex";
|
||||
});
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(false);
|
||||
ensureAuthProfileStoreMock.mockReturnValue(createCodexOAuthAuthStore());
|
||||
expect(provider.isConfigured?.({ agentDir: "/tmp/agent" })).toBe(true);
|
||||
expect(isProviderApiKeyConfiguredMock).toHaveBeenCalledWith({
|
||||
provider: "openai",
|
||||
agentDir: "/tmp/agent",
|
||||
});
|
||||
expect(isProviderApiKeyConfiguredMock).toHaveBeenCalledWith({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
agentDir: "/tmp/agent",
|
||||
});
|
||||
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(false);
|
||||
ensureAuthProfileStoreMock.mockReturnValue({ version: 1, profiles: {} });
|
||||
expect(provider.isConfigured?.({ agentDir: "/tmp/agent" })).toBe(false);
|
||||
});
|
||||
|
||||
it("reports configured before doctor rewrites retired OpenAI auth profiles", () => {
|
||||
const provider = buildOpenAIImageGenerationProvider();
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(false);
|
||||
ensureAuthProfileStoreMock.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai:default": {
|
||||
type: "oauth",
|
||||
provider: ["openai", "codex"].join("-"),
|
||||
access: "legacy-chatgpt-access",
|
||||
refresh: "legacy-chatgpt-refresh",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(provider.isConfigured?.({ agentDir: "/tmp/agent" })).toBe(true);
|
||||
});
|
||||
|
||||
it("does not report Codex OAuth image auth as configured for custom OpenAI endpoints", () => {
|
||||
const provider = buildOpenAIImageGenerationProvider();
|
||||
|
||||
isProviderApiKeyConfiguredMock.mockImplementation((params?: { provider?: string }) => {
|
||||
return params?.provider === "openai-codex";
|
||||
});
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(false);
|
||||
|
||||
expect(
|
||||
provider.isConfigured?.({
|
||||
@@ -410,12 +427,50 @@ describe("openai image generation provider", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("reports ChatGPT OAuth image auth as configured for ChatGPT routes", () => {
|
||||
const provider = buildOpenAIImageGenerationProvider();
|
||||
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(false);
|
||||
ensureAuthProfileStoreMock.mockReturnValue(createCodexOAuthAuthStore());
|
||||
|
||||
expect(
|
||||
provider.isConfigured?.({
|
||||
agentDir: "/tmp/agent",
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
provider.isConfigured?.({
|
||||
agentDir: "/tmp/agent",
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://openai-compatible.example.test/v1",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not report OpenAI OAuth image auth as configured for custom OpenAI endpoints", () => {
|
||||
const provider = buildOpenAIImageGenerationProvider();
|
||||
vi.stubEnv("OPENAI_API_KEY", "");
|
||||
isProviderApiKeyConfiguredMock.mockImplementation((params?: { provider?: string }) => {
|
||||
return params?.provider === "openai";
|
||||
});
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(false);
|
||||
ensureAuthProfileStoreMock.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
@@ -449,9 +504,7 @@ describe("openai image generation provider", () => {
|
||||
it("does not report Codex OAuth image auth as configured for non-exact public OpenAI URLs", () => {
|
||||
const provider = buildOpenAIImageGenerationProvider();
|
||||
|
||||
isProviderApiKeyConfiguredMock.mockImplementation((params?: { provider?: string }) => {
|
||||
return params?.provider === "openai-codex";
|
||||
});
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(false);
|
||||
|
||||
expect(
|
||||
provider.isConfigured?.({
|
||||
@@ -917,8 +970,6 @@ describe("openai image generation provider", () => {
|
||||
|
||||
expect(authResolutionCall(0).provider).toBe("openai");
|
||||
expect(authResolutionCall(0).store).toBe(authStore);
|
||||
expect(authResolutionCall(1).provider).toBe("openai-codex");
|
||||
expect(authResolutionCall(1).store).toBe(authStore);
|
||||
const configCall = httpConfigCall();
|
||||
expect(configCall.defaultBaseUrl).toBe("https://chatgpt.com/backend-api/codex");
|
||||
expect(configCall.defaultHeaders).toEqual({
|
||||
@@ -926,7 +977,7 @@ describe("openai image generation provider", () => {
|
||||
Accept: "text/event-stream",
|
||||
});
|
||||
expect(configCall.provider).toBe("openai");
|
||||
expect(configCall.api).toBe("openai-codex-responses");
|
||||
expect(configCall.api).toBe("openai-chatgpt-responses");
|
||||
expect(configCall.capability).toBe("image");
|
||||
const request = jsonRequestCall();
|
||||
const body = request.body as Record<string, unknown>;
|
||||
@@ -1001,12 +1052,9 @@ describe("openai image generation provider", () => {
|
||||
mockGeneratedPngResponse();
|
||||
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
|
||||
if (params?.provider === "openai") {
|
||||
return {};
|
||||
}
|
||||
if (params?.provider === "openai-codex") {
|
||||
return {
|
||||
apiKey: "codex-api-key",
|
||||
source: "profile:openai-codex:manual",
|
||||
source: "profile:openai:manual",
|
||||
mode: "api-key",
|
||||
};
|
||||
}
|
||||
@@ -1022,9 +1070,8 @@ describe("openai image generation provider", () => {
|
||||
authStore: createCodexApiKeyAuthStore(),
|
||||
});
|
||||
|
||||
expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(2);
|
||||
expect(authResolutionCall(0).provider).toBe("openai");
|
||||
expect(authResolutionCall(1).provider).toBe("openai-codex");
|
||||
expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
|
||||
expect(authResolutionCall().provider).toBe("openai");
|
||||
const configCall = httpConfigCall();
|
||||
expect(configCall.defaultBaseUrl).toBe("https://api.openai.com/v1");
|
||||
expect(configCall.defaultHeaders).toEqual({
|
||||
@@ -1042,10 +1089,10 @@ describe("openai image generation provider", () => {
|
||||
it("uses native OpenAI image requests when mixed Codex profiles resolve to an API key", async () => {
|
||||
mockGeneratedPngResponse();
|
||||
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
|
||||
if (params?.provider === "openai-codex") {
|
||||
if (params?.provider === "openai") {
|
||||
return {
|
||||
apiKey: "codex-api-key",
|
||||
source: "profile:openai-codex:manual",
|
||||
source: "profile:openai:manual",
|
||||
mode: "api-key",
|
||||
};
|
||||
}
|
||||
@@ -1062,7 +1109,7 @@ describe("openai image generation provider", () => {
|
||||
});
|
||||
|
||||
expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
|
||||
expect(authResolutionCall().provider).toBe("openai-codex");
|
||||
expect(authResolutionCall().provider).toBe("openai");
|
||||
expect(httpConfigCall().defaultBaseUrl).toBe("https://api.openai.com/v1");
|
||||
expect(httpConfigCall().defaultHeaders).toEqual({
|
||||
Authorization: "Bearer codex-api-key",
|
||||
@@ -1078,12 +1125,9 @@ describe("openai image generation provider", () => {
|
||||
mockCodexImageStream({ imageData: "codex-token-image" });
|
||||
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
|
||||
if (params?.provider === "openai") {
|
||||
return {};
|
||||
}
|
||||
if (params?.provider === "openai-codex") {
|
||||
return {
|
||||
apiKey: "codex-token",
|
||||
source: "profile:openai-codex:token",
|
||||
source: "profile:openai:token",
|
||||
mode: "token",
|
||||
};
|
||||
}
|
||||
@@ -1100,10 +1144,10 @@ describe("openai image generation provider", () => {
|
||||
});
|
||||
|
||||
expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
|
||||
expect(authResolutionCall().provider).toBe("openai-codex");
|
||||
expect(authResolutionCall().provider).toBe("openai");
|
||||
expect(httpConfigCall().defaultBaseUrl).toBe("https://chatgpt.com/backend-api/codex");
|
||||
expect(httpConfigCall().provider).toBe("openai");
|
||||
expect(httpConfigCall().api).toBe("openai-codex-responses");
|
||||
expect(httpConfigCall().api).toBe("openai-chatgpt-responses");
|
||||
expect(jsonRequestCall().url).toBe("https://chatgpt.com/backend-api/codex/responses");
|
||||
expect(postMultipartRequestMock).not.toHaveBeenCalled();
|
||||
expect(result.images[0]?.buffer).toEqual(Buffer.from("codex-token-image"));
|
||||
@@ -1113,12 +1157,9 @@ describe("openai image generation provider", () => {
|
||||
mockCodexImageStream({ imageData: "codex-token-image" });
|
||||
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
|
||||
if (params?.provider === "openai") {
|
||||
return { apiKey: "openai-key", source: "OPENAI_API_KEY", mode: "api-key" };
|
||||
}
|
||||
if (params?.provider === "openai-codex") {
|
||||
return {
|
||||
apiKey: "codex-token",
|
||||
source: "profile:openai-codex:token",
|
||||
source: "profile:openai:token",
|
||||
mode: "token",
|
||||
};
|
||||
}
|
||||
@@ -1135,12 +1176,7 @@ describe("openai image generation provider", () => {
|
||||
});
|
||||
|
||||
expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
|
||||
expect(authResolutionCall().provider).toBe("openai-codex");
|
||||
expect(
|
||||
resolveApiKeyForProviderMock.mock.calls.some(
|
||||
([call]) => (call as AuthResolutionCall).provider === "openai",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(authResolutionCall().provider).toBe("openai");
|
||||
expect(jsonRequestCall().url).toBe("https://chatgpt.com/backend-api/codex/responses");
|
||||
});
|
||||
|
||||
@@ -1200,10 +1236,7 @@ describe("openai image generation provider", () => {
|
||||
it("uses configured Codex OAuth directly instead of probing an available OpenAI API key", async () => {
|
||||
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
|
||||
if (params?.provider === "openai") {
|
||||
return { apiKey: "openai-key", source: "OPENAI_API_KEY", mode: "api-key" };
|
||||
}
|
||||
if (params?.provider === "openai-codex") {
|
||||
return { apiKey: "codex-key", source: "profile:openai-codex:default", mode: "oauth" };
|
||||
return { apiKey: "codex-key", source: "profile:openai:default", mode: "oauth" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
@@ -1220,13 +1253,8 @@ describe("openai image generation provider", () => {
|
||||
});
|
||||
|
||||
expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
|
||||
expect(authResolutionCall().provider).toBe("openai-codex");
|
||||
expect(authResolutionCall().provider).toBe("openai");
|
||||
expect(authResolutionCall().store).toBe(authStore);
|
||||
expect(
|
||||
resolveApiKeyForProviderMock.mock.calls.some(
|
||||
([call]) => (call as AuthResolutionCall).provider === "openai",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(jsonRequestCall().url).toBe("https://chatgpt.com/backend-api/codex/responses");
|
||||
expect(logInfoMock).toHaveBeenCalledWith(
|
||||
"image auth selected: provider=openai mode=oauth transport=codex-responses requestedModel=gpt-image-2 responsesModel=gpt-5.5 timeoutMs=180000",
|
||||
@@ -1337,9 +1365,6 @@ describe("openai image generation provider", () => {
|
||||
if (params?.provider === "openai") {
|
||||
throw new Error("Keychain unavailable");
|
||||
}
|
||||
if (params?.provider === "openai-codex") {
|
||||
return { apiKey: "codex-key", source: "profile:openai-codex:default", mode: "oauth" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
mockCodexImageStream({ imageData: "codex-image" });
|
||||
@@ -1361,10 +1386,10 @@ describe("openai image generation provider", () => {
|
||||
|
||||
it("sanitizes Codex OAuth image auth log values", async () => {
|
||||
resolveApiKeyForProviderMock.mockImplementation(async (params?: { provider?: string }) => {
|
||||
if (params?.provider === "openai-codex") {
|
||||
if (params?.provider === "openai") {
|
||||
return {
|
||||
apiKey: "codex-key",
|
||||
source: "profile:openai-codex:default",
|
||||
source: "profile:openai:default",
|
||||
mode: "oauth\nfake\u202eignored",
|
||||
};
|
||||
}
|
||||
@@ -1432,9 +1457,9 @@ describe("openai image generation provider", () => {
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
openai: {
|
||||
baseUrl: "http://127.0.0.1:44220/backend-api/codex",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
request: { allowPrivateNetwork: true },
|
||||
models: [],
|
||||
},
|
||||
@@ -1473,9 +1498,9 @@ describe("openai image generation provider", () => {
|
||||
cfg: {
|
||||
models: {
|
||||
providers: {
|
||||
"openai-codex": {
|
||||
openai: {
|
||||
baseUrl: configuredBaseUrl,
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
@@ -1486,7 +1511,7 @@ describe("openai image generation provider", () => {
|
||||
|
||||
expect(httpConfigCall().baseUrl).toBe("https://chatgpt.com/backend-api/codex");
|
||||
expect(httpConfigCall().provider).toBe("openai");
|
||||
expect(httpConfigCall().api).toBe("openai-codex-responses");
|
||||
expect(httpConfigCall().api).toBe("openai-chatgpt-responses");
|
||||
expect(httpConfigCall().capability).toBe("image");
|
||||
expect(jsonRequestCall().url).toBe("https://chatgpt.com/backend-api/codex/responses");
|
||||
});
|
||||
@@ -1497,8 +1522,8 @@ describe("openai image generation provider", () => {
|
||||
if (params?.provider === "openai") {
|
||||
return { apiKey: "openai-key", source: "models.json", mode: "api-key" };
|
||||
}
|
||||
if (params?.provider === "openai-codex") {
|
||||
return { apiKey: "codex-key", source: "profile:openai-codex:default", mode: "oauth" };
|
||||
if (params?.provider === "openai") {
|
||||
return { apiKey: "codex-key", source: "profile:openai:default", mode: "oauth" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
@@ -1526,11 +1551,6 @@ describe("openai image generation provider", () => {
|
||||
expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
|
||||
expect(authResolutionCall().provider).toBe("openai");
|
||||
expect(authResolutionCall().store).toBe(authStore);
|
||||
expect(
|
||||
resolveApiKeyForProviderMock.mock.calls.some(
|
||||
([call]) => (call as AuthResolutionCall).provider === "openai-codex",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(jsonRequestCall().url).toBe("https://api.openai.com/v1/images/generations");
|
||||
});
|
||||
|
||||
@@ -1540,8 +1560,8 @@ describe("openai image generation provider", () => {
|
||||
if (params?.provider === "openai") {
|
||||
return { apiKey: "openai-key", source: "models.json", mode: "api-key" };
|
||||
}
|
||||
if (params?.provider === "openai-codex") {
|
||||
return { apiKey: "codex-key", source: "profile:openai-codex:default", mode: "oauth" };
|
||||
if (params?.provider === "openai") {
|
||||
return { apiKey: "codex-key", source: "profile:openai:default", mode: "oauth" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
@@ -1612,7 +1632,7 @@ describe("openai image generation provider", () => {
|
||||
expect(resolveApiKeyForProviderMock).toHaveBeenCalledTimes(1);
|
||||
expect(authResolutionCall().provider).toBe("openai");
|
||||
expect(httpConfigCall().provider).toBe("openai");
|
||||
expect(httpConfigCall().api).toBe("openai-codex-responses");
|
||||
expect(httpConfigCall().api).toBe("openai-chatgpt-responses");
|
||||
expect(jsonRequestCall().url).toBe("https://chatgpt.com/backend-api/codex/responses");
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,11 @@ import {
|
||||
sanitizeConfiguredModelProviderRequest,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { canonicalizeCodexResponsesBaseUrl, OPENAI_CODEX_RESPONSES_BASE_URL } from "./base-url.js";
|
||||
import {
|
||||
canonicalizeCodexResponsesBaseUrl,
|
||||
isOpenAICodexBaseUrl,
|
||||
OPENAI_CODEX_RESPONSES_BASE_URL,
|
||||
} from "./base-url.js";
|
||||
import { OPENAI_DEFAULT_IMAGE_MODEL as DEFAULT_OPENAI_IMAGE_MODEL } from "./default-models.js";
|
||||
import { resolveConfiguredOpenAIBaseUrl } from "./shared.js";
|
||||
|
||||
@@ -293,10 +297,6 @@ function shouldAllowPrivateImageEndpoint(req: {
|
||||
return process.env.OPENCLAW_QA_ALLOW_LOCAL_IMAGE_PROVIDER === "1";
|
||||
}
|
||||
|
||||
function normalizeProviderId(value: string | undefined): string {
|
||||
return value?.trim().toLowerCase() ?? "";
|
||||
}
|
||||
|
||||
function resolveRequestAuthStore(req: {
|
||||
authStore?: AuthProfileStore;
|
||||
agentDir?: string;
|
||||
@@ -333,7 +333,7 @@ function hasDirectOpenAIImageApiKeyAuth(params: {
|
||||
}
|
||||
const profileIds = listProfilesForProvider(store, "openai");
|
||||
if (profileIds.length === 0) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return profileIds.some((profileId) => store.profiles[profileId]?.type === "api_key");
|
||||
}
|
||||
@@ -346,11 +346,9 @@ function hasCodexResponseTransportProfileConfigured(req: {
|
||||
if (!store) {
|
||||
return false;
|
||||
}
|
||||
return ["openai", "openai-codex"].some((provider) =>
|
||||
listProfilesForProvider(store, provider).some(
|
||||
(profileId) =>
|
||||
store.profiles[profileId]?.type === "oauth" || store.profiles[profileId]?.type === "token",
|
||||
),
|
||||
return listProfilesForProvider(store, "openai").some(
|
||||
(profileId) =>
|
||||
store.profiles[profileId]?.type === "oauth" || store.profiles[profileId]?.type === "token",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -367,16 +365,7 @@ function resolveOpenAIImageAuthProvider(req: {
|
||||
if (!store) {
|
||||
return "openai";
|
||||
}
|
||||
const profiles = Object.values(store.profiles);
|
||||
const hasCanonicalProfiles = profiles.some(
|
||||
(profile) => normalizeProviderId(profile.provider) === "openai",
|
||||
);
|
||||
const hasLegacySubscriptionProfile = profiles.some(
|
||||
(profile) =>
|
||||
normalizeProviderId(profile.provider) === "openai-codex" &&
|
||||
(profile.type === "oauth" || profile.type === "token"),
|
||||
);
|
||||
return !hasCanonicalProfiles && hasLegacySubscriptionProfile ? "openai-codex" : "openai";
|
||||
return "openai";
|
||||
}
|
||||
|
||||
function hasExplicitOpenAIImageApiKeyConfig(cfg: OpenClawConfig | undefined): boolean {
|
||||
@@ -394,7 +383,15 @@ function hasExplicitDirectOpenAIImageConfig(cfg: OpenClawConfig | undefined): bo
|
||||
providerConfig.headers !== undefined ||
|
||||
providerConfig.authHeader !== undefined ||
|
||||
providerConfig.request !== undefined ||
|
||||
(providerConfig.api !== undefined && providerConfig.api !== "openai-codex-responses")
|
||||
(providerConfig.api !== undefined && providerConfig.api !== "openai-chatgpt-responses")
|
||||
);
|
||||
}
|
||||
|
||||
function hasChatGPTImageRouteConfig(cfg: OpenClawConfig | undefined): boolean {
|
||||
const providerConfig = cfg?.models?.providers?.openai;
|
||||
return (
|
||||
isOpenAICodexBaseUrl(resolveConfiguredOpenAIBaseUrl(cfg)) ||
|
||||
providerConfig?.api === "openai-chatgpt-responses"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,18 +438,8 @@ async function resolveOpenAIImageAuth(req: {
|
||||
agentDir?: string;
|
||||
authStore?: AuthProfileStore;
|
||||
}) {
|
||||
const provider = resolveOpenAIImageAuthProvider(req);
|
||||
const primary = await resolveOptionalApiKeyForProvider({
|
||||
provider,
|
||||
cfg: req.cfg,
|
||||
agentDir: req.agentDir,
|
||||
store: req.authStore,
|
||||
});
|
||||
if (primary?.apiKey || provider === "openai-codex") {
|
||||
return primary;
|
||||
}
|
||||
return await resolveOptionalApiKeyForProvider({
|
||||
provider: "openai-codex",
|
||||
provider: resolveOpenAIImageAuthProvider(req),
|
||||
cfg: req.cfg,
|
||||
agentDir: req.agentDir,
|
||||
store: req.authStore,
|
||||
@@ -717,11 +704,8 @@ async function generateOpenAICodexImage(params: {
|
||||
const { req, apiKey } = params;
|
||||
const inputImages = req.inputImages ?? [];
|
||||
const openAIProviderConfig = req.cfg?.models?.providers?.openai;
|
||||
const legacyCodexProviderConfig = req.cfg?.models?.providers?.["openai-codex"];
|
||||
const codexProviderConfig =
|
||||
openAIProviderConfig?.api === "openai-codex-responses"
|
||||
? openAIProviderConfig
|
||||
: legacyCodexProviderConfig;
|
||||
openAIProviderConfig?.api === "openai-chatgpt-responses" ? openAIProviderConfig : undefined;
|
||||
const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } =
|
||||
resolveProviderHttpRequestConfig({
|
||||
baseUrl: canonicalizeCodexResponsesBaseUrl(codexProviderConfig?.baseUrl),
|
||||
@@ -732,7 +716,7 @@ async function generateOpenAICodexImage(params: {
|
||||
},
|
||||
request: sanitizeConfiguredModelProviderRequest(codexProviderConfig?.request),
|
||||
provider: "openai",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
capability: "image",
|
||||
transport: "http",
|
||||
});
|
||||
@@ -830,6 +814,9 @@ export function buildOpenAIImageGenerationProvider(): ImageGenerationProvider {
|
||||
id: "openai",
|
||||
label: "OpenAI",
|
||||
isConfigured: ({ cfg, agentDir }) => {
|
||||
const configuredBaseUrl = resolveConfiguredOpenAIBaseUrl(cfg);
|
||||
const hasPublicOpenAIBaseUrl = isPublicOpenAIImageBaseUrl(configuredBaseUrl);
|
||||
const hasChatGPTRouteConfig = hasChatGPTImageRouteConfig(cfg);
|
||||
if (
|
||||
isProviderApiKeyConfigured({
|
||||
provider: "openai",
|
||||
@@ -837,32 +824,29 @@ export function buildOpenAIImageGenerationProvider(): ImageGenerationProvider {
|
||||
})
|
||||
) {
|
||||
return (
|
||||
isPublicOpenAIImageBaseUrl(resolveConfiguredOpenAIBaseUrl(cfg)) ||
|
||||
hasDirectOpenAIImageApiKeyAuth({ cfg, agentDir })
|
||||
hasPublicOpenAIBaseUrl ||
|
||||
hasDirectOpenAIImageApiKeyAuth({ cfg, agentDir }) ||
|
||||
(hasChatGPTRouteConfig && hasCodexResponseTransportProfileConfigured({ agentDir }))
|
||||
);
|
||||
}
|
||||
if (
|
||||
isProviderApiKeyConfigured({
|
||||
provider: "openai-codex",
|
||||
agentDir,
|
||||
})
|
||||
) {
|
||||
return isPublicOpenAIImageBaseUrl(resolveConfiguredOpenAIBaseUrl(cfg));
|
||||
}
|
||||
if (!isPublicOpenAIImageBaseUrl(resolveConfiguredOpenAIBaseUrl(cfg))) {
|
||||
if (!hasPublicOpenAIBaseUrl && !hasChatGPTRouteConfig) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
return hasCodexResponseTransportProfileConfigured({ agentDir });
|
||||
},
|
||||
async generateImage(req) {
|
||||
const inputImages = req.inputImages ?? [];
|
||||
const isEdit = inputImages.length > 0;
|
||||
const rawBaseUrl = resolveConfiguredOpenAIBaseUrl(req.cfg);
|
||||
const publicOpenAIBaseUrl = isPublicOpenAIImageBaseUrl(rawBaseUrl);
|
||||
const chatGPTBaseUrl = isOpenAICodexBaseUrl(rawBaseUrl);
|
||||
const codexResponsesConfigured =
|
||||
req.cfg?.models?.providers?.openai?.api === "openai-chatgpt-responses";
|
||||
const explicitOpenAIApiKeyConfig = hasExplicitOpenAIImageApiKeyConfig(req.cfg);
|
||||
const explicitDirectOpenAIConfig = hasExplicitDirectOpenAIImageConfig(req.cfg);
|
||||
const explicitDirectOpenAIConfig =
|
||||
!chatGPTBaseUrl && !codexResponsesConfigured && hasExplicitDirectOpenAIImageConfig(req.cfg);
|
||||
const useCodexResponseTransportRoute =
|
||||
publicOpenAIBaseUrl &&
|
||||
(publicOpenAIBaseUrl || chatGPTBaseUrl || codexResponsesConfigured) &&
|
||||
!explicitDirectOpenAIConfig &&
|
||||
hasCodexResponseTransportProfileConfigured(req);
|
||||
let preResolvedImageAuth:
|
||||
@@ -920,20 +904,6 @@ export function buildOpenAIImageGenerationProvider(): ImageGenerationProvider {
|
||||
}
|
||||
imageAuth = undefined;
|
||||
}
|
||||
if (!imageAuth?.apiKey && publicOpenAIBaseUrl && !explicitDirectOpenAIConfig) {
|
||||
const legacyCodexAuth = await resolveOptionalApiKeyForProvider({
|
||||
provider: "openai-codex",
|
||||
cfg: req.cfg,
|
||||
agentDir: req.agentDir,
|
||||
store: req.authStore,
|
||||
});
|
||||
if (legacyCodexAuth?.apiKey && isCodexSubscriptionAuthMode(legacyCodexAuth.mode)) {
|
||||
const timeoutMs = resolveOpenAIImageTimeoutMs(req.timeoutMs);
|
||||
logCodexImageAuthSelected({ req, authMode: legacyCodexAuth.mode, timeoutMs });
|
||||
return generateOpenAICodexImage({ req, apiKey: legacyCodexAuth.apiKey });
|
||||
}
|
||||
imageAuth = legacyCodexAuth;
|
||||
}
|
||||
if (!imageAuth?.apiKey) {
|
||||
if (!publicOpenAIBaseUrl) {
|
||||
throw new Error("OpenAI API key missing");
|
||||
|
||||
@@ -32,11 +32,11 @@ vi.mock("openclaw/plugin-sdk/runtime-env", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./openai-codex-oauth-flow.runtime.js", () => ({
|
||||
vi.mock("./openai-chatgpt-oauth-flow.runtime.js", () => ({
|
||||
refreshOpenAICodexToken: runtimeMocks.refreshOpenAICodexToken,
|
||||
}));
|
||||
|
||||
import { createOpenAICodexProviderRuntime } from "./openai-codex-provider.runtime.js";
|
||||
import { createOpenAICodexProviderRuntime } from "./openai-chatgpt-provider.runtime.js";
|
||||
|
||||
const registerOpenAIPluginForTest = async () =>
|
||||
registerProviderPlugin({
|
||||
@@ -354,10 +354,10 @@ describe("openai plugin", () => {
|
||||
const normalizedCodex = openaiProvider.normalizeToolSchemas?.({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
modelApi: "openai-codex-responses",
|
||||
modelApi: "openai-chatgpt-responses",
|
||||
model: {
|
||||
provider: "openai",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
id: "gpt-5.4",
|
||||
} as never,
|
||||
@@ -394,10 +394,10 @@ describe("openai plugin", () => {
|
||||
openaiProvider.inspectToolSchemas?.({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
modelApi: "openai-codex-responses",
|
||||
modelApi: "openai-chatgpt-responses",
|
||||
model: {
|
||||
provider: "openai",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
id: "gpt-5.4",
|
||||
} as never,
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveCodexAuthIdentity } from "./openai-codex-auth-identity.js";
|
||||
import { resolveCodexAuthIdentity } from "./openai-chatgpt-auth-identity.js";
|
||||
|
||||
function createJwt(payload: Record<string, unknown>): string {
|
||||
const header = Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" })).toString("base64url");
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { trimNonEmptyString } from "./openai-codex-shared.js";
|
||||
import { trimNonEmptyString } from "./openai-chatgpt-shared.js";
|
||||
|
||||
type CodexJwtPayload = {
|
||||
exp?: unknown;
|
||||
+1
-1
@@ -6,7 +6,7 @@ const OPENAI_CODEX_BASE_URL = OPENAI_CODEX_RESPONSES_BASE_URL;
|
||||
export function buildOpenAICodexProvider(): ModelProviderConfig {
|
||||
return {
|
||||
baseUrl: OPENAI_CODEX_BASE_URL,
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
models: [],
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { resolveCodexAccessTokenExpiry } from "./openai-codex-auth-identity.js";
|
||||
import { loginOpenAICodexDeviceCode } from "./openai-codex-device-code.js";
|
||||
import { resolveCodexAccessTokenExpiry } from "./openai-chatgpt-auth-identity.js";
|
||||
import { loginOpenAICodexDeviceCode } from "./openai-chatgpt-device-code.js";
|
||||
|
||||
function createJwt(payload: Record<string, unknown>): string {
|
||||
const header = Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" })).toString("base64url");
|
||||
+2
-2
@@ -2,8 +2,8 @@ import {
|
||||
positiveSecondsToSafeMilliseconds,
|
||||
resolveExpiresAtMsFromDurationSeconds,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { resolveCodexAccessTokenExpiry } from "./openai-codex-auth-identity.js";
|
||||
import { trimNonEmptyString } from "./openai-codex-shared.js";
|
||||
import { resolveCodexAccessTokenExpiry } from "./openai-chatgpt-auth-identity.js";
|
||||
import { trimNonEmptyString } from "./openai-chatgpt-shared.js";
|
||||
|
||||
const OPENAI_AUTH_BASE_URL = "https://auth.openai.com";
|
||||
const OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
||||
+3
-3
@@ -8,7 +8,7 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
fetchWithSsrFGuard: ssrfMocks.fetchWithSsrFGuard,
|
||||
}));
|
||||
|
||||
import { openaiCodexOAuthProvider, testing } from "./openai-codex-oauth-flow.runtime.js";
|
||||
import { openaiCodexOAuthProvider, testing } from "./openai-chatgpt-oauth-flow.runtime.js";
|
||||
|
||||
function timeoutError(): Error {
|
||||
return new DOMException("timed out", "TimeoutError");
|
||||
@@ -96,7 +96,7 @@ describe("OpenAI Codex OAuth flow", () => {
|
||||
|
||||
expect(ssrfMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
auditContext: "openai-codex-oauth-token",
|
||||
auditContext: "openai-chatgpt-oauth-token",
|
||||
timeoutMs: 5,
|
||||
}),
|
||||
);
|
||||
@@ -149,7 +149,7 @@ describe("OpenAI Codex OAuth flow", () => {
|
||||
|
||||
expect(ssrfMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
auditContext: "openai-codex-oauth-token",
|
||||
auditContext: "openai-chatgpt-oauth-token",
|
||||
timeoutMs: 5,
|
||||
}),
|
||||
);
|
||||
+7
-7
@@ -11,20 +11,20 @@ import {
|
||||
resolveOAuthTokenLifetimeMs,
|
||||
} from "openclaw/plugin-sdk/provider-oauth-runtime";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { resolveCodexAuthIdentity } from "./openai-codex-auth-identity.js";
|
||||
import { resolveCodexAuthIdentity } from "./openai-chatgpt-auth-identity.js";
|
||||
import {
|
||||
createOAuthLoginCancelledError,
|
||||
throwIfOAuthLoginAborted,
|
||||
withOAuthLoginAbort,
|
||||
} from "./openai-codex-oauth-abort.runtime.js";
|
||||
import { oauthErrorHtml, oauthSuccessHtml } from "./openai-codex-oauth-page.runtime.js";
|
||||
} from "./openai-chatgpt-oauth-abort.runtime.js";
|
||||
import { oauthErrorHtml, oauthSuccessHtml } from "./openai-chatgpt-oauth-page.runtime.js";
|
||||
import type {
|
||||
OAuthCredentials,
|
||||
OAuthLoginCallbacks,
|
||||
OAuthPrompt,
|
||||
OAuthProviderInterface,
|
||||
} from "./openai-codex-oauth-types.runtime.js";
|
||||
import { generatePKCE } from "./openai-codex-pkce.runtime.js";
|
||||
} from "./openai-chatgpt-oauth-types.runtime.js";
|
||||
import { generatePKCE } from "./openai-chatgpt-pkce.runtime.js";
|
||||
|
||||
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
||||
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
||||
@@ -175,7 +175,7 @@ async function postTokenForm(
|
||||
},
|
||||
timeoutMs,
|
||||
signal: options.signal,
|
||||
auditContext: "openai-codex-oauth-token",
|
||||
auditContext: "openai-chatgpt-oauth-token",
|
||||
});
|
||||
try {
|
||||
const responseBody = await response.arrayBuffer();
|
||||
@@ -573,7 +573,7 @@ export async function refreshOpenAICodexToken(refreshToken: string): Promise<OAu
|
||||
}
|
||||
|
||||
export const openaiCodexOAuthProvider: OAuthProviderInterface = {
|
||||
id: "openai-codex",
|
||||
id: "openai",
|
||||
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
||||
usesCallbackServer: true,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { testing } from "./openai-codex-oauth.runtime.js";
|
||||
import { testing } from "./openai-chatgpt-oauth.runtime.js";
|
||||
|
||||
describe("OpenAI Codex OAuth runtime", () => {
|
||||
afterEach(() => {
|
||||
+2
-2
@@ -4,8 +4,8 @@ import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import type { ProviderAuthContext } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { ensureGlobalUndiciEnvProxyDispatcher } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { formatCliCommand } from "openclaw/plugin-sdk/setup-tools";
|
||||
import { loginOpenAICodex } from "./openai-codex-oauth-flow.runtime.js";
|
||||
import type { OAuthCredentials } from "./openai-codex-oauth-types.runtime.js";
|
||||
import { loginOpenAICodex } from "./openai-chatgpt-oauth-flow.runtime.js";
|
||||
import type { OAuthCredentials } from "./openai-chatgpt-oauth-types.runtime.js";
|
||||
|
||||
const manualInputPromptMessage = "Paste the authorization code (or full redirect URL):";
|
||||
const openAICodexOAuthOriginator = "openclaw";
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
import { ensureGlobalUndiciEnvProxyDispatcher } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { refreshOpenAICodexToken as refreshOpenAICodexTokenFromFlow } from "./openai-codex-oauth-flow.runtime.js";
|
||||
import type { OAuthCredentials } from "./openai-codex-oauth-types.runtime.js";
|
||||
import { refreshOpenAICodexToken as refreshOpenAICodexTokenFromFlow } from "./openai-chatgpt-oauth-flow.runtime.js";
|
||||
import type { OAuthCredentials } from "./openai-chatgpt-oauth-types.runtime.js";
|
||||
|
||||
type OpenAICodexProviderRuntimeDeps = {
|
||||
ensureGlobalUndiciEnvProxyDispatcher: typeof ensureGlobalUndiciEnvProxyDispatcher;
|
||||
@@ -46,10 +46,10 @@ async function getOpenAICodexOAuthApiKey(
|
||||
providerId: string,
|
||||
credentials: Record<string, OAuthCredentials>,
|
||||
): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {
|
||||
if (providerId !== "openai" && providerId !== "openai-codex") {
|
||||
if (providerId !== "openai") {
|
||||
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
||||
}
|
||||
let creds = credentials[providerId] ?? credentials["openai-codex"];
|
||||
let creds = credentials[providerId];
|
||||
if (!creds) {
|
||||
return null;
|
||||
}
|
||||
+10
-19
@@ -3,11 +3,11 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
const refreshOpenAICodexTokenMock = vi.hoisted(() => vi.fn());
|
||||
const loginOpenAICodexDeviceCodeMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./openai-codex-provider.runtime.js", () => ({
|
||||
vi.mock("./openai-chatgpt-provider.runtime.js", () => ({
|
||||
refreshOpenAICodexToken: refreshOpenAICodexTokenMock,
|
||||
}));
|
||||
|
||||
vi.mock("./openai-codex-device-code.js", () => ({
|
||||
vi.mock("./openai-chatgpt-device-code.js", () => ({
|
||||
loginOpenAICodexDeviceCode: loginOpenAICodexDeviceCodeMock,
|
||||
}));
|
||||
|
||||
@@ -28,23 +28,14 @@ describe("OpenAI provider Codex transport hooks", () => {
|
||||
|
||||
expect(provider.id).toBe("openai");
|
||||
expect(provider.aliases).toBeUndefined();
|
||||
expect(provider.hookAliases).toEqual([
|
||||
"openai-codex",
|
||||
"azure-openai",
|
||||
"azure-openai-responses",
|
||||
]);
|
||||
expect(provider.hookAliases).toEqual(["azure-openai", "azure-openai-responses"]);
|
||||
expect(provider.auth?.map((method) => method.id)).toEqual(["oauth", "device-code", "api-key"]);
|
||||
expect(provider.auth?.map((method) => method.wizard?.choiceId)).toEqual([
|
||||
"openai",
|
||||
"openai-device-code",
|
||||
"openai-api-key",
|
||||
]);
|
||||
expect(provider.oauthProfileIdRepairs).toEqual([
|
||||
{
|
||||
legacyProfileId: "openai-codex:default",
|
||||
promptLabel: "OpenAI",
|
||||
},
|
||||
]);
|
||||
expect(provider.oauthProfileIdRepairs).toBeUndefined();
|
||||
});
|
||||
|
||||
it("stores device-code logins as OpenAI OAuth profiles", async () => {
|
||||
@@ -85,14 +76,14 @@ describe("OpenAI provider Codex transport hooks", () => {
|
||||
const model = provider.resolveDynamicModel?.({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
providerConfig: { api: "openai-codex-responses" },
|
||||
providerConfig: { api: "openai-chatgpt-responses" },
|
||||
modelRegistry: { find: () => null },
|
||||
} as never);
|
||||
|
||||
expect(model).toMatchObject({
|
||||
provider: "openai",
|
||||
id: "gpt-5.4",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
});
|
||||
});
|
||||
@@ -103,7 +94,7 @@ describe("OpenAI provider Codex transport hooks", () => {
|
||||
const model = provider.resolveDynamicModel?.({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
providerConfig: { api: "openai-codex-responses" },
|
||||
providerConfig: { api: "openai-chatgpt-responses" },
|
||||
modelRegistry: {
|
||||
find: () => ({
|
||||
provider: "openai",
|
||||
@@ -123,7 +114,7 @@ describe("OpenAI provider Codex transport hooks", () => {
|
||||
expect(model).toMatchObject({
|
||||
provider: "openai",
|
||||
id: "gpt-5.5",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
});
|
||||
});
|
||||
@@ -134,7 +125,7 @@ describe("OpenAI provider Codex transport hooks", () => {
|
||||
const model = provider.resolveDynamicModel?.({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
providerConfig: { api: "openai-codex-responses" },
|
||||
providerConfig: { api: "openai-chatgpt-responses" },
|
||||
modelRegistry: {
|
||||
find: () => ({
|
||||
provider: "openai",
|
||||
@@ -154,7 +145,7 @@ describe("OpenAI provider Codex transport hooks", () => {
|
||||
expect(model).toMatchObject({
|
||||
provider: "openai",
|
||||
id: "gpt-5.4",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
});
|
||||
});
|
||||
+13
-12
@@ -33,9 +33,9 @@ import {
|
||||
OPENAI_CODEX_RESPONSES_BASE_URL,
|
||||
} from "./base-url.js";
|
||||
import { OPENAI_CODEX_DEFAULT_MODEL } from "./default-models.js";
|
||||
import { resolveCodexAuthIdentity } from "./openai-codex-auth-identity.js";
|
||||
import { loginOpenAICodexDeviceCode } from "./openai-codex-device-code.js";
|
||||
import { loginOpenAICodexOAuth } from "./openai-codex-oauth.runtime.js";
|
||||
import { resolveCodexAuthIdentity } from "./openai-chatgpt-auth-identity.js";
|
||||
import { loginOpenAICodexDeviceCode } from "./openai-chatgpt-device-code.js";
|
||||
import { loginOpenAICodexOAuth } from "./openai-chatgpt-oauth.runtime.js";
|
||||
import {
|
||||
buildOpenAIResponsesProviderHooks,
|
||||
buildOpenAISyntheticCatalogEntry,
|
||||
@@ -46,7 +46,6 @@ import {
|
||||
import { resolveOpenAICodexThinkingProfile } from "./thinking-policy.js";
|
||||
|
||||
const PROVIDER_ID = "openai";
|
||||
const LEGACY_OPENAI_CODEX_PROVIDER_ID = "openai-codex";
|
||||
const OPENAI_CODEX_BASE_URL = OPENAI_CODEX_RESPONSES_BASE_URL;
|
||||
const OPENAI_CODEX_LOGIN_ASSISTANT_PRIORITY = -30;
|
||||
const OPENAI_CODEX_DEVICE_PAIRING_ASSISTANT_PRIORITY = -10;
|
||||
@@ -109,7 +108,7 @@ const OPENAI_CODEX_MODERN_MODEL_IDS = [
|
||||
|
||||
function isOpenAIOrLegacyCodexProvider(provider: string | undefined): boolean {
|
||||
const normalized = normalizeProviderId(provider ?? "");
|
||||
return normalized === PROVIDER_ID || normalized === LEGACY_OPENAI_CODEX_PROVIDER_ID;
|
||||
return normalized === PROVIDER_ID;
|
||||
}
|
||||
|
||||
function isLegacyCodexCompatBaseUrl(baseUrl?: string): boolean {
|
||||
@@ -132,10 +131,12 @@ function normalizeCodexTransportFields(params: {
|
||||
const api =
|
||||
useCodexTransport &&
|
||||
(!params.api || params.api === "openai-responses" || params.api === "openai-completions")
|
||||
? "openai-codex-responses"
|
||||
? "openai-chatgpt-responses"
|
||||
: (params.api ?? undefined);
|
||||
const baseUrl =
|
||||
api === "openai-codex-responses" && useCodexTransport ? OPENAI_CODEX_BASE_URL : params.baseUrl;
|
||||
api === "openai-chatgpt-responses" && useCodexTransport
|
||||
? OPENAI_CODEX_BASE_URL
|
||||
: params.baseUrl;
|
||||
return { api, baseUrl };
|
||||
}
|
||||
|
||||
@@ -225,7 +226,7 @@ function resolveCodexForwardCompatModel(ctx: ProviderResolveDynamicModelContext)
|
||||
normalizeModelCompat({
|
||||
id: trimmedModelId,
|
||||
name: trimmedModelId,
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
provider: PROVIDER_ID,
|
||||
baseUrl: synthBaseUrl,
|
||||
reasoning: true,
|
||||
@@ -280,7 +281,7 @@ function resolveCodexForwardCompatModel(ctx: ProviderResolveDynamicModelContext)
|
||||
}
|
||||
patch = {
|
||||
...patch,
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: synthBaseUrl,
|
||||
};
|
||||
|
||||
@@ -304,7 +305,7 @@ function resolveCodexForwardCompatModel(ctx: ProviderResolveDynamicModelContext)
|
||||
lower === OPENAI_CODEX_GPT_54_LEGACY_MODEL_ID
|
||||
? OPENAI_CODEX_GPT_54_MODEL_ID
|
||||
: trimmedModelId,
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
provider: PROVIDER_ID,
|
||||
baseUrl: synthBaseUrl,
|
||||
reasoning: true,
|
||||
@@ -351,7 +352,7 @@ function withCodexTransport(
|
||||
}
|
||||
return normalizeModelCompat({
|
||||
...model,
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl,
|
||||
} as ProviderRuntimeModel);
|
||||
}
|
||||
@@ -381,7 +382,7 @@ function buildOpenAICodexAuthConfigPatch(): NonNullable<ProviderAuthResult["conf
|
||||
|
||||
async function refreshOpenAICodexOAuthCredential(cred: OAuthCredential) {
|
||||
try {
|
||||
const { refreshOpenAICodexToken } = await import("./openai-codex-provider.runtime.js");
|
||||
const { refreshOpenAICodexToken } = await import("./openai-chatgpt-provider.runtime.js");
|
||||
const refreshed = await refreshOpenAICodexToken(cred.refresh);
|
||||
const identity = resolveCodexAuthIdentity({
|
||||
accessToken: refreshed.access,
|
||||
@@ -8,7 +8,7 @@ const mocks = vi.hoisted(() => ({
|
||||
openAIResponsesTransportStreamFn: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./openai-codex-provider.runtime.js", () => ({
|
||||
vi.mock("./openai-chatgpt-provider.runtime.js", () => ({
|
||||
refreshOpenAICodexToken: mocks.refreshOpenAICodexToken,
|
||||
}));
|
||||
|
||||
@@ -59,7 +59,7 @@ function runWrappedPayloadCase(params: {
|
||||
modelId: string;
|
||||
model:
|
||||
| Model<"openai-responses">
|
||||
| Model<"openai-codex-responses">
|
||||
| Model<"openai-chatgpt-responses">
|
||||
| Model<"azure-openai-responses">;
|
||||
extraParams?: Record<string, unknown>;
|
||||
cfg?: Record<string, unknown>;
|
||||
@@ -126,7 +126,7 @@ describe("buildOpenAIProvider", () => {
|
||||
const provider = buildOpenAIProvider();
|
||||
const apiKey = provider.auth.find((method) => method.id === "api-key");
|
||||
|
||||
expect(provider.hookAliases).toContain("openai-codex");
|
||||
expect(provider.hookAliases).toEqual(["azure-openai", "azure-openai-responses"]);
|
||||
expect(provider.catalog).toBeUndefined();
|
||||
expectFields(apiKey?.wizard, {
|
||||
choiceLabel: "OpenAI API Key",
|
||||
@@ -142,7 +142,7 @@ describe("buildOpenAIProvider", () => {
|
||||
const provider = buildOpenAICodexProviderPlugin();
|
||||
|
||||
expect(provider.id).toBe("openai");
|
||||
expect(provider.hookAliases).toContain("openai-codex");
|
||||
expect(provider.hookAliases).toEqual(["azure-openai", "azure-openai-responses"]);
|
||||
});
|
||||
|
||||
it("prefers auth-aware Codex runtime metadata over static OpenAI catalog rows", () => {
|
||||
@@ -161,20 +161,20 @@ describe("buildOpenAIProvider", () => {
|
||||
|
||||
expect(
|
||||
provider.normalizeTransport?.({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
api: "openai-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
} as never),
|
||||
).toEqual({
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
});
|
||||
expect(
|
||||
provider.normalizeResolvedModel?.({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
model: {
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
id: "gpt-5.4-codex",
|
||||
name: "gpt-5.4-codex",
|
||||
api: "openai-responses",
|
||||
@@ -184,7 +184,7 @@ describe("buildOpenAIProvider", () => {
|
||||
).toMatchObject({
|
||||
id: "gpt-5.4",
|
||||
name: "gpt-5.4",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
input: ["text", "image"],
|
||||
});
|
||||
@@ -371,7 +371,7 @@ describe("buildOpenAIProvider", () => {
|
||||
expectFields(codexModel, {
|
||||
provider: "openai",
|
||||
id: "gpt-5.4",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
contextWindow: 1_050_000,
|
||||
maxTokens: 128_000,
|
||||
@@ -379,7 +379,7 @@ describe("buildOpenAIProvider", () => {
|
||||
expectFields(selectedOauthModel, {
|
||||
provider: "openai",
|
||||
id: "gpt-5.4",
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
contextWindow: 1_050_000,
|
||||
maxTokens: 128_000,
|
||||
@@ -516,13 +516,13 @@ describe("buildOpenAIProvider", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps legacy OpenAI Codex refs on the Codex thinking policy", () => {
|
||||
it("keeps Codex-family OpenAI models on the Codex thinking policy", () => {
|
||||
const provider = buildOpenAIProvider();
|
||||
|
||||
expect(
|
||||
provider
|
||||
.resolveThinkingProfile?.({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.3-codex-spark",
|
||||
} as never)
|
||||
?.levels.map((level) => level.id),
|
||||
@@ -531,7 +531,7 @@ describe("buildOpenAIProvider", () => {
|
||||
provider
|
||||
.resolveThinkingProfile?.({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.3-codex-spark",
|
||||
modelId: "gpt-5.3",
|
||||
} as never)
|
||||
?.levels.map((level) => level.id),
|
||||
).not.toContain("xhigh");
|
||||
@@ -661,7 +661,7 @@ describe("buildOpenAIProvider", () => {
|
||||
expect(
|
||||
codexProvider.buildReplayPolicy?.({
|
||||
provider: "openai",
|
||||
modelApi: "openai-codex-responses",
|
||||
modelApi: "openai-chatgpt-responses",
|
||||
modelId: "gpt-5.4",
|
||||
} as never),
|
||||
).toEqual({
|
||||
@@ -921,11 +921,11 @@ describe("buildOpenAIProvider", () => {
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
model: {
|
||||
api: "openai-codex-responses",
|
||||
api: "openai-chatgpt-responses",
|
||||
provider: "openai",
|
||||
id: "gpt-5.4",
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex/responses",
|
||||
} as Model<"openai-codex-responses">,
|
||||
} as Model<"openai-chatgpt-responses">,
|
||||
extraParams: { effort: "high" },
|
||||
}),
|
||||
).toEqual({
|
||||
@@ -983,81 +983,6 @@ describe("buildOpenAIProvider", () => {
|
||||
expect(result.payload.reasoning).toEqual({ effort: "none" });
|
||||
});
|
||||
|
||||
it("owns Codex wrapper composition for responses payloads", () => {
|
||||
const provider = buildOpenAIProvider();
|
||||
const wrap = provider.wrapStreamFn;
|
||||
expect(wrap).toBeTypeOf("function");
|
||||
if (!wrap) {
|
||||
throw new Error("expected Codex wrapper");
|
||||
}
|
||||
const payload = {
|
||||
store: false,
|
||||
text: { verbosity: "medium" },
|
||||
tools: [{ type: "function", name: "read" }],
|
||||
};
|
||||
mocks.openAIResponsesTransportStreamFn.mockImplementation((model, _context, options) => {
|
||||
options?.onPayload?.(payload, model);
|
||||
return {} as ReturnType<StreamFn>;
|
||||
});
|
||||
const result = runWrappedPayloadCase({
|
||||
wrap,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
extraParams: {
|
||||
fastMode: true,
|
||||
serviceTier: "priority",
|
||||
text_verbosity: "high",
|
||||
},
|
||||
cfg: {
|
||||
auth: {
|
||||
profiles: {
|
||||
"openai:default": {
|
||||
provider: "openai",
|
||||
mode: "oauth",
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
enabled: true,
|
||||
openaiCodex: {
|
||||
enabled: true,
|
||||
mode: "live",
|
||||
allowedDomains: ["example.com"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
model: {
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai",
|
||||
id: "gpt-5.4",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
} as Model<"openai-codex-responses">,
|
||||
payload,
|
||||
});
|
||||
|
||||
expect(mocks.openAIResponsesTransportStreamFn).not.toHaveBeenCalled();
|
||||
const headers = result.options?.headers as Record<string, unknown> | undefined;
|
||||
expectFields(result.options?.headers, {
|
||||
originator: "openclaw",
|
||||
});
|
||||
expect(typeof headers?.["User-Agent"]).toBe("string");
|
||||
expect(String(headers?.["User-Agent"]).startsWith("openclaw/")).toBe(true);
|
||||
expect(result.payload.store).toBe(false);
|
||||
expect(result.payload.service_tier).toBe("priority");
|
||||
expect(result.payload.text).toEqual({ verbosity: "high" });
|
||||
expect(result.payload.tools).toEqual([
|
||||
{ type: "function", name: "read" },
|
||||
{
|
||||
type: "web_search",
|
||||
external_web_access: true,
|
||||
filters: { allowed_domains: ["example.com"] },
|
||||
},
|
||||
]);
|
||||
});
|
||||
it("falls back to cached codex oauth credentials on accountId extraction failures", async () => {
|
||||
const provider = buildOpenAIProvider();
|
||||
const credential = {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { applyOpenAIConfig, OPENAI_DEFAULT_MODEL } from "./default-models.js";
|
||||
import {
|
||||
buildOpenAIChatGPTAuthMethods,
|
||||
buildOpenAICodexProviderHooks,
|
||||
} from "./openai-codex-provider.js";
|
||||
} from "./openai-chatgpt-provider.js";
|
||||
import {
|
||||
buildOpenAIResponsesProviderHooks,
|
||||
buildOpenAISyntheticCatalogEntry,
|
||||
@@ -24,13 +24,9 @@ import {
|
||||
findCatalogTemplate,
|
||||
matchesExactOrPrefix,
|
||||
} from "./shared.js";
|
||||
import {
|
||||
resolveOpenAICodexThinkingProfile,
|
||||
resolveOpenAIThinkingProfile,
|
||||
} from "./thinking-policy.js";
|
||||
import { resolveUnifiedOpenAIThinkingProfile } from "./thinking-policy.js";
|
||||
|
||||
const PROVIDER_ID = "openai";
|
||||
const LEGACY_OPENAI_CODEX_PROVIDER_ID = "openai-codex";
|
||||
const OPENAI_CHAT_LATEST_MODEL_ID = "chat-latest";
|
||||
const OPENAI_GPT_55_MODEL_ID = "gpt-5.5";
|
||||
const OPENAI_GPT_55_PRO_MODEL_ID = "gpt-5.5-pro";
|
||||
@@ -103,9 +99,9 @@ function shouldUseOpenAIResponsesTransport(params: {
|
||||
return typeof params.baseUrl === "string" && isOpenAIApiBaseUrl(params.baseUrl);
|
||||
}
|
||||
|
||||
function isOpenAIOrLegacyCodexProvider(provider: string | undefined): boolean {
|
||||
function isOpenAIProvider(provider: string | undefined): boolean {
|
||||
const normalized = normalizeProviderId(provider ?? "");
|
||||
return normalized === PROVIDER_ID || normalized === LEGACY_OPENAI_CODEX_PROVIDER_ID;
|
||||
return normalized === PROVIDER_ID;
|
||||
}
|
||||
|
||||
function normalizeOpenAITransport(model: ProviderRuntimeModel): ProviderRuntimeModel {
|
||||
@@ -130,10 +126,7 @@ function shouldUseCodexResponsesHooks(params: {
|
||||
api?: ProviderRuntimeModel["api"] | null;
|
||||
baseUrl?: string;
|
||||
}): boolean {
|
||||
if (normalizeProviderId(params.provider ?? "") === LEGACY_OPENAI_CODEX_PROVIDER_ID) {
|
||||
return true;
|
||||
}
|
||||
if (params.api === "openai-codex-responses") {
|
||||
if (params.api === "openai-chatgpt-responses") {
|
||||
return true;
|
||||
}
|
||||
return typeof params.baseUrl === "string" && isOpenAICodexBaseUrl(params.baseUrl);
|
||||
@@ -151,9 +144,6 @@ function resolveConfiguredAuthTransport(
|
||||
if (ctx.authProfileMode === "api_key" || ctx.authProfileMode === "aws-sdk") {
|
||||
return "responses";
|
||||
}
|
||||
if (ctx.authProfileId && isLegacyOpenAIProfileId(ctx.authProfileId)) {
|
||||
return "codex";
|
||||
}
|
||||
const authMode = ctx.providerConfig?.auth;
|
||||
if (authMode === "oauth" || authMode === "token") {
|
||||
return "codex";
|
||||
@@ -164,10 +154,7 @@ function resolveConfiguredAuthTransport(
|
||||
|
||||
const auth = ctx.config?.auth;
|
||||
const profiles = auth?.profiles ?? {};
|
||||
const orderedProfileIds = [
|
||||
...(auth?.order?.[PROVIDER_ID] ?? []),
|
||||
...(auth?.order?.[LEGACY_OPENAI_CODEX_PROVIDER_ID] ?? []),
|
||||
];
|
||||
const orderedProfileIds = auth?.order?.[PROVIDER_ID] ?? [];
|
||||
for (const profileId of orderedProfileIds) {
|
||||
const mode = profiles[profileId]?.mode;
|
||||
if (mode === "oauth" || mode === "token") {
|
||||
@@ -179,11 +166,7 @@ function resolveConfiguredAuthTransport(
|
||||
}
|
||||
|
||||
const providerModes = Object.values(profiles)
|
||||
.filter((profile) =>
|
||||
[PROVIDER_ID, LEGACY_OPENAI_CODEX_PROVIDER_ID].includes(
|
||||
normalizeProviderId(profile.provider),
|
||||
),
|
||||
)
|
||||
.filter((profile) => normalizeProviderId(profile.provider) === PROVIDER_ID)
|
||||
.map((profile) => profile.mode);
|
||||
if (providerModes.some((mode) => mode === "oauth" || mode === "token")) {
|
||||
return "codex";
|
||||
@@ -194,10 +177,6 @@ function resolveConfiguredAuthTransport(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isLegacyOpenAIProfileId(profileId: string): boolean {
|
||||
return normalizeProviderId(profileId.split(":", 1)[0] ?? "") === LEGACY_OPENAI_CODEX_PROVIDER_ID;
|
||||
}
|
||||
|
||||
function shouldResolveDynamicModelThroughCodex(ctx: ProviderResolveDynamicModelContext): boolean {
|
||||
if (
|
||||
shouldUseCodexResponsesHooks({
|
||||
@@ -335,15 +314,9 @@ export function buildOpenAIProvider(): ProviderPlugin {
|
||||
return {
|
||||
id: PROVIDER_ID,
|
||||
label: "OpenAI",
|
||||
hookAliases: ["openai-codex", "azure-openai", "azure-openai-responses"],
|
||||
hookAliases: ["azure-openai", "azure-openai-responses"],
|
||||
docsPath: "/providers/models",
|
||||
envVars: ["OPENAI_API_KEY"],
|
||||
oauthProfileIdRepairs: [
|
||||
{
|
||||
legacyProfileId: "openai-codex:default",
|
||||
promptLabel: "OpenAI",
|
||||
},
|
||||
],
|
||||
auth: [
|
||||
...buildOpenAIChatGPTAuthMethods(),
|
||||
createProviderApiKeyAuthMethod({
|
||||
@@ -374,7 +347,7 @@ export function buildOpenAIProvider(): ProviderPlugin {
|
||||
: resolveOpenAIGptForwardCompatModel(ctx),
|
||||
preferRuntimeResolvedModel: (ctx) => codexHooks.preferRuntimeResolvedModel?.(ctx) ?? false,
|
||||
normalizeResolvedModel: (ctx) => {
|
||||
if (!isOpenAIOrLegacyCodexProvider(ctx.provider)) {
|
||||
if (!isOpenAIProvider(ctx.provider)) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
@@ -420,16 +393,10 @@ export function buildOpenAIProvider(): ProviderPlugin {
|
||||
/content_filter.*(?:prompt|input).*(?:too long|exceed)/i.test(errorMessage),
|
||||
resolveReasoningOutputMode: () => "native",
|
||||
resolveThinkingProfile: ({ provider, modelId }) =>
|
||||
normalizeProviderId(provider) === LEGACY_OPENAI_CODEX_PROVIDER_ID
|
||||
? resolveOpenAICodexThinkingProfile(modelId)
|
||||
: resolveOpenAIThinkingProfile(modelId),
|
||||
normalizeProviderId(provider) === PROVIDER_ID
|
||||
? resolveUnifiedOpenAIThinkingProfile(modelId)
|
||||
: null,
|
||||
isModernModelRef: ({ modelId }) => matchesExactOrPrefix(modelId, OPENAI_MODERN_MODEL_IDS),
|
||||
buildMissingAuthMessage: (ctx) => {
|
||||
if (ctx.provider !== PROVIDER_ID || ctx.listProfileIds("openai-codex").length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return 'No API key found for provider "openai". You are authenticated with OpenAI Codex OAuth; OpenAI agent model runs use openai/gpt-* through the Codex runtime. Set OPENAI_API_KEY only for direct OpenAI API-key surfaces.';
|
||||
},
|
||||
augmentModelCatalog: (ctx) => {
|
||||
const openAiGpt55ProTemplate = findCatalogTemplate({
|
||||
entries: ctx.entries,
|
||||
|
||||
@@ -8,16 +8,13 @@
|
||||
"modelSupport": {
|
||||
"modelPrefixes": ["gpt-", "o1", "o3", "o4"]
|
||||
},
|
||||
"providerAuthAliases": {
|
||||
"openai-codex": "openai"
|
||||
},
|
||||
"providerEndpoints": [
|
||||
{
|
||||
"endpointClass": "openai-public",
|
||||
"hosts": ["api.openai.com"]
|
||||
},
|
||||
{
|
||||
"endpointClass": "openai-codex",
|
||||
"endpointClass": "openai",
|
||||
"hosts": ["chatgpt.com"]
|
||||
},
|
||||
{
|
||||
@@ -239,7 +236,6 @@
|
||||
"provider": "openai",
|
||||
"method": "oauth",
|
||||
"choiceId": "openai",
|
||||
"deprecatedChoiceIds": ["openai-codex", "codex-cli", "openai-codex-import"],
|
||||
"choiceLabel": "ChatGPT Login",
|
||||
"choiceHint": "Sign in with your ChatGPT or Codex subscription",
|
||||
"assistantPriority": -40,
|
||||
@@ -252,7 +248,6 @@
|
||||
"provider": "openai",
|
||||
"method": "device-code",
|
||||
"choiceId": "openai-device-code",
|
||||
"deprecatedChoiceIds": ["openai-codex-device-code"],
|
||||
"choiceLabel": "ChatGPT Device Pairing",
|
||||
"choiceHint": "Pair your ChatGPT account in browser with a device code",
|
||||
"assistantPriority": -10,
|
||||
@@ -265,7 +260,6 @@
|
||||
"provider": "openai",
|
||||
"method": "api-key",
|
||||
"choiceId": "openai-api-key",
|
||||
"deprecatedChoiceIds": ["openai-codex-api-key"],
|
||||
"choiceLabel": "OpenAI API Key",
|
||||
"choiceHint": "Use your OpenAI API key directly",
|
||||
"assistantPriority": 5,
|
||||
|
||||
@@ -29,6 +29,7 @@ const manifest = JSON.parse(
|
||||
setup?: {
|
||||
providers?: Array<{ id: string }>;
|
||||
};
|
||||
providerEndpoints?: Array<{ endpointClass?: string; hosts?: string[] }>;
|
||||
providerAuthAliases?: Record<string, string>;
|
||||
};
|
||||
|
||||
@@ -95,17 +96,24 @@ describe("OpenAI plugin manifest", () => {
|
||||
expect(packageJson.dependencies?.ws).toBe("8.21.0");
|
||||
});
|
||||
|
||||
it("keeps removed Codex CLI import auth choice as a deprecated OpenAI login alias", () => {
|
||||
it("exposes only current OpenAI login choices", () => {
|
||||
const openAiLogin = manifest.providerAuthChoices?.find(
|
||||
(choice) => choice.choiceId === "openai",
|
||||
);
|
||||
|
||||
expect(openAiLogin?.deprecatedChoiceIds).toContain("openai-codex-import");
|
||||
expect(openAiLogin?.deprecatedChoiceIds).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps legacy OpenAI Codex setup lookup routed to the OpenAI setup runtime", () => {
|
||||
it("routes setup through the OpenAI setup runtime", () => {
|
||||
expect(manifest.setup?.providers?.map((provider) => provider.id)).toEqual(["openai"]);
|
||||
expect(manifest.providerAuthAliases?.["openai-codex"]).toBe("openai");
|
||||
expect(manifest.providerAuthAliases).toBeUndefined();
|
||||
});
|
||||
|
||||
it("classifies ChatGPT backend traffic with the supported OpenAI endpoint class", () => {
|
||||
const chatGptEndpoint = manifest.providerEndpoints?.find((endpoint) =>
|
||||
endpoint.hosts?.includes("chatgpt.com"),
|
||||
);
|
||||
expect(chatGptEndpoint?.endpointClass).toBe("openai");
|
||||
});
|
||||
|
||||
it("keeps OpenAI media-understanding manifest metadata aligned with runtime audio support", () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { vi } from "vitest";
|
||||
|
||||
const loginOpenAICodexOAuthMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./openai-codex-oauth.runtime.js", () => ({
|
||||
vi.mock("./openai-chatgpt-oauth.runtime.js", () => ({
|
||||
loginOpenAICodexOAuth: loginOpenAICodexOAuthMock,
|
||||
}));
|
||||
|
||||
|
||||
@@ -14,15 +14,9 @@ export function createOpenAIProvider(): ProviderPlugin {
|
||||
return {
|
||||
id: "openai",
|
||||
label: "OpenAI",
|
||||
hookAliases: ["openai-codex", "azure-openai", "azure-openai-responses"],
|
||||
hookAliases: ["azure-openai", "azure-openai-responses"],
|
||||
docsPath: "/providers/models",
|
||||
envVars: ["OPENAI_API_KEY"],
|
||||
oauthProfileIdRepairs: [
|
||||
{
|
||||
legacyProfileId: "openai-codex:default",
|
||||
promptLabel: "OpenAI",
|
||||
},
|
||||
],
|
||||
auth: [
|
||||
{
|
||||
id: "oauth",
|
||||
|
||||
@@ -2,17 +2,22 @@ import { describe, expect, it } from "vitest";
|
||||
import { resolveThinkingProfile } from "./provider-policy-api.js";
|
||||
|
||||
describe("OpenAI provider policy artifact", () => {
|
||||
it("keeps legacy Codex thinking policy for openai-codex refs", () => {
|
||||
it("keeps OpenAI thinking policy for openai refs", () => {
|
||||
const codexProfile = resolveThinkingProfile({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.3-codex-spark",
|
||||
});
|
||||
const openaiProfile = resolveThinkingProfile({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.3-codex-spark",
|
||||
modelId: "gpt-5.3",
|
||||
});
|
||||
const openaiMiniProfile = resolveThinkingProfile({
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4-mini",
|
||||
});
|
||||
|
||||
expect(codexProfile?.levels.map((level) => level.id)).toContain("xhigh");
|
||||
expect(openaiProfile?.levels.map((level) => level.id)).not.toContain("xhigh");
|
||||
expect(openaiMiniProfile?.levels.map((level) => level.id)).toContain("xhigh");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
|
||||
import {
|
||||
resolveOpenAICodexThinkingProfile,
|
||||
resolveOpenAIThinkingProfile,
|
||||
} from "./thinking-policy.js";
|
||||
import { resolveUnifiedOpenAIThinkingProfile } from "./thinking-policy.js";
|
||||
|
||||
export function normalizeConfig(params: { provider: string; providerConfig: ModelProviderConfig }) {
|
||||
return params.providerConfig;
|
||||
@@ -11,9 +8,7 @@ export function normalizeConfig(params: { provider: string; providerConfig: Mode
|
||||
export function resolveThinkingProfile(params: { provider: string; modelId: string }) {
|
||||
switch (params.provider.trim().toLowerCase()) {
|
||||
case "openai":
|
||||
return resolveOpenAIThinkingProfile(params.modelId);
|
||||
case "openai-codex":
|
||||
return resolveOpenAICodexThinkingProfile(params.modelId);
|
||||
return resolveUnifiedOpenAIThinkingProfile(params.modelId);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -204,12 +204,12 @@ describe("buildOpenAIRealtimeTranscriptionProvider", () => {
|
||||
|
||||
it("treats a Codex OAuth profile as configured when no API key is present", () => {
|
||||
const provider = buildOpenAIRealtimeTranscriptionProvider();
|
||||
const cfg = { auth: { order: { "openai-codex": ["openai-codex:default"] } } };
|
||||
const cfg = { auth: { order: { openai: ["openai:default"] } } };
|
||||
providerAuthMocks.isProviderAuthProfileConfigured.mockReturnValue(true);
|
||||
|
||||
expect(provider.isConfigured({ cfg: cfg as never, providerConfig: {} })).toBe(true);
|
||||
expect(providerAuthMocks.isProviderAuthProfileConfigured).toHaveBeenCalledWith({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
cfg,
|
||||
});
|
||||
});
|
||||
@@ -222,7 +222,7 @@ describe("buildOpenAIRealtimeTranscriptionProvider", () => {
|
||||
response: new Response(JSON.stringify({ value: "ek-test" }), { status: 200 }),
|
||||
release,
|
||||
});
|
||||
const cfg = { auth: { order: { "openai-codex": ["openai-codex:default"] } } };
|
||||
const cfg = { auth: { order: { openai: ["openai:default"] } } };
|
||||
const session = provider.createSession({
|
||||
cfg: cfg as never,
|
||||
providerConfig: {},
|
||||
@@ -233,7 +233,7 @@ describe("buildOpenAIRealtimeTranscriptionProvider", () => {
|
||||
|
||||
expect(socket.headers?.Authorization).toBe("Bearer ek-test");
|
||||
expect(providerAuthMocks.resolveProviderAuthProfileApiKey).toHaveBeenCalledWith({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
cfg,
|
||||
});
|
||||
const request = mockCallArg(ssrfMocks.fetchWithSsrFGuard);
|
||||
|
||||
@@ -300,7 +300,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
|
||||
bridge.close();
|
||||
|
||||
expect(resolveProviderAuthProfileApiKeyMock).toHaveBeenCalledWith({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
cfg: {},
|
||||
includeExternalCliAuth: true,
|
||||
});
|
||||
@@ -588,7 +588,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
|
||||
|
||||
expect(provider.isConfigured({ cfg, providerConfig: {} })).toBe(true);
|
||||
expect(isProviderAuthProfileConfiguredMock).toHaveBeenCalledWith({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
cfg,
|
||||
includeExternalCliAuth: true,
|
||||
});
|
||||
@@ -632,7 +632,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
|
||||
});
|
||||
|
||||
expect(resolveProviderAuthProfileApiKeyMock).toHaveBeenCalledWith({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
cfg,
|
||||
includeExternalCliAuth: true,
|
||||
});
|
||||
@@ -664,7 +664,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
|
||||
});
|
||||
|
||||
expect(resolveProviderAuthProfileApiKeyMock).toHaveBeenCalledWith({
|
||||
provider: "openai-codex",
|
||||
provider: "openai",
|
||||
cfg,
|
||||
includeExternalCliAuth: true,
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
|
||||
const RESPONSES_FAMILY_APIS = new Set([
|
||||
"openai-responses",
|
||||
"openai-codex-responses",
|
||||
"openai-chatgpt-responses",
|
||||
"azure-openai-responses",
|
||||
]);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("OpenAI setup auth provider", () => {
|
||||
const apiKey = provider.auth.find((method) => method.id === "api-key");
|
||||
|
||||
expect(provider.id).toBe("openai");
|
||||
expect(provider.aliases).toEqual(["openai-codex"]);
|
||||
expect(provider.aliases).toEqual(["openai"]);
|
||||
expect(authMethodIds(provider)).toEqual(["oauth", "device-code", "api-key"]);
|
||||
expect(oauth?.label).toBe("ChatGPT Login");
|
||||
expect(oauth?.wizard?.choiceId).toBe("openai");
|
||||
|
||||
@@ -73,7 +73,6 @@ export function buildOpenAISetupProvider(): ProviderPlugin {
|
||||
|
||||
return {
|
||||
id: "openai",
|
||||
aliases: ["openai-codex"],
|
||||
label: "OpenAI",
|
||||
docsPath: "/providers/models",
|
||||
envVars: ["OPENAI_API_KEY"],
|
||||
|
||||
@@ -103,7 +103,6 @@ export function describeOpenAIProviderCatalogContract() {
|
||||
switch (params.provider) {
|
||||
case "azure-openai-responses":
|
||||
case "openai":
|
||||
case "openai-codex":
|
||||
return ["openai"];
|
||||
default:
|
||||
return undefined;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user