From 4c33aaa86c1673924c9e15c41414ea8af253092f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 31 May 2026 00:29:44 +0100 Subject: [PATCH] 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 --- .../main/java/ai/openclaw/app/NodeRuntime.kt | 2 +- .../openclaw/app/ui/ProvidersModelsScreen.kt | 2 +- .../MenuSessionsInjectorTests.swift | 2 +- .../OpenClawKitTests/ChatViewModelTests.swift | 2 +- docs/cli/migrate.md | 2 +- docs/cli/models.md | 2 +- docs/concepts/agent-runtimes.md | 6 +- docs/concepts/model-providers.md | 4 +- docs/gateway/doctor.md | 2 +- docs/help/faq-first-run.md | 12 +- docs/install/migrating-hermes.md | 4 +- docs/plugins/codex-harness.md | 23 +- docs/plugins/sdk-agent-harness.md | 2 +- docs/providers/openai.md | 32 +- docs/reference/wizard.md | 2 +- docs/start/wizard-cli-reference.md | 2 +- docs/tools/acp-agents.md | 2 +- docs/tools/plugin.md | 2 +- docs/tools/web.md | 2 +- extensions/acpx/src/runtime.test.ts | 14 +- extensions/acpx/src/runtime.ts | 8 +- extensions/active-memory/index.test.ts | 4 +- extensions/anthropic/index.test.ts | 2 +- .../anthropic/provider-policy-api.test.ts | 2 +- extensions/codex/harness.test.ts | 4 +- extensions/codex/harness.ts | 2 +- extensions/codex/index.test.ts | 2 +- extensions/codex/provider-catalog.ts | 4 +- extensions/codex/provider.test.ts | 4 +- .../app-server/attempt-diagnostics.test.ts | 6 +- .../codex/src/app-server/auth-bridge.test.ts | 196 +++++----- .../auth-profile-runtime-contract.test.ts | 2 +- .../codex/src/app-server/compact.test.ts | 12 +- .../src/app-server/dynamic-tool-build.test.ts | 8 +- .../src/app-server/event-projector.test.ts | 14 +- .../codex/src/app-server/event-projector.ts | 6 +- .../app-server/local-runtime-attribution.ts | 2 +- .../outcome-fallback-runtime-contract.test.ts | 4 +- .../app-server/run-attempt-test-harness.ts | 4 +- .../run-attempt.context-engine.test.ts | 4 +- .../codex/src/app-server/run-attempt.test.ts | 24 +- .../run-attempt.usage-limits.test.ts | 8 +- .../src/app-server/session-binding.test.ts | 12 +- .../src/app-server/shared-client.test.ts | 22 +- .../src/app-server/side-question.test.ts | 8 +- .../codex/src/app-server/test-support.ts | 4 +- .../thread-lifecycle.binding.test.ts | 4 +- .../src/app-server/thread-lifecycle.test.ts | 8 +- .../codex/src/app-server/thread-lifecycle.ts | 7 +- .../codex/src/app-server/trajectory.test.ts | 4 +- extensions/codex/src/commands.test.ts | 61 ++- .../codex/src/conversation-binding.test.ts | 20 +- extensions/codex/src/conversation-binding.ts | 7 +- .../codex/src/conversation-control.test.ts | 2 +- extensions/discord/src/config-schema.test.ts | 4 +- extensions/discord/src/config-ui-hints.ts | 2 +- .../native-command.model-picker.test.ts | 6 +- .../native-command.plugin-dispatch.test.ts | 8 +- .../native-command.think-autocomplete.test.ts | 10 +- .../discord/src/voice/manager.e2e.test.ts | 6 +- extensions/llm-task/openclaw.plugin.json | 2 +- extensions/migrate-hermes/auth.ts | 257 ++++++------- .../migrate-hermes/files-and-skills.test.ts | 65 ++++ .../provider.secret-failure.test.ts | 18 +- extensions/migrate-hermes/secrets.test.ts | 347 +----------------- extensions/openai/api.ts | 6 +- .../openai/image-generation-provider.test.ts | 194 +++++----- .../openai/image-generation-provider.ts | 102 ++--- extensions/openai/index.test.ts | 12 +- ...s => openai-chatgpt-auth-identity.test.ts} | 2 +- ...ity.ts => openai-chatgpt-auth-identity.ts} | 2 +- ...x-catalog.ts => openai-chatgpt-catalog.ts} | 2 +- ....ts => openai-chatgpt-device-code.test.ts} | 4 +- ...-code.ts => openai-chatgpt-device-code.ts} | 4 +- ... => openai-chatgpt-oauth-abort.runtime.ts} | 0 ...openai-chatgpt-oauth-flow.runtime.test.ts} | 6 +- ...s => openai-chatgpt-oauth-flow.runtime.ts} | 14 +- ...s => openai-chatgpt-oauth-page.runtime.ts} | 0 ... => openai-chatgpt-oauth-types.runtime.ts} | 0 ...s => openai-chatgpt-oauth.runtime.test.ts} | 2 +- ...ime.ts => openai-chatgpt-oauth.runtime.ts} | 4 +- ...time.ts => openai-chatgpt-pkce.runtime.ts} | 0 ....ts => openai-chatgpt-provider.runtime.ts} | 8 +- ...est.ts => openai-chatgpt-provider.test.ts} | 29 +- ...provider.ts => openai-chatgpt-provider.ts} | 25 +- ...dex-shared.ts => openai-chatgpt-shared.ts} | 0 extensions/openai/openai-provider.test.ts | 109 +----- extensions/openai/openai-provider.ts | 57 +-- extensions/openai/openclaw.plugin.json | 8 +- extensions/openai/openclaw.plugin.test.ts | 16 +- .../openai/provider-auth.contract.test.ts | 2 +- extensions/openai/provider-contract-api.ts | 8 +- extensions/openai/provider-policy-api.test.ts | 11 +- extensions/openai/provider-policy-api.ts | 9 +- .../realtime-transcription-provider.test.ts | 8 +- .../openai/realtime-voice-provider.test.ts | 8 +- extensions/openai/replay-policy.ts | 2 +- extensions/openai/setup-api.test.ts | 2 +- extensions/openai/setup-api.ts | 1 - .../provider-catalog.contract-test-support.ts | 1 - extensions/openai/thinking-policy.ts | 9 + extensions/openai/transport-policy.test.ts | 16 +- extensions/qa-lab/src/auth-profile.fixture.ts | 16 +- .../qa-lab/src/codex-plugin-lifecycle.test.ts | 4 +- extensions/qa-lab/src/gateway-child.test.ts | 83 ++--- extensions/qa-lab/src/gateway-child.ts | 39 +- .../src/model-selection.runtime.test.ts | 8 +- .../src/providers/aimock/server.test.ts | 6 +- .../qa-lab/src/providers/aimock/server.ts | 2 +- .../src/providers/live-frontier/auth.ts | 18 +- .../live-frontier/model-selection.runtime.ts | 4 +- .../src/providers/mock-openai/server.test.ts | 2 +- .../src/providers/mock-openai/server.ts | 2 +- .../src/providers/shared/auth-store.test.ts | 8 +- .../qa-lab/src/providers/shared/auth-store.ts | 4 +- .../bot-native-commands.session-meta.test.ts | 4 +- extensions/telegram/src/model-buttons.test.ts | 2 +- packages/llm-core/src/types.ts | 2 +- .../src/model-ref.test.ts | 6 +- .../src/model-catalog-types.ts | 2 +- .../auth-profile-codex-mixed-profiles.md | 8 +- .../auth-profile-doctor-migration-safety.md | 4 +- .../runtime/codex-plugin-cold-install.md | 2 +- .../e2e/openai-image-auth-docker-client.ts | 8 +- scripts/lib/live-docker-auth.sh | 2 +- scripts/test-live-codex-harness-docker.sh | 2 +- src/acp/control-plane/manager.test.ts | 18 +- src/acp/runtime/session-identifiers.ts | 2 +- src/agents/acp-spawn.test.ts | 4 +- src/agents/agent-auth-json.test.ts | 14 +- .../agent-command.live-model-switch.test.ts | 8 +- src/agents/agent-command.ts | 2 +- src/agents/agent-model-discovery.auth.test.ts | 16 +- src/agents/agent-scope.test.ts | 18 +- ...tools.create-openclaw-coding-tools.test.ts | 2 +- ...ent-tools.model-provider-collision.test.ts | 16 +- src/agents/agent-tools.ts | 4 +- src/agents/auth-health.test.ts | 59 ++- ...th-profiles.ensureauthprofilestore.test.ts | 134 +++---- .../auth-profiles.external-cli-scope.test.ts | 7 +- .../auth-profiles.external-cli-sync.test.ts | 48 +-- ...tize-lastgood-round-robin-ordering.test.ts | 30 +- src/agents/auth-profiles.store-cache.test.ts | 10 +- src/agents/auth-profiles.store.save.test.ts | 308 ++-------------- .../auth-profiles/credential-state.test.ts | 4 +- src/agents/auth-profiles/display.test.ts | 16 +- .../auth-profiles/effective-oauth.test.ts | 8 +- .../auth-profiles/external-cli-scope.ts | 2 +- src/agents/auth-profiles/external-cli-sync.ts | 6 +- .../auth-profiles/external-oauth.test.ts | 58 +-- .../auth-profiles/oauth-identity.test.ts | 22 +- .../auth-profiles/oauth-lock-path.test.ts | 44 +-- .../oauth-lock-timeout-classification.test.ts | 8 +- .../auth-profiles/oauth-manager.test.ts | 130 +------ src/agents/auth-profiles/oauth-manager.ts | 1 - .../oauth-refresh-failure.test.ts | 23 ++ .../auth-profiles/oauth-refresh-failure.ts | 2 +- .../auth-profiles/oauth-refresh-queue.test.ts | 12 +- src/agents/auth-profiles/oauth-shared.test.ts | 8 +- .../oauth.adopt-identity.test.ts | 18 +- .../oauth.concurrent-agents.test.ts | 6 +- .../oauth.fallback-to-main-agent.test.ts | 2 +- .../oauth.mirror-refresh.test.ts | 34 +- ...auth.openai-codex-refresh-fallback.test.ts | 162 ++++---- src/agents/auth-profiles/oauth.ts | 2 +- src/agents/auth-profiles/order.test.ts | 70 ++-- src/agents/auth-profiles/order.ts | 2 +- .../auth-profiles/persisted-boundary.test.ts | 128 +------ src/agents/auth-profiles/persisted.ts | 322 +--------------- src/agents/auth-profiles/portability.test.ts | 14 +- src/agents/auth-profiles/profiles.test.ts | 176 +++++++-- .../auth-profiles/runtime-snapshots.test.ts | 26 +- .../auth-profiles/session-override.test.ts | 34 +- .../store.sidecar-runtime-defaults.test.ts | 156 -------- src/agents/auth-profiles/store.ts | 98 +++-- src/agents/auth-profiles/usage.test.ts | 42 +-- src/agents/auth-profiles/usage.ts | 6 +- src/agents/btw.test.ts | 4 +- src/agents/btw.ts | 2 +- src/agents/cli-auth-epoch.test.ts | 18 +- src/agents/cli-credentials.test.ts | 16 +- src/agents/cli-credentials.ts | 4 +- src/agents/cli-runner/prepare.test.ts | 6 +- src/agents/codex-native-web-search-core.ts | 6 +- src/agents/codex-native-web-search.test.ts | 34 +- .../command/attempt-execution.cli.test.ts | 26 +- src/agents/command/attempt-execution.ts | 2 +- src/agents/command/cli-compaction.ts | 2 +- src/agents/command/delivery.test.ts | 4 +- src/agents/command/session-store.test.ts | 4 +- src/agents/context.lookup.test.ts | 2 +- src/agents/context.test.ts | 4 +- src/agents/copilot-routing.test.ts | 2 +- src/agents/copilot-routing.ts | 2 +- ...t-helpers.formatassistanterrortext.test.ts | 28 +- ...gent-helpers.isbillingerrormessage.test.ts | 30 +- src/agents/embedded-agent-helpers/errors.ts | 6 +- .../failover-matches.ts | 2 +- .../embedded-agent-runner-extraparams.test.ts | 172 ++++----- src/agents/embedded-agent-runner.e2e.test.ts | 12 +- ...ed-agent.auth-profile-rotation.e2e.test.ts | 8 +- ...nt-runner.sanitize-session-history.test.ts | 6 +- .../compact.hooks.test.ts | 72 ++-- .../embedded-agent-runner/compact.queued.ts | 5 +- src/agents/embedded-agent-runner/compact.ts | 4 +- .../compaction-runtime-context.test.ts | 29 +- .../compaction-runtime-context.ts | 2 +- .../embedded-agent-runner/extra-params.ts | 2 +- ...orward-compat.errors-and-overrides.test.ts | 59 ++- .../model.forward-compat.test.ts | 2 +- .../model.inline-provider.ts | 2 +- .../model.provider-runtime.test-support.ts | 306 ++++++++------- .../model.startup-retry.test.ts | 20 +- .../model.test-harness.ts | 10 +- .../embedded-agent-runner/model.test.ts | 200 +++++----- src/agents/embedded-agent-runner/model.ts | 4 +- .../embedded-agent-runner/replay-history.ts | 2 +- .../run.codex-app-server-recovery.test.ts | 2 +- .../run.codex-server-error-fallback.test.ts | 4 +- ...ss-provider-fallback-error-context.test.ts | 8 +- .../run.incomplete-turn.test.ts | 36 +- .../run.overflow-compaction.harness.ts | 2 +- .../run.overflow-compaction.test.ts | 150 ++++---- src/agents/embedded-agent-runner/run.ts | 6 +- .../run/attempt.run-decisions.test.ts | 6 +- ...mpt.spawn-workspace.context-engine.test.ts | 2 +- .../embedded-agent-runner/run/attempt.test.ts | 18 +- .../attempt.tool-call-argument-repair.test.ts | 6 +- .../run/attempt.tool-call-argument-repair.ts | 2 +- .../embedded-agent-runner/run/attempt.ts | 4 +- .../run/failover-observation.test.ts | 6 +- .../run/incomplete-turn.ts | 2 +- .../embedded-agent-runner/run/setup.test.ts | 10 +- .../stream-resolution.test.ts | 16 +- .../stream-resolution.ts | 2 +- ...agent-subscribe.handlers.lifecycle.test.ts | 4 +- src/agents/execution-contract.test.ts | 4 +- src/agents/failover-error.test.ts | 2 +- src/agents/gpt5-prompt-overlay.ts | 2 +- src/agents/harness/policy.ts | 2 +- src/agents/harness/runtime-plugin.test.ts | 11 +- src/agents/harness/selection.test.ts | 2 +- src/agents/live-model-filter.test.ts | 4 +- src/agents/live-model-filter.ts | 3 - src/agents/live-model-switch.test.ts | 29 +- src/agents/live-model-switch.ts | 4 +- src/agents/live-test-helpers.test.ts | 14 +- src/agents/live-test-helpers.ts | 2 +- src/agents/model-auth-label.test.ts | 14 +- src/agents/model-auth.profiles.test.ts | 34 +- src/agents/model-auth.test.ts | 2 +- src/agents/model-auth.ts | 2 +- src/agents/model-catalog-visibility.test.ts | 14 +- src/agents/model-catalog-visibility.ts | 2 +- src/agents/model-catalog.test.ts | 63 ++-- src/agents/model-compat.test.ts | 16 +- .../model-fallback.run-embedded.e2e.test.ts | 2 +- src/agents/model-fallback.test.ts | 34 +- src/agents/model-runtime-aliases.ts | 2 +- src/agents/model-selection.test.ts | 66 ++-- ...odels-config.providers.live-filter.test.ts | 6 +- src/agents/models.profiles.live.test.ts | 24 +- .../openai-reasoning-compat.live.test.ts | 4 +- src/agents/openai-reasoning-effort.test.ts | 4 +- .../openai-responses-payload-policy.test.ts | 6 +- src/agents/openai-responses-payload-policy.ts | 16 +- ...routing.test.ts => openai-routing.test.ts} | 28 +- ...nai-codex-routing.ts => openai-routing.ts} | 34 +- src/agents/openai-thinking-contract.test.ts | 12 +- src/agents/openai-transport-stream.test.ts | 160 ++++---- src/agents/openai-transport-stream.ts | 7 +- .../openclaw-tools.media-factory-plan.test.ts | 14 +- .../openclaw-tools.session-status.test.ts | 20 +- src/agents/openclaw-tools.sessions.test.ts | 4 +- src/agents/provider-api-families.test.ts | 2 +- src/agents/provider-api-families.ts | 2 +- src/agents/provider-attribution.test.ts | 22 +- src/agents/provider-attribution.ts | 30 +- src/agents/provider-auth-aliases.test.ts | 16 +- src/agents/provider-auth-aliases.ts | 10 +- src/agents/provider-transport-stream.test.ts | 8 +- src/agents/provider-transport-stream.ts | 6 +- src/agents/runtime-plan/build.test.ts | 25 +- src/agents/session-file-repair.test.ts | 43 ++- src/agents/session-file-repair.ts | 9 +- src/agents/session-runtime-compat.ts | 2 +- src/agents/sessions-spawn-hooks.test.ts | 10 +- src/agents/simple-completion-runtime.ts | 2 +- .../simple-completion-transport.test.ts | 4 +- src/agents/simple-completion-transport.ts | 2 +- .../subagent-spawn.model-session.test.ts | 8 +- src/agents/subagent-spawn.test.ts | 12 +- .../subagent-spawn.thread-binding.test.ts | 2 +- src/agents/tool-replay-repair.live.test.ts | 4 +- src/agents/tools-effective-inventory.test.ts | 14 +- src/agents/tools/image-generate-tool.test.ts | 4 +- src/agents/tools/pdf-tool.test.ts | 20 +- src/agents/tools/pdf-tool.ts | 3 +- src/agents/tools/video-generate-tool.test.ts | 8 +- src/agents/transcript-policy.test.ts | 3 +- src/agents/transcript-policy.ts | 4 +- src/agents/transport-message-transform.ts | 4 +- .../transport-params-runtime-contract.test.ts | 20 +- src/auto-reply/fallback-state.test.ts | 4 +- ...irective.directive-behavior.e2e-harness.ts | 8 +- ...ets-active-session-native-stop.e2e.test.ts | 14 +- .../reply/agent-runner-execution.test.ts | 76 ++-- .../reply/agent-runner-execution.ts | 10 +- .../reply/agent-runner-memory.test.ts | 5 +- src/auto-reply/reply/agent-runner-memory.ts | 2 +- .../agent-runner.runreplyagent.e2e.test.ts | 58 +-- src/auto-reply/reply/commands-compact.test.ts | 2 +- src/auto-reply/reply/commands-compact.ts | 2 +- src/auto-reply/reply/commands-models.test.ts | 32 +- src/auto-reply/reply/commands-models.ts | 2 +- src/auto-reply/reply/commands-plugin.test.ts | 4 +- src/auto-reply/reply/commands-status.test.ts | 48 ++- .../conversation-label-generator.test.ts | 4 +- .../reply/conversation-label-generator.ts | 2 +- .../reply/directive-handling.auth.ts | 23 +- .../reply/directive-handling.model-picker.ts | 2 +- .../reply/directive-handling.model.test.ts | 93 ++++- .../reply/directive-handling.model.ts | 49 ++- .../reply/directive-handling.persist.ts | 2 +- src/auto-reply/reply/followup-runner.test.ts | 6 +- src/auto-reply/reply/get-reply-run.ts | 2 +- src/auto-reply/reply/model-selection.test.ts | 96 ++--- src/auto-reply/reply/model-selection.ts | 2 +- src/auto-reply/reply/reply-utils.test.ts | 6 +- .../reply/response-prefix-template.ts | 2 +- src/auto-reply/reply/session.test.ts | 26 +- src/auto-reply/status.test.ts | 16 +- src/auto-reply/thinking.test.ts | 12 +- src/cli/capability-cli.test.ts | 12 +- src/cli/capability-cli.ts | 5 +- src/cli/models-cli.test.ts | 23 +- src/cli/models-cli.ts | 2 +- src/cli/plugins-cli.policy.test.ts | 1 - src/commands/agents.add.test.ts | 62 +--- src/commands/agents.commands.add.ts | 8 +- src/commands/auth-choice-legacy.test.ts | 24 +- src/commands/auth-choice-legacy.ts | 9 +- src/commands/auth-choice-options.test.ts | 8 +- src/commands/auth-choice.model-check.test.ts | 36 +- src/commands/auth-choice.model-check.ts | 43 ++- src/commands/auth-choice.test.ts | 6 +- src/commands/codex-runtime-plugin-install.ts | 2 +- ...re.gateway-auth.prompt-auth-config.test.ts | 12 +- src/commands/configure.wizard.test.ts | 10 +- src/commands/doctor-auth-oauth-sidecar.ts | 18 +- src/commands/doctor-auth.hints.test.ts | 97 ++++- src/commands/doctor-auth.ts | 19 +- .../doctor-legacy-config.migrations.test.ts | 12 +- src/commands/doctor-memory-search.ts | 3 + src/commands/doctor.e2e-harness.ts | 8 + src/commands/doctor.fast-path-mocks.ts | 60 +++ .../shared/codex-route-warnings.test.ts | 2 +- .../doctor/shared/codex-route-warnings.ts | 2 +- .../shared/legacy-models-add-metadata.test.ts | 16 +- .../shared/legacy-models-add-metadata.ts | 7 +- .../shared}/legacy-oauth-sidecar.test.ts | 6 +- .../doctor/shared}/legacy-oauth-sidecar.ts | 10 +- .../doctor/shared/stale-plugin-config.test.ts | 18 +- src/commands/model-picker.test.ts | 74 ++-- .../models.auth.provider-resolution.test.ts | 4 +- src/commands/models.list.e2e.test.ts | 4 +- src/commands/models/auth-list.test.ts | 60 +-- src/commands/models/auth-list.ts | 13 +- .../models/auth.login-profiles.test.ts | 12 +- src/commands/models/auth.test.ts | 170 +++++---- src/commands/models/auth.ts | 25 +- src/commands/models/list.auth-index.test.ts | 36 +- src/commands/models/list.auth-index.ts | 23 +- .../models/list.auth-overview.test.ts | 22 +- src/commands/models/list.configured.test.ts | 2 +- .../list.list-command.forward-compat.test.ts | 113 +++--- src/commands/models/list.probe.test.ts | 20 +- src/commands/models/list.rows.test.ts | 2 +- src/commands/models/list.status-command.ts | 2 +- src/commands/models/list.status.test.ts | 100 +++-- src/commands/models/list.table.test.ts | 4 +- .../oauth-tls-preflight.doctor.test.ts | 4 +- src/commands/oauth-tls-preflight.test.ts | 22 ++ src/commands/oauth-tls-preflight.ts | 2 +- src/commands/onboard-auth.test.ts | 24 +- .../local/auth-choice.test.ts | 31 -- .../sessions.model-resolution.test.ts | 7 +- src/commands/status-all/gateway.test.ts | 4 +- src/commands/status-all/gateway.ts | 4 +- src/commands/status.summary.runtime.test.ts | 8 +- src/commands/status.summary.test.ts | 6 +- src/commitments/runtime.test.ts | 6 +- ...ndled-channel-config-metadata.generated.ts | 2 +- .../config.model-ref-validation.test.ts | 18 +- src/config/config.secrets-schema.test.ts | 6 +- src/config/plugin-auto-enable.core.test.ts | 14 +- src/config/sessions/sessions.test.ts | 4 +- src/config/types.models.ts | 2 +- src/config/validation.ts | 77 +++- src/config/zod-schema.core.ts | 2 +- src/config/zod-schema.models.test.ts | 33 ++ .../run-fallback-policy.test.ts | 10 +- .../run.meta-error-status.test.ts | 2 +- .../run.payload-fallbacks.test.ts | 6 +- .../isolated-agent/run.skill-filter.test.ts | 8 +- src/cron/isolated-agent/run.ts | 2 +- src/flows/doctor-core-checks.runtime.test.ts | 68 +++- src/flows/doctor-core-checks.runtime.ts | 13 +- src/flows/model-picker.ts | 2 +- .../gateway-models.profiles.live.test.ts | 32 +- .../server-methods/models-auth-status.test.ts | 54 +-- src/gateway/server-methods/models.test.ts | 12 +- .../server-methods/server-methods.test.ts | 4 +- .../server-startup-config.recovery.test.ts | 4 +- src/gateway/server-startup-log.test.ts | 16 +- src/gateway/server-startup.test.ts | 4 +- .../server.sessions.list-changed.test.ts | 6 +- src/gateway/session-utils.search.test.ts | 8 +- src/gateway/session-utils.test.ts | 38 +- src/gateway/sessions-patch.test.ts | 6 +- src/hooks/llm-slug-generator.test.ts | 4 +- src/infra/errors.test.ts | 2 +- ...at-runner.response-prefix-template.test.ts | 4 +- ...rovider-usage.auth.normalizes-keys.test.ts | 45 +++ src/infra/provider-usage.auth.ts | 36 +- src/infra/provider-usage.fetch.codex.test.ts | 2 +- src/infra/provider-usage.fetch.codex.ts | 14 +- src/infra/provider-usage.fetch.gemini.test.ts | 4 +- src/infra/provider-usage.fetch.shared.test.ts | 4 +- src/infra/provider-usage.format.test.ts | 2 +- src/infra/provider-usage.load.test.ts | 12 +- src/infra/provider-usage.shared.test.ts | 4 +- src/infra/provider-usage.shared.ts | 13 +- src/infra/provider-usage.test.ts | 2 +- src/infra/provider-usage.types.ts | 2 +- src/llm/providers/azure-openai-responses.ts | 2 +- ...st.ts => openai-chatgpt-responses.test.ts} | 8 +- ...sponses.ts => openai-chatgpt-responses.ts} | 24 +- src/llm/providers/openai-completions.ts | 2 +- src/llm/providers/openai-responses.ts | 2 +- src/llm/providers/register-builtins.ts | 21 +- .../providers/stream-wrappers/openai.test.ts | 22 +- src/llm/providers/stream-wrappers/openai.ts | 18 +- src/llm/utils/oauth/index.ts | 4 +- ...nai-codex-jwt.ts => openai-chatgpt-jwt.ts} | 0 ...i-codex.test.ts => openai-chatgpt.test.ts} | 8 +- .../{openai-codex.ts => openai-chatgpt.ts} | 3 +- src/media-generation/runtime-shared.test.ts | 4 +- src/media-understanding/defaults.test.ts | 17 +- src/media-understanding/image.test.ts | 10 +- src/media-understanding/image.ts | 2 +- .../runner.auto-audio.test.ts | 10 +- src/model-catalog/manifest-planner.test.ts | 2 +- src/plugin-sdk/provider-auth-login.runtime.ts | 2 +- src/plugin-sdk/provider-auth.test.ts | 22 +- src/plugin-sdk/provider-auth.ts | 3 +- ...s => provider-openai-chatgpt-auth.test.ts} | 0 ...uth.ts => provider-openai-chatgpt-auth.ts} | 0 src/plugin-sdk/provider-tools.test.ts | 12 +- src/plugin-sdk/provider-tools.ts | 6 +- ...ema-normalization-runtime-contract.test.ts | 12 +- .../agents/auth-profile-runtime-contract.ts | 4 +- .../outcome-fallback-runtime-contract.ts | 2 +- .../agents/prompt-overlay-runtime-contract.ts | 2 +- .../schema-normalization-runtime-contract.ts | 4 +- .../test-helpers/provider-auth-contract.ts | 4 +- .../test-helpers/provider-runtime-contract.ts | 46 +-- .../test-helpers/public-artifacts.ts | 2 +- src/plugins/activation-planner.test.ts | 6 +- src/plugins/channel-plugin-ids.test.ts | 2 +- src/plugins/commands.test.ts | 6 +- src/plugins/config-state.test.ts | 6 +- src/plugins/config-state.ts | 1 - src/plugins/contracts/registry.retry.test.ts | 4 +- ...octor-contract-registry.load-paths.test.ts | 6 +- src/plugins/doctor-contract-registry.test.ts | 2 +- .../document-extractors.runtime.test.ts | 2 +- src/plugins/gateway-startup-plugin-ids.ts | 2 +- src/plugins/hook-types.ts | 2 +- src/plugins/installed-plugin-index.test.ts | 2 +- src/plugins/manifest-registry.test.ts | 22 +- src/plugins/manifest.json5-tolerance.test.ts | 4 +- src/plugins/plugin-lookup-table.test.ts | 6 +- src/plugins/plugin-registry.test.ts | 2 +- ...s => provider-openai-chatgpt-oauth-tls.ts} | 4 +- ... => provider-openai-chatgpt-oauth.test.ts} | 6 +- ...th.ts => provider-openai-chatgpt-oauth.ts} | 0 src/plugins/provider-public-artifacts.test.ts | 18 +- src/plugins/provider-replay-helpers.ts | 4 +- src/plugins/provider-runtime.test-support.ts | 4 +- src/plugins/provider-runtime.test.ts | 10 +- src/plugins/providers.test.ts | 20 +- .../runtime/runtime-llm.runtime.test.ts | 26 +- src/plugins/setup-registry.test.ts | 8 +- src/scripts/test-projects.test.ts | 4 +- src/secrets/apply.test.ts | 20 +- src/secrets/provider-env-vars.dynamic.test.ts | 4 +- src/secrets/provider-env-vars.test.ts | 2 +- src/secrets/provider-env-vars.ts | 3 +- src/secrets/provider-integrations.test.ts | 2 +- src/secrets/runtime.fast-path.test.ts | 2 +- src/security/audit-model-hygiene.test.ts | 4 +- src/status/status-message.ts | 2 +- src/status/status-text.ts | 27 +- src/tui/tui-command-handlers.test.ts | 10 +- src/tui/tui-event-handlers.test.ts | 6 +- src/tui/tui.ts | 2 +- src/wizard/setup.test.ts | 16 +- .../transport-params-runtime-contract.ts | 4 +- .../trigger-handling-test-harness.ts | 2 +- .../package-acceptance-workflow.test.ts | 2 +- test/vitest-scoped-config.test.ts | 2 +- ui/src/ui/chat-model-ref.test.ts | 4 +- .../chat/slash-command-executor.node.test.ts | 4 +- ui/src/ui/controllers/chat.test.ts | 6 +- ui/src/ui/views/agents-utils.test.ts | 4 +- ui/src/ui/views/chat.test.ts | 8 +- ui/src/ui/views/overview.render.test.ts | 2 +- ui/src/ui/views/sessions.test.ts | 2 +- 519 files changed, 4698 insertions(+), 5424 deletions(-) rename extensions/openai/{openai-codex-auth-identity.test.ts => openai-chatgpt-auth-identity.test.ts} (97%) rename extensions/openai/{openai-codex-auth-identity.ts => openai-chatgpt-auth-identity.ts} (97%) rename extensions/openai/{openai-codex-catalog.ts => openai-chatgpt-catalog.ts} (90%) rename extensions/openai/{openai-codex-device-code.test.ts => openai-chatgpt-device-code.test.ts} (98%) rename extensions/openai/{openai-codex-device-code.ts => openai-chatgpt-device-code.ts} (98%) rename extensions/openai/{openai-codex-oauth-abort.runtime.ts => openai-chatgpt-oauth-abort.runtime.ts} (100%) rename extensions/openai/{openai-codex-oauth-flow.runtime.test.ts => openai-chatgpt-oauth-flow.runtime.test.ts} (96%) rename extensions/openai/{openai-codex-oauth-flow.runtime.ts => openai-chatgpt-oauth-flow.runtime.ts} (97%) rename extensions/openai/{openai-codex-oauth-page.runtime.ts => openai-chatgpt-oauth-page.runtime.ts} (100%) rename extensions/openai/{openai-codex-oauth-types.runtime.ts => openai-chatgpt-oauth-types.runtime.ts} (100%) rename extensions/openai/{openai-codex-oauth.runtime.test.ts => openai-chatgpt-oauth.runtime.test.ts} (92%) rename extensions/openai/{openai-codex-oauth.runtime.ts => openai-chatgpt-oauth.runtime.ts} (98%) rename extensions/openai/{openai-codex-pkce.runtime.ts => openai-chatgpt-pkce.runtime.ts} (100%) rename extensions/openai/{openai-codex-provider.runtime.ts => openai-chatgpt-provider.runtime.ts} (88%) rename extensions/openai/{openai-codex-provider.test.ts => openai-chatgpt-provider.test.ts} (87%) rename extensions/openai/{openai-codex-provider.ts => openai-chatgpt-provider.ts} (97%) rename extensions/openai/{openai-codex-shared.ts => openai-chatgpt-shared.ts} (100%) create mode 100644 src/agents/auth-profiles/oauth-refresh-failure.test.ts delete mode 100644 src/agents/auth-profiles/store.sidecar-runtime-defaults.test.ts rename src/agents/{openai-codex-routing.test.ts => openai-routing.test.ts} (89%) rename src/agents/{openai-codex-routing.ts => openai-routing.ts} (74%) rename src/{agents/auth-profiles => commands/doctor/shared}/legacy-oauth-sidecar.test.ts (95%) rename src/{agents/auth-profiles => commands/doctor/shared}/legacy-oauth-sidecar.ts (97%) rename src/llm/providers/{openai-codex-responses.test.ts => openai-chatgpt-responses.test.ts} (98%) rename src/llm/providers/{openai-codex-responses.ts => openai-chatgpt-responses.ts} (98%) rename src/llm/utils/oauth/{openai-codex-jwt.ts => openai-chatgpt-jwt.ts} (100%) rename src/llm/utils/oauth/{openai-codex.test.ts => openai-chatgpt.test.ts} (97%) rename src/llm/utils/oauth/{openai-codex.ts => openai-chatgpt.ts} (97%) rename src/plugin-sdk/{provider-openai-codex-auth.test.ts => provider-openai-chatgpt-auth.test.ts} (100%) rename src/plugin-sdk/{provider-openai-codex-auth.ts => provider-openai-chatgpt-auth.ts} (100%) rename src/plugins/{provider-openai-codex-oauth-tls.ts => provider-openai-chatgpt-oauth-tls.ts} (96%) rename src/plugins/{provider-openai-codex-oauth.test.ts => provider-openai-chatgpt-oauth.test.ts} (96%) rename src/plugins/{provider-openai-codex-oauth.ts => provider-openai-chatgpt-oauth.ts} (100%) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt index e105826a8ddd..7ec9a557b136 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt @@ -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 diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/ProvidersModelsScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/ProvidersModelsScreen.kt index ade0e34792fe..fa7f78aef64c 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/ProvidersModelsScreen.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/ProvidersModelsScreen.kt @@ -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 } diff --git a/apps/macos/Tests/OpenClawIPCTests/MenuSessionsInjectorTests.swift b/apps/macos/Tests/OpenClawIPCTests/MenuSessionsInjectorTests.swift index 56e03e7c627a..4bca8159ac7a 100644 --- a/apps/macos/Tests/OpenClawIPCTests/MenuSessionsInjectorTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/MenuSessionsInjectorTests.swift @@ -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, diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift index 91b2223cf939..a100d602bf8d 100644 --- a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift @@ -2192,7 +2192,7 @@ extension TestChatTransportState { path: nil, count: 1, defaults: OpenClawChatSessionsDefaults( - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", contextTokens: nil, thinkingLevels: [ diff --git a/docs/cli/migrate.md b/docs/cli/migrate.md index 1b2fd8668bd0..d80539d972f7 100644 --- a/docs/cli/migrate.md +++ b/docs/cli/migrate.md @@ -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//`. - 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 diff --git a/docs/cli/models.md b/docs/cli/models.md index dbfff69a025c..b6ec4e9bdb60 100644 --- a/docs/cli/models.md +++ b/docs/cli/models.md @@ -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: diff --git a/docs/concepts/agent-runtimes.md b/docs/concepts/agent-runtimes.md index da3f6fe0e65f..4fe591de50f0 100644 --- a/docs/concepts/agent-runtimes.md +++ b/docs/concepts/agent-runtimes.md @@ -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/` 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/` 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 diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index 5a1563e3f047..021210d06ec4 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -30,7 +30,7 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram) OpenAI-family routes are prefix-specific: - `openai/` uses the native Codex app-server harness for agent turns by default. This is the usual ChatGPT/Codex subscription setup. - - `openai-codex/` is legacy config that doctor rewrites to `openai/`. + - legacy Codex model refs are legacy config that doctor rewrites to `openai/`. - `openai/` 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 { diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index 397d1984ac8d..7cd3920fdd9e 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -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. diff --git a/docs/help/faq-first-run.md b/docs/help/faq-first-run.md index 700a2143e588..9920c0b68773 100644 --- a/docs/help/faq-first-run.md +++ b/docs/help/faq-first-run.md @@ -596,28 +596,28 @@ and troubleshooting see the main [FAQ](/help/faq). 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). - + `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. diff --git a/docs/install/migrating-hermes.md b/docs/install/migrating-hermes.md index 93bd69199786..2f91862f10b3 100644 --- a/docs/install/migrating-hermes.md +++ b/docs/install/migrating-hermes.md @@ -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//` are copied, along with per-skill config values from `skills.config`. - 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. @@ -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. diff --git a/docs/plugins/codex-harness.md b/docs/plugins/codex-harness.md index 064eb6b71926..b2a358582b28 100644 --- a/docs/plugins/codex-harness.md +++ b/docs/plugins/codex-harness.md @@ -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. diff --git a/docs/plugins/sdk-agent-harness.md b/docs/plugins/sdk-agent-harness.md index 924605b5314a..65f508c6d9a2 100644 --- a/docs/plugins/sdk-agent-harness.md +++ b/docs/plugins/sdk-agent-harness.md @@ -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. diff --git a/docs/providers/openai.md b/docs/providers/openai.md index 0bc8516d5379..e395df1cab19 100644 --- a/docs/providers/openai.md +++ b/docs/providers/openai.md @@ -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. 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. @@ -85,7 +85,7 @@ explicit runtime config. | ------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | Chat / Responses | `openai/` model provider | Yes | | Codex subscription models | `openai/` with OpenAI OAuth | Yes | -| Legacy Codex model refs | `openai-codex/` or `codex-cli/` | Repaired by doctor to `openai/` | +| Legacy Codex model refs | legacy Codex model refs or `codex-cli/` | Repaired by doctor to `openai/` | | Codex app-server harness | `openai/` 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. ### 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 | 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. - 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. ### Config example @@ -345,7 +345,7 @@ Choose your preferred auth method and follow the setup steps. openclaw models auth list --agent --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. diff --git a/docs/reference/wizard.md b/docs/reference/wizard.md index ffd019c331ba..f385567bc2bb 100644 --- a/docs/reference/wizard.md +++ b/docs/reference/wizard.md @@ -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. diff --git a/docs/start/wizard-cli-reference.md b/docs/start/wizard-cli-reference.md index dbb27c42a033..29ee25327dc4 100644 --- a/docs/start/wizard-cli-reference.md +++ b/docs/start/wizard-cli-reference.md @@ -150,7 +150,7 @@ What you set: 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. diff --git a/docs/tools/acp-agents.md b/docs/tools/acp-agents.md index 9b614bbcdf26..3eda981e8dbc 100644 --- a/docs/tools/acp-agents.md +++ b/docs/tools/acp-agents.md @@ -183,7 +183,7 @@ Quick `/acp` flow from chat: - - `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. diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index 621bc836c7f4..fbc6354b147f 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -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. diff --git a/docs/tools/web.md b/docs/tools/web.md index 330ebef375b4..9a958c416038 100644 --- a/docs/tools/web.md +++ b/docs/tools/web.md @@ -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 diff --git a/extensions/acpx/src/runtime.test.ts b/extensions/acpx/src/runtime.test.ts index d6700b09e188..1b30734a9faa 100644 --- a/extensions/acpx/src/runtime.test.ts +++ b/extensions/acpx/src/runtime.test.ts @@ -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, { diff --git a/extensions/acpx/src/runtime.ts b/extensions/acpx/src/runtime.ts index b8f1211f64d3..fdbafbc7ec75 100644 --- a/extensions/acpx/src/runtime.ts +++ b/extensions/acpx/src/runtime.ts @@ -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([ ["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/ or /.`, + `Codex ACP model "${rawModel}" is not supported. Use openai/ or /.`, ); } @@ -498,7 +498,7 @@ function normalizeCodexAcpModelOverride( if (parts.length > 2) { failUnsupportedCodexAcpModel( raw, - `Codex ACP model "${raw}" is not supported. Use openai-codex/ or /.`, + `Codex ACP model "${raw}" is not supported. Use openai/ or /.`, ); } 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/ or /.`, + `Codex ACP model "${raw}" is not supported. Use openai/ or /.`, ); } const reasoningEffort = thinkingReasoningEffort ?? modelReasoningEffort; diff --git a/extensions/active-memory/index.test.ts b/extensions/active-memory/index.test.ts index f37692e92f74..6eb6e5ec31fa 100644 --- a/extensions/active-memory/index.test.ts +++ b/extensions/active-memory/index.test.ts @@ -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"); }); diff --git a/extensions/anthropic/index.test.ts b/extensions/anthropic/index.test.ts index 8f2d769ead06..18d010571f99 100644 --- a/extensions/anthropic/index.test.ts +++ b/extensions/anthropic/index.test.ts @@ -164,7 +164,7 @@ describe("anthropic provider replay hooks", () => { expect( provider.normalizeConfig?.({ - provider: "openai-codex", + provider: "openai", providerConfig, } as never), ).toBe(providerConfig); diff --git a/extensions/anthropic/provider-policy-api.test.ts b/extensions/anthropic/provider-policy-api.test.ts index be5dc1990a8d..a02639cc12db 100644 --- a/extensions/anthropic/provider-policy-api.test.ts +++ b/extensions/anthropic/provider-policy-api.test.ts @@ -69,7 +69,7 @@ describe("anthropic provider policy public artifact", () => { expect( normalizeConfig({ - provider: "openai-codex", + provider: "openai", providerConfig, }), ).toBe(providerConfig); diff --git a/extensions/codex/harness.test.ts b/extensions/codex/harness.test.ts index 3ad894a0e24b..441f59af7d36 100644 --- a/extensions/codex/harness.test.ts +++ b/extensions/codex/harness.test.ts @@ -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, }); diff --git a/extensions/codex/harness.ts b/extensions/codex/harness.ts index 8c36ea70a8fe..9486a5db29d2 100644 --- a/extensions/codex/harness.ts +++ b/extensions/codex/harness.ts @@ -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", diff --git a/extensions/codex/index.test.ts b/extensions/codex/index.test.ts index da0fdb298bfe..9a8bce0c2513 100644 --- a/extensions/codex/index.test.ts +++ b/extensions/codex/index.test.ts @@ -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", }); diff --git a/extensions/codex/provider-catalog.ts b/extensions/codex/provider-catalog.ts index b24f134befa4..7b36f5b286d1 100644 --- a/extensions/codex/provider-catalog.ts +++ b/extensions/codex/provider-catalog.ts @@ -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), }; } diff --git a/extensions/codex/provider.test.ts b/extensions/codex/provider.test.ts index e634b8f05836..0e9d16032f90 100644 --- a/extensions/codex/provider.test.ts +++ b/extensions/codex/provider.test.ts @@ -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"], }); diff --git a/extensions/codex/src/app-server/attempt-diagnostics.test.ts b/extensions/codex/src/app-server/attempt-diagnostics.test.ts index 3570863bf2a3..5d7f2945c522 100644 --- a/extensions/codex/src/app-server/attempt-diagnostics.test.ts +++ b/extensions/codex/src/app-server/attempt-diagnostics.test.ts @@ -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", }), diff --git a/extensions/codex/src/app-server/auth-bridge.test.ts b/extensions/codex/src/app-server/auth-bridge.test.ts index 871abdae6ef3..bb8a46d54c05 100644 --- a/extensions/codex/src/app-server/auth-bridge.test.ts +++ b/extensions/codex/src/app-server/auth-bridge.test.ts @@ -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", { diff --git a/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts b/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts index cf0f22968d4c..f60048f753ed 100644 --- a/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts @@ -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); diff --git a/extensions/codex/src/app-server/compact.test.ts b/extensions/codex/src/app-server/compact.test.ts index 55dc476c6069..2b0e53bcf0a9 100644 --- a/extensions/codex/src/app-server/compact.test.ts +++ b/extensions/codex/src/app-server/compact.test.ts @@ -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({ diff --git a/extensions/codex/src/app-server/dynamic-tool-build.test.ts b/extensions/codex/src/app-server/dynamic-tool-build.test.ts index 935851351e85..c8a2fdecfa40 100644 --- a/extensions/codex/src/app-server/dynamic-tool-build.test.ts +++ b/extensions/codex/src/app-server/dynamic-tool-build.test.ts @@ -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", diff --git a/extensions/codex/src/app-server/event-projector.test.ts b/extensions/codex/src/app-server/event-projector.test.ts index 340a44e3f90a..6ccaae095527 100644 --- a/extensions/codex/src/app-server/event-projector.test.ts +++ b/extensions/codex/src/app-server/event-projector.test.ts @@ -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 { 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", diff --git a/extensions/codex/src/app-server/event-projector.ts b/extensions/codex/src/app-server/event-projector.ts index 4ad13d8076d6..b85fe424ab27 100644 --- a/extensions/codex/src/app-server/event-projector.ts +++ b/extensions/codex/src/app-server/event-projector.ts @@ -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, diff --git a/extensions/codex/src/app-server/local-runtime-attribution.ts b/extensions/codex/src/app-server/local-runtime-attribution.ts index 2784b06619d9..6b9c5d893923 100644 --- a/extensions/codex/src/app-server/local-runtime-attribution.ts +++ b/extensions/codex/src/app-server/local-runtime-attribution.ts @@ -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; diff --git a/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts b/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts index 561c005734ff..a3cce135a028 100644 --- a/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts @@ -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({ diff --git a/extensions/codex/src/app-server/run-attempt-test-harness.ts b/extensions/codex/src/app-server/run-attempt-test-harness.ts index fab87c98f522..d58890c0d4d4 100644 --- a/extensions/codex/src/app-server/run-attempt-test-harness.ts +++ b/extensions/codex/src/app-server/run-attempt-test-harness.ts @@ -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, diff --git a/extensions/codex/src/app-server/run-attempt.context-engine.test.ts b/extensions/codex/src/app-server/run-attempt.context-engine.test.ts index bc2150b18679..d62eadcb7f3e 100644 --- a/extensions/codex/src/app-server/run-attempt.context-engine.test.ts +++ b/extensions/codex/src/app-server/run-attempt.context-engine.test.ts @@ -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, diff --git a/extensions/codex/src/app-server/run-attempt.test.ts b/extensions/codex/src/app-server/run-attempt.test.ts index 4a554d601146..195b5c652b71 100644 --- a/extensions/codex/src/app-server/run-attempt.test.ts +++ b/extensions/codex/src/app-server/run-attempt.test.ts @@ -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 = []; @@ -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"); }); diff --git a/extensions/codex/src/app-server/run-attempt.usage-limits.test.ts b/extensions/codex/src/app-server/run-attempt.usage-limits.test.ts index de5360540f24..908502f21de1 100644 --- a/extensions/codex/src/app-server/run-attempt.usage-limits.test.ts +++ b/extensions/codex/src/app-server/run-attempt.usage-limits.test.ts @@ -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 } = {}; 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, diff --git a/extensions/codex/src/app-server/session-binding.test.ts b/extensions/codex/src/app-server/session-binding.test.ts index c799772b6e96..0e427972c558 100644 --- a/extensions/codex/src/app-server/session-binding.test.ts +++ b/extensions/codex/src/app-server/session-binding.test.ts @@ -19,7 +19,7 @@ const nativeAuthLookup: Pick { { 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(); }); diff --git a/extensions/codex/src/app-server/shared-client.test.ts b/extensions/codex/src/app-server/shared-client.test.ts index c8464b38b7b2..ee64d8476cc9 100644 --- a/extensions/codex/src/app-server/shared-client.test.ts +++ b/extensions/codex/src/app-server/shared-client.test.ts @@ -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 () => { diff --git a/extensions/codex/src/app-server/side-question.test.ts b/extensions/codex/src/app-server/side-question.test.ts index 898cf424a919..60435ae6846d 100644 --- a/extensions/codex/src/app-server/side-question.test.ts +++ b/extensions/codex/src/app-server/side-question.test.ts @@ -308,7 +308,7 @@ function sideParams(overrides: Partial[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: {}, }); }); diff --git a/extensions/codex/src/app-server/test-support.ts b/extensions/codex/src/app-server/test-support.ts index 531b496c9774..dc67837f0643 100644 --- a/extensions/codex/src/app-server/test-support.ts +++ b/extensions/codex/src/app-server/test-support.ts @@ -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 }, diff --git a/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts b/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts index 835360ff05f9..b3b31da46ed5 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.binding.test.ts @@ -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"); }); }); diff --git a/extensions/codex/src/app-server/thread-lifecycle.test.ts b/extensions/codex/src/app-server/thread-lifecycle.test.ts index 8e674d7ded46..a0c6dceb2692 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.test.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.test.ts @@ -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", }), { diff --git a/extensions/codex/src/app-server/thread-lifecycle.ts b/extensions/codex/src/app-server/thread-lifecycle.ts index 0708d1b85573..ba3b1512e927 100644 --- a/extensions/codex/src/app-server/thread-lifecycle.ts +++ b/extensions/codex/src/app-server/thread-lifecycle.ts @@ -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 diff --git a/extensions/codex/src/app-server/trajectory.test.ts b/extensions/codex/src/app-server/trajectory.test.ts index 9fe57e3af06b..d98b632e741f 100644 --- a/extensions/codex/src/app-server/trajectory.test.ts +++ b/extensions/codex/src/app-server/trajectory.test.ts @@ -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"); }); diff --git a/extensions/codex/src/commands.test.ts b/extensions/codex/src/commands.test.ts index 0e9bfd61b9a8..47f3edc1024a 100644 --- a/extensions/codex/src/commands.test.ts +++ b/extensions/codex/src/commands.test.ts @@ -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", diff --git a/extensions/codex/src/conversation-binding.test.ts b/extensions/codex/src/conversation-binding.test.ts index eabb8dbc90a6..48619b52701a 100644 --- a/extensions/codex/src/conversation-binding.test.ts +++ b/extensions/codex/src/conversation-binding.test.ts @@ -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 }> = []; 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) => { 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[] = []; diff --git a/extensions/codex/src/conversation-binding.ts b/extensions/codex/src/conversation-binding.ts index 3619da1222b4..93d8d7337461 100644 --- a/extensions/codex/src/conversation-binding.ts +++ b/extensions/codex/src/conversation-binding.ts @@ -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: { diff --git a/extensions/codex/src/conversation-control.test.ts b/extensions/codex/src/conversation-control.test.ts index 902bbe856191..2531fdc49f25 100644 --- a/extensions/codex/src/conversation-control.test.ts +++ b/extensions/codex/src/conversation-control.test.ts @@ -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, diff --git a/extensions/discord/src/config-schema.test.ts b/extensions/discord/src/config-schema.test.ts index 65804672f0bf..7682ddfe1bba 100644 --- a/extensions/discord/src/config-schema.test.ts +++ b/extensions/discord/src/config-schema.test.ts @@ -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"); diff --git a/extensions/discord/src/config-ui-hints.ts b/extensions/discord/src/config-ui-hints.ts index bddf84a0c3c9..c1795412d891 100644 --- a/extensions/discord/src/config-ui-hints.ts +++ b/extensions/discord/src/config-ui-hints.ts @@ -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", diff --git a/extensions/discord/src/monitor/native-command.model-picker.test.ts b/extensions/discord/src/monitor/native-command.model-picker.test.ts index ec9377b5b9df..f834a88c43c5 100644 --- a/extensions/discord/src/monitor/native-command.model-picker.test.ts +++ b/extensions/discord/src/monitor/native-command.model-picker.test.ts @@ -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"); }); diff --git a/extensions/discord/src/monitor/native-command.plugin-dispatch.test.ts b/extensions/discord/src/monitor/native-command.plugin-dispatch.test.ts index e95f3a8b7d86..74c72c8ed39f 100644 --- a/extensions/discord/src/monitor/native-command.plugin-dispatch.test.ts +++ b/extensions/discord/src/monitor/native-command.plugin-dispatch.test.ts @@ -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({ diff --git a/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts b/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts index 92423803e2d1..157bdce77719 100644 --- a/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts +++ b/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts @@ -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", }); diff --git a/extensions/discord/src/voice/manager.e2e.test.ts b/extensions/discord/src/voice/manager.e2e.test.ts index f77a31baeb0a..607b353fa533 100644 --- a/extensions/discord/src/voice/manager.e2e.test.ts +++ b/extensions/discord/src/voice/manager.e2e.test.ts @@ -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", diff --git a/extensions/llm-task/openclaw.plugin.json b/extensions/llm-task/openclaw.plugin.json index 14e5153748ff..6e38db353577 100644 --- a/extensions/llm-task/openclaw.plugin.json +++ b/extensions/llm-task/openclaw.plugin.json @@ -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", diff --git a/extensions/migrate-hermes/auth.ts b/extensions/migrate-hermes/auth.ts index 945a4fad4372..06cff0a95472 100644 --- a/extensions/migrate-hermes/auth.ts +++ b/extensions/migrate-hermes/auth.ts @@ -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, - 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, - 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 { - 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 { @@ -203,6 +116,48 @@ async function readOpenCodeOpenAICandidates( ]; } +async function hasLegacyHermesAuthJson(authPath: string | undefined): Promise { + 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 { - 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(); 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 { + 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( diff --git a/extensions/migrate-hermes/files-and-skills.test.ts b/extensions/migrate-hermes/files-and-skills.test.ts index 80e0aa6a8b42..1561484caf9a 100644 --- a/extensions/migrate-hermes/files-and-skills.test.ts +++ b/extensions/migrate-hermes/files-and-skills.test.ts @@ -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(); + }); }); diff --git a/extensions/migrate-hermes/provider.secret-failure.test.ts b/extensions/migrate-hermes/provider.secret-failure.test.ts index 56a94f749f41..7eed2b579177 100644 --- a/extensions/migrate-hermes/provider.secret-failure.test.ts +++ b/extensions/migrate-hermes/provider.secret-failure.test.ts @@ -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, diff --git a/extensions/migrate-hermes/secrets.test.ts b/extensions/migrate-hermes/secrets.test.ts index 3a369c16bc1e..f1a35b0e8d30 100644 --- a/extensions/migrate-hermes/secrets.test.ts +++ b/extensions/migrate-hermes/secrets.test.ts @@ -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; - }; - 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", }, }), ); diff --git a/extensions/openai/api.ts b/extensions/openai/api.ts index de2f5a21cbbb..dcad31fbdf0f 100644 --- a/extensions/openai/api.ts +++ b/extensions/openai/api.ts @@ -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"; diff --git a/extensions/openai/image-generation-provider.test.ts b/extensions/openai/image-generation-provider.test.ts index 2e959e6a4054..791e56d11272 100644 --- a/extensions/openai/image-generation-provider.test.ts +++ b/extensions/openai/image-generation-provider.test.ts @@ -18,10 +18,12 @@ const { (params: { provider: string; agentDir?: string }) => boolean >(() => false), listProfilesForProviderMock: vi.fn( - (store: { profiles?: Record }, provider: string) => - Object.entries(store.profiles ?? {}) - .filter(([, profile]) => profile.provider === provider) - .map(([profileId]) => profileId), + (store: { profiles?: Record }, 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; @@ -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"); }); diff --git a/extensions/openai/image-generation-provider.ts b/extensions/openai/image-generation-provider.ts index dc60a121d494..e28fdcb9e217 100644 --- a/extensions/openai/image-generation-provider.ts +++ b/extensions/openai/image-generation-provider.ts @@ -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"); diff --git a/extensions/openai/index.test.ts b/extensions/openai/index.test.ts index 7cdab0be348a..4044e66c5c2f 100644 --- a/extensions/openai/index.test.ts +++ b/extensions/openai/index.test.ts @@ -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, diff --git a/extensions/openai/openai-codex-auth-identity.test.ts b/extensions/openai/openai-chatgpt-auth-identity.test.ts similarity index 97% rename from extensions/openai/openai-codex-auth-identity.test.ts rename to extensions/openai/openai-chatgpt-auth-identity.test.ts index 0b9bdb3a86c1..12290d8cfe77 100644 --- a/extensions/openai/openai-codex-auth-identity.test.ts +++ b/extensions/openai/openai-chatgpt-auth-identity.test.ts @@ -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 { const header = Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" })).toString("base64url"); diff --git a/extensions/openai/openai-codex-auth-identity.ts b/extensions/openai/openai-chatgpt-auth-identity.ts similarity index 97% rename from extensions/openai/openai-codex-auth-identity.ts rename to extensions/openai/openai-chatgpt-auth-identity.ts index 87fb21bd8a69..e5e0c510b293 100644 --- a/extensions/openai/openai-codex-auth-identity.ts +++ b/extensions/openai/openai-chatgpt-auth-identity.ts @@ -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; diff --git a/extensions/openai/openai-codex-catalog.ts b/extensions/openai/openai-chatgpt-catalog.ts similarity index 90% rename from extensions/openai/openai-codex-catalog.ts rename to extensions/openai/openai-chatgpt-catalog.ts index 96244925989d..a88b6c2d47ed 100644 --- a/extensions/openai/openai-codex-catalog.ts +++ b/extensions/openai/openai-chatgpt-catalog.ts @@ -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: [], }; } diff --git a/extensions/openai/openai-codex-device-code.test.ts b/extensions/openai/openai-chatgpt-device-code.test.ts similarity index 98% rename from extensions/openai/openai-codex-device-code.test.ts rename to extensions/openai/openai-chatgpt-device-code.test.ts index ff7bb40d1e6b..7d14c6091e44 100644 --- a/extensions/openai/openai-codex-device-code.test.ts +++ b/extensions/openai/openai-chatgpt-device-code.test.ts @@ -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 { const header = Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" })).toString("base64url"); diff --git a/extensions/openai/openai-codex-device-code.ts b/extensions/openai/openai-chatgpt-device-code.ts similarity index 98% rename from extensions/openai/openai-codex-device-code.ts rename to extensions/openai/openai-chatgpt-device-code.ts index 93b669ea38e9..fa7cabaf408e 100644 --- a/extensions/openai/openai-codex-device-code.ts +++ b/extensions/openai/openai-chatgpt-device-code.ts @@ -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"; diff --git a/extensions/openai/openai-codex-oauth-abort.runtime.ts b/extensions/openai/openai-chatgpt-oauth-abort.runtime.ts similarity index 100% rename from extensions/openai/openai-codex-oauth-abort.runtime.ts rename to extensions/openai/openai-chatgpt-oauth-abort.runtime.ts diff --git a/extensions/openai/openai-codex-oauth-flow.runtime.test.ts b/extensions/openai/openai-chatgpt-oauth-flow.runtime.test.ts similarity index 96% rename from extensions/openai/openai-codex-oauth-flow.runtime.test.ts rename to extensions/openai/openai-chatgpt-oauth-flow.runtime.test.ts index f2e2e198fdaa..aeee18ff5dbd 100644 --- a/extensions/openai/openai-codex-oauth-flow.runtime.test.ts +++ b/extensions/openai/openai-chatgpt-oauth-flow.runtime.test.ts @@ -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, }), ); diff --git a/extensions/openai/openai-codex-oauth-flow.runtime.ts b/extensions/openai/openai-chatgpt-oauth-flow.runtime.ts similarity index 97% rename from extensions/openai/openai-codex-oauth-flow.runtime.ts rename to extensions/openai/openai-chatgpt-oauth-flow.runtime.ts index 310d4a6594d3..eacd1bc3a5af 100644 --- a/extensions/openai/openai-codex-oauth-flow.runtime.ts +++ b/extensions/openai/openai-chatgpt-oauth-flow.runtime.ts @@ -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 { afterEach(() => { diff --git a/extensions/openai/openai-codex-oauth.runtime.ts b/extensions/openai/openai-chatgpt-oauth.runtime.ts similarity index 98% rename from extensions/openai/openai-codex-oauth.runtime.ts rename to extensions/openai/openai-chatgpt-oauth.runtime.ts index 12036c8de36e..bd84bdc93053 100644 --- a/extensions/openai/openai-codex-oauth.runtime.ts +++ b/extensions/openai/openai-chatgpt-oauth.runtime.ts @@ -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"; diff --git a/extensions/openai/openai-codex-pkce.runtime.ts b/extensions/openai/openai-chatgpt-pkce.runtime.ts similarity index 100% rename from extensions/openai/openai-codex-pkce.runtime.ts rename to extensions/openai/openai-chatgpt-pkce.runtime.ts diff --git a/extensions/openai/openai-codex-provider.runtime.ts b/extensions/openai/openai-chatgpt-provider.runtime.ts similarity index 88% rename from extensions/openai/openai-codex-provider.runtime.ts rename to extensions/openai/openai-chatgpt-provider.runtime.ts index 6e24ab134e65..3579a703a3db 100644 --- a/extensions/openai/openai-codex-provider.runtime.ts +++ b/extensions/openai/openai-chatgpt-provider.runtime.ts @@ -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, ): 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; } diff --git a/extensions/openai/openai-codex-provider.test.ts b/extensions/openai/openai-chatgpt-provider.test.ts similarity index 87% rename from extensions/openai/openai-codex-provider.test.ts rename to extensions/openai/openai-chatgpt-provider.test.ts index ec6a18671e29..41f7bb357537 100644 --- a/extensions/openai/openai-codex-provider.test.ts +++ b/extensions/openai/openai-chatgpt-provider.test.ts @@ -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", }); }); diff --git a/extensions/openai/openai-codex-provider.ts b/extensions/openai/openai-chatgpt-provider.ts similarity index 97% rename from extensions/openai/openai-codex-provider.ts rename to extensions/openai/openai-chatgpt-provider.ts index fe69952a6a8a..ccb85b5a25a3 100644 --- a/extensions/openai/openai-codex-provider.ts +++ b/extensions/openai/openai-chatgpt-provider.ts @@ -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 ({ 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; cfg?: Record; @@ -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; - }); - 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 | 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 = { diff --git a/extensions/openai/openai-provider.ts b/extensions/openai/openai-provider.ts index 279cdc4c95a8..5f01d4983307 100644 --- a/extensions/openai/openai-provider.ts +++ b/extensions/openai/openai-provider.ts @@ -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, diff --git a/extensions/openai/openclaw.plugin.json b/extensions/openai/openclaw.plugin.json index 23415355fbf8..514fe91be238 100644 --- a/extensions/openai/openclaw.plugin.json +++ b/extensions/openai/openclaw.plugin.json @@ -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, diff --git a/extensions/openai/openclaw.plugin.test.ts b/extensions/openai/openclaw.plugin.test.ts index 8003ad690923..5aba0c7c1301 100644 --- a/extensions/openai/openclaw.plugin.test.ts +++ b/extensions/openai/openclaw.plugin.test.ts @@ -29,6 +29,7 @@ const manifest = JSON.parse( setup?: { providers?: Array<{ id: string }>; }; + providerEndpoints?: Array<{ endpointClass?: string; hosts?: string[] }>; providerAuthAliases?: Record; }; @@ -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", () => { diff --git a/extensions/openai/provider-auth.contract.test.ts b/extensions/openai/provider-auth.contract.test.ts index 5e62fd02740e..d7cad26df84c 100644 --- a/extensions/openai/provider-auth.contract.test.ts +++ b/extensions/openai/provider-auth.contract.test.ts @@ -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, })); diff --git a/extensions/openai/provider-contract-api.ts b/extensions/openai/provider-contract-api.ts index ea2f46065969..b9a7e4dce7ca 100644 --- a/extensions/openai/provider-contract-api.ts +++ b/extensions/openai/provider-contract-api.ts @@ -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", diff --git a/extensions/openai/provider-policy-api.test.ts b/extensions/openai/provider-policy-api.test.ts index 21a90764dcf8..839157b03d83 100644 --- a/extensions/openai/provider-policy-api.test.ts +++ b/extensions/openai/provider-policy-api.test.ts @@ -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"); }); }); diff --git a/extensions/openai/provider-policy-api.ts b/extensions/openai/provider-policy-api.ts index 09028fd26c8b..1531260d2de5 100644 --- a/extensions/openai/provider-policy-api.ts +++ b/extensions/openai/provider-policy-api.ts @@ -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; } diff --git a/extensions/openai/realtime-transcription-provider.test.ts b/extensions/openai/realtime-transcription-provider.test.ts index 5eb736ee9ed0..ab7da104004f 100644 --- a/extensions/openai/realtime-transcription-provider.test.ts +++ b/extensions/openai/realtime-transcription-provider.test.ts @@ -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); diff --git a/extensions/openai/realtime-voice-provider.test.ts b/extensions/openai/realtime-voice-provider.test.ts index 7a050dffa872..acf147d4ea5a 100644 --- a/extensions/openai/realtime-voice-provider.test.ts +++ b/extensions/openai/realtime-voice-provider.test.ts @@ -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, }); diff --git a/extensions/openai/replay-policy.ts b/extensions/openai/replay-policy.ts index 78881a32bb53..ab4ed523c870 100644 --- a/extensions/openai/replay-policy.ts +++ b/extensions/openai/replay-policy.ts @@ -5,7 +5,7 @@ import type { const RESPONSES_FAMILY_APIS = new Set([ "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "azure-openai-responses", ]); diff --git a/extensions/openai/setup-api.test.ts b/extensions/openai/setup-api.test.ts index 7e753838e993..226987c43d56 100644 --- a/extensions/openai/setup-api.test.ts +++ b/extensions/openai/setup-api.test.ts @@ -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"); diff --git a/extensions/openai/setup-api.ts b/extensions/openai/setup-api.ts index 5003640d2ab1..5854838e571e 100644 --- a/extensions/openai/setup-api.ts +++ b/extensions/openai/setup-api.ts @@ -73,7 +73,6 @@ export function buildOpenAISetupProvider(): ProviderPlugin { return { id: "openai", - aliases: ["openai-codex"], label: "OpenAI", docsPath: "/providers/models", envVars: ["OPENAI_API_KEY"], diff --git a/extensions/openai/test-support/provider-catalog.contract-test-support.ts b/extensions/openai/test-support/provider-catalog.contract-test-support.ts index 229aa8002740..88b9ac6ab60d 100644 --- a/extensions/openai/test-support/provider-catalog.contract-test-support.ts +++ b/extensions/openai/test-support/provider-catalog.contract-test-support.ts @@ -103,7 +103,6 @@ export function describeOpenAIProviderCatalogContract() { switch (params.provider) { case "azure-openai-responses": case "openai": - case "openai-codex": return ["openai"]; default: return undefined; diff --git a/extensions/openai/thinking-policy.ts b/extensions/openai/thinking-policy.ts index 20ec70d9d771..439f52a23738 100644 --- a/extensions/openai/thinking-policy.ts +++ b/extensions/openai/thinking-policy.ts @@ -25,6 +25,11 @@ const OPENAI_CODEX_XHIGH_MODEL_IDS = [ "gpt-5.3-codex-spark", ] as const; +const OPENAI_UNIFIED_XHIGH_MODEL_IDS = [ + ...OPENAI_XHIGH_MODEL_IDS, + ...OPENAI_CODEX_XHIGH_MODEL_IDS, +] as const; + function normalizeModelId(value: string): string { return value.trim().toLowerCase(); } @@ -58,3 +63,7 @@ export function resolveOpenAIThinkingProfile(modelId: string): ProviderThinkingP export function resolveOpenAICodexThinkingProfile(modelId: string): ProviderThinkingProfile { return buildOpenAIThinkingProfile({ modelId, xhighModelIds: OPENAI_CODEX_XHIGH_MODEL_IDS }); } + +export function resolveUnifiedOpenAIThinkingProfile(modelId: string): ProviderThinkingProfile { + return buildOpenAIThinkingProfile({ modelId, xhighModelIds: OPENAI_UNIFIED_XHIGH_MODEL_IDS }); +} diff --git a/extensions/openai/transport-policy.test.ts b/extensions/openai/transport-policy.test.ts index c8c1d68c5515..2182ad65e6de 100644 --- a/extensions/openai/transport-policy.test.ts +++ b/extensions/openai/transport-policy.test.ts @@ -62,13 +62,13 @@ describe("openai transport policy", () => { it("keeps Codex request identity session-scoped while adding turn metadata", () => { const state = resolveOpenAITransportTurnState({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", model: { ...nativeModel, - provider: "openai-codex", - api: "openai-codex-responses", - baseUrl: "https://chatgpt.com/backend-api", + provider: "openai", + api: "openai-chatgpt-responses", + baseUrl: "https://chatgpt.com/backend-api/codex", }, sessionId: "session-123", turnId: "turn-123", @@ -111,13 +111,13 @@ describe("openai transport policy", () => { it("treats ChatGPT Codex backend routes as native OpenAI-family transports", () => { const policy = resolveOpenAIWebSocketSessionPolicy({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", model: { ...nativeModel, - provider: "openai-codex", - api: "openai-codex-responses", - baseUrl: "https://chatgpt.com/backend-api", + provider: "openai", + api: "openai-chatgpt-responses", + baseUrl: "https://chatgpt.com/backend-api/codex", }, sessionId: "session-123", }); diff --git a/extensions/qa-lab/src/auth-profile.fixture.ts b/extensions/qa-lab/src/auth-profile.fixture.ts index e2533e4d4d58..5abd30478442 100644 --- a/extensions/qa-lab/src/auth-profile.fixture.ts +++ b/extensions/qa-lab/src/auth-profile.fixture.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; -export const QA_CODEX_OAUTH_PROFILE_ID = "openai-codex:qa-oauth"; +export const QA_CODEX_OAUTH_PROFILE_ID = "openai:qa-oauth"; export const QA_OPENAI_API_KEY_PROFILE_ID = "openai:media-api"; export const QA_AUTH_PROFILE_STORE_VERSION = 1; @@ -16,7 +16,7 @@ export type QaApiKeyAuthProfile = { export type QaOAuthAuthProfile = { type: "oauth"; - provider: "openai-codex"; + provider: "openai"; access: string; refresh: string; expires: number; @@ -35,7 +35,7 @@ export type QaCodexAuthProfileSelection = | { status: "ready"; profileId: string; - provider: "openai-codex"; + provider: "openai"; mode: "oauth"; } | { @@ -52,7 +52,7 @@ function authProfilesPath(agentDir: string) { function buildCodexOAuthProfile(): QaOAuthAuthProfile { return { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "qa-codex-oauth-access-placeholder", refresh: "qa-codex-oauth-refresh-placeholder", expires: QA_FIXED_OAUTH_EXPIRY_MS, @@ -96,7 +96,7 @@ function isQaAuthProfile(value: unknown): value is QaAuthProfile { } const record = value as Record; return ( - (record.type === "oauth" && record.provider === "openai-codex") || + (record.type === "oauth" && record.provider === "openai") || (record.type === "api_key" && record.provider === "openai") ); } @@ -157,21 +157,21 @@ export function resolveCodexAuthProfile( .toSorted((left, right) => left.localeCompare(right)) .find((candidate) => { const profile = snapshot.profiles[candidate]; - return profile?.type === "oauth" && profile.provider === "openai-codex"; + return profile?.type === "oauth" && profile.provider === "openai"; }); if (!profileId) { return { status: "blocked", remediation: - 'Codex app-server auth requires an openai-codex OAuth profile. Run "openclaw doctor --fix" to repair Codex auth routing before retrying.', + 'Codex app-server auth requires an openai OAuth profile. Run "openclaw doctor --fix" to repair Codex auth routing before retrying.', }; } return { status: "ready", profileId, - provider: "openai-codex", + provider: "openai", mode: "oauth", }; } diff --git a/extensions/qa-lab/src/codex-plugin-lifecycle.test.ts b/extensions/qa-lab/src/codex-plugin-lifecycle.test.ts index 0fc55af4d941..a0f1d62d75c7 100644 --- a/extensions/qa-lab/src/codex-plugin-lifecycle.test.ts +++ b/extensions/qa-lab/src/codex-plugin-lifecycle.test.ts @@ -62,7 +62,7 @@ describe("codex plugin lifecycle: cold install", () => { }); describe("codex plugin lifecycle: OAuth-only with mixed profiles", () => { - it("selects openai-codex OAuth when openai API-key profiles are present", async () => { + it("selects openai OAuth when openai API-key profiles are present", async () => { const agentDir = await createAgentDir("qa-codex-auth-mixed-"); await seedAuthProfiles("mixed", agentDir); @@ -74,7 +74,7 @@ describe("codex plugin lifecycle: OAuth-only with mixed profiles", () => { } expect(selection.profileId).toBe(QA_CODEX_OAUTH_PROFILE_ID); expect(selection.profileId).not.toBe(QA_OPENAI_API_KEY_PROFILE_ID); - expect(selection.provider).toBe("openai-codex"); + expect(selection.provider).toBe("openai"); expect(selection.mode).toBe("oauth"); }); }); diff --git a/extensions/qa-lab/src/gateway-child.test.ts b/extensions/qa-lab/src/gateway-child.test.ts index b95e44aa05d2..f13c7b5e96b1 100644 --- a/extensions/qa-lab/src/gateway-child.test.ts +++ b/extensions/qa-lab/src/gateway-child.test.ts @@ -500,7 +500,7 @@ describe("buildQaRuntimeEnv", () => { } }); - it("stages the OpenAI API-key fallback for live OpenAI Codex QA workers", async () => { + it("stages the OpenAI API-key fallback for live OpenAI QA workers", async () => { const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-api-key-state-")); cleanups.push(async () => { await rm(stateDir, { recursive: true, force: true }); @@ -509,7 +509,7 @@ describe("buildQaRuntimeEnv", () => { const cfg = await testing.stageQaLiveApiKeyProfiles({ cfg: {}, stateDir, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: { OPENCLAW_LIVE_OPENAI_KEY: "qa-live-codex-fallback-key", }, @@ -517,7 +517,7 @@ describe("buildQaRuntimeEnv", () => { for (const [profileId, provider] of [ ["qa-live-openai-env", "openai"], - ["qa-live-openai-codex-env", "openai-codex"], + ["qa-live-openai-env", "openai"], ] as const) { const configProfile = requireAuthProfile(cfg.auth?.profiles, profileId); expect(configProfile.provider).toBe(provider); @@ -532,7 +532,7 @@ describe("buildQaRuntimeEnv", () => { const storeProfiles = parseAuthProfileStore(storeRaw).profiles; for (const [profileId, provider] of [ ["qa-live-openai-env", "openai"], - ["qa-live-openai-codex-env", "openai-codex"], + ["qa-live-openai-env", "openai"], ] as const) { const storeProfile = requireAuthProfile(storeProfiles, profileId); expect(storeProfile.type).toBe("api_key"); @@ -542,7 +542,7 @@ describe("buildQaRuntimeEnv", () => { } }); - it("stages direct live Codex API-key aliases for isolated QA workers", async () => { + it("stages direct live OpenAI API-key aliases for isolated QA workers", async () => { const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-direct-key-state-")); cleanups.push(async () => { await rm(stateDir, { recursive: true, force: true }); @@ -551,7 +551,7 @@ describe("buildQaRuntimeEnv", () => { const cfg = await testing.stageQaLiveApiKeyProfiles({ cfg: {}, stateDir, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: { OPENCLAW_LIVE_CODEX_API_KEY: "qa-live-direct-codex-key", }, @@ -563,16 +563,16 @@ describe("buildQaRuntimeEnv", () => { ); const storeProfile = requireAuthProfile( parseAuthProfileStore(storeRaw).profiles, - "qa-live-openai-codex-env", + "qa-live-openai-env", ); expect(storeProfile.type).toBe("api_key"); - expect(storeProfile.provider).toBe("openai-codex"); + expect(storeProfile.provider).toBe("openai"); expect(storeProfile.key).toBe("qa-live-direct-codex-key"); expect(() => testing.assertQaLiveCodexAuthAvailable({ cfg, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: { OPENCLAW_LIVE_CODEX_API_KEY: "qa-live-direct-codex-key", }, @@ -581,11 +581,11 @@ describe("buildQaRuntimeEnv", () => { ).not.toThrow(); }); - it("fails fast when live OpenAI Codex runs have no portable QA auth", () => { + it("fails fast when live OpenAI runs have no portable QA auth", () => { expect(() => testing.assertQaLiveCodexAuthAvailable({ cfg: {}, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: { CODEX_HOME: path.join(os.tmpdir(), "missing-openclaw-codex-home"), }, @@ -657,7 +657,7 @@ describe("buildQaRuntimeEnv", () => { ).not.toThrow(); }); - it("stages configured OpenAI Codex API keys for live QA runs", async () => { + it("stages configured OpenAI API keys for live QA runs", async () => { const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-config-key-state-")); cleanups.push(async () => { await rm(stateDir, { recursive: true, force: true }); @@ -666,7 +666,7 @@ describe("buildQaRuntimeEnv", () => { cfg: { models: { providers: { - "openai-codex": { + openai: { baseUrl: "", models: [], apiKey: "qa-configured-not-a-real-key", @@ -675,12 +675,12 @@ describe("buildQaRuntimeEnv", () => { }, }, stateDir, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: {}, }); - const configProfile = requireAuthProfile(cfg.auth?.profiles, "qa-live-openai-codex-env"); - expect(configProfile.provider).toBe("openai-codex"); + const configProfile = requireAuthProfile(cfg.auth?.profiles, "qa-live-openai-env"); + expect(configProfile.provider).toBe("openai"); expect(configProfile.mode).toBe("api_key"); for (const agentId of ["main", "qa"]) { const storeRaw = await readFile( @@ -689,24 +689,24 @@ describe("buildQaRuntimeEnv", () => { ); const storeProfile = requireAuthProfile( parseAuthProfileStore(storeRaw).profiles, - "qa-live-openai-codex-env", + "qa-live-openai-env", ); expect(storeProfile.type).toBe("api_key"); - expect(storeProfile.provider).toBe("openai-codex"); + expect(storeProfile.provider).toBe("openai"); expect(storeProfile.key).toBe("qa-configured-not-a-real-key"); } expect(() => testing.assertQaLiveCodexAuthAvailable({ cfg, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: {}, readCodexCredentials: () => null, }), ).not.toThrow(); }); - it("stages configured OpenAI Codex env secret refs for default OpenAI live QA runs", async () => { + it("stages configured OpenAI env secret refs for default OpenAI live QA runs", async () => { const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-config-ref-state-")); cleanups.push(async () => { await rm(stateDir, { recursive: true, force: true }); @@ -718,7 +718,7 @@ describe("buildQaRuntimeEnv", () => { cfg: { models: { providers: { - "openai-codex": { + openai: { baseUrl: "", models: [], apiKey: { @@ -741,10 +741,10 @@ describe("buildQaRuntimeEnv", () => { ); const storeProfile = requireAuthProfile( parseAuthProfileStore(storeRaw).profiles, - "qa-live-openai-codex-env", + "qa-live-openai-env", ); expect(storeProfile.type).toBe("api_key"); - expect(storeProfile.provider).toBe("openai-codex"); + expect(storeProfile.provider).toBe("openai"); expect(storeProfile.key).toBe("qa-configured-env-ref-not-a-real-key"); expect(() => @@ -757,7 +757,7 @@ describe("buildQaRuntimeEnv", () => { ).not.toThrow(); }); - it("stages configured OpenAI Codex env markers for live QA runs", async () => { + it("stages configured OpenAI env markers for live QA runs", async () => { const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-config-marker-state-")); cleanups.push(async () => { await rm(stateDir, { recursive: true, force: true }); @@ -766,7 +766,7 @@ describe("buildQaRuntimeEnv", () => { cfg: { models: { providers: { - "openai-codex": { + openai: { baseUrl: "", models: [], apiKey: "OPENCLAW_LIVE_CODEX_API_KEY", @@ -775,7 +775,7 @@ describe("buildQaRuntimeEnv", () => { }, }, stateDir, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: { OPENCLAW_LIVE_CODEX_API_KEY: "qa-configured-marker-not-a-real-key", }, @@ -787,26 +787,26 @@ describe("buildQaRuntimeEnv", () => { ); const storeProfile = requireAuthProfile( parseAuthProfileStore(storeRaw).profiles, - "qa-live-openai-codex-env", + "qa-live-openai-env", ); expect(storeProfile.type).toBe("api_key"); - expect(storeProfile.provider).toBe("openai-codex"); + expect(storeProfile.provider).toBe("openai"); expect(storeProfile.key).toBe("qa-configured-marker-not-a-real-key"); expect(() => testing.assertQaLiveCodexAuthAvailable({ cfg, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: {}, readCodexCredentials: () => null, }), ).not.toThrow(); }); - it("accepts a logged-in Codex CLI home for live OpenAI Codex QA runs", () => { + it("accepts a logged-in Codex CLI home for live OpenAI QA runs", () => { const readCodexCredentials = vi.fn(() => ({ type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -815,7 +815,7 @@ describe("buildQaRuntimeEnv", () => { expect(() => testing.assertQaLiveCodexAuthAvailable({ cfg: {}, - providerIds: ["openai-codex"], + providerIds: ["openai"], env: { CODEX_HOME: "/host/.codex", }, @@ -1575,7 +1575,7 @@ describe("qa bundled plugin dir", () => { path.join(repoRoot, "dist", "extensions", "openai", "openclaw.plugin.json"), JSON.stringify({ id: "openai", - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], cliBackends: ["codex-cli"], }), "utf8", @@ -1680,7 +1680,7 @@ describe("qa bundled plugin dir", () => { expect(overrides["custom-openai"]?.api).toBe("openai-responses"); }); - it("copies OpenAI Codex auth-only live provider configs for default OpenAI runs", async () => { + it("copies OpenAI auth-only live provider configs for default OpenAI runs", async () => { const configPath = path.join( await mkdtemp(path.join(os.tmpdir(), "qa-provider-config-")), "openclaw.json", @@ -1693,7 +1693,7 @@ describe("qa bundled plugin dir", () => { JSON.stringify({ models: { providers: { - "openai-codex": { + openai: { apiKey: { source: "env", id: "OPENCLAW_LIVE_CODEX_API_KEY", @@ -1709,16 +1709,16 @@ describe("qa bundled plugin dir", () => { providerIds: ["openai"], env: { OPENCLAW_QA_LIVE_PROVIDER_CONFIG_PATH: configPath }, }); - expect(Object.keys(overrides)).toEqual(["openai-codex"]); - expect(overrides["openai-codex"]?.baseUrl).toBe(""); - expect(overrides["openai-codex"]?.models).toEqual([]); - expect(overrides["openai-codex"]?.apiKey).toEqual({ + expect(Object.keys(overrides)).toEqual(["openai"]); + expect(overrides["openai"]?.baseUrl).toBe(""); + expect(overrides["openai"]?.models).toEqual([]); + expect(overrides["openai"]?.apiKey).toEqual({ source: "env", id: "OPENCLAW_LIVE_CODEX_API_KEY", }); }); - it("does not copy OpenAI Codex provider configs for custom OpenAI-compatible runs", async () => { + it("does not copy OpenAI provider configs for custom OpenAI-compatible runs", async () => { const configPath = path.join( await mkdtemp(path.join(os.tmpdir(), "qa-provider-config-")), "openclaw.json", @@ -1734,8 +1734,6 @@ describe("qa bundled plugin dir", () => { openai: { baseUrl: "https://proxy.example.test/v1", models: [], - }, - "openai-codex": { apiKey: { source: "env", id: "OPENCLAW_LIVE_CODEX_API_KEY", @@ -1753,7 +1751,6 @@ describe("qa bundled plugin dir", () => { }); expect(Object.keys(overrides)).toEqual(["openai"]); expect(overrides.openai?.baseUrl).toBe("https://proxy.example.test/v1"); - expect(overrides["openai-codex"]).toBeUndefined(); }); it("raises the QA runtime host version to the highest allowed plugin floor", async () => { diff --git a/extensions/qa-lab/src/gateway-child.ts b/extensions/qa-lab/src/gateway-child.ts index 51f66083929d..15f0595e11ac 100644 --- a/extensions/qa-lab/src/gateway-child.ts +++ b/extensions/qa-lab/src/gateway-child.ts @@ -409,43 +409,6 @@ function normalizeQaLiveProviderConfig(value: unknown): ModelProviderConfig | nu } as ModelProviderConfig; } -function isQaLiveOfficialOpenAiProviderConfig(value: unknown): boolean { - if (!isRecord(value)) { - return true; - } - const baseUrl = value.baseUrl; - if (typeof baseUrl !== "string" || !baseUrl.trim()) { - return true; - } - try { - const url = new URL(baseUrl.trim()); - return ( - url.protocol === "https:" && - url.hostname.toLowerCase() === "api.openai.com" && - (url.pathname === "" || - url.pathname === "/" || - url.pathname === "/v1" || - url.pathname === "/v1/") - ); - } catch { - return false; - } -} - -function expandQaLiveProviderConfigIds( - providerIds: readonly string[], - providers: Record, -) { - const expanded = new Set(providerIds); - if (expanded.has("openai-codex")) { - expanded.add("openai"); - expanded.add("openai-codex"); - } else if (expanded.has("openai") && isQaLiveOfficialOpenAiProviderConfig(providers.openai)) { - expanded.add("openai-codex"); - } - return [...expanded]; -} - async function readQaLiveProviderConfigOverrides(params: { providerIds: readonly string[]; env?: NodeJS.ProcessEnv; @@ -469,7 +432,7 @@ async function readQaLiveProviderConfigOverrides(params: { : {} : {}; const selected: Record = {}; - for (const providerId of expandQaLiveProviderConfigIds(providerIds, providers)) { + for (const providerId of providerIds) { const providerConfig = normalizeQaLiveProviderConfig(providers[providerId]); if (providerConfig) { selected[providerId] = providerConfig; diff --git a/extensions/qa-lab/src/model-selection.runtime.test.ts b/extensions/qa-lab/src/model-selection.runtime.test.ts index 616ae5f11292..3185e36bc402 100644 --- a/extensions/qa-lab/src/model-selection.runtime.test.ts +++ b/extensions/qa-lab/src/model-selection.runtime.test.ts @@ -40,7 +40,7 @@ describe("qa model selection runtime", () => { it("prefers the Codex OAuth live default when only Codex auth profiles are available", () => { listProfilesForProvider.mockImplementation((_store: unknown, provider: string) => - provider === "openai-codex" ? ["openai-codex:user@example.com"] : [], + provider === "openai" ? ["openai:user@example.com"] : [], ); expect(resolveQaPreferredLiveModel()).toBe("openai/gpt-5.5"); @@ -48,13 +48,13 @@ describe("qa model selection runtime", () => { expect(loadAuthProfileStoreForRuntime).toHaveBeenCalledWith(undefined, { readOnly: true, allowKeychainPrompt: false, - externalCliProviderIds: ["openai-codex"], + externalCliProviderIds: ["openai"], }); }); it("keeps the OpenAI live default when stored OpenAI profiles are available", () => { listProfilesForProvider.mockImplementation((_store: unknown, provider: string) => - provider === "openai" || provider === "openai-codex" ? [`${provider}:user@example.com`] : [], + provider === "openai" ? [`${provider}:user@example.com`] : [], ); expect(resolveQaPreferredLiveModel()).toBeUndefined(); @@ -63,7 +63,7 @@ describe("qa model selection runtime", () => { it("leaves mock defaults unchanged", () => { listProfilesForProvider.mockImplementation((_store: unknown, provider: string) => - provider === "openai-codex" ? ["openai-codex:user@example.com"] : [], + provider === "openai" ? ["openai:user@example.com"] : [], ); expect(defaultQaRuntimeModelForMode("mock-openai")).toBe("mock-openai/gpt-5.5"); diff --git a/extensions/qa-lab/src/providers/aimock/server.test.ts b/extensions/qa-lab/src/providers/aimock/server.test.ts index 310c943bf71c..821092457e5a 100644 --- a/extensions/qa-lab/src/providers/aimock/server.test.ts +++ b/extensions/qa-lab/src/providers/aimock/server.test.ts @@ -111,7 +111,7 @@ describe("qa aimock server", () => { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", stream: false, input: [makeResponsesInput("hello codex-compatible aimock")], }), @@ -121,7 +121,7 @@ describe("qa aimock server", () => { const debug = await fetch(`${server.baseUrl}/debug/last-request`); expect(debug.status).toBe(200); const expectedBody = { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", messages: [{ role: "user", content: "hello codex-compatible aimock" }], stream: false, _endpointType: "chat", @@ -132,7 +132,7 @@ describe("qa aimock server", () => { prompt: "hello codex-compatible aimock", allInputText: "hello codex-compatible aimock", toolOutput: "", - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", providerVariant: "openai", imageInputCount: 0, }); diff --git a/extensions/qa-lab/src/providers/aimock/server.ts b/extensions/qa-lab/src/providers/aimock/server.ts index 48a353d69626..487829aaff81 100644 --- a/extensions/qa-lab/src/providers/aimock/server.ts +++ b/extensions/qa-lab/src/providers/aimock/server.ts @@ -107,7 +107,7 @@ function countImageInputs(value: unknown): number { function resolveProviderVariant(model: string): AimockRequestSnapshot["providerVariant"] { const normalized = model.trim().toLowerCase(); const provider = /^([^/:]+)[/:]/.exec(normalized)?.[1] ?? normalized; - if (provider === "openai" || provider === "aimock" || provider === "openai-codex") { + if (provider === "openai" || provider === "aimock" || provider === "openai") { return "openai"; } if (provider === "anthropic" || provider === "claude-cli") { diff --git a/extensions/qa-lab/src/providers/live-frontier/auth.ts b/extensions/qa-lab/src/providers/live-frontier/auth.ts index 9154e6c10d29..c34b342706c4 100644 --- a/extensions/qa-lab/src/providers/live-frontier/auth.ts +++ b/extensions/qa-lab/src/providers/live-frontier/auth.ts @@ -18,12 +18,10 @@ const QA_LIVE_ANTHROPIC_SETUP_TOKEN_PROFILE_ENV = "OPENCLAW_QA_LIVE_ANTHROPIC_SE const QA_LIVE_ANTHROPIC_SETUP_TOKEN_PROFILE_ID = "anthropic:qa-setup-token"; const QA_LIVE_API_KEY_AGENT_IDS = Object.freeze(["main", "qa"] as const); const QA_OPENAI_PROVIDER_ID = "openai"; -const QA_OPENAI_CODEX_PROVIDER_ID = "openai-codex"; const QA_LIVE_API_KEY_ALIASES: Readonly> = Object.freeze({ anthropic: ["OPENCLAW_LIVE_ANTHROPIC_KEY"], gemini: ["OPENCLAW_LIVE_GEMINI_KEY"], - openai: ["OPENCLAW_LIVE_OPENAI_KEY", "OPENAI_API_KEY"], - "openai-codex": [ + openai: [ "CODEX_API_KEY", "OPENCLAW_LIVE_CODEX_API_KEY", "OPENCLAW_LIVE_OPENAI_KEY", @@ -69,12 +67,8 @@ function expandQaLiveApiKeyProviderIds(params: { providerIds: readonly string[]; }) { const expanded = new Set(normalizeQaLiveProviderIds(params.providerIds)); - if ( - expanded.has(QA_OPENAI_CODEX_PROVIDER_ID) || - (expanded.has(QA_OPENAI_PROVIDER_ID) && qaLiveOpenAiUsesCodexByDefault(params.cfg)) - ) { + if (expanded.has(QA_OPENAI_PROVIDER_ID) && qaLiveOpenAiUsesCodexByDefault(params.cfg)) { expanded.add(QA_OPENAI_PROVIDER_ID); - expanded.add(QA_OPENAI_CODEX_PROVIDER_ID); } return [...expanded].toSorted(); } @@ -160,9 +154,6 @@ function qaLiveRequiresCodexAuth(params: { env: NodeJS.ProcessEnv; }) { const providerIds = normalizeQaLiveProviderIds(params.providerIds); - if (providerIds.includes(QA_OPENAI_CODEX_PROVIDER_ID)) { - return true; - } if (!providerIds.includes(QA_OPENAI_PROVIDER_ID)) { return false; } @@ -288,10 +279,7 @@ export function assertQaLiveCodexAuthAvailable(params: { } if ( resolveQaLiveEnvApiKey({ providerId: QA_OPENAI_PROVIDER_ID, env, cfg: params.cfg })?.apiKey || - resolveQaLiveEnvApiKey({ providerId: QA_OPENAI_CODEX_PROVIDER_ID, env, cfg: params.cfg }) - ?.apiKey || - hasQaLiveStagedApiKeyProfile({ cfg: params.cfg, providerId: QA_OPENAI_PROVIDER_ID }) || - hasQaLiveStagedApiKeyProfile({ cfg: params.cfg, providerId: QA_OPENAI_CODEX_PROVIDER_ID }) + hasQaLiveStagedApiKeyProfile({ cfg: params.cfg, providerId: QA_OPENAI_PROVIDER_ID }) ) { return; } diff --git a/extensions/qa-lab/src/providers/live-frontier/model-selection.runtime.ts b/extensions/qa-lab/src/providers/live-frontier/model-selection.runtime.ts index e355c1e88136..8c7ca8fc0f73 100644 --- a/extensions/qa-lab/src/providers/live-frontier/model-selection.runtime.ts +++ b/extensions/qa-lab/src/providers/live-frontier/model-selection.runtime.ts @@ -14,12 +14,12 @@ export function resolveQaLiveFrontierPreferredModel() { const store = loadAuthProfileStoreForRuntime(undefined, { readOnly: true, allowKeychainPrompt: false, - externalCliProviderIds: ["openai-codex"], + externalCliProviderIds: ["openai"], }); if (listProfilesForProvider(store, "openai").length > 0) { return undefined; } - return listProfilesForProvider(store, "openai-codex").length > 0 + return listProfilesForProvider(store, "openai").length > 0 ? QA_CODEX_OAUTH_LIVE_MODEL : undefined; } catch { diff --git a/extensions/qa-lab/src/providers/mock-openai/server.test.ts b/extensions/qa-lab/src/providers/mock-openai/server.test.ts index 84428dbc472c..109cbb1ef30e 100644 --- a/extensions/qa-lab/src/providers/mock-openai/server.test.ts +++ b/extensions/qa-lab/src/providers/mock-openai/server.test.ts @@ -4245,7 +4245,7 @@ describe("resolveProviderVariant", () => { it("tags prefix-qualified openai models", () => { expect(resolveProviderVariant("openai/gpt-5.5")).toBe("openai"); expect(resolveProviderVariant("openai:gpt-5.5")).toBe("openai"); - expect(resolveProviderVariant("openai-codex/gpt-5.5")).toBe("openai"); + expect(resolveProviderVariant("openai/gpt-5.5")).toBe("openai"); }); it("tags prefix-qualified anthropic models", () => { diff --git a/extensions/qa-lab/src/providers/mock-openai/server.ts b/extensions/qa-lab/src/providers/mock-openai/server.ts index 1a032f7c8f6c..b3e25b1e33a1 100644 --- a/extensions/qa-lab/src/providers/mock-openai/server.ts +++ b/extensions/qa-lab/src/providers/mock-openai/server.ts @@ -74,7 +74,7 @@ export function resolveProviderVariant(model: string | undefined): MockOpenAiPro // the caller supplied one — that's the most reliable signal. const separatorMatch = /^([^/:]+)[/:]/.exec(trimmed); const provider = separatorMatch?.[1] ?? trimmed; - if (provider === "openai" || provider === "openai-codex") { + if (provider === "openai") { return "openai"; } if (provider === "anthropic" || provider === "claude-cli") { diff --git a/extensions/qa-lab/src/providers/shared/auth-store.test.ts b/extensions/qa-lab/src/providers/shared/auth-store.test.ts index 0abf1cbfee9a..fdb31183b514 100644 --- a/extensions/qa-lab/src/providers/shared/auth-store.test.ts +++ b/extensions/qa-lab/src/providers/shared/auth-store.test.ts @@ -144,11 +144,11 @@ describe("QA auth profile store", () => { }, legacyOAuthProfile: { type: "oauth", - provider: "openai-codex", + provider: "openai", expires: 1_900_000_000_000, oauthRef: { source: "openclaw-credentials", - provider: "openai-codex", + provider: "openai", id: "0123456789abcdef0123456789abcdef", }, }, @@ -185,11 +185,11 @@ describe("QA auth profile store", () => { }); expect(written.profiles?.legacyOAuthProfile).toEqual({ type: "oauth", - provider: "openai-codex", + provider: "openai", expires: 1_900_000_000_000, oauthRef: { source: "openclaw-credentials", - provider: "openai-codex", + provider: "openai", id: "0123456789abcdef0123456789abcdef", }, }); diff --git a/extensions/qa-lab/src/providers/shared/auth-store.ts b/extensions/qa-lab/src/providers/shared/auth-store.ts index 8c3d865ef098..ca1b75ac3c09 100644 --- a/extensions/qa-lab/src/providers/shared/auth-store.ts +++ b/extensions/qa-lab/src/providers/shared/auth-store.ts @@ -39,7 +39,7 @@ type QaSecretRef = { type QaLegacyOAuthRef = { source: "openclaw-credentials"; - provider: "openai-codex"; + provider: "openai"; id: string; }; @@ -166,7 +166,7 @@ function isQaLegacyOAuthRef(value: unknown): value is QaLegacyOAuthRef { return ( isRecord(value) && value.source === "openclaw-credentials" && - value.provider === "openai-codex" && + value.provider === "openai" && typeof value.id === "string" && /^[a-f0-9]{32}$/.test(value.id) ); diff --git a/extensions/telegram/src/bot-native-commands.session-meta.test.ts b/extensions/telegram/src/bot-native-commands.session-meta.test.ts index fb3fa998dba9..5de4a7736eb6 100644 --- a/extensions/telegram/src/bot-native-commands.session-meta.test.ts +++ b/extensions/telegram/src/bot-native-commands.session-meta.test.ts @@ -1287,7 +1287,7 @@ describe("registerTelegramNativeCommands — session metadata", () => { sessionMocks.resolveStorePath.mockReturnValue("/tmp/openclaw-sessions/sessions.json"); sessionMocks.loadSessionStore.mockReturnValue({ "agent:main:telegram:group:-1001234567890:topic:42": { - authProfileOverride: "openai-codex:owner@example.com", + authProfileOverride: "openai:owner@example.com", sessionId: "sess-topic", updatedAt: 1, }, @@ -1339,7 +1339,7 @@ describe("registerTelegramNativeCommands — session metadata", () => { sessionKey: "agent:main:telegram:group:-1001234567890:topic:42", sessionId: "sess-topic", sessionFile: path.resolve("/tmp/openclaw-sessions", "sess-topic-topic-42.jsonl"), - authProfileId: "openai-codex:owner@example.com", + authProfileId: "openai:owner@example.com", messageThreadId: 42, }, "plugin command params", diff --git a/extensions/telegram/src/model-buttons.test.ts b/extensions/telegram/src/model-buttons.test.ts index 6d5b2ed61f10..51860e3266cb 100644 --- a/extensions/telegram/src/model-buttons.test.ts +++ b/extensions/telegram/src/model-buttons.test.ts @@ -313,7 +313,7 @@ describe("buildModelsKeyboard", () => { it("does not mark same-id models from other providers as current", () => { const result = buildModelsKeyboard({ - provider: "openai-codex", + provider: "openai", models: ["gpt-5.4", "gpt-5.3-codex-spark"], currentModel: "github-copilot/gpt-5.4", currentPage: 1, diff --git a/packages/llm-core/src/types.ts b/packages/llm-core/src/types.ts index a6f42f535e5d..7e91c8995096 100644 --- a/packages/llm-core/src/types.ts +++ b/packages/llm-core/src/types.ts @@ -6,7 +6,7 @@ export type KnownApi = | "mistral-conversations" | "openai-responses" | "azure-openai-responses" - | "openai-codex-responses" + | "openai-chatgpt-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" diff --git a/packages/media-generation-core/src/model-ref.test.ts b/packages/media-generation-core/src/model-ref.test.ts index a8c92f50d5ec..f14b02cfbf13 100644 --- a/packages/media-generation-core/src/model-ref.test.ts +++ b/packages/media-generation-core/src/model-ref.test.ts @@ -58,17 +58,17 @@ describe("media-generation model refs", () => { it("matches provider aliases through a caller-supplied normalizer", () => { expect( resolveCapabilityModelRefForProviders({ - raw: "openai-codex/gpt-image-2", + raw: "openai/gpt-image-2", parseModelRef: parseGenerationModelRef, normalizeProviderId: (value) => value.toLowerCase(), providers: [ { id: "openai", - aliases: ["openai-codex"], + aliases: ["openai"], defaultModel: "gpt-image-2", }, ], }), - ).toEqual({ provider: "openai-codex", model: "gpt-image-2" }); + ).toEqual({ provider: "openai", model: "gpt-image-2" }); }); }); diff --git a/packages/model-catalog-core/src/model-catalog-types.ts b/packages/model-catalog-core/src/model-catalog-types.ts index 29e896b26d6d..b56a81b8425f 100644 --- a/packages/model-catalog-core/src/model-catalog-types.ts +++ b/packages/model-catalog-core/src/model-catalog-types.ts @@ -1,7 +1,7 @@ export const MODEL_CATALOG_APIS = [ "openai-completions", "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "anthropic-messages", "google-generative-ai", "google-vertex", diff --git a/qa/scenarios/runtime/auth-profile-codex-mixed-profiles.md b/qa/scenarios/runtime/auth-profile-codex-mixed-profiles.md index 3adebe2cddd0..ecf07a5cd7b5 100644 --- a/qa/scenarios/runtime/auth-profile-codex-mixed-profiles.md +++ b/qa/scenarios/runtime/auth-profile-codex-mixed-profiles.md @@ -10,9 +10,9 @@ coverage: - runtime.codex-plugin.auth secondary: - auth-profiles.provider-selection -objective: Verify mixed openai-codex OAuth and openai API-key profile stores select the Codex OAuth profile for Codex app-server turns. +objective: Verify mixed openai OAuth and openai API-key profile stores select the Codex OAuth profile for Codex app-server turns. successCriteria: - - The selected auth profile id is openai-codex:qa-oauth. + - The selected auth profile id is openai:qa-oauth. - The openai:media-api API-key profile is present but not selected. - The fixture rejects the residual provider mismatch covered by issue #78499. docsRefs: @@ -24,7 +24,7 @@ execution: kind: flow summary: Exercise the auth-profile fixture for mixed OpenAI API-key and Codex OAuth stores. config: - selectedProfileId: openai-codex:qa-oauth + selectedProfileId: openai:qa-oauth rejectedProfileId: openai:media-api ``` @@ -53,7 +53,7 @@ steps: expr: "`expected ready Codex auth selection, got ${JSON.stringify(selection)}`" - assert: expr: "selection.profileId === config.selectedProfileId" - message: mixed profiles must select openai-codex OAuth + message: mixed profiles must select openai OAuth - assert: expr: "selection.profileId !== config.rejectedProfileId" message: codex profile must not equal openai api-key profile diff --git a/qa/scenarios/runtime/auth-profile-doctor-migration-safety.md b/qa/scenarios/runtime/auth-profile-doctor-migration-safety.md index 9b2b6790b9a6..6fc70561b526 100644 --- a/qa/scenarios/runtime/auth-profile-doctor-migration-safety.md +++ b/qa/scenarios/runtime/auth-profile-doctor-migration-safety.md @@ -12,8 +12,8 @@ coverage: - runtime.codex-plugin.auth objective: Reproduce the doctor-migration auth cells as an automated fixture matrix for Codex OAuth selection. successCriteria: - - OAuth-only hosts select the openai-codex OAuth profile and use the Codex harness. - - Mixed-profile hosts still select openai-codex OAuth when an openai API-key profile exists. + - OAuth-only hosts select the openai OAuth profile and use the Codex harness. + - Mixed-profile hosts still select openai OAuth when an openai API-key profile exists. docsRefs: - docs/cli/doctor.md codeRefs: diff --git a/qa/scenarios/runtime/codex-plugin-cold-install.md b/qa/scenarios/runtime/codex-plugin-cold-install.md index 7b8b602b1234..6f435c38ee67 100644 --- a/qa/scenarios/runtime/codex-plugin-cold-install.md +++ b/qa/scenarios/runtime/codex-plugin-cold-install.md @@ -14,7 +14,7 @@ objective: Verify a clean home that needs the Codex runtime reports a clear miss successCriteria: - Missing Codex plugin emits the exact remediation string asserted by the fixture test. - Doctor repair seeds the Codex plugin before retrying the agent turn. - - The retry uses the openai-codex OAuth profile and never routes through the openai API-key profile. + - The retry uses the openai OAuth profile and never routes through the openai API-key profile. docsRefs: - docs/cli/doctor.md - docs/cli/plugins.md diff --git a/scripts/e2e/openai-image-auth-docker-client.ts b/scripts/e2e/openai-image-auth-docker-client.ts index 2070a14b72cb..d59ef2b0ffbb 100644 --- a/scripts/e2e/openai-image-auth-docker-client.ts +++ b/scripts/e2e/openai-image-auth-docker-client.ts @@ -151,9 +151,9 @@ function createCodexOAuthStore() { return { version: 1, profiles: { - "openai-codex:default": { + "openai:chatgpt": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: CODEX_TOKEN, refresh: "docker-codex-refresh-token", expires: Date.now() + 60 * 60 * 1000, @@ -207,9 +207,9 @@ async function main() { cfg: { models: { providers: { - "openai-codex": { + openai: { baseUrl: `${mock.baseUrl}/backend-api/codex`, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", request: { allowPrivateNetwork: true }, models: [], }, diff --git a/scripts/lib/live-docker-auth.sh b/scripts/lib/live-docker-auth.sh index 92d180d6d275..baed506bf4bc 100644 --- a/scripts/lib/live-docker-auth.sh +++ b/scripts/lib/live-docker-auth.sh @@ -93,7 +93,7 @@ openclaw_live_should_include_auth_file_for_provider() { local provider provider="$(openclaw_live_trim "${1:-}")" case "$provider" in - codex-cli | openai | openai-codex) + codex-cli | openai) printf '%s\n' ".codex/auth.json" printf '%s\n' ".codex/config.toml" ;; diff --git a/scripts/test-live-codex-harness-docker.sh b/scripts/test-live-codex-harness-docker.sh index 1da159dfaa0f..d5d67fc48ed6 100644 --- a/scripts/test-live-codex-harness-docker.sh +++ b/scripts/test-live-codex-harness-docker.sh @@ -148,7 +148,7 @@ if [[ "$CODEX_HARNESS_AUTH_MODE" != "api-key" ]]; then while IFS= read -r auth_file; do [[ -n "$auth_file" ]] || continue AUTH_FILES+=("$auth_file") - done < <(openclaw_live_collect_auth_files_from_csv "openai-codex") + done < <(openclaw_live_collect_auth_files_from_csv "openai") fi AUTH_FILES_CSV="" diff --git a/src/acp/control-plane/manager.test.ts b/src/acp/control-plane/manager.test.ts index 0559331ee3de..4f5c106ea24b 100644 --- a/src/acp/control-plane/manager.test.ts +++ b/src/acp/control-plane/manager.test.ts @@ -1359,7 +1359,7 @@ describe("AcpSessionManager", () => { acp: { ...readySessionMeta(), runtimeOptions: { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", }, }, }; @@ -1376,7 +1376,7 @@ describe("AcpSessionManager", () => { expectRecordFields(mockCallArg(runtimeState.ensureSession), { sessionKey, - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", }); }); @@ -1947,7 +1947,7 @@ describe("AcpSessionManager", () => { storeSessionKey: "agent:codex:acp:session-a", acp: readySessionMeta({ runtimeOptions: { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", thinking: "high", }, }), @@ -1960,20 +1960,20 @@ describe("AcpSessionManager", () => { agent: "codex", mode: "persistent", runtimeOptions: { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", thinking: "high", }, }); expect(extractRuntimeOptionsFromUpserts()).toEqual([ { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", thinking: "high", }, ]); expectRecordFields(mockCallArg(runtimeState.ensureSession), { sessionKey: "agent:codex:acp:session-a", - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", thinking: "high", }); }); @@ -3741,7 +3741,7 @@ describe("AcpSessionManager", () => { cfg: baseCfg, sessionKey: "agent:codex:acp:session-1", key: "model", - value: "openai-codex/gpt-5.4", + value: "openai/gpt-5.4", }); expect(runtimeState.setMode).not.toHaveBeenCalled(); @@ -4210,7 +4210,7 @@ describe("AcpSessionManager", () => { ...readySessionMeta(), runtimeOptions: { runtimeMode: "plan", - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", thinking: "high", permissionProfile: "strict", timeoutSeconds: 120, @@ -4232,7 +4232,7 @@ describe("AcpSessionManager", () => { }); expectMockCallFields(runtimeState.setConfigOption, { key: "model", - value: "openai-codex/gpt-5.4", + value: "openai/gpt-5.4", }); expectMockCallFields(runtimeState.setConfigOption, { key: "thinking", diff --git a/src/acp/runtime/session-identifiers.ts b/src/acp/runtime/session-identifiers.ts index 7d2ff92172fa..2634e49cbd53 100644 --- a/src/acp/runtime/session-identifiers.ts +++ b/src/acp/runtime/session-identifiers.ts @@ -15,7 +15,7 @@ const ACP_AGENT_RESUME_HINT_BY_KEY = new Map( `resume in Codex CLI: \`codex resume ${agentSessionId}\` (continues this conversation).`, ], [ - "openai-codex", + "openai", ({ agentSessionId }) => `resume in Codex CLI: \`codex resume ${agentSessionId}\` (continues this conversation).`, ], diff --git a/src/agents/acp-spawn.test.ts b/src/agents/acp-spawn.test.ts index d4af56f4bdda..ffcf75d84c5b 100644 --- a/src/agents/acp-spawn.test.ts +++ b/src/agents/acp-spawn.test.ts @@ -910,7 +910,7 @@ describe("spawnAcpDirect", () => { { task: "Investigate flaky tests", agentId: "codex", - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", thinking: "high", }, { @@ -922,7 +922,7 @@ describe("spawnAcpDirect", () => { const initInput = expectInitializeSessionFields({ agent: "codex", runtimeOptions: { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", thinking: "high", }, }); diff --git a/src/agents/agent-auth-json.test.ts b/src/agents/agent-auth-json.test.ts index 3e03b8bb836f..72306ce75ea9 100644 --- a/src/agents/agent-auth-json.test.ts +++ b/src/agents/agent-auth-json.test.ts @@ -65,13 +65,13 @@ function expectOAuthAuth( } describe("ensureAgentAuthJsonFromAuthProfiles", () => { - it("writes openai-codex oauth credentials into auth.json for session runtime discovery", async () => { + it("writes openai oauth credentials into auth.json for session runtime discovery", async () => { const agentDir = await createAgentDir(); writeProfiles(agentDir, { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -82,7 +82,7 @@ describe("ensureAgentAuthJsonFromAuthProfiles", () => { expect(first.wrote).toBe(true); const auth = await readAuthJson(agentDir); - expectOAuthAuth(auth, "openai-codex", "access-token", "refresh-token"); + expectOAuthAuth(auth, "openai", "access-token", "refresh-token"); const second = await ensureAgentAuthJsonFromAuthProfiles(agentDir); expect(second.wrote).toBe(false); @@ -138,9 +138,9 @@ describe("ensureAgentAuthJsonFromAuthProfiles", () => { provider: "anthropic", token: "sk-ant-token", }, - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access", refresh: "refresh", expires: Date.now() + 60_000, @@ -154,7 +154,7 @@ describe("ensureAgentAuthJsonFromAuthProfiles", () => { expectApiKeyAuth(auth, "openrouter", "sk-or-key"); expectApiKeyAuth(auth, "anthropic", "sk-ant-token"); - expectOAuthAuth(auth, "openai-codex", "access"); + expectOAuthAuth(auth, "openai", "access"); }); it("skips profiles with empty keys", async () => { diff --git a/src/agents/agent-command.live-model-switch.test.ts b/src/agents/agent-command.live-model-switch.test.ts index a8d16a83a2b1..e704294ab5f9 100644 --- a/src/agents/agent-command.live-model-switch.test.ts +++ b/src/agents/agent-command.live-model-switch.test.ts @@ -618,7 +618,7 @@ vi.mock("./model-visibility-policy.js", () => ({ vi.mock("./provider-auth-aliases.js", () => ({ resolveProviderAuthAliasMap: () => ({}), resolveProviderIdForAuth: (provider: string) => - provider.trim().toLowerCase() === "codex-cli" ? "openai-codex" : provider.trim().toLowerCase(), + provider.trim().toLowerCase() === "codex-cli" ? "openai" : provider.trim().toLowerCase(), })); vi.mock("../skills/discovery/agent-filter.js", () => ({ @@ -2052,7 +2052,7 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { updatedAt: Date.now(), providerOverride: "codex-cli", modelOverride: "gpt-5.4", - authProfileOverride: "openai-codex:work", + authProfileOverride: "openai:work", authProfileOverrideSource: "user", skillsSnapshot: { prompt: "", skills: [], version: 0 }, }; @@ -2068,9 +2068,9 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => { }; state.authProfileStoreMock = { profiles: { - "openai-codex:work": { + "openai:work": { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-test", }, }, diff --git a/src/agents/agent-command.ts b/src/agents/agent-command.ts index 532787e92ce4..2161d965ccb9 100644 --- a/src/agents/agent-command.ts +++ b/src/agents/agent-command.ts @@ -113,7 +113,7 @@ import { createModelVisibilityPolicy, type ModelVisibilityPolicy, } from "./model-visibility-policy.js"; -import { listOpenAIAuthProfileProvidersForAgentRuntime } from "./openai-codex-routing.js"; +import { listOpenAIAuthProfileProvidersForAgentRuntime } from "./openai-routing.js"; import { resolveProviderIdForAuth } from "./provider-auth-aliases.js"; import { normalizeSpawnedRunMetadata } from "./spawned-context.js"; import { resolveAgentTimeoutMs } from "./timeout.js"; diff --git a/src/agents/agent-model-discovery.auth.test.ts b/src/agents/agent-model-discovery.auth.test.ts index b427b6051f70..246cb2100cbd 100644 --- a/src/agents/agent-model-discovery.auth.test.ts +++ b/src/agents/agent-model-discovery.auth.test.ts @@ -107,9 +107,9 @@ describe("discoverAuthStorage", () => { provider: "anthropic", token: "sk-ant-runtime", }, - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "oauth-access", refresh: "oauth-refresh", expires: Date.now() + 60_000, @@ -125,7 +125,7 @@ describe("discoverAuthStorage", () => { type: "api_key", key: "sk-ant-runtime", }); - const codexCredential = credentials["openai-codex"] as + const codexCredential = credentials["openai"] as | { type?: string; access?: string; refresh?: string } | undefined; expect(codexCredential?.type).toBe("oauth"); @@ -143,9 +143,9 @@ describe("discoverAuthStorage", () => { token: "sk-ant-runtime", expires: MAX_DATE_TIMESTAMP_MS + 1, }, - "openai-codex:bad-oauth-expiry": { + "openai:bad-oauth-expiry": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "oauth-access", refresh: "oauth-refresh", expires: MAX_DATE_TIMESTAMP_MS + 1, @@ -154,7 +154,7 @@ describe("discoverAuthStorage", () => { }); expect(credentials.anthropic).toBeUndefined(); - expect(credentials["openai-codex"]).toBeUndefined(); + expect(credentials.openai).toBeUndefined(); }); it("keeps keyRef and tokenRef profiles visible only for read-only agent discovery", () => { @@ -243,7 +243,7 @@ describe("discoverAuthStorage", () => { await withAgentDir(async (agentDir) => { await writeLegacyAuthJson(agentDir, { openrouter: { type: "api_key", key: "legacy-static-key" }, - "openai-codex": { + openai: { type: "oauth", access: "oauth-access", refresh: "oauth-refresh", @@ -255,7 +255,7 @@ describe("discoverAuthStorage", () => { const parsed = await readLegacyAuthJson(agentDir); expect(parsed.openrouter).toBeUndefined(); - const codexEntry = parsed["openai-codex"] as { type?: string; access?: string } | undefined; + const codexEntry = parsed["openai"] as { type?: string; access?: string } | undefined; expect(codexEntry?.type).toBe("oauth"); expect(codexEntry?.access).toBe("oauth-access"); }); diff --git a/src/agents/agent-scope.test.ts b/src/agents/agent-scope.test.ts index 1b7f6d35720d..acba12580fa3 100644 --- a/src/agents/agent-scope.test.ts +++ b/src/agents/agent-scope.test.ts @@ -842,7 +842,7 @@ describe("resolveAgentConfig", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.4", "zai/glm-5"], + fallbacks: ["openai/gpt-5.4", "zai/glm-5"], }, }, }, @@ -852,7 +852,7 @@ describe("resolveAgentConfig", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.4", "zai/glm-5"], + fallbacks: ["openai/gpt-5.4", "zai/glm-5"], }, }, }, @@ -891,21 +891,21 @@ describe("resolveAgentConfig", () => { }; expect(resolveSubagentModelFallbacksOverride(cfg, "research")).toEqual([ - "openai-codex/gpt-5.4", + "openai/gpt-5.4", "zai/glm-5", ]); expect(resolveSubagentModelFallbacksOverride(cfg, "agent-model")).toEqual([ "google/gemini-3-pro", ]); expect(resolveSubagentModelFallbacksOverride(cfg, "fallback-only-agent-model")).toEqual([ - "openai-codex/gpt-5.4", + "openai/gpt-5.4", "zai/glm-5", ]); expect( resolveSubagentModelFallbacksOverride(cfg, "fallback-only-subagent-model"), ).toStrictEqual([]); expect(resolveSubagentModelFallbacksOverride(cfg, "default-subagent")).toEqual([ - "openai-codex/gpt-5.4", + "openai/gpt-5.4", "zai/glm-5", ]); expect(resolveSubagentModelFallbacksOverride(cfg, "strict")).toStrictEqual([]); @@ -921,7 +921,7 @@ describe("resolveAgentConfig", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.4", "zai/glm-5"], + fallbacks: ["openai/gpt-5.4", "zai/glm-5"], }, }, }, @@ -955,7 +955,7 @@ describe("resolveAgentConfig", () => { hasSessionModelOverride: true, modelOverrideSource: "auto", }), - ).toEqual(["openai-codex/gpt-5.4", "zai/glm-5"]); + ).toEqual(["openai/gpt-5.4", "zai/glm-5"]); expect( resolveEffectiveModelFallbacks({ cfg, @@ -996,7 +996,7 @@ describe("resolveAgentConfig", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.4"], + fallbacks: ["openai/gpt-5.4"], }, }, }, @@ -1017,7 +1017,7 @@ describe("resolveAgentConfig", () => { }); expect(resolveSubagentModelConfigSelection({ cfg, agentId: "subagent-model" })).toEqual({ primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.4"], + fallbacks: ["openai/gpt-5.4"], }); expect(resolveSubagentModelConfigSelection({ cfg, agentId: "fallback-only-subagent" })).toBe( "anthropic/claude-sonnet-4-6", diff --git a/src/agents/agent-tools.create-openclaw-coding-tools.test.ts b/src/agents/agent-tools.create-openclaw-coding-tools.test.ts index 3ecbba9a9355..d995c9358cc7 100644 --- a/src/agents/agent-tools.create-openclaw-coding-tools.test.ts +++ b/src/agents/agent-tools.create-openclaw-coding-tools.test.ts @@ -698,7 +698,7 @@ describe("createOpenClawCodingTools", () => { const codexTools = createOpenClawCodingTools({ config: testConfig, - modelProvider: "openai-codex", + modelProvider: "openai", modelId: "gpt-5.4", }); expect(toolNameList(codexTools)).toContain("apply_patch"); diff --git a/src/agents/agent-tools.model-provider-collision.test.ts b/src/agents/agent-tools.model-provider-collision.test.ts index de59212fa0aa..3e12a789c0d1 100644 --- a/src/agents/agent-tools.model-provider-collision.test.ts +++ b/src/agents/agent-tools.model-provider-collision.test.ts @@ -60,7 +60,7 @@ describe("applyModelProviderToolPolicy", () => { }, }, modelProvider: "gateway", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", modelId: "gpt-5.4", }); @@ -80,7 +80,7 @@ describe("applyModelProviderToolPolicy", () => { }, }, modelProvider: "gateway", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", modelId: "gpt-5.4", suppressManagedWebSearch: false, }); @@ -101,15 +101,15 @@ describe("applyModelProviderToolPolicy", () => { }, auth: { profiles: { - "openai-codex:default": { - provider: "openai-codex", + "openai:default": { + provider: "openai", mode: "oauth", }, }, }, }, - modelProvider: "openai-codex", - modelApi: "openai-codex-responses", + modelProvider: "openai", + modelApi: "openai-chatgpt-responses", modelId: "gpt-5.4", }); @@ -128,8 +128,8 @@ describe("applyModelProviderToolPolicy", () => { }, }, }, - modelProvider: "openai-codex", - modelApi: "openai-codex-responses", + modelProvider: "openai", + modelApi: "openai-chatgpt-responses", modelId: "gpt-5.4", }); diff --git a/src/agents/agent-tools.ts b/src/agents/agent-tools.ts index 68f4b852cac7..197a24c2a31b 100644 --- a/src/agents/agent-tools.ts +++ b/src/agents/agent-tools.ts @@ -109,7 +109,7 @@ import { resolveWorkspaceRoot } from "./workspace-dir.js"; function isOpenAIProvider(provider?: string) { const normalized = normalizeOptionalLowercaseString(provider); - return normalized === "openai" || normalized === "openai-codex"; + return normalized === "openai"; } const MEMORY_FLUSH_ALLOWED_TOOL_NAMES = new Set(["read", "write"]); @@ -425,7 +425,7 @@ export function createOpenClawCodingTools(options?: { emitBeforeToolCallDiagnostics?: boolean; /** * Provider of the currently selected model (used for provider-specific tool quirks). - * Example: "anthropic", "openai", "google", "openai-codex". + * Example: "anthropic", "openai", "google", "openai". */ modelProvider?: string; /** Model id for the current provider (used for model-specific tool gating). */ diff --git a/src/agents/auth-health.test.ts b/src/agents/auth-health.test.ts index 287e6be2d120..591bff4ef684 100644 --- a/src/agents/auth-health.test.ts +++ b/src/agents/auth-health.test.ts @@ -13,8 +13,7 @@ vi.mock("./cli-credentials.js", () => ({ resetCliCredentialCachesForTest: () => undefined, })); vi.mock("./provider-auth-aliases.js", () => ({ - resolveProviderIdForAuth: (provider: string) => - provider === "codex-cli" ? "openai-codex" : provider, + resolveProviderIdForAuth: (provider: string) => (provider === "codex-cli" ? "openai" : provider), })); import { @@ -33,7 +32,7 @@ describe("buildAuthHealthSummary", () => { function mockFreshCodexCliCredentials() { readCodexCliCredentialsCachedMock.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "fresh-cli-access", refresh: "fresh-cli-refresh", expires: now + DEFAULT_OAUTH_WARN_MS + 60_000, @@ -50,9 +49,9 @@ describe("buildAuthHealthSummary", () => { return { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", ...params, }, }, @@ -126,23 +125,23 @@ describe("buildAuthHealthSummary", () => { const store = { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "stale-access", refresh: "stale-refresh", expires: now - 10_000, }, - "openai-codex:named": { + "openai:named": { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "fresh-access", refresh: "fresh-refresh", expires: now + DEFAULT_OAUTH_WARN_MS + 60_000, }, }, order: { - "openai-codex": ["openai-codex:named"], + openai: ["openai:named"], }, }; @@ -152,18 +151,18 @@ describe("buildAuthHealthSummary", () => { }); expect(profileStatuses(summary)).toEqual({ - "openai-codex:default": "expired", - "openai-codex:named": "ok", + "openai:default": "expired", + "openai:named": "ok", }); - const provider = summary.providers.find((entry) => entry.provider === "openai-codex"); + const provider = summary.providers.find((entry) => entry.provider === "openai"); expect(provider?.status).toBe("ok"); expect(provider?.expiresAt).toBe(now + DEFAULT_OAUTH_WARN_MS + 60_000); expect(provider?.effectiveProfiles?.map((profile) => profile.profileId)).toEqual([ - "openai-codex:named", + "openai:named", ]); expect(provider?.profiles.map((profile) => profile.profileId)).toEqual([ - "openai-codex:default", - "openai-codex:named", + "openai:default", + "openai:named", ]); }); @@ -181,7 +180,7 @@ describe("buildAuthHealthSummary", () => { }, }, order: { - "openai-codex": [], + openai: [], }, }; @@ -273,14 +272,14 @@ describe("buildAuthHealthSummary", () => { }); const statuses = profileStatuses(summary); - expect(statuses["openai-codex:default"]).toBe("expired"); + expect(statuses["openai:default"]).toBe("expired"); }); it("keeps healthy local oauth over fresher imported Codex CLI credentials in health status", () => { vi.spyOn(Date, "now").mockReturnValue(now); readCodexCliCredentialsCachedMock.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "fresh-cli-access", refresh: "fresh-cli-refresh", expires: now + 7 * DEFAULT_OAUTH_WARN_MS, @@ -289,9 +288,9 @@ describe("buildAuthHealthSummary", () => { const store = { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "healthy-local-access", refresh: "healthy-local-refresh", expires: now + DEFAULT_OAUTH_WARN_MS + 10_000, @@ -304,7 +303,7 @@ describe("buildAuthHealthSummary", () => { warnAfterMs: DEFAULT_OAUTH_WARN_MS, }); - const profile = summary.profiles.find((entry) => entry.profileId === "openai-codex:default"); + const profile = summary.profiles.find((entry) => entry.profileId === "openai:default"); expect(profile?.status).toBe("ok"); expect(profile?.expiresAt).toBe(now + DEFAULT_OAUTH_WARN_MS + 10_000); }); @@ -314,9 +313,9 @@ describe("buildAuthHealthSummary", () => { const store = { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "near-expiry-access", refresh: "near-expiry-refresh", expires: now + 2 * 60_000, @@ -329,7 +328,7 @@ describe("buildAuthHealthSummary", () => { warnAfterMs: 60_000, }); - const profile = summary.profiles.find((entry) => entry.profileId === "openai-codex:default"); + const profile = summary.profiles.find((entry) => entry.profileId === "openai:default"); expect(profile?.status).toBe("expiring"); }); @@ -347,7 +346,7 @@ describe("buildAuthHealthSummary", () => { warnAfterMs: 60_000, }); - const profile = summary.profiles.find((entry) => entry.profileId === "openai-codex:default"); + const profile = summary.profiles.find((entry) => entry.profileId === "openai:default"); expect(profile?.status).toBe("expiring"); expect(profile?.expiresAt).toBe(now + 2 * 60_000); }); @@ -382,9 +381,9 @@ describe("buildAuthHealthSummary", () => { const store = { version: 1, profiles: { - "openai-codex:bad-expiry": { + "openai:bad-expiry": { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "oauth-access", refresh: "oauth-refresh", expires: MAX_DATE_TIMESTAMP_MS + 1, @@ -397,8 +396,8 @@ describe("buildAuthHealthSummary", () => { warnAfterMs: DEFAULT_OAUTH_WARN_MS, }); - const profile = summary.profiles.find((entry) => entry.profileId === "openai-codex:bad-expiry"); - const provider = summary.providers.find((entry) => entry.provider === "openai-codex"); + const profile = summary.profiles.find((entry) => entry.profileId === "openai:bad-expiry"); + const provider = summary.providers.find((entry) => entry.provider === "openai"); expect(profile?.status).toBe("missing"); expect(profile?.expiresAt).toBeUndefined(); diff --git a/src/agents/auth-profiles.ensureauthprofilestore.test.ts b/src/agents/auth-profiles.ensureauthprofilestore.test.ts index 1f33faecfc6e..7d66c4815480 100644 --- a/src/agents/auth-profiles.ensureauthprofilestore.test.ts +++ b/src/agents/auth-profiles.ensureauthprofilestore.test.ts @@ -42,7 +42,7 @@ vi.mock("./cli-credentials.js", () => ({ } return { type: "oauth", - provider: "openai-codex", + provider: "openai", access, refresh, expires: Date.now() + 60 * 60 * 1000, @@ -308,15 +308,15 @@ describe("ensureAuthProfileStore", () => { const { mainDir, agentDir, previousStateDir, previousAgentDir } = configureMainAuthTestDirs(root); try { - const freshProfileId = "openai-codex:user@example.com"; - const staleProfileId = "openai-codex:default"; + const freshProfileId = "openai:user@example.com"; + const staleProfileId = "openai:default"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [freshProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-access", refresh: "main-refresh", expires: Date.now() + 60 * 60 * 1000, @@ -324,10 +324,10 @@ describe("ensureAuthProfileStore", () => { }, }, order: { - "openai-codex": [freshProfileId], + openai: [freshProfileId], }, lastGood: { - "openai-codex": freshProfileId, + openai: freshProfileId, }, }, mainDir, @@ -338,7 +338,7 @@ describe("ensureAuthProfileStore", () => { profiles: { [freshProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-identity-access", refresh: "stale-identity-refresh", expires: Date.now() - 30 * 60 * 1000, @@ -346,7 +346,7 @@ describe("ensureAuthProfileStore", () => { }, [staleProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-access", refresh: "stale-refresh", expires: Date.now() - 60 * 60 * 1000, @@ -354,10 +354,10 @@ describe("ensureAuthProfileStore", () => { }, }, order: { - "openai-codex": [staleProfileId], + openai: [staleProfileId], }, lastGood: { - "openai-codex": staleProfileId, + openai: staleProfileId, }, usageStats: { [staleProfileId]: { @@ -374,13 +374,13 @@ describe("ensureAuthProfileStore", () => { expectRecordFields(store.profiles[freshProfileId], { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-access", refresh: "main-refresh", }); expect(store.profiles[staleProfileId]).toBeUndefined(); - expect(store.order?.["openai-codex"]).toEqual([freshProfileId]); - expect(store.lastGood?.["openai-codex"]).toBe(freshProfileId); + expect(store.order?.["openai"]).toEqual([freshProfileId]); + expect(store.lastGood?.["openai"]).toBe(freshProfileId); expect(store.usageStats?.[staleProfileId]).toBeUndefined(); const persistedAgentStore = JSON.parse( @@ -398,15 +398,15 @@ describe("ensureAuthProfileStore", () => { const { mainDir, agentDir, previousStateDir, previousAgentDir } = configureMainAuthTestDirs(root); try { - const freshProfileId = "openai-codex:user@example.com"; - const staleProfileId = "openai-codex:default"; + const freshProfileId = "openai:user@example.com"; + const staleProfileId = "openai:default"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [freshProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "older-main-access", refresh: "older-main-refresh", expires: Date.now() + 30 * 60 * 1000, @@ -414,7 +414,7 @@ describe("ensureAuthProfileStore", () => { }, }, order: { - "openai-codex": [freshProfileId], + openai: [freshProfileId], }, }, mainDir, @@ -425,7 +425,7 @@ describe("ensureAuthProfileStore", () => { profiles: { [freshProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "newer-agent-access", refresh: "newer-agent-refresh", expires: Date.now() + 90 * 60 * 1000, @@ -433,7 +433,7 @@ describe("ensureAuthProfileStore", () => { }, [staleProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-access", refresh: "stale-refresh", expires: Date.now() - 60 * 60 * 1000, @@ -441,10 +441,10 @@ describe("ensureAuthProfileStore", () => { }, }, order: { - "openai-codex": [staleProfileId], + openai: [staleProfileId], }, lastGood: { - "openai-codex": staleProfileId, + openai: staleProfileId, }, }, agentDir, @@ -455,13 +455,13 @@ describe("ensureAuthProfileStore", () => { expectRecordFields(store.profiles[freshProfileId], { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "newer-agent-access", refresh: "newer-agent-refresh", }); expect(store.profiles[staleProfileId]).toBeUndefined(); - expect(store.order?.["openai-codex"]).toEqual([freshProfileId]); - expect(store.lastGood?.["openai-codex"]).toBe(freshProfileId); + expect(store.order?.["openai"]).toEqual([freshProfileId]); + expect(store.lastGood?.["openai"]).toBe(freshProfileId); } finally { restoreAgentDirEnv({ previousStateDir, previousAgentDir }); fs.rmSync(root, { recursive: true, force: true }); @@ -473,15 +473,15 @@ describe("ensureAuthProfileStore", () => { const { mainDir, agentDir, previousStateDir, previousAgentDir } = configureMainAuthTestDirs(root); try { - const freshProfileId = "openai-codex:user@example.com"; - const defaultProfileId = "openai-codex:default"; + const freshProfileId = "openai:user@example.com"; + const defaultProfileId = "openai:default"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [freshProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-access", refresh: "main-refresh", expires: Date.now() + 60 * 60 * 1000, @@ -489,14 +489,14 @@ describe("ensureAuthProfileStore", () => { }, [defaultProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-default-access", refresh: "main-default-refresh", expires: Date.now() + 45 * 60 * 1000, }, }, order: { - "openai-codex": [freshProfileId, defaultProfileId], + openai: [freshProfileId, defaultProfileId], }, usageStats: { [defaultProfileId]: { @@ -512,14 +512,14 @@ describe("ensureAuthProfileStore", () => { profiles: { [defaultProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-agent-default-access", refresh: "stale-agent-default-refresh", expires: Date.now() - 60 * 60 * 1000, }, }, order: { - "openai-codex": [defaultProfileId], + openai: [defaultProfileId], }, usageStats: { [defaultProfileId]: { @@ -534,10 +534,10 @@ describe("ensureAuthProfileStore", () => { const store = loadAuthProfileStoreForRuntime(agentDir, { readOnly: true }); - expect(store.order?.["openai-codex"]).toEqual([freshProfileId, defaultProfileId]); + expect(store.order?.["openai"]).toEqual([freshProfileId, defaultProfileId]); expectRecordFields(store.profiles[defaultProfileId], { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-default-access", }); expectRecordFields(store.usageStats?.[defaultProfileId], { @@ -554,15 +554,15 @@ describe("ensureAuthProfileStore", () => { const { mainDir, agentDir, previousStateDir, previousAgentDir } = configureMainAuthTestDirs(root); try { - const freshProfileId = "openai-codex:user@example.com"; - const staleProfileId = "openai-codex:default"; + const freshProfileId = "openai:user@example.com"; + const staleProfileId = "openai:default"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [freshProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-access", refresh: "main-refresh", expires: Date.now() + 60 * 60 * 1000, @@ -578,7 +578,7 @@ describe("ensureAuthProfileStore", () => { profiles: { [staleProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "other-access", refresh: "other-refresh", expires: Date.now() - 60 * 60 * 1000, @@ -586,10 +586,10 @@ describe("ensureAuthProfileStore", () => { }, }, order: { - "openai-codex": [staleProfileId], + openai: [staleProfileId], }, lastGood: { - "openai-codex": staleProfileId, + openai: staleProfileId, }, }, agentDir, @@ -601,32 +601,32 @@ describe("ensureAuthProfileStore", () => { expect(store.profiles).toHaveProperty(freshProfileId); expectRecordFields(store.profiles[staleProfileId], { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "other-access", }); - expect(store.order?.["openai-codex"]).toEqual([staleProfileId]); - expect(store.lastGood?.["openai-codex"]).toBe(staleProfileId); + expect(store.order?.["openai"]).toEqual([staleProfileId]); + expect(store.lastGood?.["openai"]).toBe(staleProfileId); } finally { restoreAgentDirEnv({ previousStateDir, previousAgentDir }); fs.rmSync(root, { recursive: true, force: true }); } }); - it("rewrites invalidated per-agent Codex order to the main agent's healthy relogin profile", () => { + it("keeps an invalidated identity-specific agent profile when the main agent has a different identity", () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-codex-relogin-")); const { mainDir, agentDir, previousStateDir, previousAgentDir } = configureMainAuthTestDirs(root); try { const now = Date.now(); - const healthyProfileId = "openai-codex:bunsthedev@gmail.com"; - const staleProfileId = "openai-codex:val@viewdue.ai"; + const healthyProfileId = "openai:bunsthedev@gmail.com"; + const staleProfileId = "openai:val@viewdue.ai"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [healthyProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "healthy-access", refresh: "healthy-refresh", expires: now + 60 * 60 * 1000, @@ -634,10 +634,10 @@ describe("ensureAuthProfileStore", () => { }, }, order: { - "openai-codex": [healthyProfileId], + openai: [healthyProfileId], }, lastGood: { - "openai-codex": healthyProfileId, + openai: healthyProfileId, }, }, mainDir, @@ -648,7 +648,7 @@ describe("ensureAuthProfileStore", () => { profiles: { [staleProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-access", refresh: "stale-refresh", expires: now + 30 * 60 * 1000, @@ -656,10 +656,10 @@ describe("ensureAuthProfileStore", () => { }, }, order: { - "openai-codex": [staleProfileId], + openai: [staleProfileId], }, lastGood: { - "openai-codex": staleProfileId, + openai: staleProfileId, }, usageStats: { [staleProfileId]: { @@ -679,13 +679,17 @@ describe("ensureAuthProfileStore", () => { expectRecordFields(store.profiles[healthyProfileId], { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "healthy-access", }); - expect(store.profiles[staleProfileId]).toBeUndefined(); - expect(store.order?.["openai-codex"]).toEqual([healthyProfileId]); - expect(store.lastGood?.["openai-codex"]).toBe(healthyProfileId); - expect(store.usageStats?.[staleProfileId]).toBeUndefined(); + expectRecordFields(store.profiles[staleProfileId], { + type: "oauth", + provider: "openai", + access: "stale-access", + }); + expect(store.order?.["openai"]).toEqual([staleProfileId]); + expect(store.lastGood?.["openai"]).toBe(staleProfileId); + expect(store.usageStats?.[staleProfileId]?.cooldownReason).toBe("auth"); } finally { restoreAgentDirEnv({ previousStateDir, previousAgentDir }); fs.rmSync(root, { recursive: true, force: true }); @@ -826,7 +830,7 @@ describe("ensureAuthProfileStore", () => { path.join(oauthDir, "oauth.json"), `${JSON.stringify( { - "openai-codex": { + openai: { access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -844,9 +848,9 @@ describe("ensureAuthProfileStore", () => { clearRuntimeAuthProfileStoreSnapshots(); const store = ensureAuthProfileStore(agentDir); - expectRecordFields(store.profiles["openai-codex:default"], { + expectRecordFields(store.profiles["openai:default"], { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", }); @@ -856,9 +860,9 @@ describe("ensureAuthProfileStore", () => { ) as { profiles: Record>; }; - const persistedProfile = persisted.profiles["openai-codex:default"]; + const persistedProfile = persisted.profiles["openai:default"]; expect(persistedProfile?.type).toBe("oauth"); - expect(persistedProfile?.provider).toBe("openai-codex"); + expect(persistedProfile?.provider).toBe("openai"); expect(persistedProfile?.access).toBe("access-token"); expect(persistedProfile?.refresh).toBe("refresh-token"); expect(persistedProfile).not.toHaveProperty("oauthRef"); @@ -970,9 +974,9 @@ describe("ensureAuthProfileStore", () => { { version: AUTH_STORE_VERSION, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-access", refresh: "main-refresh", expires: Date.now() + 60_000, @@ -989,9 +993,9 @@ describe("ensureAuthProfileStore", () => { const store = ensureAuthProfileStore(workerAgentDir); - expectRecordFields(store.profiles["openai-codex:default"], { + expectRecordFields(store.profiles["openai:default"], { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-access", }); expect(fs.existsSync(workerStorePath)).toBe(false); diff --git a/src/agents/auth-profiles.external-cli-scope.test.ts b/src/agents/auth-profiles.external-cli-scope.test.ts index 5d755d39b95d..e8a7c44e8ace 100644 --- a/src/agents/auth-profiles.external-cli-scope.test.ts +++ b/src/agents/auth-profiles.external-cli-scope.test.ts @@ -33,7 +33,7 @@ describe("external CLI auth scope", () => { expect(scope?.providerIds).toContain("opencode-go"); expect(scope?.profileIds).toEqual(["opencode-go:default"]); expect(scope?.providerIds).not.toContain("claude-cli"); - expect(scope?.providerIds).not.toContain("openai-codex"); + expect(scope?.providerIds).not.toContain("openai"); expect(scope?.providerIds).not.toContain("minimax-portal"); }); @@ -41,7 +41,7 @@ describe("external CLI auth scope", () => { const cfg = { auth: { order: { - "openai-codex": ["openai-codex:default"], + openai: ["openai:default"], }, }, agents: { @@ -80,12 +80,11 @@ describe("external CLI auth scope", () => { "elevenlabs", "minimax-portal", "openai", - "openai-codex", "opencode-go", "z.ai", ]); expect(scope?.providerIds).not.toContain("claude-cli"); - expect(scope?.profileIds).toContain("openai-codex:default"); + expect(scope?.profileIds).toContain("openai:default"); }); it("includes a CLI provider only when it is the active runtime", () => { diff --git a/src/agents/auth-profiles.external-cli-sync.test.ts b/src/agents/auth-profiles.external-cli-sync.test.ts index e485bfbec6e9..b86add430292 100644 --- a/src/agents/auth-profiles.external-cli-sync.test.ts +++ b/src/agents/auth-profiles.external-cli-sync.test.ts @@ -129,13 +129,13 @@ describe("external cli oauth resolution", () => { it("keeps equivalent stored credentials", () => { const expires = Date.now() + 60_000; const stored = makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "a", refresh: "r", expires, }); const incoming = makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "a", refresh: "r", expires, @@ -146,11 +146,11 @@ describe("external cli oauth resolution", () => { it("keeps the newer stored credential", () => { const incoming = makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", expires: Date.now() + 60_000, }); const stored = makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "fresh-access", refresh: "fresh-refresh", expires: Date.now() + 5 * 24 * 60 * 60_000, @@ -161,11 +161,11 @@ describe("external cli oauth resolution", () => { it("replaces when incoming credentials are fresher", () => { const stored = makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", expires: Date.now() + 60_000, }); const incoming = makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "new-access", refresh: "new-refresh", expires: Date.now() + 5 * 24 * 60 * 60_000, @@ -181,7 +181,7 @@ describe("external cli oauth resolution", () => { expect( hasUsableOAuthCredential( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "live-access", expires: Date.now() + 10 * 60_000, }), @@ -190,7 +190,7 @@ describe("external cli oauth resolution", () => { expect( hasUsableOAuthCredential( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "expired-access", expires: Date.now() - 60_000, }), @@ -199,7 +199,7 @@ describe("external cli oauth resolution", () => { expect( hasUsableOAuthCredential( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "near-expiry-access", expires: Date.now() + 60_000, }), @@ -208,7 +208,7 @@ describe("external cli oauth resolution", () => { expect( hasUsableOAuthCredential( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "", expires: Date.now() + 60_000, }), @@ -218,7 +218,7 @@ describe("external cli oauth resolution", () => { it("only bootstraps from external cli when the stored oauth is not usable", () => { const imported = makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "fresh-cli-access", refresh: "fresh-cli-refresh", expires: Date.now() + 5 * 24 * 60 * 60_000, @@ -228,7 +228,7 @@ describe("external cli oauth resolution", () => { expect( shouldBootstrapFromExternalCliCredential({ existing: makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "healthy-local-access", refresh: "healthy-local-refresh", expires: Date.now() + 10 * 60_000, @@ -239,7 +239,7 @@ describe("external cli oauth resolution", () => { expect( shouldBootstrapFromExternalCliCredential({ existing: makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "expired-local-access", refresh: "expired-local-refresh", expires: Date.now() - 60_000, @@ -251,7 +251,7 @@ describe("external cli oauth resolution", () => { expect( shouldBootstrapFromExternalCliCredential({ existing: makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "near-expiry-local-access", refresh: "near-expiry-local-refresh", expires: Date.now() + 60_000, @@ -263,7 +263,7 @@ describe("external cli oauth resolution", () => { it("refuses external oauth usage across different known identities", () => { const imported = makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "fresh-cli-access", refresh: "fresh-cli-refresh", expires: Date.now() + 5 * 24 * 60 * 60_000, @@ -273,7 +273,7 @@ describe("external cli oauth resolution", () => { expect( isSafeToUseExternalCliCredential( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "expired-local-access", refresh: "expired-local-refresh", expires: Date.now() - 60_000, @@ -288,7 +288,7 @@ describe("external cli oauth resolution", () => { it("does not use codex as a runtime bootstrap source anymore", () => { mocks.readCodexCliCredentialsCached.mockReturnValue( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "codex-access-token", refresh: "codex-refresh-token", }), @@ -296,7 +296,7 @@ describe("external cli oauth resolution", () => { const credential = readManagedExternalCliCredential({ profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID, - credential: makeOAuthCredential({ provider: "openai-codex" }), + credential: makeOAuthCredential({ provider: "openai" }), }); expect(credential).toBeNull(); @@ -305,7 +305,7 @@ describe("external cli oauth resolution", () => { it("bootstraps the default codex profile from Codex CLI credentials when in scope", () => { mocks.readCodexCliCredentialsCached.mockReturnValue( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "codex-cli-access", refresh: "codex-cli-refresh", expires: Date.now() + 5 * 24 * 60 * 60_000, @@ -314,7 +314,7 @@ describe("external cli oauth resolution", () => { ); const profiles = resolveExternalCliAuthProfiles(makeStore(), { - providerIds: ["openai-codex"], + providerIds: ["openai"], }); expectCredentialFields( @@ -331,7 +331,7 @@ describe("external cli oauth resolution", () => { it("keeps any existing default codex oauth over Codex CLI bootstrap credentials", () => { mocks.readCodexCliCredentialsCached.mockReturnValue( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "codex-cli-fresh-access", refresh: "codex-cli-fresh-refresh", expires: Date.now() + 5 * 24 * 60 * 60_000, @@ -343,7 +343,7 @@ describe("external cli oauth resolution", () => { makeStore( OPENAI_CODEX_DEFAULT_PROFILE_ID, makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "local-expired-access", refresh: "local-canonical-refresh", expires: Date.now() - 5_000, @@ -357,7 +357,7 @@ describe("external cli oauth resolution", () => { it("returns null when the profile id/provider do not map to the same external source", () => { mocks.readCodexCliCredentialsCached.mockReturnValue( - makeOAuthCredential({ provider: "openai-codex" }), + makeOAuthCredential({ provider: "openai" }), ); const credential = readManagedExternalCliCredential({ @@ -495,7 +495,7 @@ describe("external cli oauth resolution", () => { it("passes non-prompting keychain policy to scoped Codex CLI credential reads", () => { mocks.readCodexCliCredentialsCached.mockReturnValue( makeOAuthCredential({ - provider: "openai-codex", + provider: "openai", access: "codex-cli-access", refresh: "codex-cli-refresh", }), diff --git a/src/agents/auth-profiles.resolve-auth-profile-order.does-not-prioritize-lastgood-round-robin-ordering.test.ts b/src/agents/auth-profiles.resolve-auth-profile-order.does-not-prioritize-lastgood-round-robin-ordering.test.ts index b0c296f651db..26e0cbc4dd6e 100644 --- a/src/agents/auth-profiles.resolve-auth-profile-order.does-not-prioritize-lastgood-round-robin-ordering.test.ts +++ b/src/agents/auth-profiles.resolve-auth-profile-order.does-not-prioritize-lastgood-round-robin-ordering.test.ts @@ -304,66 +304,66 @@ describe("resolveAuthProfileOrder", () => { cfg: { auth: { profiles: { - "openai-codex:default": { - provider: "openai-codex", + "openai:default": { + provider: "openai", mode: "oauth", }, }, order: { - "openai-codex": ["openai-codex:default"], + openai: ["openai:default"], }, }, }, store: { version: 1, profiles: { - "openai-codex:user@example.com": { + "openai:user@example.com": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, }, }, }, - provider: "openai-codex", + provider: "openai", }); - expect(order).toEqual(["openai-codex:user@example.com"]); + expect(order).toEqual(["openai:user@example.com"]); }); it("does not bypass explicit ids when the configured profile exists but is invalid", () => { const order = resolveAuthProfileOrder({ cfg: { auth: { profiles: { - "openai-codex:default": { - provider: "openai-codex", + "openai:default": { + provider: "openai", mode: "token", }, }, order: { - "openai-codex": ["openai-codex:default"], + openai: ["openai:default"], }, }, }, store: { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "token", - provider: "openai-codex", + provider: "openai", token: "expired-token", expires: Date.now() - 1_000, }, - "openai-codex:user@example.com": { + "openai:user@example.com": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, }, }, }, - provider: "openai-codex", + provider: "openai", }); expect(order).toStrictEqual([]); }); diff --git a/src/agents/auth-profiles.store-cache.test.ts b/src/agents/auth-profiles.store-cache.test.ts index 8014c6f68a4e..25b4cde3bb1f 100644 --- a/src/agents/auth-profiles.store-cache.test.ts +++ b/src/agents/auth-profiles.store-cache.test.ts @@ -102,10 +102,10 @@ describe("auth profile store cache", () => { function createRuntimeOnlyOverlay(access: string): RuntimeOnlyOverlay { return { - profileId: "openai-codex:default", + profileId: "openai:default", credential: { type: "oauth", - provider: "openai-codex", + provider: "openai", access, refresh: `refresh-${access}`, expires: Date.now() + 60_000, @@ -134,10 +134,10 @@ describe("auth profile store cache", () => { const first = ensureAuthProfileStore(agentDir); const second = ensureAuthProfileStore(agentDir); - expect((first.profiles["openai-codex:default"] as OAuthCredential | undefined)?.access).toBe( + expect((first.profiles["openai:default"] as OAuthCredential | undefined)?.access).toBe( "access-1", ); - expect((second.profiles["openai-codex:default"] as OAuthCredential | undefined)?.access).toBe( + expect((second.profiles["openai:default"] as OAuthCredential | undefined)?.access).toBe( "access-2", ); expect(mocks.resolveExternalCliAuthProfiles).toHaveBeenCalledTimes(2); @@ -194,7 +194,7 @@ describe("auth profile store cache", () => { await withAgentDirEnv("openclaw-auth-store-missing-", (agentDir) => { const store = ensureAuthProfileStore(agentDir); - expect((store.profiles["openai-codex:default"] as OAuthCredential | undefined)?.access).toBe( + expect((store.profiles["openai:default"] as OAuthCredential | undefined)?.access).toBe( "access-1", ); expect(fs.existsSync(path.join(agentDir, "auth-profiles.json"))).toBe(false); diff --git a/src/agents/auth-profiles.store.save.test.ts b/src/agents/auth-profiles.store.save.test.ts index 702fcd6b9601..eb4f570bd9c6 100644 --- a/src/agents/auth-profiles.store.save.test.ts +++ b/src/agents/auth-profiles.store.save.test.ts @@ -2,8 +2,6 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { resolveOAuthDir } from "../config/paths.js"; -import { legacyOAuthSidecarTestUtils } from "./auth-profiles/legacy-oauth-sidecar.js"; import { resolveAuthStatePath, resolveAuthStorePath } from "./auth-profiles/paths.js"; import { getRuntimeAuthProfileStoreSnapshot } from "./auth-profiles/runtime-snapshots.js"; import { @@ -60,16 +58,16 @@ describe("saveAuthProfileStore", () => { const store: AuthProfileStore = { version: 1, profiles: { - "openai-codex:one": { + "openai:one": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-one", refresh: "refresh-one", expires: Date.now() + 60_000, }, - "openai-codex:two": { + "openai:two": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-two", refresh: "refresh-two", expires: Date.now() + 60_000, @@ -161,252 +159,6 @@ describe("saveAuthProfileStore", () => { } }); - it("preserves legacy oauthRef only as doctor migration metadata during saves", async () => { - const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-save-oauth-ref-")); - const authPath = resolveAuthStorePath(agentDir); - const oauthRef = { - source: "openclaw-credentials", - provider: "openai-codex", - id: "0123456789abcdef0123456789abcdef", - }; - try { - await fs.mkdir(agentDir, { recursive: true }); - await fs.writeFile( - authPath, - `${JSON.stringify( - { - version: 1, - profiles: { - "openai-codex:default": { - type: "oauth", - provider: "openai-codex", - expires: Date.now() + 60_000, - oauthRef, - }, - }, - }, - null, - 2, - )}\n`, - ); - - const legacyRuntimeStore = { - version: 1, - profiles: { - "openai-codex:default": { - type: "oauth", - provider: "openai-codex", - expires: Date.now() + 60_000, - }, - }, - } as unknown as AuthProfileStore; - - saveAuthProfileStore(legacyRuntimeStore, agentDir); - - let parsed = JSON.parse(await fs.readFile(authPath, "utf8")) as { - profiles: Record>; - }; - expect(parsed.profiles["openai-codex:default"]?.oauthRef).toEqual(oauthRef); - expect(ensureAuthProfileStore(agentDir).profiles["openai-codex:default"]).not.toHaveProperty( - "oauthRef", - ); - - saveAuthProfileStore( - { - version: 1, - profiles: { - "openai-codex:default": { - type: "oauth", - provider: "openai-codex", - access: "new-access-token", - refresh: "new-refresh-token", - expires: Date.now() + 60_000, - }, - }, - }, - agentDir, - ); - - parsed = JSON.parse(await fs.readFile(authPath, "utf8")) as { - profiles: Record>; - }; - expect(parsed.profiles["openai-codex:default"]).not.toHaveProperty("oauthRef"); - expect(parsed.profiles["openai-codex:default"]?.access).toBe("new-access-token"); - expect(parsed.profiles["openai-codex:default"]?.refresh).toBe("new-refresh-token"); - } finally { - clearRuntimeAuthProfileStoreSnapshots(); - await fs.rm(agentDir, { recursive: true, force: true }); - } - }); - - it("keeps rehydrated legacy oauthRef sidecar tokens runtime-only during ordinary saves", async () => { - const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-save-oauth-ref-")); - const authPath = resolveAuthStorePath(agentDir); - const previousOAuthDir = process.env.OPENCLAW_OAUTH_DIR; - const previousSecretKey = process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY; - process.env.OPENCLAW_OAUTH_DIR = path.join(agentDir, "credentials"); - process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY = "legacy-seed"; - const oauthRef = { - source: "openclaw-credentials" as const, - provider: "openai-codex" as const, - id: "0123456789abcdef0123456789abcdef", - }; - try { - await fs.mkdir(agentDir, { recursive: true }); - await fs.writeFile( - authPath, - `${JSON.stringify( - { - version: 1, - profiles: { - "openai-codex:default": { - type: "oauth", - provider: "openai-codex", - expires: Date.now() + 60_000, - oauthRef, - }, - }, - }, - null, - 2, - )}\n`, - ); - const sidecarPath = path.join(resolveOAuthDir(), "auth-profiles", `${oauthRef.id}.json`); - await fs.mkdir(path.dirname(sidecarPath), { recursive: true }); - await fs.writeFile( - sidecarPath, - `${JSON.stringify( - { - version: 1, - profileId: "openai-codex:default", - provider: "openai-codex", - encrypted: legacyOAuthSidecarTestUtils.encryptLegacyOAuthMaterial({ - ref: oauthRef, - profileId: "openai-codex:default", - provider: "openai-codex", - seed: "legacy-seed", - material: { - access: "legacy-access-token", - refresh: "legacy-refresh-token", - }, - }), - }, - null, - 2, - )}\n`, - ); - - const runtimeStore = ensureAuthProfileStore(agentDir); - expectProfileFields(runtimeStore.profiles["openai-codex:default"], { - access: "legacy-access-token", - refresh: "legacy-refresh-token", - }); - - delete process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY; - const clonedRuntimeStore = JSON.parse(JSON.stringify(runtimeStore)) as AuthProfileStore; - saveAuthProfileStore(clonedRuntimeStore, agentDir); - - const parsed = JSON.parse(await fs.readFile(authPath, "utf8")) as { - profiles: Record>; - }; - expect(parsed.profiles["openai-codex:default"]?.oauthRef).toEqual(oauthRef); - expect(parsed.profiles["openai-codex:default"]).not.toHaveProperty("access"); - expect(parsed.profiles["openai-codex:default"]).not.toHaveProperty("refresh"); - } finally { - if (previousOAuthDir === undefined) { - delete process.env.OPENCLAW_OAUTH_DIR; - } else { - process.env.OPENCLAW_OAUTH_DIR = previousOAuthDir; - } - if (previousSecretKey === undefined) { - delete process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY; - } else { - process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY = previousSecretKey; - } - clearRuntimeAuthProfileStoreSnapshots(); - await fs.rm(agentDir, { recursive: true, force: true }); - } - }); - - it("writes refreshed legacy sidecar tokens inline when they replace runtime sidecar material", async () => { - const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-save-oauth-ref-")); - const authPath = resolveAuthStorePath(agentDir); - const previousOAuthDir = process.env.OPENCLAW_OAUTH_DIR; - process.env.OPENCLAW_OAUTH_DIR = path.join(agentDir, "credentials"); - const profileId = "openai-codex:default"; - const oauthRef = { - source: "openclaw-credentials", - provider: "openai-codex", - id: "0123456789abcdef0123456789abcdef", - }; - try { - await fs.mkdir(agentDir, { recursive: true }); - await fs.writeFile( - authPath, - `${JSON.stringify( - { - version: 1, - profiles: { - [profileId]: { - type: "oauth", - provider: "openai-codex", - expires: Date.now() + 60_000, - oauthRef, - }, - }, - }, - null, - 2, - )}\n`, - ); - const sidecarPath = path.join(resolveOAuthDir(), "auth-profiles", `${oauthRef.id}.json`); - await fs.mkdir(path.dirname(sidecarPath), { recursive: true }); - await fs.writeFile( - sidecarPath, - `${JSON.stringify( - { - version: 1, - profileId, - provider: "openai-codex", - access: "legacy-access-token", - refresh: "legacy-refresh-token", - }, - null, - 2, - )}\n`, - ); - - const runtimeStore = ensureAuthProfileStore(agentDir); - const refreshedStore: AuthProfileStore = { - ...runtimeStore, - profiles: { - ...runtimeStore.profiles, - [profileId]: { - ...runtimeStore.profiles[profileId], - access: "refreshed-access-token", - refresh: "refreshed-refresh-token", - } as AuthProfileStore["profiles"][string], - }, - }; - saveAuthProfileStore(refreshedStore, agentDir); - - const parsed = JSON.parse(await fs.readFile(authPath, "utf8")) as { - profiles: Record>; - }; - expect(parsed.profiles[profileId]).not.toHaveProperty("oauthRef"); - expect(parsed.profiles[profileId]?.access).toBe("refreshed-access-token"); - expect(parsed.profiles[profileId]?.refresh).toBe("refreshed-refresh-token"); - } finally { - if (previousOAuthDir === undefined) { - delete process.env.OPENCLAW_OAUTH_DIR; - } else { - process.env.OPENCLAW_OAUTH_DIR = previousOAuthDir; - } - clearRuntimeAuthProfileStoreSnapshots(); - await fs.rm(agentDir, { recursive: true, force: true }); - } - }); - it("refreshes the runtime snapshot when a saved store rotates oauth tokens", async () => { const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-save-runtime-")); try { @@ -900,7 +652,7 @@ describe("saveAuthProfileStore", () => { it("does not persist runtime-only external profiles without an installed snapshot", async () => { const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-save-unsnapshotted-")); - const profileId = "openai-codex:default"; + const profileId = "openai:oauth"; try { saveAuthProfileStore( @@ -910,7 +662,7 @@ describe("saveAuthProfileStore", () => { profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "runtime-access", refresh: "runtime-refresh", expires: 1, @@ -934,7 +686,7 @@ describe("saveAuthProfileStore", () => { it("returns active runtime-only external profiles on unscoped reads", async () => { const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-read-runtime-only-")); - const profileId = "openai-codex:default"; + const profileId = "openai:oauth"; try { replaceRuntimeAuthProfileStoreSnapshots([ @@ -947,7 +699,7 @@ describe("saveAuthProfileStore", () => { profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "runtime-access", refresh: "runtime-refresh", expires: 1, @@ -965,7 +717,7 @@ describe("saveAuthProfileStore", () => { expect(store.runtimeExternalProfileIds).toEqual([profileId]); expectProfileFields(store.profiles[profileId], { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "runtime-access", refresh: "runtime-refresh", }); @@ -1286,9 +1038,9 @@ describe("saveAuthProfileStore", () => { saveAuthProfileStore({ version: 1, profiles: { - "openai-codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-access-token", refresh: "main-refresh-token", expires: Date.now() + 60_000, @@ -1297,7 +1049,7 @@ describe("saveAuthProfileStore", () => { }); const localUpdateStore = ensureAuthProfileStoreForLocalUpdate(childAgentDir); - expectProfileFields(localUpdateStore.profiles["openai-codex:default"], { + expectProfileFields(localUpdateStore.profiles["openai:oauth"], { type: "oauth", refresh: "main-refresh-token", }); @@ -1318,14 +1070,14 @@ describe("saveAuthProfileStore", () => { type: "api_key", provider: "openai", }); - expect(child.profiles["openai-codex:default"]).toBeUndefined(); + expect(child.profiles["openai:oauth"]).toBeUndefined(); saveAuthProfileStore({ version: 1, profiles: { - "openai-codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-refreshed-access-token", refresh: "main-refreshed-refresh-token", expires: Date.now() + 120_000, @@ -1333,7 +1085,7 @@ describe("saveAuthProfileStore", () => { }, }); - expectProfileFields(ensureAuthProfileStore(childAgentDir).profiles["openai-codex:default"], { + expectProfileFields(ensureAuthProfileStore(childAgentDir).profiles["openai:oauth"], { type: "oauth", access: "main-refreshed-access-token", refresh: "main-refreshed-refresh-token", @@ -1356,9 +1108,9 @@ describe("saveAuthProfileStore", () => { saveAuthProfileStore({ version: 1, profiles: { - "openai-codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-old-access-token", refresh: "main-old-refresh-token", expires: Date.now() + 60_000, @@ -1369,7 +1121,7 @@ describe("saveAuthProfileStore", () => { }); const localUpdateStore = ensureAuthProfileStoreForLocalUpdate(childAgentDir); - expectProfileFields(localUpdateStore.profiles["openai-codex:default"], { + expectProfileFields(localUpdateStore.profiles["openai:oauth"], { type: "oauth", refresh: "main-old-refresh-token", }); @@ -1377,9 +1129,9 @@ describe("saveAuthProfileStore", () => { saveAuthProfileStore({ version: 1, profiles: { - "openai-codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-refreshed-access-token", refresh: "main-refreshed-refresh-token", expires: Date.now() + 120_000, @@ -1405,8 +1157,8 @@ describe("saveAuthProfileStore", () => { type: "api_key", provider: "openai", }); - expect(child.profiles["openai-codex:default"]).toBeUndefined(); - expectProfileFields(ensureAuthProfileStore(childAgentDir).profiles["openai-codex:default"], { + expect(child.profiles["openai:oauth"]).toBeUndefined(); + expectProfileFields(ensureAuthProfileStore(childAgentDir).profiles["openai:oauth"], { type: "oauth", access: "main-refreshed-access-token", refresh: "main-refreshed-refresh-token", @@ -1429,9 +1181,9 @@ describe("saveAuthProfileStore", () => { saveAuthProfileStore({ version: 1, profiles: { - "openai-codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-access-token", refresh: "main-refresh-token", expires: Date.now() + 60_000, @@ -1459,14 +1211,14 @@ describe("saveAuthProfileStore", () => { const child = JSON.parse(await fs.readFile(childAuthPath, "utf8")) as { profiles: Record; }; - expect(child.profiles["openai-codex:default"]).toBeUndefined(); + expect(child.profiles["openai:oauth"]).toBeUndefined(); const runtime = ensureAuthProfileStore(childAgentDir); expectProfileFields(runtime.profiles["openai:default"], { type: "api_key", provider: "openai", }); - expectProfileFields(runtime.profiles["openai-codex:default"], { + expectProfileFields(runtime.profiles["openai:oauth"], { type: "oauth", access: "main-access-token", refresh: "main-refresh-token", @@ -1475,9 +1227,9 @@ describe("saveAuthProfileStore", () => { saveAuthProfileStore({ version: 1, profiles: { - "openai-codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-refreshed-access-token", refresh: "main-refreshed-refresh-token", expires: Date.now() + 120_000, @@ -1485,7 +1237,7 @@ describe("saveAuthProfileStore", () => { }, }); - expectProfileFields(ensureAuthProfileStore(childAgentDir).profiles["openai-codex:default"], { + expectProfileFields(ensureAuthProfileStore(childAgentDir).profiles["openai:oauth"], { type: "oauth", access: "main-refreshed-access-token", refresh: "main-refreshed-refresh-token", diff --git a/src/agents/auth-profiles/credential-state.test.ts b/src/agents/auth-profiles/credential-state.test.ts index 0922dec19aab..d9682241e9f0 100644 --- a/src/agents/auth-profiles/credential-state.test.ts +++ b/src/agents/auth-profiles/credential-state.test.ts @@ -49,7 +49,7 @@ describe("hasUsableOAuthCredential", () => { hasUsableOAuthCredential( { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: now + DEFAULT_OAUTH_REFRESH_MARGIN_MS - 1, @@ -112,7 +112,7 @@ describe("evaluateStoredCredentialEligibility", () => { const result = evaluateStoredCredentialEligibility({ credential: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "", refresh: "", expires: now + 60_000, diff --git a/src/agents/auth-profiles/display.test.ts b/src/agents/auth-profiles/display.test.ts index 5e326e8c5dc4..129a8c22e267 100644 --- a/src/agents/auth-profiles/display.test.ts +++ b/src/agents/auth-profiles/display.test.ts @@ -7,8 +7,8 @@ describe("resolveAuthProfileDisplayLabel", () => { cfg: { auth: { profiles: { - "openai-codex:id-abc": { - provider: "openai-codex", + "openai:id-abc": { + provider: "openai", mode: "oauth", displayName: "Work account", email: "work@example.com", @@ -17,10 +17,10 @@ describe("resolveAuthProfileDisplayLabel", () => { }, }, store: { version: 1, profiles: {} }, - profileId: "openai-codex:id-abc", + profileId: "openai:id-abc", }); - expect(label).toBe("openai-codex:id-abc (Work account)"); + expect(label).toBe("openai:id-abc (Work account)"); }); it("does not synthesize bogus labels when no human metadata exists", () => { @@ -28,18 +28,18 @@ describe("resolveAuthProfileDisplayLabel", () => { store: { version: 1, profiles: { - "openai-codex:id-abc": { + "openai:id-abc": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "token", refresh: "refresh-token", expires: Date.now() + 60_000, }, }, }, - profileId: "openai-codex:id-abc", + profileId: "openai:id-abc", }); - expect(label).toBe("openai-codex:id-abc"); + expect(label).toBe("openai:id-abc"); }); }); diff --git a/src/agents/auth-profiles/effective-oauth.test.ts b/src/agents/auth-profiles/effective-oauth.test.ts index ebfba7d3d0d4..c4cdbeb1ece1 100644 --- a/src/agents/auth-profiles/effective-oauth.test.ts +++ b/src/agents/auth-profiles/effective-oauth.test.ts @@ -13,7 +13,7 @@ vi.mock("./external-cli-sync.js", () => ({ function makeCredential(overrides: Partial = {}): OAuthCredential { return { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "local-access-token", refresh: "local-refresh-token", expires: Date.now() - 60_000, @@ -36,7 +36,7 @@ describe("resolveEffectiveOAuthCredential", () => { expect( resolveEffectiveOAuthCredential({ - profileId: "openai-codex:default", + profileId: "openai:default", credential: makeCredential(), }), ).toBe(imported); @@ -57,7 +57,7 @@ describe("resolveEffectiveOAuthCredential", () => { expect( resolveEffectiveOAuthCredential({ - profileId: "openai-codex:default", + profileId: "openai:default", credential: local, }), ).toBe(local); @@ -74,7 +74,7 @@ describe("resolveEffectiveOAuthCredential", () => { expect( resolveEffectiveOAuthCredential({ - profileId: "openai-codex:default", + profileId: "openai:default", credential: local, }), ).toBe(local); diff --git a/src/agents/auth-profiles/external-cli-scope.ts b/src/agents/auth-profiles/external-cli-scope.ts index c7e6a2aac4d9..4d1c1499f3e8 100644 --- a/src/agents/auth-profiles/external-cli-scope.ts +++ b/src/agents/auth-profiles/external-cli-scope.ts @@ -49,7 +49,7 @@ function addExternalCliRuntimeScope(out: Set, value: string | undefined) normalized === "codex" || normalized === "codex-cli" || normalized === "codex-app-server" || - normalized === "openai-codex" || + normalized === "openai" || normalized === "minimax" || normalized === "minimax-cli" || normalized === "minimax-portal" diff --git a/src/agents/auth-profiles/external-cli-sync.ts b/src/agents/auth-profiles/external-cli-sync.ts index 04e7e7e84e2c..feee7ced4971 100644 --- a/src/agents/auth-profiles/external-cli-sync.ts +++ b/src/agents/auth-profiles/external-cli-sync.ts @@ -99,9 +99,9 @@ export function isSafeToUseExternalCliCredential( const EXTERNAL_CLI_SYNC_PROVIDERS: ExternalCliSyncProvider[] = [ { profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID, - profileAliases: ["openai-codex:default"], + profileAliases: ["openai:default"], provider: "openai", - aliases: ["openai-codex", "codex", "codex-cli", "codex-app-server"], + aliases: ["openai", "codex", "codex-cli", "codex-app-server"], readCredentials: (options) => readCodexCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS, @@ -184,7 +184,7 @@ function externalCliProfileIdMatches( return false; } const normalizedPrefix = normalizeProviderId(getAuthProfileProviderPrefix(profileId)); - return normalizedPrefix === "openai-codex"; + return normalizedPrefix === "openai"; } function hasInlineOAuthTokenMaterial(credential: OAuthCredential): boolean { diff --git a/src/agents/auth-profiles/external-oauth.test.ts b/src/agents/auth-profiles/external-oauth.test.ts index 15f0de4127de..adc2cd69c093 100644 --- a/src/agents/auth-profiles/external-oauth.test.ts +++ b/src/agents/auth-profiles/external-oauth.test.ts @@ -28,7 +28,7 @@ function createStore(profiles: AuthProfileStore["profiles"] = {}): AuthProfileSt function createCredential(overrides: Partial = {}): OAuthCredential { return { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: 123, @@ -68,23 +68,23 @@ describe("auth external oauth helpers", () => { it("overlays provider-managed runtime oauth profiles onto the store", () => { resolveExternalAuthProfilesWithPluginsMock.mockReturnValueOnce([ { - profileId: "openai-codex:default", + profileId: "openai:default", credential: createCredential(), }, ]); const store = overlayExternalOAuthProfiles(createStore()); - const profile = requireProfile(store, "openai-codex:default"); + const profile = requireProfile(store, "openai:default"); expect(profile.type).toBe("oauth"); - expect(profile.provider).toBe("openai-codex"); + expect(profile.provider).toBe("openai"); expect(profile.access).toBe("access-token"); }); it("passes config and CLI scope through overlay resolution", () => { const cfg = { models: { - providers: { "openai-codex": { auth: "oauth" as const, baseUrl: "", models: [] } }, + providers: { openai: { auth: "oauth" as const, baseUrl: "", models: [] } }, }, }; readCodexCliCredentialsCachedMock.mockReturnValueOnce(createCredential()); @@ -92,7 +92,7 @@ describe("auth external oauth helpers", () => { overlayExternalOAuthProfiles(createStore(), { allowKeychainPrompt: false, config: cfg, - externalCliProviderIds: ["openai-codex"], + externalCliProviderIds: ["openai"], }); const resolveParams = requireRecord( @@ -133,14 +133,14 @@ describe("auth external oauth helpers", () => { const credential = createCredential(); resolveExternalAuthProfilesWithPluginsMock.mockReturnValueOnce([ { - profileId: "openai-codex:default", + profileId: "openai:default", credential, }, ]); const shouldPersist = shouldPersistExternalOAuthProfile({ - store: createStore({ "openai-codex:default": credential }), - profileId: "openai-codex:default", + store: createStore({ "openai:default": credential }), + profileId: "openai:default", credential, }); @@ -151,15 +151,15 @@ describe("auth external oauth helpers", () => { const credential = createCredential(); resolveExternalAuthProfilesWithPluginsMock.mockReturnValueOnce([ { - profileId: "openai-codex:default", + profileId: "openai:default", credential, persistence: "persisted", }, ]); const shouldPersist = shouldPersistExternalOAuthProfile({ - store: createStore({ "openai-codex:default": credential }), - profileId: "openai-codex:default", + store: createStore({ "openai:default": credential }), + profileId: "openai:default", credential, }); @@ -170,14 +170,14 @@ describe("auth external oauth helpers", () => { const credential = createCredential(); resolveExternalAuthProfilesWithPluginsMock.mockReturnValueOnce([ { - profileId: "openai-codex:default", + profileId: "openai:default", credential: createCredential({ access: "fresh-access-token" }), }, ]); const shouldPersist = shouldPersistExternalOAuthProfile({ - store: createStore({ "openai-codex:default": credential }), - profileId: "openai-codex:default", + store: createStore({ "openai:default": credential }), + profileId: "openai:default", credential, }); @@ -196,7 +196,7 @@ describe("auth external oauth helpers", () => { const overlaid = overlayExternalOAuthProfiles( createStore({ - "openai-codex:default": createCredential({ + "openai:default": createCredential({ access: "stale-store-access-token", refresh: "stale-store-refresh-token", expires: Date.now() - 60_000, @@ -205,7 +205,7 @@ describe("auth external oauth helpers", () => { }), ); - const profile = requireProfile(overlaid, "openai-codex:default"); + const profile = requireProfile(overlaid, "openai:default"); expect(profile.access).toBe("stale-store-access-token"); expect(profile.refresh).toBe("stale-store-refresh-token"); expect(profile.accountId).toBe("acct-cli"); @@ -220,7 +220,7 @@ describe("auth external oauth helpers", () => { }); const tokenlessCredential = { type: "oauth", - provider: "openai-codex", + provider: "openai", expires: Date.now() - 60_000, accountId: "acct-cli", } as OAuthCredential; @@ -228,11 +228,11 @@ describe("auth external oauth helpers", () => { const overlaid = overlayExternalOAuthProfiles( createStore({ - "openai-codex:default": tokenlessCredential, + "openai:default": tokenlessCredential, }), ); - const overlaidProfile = overlaid.profiles["openai-codex:default"]; + const overlaidProfile = overlaid.profiles["openai:default"]; expect(overlaidProfile?.type).toBe("oauth"); if (!overlaidProfile || overlaidProfile.type !== "oauth") { throw new Error("expected overlaid OAuth profile"); @@ -241,7 +241,7 @@ describe("auth external oauth helpers", () => { expect(overlaidProfile.refresh).toBe("fresh-cli-refresh-token"); expect(overlaidProfile.accountId).toBe("acct-cli"); const managedCredential = readManagedExternalCliCredential({ - profileId: "openai-codex:default", + profileId: "openai:default", credential: tokenlessCredential, }); expect(managedCredential?.access).toBe("fresh-cli-access-token"); @@ -260,7 +260,7 @@ describe("auth external oauth helpers", () => { const overlaid = overlayExternalOAuthProfiles( createStore({ - "openai-codex:default": createCredential({ + "openai:default": createCredential({ access: "healthy-local-access-token", refresh: "healthy-local-refresh-token", expires: createUsableOAuthExpiry(), @@ -268,7 +268,7 @@ describe("auth external oauth helpers", () => { }), ); - const profile = requireProfile(overlaid, "openai-codex:default"); + const profile = requireProfile(overlaid, "openai:default"); expect(profile.access).toBe("healthy-local-access-token"); expect(profile.refresh).toBe("healthy-local-refresh-token"); }); @@ -284,17 +284,17 @@ describe("auth external oauth helpers", () => { const overlaid = overlayExternalOAuthProfiles( createStore({ - "openai-codex:default": { + "openai:default": { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-local", }, }), ); - const profile = requireProfile(overlaid, "openai-codex:default"); + const profile = requireProfile(overlaid, "openai:default"); expect(profile.type).toBe("api_key"); - expect(profile.provider).toBe("openai-codex"); + expect(profile.provider).toBe("openai"); expect(profile.key).toBe("sk-local"); }); @@ -310,7 +310,7 @@ describe("auth external oauth helpers", () => { const overlaid = overlayExternalOAuthProfiles( createStore({ - "openai-codex:default": createCredential({ + "openai:default": createCredential({ access: "expired-local-access-token", refresh: "expired-local-refresh-token", expires: Date.now() - 60_000, @@ -319,7 +319,7 @@ describe("auth external oauth helpers", () => { }), ); - const profile = requireProfile(overlaid, "openai-codex:default"); + const profile = requireProfile(overlaid, "openai:default"); expect(profile.access).toBe("expired-local-access-token"); expect(profile.refresh).toBe("expired-local-refresh-token"); expect(profile.accountId).toBe("acct-local"); diff --git a/src/agents/auth-profiles/oauth-identity.test.ts b/src/agents/auth-profiles/oauth-identity.test.ts index d9b4e2a9e0b0..a5598612193c 100644 --- a/src/agents/auth-profiles/oauth-identity.test.ts +++ b/src/agents/auth-profiles/oauth-identity.test.ts @@ -319,7 +319,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { }; const refreshed = { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "fresh-access", refresh: "fresh-refresh", expires: 2_000, @@ -337,7 +337,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { name: "matching older oauth credential", existing: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "old", refresh: "old-refresh", expires: 1_000, @@ -350,7 +350,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { name: "non-finite existing expiry", existing: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "old", refresh: "old-refresh", expires: Number.NaN, @@ -363,7 +363,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { name: "out-of-range existing expiry", existing: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "old", refresh: "old-refresh", expires: MAX_DATE_TIMESTAMP_MS + 1, @@ -380,7 +380,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { }, existing: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "old", refresh: "old-refresh", expires: 1_000, @@ -393,7 +393,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { name: "identity upgrade", existing: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "old", refresh: "old-refresh", expires: 1_000, @@ -405,7 +405,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { name: "api key override", existing: { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "operator-key", }, shouldMirror: false, @@ -428,7 +428,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { name: "identity mismatch", existing: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "old", refresh: "old-refresh", expires: 1_000, @@ -441,7 +441,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { name: "strictly fresher existing credential", existing: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-fresh", refresh: "main-fresh-refresh", expires: 3_000, @@ -469,7 +469,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { shouldMirrorRefreshedOAuthCredential({ existing: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "main-identity-access", refresh: "main-identity-refresh", expires: 1_000, @@ -477,7 +477,7 @@ describe("shouldMirrorRefreshedOAuthCredential", () => { }, refreshed: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "fresh-access", refresh: "fresh-refresh", expires: 2_000, diff --git a/src/agents/auth-profiles/oauth-lock-path.test.ts b/src/agents/auth-profiles/oauth-lock-path.test.ts index d64e87ecdee9..9d2c57cc9d48 100644 --- a/src/agents/auth-profiles/oauth-lock-path.test.ts +++ b/src/agents/auth-profiles/oauth-lock-path.test.ts @@ -31,8 +31,8 @@ describe("resolveOAuthRefreshLockPath", () => { it("keeps lock paths inside the oauth-refresh directory for dot-segment ids", () => { const refreshLockDir = path.join(stateDir, "locks", "oauth-refresh"); - const dotSegmentPath = resolveOAuthRefreshLockPath("openai-codex", ".."); - const currentDirPath = resolveOAuthRefreshLockPath("openai-codex", "."); + const dotSegmentPath = resolveOAuthRefreshLockPath("openai", ".."); + const currentDirPath = resolveOAuthRefreshLockPath("openai", "."); expect(path.dirname(dotSegmentPath)).toBe(refreshLockDir); expect(path.dirname(currentDirPath)).toBe(refreshLockDir); @@ -42,19 +42,19 @@ describe("resolveOAuthRefreshLockPath", () => { }); it("hashes profile ids so distinct values stay distinct", () => { - expect(resolveOAuthRefreshLockPath("openai-codex", "openai-codex:work/test")).not.toBe( - resolveOAuthRefreshLockPath("openai-codex", "openai-codex_work:test"), + expect(resolveOAuthRefreshLockPath("openai", "openai:work/test")).not.toBe( + resolveOAuthRefreshLockPath("openai", "openai_work:test"), ); // Unicode normalization / collation corner cases must still hash distinctly. - expect(resolveOAuthRefreshLockPath("openai-codex", "«c")).not.toBe( - resolveOAuthRefreshLockPath("openai-codex", "઼"), + expect(resolveOAuthRefreshLockPath("openai", "«c")).not.toBe( + resolveOAuthRefreshLockPath("openai", "઼"), ); }); it("hashes distinct providers to distinct paths for the same profileId", () => { // The new (provider, profileId) keying is the whole point of P2 from // review: a shared profileId across providers must not collide. - expect(resolveOAuthRefreshLockPath("openai-codex", "shared:default")).not.toBe( + expect(resolveOAuthRefreshLockPath("openai", "shared:default")).not.toBe( resolveOAuthRefreshLockPath("anthropic", "shared:default"), ); }); @@ -69,16 +69,16 @@ describe("resolveOAuthRefreshLockPath", () => { }); it("keeps lock filenames short for long profile ids", () => { - const longProfileId = `openai-codex:${"x".repeat(512)}`; - const basename = path.basename(resolveOAuthRefreshLockPath("openai-codex", longProfileId)); + const longProfileId = `openai:${"x".repeat(512)}`; + const basename = path.basename(resolveOAuthRefreshLockPath("openai", longProfileId)); expect(basename).toMatch(/^sha256-[0-9a-f]{64}$/); expect(Buffer.byteLength(basename, "utf8")).toBeLessThan(255); }); it("is deterministic: same (provider, profileId) produces the same path", () => { - const first = resolveOAuthRefreshLockPath("openai-codex", "openai-codex:default"); - const second = resolveOAuthRefreshLockPath("openai-codex", "openai-codex:default"); + const first = resolveOAuthRefreshLockPath("openai", "openai:default"); + const second = resolveOAuthRefreshLockPath("openai", "openai:default"); expect(first).toBe(second); }); @@ -92,7 +92,7 @@ describe("resolveOAuthRefreshLockPath", () => { // Sanity precondition: parent dir must not exist yet. await expectPathMissing(locksDir); - const resolved = resolveOAuthRefreshLockPath("openai-codex", "openai-codex:default"); + const resolved = resolveOAuthRefreshLockPath("openai", "openai:default"); expect(path.dirname(resolved)).toBe(locksDir); expect(path.basename(resolved)).toMatch(/^sha256-[0-9a-f]{64}$/); // Function itself must not create the directory (path resolver only). @@ -101,13 +101,13 @@ describe("resolveOAuthRefreshLockPath", () => { it("never embeds path separators or .. in the basename", () => { const hazards = [ - ["openai-codex", "../etc/passwd"], - ["openai-codex", "../../../../secrets"], - ["openai-codex", "openai\\codex"], - ["openai-codex", "openai/codex/default"], - ["openai-codex", "profile\x00with-null"], - ["openai-codex", "profile\nwith-newline"], - ["openai-codex", "profile with spaces"], + ["openai", "../etc/passwd"], + ["openai", "../../../../secrets"], + ["openai", "openai\\codex"], + ["openai", "openai/codex/default"], + ["openai", "profile\x00with-null"], + ["openai", "profile\nwith-newline"], + ["openai", "profile with spaces"], ["../../etc", "passwd"], ["provider\x00with-null", "default"], ] as const; @@ -174,7 +174,7 @@ describe("resolveOAuthRefreshLockPath fuzz", () => { it("always produces a basename that matches sha256- regardless of input", () => { const rng = makeSeededRandom(0x2026_0417); for (let i = 0; i < 500; i += 1) { - const provider = randomProfileId(rng, 64) || "openai-codex"; + const provider = randomProfileId(rng, 64) || "openai"; const id = randomProfileId(rng, 4096); const basename = path.basename(resolveOAuthRefreshLockPath(provider, id)); expect(basename).toMatch(/^sha256-[0-9a-f]{64}$/); @@ -194,7 +194,7 @@ describe("resolveOAuthRefreshLockPath fuzz", () => { const rng = makeSeededRandom(0xdecafbad); const expectedDir = path.join(stateDir, "locks", "oauth-refresh"); for (let i = 0; i < 200; i += 1) { - const provider = randomProfileId(rng, 32) || "openai-codex"; + const provider = randomProfileId(rng, 32) || "openai"; const id = randomProfileId(rng, 1024); const resolved = resolveOAuthRefreshLockPath(provider, id); expect(path.dirname(resolved)).toBe(expectedDir); @@ -228,7 +228,7 @@ describe("resolveOAuthRefreshLockPath fuzz", () => { let collisions = 0; for (let i = 0; i < 1000; i += 1) { const id = randomProfileId(rng, 128) || `id-${i}`; - const resolved = resolveOAuthRefreshLockPath("openai-codex", id); + const resolved = resolveOAuthRefreshLockPath("openai", id); const existing = seen.get(resolved); if (existing !== undefined && existing !== id) { collisions += 1; diff --git a/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts b/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts index ba5aab243903..9bd1c6df43f1 100644 --- a/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts +++ b/src/agents/auth-profiles/oauth-lock-timeout-classification.test.ts @@ -15,8 +15,8 @@ function createLockTimeoutError(lockPath: string): FileLockTimeoutError { describe("OAuth refresh lock timeout classification", () => { it("matches only the global refresh lock path", () => { - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const refreshLockPath = resolveOAuthRefreshLockPath(provider, profileId); const authStoreLockPath = resolveAuthStorePath("/tmp/openclaw-oauth-lock-timeout/agent"); @@ -35,8 +35,8 @@ describe("OAuth refresh lock timeout classification", () => { }); it("builds refresh_contention errors that preserve the file-lock cause", () => { - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const refreshLockPath = resolveOAuthRefreshLockPath(provider, profileId); const cause = createLockTimeoutError(`${refreshLockPath}.lock`); diff --git a/src/agents/auth-profiles/oauth-manager.test.ts b/src/agents/auth-profiles/oauth-manager.test.ts index 8e0b215da719..6ef280c2e7c0 100644 --- a/src/agents/auth-profiles/oauth-manager.test.ts +++ b/src/agents/auth-profiles/oauth-manager.test.ts @@ -2,12 +2,10 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { resolveOAuthDir } from "../../config/paths.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { formatErrorMessage } from "../../infra/errors.js"; import { captureEnv } from "../../test-utils/env.js"; import { testing as externalAuthTesting } from "./external-auth.js"; -import { legacyOAuthSidecarTestUtils } from "./legacy-oauth-sidecar.js"; import { createOAuthManager, isSafeToAdoptBootstrapOAuthIdentity, @@ -27,7 +25,7 @@ import type { AuthProfileStore, OAuthCredential } from "./types.js"; function createCredential(overrides: Partial = {}): OAuthCredential { return { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -141,7 +139,7 @@ describe("OAuthManagerRefreshError", () => { const refreshedStore: AuthProfileStore = { version: 1, profiles: { - "openai-codex:default": createCredential({ + "openai:oauth": createCredential({ access: "store-access", refresh: "store-refresh", }), @@ -149,14 +147,14 @@ describe("OAuthManagerRefreshError", () => { }; const error = new OAuthManagerRefreshError({ credential: createCredential({ access: "error-access", refresh: "error-refresh" }), - profileId: "openai-codex:default", + profileId: "openai:oauth", refreshedStore, cause: new Error("boom"), }); const serialized = JSON.stringify(error); - expect(serialized).toContain("openai-codex"); - expect(serialized).toContain("openai-codex:default"); + expect(serialized).toContain("openai"); + expect(serialized).toContain("openai:oauth"); expect(serialized).not.toContain("error-access"); expect(serialized).not.toContain("error-refresh"); expect(serialized).not.toContain("store-access"); @@ -167,7 +165,7 @@ describe("OAuthManagerRefreshError", () => { const refreshedStore: AuthProfileStore = { version: 1, profiles: { - "openai-codex:default": createCredential({ + "openai:oauth": createCredential({ access: "store-access", refresh: "store-refresh", idToken: "store-id-token", @@ -180,7 +178,7 @@ describe("OAuthManagerRefreshError", () => { refresh: "error-refresh", idToken: "error-id-token", }), - profileId: "openai-codex:default", + profileId: "openai:oauth", refreshedStore, cause: new Error( "refresh rejected error-access error-refresh error-id-token store-access store-refresh store-id-token", @@ -210,7 +208,7 @@ describe("OAuthManagerRefreshError", () => { const refresh = "ya29.oauthreviewredaction1234567890yyyy"; const error = new OAuthManagerRefreshError({ credential: createCredential({ access, refresh }), - profileId: "openai-codex:default", + profileId: "openai:oauth", refreshedStore: { version: 1, profiles: {} }, cause: new Error(`refresh rejected ${access} ${refresh}`, { cause: new Error(`nested failure ${access}`), @@ -236,7 +234,7 @@ describe("OAuthManagerRefreshError", () => { credential: createCredential({ access: "sk-nonjsonredaction1234567890zzzz", }), - profileId: "openai-codex:default", + profileId: "openai:oauth", refreshedStore: { version: 1, profiles: {} }, cause, }); @@ -251,7 +249,7 @@ describe("OAuthManagerRefreshError", () => { access: "abc123", refresh: "abc123456", }), - profileId: "openai-codex:default", + profileId: "openai:oauth", refreshedStore: { version: 1, profiles: {} }, cause: new Error("refresh rejected abc123 abc123456"), }); @@ -266,12 +264,12 @@ describe("OAuthManagerRefreshError", () => { describe("createOAuthManager", () => { it("passes active config to OAuth API-key formatting", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:oauth"; const credential = createCredential({ expires: Date.now() + 10 * 60_000 }); const cfg = { models: { providers: { - "openai-codex": { auth: "oauth", baseUrl: "", models: [] }, + openai: { auth: "oauth", baseUrl: "", models: [] }, }, }, } satisfies OpenClawConfig; @@ -299,7 +297,7 @@ describe("createOAuthManager", () => { } expect(result.apiKey).toBe("access-token"); - expect(buildApiKey).toHaveBeenCalledWith("openai-codex", credential, { + expect(buildApiKey).toHaveBeenCalledWith("openai", credential, { cfg, agentDir: undefined, }); @@ -315,7 +313,7 @@ describe("createOAuthManager", () => { await fs.mkdir(agentDir, { recursive: true }); await fs.mkdir(mainAgentDir, { recursive: true }); - const profileId = "openai-codex:default"; + const profileId = "openai:oauth"; const subCredential = createCredential({ access: "expired-sub-access", refresh: "sub-refresh", @@ -454,111 +452,13 @@ describe("createOAuthManager", () => { expect(result.credential.refresh).toBe("rotated-refresh"); }); - it("refreshes legacy oauthRef sidecar credentials and writes rotated tokens inline", async () => { - const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "oauth-manager-legacy-ref-")); - tempDirs.push(tempRoot); - process.env.OPENCLAW_STATE_DIR = tempRoot; - process.env.OPENCLAW_OAUTH_DIR = path.join(tempRoot, "credentials"); - process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY = "legacy-seed"; - const agentDir = path.join(tempRoot, "agents", "main", "agent"); - const profileId = "openai-codex:default"; - const ref = { - source: "openclaw-credentials" as const, - provider: "openai-codex" as const, - id: "0123456789abcdef0123456789abcdef", - }; - await fs.mkdir(agentDir, { recursive: true }); - await fs.writeFile( - resolveAuthStorePath(agentDir), - `${JSON.stringify( - { - version: 1, - profiles: { - [profileId]: { - type: "oauth", - provider: "openai-codex", - expires: Date.now() - 60_000, - oauthRef: ref, - }, - }, - }, - null, - 2, - )}\n`, - ); - const sidecarPath = path.join(resolveOAuthDir(), "auth-profiles", `${ref.id}.json`); - await fs.mkdir(path.dirname(sidecarPath), { recursive: true }); - await fs.writeFile( - sidecarPath, - `${JSON.stringify( - { - version: 1, - profileId, - provider: "openai-codex", - encrypted: legacyOAuthSidecarTestUtils.encryptLegacyOAuthMaterial({ - ref, - profileId, - provider: "openai-codex", - seed: "legacy-seed", - material: { - access: "legacy-access", - refresh: "legacy-refresh", - }, - }), - }, - null, - 2, - )}\n`, - ); - - const store = ensureAuthProfileStore(agentDir); - const credential = store.profiles[profileId]; - expect(credential?.type).toBe("oauth"); - expect(credential).toMatchObject({ - access: "legacy-access", - refresh: "legacy-refresh", - }); - const refreshCredential = vi.fn(async (input: OAuthCredential) => { - expect(input.refresh).toBe("legacy-refresh"); - return { - access: "rotated-access", - refresh: "rotated-refresh", - expires: Date.now() + 60_000, - }; - }); - const manager = createOAuthManager({ - buildApiKey: async (_provider, value) => value.access, - refreshCredential, - readBootstrapCredential: () => null, - isRefreshTokenReusedError: () => false, - }); - - const result = await manager.resolveOAuthAccess({ - store, - profileId, - credential: credential as OAuthCredential, - agentDir, - }); - - expect(refreshCredential).toHaveBeenCalledTimes(1); - expect(result?.apiKey).toBe("rotated-access"); - const parsed = JSON.parse(await fs.readFile(resolveAuthStorePath(agentDir), "utf8")) as { - profiles: Record>; - }; - expect(parsed.profiles[profileId]).not.toHaveProperty("oauthRef"); - expect(parsed.profiles[profileId]).toMatchObject({ - access: "rotated-access", - refresh: "rotated-refresh", - }); - }); - it("skips the refresh adapter when the credential has no refresh token", async () => { const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "oauth-manager-no-refresh-")); tempDirs.push(tempRoot); process.env.OPENCLAW_STATE_DIR = tempRoot; const agentDir = path.join(tempRoot, "agents", "main", "agent"); await fs.mkdir(agentDir, { recursive: true }); - const profileId = "openai-codex:default"; + const profileId = "openai:oauth"; const credential = createCredential({ access: "", refresh: "", diff --git a/src/agents/auth-profiles/oauth-manager.ts b/src/agents/auth-profiles/oauth-manager.ts index 6a8564aa7ce4..8ceb7ffd2cab 100644 --- a/src/agents/auth-profiles/oauth-manager.ts +++ b/src/agents/auth-profiles/oauth-manager.ts @@ -237,7 +237,6 @@ function createRedactedOAuthRefreshCause(cause: unknown, secrets: string[]): Err function loadStoredOAuthRefreshStore(agentDir?: string): AuthProfileStore { return loadAuthProfileStoreWithoutExternalProfiles(agentDir, { allowKeychainPrompt: true, - resolveLegacyOAuthSidecars: true, }); } diff --git a/src/agents/auth-profiles/oauth-refresh-failure.test.ts b/src/agents/auth-profiles/oauth-refresh-failure.test.ts new file mode 100644 index 000000000000..e08dda8f5166 --- /dev/null +++ b/src/agents/auth-profiles/oauth-refresh-failure.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; +import { + buildOAuthRefreshFailureLoginCommand, + classifyOAuthRefreshFailure, +} from "./oauth-refresh-failure.js"; + +describe("oauth refresh failure hints", () => { + it("canonicalizes retired OpenAI provider ids in refresh-failure login hints", () => { + const legacyProvider = ["openai", "codex"].join("-"); + + expect( + classifyOAuthRefreshFailure( + `OAuth token refresh failed for ${legacyProvider}: invalid_grant`, + ), + ).toEqual({ + provider: "openai", + reason: "invalid_grant", + }); + expect(buildOAuthRefreshFailureLoginCommand(legacyProvider)).toBe( + "openclaw models auth login --provider openai", + ); + }); +}); diff --git a/src/agents/auth-profiles/oauth-refresh-failure.ts b/src/agents/auth-profiles/oauth-refresh-failure.ts index 7aab0fbd3595..05364edf1cc5 100644 --- a/src/agents/auth-profiles/oauth-refresh-failure.ts +++ b/src/agents/auth-profiles/oauth-refresh-failure.ts @@ -11,7 +11,7 @@ export type OAuthRefreshFailureReason = const OAUTH_REFRESH_FAILURE_PROVIDER_RE = /OAuth token refresh failed for ([^:]+):/i; const SAFE_PROVIDER_ID_RE = /^[a-z0-9][a-z0-9._-]*$/; -const LEGACY_OPENAI_CODEX_PROVIDER_ID = "openai-codex"; +const LEGACY_OPENAI_CODEX_PROVIDER_ID = ["openai", "codex"].join("-"); const OPENAI_PROVIDER_ID = "openai"; function isOAuthRefreshFailureMessage(message: string): boolean { diff --git a/src/agents/auth-profiles/oauth-refresh-queue.test.ts b/src/agents/auth-profiles/oauth-refresh-queue.test.ts index 253e5690aab2..0d9da9f71de5 100644 --- a/src/agents/auth-profiles/oauth-refresh-queue.test.ts +++ b/src/agents/auth-profiles/oauth-refresh-queue.test.ts @@ -28,7 +28,7 @@ const { vi.mock("../../llm/oauth.js", () => ({ getOAuthApiKey: vi.fn(async () => null), - getOAuthProviders: () => [{ id: "openai-codex" }], + getOAuthProviders: () => [{ id: "openai" }], })); describe("OAuth refresh in-process queue", () => { @@ -65,8 +65,8 @@ describe("OAuth refresh in-process queue", () => { }); it("releases the queue even when the refresh throws", async () => { - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; saveAuthProfileStore(createExpiredOauthStore({ profileId, provider }), agentDir); let callCount = 0; @@ -106,7 +106,7 @@ describe("OAuth refresh in-process queue", () => { expect(second).toEqual({ apiKey: "second-try-access", email: undefined, - provider: "openai-codex", + provider: "openai", }); }); @@ -125,8 +125,8 @@ describe("OAuth refresh in-process queue", () => { // wrapper does not let later arrivals skip ahead (see review P2: the // `refreshQueues.set(key, gate)` overwrites only the *map head*, while // FIFO ordering is enforced via the `await prev` chain). - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; saveAuthProfileStore(createExpiredOauthStore({ profileId, provider }), agentDir); const startOrder: number[] = []; diff --git a/src/agents/auth-profiles/oauth-shared.test.ts b/src/agents/auth-profiles/oauth-shared.test.ts index 6dbaa80d7e5f..7b0b566b4db6 100644 --- a/src/agents/auth-profiles/oauth-shared.test.ts +++ b/src/agents/auth-profiles/oauth-shared.test.ts @@ -22,10 +22,10 @@ describe("overlayRuntimeExternalOAuthProfiles", () => { try { const overlaid = overlayRuntimeExternalOAuthProfiles(store, [ { - profileId: "openai-codex:default", + profileId: "openai:default", credential: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-1", refresh: "refresh-1", expires: Date.now() + 60_000, @@ -33,13 +33,13 @@ describe("overlayRuntimeExternalOAuthProfiles", () => { }, ]); - const overlaidCodexProfile = overlaid.profiles["openai-codex:default"]; + const overlaidCodexProfile = overlaid.profiles["openai:default"]; expect(overlaidCodexProfile?.type).toBe("oauth"); if (overlaidCodexProfile?.type !== "oauth") { throw new Error("expected overlaid Codex OAuth profile"); } expect(overlaidCodexProfile.access).toBe("access-1"); - expect(store.profiles["openai-codex:default"]).toBeUndefined(); + expect(store.profiles["openai:default"]?.type).toBe("api_key"); overlaid.profiles["openai:default"].provider = "mutated"; overlaid.order!.openai.push("mutated"); diff --git a/src/agents/auth-profiles/oauth.adopt-identity.test.ts b/src/agents/auth-profiles/oauth.adopt-identity.test.ts index 14a326b0c094..c0bb372222e8 100644 --- a/src/agents/auth-profiles/oauth.adopt-identity.test.ts +++ b/src/agents/auth-profiles/oauth.adopt-identity.test.ts @@ -34,7 +34,7 @@ function expectPersistedOpenAICodexProfile( metadata: Record = {}, ): void { expect(credential?.type).toBe("oauth"); - expect(credential?.provider).toBe("openai-codex"); + expect(credential?.provider).toBe("openai"); for (const [key, value] of Object.entries(metadata)) { expect((credential as Record | undefined)?.[key]).toEqual(value); } @@ -47,7 +47,7 @@ function expectPersistedOpenAICodexProfile( vi.mock("../../llm/oauth.js", () => ({ getOAuthApiKey: vi.fn(async () => null), - getOAuthProviders: () => [{ id: "openai-codex" }, { id: "anthropic" }], + getOAuthProviders: () => [{ id: "openai" }, { id: "anthropic" }], })); describe("OAuth credential adoption is identity-gated", () => { @@ -88,8 +88,8 @@ describe("OAuth credential adoption is identity-gated", () => { // Scenario: sub-agent starts with a still-valid OAuth cred (so no // refresh is triggered), but main holds an even fresher cred for a // different account. The pre-refresh adopt must refuse. - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const subExpiry = Date.now() + 10 * 60 * 1000; const mainFresher = Date.now() + 60 * 60 * 1000; @@ -149,8 +149,8 @@ describe("OAuth credential adoption is identity-gated", () => { // Inside the lock, main holds FRESH creds for a DIFFERENT account. The // inside-lock adopt branch must refuse and fall through to the HTTP // refresh path using the sub-agent's own refresh token. - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const freshExpiry = Date.now() + 60 * 60 * 1000; const subAgentDir = path.join(tempRoot, "agents", "sub-insidelock", "agent"); @@ -223,8 +223,8 @@ describe("OAuth credential adoption is identity-gated", () => { // Main has fresh creds for a DIFFERENT account. The catch-block // main-inherit fallback must refuse to adopt and let the original // error propagate (wrapped). - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const freshExpiry = Date.now() + 60 * 60 * 1000; const subAgentDir = path.join(tempRoot, "agents", "sub-catch-refuse", "agent"); @@ -282,7 +282,7 @@ describe("OAuth credential adoption is identity-gated", () => { profileId, agentDir: subAgentDir, }), - ).rejects.toThrow(/OAuth token refresh failed for openai-codex/); + ).rejects.toThrow(/OAuth token refresh failed for openai/); // Sub-agent store must still have its own stale cred \u2014 no leak. const subRaw = JSON.parse( diff --git a/src/agents/auth-profiles/oauth.concurrent-agents.test.ts b/src/agents/auth-profiles/oauth.concurrent-agents.test.ts index e5b860576109..dff63fa67c13 100644 --- a/src/agents/auth-profiles/oauth.concurrent-agents.test.ts +++ b/src/agents/auth-profiles/oauth.concurrent-agents.test.ts @@ -34,7 +34,7 @@ async function loadOAuthModuleForTest() { vi.mock("../../llm/oauth.js", () => ({ getOAuthApiKey: vi.fn(async () => null), - getOAuthProviders: () => [{ id: "openai-codex" }], + getOAuthProviders: () => [{ id: "openai" }], })); describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () => { @@ -68,8 +68,8 @@ describe("resolveApiKeyForProfile cross-agent refresh coordination (#26322)", () it("refreshes exactly once when many agents share one OAuth profile and all race on expiry", async () => { const agentCount = 4; - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const accountId = "acct-shared"; const freshExpiry = Date.now() + 60 * 60 * 1000; diff --git a/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts b/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts index 82ed0633a5f1..2525652b1129 100644 --- a/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts +++ b/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts @@ -15,7 +15,7 @@ const { getOAuthApiKeyMock } = vi.hoisted(() => ({ vi.mock("../../llm/oauth.js", () => ({ getOAuthApiKey: getOAuthApiKeyMock, - getOAuthProviders: () => [{ id: "anthropic" }, { id: "openai-codex" }], + getOAuthProviders: () => [{ id: "anthropic" }, { id: "openai" }], })); vi.mock("../cli-credentials.js", () => ({ diff --git a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts index 00f8700e2d7f..50aee50ff79a 100644 --- a/src/agents/auth-profiles/oauth.mirror-refresh.test.ts +++ b/src/agents/auth-profiles/oauth.mirror-refresh.test.ts @@ -33,7 +33,7 @@ function expectPersistedOpenAICodexProfile( metadata: Record = {}, ): void { expect(credential?.type).toBe("oauth"); - expect(credential?.provider).toBe("openai-codex"); + expect(credential?.provider).toBe("openai"); for (const [key, value] of Object.entries(metadata)) { expect((credential as Record | undefined)?.[key]).toEqual(value); } @@ -48,7 +48,7 @@ function requireOAuthCredential(store: AuthProfileStore, profileId: string): OAu } vi.mock("../../llm/oauth.js", () => ({ - getOAuthProviders: () => [{ id: "anthropic" }, { id: "openai-codex" }], + getOAuthProviders: () => [{ id: "anthropic" }, { id: "openai" }], getOAuthApiKey: vi.fn(async (provider: string, credentials: Record) => { const credential = credentials[provider]; return credential @@ -97,8 +97,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }); it("mirrors refreshed Codex OAuth credentials into the main store", async () => { - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const accountId = "acct-shared"; const freshExpiry = Date.now() + 60 * 60 * 1000; @@ -141,8 +141,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }); it("does not mirror when refresh was performed from the main agent itself", async () => { - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const freshExpiry = Date.now() + 60 * 60 * 1000; saveAuthProfileStore( @@ -187,8 +187,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => // resolveApiKeyForProfile: main is fresher at flow start, so we adopt // BEFORE the refresh attempt. End-user outcome: sub transparently uses // main's creds. - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const freshExpiry = Date.now() + 60 * 60 * 1000; const subAgentDir = path.join(tempRoot, "agents", "sub-fail-inherit", "agent"); @@ -228,8 +228,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }); it("answers app-server forced refresh from fresh main credentials when a sub-agent copy is expired", async () => { - const profileId = "openai-codex:peter@example.test"; - const provider = "openai-codex"; + const profileId = "openai:peter@example.test"; + const provider = "openai"; const freshExpiry = Date.now() + 60 * 60 * 1000; const subAgentDir = path.join(tempRoot, "agents", "sub-app-server-force", "agent"); @@ -281,8 +281,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }); it("refreshes the main owner when a stale local OAuth clone shadows a newer main credential", async () => { - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const accountId = "acct-shared"; const now = Date.now(); const freshExpiry = now + 60 * 60 * 1000; @@ -377,8 +377,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => // store (still expired). Then the main-agent-inherit fallback // kicks in and returns main's fresh creds read-through without copying // the refresh token into the sub store. - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const freshExpiry = Date.now() + 60 * 60 * 1000; const subAgentDir = path.join(tempRoot, "agents", "sub-catch-inherit", "agent"); @@ -437,8 +437,8 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => }); it("does not satisfy forced refresh from unchanged main-agent credentials after refresh fails", async () => { - const profileId = "openai-codex:default"; - const provider = "openai-codex"; + const profileId = "openai:default"; + const provider = "openai"; const accountId = "acct-shared"; const subAgentDir = path.join(tempRoot, "agents", "sub-force-catch", "agent"); @@ -474,7 +474,7 @@ describe("resolveApiKeyForProfile OAuth refresh mirror-to-main (#26322)", () => agentDir: subAgentDir, forceRefresh: true, }), - ).rejects.toThrow(/OAuth token refresh failed for openai-codex/); + ).rejects.toThrow(/OAuth token refresh failed for openai/); expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1); }); diff --git a/src/agents/auth-profiles/oauth.openai-codex-refresh-fallback.test.ts b/src/agents/auth-profiles/oauth.openai-codex-refresh-fallback.test.ts index c02d95e6e6af..461f1ca6eb85 100644 --- a/src/agents/auth-profiles/oauth.openai-codex-refresh-fallback.test.ts +++ b/src/agents/auth-profiles/oauth.openai-codex-refresh-fallback.test.ts @@ -50,7 +50,7 @@ vi.mock("../cli-credentials.js", () => ({ vi.mock("../../llm/oauth.js", () => ({ getOAuthApiKey: getOAuthApiKeyMock, getOAuthProviders: () => [ - { id: "openai-codex", envApiKey: "OPENAI_API_KEY", oauthTokenEnv: "OPENAI_OAUTH_TOKEN" }, // pragma: allowlist secret + { id: "openai", envApiKey: "OPENAI_API_KEY", oauthTokenEnv: "OPENAI_OAUTH_TOKEN" }, // pragma: allowlist secret { id: "anthropic", envApiKey: "ANTHROPIC_API_KEY", oauthTokenEnv: "ANTHROPIC_OAUTH_TOKEN" }, // pragma: allowlist secret ], })); @@ -84,7 +84,7 @@ async function readPersistedStore(agentDir: string): Promise { function mockRotatedOpenAICodexRefresh() { refreshProviderOAuthCredentialWithPluginMock.mockResolvedValueOnce({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "rotated-access-token", refresh: "rotated-refresh-token", expires: Date.now() + 86_400_000, @@ -97,7 +97,7 @@ function expectPersistedOpenAICodexProfile( metadata: Record = {}, ): void { expect(credential?.type).toBe("oauth"); - expect(credential?.provider).toBe("openai-codex"); + expect(credential?.provider).toBe("openai"); for (const [key, value] of Object.entries(metadata)) { expect(credential?.[key as keyof typeof credential]).toBe(value); } @@ -130,7 +130,7 @@ function requireOAuthContext(context: unknown): OAuthCredential { return credential; } -describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { +describe("resolveApiKeyForProfile openai refresh fallback", () => { const envSnapshot = captureEnv(OAUTH_AGENT_ENV_KEYS); let tempRoot = ""; let agentDir = ""; @@ -175,15 +175,15 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { await fs.rm(tempRoot, { recursive: true, force: true }); }); - it("falls back to cached access token when openai-codex refresh fails on accountId extraction", async () => { - const profileId = "openai-codex:default"; + it("falls back to cached access token when openai refresh fails on accountId extraction", async () => { + const profileId = "openai:default"; refreshProviderOAuthCredentialWithPluginMock.mockImplementationOnce( async (params?: { context?: unknown }) => params?.context as never, ); saveAuthProfileStore( createExpiredOauthStore({ profileId, - provider: "openai-codex", + provider: "openai", }), agentDir, ); @@ -196,21 +196,21 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { expect(result).toEqual({ apiKey: "cached-access-token", // pragma: allowlist secret - provider: "openai-codex", + provider: "openai", email: undefined, }); expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1); }); - it("refreshes near-expiry openai-codex credentials before hard expiry", async () => { - const profileId = "openai-codex:default"; + it("refreshes near-expiry openai credentials before hard expiry", async () => { + const profileId = "openai:default"; saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "near-expiry-access-token", refresh: "near-expiry-refresh-token", expires: Date.now() + 60_000, @@ -225,21 +225,21 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { expect(result).toEqual({ apiKey: "rotated-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1); }); - it("forces refresh for unexpired openai-codex credentials through the exported resolver", async () => { - const profileId = "openai-codex:default"; + it("forces refresh for unexpired openai credentials through the exported resolver", async () => { + const profileId = "openai:default"; saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "fresh-access-token", refresh: "fresh-refresh-token", expires: Date.now() + 86_400_000, @@ -259,18 +259,18 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { expect(result).toEqual({ apiKey: "rotated-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); expect(refreshProviderOAuthCredentialWithPluginMock).toHaveBeenCalledTimes(1); }); - it("persists plugin-refreshed openai-codex credentials before returning", async () => { - const profileId = "openai-codex:default"; + it("persists plugin-refreshed openai credentials before returning", async () => { + const profileId = "openai:default"; saveAuthProfileStore( createExpiredOauthStore({ profileId, - provider: "openai-codex", + provider: "openai", access: "stale-access-token", }), agentDir, @@ -281,7 +281,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { expect(result).toEqual({ apiKey: "rotated-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); @@ -294,14 +294,14 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("refreshes imported Codex credentials into the canonical auth store without writing back to .codex", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "expired-access-token", refresh: "expired-refresh-token", expires: Date.now() - 60_000, @@ -312,7 +312,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); readCodexCliCredentialsCachedMock.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "still-expired-cli-access-token", refresh: "still-expired-cli-refresh-token", expires: Date.now() - 30_000, @@ -320,7 +320,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); refreshProviderOAuthCredentialWithPluginMock.mockResolvedValueOnce({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "rotated-cli-access-token", refresh: "rotated-cli-refresh-token", expires: Date.now() + 86_400_000, @@ -335,7 +335,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { expect(result).toEqual({ apiKey: "rotated-cli-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); const persisted = await readPersistedStore(agentDir); @@ -347,11 +347,11 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("ignores mismatched fresh Codex CLI credentials when canonical local auth is bound to another account", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; saveAuthProfileStore( createExpiredOauthStore({ profileId, - provider: "openai-codex", + provider: "openai", access: "expired-local-access-token", refresh: "local-refresh-token", accountId: "acct-local", @@ -360,7 +360,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); readCodexCliCredentialsCachedMock.mockReturnValueOnce({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "fresh-cli-access-token", refresh: "fresh-cli-refresh-token", expires: Date.now() + 86_400_000, @@ -374,7 +374,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { expect(context.accountId).toBe("acct-local"); return { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "fresh-local-access-token", refresh: "fresh-local-refresh-token", expires: Date.now() + 86_400_000, @@ -391,7 +391,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }), ).resolves.toEqual({ apiKey: "fresh-local-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); @@ -406,14 +406,14 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("keeps the canonical refresh token when imported Codex CLI state is expired", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "expired-local-access-token", refresh: "stale-local-refresh-token", expires: Date.now() - 120_000, @@ -424,7 +424,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); readCodexCliCredentialsCachedMock.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "newer-but-expired-cli-access-token", refresh: "fresh-cli-refresh-token", expires: Date.now() - 30_000, @@ -437,7 +437,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { expect(context.refresh).toBe("stale-local-refresh-token"); return { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "fresh-access-token", refresh: "fresh-refresh-token", expires: Date.now() + 86_400_000, @@ -453,7 +453,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }), ).resolves.toEqual({ apiKey: "fresh-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); @@ -465,14 +465,14 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("uses same-account Codex CLI credentials after forced local refresh fails", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "local-access-token", refresh: "local-refresh-token", expires: Date.now() + 86_400_000, @@ -484,7 +484,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); readCodexCliCredentialsCachedMock.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "codex-cli-access-token", refresh: "codex-cli-refresh-token", expires: Date.now() + 86_400_000, @@ -505,7 +505,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }), ).resolves.toEqual({ apiKey: "codex-cli-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); @@ -523,14 +523,14 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("uses same-account Codex CLI credentials for named Codex profiles after forced local refresh fails", async () => { - const profileId = "openai-codex:user@example.com"; + const profileId = "openai:user@example.com"; saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "local-access-token", refresh: "local-refresh-token", expires: Date.now() + 86_400_000, @@ -543,7 +543,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); readCodexCliCredentialsCachedMock.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "codex-cli-access-token", refresh: "codex-cli-refresh-token", expires: Date.now() + 86_400_000, @@ -564,7 +564,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }), ).resolves.toEqual({ apiKey: "codex-cli-access-token", - provider: "openai-codex", + provider: "openai", email: "user@example.com", }); @@ -577,14 +577,14 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("rejects mismatched Codex CLI fallback after forced local refresh fails", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "local-access-token", refresh: "local-refresh-token", expires: Date.now() + 86_400_000, @@ -596,7 +596,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); readCodexCliCredentialsCachedMock.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "codex-cli-access-token", refresh: "codex-cli-refresh-token", expires: Date.now() + 86_400_000, @@ -615,18 +615,18 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { agentDir, forceRefresh: true, }), - ).rejects.toThrow(/OAuth token refresh failed for openai-codex/); + ).rejects.toThrow(/OAuth token refresh failed for openai/); }); it("rejects identity-less Codex CLI fallback after forced local refresh fails", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "local-access-token", refresh: "local-refresh-token", expires: Date.now() + 86_400_000, @@ -637,7 +637,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); readCodexCliCredentialsCachedMock.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "codex-cli-access-token", refresh: "codex-cli-refresh-token", expires: Date.now() + 86_400_000, @@ -656,14 +656,14 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { agentDir, forceRefresh: true, }), - ).rejects.toThrow(/OAuth token refresh failed for openai-codex/); + ).rejects.toThrow(/OAuth token refresh failed for openai/); }); it("rejects unchanged Codex CLI fallback during forced refresh", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; const credential: OAuthCredential = { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "shared-access-token", refresh: "shared-refresh-token", expires: Date.now() + 86_400_000, @@ -692,15 +692,15 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { agentDir, forceRefresh: true, }), - ).rejects.toThrow(/OAuth token refresh failed for openai-codex/); + ).rejects.toThrow(/OAuth token refresh failed for openai/); }); it("adopts fresher stored credentials after refresh_token_reused", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; saveAuthProfileStore( createExpiredOauthStore({ profileId, - provider: "openai-codex", + provider: "openai", }), agentDir, ); @@ -711,7 +711,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "reloaded-access-token", refresh: "reloaded-refresh-token", expires: Date.now() + 10 * 60_000, @@ -733,7 +733,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }), ).resolves.toEqual({ apiKey: "reloaded-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); @@ -741,29 +741,29 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("clears stale lastGood before selecting an alternate Codex OAuth profile", async () => { - const staleProfileId = "openai-codex:default"; - const healthyProfileId = "openai-codex:user@example.test"; + const staleProfileId = "openai:default"; + const healthyProfileId = "openai:user@example.test"; saveAuthProfileStore( { version: 1, profiles: { [staleProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-access-token", refresh: "stale-refresh-token", expires: Date.now() - 60_000, }, [healthyProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "healthy-access-token", refresh: "healthy-refresh-token", expires: Date.now() + 60 * 60_000, email: "user@example.test", }, }, - lastGood: { "openai-codex": staleProfileId }, + lastGood: { openai: staleProfileId }, }, agentDir, ); @@ -781,7 +781,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }), ).resolves.toEqual({ apiKey: "healthy-access-token", - provider: "openai-codex", + provider: "openai", email: "user@example.test", }); @@ -790,29 +790,29 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("reports the alternate Codex OAuth profile after stale lastGood fallback", async () => { - const staleProfileId = "openai-codex:default"; - const healthyProfileId = "openai-codex:user@example.test"; + const staleProfileId = "openai:default"; + const healthyProfileId = "openai:user@example.test"; saveAuthProfileStore( { version: 1, profiles: { [staleProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-access-token", refresh: "stale-refresh-token", expires: Date.now() - 60_000, }, [healthyProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "healthy-access-token", refresh: "healthy-refresh-token", expires: Date.now() + 60 * 60_000, email: "user@example.test", }, }, - lastGood: { "openai-codex": staleProfileId }, + lastGood: { openai: staleProfileId }, }, agentDir, ); @@ -823,7 +823,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); const resolved = await resolveApiKeyForProvider({ - provider: "openai-codex", + provider: "openai", store: ensureAuthProfileStore(agentDir), agentDir, }); @@ -837,7 +837,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { await markAuthProfileSuccess({ store: ensureAuthProfileStore(agentDir), - provider: "openai-codex", + provider: "openai", profileId: resolved.profileId ?? "", agentDir, }); @@ -845,24 +845,24 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }); it("retries Codex refresh once after refresh_token_reused updates only the stored refresh token", async () => { - const profileId = "openai-codex:default"; + const profileId = "openai:default"; saveAuthProfileStore( createExpiredOauthStore({ profileId, - provider: "openai-codex", + provider: "openai", }), agentDir, ); getOAuthApiKeyMock .mockImplementationOnce(async (_provider, creds) => { - expect(creds["openai-codex"]?.refresh).toBe("refresh-token"); + expect(creds["openai"]?.refresh).toBe("refresh-token"); saveAuthProfileStore( { version: 1, profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "still-expired-access-token", refresh: "rotated-refresh-token", expires: Date.now() - 5_000, @@ -876,7 +876,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ); }) .mockImplementationOnce(async (_provider, creds) => { - expect(creds["openai-codex"]?.refresh).toBe("rotated-refresh-token"); + expect(creds["openai"]?.refresh).toBe("rotated-refresh-token"); return { apiKey: "retried-access-token", newCredentials: { @@ -895,7 +895,7 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { }), ).resolves.toEqual({ apiKey: "retried-access-token", - provider: "openai-codex", + provider: "openai", email: undefined, }); @@ -926,12 +926,12 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { ).rejects.toThrow(/OAuth token refresh failed for anthropic/); }); - it("does not use fallback for unrelated openai-codex refresh errors", async () => { - const profileId = "openai-codex:default"; + it("does not use fallback for unrelated openai refresh errors", async () => { + const profileId = "openai:default"; saveAuthProfileStore( createExpiredOauthStore({ profileId, - provider: "openai-codex", + provider: "openai", }), agentDir, ); @@ -945,6 +945,6 @@ describe("resolveApiKeyForProfile openai-codex refresh fallback", () => { profileId, agentDir, }), - ).rejects.toThrow(/OAuth token refresh failed for openai-codex/); + ).rejects.toThrow(/OAuth token refresh failed for openai/); }); }); diff --git a/src/agents/auth-profiles/oauth.ts b/src/agents/auth-profiles/oauth.ts index b0a7058211d2..a81e882003da 100644 --- a/src/agents/auth-profiles/oauth.ts +++ b/src/agents/auth-profiles/oauth.ts @@ -223,7 +223,7 @@ const oauthManager = createOAuthManager({ credential, }), readFallbackCredential: ({ profileId, credential }) => - credential.provider === "openai" || credential.provider === "openai-codex" + credential.provider === "openai" ? readExternalCliFallbackCredential({ profileId, credential, diff --git a/src/agents/auth-profiles/order.test.ts b/src/agents/auth-profiles/order.test.ts index 26768658187f..92cfb387bb52 100644 --- a/src/agents/auth-profiles/order.test.ts +++ b/src/agents/auth-profiles/order.test.ts @@ -210,14 +210,14 @@ describe("resolveAuthProfileOrder", () => { profiles: { "openai:personal": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access", refresh: "refresh", expires: Date.now() + 60_000, }, "openai:backup": { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-backup", }, "openai:platform": { @@ -237,19 +237,19 @@ describe("resolveAuthProfileOrder", () => { }, }, store, - provider: "openai-codex", + provider: "openai", }); expect(order).toEqual(["openai:personal", "openai:backup", "openai:platform"]); }); - it("lets Codex auth discover normal OpenAI API-key profiles as backups", async () => { + it("discovers OpenAI OAuth profiles before API-key backups", async () => { const store: AuthProfileStore = { version: 1, profiles: { - "openai-codex:personal": { + "openai:personal": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access", refresh: "refresh", expires: Date.now() + 60_000, @@ -271,19 +271,19 @@ describe("resolveAuthProfileOrder", () => { const order = resolveAuthProfileOrder({ store, - provider: "openai-codex", + provider: "openai", }); - expect(order).toEqual(["openai-codex:personal", "openai:backup"]); + expect(order).toEqual(["openai:personal", "openai:oauth", "openai:backup"]); }); it("does not discover OAuth profiles without inline credential material", async () => { const store: AuthProfileStore = { version: 1, profiles: { - "openai-codex:personal": { + "openai:personal": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "", refresh: "", expires: Date.now() + 60_000, @@ -293,13 +293,13 @@ describe("resolveAuthProfileOrder", () => { const order = resolveAuthProfileOrder({ store, - provider: "openai-codex", + provider: "openai", }); expect(order).toEqual([]); }); - it("preserves native Codex profiles before OpenAI alias API-key order", async () => { + it("uses explicit OpenAI auth order without implicit profile prepending", async () => { const store: AuthProfileStore = { version: 1, profiles: { @@ -308,9 +308,9 @@ describe("resolveAuthProfileOrder", () => { provider: "openai", key: "sk-platform", }, - "openai-codex:personal": { + "openai:personal": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access", refresh: "refresh", expires: Date.now() + 60_000, @@ -327,19 +327,19 @@ describe("resolveAuthProfileOrder", () => { }, }, store, - provider: "openai-codex", + provider: "openai", }); - expect(order).toEqual(["openai-codex:personal", "openai:default"]); + expect(order).toEqual(["openai:default"]); }); it("keeps Codex profiles listed in the friendly OpenAI order for Codex auth", async () => { const store: AuthProfileStore = { version: 1, profiles: { - "openai-codex:personal": { + "openai:personal": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access", refresh: "refresh", expires: Date.now() + 60_000, @@ -356,35 +356,28 @@ describe("resolveAuthProfileOrder", () => { cfg: { auth: { order: { - openai: ["openai-codex:personal", "openai:backup"], + openai: ["openai:personal", "openai:backup"], }, }, }, store, - provider: "openai-codex", + provider: "openai", }); - expect(order).toEqual(["openai-codex:personal", "openai:backup"]); + expect(order).toEqual(["openai:personal", "openai:backup"]); }); - it("keeps direct OpenAI Codex auth order ahead of the friendly OpenAI alias", async () => { + it("uses canonical OpenAI auth order", async () => { const store: AuthProfileStore = { version: 1, profiles: { "openai:personal": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access", refresh: "refresh", expires: Date.now() + 60_000, }, - "openai-codex:legacy": { - type: "oauth", - provider: "openai-codex", - access: "legacy-access", - refresh: "legacy-refresh", - expires: Date.now() + 60_000, - }, }, }; @@ -393,18 +386,17 @@ describe("resolveAuthProfileOrder", () => { auth: { order: { openai: ["openai:personal"], - "openai-codex": ["openai-codex:legacy"], }, }, }, store, - provider: "openai-codex", + provider: "openai", }); - expect(order).toEqual(["openai-codex:legacy"]); + expect(order).toEqual(["openai:personal"]); }); - it("keeps configured Codex auth order ahead of stored OpenAI fallback order", async () => { + it("keeps stored OpenAI auth order when present", async () => { const store: AuthProfileStore = { version: 1, profiles: { @@ -413,9 +405,9 @@ describe("resolveAuthProfileOrder", () => { provider: "openai", key: "sk-platform", }, - "openai-codex:work": { + "openai:work": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "work-access", refresh: "work-refresh", expires: Date.now() + 60_000, @@ -430,15 +422,15 @@ describe("resolveAuthProfileOrder", () => { cfg: { auth: { order: { - "openai-codex": ["openai-codex:work"], + openai: ["openai:work"], }, }, }, store, - provider: "openai-codex", + provider: "openai", }); - expect(order).toEqual(["openai-codex:work"]); + expect(order).toEqual(["openai:platform"]); }); it("marks profile success with one canonical last-good and usage update", async () => { diff --git a/src/agents/auth-profiles/order.ts b/src/agents/auth-profiles/order.ts index fdbb0c58956d..4d2d9d349192 100644 --- a/src/agents/auth-profiles/order.ts +++ b/src/agents/auth-profiles/order.ts @@ -28,7 +28,7 @@ export type AuthProfileEligibility = { }; const OPENAI_PROVIDER_ID = "openai"; -const OPENAI_CODEX_PROVIDER_ID = "openai-codex"; +const OPENAI_CODEX_PROVIDER_ID = "openai"; function isOpenAIApiKeyCompatibleWithCodexAuth(params: { cfg?: OpenClawConfig; diff --git a/src/agents/auth-profiles/persisted-boundary.test.ts b/src/agents/auth-profiles/persisted-boundary.test.ts index 2627701e2b59..46ea4165f859 100644 --- a/src/agents/auth-profiles/persisted-boundary.test.ts +++ b/src/agents/auth-profiles/persisted-boundary.test.ts @@ -1,32 +1,6 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; import { describe, expect, it } from "vitest"; -import { resolveOAuthDir } from "../../config/paths.js"; import { AUTH_STORE_VERSION } from "./constants.js"; -import { legacyOAuthSidecarTestUtils } from "./legacy-oauth-sidecar.js"; -import { resolveAuthStorePath } from "./paths.js"; -import { - coercePersistedAuthProfileStore, - loadPersistedAuthProfileStore, - mergeAuthProfileStores, -} from "./persisted.js"; - -function withEnvValue(key: string, value: string | undefined): () => void { - const previous = process.env[key]; - if (value === undefined) { - delete process.env[key]; - } else { - process.env[key] = value; - } - return () => { - if (previous === undefined) { - delete process.env[key]; - } else { - process.env[key] = previous; - } - }; -} +import { coercePersistedAuthProfileStore, mergeAuthProfileStores } from "./persisted.js"; describe("persisted auth profile boundary", () => { it("normalizes malformed persisted credentials and state before runtime use", () => { @@ -50,15 +24,15 @@ describe("persisted auth profile boundary", () => { tokenRef: { source: "env", provider: "default", id: "MINIMAX_TOKEN" }, expires: "tomorrow", }, - "codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: ["wrong"], refresh: "refresh-token", expires: "later", oauthRef: { source: "openclaw-credentials", - provider: "openai-codex", + provider: "openai", id: "not-a-secret-id", }, }, @@ -102,9 +76,9 @@ describe("persisted auth profile boundary", () => { tokenRef: { source: "env", provider: "default", id: "MINIMAX_TOKEN" }, expires: 0, }, - "codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", refresh: "refresh-token", expires: 0, }, @@ -126,95 +100,7 @@ describe("persisted auth profile boundary", () => { expect(store?.profiles["broken:array"]).toBeUndefined(); expect(store?.profiles["openai:default"]).not.toHaveProperty("key"); expect(store?.profiles["openai:default"]).not.toHaveProperty("copyToAgents"); - expect(store?.profiles["codex:default"]).not.toHaveProperty("oauthRef"); - }); - - it("rehydrates legacy oauthRef sidecars read-only for upgraded Codex OAuth users", () => { - const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-oauthref-runtime-")); - const agentDir = path.join(stateDir, "agents", "main", "agent"); - const restoreStateDir = withEnvValue("OPENCLAW_STATE_DIR", stateDir); - const restoreOAuthDir = withEnvValue("OPENCLAW_OAUTH_DIR", undefined); - const restoreSecretKey = withEnvValue("OPENCLAW_AUTH_PROFILE_SECRET_KEY", "legacy-seed"); - try { - fs.mkdirSync(agentDir, { recursive: true }); - const profileId = "openai-codex:default"; - const ref = { - source: "openclaw-credentials" as const, - provider: "openai-codex" as const, - id: "0123456789abcdef0123456789abcdef", - }; - fs.writeFileSync( - resolveAuthStorePath(agentDir), - `${JSON.stringify( - { - version: AUTH_STORE_VERSION, - profiles: { - [profileId]: { - type: "oauth", - provider: "openai-codex", - expires: 123456, - accountId: "acct-legacy", - chatgptPlanType: "plus", - oauthRef: ref, - }, - }, - }, - null, - 2, - )}\n`, - ); - - const sidecarPath = path.join(resolveOAuthDir(), "auth-profiles", `${ref.id}.json`); - fs.mkdirSync(path.dirname(sidecarPath), { recursive: true }); - fs.writeFileSync( - sidecarPath, - `${JSON.stringify( - { - version: 1, - profileId, - provider: "openai-codex", - encrypted: legacyOAuthSidecarTestUtils.encryptLegacyOAuthMaterial({ - ref, - profileId, - provider: "openai-codex", - seed: "legacy-seed", - material: { - access: "legacy-access-token", - refresh: "legacy-refresh-token", - idToken: "legacy-id-token", - }, - }), - }, - null, - 2, - )}\n`, - ); - - const unresolved = loadPersistedAuthProfileStore(agentDir)?.profiles[profileId]; - expect(unresolved).not.toHaveProperty("access"); - expect(unresolved).not.toHaveProperty("refresh"); - expect(unresolved).not.toHaveProperty("idToken"); - - const credential = loadPersistedAuthProfileStore(agentDir, { - resolveLegacyOAuthSidecars: true, - })?.profiles[profileId]; - expect(credential).toMatchObject({ - type: "oauth", - provider: "openai-codex", - access: "legacy-access-token", - refresh: "legacy-refresh-token", - idToken: "legacy-id-token", - expires: 123456, - accountId: "acct-legacy", - chatgptPlanType: "plus", - }); - expect(credential).not.toHaveProperty("oauthRef"); - } finally { - restoreSecretKey(); - restoreOAuthDir(); - restoreStateDir(); - fs.rmSync(stateDir, { recursive: true, force: true }); - } + expect(store?.profiles["openai:oauth"]).not.toHaveProperty("oauthRef"); }); it("lets authoritative runtime external metadata remove stale base profiles", () => { diff --git a/src/agents/auth-profiles/persisted.ts b/src/agents/auth-profiles/persisted.ts index 9b1e53cb8f6d..da996e49dd24 100644 --- a/src/agents/auth-profiles/persisted.ts +++ b/src/agents/auth-profiles/persisted.ts @@ -1,4 +1,3 @@ -import { createHash } from "node:crypto"; import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import { resolveOAuthPath } from "../../config/paths.js"; import { coerceSecretRef } from "../../config/types.secrets.js"; @@ -7,11 +6,6 @@ import { isRecord } from "../../shared/record-coerce.js"; import { uniqueStrings } from "../../shared/string-normalization.js"; import { asBoolean } from "../../utils/boolean.js"; import { AUTH_STORE_VERSION, log } from "./constants.js"; -import { - isLegacyOAuthRef, - loadLegacyOAuthSidecarMaterial, - type LegacyOAuthSecretMaterial, -} from "./legacy-oauth-sidecar.js"; import { hasOAuthIdentity, hasUsableOAuthCredential, @@ -27,28 +21,22 @@ import { } from "./state.js"; import type { AuthProfileCredential, - AuthProfileFailureReason, AuthProfileSecretsStore, AuthProfileStore, OAuthCredential, OAuthCredentials, - ProfileUsageStats, } from "./types.js"; export type LegacyAuthStore = Record; type LoadPersistedAuthProfileStoreOptions = { allowKeychainPrompt?: boolean; - resolveLegacyOAuthSidecars?: boolean; }; type CredentialRejectReason = "non_object" | "invalid_type" | "missing_provider"; type RejectedCredentialEntry = { key: string; reason: CredentialRejectReason }; const AUTH_PROFILE_TYPES = new Set(["api_key", "oauth", "token"]); -const LEGACY_OAUTH_REF_PROVIDER = "openai-codex"; -const runtimeLegacyOAuthSidecarCredentials = new WeakSet(); -const runtimeLegacyOAuthSidecarMaterialFingerprints = new Map(); function normalizeOptionalCredentialString(value: unknown): string | undefined { if (typeof value !== "string") { @@ -58,29 +46,6 @@ function normalizeOptionalCredentialString(value: unknown): string | undefined { return trimmed ? value : undefined; } -function hasInlineOAuthTokenMaterial(credential: OAuthCredential): boolean { - return [credential.access, credential.refresh, credential.idToken].some( - (value) => typeof value === "string" && value.trim().length > 0, - ); -} - -function buildRuntimeLegacyOAuthSidecarFingerprintKey(params: { - storeKey?: string; - profileId: string; -}): string { - return `${params.storeKey ?? ""}\0${params.profileId}`; -} - -function buildLegacyOAuthSecretMaterialFingerprint( - material: Pick, -): string { - return createHash("sha256") - .update( - JSON.stringify([material.access ?? null, material.refresh ?? null, material.idToken ?? null]), - ) - .digest("hex"); -} - function normalizeExpiryField(value: unknown): number | undefined { if (value === undefined) { return undefined; @@ -258,80 +223,6 @@ function warnRejectedCredentialEntries(source: string, rejected: RejectedCredent }); } -function resolveLegacyOAuthSidecarCredential(params: { - profileId: string; - raw: unknown; - credential: AuthProfileCredential; - storeKey?: string; - options?: LoadPersistedAuthProfileStoreOptions; -}): AuthProfileCredential { - if ( - params.credential.type !== "oauth" || - normalizeProviderId(params.credential.provider) !== LEGACY_OAUTH_REF_PROVIDER || - hasInlineOAuthTokenMaterial(params.credential) || - !isRecord(params.raw) || - !isLegacyOAuthRef(params.raw.oauthRef) - ) { - return params.credential; - } - // Read-only compatibility for #79006 sidecar OAuth profiles. Do not add - // new writers or OS-level Keychain creation here; doctor remains the path - // that migrates users back to canonical inline OAuth credentials. - const material = loadLegacyOAuthSidecarMaterial({ - ref: params.raw.oauthRef, - profileId: params.profileId, - provider: params.credential.provider, - allowKeychainPrompt: params.options?.allowKeychainPrompt, - }); - if (!material) { - return params.credential; - } - const credential = { - ...params.credential, - ...(material.access ? { access: material.access } : {}), - ...(material.refresh ? { refresh: material.refresh } : {}), - ...(material.idToken ? { idToken: material.idToken } : {}), - }; - runtimeLegacyOAuthSidecarCredentials.add(credential); - runtimeLegacyOAuthSidecarMaterialFingerprints.set( - buildRuntimeLegacyOAuthSidecarFingerprintKey({ - storeKey: params.storeKey, - profileId: params.profileId, - }), - buildLegacyOAuthSecretMaterialFingerprint(credential), - ); - return credential; -} - -export function isRuntimeLegacyOAuthSidecarCredential( - credential: AuthProfileCredential | undefined, -): boolean { - return credential?.type === "oauth" && runtimeLegacyOAuthSidecarCredentials.has(credential); -} - -export function matchesRuntimeLegacyOAuthSidecarMaterial(params: { - authPath?: string; - profileId: string; - credential: AuthProfileCredential | undefined; -}): boolean { - if (params.credential?.type !== "oauth") { - return false; - } - if (runtimeLegacyOAuthSidecarCredentials.has(params.credential)) { - return true; - } - const fingerprint = runtimeLegacyOAuthSidecarMaterialFingerprints.get( - buildRuntimeLegacyOAuthSidecarFingerprintKey({ - storeKey: params.authPath, - profileId: params.profileId, - }), - ); - return ( - fingerprint !== undefined && - fingerprint === buildLegacyOAuthSecretMaterialFingerprint(params.credential) - ); -} - function coerceLegacyAuthStore(raw: unknown): LegacyAuthStore | null { if (!isRecord(raw)) { return null; @@ -356,8 +247,7 @@ function coerceLegacyAuthStore(raw: unknown): LegacyAuthStore | null { export function coercePersistedAuthProfileStore( raw: unknown, - options?: LoadPersistedAuthProfileStoreOptions, - storeKey?: string, + _options?: LoadPersistedAuthProfileStoreOptions, ): AuthProfileStore | null { if (!isRecord(raw)) { return null; @@ -375,16 +265,7 @@ export function coercePersistedAuthProfileStore( rejected.push({ key, reason: parsed.reason }); continue; } - normalized[key] = - options?.resolveLegacyOAuthSidecars === true - ? resolveLegacyOAuthSidecarCredential({ - profileId: key, - raw: value, - credential: parsed.credential, - storeKey, - options, - }) - : parsed.credential; + normalized[key] = parsed.credential; } warnRejectedCredentialEntries("auth-profiles.json", rejected); const version = Number(record.version ?? AUTH_STORE_VERSION); @@ -456,107 +337,6 @@ function isNewerUsableOAuthCredential( ); } -const AUTH_INVALIDATION_REASONS = new Set([ - "auth", - "auth_permanent", - "session_expired", -]); - -function hasAuthInvalidationSignal(stats: ProfileUsageStats | undefined): boolean { - if (!stats) { - return false; - } - if ( - (stats.cooldownReason && AUTH_INVALIDATION_REASONS.has(stats.cooldownReason)) || - (stats.disabledReason && AUTH_INVALIDATION_REASONS.has(stats.disabledReason)) - ) { - return true; - } - return Object.entries(stats.failureCounts ?? {}).some( - ([reason, count]) => - AUTH_INVALIDATION_REASONS.has(reason as AuthProfileFailureReason) && - typeof count === "number" && - count > 0, - ); -} - -function isProfileReferencedByAuthState(store: AuthProfileStore, profileId: string): boolean { - if (Object.values(store.order ?? {}).some((profileIds) => profileIds.includes(profileId))) { - return true; - } - return Object.values(store.lastGood ?? {}).some((value) => value === profileId); -} - -function resolveProviderAuthStateValue( - values: Record | undefined, - providerKey: string, -): T | undefined { - if (!values) { - return undefined; - } - for (const [key, value] of Object.entries(values)) { - if (normalizeProviderId(key) === providerKey) { - return value; - } - } - return undefined; -} - -function findMainStoreOAuthReplacementForInvalidatedProfile(params: { - base: AuthProfileStore; - override: AuthProfileStore; - profileId: string; - credential: OAuthCredential; -}): string | undefined { - const providerKey = normalizeProviderId(params.credential.provider); - if ( - providerKey !== "openai-codex" || - !isProfileReferencedByAuthState(params.override, params.profileId) || - !hasAuthInvalidationSignal(params.override.usageStats?.[params.profileId]) - ) { - return undefined; - } - - const candidates = Object.entries(params.base.profiles) - .flatMap(([profileId, credential]): Array<[string, OAuthCredential]> => { - if ( - profileId === params.profileId || - credential.type !== "oauth" || - normalizeProviderId(credential.provider) !== providerKey || - !hasUsableOAuthCredential(credential) - ) { - return []; - } - return [[profileId, credential]]; - }) - .toSorted(([leftId, leftCredential], [rightId, rightCredential]) => { - const leftExpires = Number.isFinite(leftCredential.expires) ? leftCredential.expires : 0; - const rightExpires = Number.isFinite(rightCredential.expires) ? rightCredential.expires : 0; - if (rightExpires !== leftExpires) { - return rightExpires - leftExpires; - } - return leftId.localeCompare(rightId); - }); - if (candidates.length === 0) { - return undefined; - } - - const candidateIds = new Set(candidates.map(([profileId]) => profileId)); - const orderedProfileId = resolveProviderAuthStateValue(params.base.order, providerKey)?.find( - (profileId) => candidateIds.has(profileId), - ); - if (orderedProfileId) { - return orderedProfileId; - } - - const lastGoodProfileId = resolveProviderAuthStateValue(params.base.lastGood, providerKey); - if (lastGoodProfileId && candidateIds.has(lastGoodProfileId)) { - return lastGoodProfileId; - } - - return candidates.length === 1 ? candidates[0]?.[0] : undefined; -} - function findMainStoreOAuthReplacement(params: { base: AuthProfileStore; legacyProfileId: string; @@ -696,12 +476,7 @@ function reconcileMainStoreOAuthProfileDrift(params: { legacyProfileId: profileId, legacyCredential: credential, }) - : findMainStoreOAuthReplacementForInvalidatedProfile({ - base: params.base, - override: params.override, - profileId, - credential, - }); + : undefined; if (replacementProfileId) { replacements.set(profileId, replacementProfileId); } @@ -814,10 +589,6 @@ export function buildPersistedAuthProfileSecretsStore( profileId: string; credential: AuthProfileCredential; }) => boolean, - options?: { - existingRaw?: unknown; - runtimeLegacyOAuthSidecarProfileIds?: ReadonlySet; - }, ): AuthProfileSecretsStore { const profiles = Object.fromEntries( Object.entries(store.profiles).flatMap(([profileId, credential]) => { @@ -838,93 +609,10 @@ export function buildPersistedAuthProfileSecretsStore( }), ) as AuthProfileSecretsStore["profiles"]; - const payload: AuthProfileSecretsStore = { + return { version: AUTH_STORE_VERSION, profiles, }; - return preserveLegacyOAuthRefsForDoctorMigration(payload, options); -} - -function preserveLegacyOAuthRefsForDoctorMigration( - payload: AuthProfileSecretsStore, - options: - | { - existingRaw?: unknown; - runtimeLegacyOAuthSidecarProfileIds?: ReadonlySet; - } - | undefined, -): AuthProfileSecretsStore { - const existingRaw = options?.existingRaw; - if (!isRecord(existingRaw) || !isRecord(existingRaw.profiles)) { - return payload; - } - let profiles: AuthProfileSecretsStore["profiles"] | undefined; - for (const [profileId, rawProfile] of Object.entries(existingRaw.profiles)) { - if (!isRecord(rawProfile) || !isLegacyOAuthRef(rawProfile.oauthRef)) { - continue; - } - const credential = payload.profiles[profileId]; - if ( - credential?.type !== "oauth" || - normalizeProviderId(credential.provider) !== LEGACY_OAUTH_REF_PROVIDER - ) { - continue; - } - if (hasInlineOAuthTokenMaterial(credential)) { - const isRuntimeSidecarMaterial = - options?.runtimeLegacyOAuthSidecarProfileIds?.has(profileId) === true; - // Untracked inline material may be a real token refresh. Only reread the - // sidecar then, and never use Keychain from this save-path check. - if ( - !isRuntimeSidecarMaterial && - !isUnchangedLegacyOAuthSidecarMaterial({ profileId, rawProfile, credential }) - ) { - continue; - } - } - // Removal-only retention for #79006: ordinary runtime saves must not turn - // rehydrated sidecar tokens into inline credentials. Doctor remains the - // explicit migration path that creates backups and removes sidecars. - profiles ??= { ...payload.profiles }; - const sanitized = { ...credential } as Record; - delete sanitized.access; - delete sanitized.refresh; - delete sanitized.idToken; - profiles[profileId] = { - ...sanitized, - oauthRef: rawProfile.oauthRef, - } as unknown as AuthProfileCredential; - } - return profiles ? { ...payload, profiles } : payload; -} - -function isUnchangedLegacyOAuthSidecarMaterial(params: { - profileId: string; - rawProfile: Record; - credential: OAuthCredential; -}): boolean { - if (!isLegacyOAuthRef(params.rawProfile.oauthRef)) { - return false; - } - const material = loadLegacyOAuthSidecarMaterial({ - ref: params.rawProfile.oauthRef, - profileId: params.profileId, - provider: params.credential.provider, - allowKeychainPrompt: false, - }); - if (!material) { - return false; - } - return isSameLegacyOAuthSecretMaterial(params.credential, material); -} - -function isSameLegacyOAuthSecretMaterial( - credential: OAuthCredential, - material: LegacyOAuthSecretMaterial, -): boolean { - return (["access", "refresh", "idToken"] as const).every( - (field) => (credential[field] ?? undefined) === (material[field] ?? undefined), - ); } export function applyLegacyAuthStore(store: AuthProfileStore, legacy: LegacyAuthStore): void { @@ -996,7 +684,7 @@ export function loadPersistedAuthProfileStore( ): AuthProfileStore | null { const authPath = resolveAuthStorePath(agentDir); const raw = loadJsonFile(authPath); - const store = coercePersistedAuthProfileStore(raw, options, authPath); + const store = coercePersistedAuthProfileStore(raw, options); if (!store) { return null; } diff --git a/src/agents/auth-profiles/portability.test.ts b/src/agents/auth-profiles/portability.test.ts index 18b4bd4cc179..5e8b3ce31799 100644 --- a/src/agents/auth-profiles/portability.test.ts +++ b/src/agents/auth-profiles/portability.test.ts @@ -10,7 +10,7 @@ describe("auth profile portability", () => { const store: AuthProfileStore = { version: 1, profiles: { - "openai:default": { + "openai:api-key": { type: "api_key", provider: "openai", key: "sk-test", @@ -20,9 +20,9 @@ describe("auth profile portability", () => { provider: "github-copilot", token: "gho-test", }, - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -32,10 +32,10 @@ describe("auth profile portability", () => { const portable = buildPortableAuthProfileSecretsStoreForAgentCopy(store); - expect(portable.copiedProfileIds).toEqual(["openai:default", "github-copilot:default"]); - expect(portable.skippedProfileIds).toEqual(["openai-codex:default"]); + expect(portable.copiedProfileIds).toEqual(["openai:api-key", "github-copilot:default"]); + expect(portable.skippedProfileIds).toEqual(["openai:default"]); expect(portable.store.profiles).toEqual({ - "openai:default": store.profiles["openai:default"], + "openai:api-key": store.profiles["openai:api-key"], "github-copilot:default": store.profiles["github-copilot:default"], }); }); @@ -59,7 +59,7 @@ describe("auth profile portability", () => { it("does not copy empty OAuth profiles even when they opt in", () => { const credential = { type: "oauth", - provider: "openai-codex", + provider: "openai", expires: Date.now() + 60_000, copyToAgents: true, } as AuthProfileCredential; diff --git a/src/agents/auth-profiles/profiles.test.ts b/src/agents/auth-profiles/profiles.test.ts index c38d8f7c607d..1cd283226e1a 100644 --- a/src/agents/auth-profiles/profiles.test.ts +++ b/src/agents/auth-profiles/profiles.test.ts @@ -111,14 +111,14 @@ describe("promoteAuthProfileInOrder", () => { } }); - it("persists openai-codex oauth credentials inline", () => { + it("persists openai oauth credentials inline", () => { const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-metadata-")); const agentDir = path.join(stateDir, "agents", "main", "agent"); const previousStateDir = process.env.OPENCLAW_STATE_DIR; process.env.OPENCLAW_STATE_DIR = stateDir; try { fs.mkdirSync(agentDir, { recursive: true }); - const profileId = "openai-codex:default"; + const profileId = "openai:default"; const expires = Date.now() + 60 * 60 * 1000; saveAuthProfileStore( { @@ -126,7 +126,7 @@ describe("promoteAuthProfileInOrder", () => { profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "local-access-token", refresh: "local-refresh-token", idToken: "local-id-token", @@ -147,7 +147,7 @@ describe("promoteAuthProfileInOrder", () => { const credential = persisted.profiles[profileId]; expectOAuthCredentialFields(credential, { - provider: "openai-codex", + provider: "openai", access: "local-access-token", refresh: "local-refresh-token", idToken: "local-id-token", @@ -163,7 +163,7 @@ describe("promoteAuthProfileInOrder", () => { expectOAuthCredentialFields( loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles[profileId], { - provider: "openai-codex", + provider: "openai", access: "local-access-token", refresh: "local-refresh-token", idToken: "local-id-token", @@ -179,14 +179,14 @@ describe("promoteAuthProfileInOrder", () => { } }); - it("preserves access-only openai-codex oauth credentials inline", () => { + it("preserves access-only openai oauth credentials inline", () => { const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-access-only-")); const agentDir = path.join(stateDir, "agents", "main", "agent"); const previousStateDir = process.env.OPENCLAW_STATE_DIR; process.env.OPENCLAW_STATE_DIR = stateDir; try { fs.mkdirSync(agentDir, { recursive: true }); - const profileId = "openai-codex:default"; + const profileId = "openai:default"; const expires = Date.now() + 60 * 60 * 1000; saveAuthProfileStore( { @@ -194,7 +194,7 @@ describe("promoteAuthProfileInOrder", () => { profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-only-token", expires, } as AuthProfileStore["profiles"][string], @@ -209,7 +209,7 @@ describe("promoteAuthProfileInOrder", () => { }; const credential = persisted.profiles[profileId]; expectOAuthCredentialFields(credential, { - provider: "openai-codex", + provider: "openai", access: "access-only-token", expires, }); @@ -219,7 +219,7 @@ describe("promoteAuthProfileInOrder", () => { expectOAuthCredentialFields( loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles[profileId], { - provider: "openai-codex", + provider: "openai", access: "access-only-token", }, ); @@ -233,7 +233,115 @@ describe("promoteAuthProfileInOrder", () => { } }); - it("keeps copied openai-codex oauth profiles inline", () => { + it("preserves legacy OAuth sidecar refs during unrelated auth-store saves", () => { + const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-sidecar-")); + const agentDir = path.join(stateDir, "agents", "main", "agent"); + const previousStateDir = process.env.OPENCLAW_STATE_DIR; + process.env.OPENCLAW_STATE_DIR = stateDir; + try { + fs.mkdirSync(agentDir, { recursive: true }); + const authPath = resolveAuthStorePath(agentDir); + const legacyProvider = ["openai", "codex"].join("-"); + fs.writeFileSync( + authPath, + JSON.stringify({ + version: AUTH_STORE_VERSION, + profiles: { + "openai:default": { + type: "oauth", + provider: "openai", + refresh: "refresh-token", + oauthRef: { + source: "openclaw-credentials", + provider: legacyProvider, + id: "legacy-profile", + }, + }, + }, + }), + ); + + const store = loadAuthProfileStoreWithoutExternalProfiles(agentDir); + saveAuthProfileStore(store, agentDir); + + const persisted = JSON.parse(fs.readFileSync(authPath, "utf8")) as { + profiles: Record>; + }; + expect(persisted.profiles["openai:default"]?.oauthRef).toEqual({ + source: "openclaw-credentials", + provider: legacyProvider, + id: "legacy-profile", + }); + expect(store.profiles["openai:default"]).not.toHaveProperty("oauthRef"); + } finally { + if (previousStateDir === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousStateDir; + } + fs.rmSync(stateDir, { recursive: true, force: true }); + } + }); + + it("drops legacy OAuth sidecar refs when inline token material changes", () => { + const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-sidecar-new-")); + const agentDir = path.join(stateDir, "agents", "main", "agent"); + const previousStateDir = process.env.OPENCLAW_STATE_DIR; + process.env.OPENCLAW_STATE_DIR = stateDir; + try { + fs.mkdirSync(agentDir, { recursive: true }); + const authPath = resolveAuthStorePath(agentDir); + fs.writeFileSync( + authPath, + JSON.stringify({ + version: AUTH_STORE_VERSION, + profiles: { + "openai:default": { + type: "oauth", + provider: "openai", + refresh: "old-refresh-token", + oauthRef: { + source: "openclaw-credentials", + provider: ["openai", "codex"].join("-"), + id: "legacy-profile", + }, + }, + }, + }), + ); + + saveAuthProfileStore( + { + version: AUTH_STORE_VERSION, + profiles: { + "openai:default": { + type: "oauth", + provider: "openai", + access: "new-access-token", + refresh: "new-refresh-token", + expires: Date.now() + 60 * 60 * 1000, + }, + }, + }, + agentDir, + { filterExternalAuthProfiles: false }, + ); + + const persisted = JSON.parse(fs.readFileSync(authPath, "utf8")) as { + profiles: Record>; + }; + expect(persisted.profiles["openai:default"]).not.toHaveProperty("oauthRef"); + } finally { + if (previousStateDir === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousStateDir; + } + fs.rmSync(stateDir, { recursive: true, force: true }); + } + }); + + it("keeps copied openai oauth profiles inline", () => { const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-copy-ref-")); const mainAgentDir = path.join(stateDir, "agents", "main", "agent"); const copiedAgentDir = path.join(stateDir, "agents", "copied", "agent"); @@ -242,15 +350,15 @@ describe("promoteAuthProfileInOrder", () => { try { fs.mkdirSync(mainAgentDir, { recursive: true }); fs.mkdirSync(copiedAgentDir, { recursive: true }); - const originalProfileId = "openai-codex:default"; - const copiedProfileId = "openai-codex:copied"; + const originalProfileId = "openai:default"; + const copiedProfileId = "openai:copied"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [originalProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "copy-access-token", refresh: "copy-refresh-token", expires: Date.now() + 60 * 60 * 1000, @@ -292,7 +400,7 @@ describe("promoteAuthProfileInOrder", () => { expectOAuthCredentialFields( loadAuthProfileStoreWithoutExternalProfiles(copiedAgentDir).profiles[copiedProfileId], { - provider: "openai-codex", + provider: "openai", access: "copy-access-token", refresh: "copy-refresh-token", }, @@ -318,29 +426,29 @@ describe("promoteAuthProfileInOrder", () => { process.env.OPENCLAW_STATE_DIR = stateDir; try { fs.mkdirSync(agentDir, { recursive: true }); - const newProfileId = "openai-codex:bunsthedev@gmail.com"; - const staleProfileId = "openai-codex:val@viewdue.ai"; + const newProfileId = "openai:bunsthedev@gmail.com"; + const staleProfileId = "openai:val@viewdue.ai"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [newProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "new-access", refresh: "new-refresh", expires: Date.now() + 60 * 60 * 1000, }, [staleProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-access", refresh: "stale-refresh", expires: Date.now() + 30 * 60 * 1000, }, }, order: { - "openai-codex": [staleProfileId], + openai: [staleProfileId], }, }, agentDir, @@ -348,12 +456,12 @@ describe("promoteAuthProfileInOrder", () => { const updated = await promoteAuthProfileInOrder({ agentDir, - provider: "openai-codex", + provider: "openai", profileId: newProfileId, }); - expect(updated?.order?.["openai-codex"]).toEqual([newProfileId, staleProfileId]); - expect(loadAuthProfileStoreForRuntime(agentDir).order?.["openai-codex"]).toEqual([ + expect(updated?.order?.["openai"]).toEqual([newProfileId, staleProfileId]); + expect(loadAuthProfileStoreForRuntime(agentDir).order?.["openai"]).toEqual([ newProfileId, staleProfileId, ]); @@ -374,27 +482,27 @@ describe("promoteAuthProfileInOrder", () => { process.env.OPENCLAW_STATE_DIR = stateDir; try { fs.mkdirSync(agentDir, { recursive: true }); - const staleProfileId = "openai-codex:default"; + const staleProfileId = "openai:default"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [staleProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "stale-access-token", refresh: "stale-refresh-token", expires: Date.now() - 60_000, }, }, - lastGood: { "openai-codex": staleProfileId }, + lastGood: { openai: staleProfileId }, }, agentDir, ); await clearLastGoodProfileWithLock({ agentDir, - provider: "openai-codex", + provider: "openai", profileId: staleProfileId, }); @@ -416,33 +524,31 @@ describe("promoteAuthProfileInOrder", () => { process.env.OPENCLAW_STATE_DIR = stateDir; try { fs.mkdirSync(agentDir, { recursive: true }); - const goodProfileId = "openai-codex:user@example.test"; + const goodProfileId = "openai:user@example.test"; saveAuthProfileStore( { version: AUTH_STORE_VERSION, profiles: { [goodProfileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "good-access-token", refresh: "good-refresh-token", expires: Date.now() + 60_000, }, }, - lastGood: { "openai-codex": goodProfileId }, + lastGood: { openai: goodProfileId }, }, agentDir, ); await clearLastGoodProfileWithLock({ agentDir, - provider: "openai-codex", - profileId: "openai-codex:default", + provider: "openai", + profileId: "openai:default", }); - expect(loadAuthProfileStoreForRuntime(agentDir).lastGood?.["openai-codex"]).toBe( - goodProfileId, - ); + expect(loadAuthProfileStoreForRuntime(agentDir).lastGood?.["openai"]).toBe(goodProfileId); } finally { if (previousStateDir === undefined) { delete process.env.OPENCLAW_STATE_DIR; diff --git a/src/agents/auth-profiles/runtime-snapshots.test.ts b/src/agents/auth-profiles/runtime-snapshots.test.ts index 7ddab30db80a..5add004969c7 100644 --- a/src/agents/auth-profiles/runtime-snapshots.test.ts +++ b/src/agents/auth-profiles/runtime-snapshots.test.ts @@ -11,9 +11,9 @@ function createStore(access: string): AuthProfileStore { return { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access, refresh: `refresh-${access}`, expires: Date.now() + 60_000, @@ -21,10 +21,10 @@ function createStore(access: string): AuthProfileStore { }, }, order: { - "openai-codex": ["openai-codex:default"], + openai: ["openai:default"], }, usageStats: { - "openai-codex:default": { + "openai:default": { lastUsed: 1, }, }, @@ -35,12 +35,12 @@ function expectOpenAICodexSnapshotCredential( store: AuthProfileStore | undefined, params: { access: string; refresh?: string }, ) { - const credential = store?.profiles["openai-codex:default"]; + const credential = store?.profiles["openai:default"]; expect(credential?.type).toBe("oauth"); if (credential?.type !== "oauth") { throw new Error("Expected OpenAI Codex OAuth credential snapshot"); } - expect(credential.provider).toBe("openai-codex"); + expect(credential.provider).toBe("openai"); expect(credential.access).toBe(params.access); if (params.refresh) { expect(credential.refresh).toBe(params.refresh); @@ -54,23 +54,23 @@ describe("runtime auth profile snapshots", () => { try { const stored = createStore("access-1"); setRuntimeAuthProfileStoreSnapshot(stored, agentDir); - stored.profiles["openai-codex:default"].provider = "mutated"; - stored.order!["openai-codex"].push("mutated"); + stored.profiles["openai:default"].provider = "mutated"; + stored.order!["openai"].push("mutated"); const first = getRuntimeAuthProfileStoreSnapshot(agentDir); expectOpenAICodexSnapshotCredential(first, { access: "access-1" }); - expect(first?.order?.["openai-codex"]).toEqual(["openai-codex:default"]); + expect(first?.order?.["openai"]).toEqual(["openai:default"]); - first!.profiles["openai-codex:default"].provider = "mutated-again"; - first!.usageStats!["openai-codex:default"].lastUsed = 99; + first!.profiles["openai:default"].provider = "mutated-again"; + first!.usageStats!["openai:default"].lastUsed = 99; const second = getRuntimeAuthProfileStoreSnapshot(agentDir); expectOpenAICodexSnapshotCredential(second, { access: "access-1" }); - expect(second?.usageStats?.["openai-codex:default"]?.lastUsed).toBe(1); + expect(second?.usageStats?.["openai:default"]?.lastUsed).toBe(1); const replacement = createStore("access-2"); replaceRuntimeAuthProfileStoreSnapshots([{ agentDir, store: replacement }]); - const replacementCredential = replacement.profiles["openai-codex:default"]; + const replacementCredential = replacement.profiles["openai:default"]; expect(replacementCredential?.type).toBe("oauth"); if (replacementCredential?.type === "oauth") { replacementCredential.access = "mutated-replacement"; diff --git a/src/agents/auth-profiles/session-override.test.ts b/src/agents/auth-profiles/session-override.test.ts index 5952e86cefaf..bc578905159e 100644 --- a/src/agents/auth-profiles/session-override.test.ts +++ b/src/agents/auth-profiles/session-override.test.ts @@ -142,8 +142,8 @@ function createAuthStoreWithProfiles(params: { }; } -const TEST_PRIMARY_PROFILE_ID = "openai-codex:primary@example.test"; -const TEST_SECONDARY_PROFILE_ID = "openai-codex:secondary@example.test"; +const TEST_PRIMARY_PROFILE_ID = "openai:primary@example.test"; +const TEST_SECONDARY_PROFILE_ID = "openai:secondary@example.test"; describe("resolveSessionAuthProfileOverride", () => { afterEach(() => { @@ -334,17 +334,17 @@ describe("resolveSessionAuthProfileOverride", () => { profiles: { [TEST_PRIMARY_PROFILE_ID]: { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-josh", }, [TEST_SECONDARY_PROFILE_ID]: { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-claude", }, }, order: { - "openai-codex": [TEST_PRIMARY_PROFILE_ID], + openai: [TEST_PRIMARY_PROFILE_ID], }, }); @@ -358,7 +358,7 @@ describe("resolveSessionAuthProfileOverride", () => { const resolved = await resolveSessionAuthProfileOverride({ cfg: {} as OpenClawConfig, - provider: "openai-codex", + provider: "openai", agentDir, sessionEntry, sessionStore, @@ -382,7 +382,7 @@ describe("resolveSessionAuthProfileOverride", () => { profiles: { [TEST_PRIMARY_PROFILE_ID]: { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-codex", }, }, @@ -424,12 +424,12 @@ describe("resolveSessionAuthProfileOverride", () => { profiles: { [TEST_PRIMARY_PROFILE_ID]: { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-codex", }, }, order: { - "openai-codex": [TEST_PRIMARY_PROFILE_ID], + openai: [TEST_PRIMARY_PROFILE_ID], }, }); @@ -444,7 +444,7 @@ describe("resolveSessionAuthProfileOverride", () => { const resolved = await resolveSessionAuthProfileOverride({ cfg: {} as OpenClawConfig, provider: "openai", - acceptedProviderIds: ["openai-codex"], + acceptedProviderIds: ["openai"], agentDir, sessionEntry, sessionStore, @@ -472,12 +472,12 @@ describe("resolveSessionAuthProfileOverride", () => { }, [TEST_PRIMARY_PROFILE_ID]: { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-codex", }, }, order: { - "openai-codex": [TEST_PRIMARY_PROFILE_ID], + openai: [TEST_PRIMARY_PROFILE_ID], }, }); @@ -492,7 +492,7 @@ describe("resolveSessionAuthProfileOverride", () => { const resolved = await resolveSessionAuthProfileOverride({ cfg: {} as OpenClawConfig, provider: "openai", - acceptedProviderIds: ["openai-codex"], + acceptedProviderIds: ["openai"], agentDir, sessionEntry, sessionStore, @@ -516,17 +516,17 @@ describe("resolveSessionAuthProfileOverride", () => { profiles: { [TEST_PRIMARY_PROFILE_ID]: { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-stale", }, [TEST_SECONDARY_PROFILE_ID]: { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-healthy", }, }, order: { - "openai-codex": [TEST_SECONDARY_PROFILE_ID, TEST_PRIMARY_PROFILE_ID], + openai: [TEST_SECONDARY_PROFILE_ID, TEST_PRIMARY_PROFILE_ID], }, }); authStoreMocks.isProfileInCooldown.mockImplementation( @@ -543,7 +543,7 @@ describe("resolveSessionAuthProfileOverride", () => { const resolved = await resolveSessionAuthProfileOverride({ cfg: {} as OpenClawConfig, - provider: "openai-codex", + provider: "openai", agentDir, sessionEntry, sessionStore, diff --git a/src/agents/auth-profiles/store.sidecar-runtime-defaults.test.ts b/src/agents/auth-profiles/store.sidecar-runtime-defaults.test.ts deleted file mode 100644 index 0bf4240cd682..000000000000 --- a/src/agents/auth-profiles/store.sidecar-runtime-defaults.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { resolveOAuthDir } from "../../config/paths.js"; -import { AUTH_STORE_VERSION } from "./constants.js"; -import { legacyOAuthSidecarTestUtils } from "./legacy-oauth-sidecar.js"; -import { resolveAuthStorePath } from "./paths.js"; -import { - clearRuntimeAuthProfileStoreSnapshots, - ensureAuthProfileStoreWithoutExternalProfiles, - loadAuthProfileStoreForSecretsRuntime, - loadAuthProfileStoreWithoutExternalProfiles, -} from "./store.js"; - -const PROFILE_ID = "openai-codex:default"; -const SEED = "legacy-seed"; -const SIDECAR_REF = { - source: "openclaw-credentials" as const, - provider: "openai-codex" as const, - id: "0123456789abcdef0123456789abcdef", -}; - -const envBackup: Record = {}; -const envKeys = ["OPENCLAW_STATE_DIR", "OPENCLAW_OAUTH_DIR", "OPENCLAW_AUTH_PROFILE_SECRET_KEY"]; -const tempDirs: string[] = []; - -beforeEach(() => { - for (const key of envKeys) { - envBackup[key] = process.env[key]; - } - clearRuntimeAuthProfileStoreSnapshots(); -}); - -afterEach(() => { - for (const key of envKeys) { - if (envBackup[key] === undefined) { - delete process.env[key]; - } else { - process.env[key] = envBackup[key]; - } - } - clearRuntimeAuthProfileStoreSnapshots(); - for (const dir of tempDirs.splice(0)) { - fs.rmSync(dir, { recursive: true, force: true }); - } -}); - -function setUpSidecarFixture(): { agentDir: string } { - const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sidecar-runtime-defaults-")); - tempDirs.push(stateDir); - process.env.OPENCLAW_STATE_DIR = stateDir; - delete process.env.OPENCLAW_OAUTH_DIR; - process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY = SEED; - - const agentDir = path.join(stateDir, "agents", "main", "agent"); - fs.mkdirSync(agentDir, { recursive: true }); - fs.writeFileSync( - resolveAuthStorePath(agentDir), - `${JSON.stringify( - { - version: AUTH_STORE_VERSION, - profiles: { - [PROFILE_ID]: { - type: "oauth", - provider: "openai-codex", - expires: 123456, - accountId: "acct-legacy", - oauthRef: SIDECAR_REF, - }, - }, - }, - null, - 2, - )}\n`, - ); - - const sidecarPath = path.join(resolveOAuthDir(), "auth-profiles", `${SIDECAR_REF.id}.json`); - fs.mkdirSync(path.dirname(sidecarPath), { recursive: true }); - fs.writeFileSync( - sidecarPath, - `${JSON.stringify( - { - version: 1, - profileId: PROFILE_ID, - provider: "openai-codex", - encrypted: legacyOAuthSidecarTestUtils.encryptLegacyOAuthMaterial({ - ref: SIDECAR_REF, - profileId: PROFILE_ID, - provider: "openai-codex", - seed: SEED, - material: { - access: "legacy-access-token", - refresh: "legacy-refresh-token", - idToken: "legacy-id-token", - }, - }), - }, - null, - 2, - )}\n`, - ); - - return { agentDir }; -} - -describe("secrets-runtime store loaders rehydrate legacy oauthRef sidecars by default", () => { - it("loadAuthProfileStoreForSecretsRuntime hydrates inline tokens", () => { - const { agentDir } = setUpSidecarFixture(); - const credential = loadAuthProfileStoreForSecretsRuntime(agentDir).profiles[PROFILE_ID]; - expect(credential).toMatchObject({ - type: "oauth", - provider: "openai-codex", - access: "legacy-access-token", - refresh: "legacy-refresh-token", - idToken: "legacy-id-token", - }); - expect(credential).not.toHaveProperty("oauthRef"); - }); - - it("loadAuthProfileStoreWithoutExternalProfiles hydrates inline tokens", () => { - const { agentDir } = setUpSidecarFixture(); - const credential = loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles[PROFILE_ID]; - expect(credential).toMatchObject({ - type: "oauth", - provider: "openai-codex", - access: "legacy-access-token", - refresh: "legacy-refresh-token", - idToken: "legacy-id-token", - }); - expect(credential).not.toHaveProperty("oauthRef"); - }); - - it("ensureAuthProfileStoreWithoutExternalProfiles hydrates inline tokens", () => { - const { agentDir } = setUpSidecarFixture(); - const credential = ensureAuthProfileStoreWithoutExternalProfiles(agentDir).profiles[PROFILE_ID]; - expect(credential).toMatchObject({ - type: "oauth", - provider: "openai-codex", - access: "legacy-access-token", - refresh: "legacy-refresh-token", - idToken: "legacy-id-token", - }); - expect(credential).not.toHaveProperty("oauthRef"); - }); - - it("explicit resolveLegacyOAuthSidecars: false still opts out of sidecar hydration", () => { - const { agentDir } = setUpSidecarFixture(); - const credential = loadAuthProfileStoreWithoutExternalProfiles(agentDir, { - resolveLegacyOAuthSidecars: false, - }).profiles[PROFILE_ID]; - expect(credential).not.toHaveProperty("access"); - expect(credential).not.toHaveProperty("refresh"); - expect(credential).not.toHaveProperty("idToken"); - }); -}); diff --git a/src/agents/auth-profiles/store.ts b/src/agents/auth-profiles/store.ts index 396ca710c15c..6cc2a7731f05 100644 --- a/src/agents/auth-profiles/store.ts +++ b/src/agents/auth-profiles/store.ts @@ -4,6 +4,7 @@ import { isDeepStrictEqual } from "node:util"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { withFileLock } from "../../infra/file-lock.js"; import { loadJsonFile, repairJsonFilePermissions, saveJsonFile } from "../../infra/json-file.js"; +import { isRecord } from "../../utils.js"; import { cloneAuthProfileStore } from "./clone.js"; import { AUTH_STORE_LOCK_OPTIONS, AUTH_STORE_VERSION, log } from "./constants.js"; import { @@ -26,10 +27,8 @@ import { import { applyLegacyAuthStore, buildPersistedAuthProfileSecretsStore, - isRuntimeLegacyOAuthSidecarCredential, loadLegacyAuthProfileStore, loadPersistedAuthProfileStore, - matchesRuntimeLegacyOAuthSidecarMaterial, mergeAuthProfileStores, mergeOAuthFileIntoStore, } from "./persisted.js"; @@ -53,7 +52,6 @@ type LoadAuthProfileStoreOptions = { config?: OpenClawConfig; externalCli?: ExternalCliAuthDiscovery; readOnly?: boolean; - resolveLegacyOAuthSidecars?: boolean; syncExternalCli?: boolean; externalCliProviderIds?: Iterable; externalCliProfileIds?: Iterable; @@ -64,6 +62,61 @@ type SaveAuthProfileStoreOptions = { syncExternalCli?: boolean; }; +const INLINE_OAUTH_TOKEN_FIELDS = ["access", "refresh", "idToken"] as const; + +function hasInlineOAuthTokenMaterial(credential: Record): boolean { + return INLINE_OAUTH_TOKEN_FIELDS.some((field) => credential[field] !== undefined); +} + +function hasChangedInlineOAuthTokenMaterial(params: { + credential: Record; + existingCredential: Record; +}): boolean { + return INLINE_OAUTH_TOKEN_FIELDS.some((field) => { + if (params.credential[field] === undefined) { + return false; + } + return !isDeepStrictEqual(params.credential[field], params.existingCredential[field]); + }); +} + +function preserveLegacyOAuthRefsOnSave(params: { + payload: ReturnType; + existingRaw: unknown; +}): ReturnType { + if (!isRecord(params.existingRaw) || !isRecord(params.existingRaw.profiles)) { + return params.payload; + } + let nextProfiles: typeof params.payload.profiles | undefined; + for (const [profileId, credential] of Object.entries( + params.payload.profiles as Record, + )) { + if (!isRecord(credential) || credential.oauthRef !== undefined || credential.type !== "oauth") { + continue; + } + const existingCredential = params.existingRaw.profiles[profileId]; + if ( + !isRecord(existingCredential) || + existingCredential.oauthRef === undefined || + existingCredential.type !== "oauth" + ) { + continue; + } + if ( + hasInlineOAuthTokenMaterial(credential) && + hasChangedInlineOAuthTokenMaterial({ credential, existingCredential }) + ) { + continue; + } + nextProfiles ??= { ...params.payload.profiles }; + nextProfiles[profileId] = { + ...credential, + oauthRef: existingCredential.oauthRef, + } as unknown as (typeof nextProfiles)[string]; + } + return nextProfiles ? { ...params.payload, profiles: nextProfiles } : params.payload; +} + type ResolvedExternalCliOverlayOptions = { allowKeychainPrompt?: boolean; config?: OpenClawConfig; @@ -83,16 +136,11 @@ type ExternalCliSyncResult = { }; function resolvePersistedLoadOptions( - options: - | Pick - | undefined, -): { allowKeychainPrompt?: boolean; resolveLegacyOAuthSidecars?: boolean } { - return { - resolveLegacyOAuthSidecars: options?.resolveLegacyOAuthSidecars ?? true, - ...(options?.allowKeychainPrompt !== undefined - ? { allowKeychainPrompt: options.allowKeychainPrompt } - : {}), - }; + options: Pick | undefined, +): { allowKeychainPrompt?: boolean } { + return options?.allowKeychainPrompt !== undefined + ? { allowKeychainPrompt: options.allowKeychainPrompt } + : {}; } function isInheritedMainOAuthCredential(params: { @@ -494,7 +542,7 @@ function buildLocalAuthProfileStoreForSave(params: { function buildAuthProfileStoreWithoutExternalProfiles(params: { store: AuthProfileStore; agentDir?: string; - options?: Pick; + options?: Pick; }): AuthProfileStore { const runtimeExternalProfileIds = new Set(params.store.runtimeExternalProfileIds ?? []); const localStore = cloneAuthProfileStore(params.store); @@ -862,21 +910,16 @@ export function loadAuthProfileStoreForSecretsRuntime( ...options, readOnly: true, allowKeychainPrompt: false, - resolveLegacyOAuthSidecars: true, }); } export function loadAuthProfileStoreWithoutExternalProfiles( agentDir?: string, - loadOptions?: Pick< - LoadAuthProfileStoreOptions, - "allowKeychainPrompt" | "resolveLegacyOAuthSidecars" - >, + loadOptions?: Pick, ): AuthProfileStore { const options: LoadAuthProfileStoreOptions = { readOnly: true, allowKeychainPrompt: loadOptions?.allowKeychainPrompt ?? false, - resolveLegacyOAuthSidecars: loadOptions?.resolveLegacyOAuthSidecars ?? true, }; const store = loadAuthProfileStoreForAgent(agentDir, options); const authPath = resolveAuthStorePath(agentDir); @@ -926,13 +969,11 @@ export function ensureAuthProfileStoreWithoutExternalProfiles( options?: { allowKeychainPrompt?: boolean; readOnly?: boolean; - resolveLegacyOAuthSidecars?: boolean; syncExternalCli?: boolean; }, ): AuthProfileStore { const effectiveOptions: LoadAuthProfileStoreOptions = { ...options, - resolveLegacyOAuthSidecars: options?.resolveLegacyOAuthSidecars ?? true, }; const runtimeStore = resolveRuntimeAuthProfileStore(agentDir, effectiveOptions); if (runtimeStore) { @@ -1046,20 +1087,11 @@ export function saveAuthProfileStore( ): void { const authPath = resolveAuthStorePath(agentDir); const statePath = resolveAuthStatePath(agentDir); - const runtimeLegacyOAuthSidecarProfileIds = new Set( - Object.entries(store.profiles) - .filter( - ([profileId, credential]) => - isRuntimeLegacyOAuthSidecarCredential(credential) || - matchesRuntimeLegacyOAuthSidecarMaterial({ authPath, profileId, credential }), - ) - .map(([profileId]) => profileId), - ); const localStore = buildLocalAuthProfileStoreForSave({ store, agentDir, options }); const existingRaw = loadJsonFile(authPath); - const payload = buildPersistedAuthProfileSecretsStore(localStore, undefined, { + const payload = preserveLegacyOAuthRefsOnSave({ + payload: buildPersistedAuthProfileSecretsStore(localStore), existingRaw, - runtimeLegacyOAuthSidecarProfileIds, }); if (isDeepStrictEqual(existingRaw, payload)) { repairJsonFilePermissions(authPath); diff --git a/src/agents/auth-profiles/usage.test.ts b/src/agents/auth-profiles/usage.test.ts index 58ad9d94d139..f906e79f2d96 100644 --- a/src/agents/auth-profiles/usage.test.ts +++ b/src/agents/auth-profiles/usage.test.ts @@ -39,10 +39,10 @@ function makeStore(usageStats: AuthProfileStore["usageStats"]): AuthProfileStore version: 1, profiles: { "anthropic:default": { type: "api_key", provider: "anthropic", key: "sk-test" }, - "openai:default": { type: "api_key", provider: "openai", key: "sk-test-2" }, - "openai-codex:default": { + "openai:api-key": { type: "api_key", provider: "openai", key: "sk-test-2" }, + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "codex-access-token", refresh: "codex-refresh-token", expires: 4_102_444_800_000, @@ -123,12 +123,12 @@ describe("isProfileInCooldown", () => { it("returns true when blockedUntil is in the future", () => { const store = makeStore({ - "openai-codex:default": { + "openai:default": { blockedUntil: Date.now() + 60_000, blockedReason: "subscription_limit", }, }); - expect(isProfileInCooldown(store, "openai-codex:default")).toBe(true); + expect(isProfileInCooldown(store, "openai:default")).toBe(true); }); it("returns false when cooldownUntil has passed", () => { @@ -401,7 +401,7 @@ describe("clearExpiredCooldowns", () => { it("clears expired blockedUntil and resets errorCount", () => { const lastFailureAt = Date.now() - 120_000; const store = makeStore({ - "openai-codex:default": { + "openai:default": { blockedUntil: Date.now() - 1_000, blockedReason: "subscription_limit", blockedSource: "codex_rate_limits", @@ -413,7 +413,7 @@ describe("clearExpiredCooldowns", () => { expect(clearExpiredCooldowns(store)).toBe(true); - const stats = store.usageStats?.["openai-codex:default"]; + const stats = store.usageStats?.["openai:default"]; expect(stats?.blockedUntil).toBeUndefined(); expect(stats?.blockedReason).toBeUndefined(); expect(stats?.blockedSource).toBeUndefined(); @@ -790,14 +790,14 @@ describe("markAuthProfileBlockedUntil", () => { const nowSpy = vi.spyOn(Date, "now").mockReturnValue(Date.parse("2026-05-30T18:00:00.000Z")); const laterBlockedUntil = Date.parse("2031-01-01T00:00:00.000Z"); const store = makeStore({ - "openai-codex:default": { + "openai:default": { blockedUntil: laterBlockedUntil, }, }); try { await markAuthProfileBlockedUntil({ store, - profileId: "openai-codex:default", + profileId: "openai:default", blockedUntil: Date.parse("2030-01-01T00:00:00.000Z"), source: "codex_rate_limits", }); @@ -805,7 +805,7 @@ describe("markAuthProfileBlockedUntil", () => { nowSpy.mockRestore(); } - expect(store.usageStats?.["openai-codex:default"]?.blockedUntil).toBe(laterBlockedUntil); + expect(store.usageStats?.["openai:default"]?.blockedUntil).toBe(laterBlockedUntil); }); it("ignores blocked-until updates when the process clock is invalid", async () => { @@ -814,7 +814,7 @@ describe("markAuthProfileBlockedUntil", () => { try { await markAuthProfileBlockedUntil({ store, - profileId: "openai-codex:default", + profileId: "openai:default", blockedUntil: Date.parse("2030-01-01T00:00:00.000Z"), source: "codex_rate_limits", }); @@ -831,7 +831,7 @@ describe("markAuthProfileBlockedUntil", () => { await markAuthProfileBlockedUntil({ store, - profileId: "openai-codex:default", + profileId: "openai:default", blockedUntil: Number.MAX_SAFE_INTEGER, source: "codex_rate_limits", }); @@ -871,7 +871,7 @@ describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => { try { await markAuthProfileFailure({ store: params.store, - profileId: "openai-codex:default", + profileId: "openai:default", reason: params.reason ?? "rate_limit", }); } finally { @@ -941,7 +941,7 @@ describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => { expect(headers["ChatGPT-Account-Id"]).toBe("acct_test_123"); expect(headers.originator).toBe("openclaw"); expect(headers["User-Agent"]).toMatch(/^openclaw\//); - const stats = store.usageStats?.["openai-codex:default"]; + const stats = store.usageStats?.["openai:default"]; if (exactBlocked) { expect(stats?.blockedUntil).toBe(now + expectedMs); expect(stats?.blockedReason).toBe("subscription_limit"); @@ -958,7 +958,7 @@ describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => { await markCodexFailureAt({ store, now }); - expect(store.usageStats?.["openai-codex:default"]?.cooldownUntil).toBe(now + 43_200_000); + expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBe(now + 43_200_000); }); it("maps HTTP 403 to a 24h cooldown", async () => { @@ -968,7 +968,7 @@ describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => { await markCodexFailureAt({ store, now }); - expect(store.usageStats?.["openai-codex:default"]?.cooldownUntil).toBe(now + 86_400_000); + expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBe(now + 86_400_000); }); it("maps other HTTP errors to a 5m cooldown", async () => { @@ -978,14 +978,14 @@ describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => { await markCodexFailureAt({ store, now }); - expect(store.usageStats?.["openai-codex:default"]?.cooldownUntil).toBe(now + 300_000); + expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBe(now + 300_000); }); it("preserves a longer existing cooldown via max semantics", async () => { const now = 1_700_000_000_000; const existingCooldownUntil = now + 6 * 60 * 60 * 1000; const store = makeStore({ - "openai-codex:default": { + "openai:default": { cooldownUntil: existingCooldownUntil, cooldownReason: "rate_limit", errorCount: 2, @@ -1001,7 +1001,7 @@ describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => { await markCodexFailureAt({ store, now, useLock: true }); - expect(store.usageStats?.["openai-codex:default"]?.cooldownUntil).toBe(existingCooldownUntil); + expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBe(existingCooldownUntil); }); it("falls back to a 30s cooldown when the WHAM probe fails", async () => { @@ -1011,7 +1011,7 @@ describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => { await markCodexFailureAt({ store, now, reason: "unknown" }); - expect(store.usageStats?.["openai-codex:default"]?.cooldownUntil).toBe(now + 30_000); + expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBe(now + 30_000); }); it.each([ @@ -1029,7 +1029,7 @@ describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => { await markCodexFailureAt({ store, now }); - const stats = store.usageStats?.["openai-codex:default"]; + const stats = store.usageStats?.["openai:default"]; expect(stats?.blockedUntil).toBeUndefined(); expect(stats?.cooldownUntil).toBe(now + 30_000); expect(stats?.cooldownReason).toBe("rate_limit"); diff --git a/src/agents/auth-profiles/usage.ts b/src/agents/auth-profiles/usage.ts index 4ff8b1cf6192..b5fba31781e5 100644 --- a/src/agents/auth-profiles/usage.ts +++ b/src/agents/auth-profiles/usage.ts @@ -113,7 +113,7 @@ function shouldProbeWhamForFailure( ): boolean { const normalizedProvider = normalizeProviderId(provider ?? ""); return ( - (normalizedProvider === "openai" || normalizedProvider === "openai-codex") && + normalizedProvider === "openai" && (reason === "rate_limit" || reason === "empty_response" || reason === "no_error_details" || @@ -208,9 +208,13 @@ async function probeWhamForCooldown( const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), WHAM_TIMEOUT_MS); try { + const version = process.env.OPENCLAW_VERSION?.trim(); const defaultHeaders: Record = { Authorization: `Bearer ${profile.access}`, Accept: "application/json", + originator: "openclaw", + ...(version ? { version } : {}), + "User-Agent": `openclaw/${version || "dev"}`, }; if (profile.accountId) { defaultHeaders["ChatGPT-Account-Id"] = profile.accountId; diff --git a/src/agents/btw.test.ts b/src/agents/btw.test.ts index a54ddfff7d1a..6ff9eb15bb1e 100644 --- a/src/agents/btw.test.ts +++ b/src/agents/btw.test.ts @@ -566,7 +566,7 @@ describe("runBtwSideQuestion", () => { id: "gpt-5.5", api: "openai-responses", }); - resolveSessionAuthProfileOverrideMock.mockResolvedValue("openai-codex:work"); + resolveSessionAuthProfileOverrideMock.mockResolvedValue("openai:work"); const result = await runSideQuestion({ provider: "openai", @@ -595,7 +595,7 @@ describe("runBtwSideQuestion", () => { expect(sideQuestionParams.sessionId).toBe("session-1"); expect(sideQuestionParams.agentId).toBe("main"); expect(sideQuestionParams.workspaceDir).toBe("/tmp/workspace"); - expect(sideQuestionParams.authProfileId).toBe("openai-codex:work"); + expect(sideQuestionParams.authProfileId).toBe("openai:work"); expect( (mockArg(codexSideQuestionMock, 0, 0) as { sessionFile?: string }).sessionFile, ).toContain("session-1.jsonl"); diff --git a/src/agents/btw.ts b/src/agents/btw.ts index ab382dc86525..291038633c47 100644 --- a/src/agents/btw.ts +++ b/src/agents/btw.ts @@ -35,7 +35,7 @@ import { requireApiKey, } from "./model-auth.js"; import { ensureOpenClawModelsJson } from "./models-config.js"; -import { listOpenAIAuthProfileProvidersForAgentRuntime } from "./openai-codex-routing.js"; +import { listOpenAIAuthProfileProvidersForAgentRuntime } from "./openai-routing.js"; import { registerProviderStreamForModel } from "./provider-stream.js"; import { stripToolResultDetails } from "./session-transcript-repair.js"; import { sanitizeImageBlocks } from "./tool-images.js"; diff --git a/src/agents/cli-auth-epoch.test.ts b/src/agents/cli-auth-epoch.test.ts index 18bf8e6c0d4e..29d7955b2c1f 100644 --- a/src/agents/cli-auth-epoch.test.ts +++ b/src/agents/cli-auth-epoch.test.ts @@ -326,7 +326,7 @@ describe("resolveCliAuthEpoch", () => { setCliAuthEpochTestDeps({ readCodexCliCredentialsCached: () => ({ type: "oauth", - provider: "openai-codex", + provider: "openai", access, refresh: localRefresh, expires: 1, @@ -394,7 +394,7 @@ describe("resolveCliAuthEpoch", () => { setCliAuthEpochTestDeps({ readCodexCliCredentialsCached: () => ({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: localAccess, refresh: "local-refresh", expires: 1, @@ -403,9 +403,9 @@ describe("resolveCliAuthEpoch", () => { loadAuthProfileStoreForRuntime: () => ({ version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "profile-access", refresh: profileRefresh, expires: 1, @@ -417,25 +417,25 @@ describe("resolveCliAuthEpoch", () => { const first = await resolveCliAuthEpoch({ provider: "codex-cli", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", skipLocalCredential: true, }); localAccess = "local-access-b"; const second = await resolveCliAuthEpoch({ provider: "codex-cli", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", skipLocalCredential: true, }); profileRefresh = "profile-refresh-b"; const third = await resolveCliAuthEpoch({ provider: "codex-cli", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", skipLocalCredential: true, }); profileAccountId = "acct-2"; const fourth = await resolveCliAuthEpoch({ provider: "codex-cli", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", skipLocalCredential: true, }); @@ -449,7 +449,7 @@ describe("resolveCliAuthEpoch", () => { it("uses non-prompting Codex CLI credential reads for epoch fingerprints", async () => { const readCodexCliCredentialsCached = vi.fn(() => ({ type: "oauth" as const, - provider: "openai-codex" as const, + provider: "openai" as const, access: "local-access", refresh: "local-refresh", expires: 1, diff --git a/src/agents/cli-credentials.test.ts b/src/agents/cli-credentials.test.ts index ee3592c87e75..6b2906bd5e1b 100644 --- a/src/agents/cli-credentials.test.ts +++ b/src/agents/cli-credentials.test.ts @@ -350,7 +350,7 @@ describe("cli credentials", () => { expectFields(creds, { access: createJwtWithExp(expSeconds), refresh: "keychain-refresh", - provider: "openai-codex", + provider: "openai", expires: expSeconds * 1000, idToken: "keychain-id-token", }); @@ -380,7 +380,7 @@ describe("cli credentials", () => { expectFields(creds, { refresh: "keychain-refresh", - provider: "openai-codex", + provider: "openai", expires: fallbackExpiry, }); }); @@ -436,7 +436,7 @@ describe("cli credentials", () => { expectFields(creds, { access: createJwtWithExp(expSeconds), refresh: "file-refresh", - provider: "openai-codex", + provider: "openai", expires: expSeconds * 1000, idToken: "file-id-token", }); @@ -497,7 +497,7 @@ describe("cli credentials", () => { expectFields(creds, { refresh: "file-refresh", - provider: "openai-codex", + provider: "openai", expires: Math.floor(mtimeMs) + 60 * 60 * 1000, }); } finally { @@ -532,7 +532,7 @@ describe("cli credentials", () => { expectFields(creds, { access: createJwtWithExp(expSeconds), refresh: "file-refresh", - provider: "openai-codex", + provider: "openai", }); expect(execSyncMock).not.toHaveBeenCalled(); }); @@ -568,7 +568,7 @@ describe("cli credentials", () => { expectFields(withKeychain, { access: createJwtWithExp(expSeconds), refresh: "keychain-refresh", - provider: "openai-codex", + provider: "openai", }); expect(execSyncMock).toHaveBeenCalledTimes(1); }); @@ -615,12 +615,12 @@ describe("cli credentials", () => { expectFields(withKeychain, { refresh: "keychain-refresh", expires: keychainExpiry * 1000, - provider: "openai-codex", + provider: "openai", }); expectFields(withoutPrompt, { refresh: "file-refresh", expires: fileExpiry * 1000, - provider: "openai-codex", + provider: "openai", }); expect(execSyncMock).toHaveBeenCalledTimes(1); }); diff --git a/src/agents/cli-credentials.ts b/src/agents/cli-credentials.ts index c8465f5ed11a..e7f87f564542 100644 --- a/src/agents/cli-credentials.ts +++ b/src/agents/cli-credentials.ts @@ -329,7 +329,7 @@ function readCodexKeychainCredentials(options?: { return { type: "oauth", - provider: "openai-codex" as OAuthProvider, + provider: "openai" as OAuthProvider, access: accessToken, refresh: refreshToken, expires, @@ -664,7 +664,7 @@ export function readCodexCliCredentials(options?: { return { type: "oauth", - provider: "openai-codex" as OAuthProvider, + provider: "openai" as OAuthProvider, access: accessToken, refresh: refreshToken, expires, diff --git a/src/agents/cli-runner/prepare.test.ts b/src/agents/cli-runner/prepare.test.ts index b871f8f7d1c0..765101e40ec7 100644 --- a/src/agents/cli-runner/prepare.test.ts +++ b/src/agents/cli-runner/prepare.test.ts @@ -257,10 +257,10 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { expect( shouldSkipLocalCliCredentialEpoch({ authEpochMode: "profile-only", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", authCredential: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -278,7 +278,7 @@ describe("shouldSkipLocalCliCredentialEpoch", () => { expect( shouldSkipLocalCliCredentialEpoch({ authEpochMode: "profile-only", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", authCredential: undefined, preparedExecution: null, }), diff --git a/src/agents/codex-native-web-search-core.ts b/src/agents/codex-native-web-search-core.ts index 12d107842446..b5f1a78f8997 100644 --- a/src/agents/codex-native-web-search-core.ts +++ b/src/agents/codex-native-web-search-core.ts @@ -26,7 +26,7 @@ type CodexNativeSearchPayloadPatchResult = { status: "payload_not_object" | "native_tool_already_present" | "injected"; }; -const OPENAI_AUTH_PROVIDER_IDS = ["openai", "openai-codex"] as const; +const OPENAI_AUTH_PROVIDER_IDS = ["openai"] as const; function isOpenAIAuthProviderId(provider: string | undefined): boolean { return OPENAI_AUTH_PROVIDER_IDS.some((candidate) => candidate === provider); @@ -36,7 +36,7 @@ export function isCodexNativeSearchEligibleModel(params: { modelProvider?: string; modelApi?: string; }): boolean { - return params.modelApi === "openai-codex-responses"; + return params.modelApi === "openai-chatgpt-responses"; } function hasCodexNativeWebSearchTool(tools: unknown): boolean { @@ -95,7 +95,7 @@ export function resolveCodexNativeSearchActivation(params: { const codexConfig = resolveCodexNativeWebSearchConfig(params.config); const nativeEligible = isCodexNativeSearchEligibleModel(params); const hasRequiredAuth = - params.modelApi !== "openai-codex-responses" || + params.modelApi !== "openai-chatgpt-responses" || !isOpenAIAuthProviderId(params.modelProvider) || hasAvailableCodexAuth(params); diff --git a/src/agents/codex-native-web-search.test.ts b/src/agents/codex-native-web-search.test.ts index 3600f1605562..3d716b38e097 100644 --- a/src/agents/codex-native-web-search.test.ts +++ b/src/agents/codex-native-web-search.test.ts @@ -27,8 +27,8 @@ describe("resolveCodexNativeSearchActivation", () => { it("returns managed_only when native Codex search is disabled", () => { const result = resolveCodexNativeSearchActivation({ config: { tools: { web: { search: { enabled: true } } } }, - modelProvider: "openai-codex", - modelApi: "openai-codex-responses", + modelProvider: "openai", + modelApi: "openai-chatgpt-responses", }); expect(result.state).toBe("managed_only"); @@ -46,43 +46,43 @@ describe("resolveCodexNativeSearchActivation", () => { expect(result.inactiveReason).toBe("model_not_eligible"); }); - it("activates for direct openai-codex when auth exists", () => { + it("activates for direct openai when auth exists", () => { const result = resolveCodexNativeSearchActivation({ config: { ...baseConfig, auth: { profiles: { - "openai-codex:default": { - provider: "openai-codex", + "openai:default": { + provider: "openai", mode: "oauth", }, }, }, }, - modelProvider: "openai-codex", - modelApi: "openai-codex-responses", + modelProvider: "openai", + modelApi: "openai-chatgpt-responses", }); expect(result.state).toBe("native_active"); expect(result.codexMode).toBe("cached"); }); - it("falls back to managed_only when direct openai-codex auth is missing", () => { + it("falls back to managed_only when direct openai auth is missing", () => { const result = resolveCodexNativeSearchActivation({ config: baseConfig, - modelProvider: "openai-codex", - modelApi: "openai-codex-responses", + modelProvider: "openai", + modelApi: "openai-chatgpt-responses", }); expect(result.state).toBe("managed_only"); expect(result.inactiveReason).toBe("codex_auth_missing"); }); - it("activates for api-compatible openai-codex-responses providers without separate Codex auth", () => { + it("activates for api-compatible openai-chatgpt-responses providers without separate Codex auth", () => { const result = resolveCodexNativeSearchActivation({ config: baseConfig, modelProvider: "gateway", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", }); expect(result.state).toBe("native_active"); @@ -100,8 +100,8 @@ describe("resolveCodexNativeSearchActivation", () => { }, }, }, - modelProvider: "openai-codex", - modelApi: "openai-codex-responses", + modelProvider: "openai", + modelApi: "openai-chatgpt-responses", }); expect(result.state).toBe("managed_only"); @@ -212,7 +212,7 @@ describe("shouldSuppressManagedWebSearchTool", () => { shouldSuppressManagedWebSearchTool({ config: baseConfig, modelProvider: "gateway", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", }), ).toBe(true); @@ -227,7 +227,7 @@ describe("shouldSuppressManagedWebSearchTool", () => { }); describe("isCodexNativeWebSearchRelevant", () => { - it("treats a default model with model-level openai-codex-responses api as relevant", () => { + it("treats a default model with model-level openai-chatgpt-responses api as relevant", () => { expect( isCodexNativeWebSearchRelevant({ config: { @@ -247,7 +247,7 @@ describe("isCodexNativeWebSearchRelevant", () => { { id: "gpt-5.4", name: "gpt-5.4", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, diff --git a/src/agents/command/attempt-execution.cli.test.ts b/src/agents/command/attempt-execution.cli.test.ts index 4fec7dc96f1e..816b85ecc3d0 100644 --- a/src/agents/command/attempt-execution.cli.test.ts +++ b/src/agents/command/attempt-execution.cli.test.ts @@ -27,7 +27,7 @@ vi.mock("../model-selection.js", () => ({ vi.mock("../provider-auth-aliases.js", () => ({ resolveProviderAuthAliasMap: () => ({}), resolveProviderIdForAuth: (provider: string) => - ["codex-cli", "openai-codex"].includes(provider.trim().toLowerCase()) + ["codex-cli", "openai"].includes(provider.trim().toLowerCase()) ? "openai" : provider.trim().toLowerCase(), })); @@ -546,7 +546,7 @@ describe("CLI attempt execution", () => { const sessionEntry: SessionEntry = { sessionId: "openclaw-session-codex", updatedAt: Date.now(), - authProfileOverride: "openai-codex:work", + authProfileOverride: "openai:work", authProfileOverrideSource: "user", }; const sessionStore: Record = { [sessionKey]: sessionEntry }; @@ -577,14 +577,14 @@ describe("CLI attempt execution", () => { resolvedVerboseLevel: undefined, agentDir: tmpDir, onAgentEvent: vi.fn(), - authProfileProvider: "openai-codex", + authProfileProvider: "openai", sessionStore, storePath, sessionHasHistory: false, }); expect(runCliAgentMock).toHaveBeenCalledTimes(1); - expect(firstRunCliAgentArg().authProfileId).toBe("openai-codex:work"); + expect(firstRunCliAgentArg().authProfileId).toBe("openai:work"); }); it("persists CLI replies into the session transcript", async () => { @@ -1219,9 +1219,9 @@ describe("CLI attempt execution", () => { it("forwards user-pinned OpenAI API-key backup profiles to Codex harness runs", async () => { const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js"); - const sessionKey = "agent:main:direct:openai-codex-api-key"; + const sessionKey = "agent:main:direct:openai-chatgpt-api-key"; const sessionEntry: SessionEntry = { - sessionId: "openclaw-session-openai-codex-api-key", + sessionId: "openclaw-session-openai-chatgpt-api-key", updatedAt: Date.now(), authProfileOverride: "openai:backup", authProfileOverrideSource: "user", @@ -1273,7 +1273,7 @@ describe("CLI attempt execution", () => { isFallbackRetry: false, resolvedThinkLevel: "medium", timeoutMs: 1_000, - runId: "run-openai-codex-api-key-backup", + runId: "run-openai-chatgpt-api-key-backup", opts: {} as Parameters[0]["opts"], runContext: {} as Parameters[0]["runContext"], spawnedBy: undefined, @@ -1675,9 +1675,9 @@ describe("embedded attempt harness pinning", () => { JSON.stringify({ version: 1, profiles: { - "openai-codex:work": { + "openai:work": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -1730,7 +1730,7 @@ describe("embedded attempt harness pinning", () => { expectMockArgFields(runEmbeddedAgentMock, { agentHarnessId: undefined, - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "auto", }); }); @@ -1823,7 +1823,7 @@ describe("embedded attempt harness pinning", () => { const sessionEntry: SessionEntry = { sessionId: "explicit-agent-codex-oauth-session", updatedAt: Date.now(), - authProfileOverride: "openai-codex:work", + authProfileOverride: "openai:work", authProfileOverrideSource: "user", }; runEmbeddedAgentMock.mockResolvedValueOnce({ @@ -1864,7 +1864,7 @@ describe("embedded attempt harness pinning", () => { resolvedVerboseLevel: undefined, agentDir: tmpDir, onAgentEvent: vi.fn(), - authProfileProvider: "openai-codex", + authProfileProvider: "openai", sessionHasHistory: false, }); @@ -1873,7 +1873,7 @@ describe("embedded attempt harness pinning", () => { model: "gpt-5.4", agentHarnessId: "openclaw", agentHarnessRuntimeOverride: "openclaw", - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "user", }); }); diff --git a/src/agents/command/attempt-execution.ts b/src/agents/command/attempt-execution.ts index 9052109b3aca..0ac32392e351 100644 --- a/src/agents/command/attempt-execution.ts +++ b/src/agents/command/attempt-execution.ts @@ -34,7 +34,7 @@ import { runAgentHarnessBeforeMessageWriteHook } from "../harness/hook-helpers.j import { resolveAvailableAgentHarnessPolicy } from "../harness/selection.js"; import { resolveCliRuntimeExecutionProvider } from "../model-runtime-aliases.js"; import { isCliProvider } from "../model-selection.js"; -import { resolveOpenAIRuntimeProvider } from "../openai-codex-routing.js"; +import { resolveOpenAIRuntimeProvider } from "../openai-routing.js"; import { buildAgentRuntimeAuthPlan } from "../runtime-plan/auth.js"; import type { AgentMessage } from "../runtime/index.js"; import { acquireSessionWriteLock, resolveSessionWriteLockOptions } from "../session-write-lock.js"; diff --git a/src/agents/command/cli-compaction.ts b/src/agents/command/cli-compaction.ts index ce717864e1b9..89486c6022ca 100644 --- a/src/agents/command/cli-compaction.ts +++ b/src/agents/command/cli-compaction.ts @@ -167,7 +167,7 @@ function isNativeHarnessCompactionSession( return ( harnessId === providerId || (harnessId === "codex" && - (providerId === "codex" || providerId === "openai" || providerId === "openai-codex")) + (providerId === "codex" || providerId === "openai" || providerId === "openai")) ); } diff --git a/src/agents/command/delivery.test.ts b/src/agents/command/delivery.test.ts index cef1e7e15225..3b7418c3906c 100644 --- a/src/agents/command/delivery.test.ts +++ b/src/agents/command/delivery.test.ts @@ -248,7 +248,7 @@ describe("normalizeAgentCommandReplyPayloads", () => { durationMs: 1, agentMeta: { sessionId: "session-1", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", }, }, @@ -256,7 +256,7 @@ describe("normalizeAgentCommandReplyPayloads", () => { }); expect(normalized).toHaveLength(1); - expectTextPayload(normalized[0], "[openai-codex/gpt-5.4] Ready."); + expectTextPayload(normalized[0], "[openai/gpt-5.4] Ready."); }); it("keeps Slack options text intact for local preview when delivery is disabled", async () => { diff --git a/src/agents/command/session-store.test.ts b/src/agents/command/session-store.test.ts index f5b35edc484c..98be30bb27c7 100644 --- a/src/agents/command/session-store.test.ts +++ b/src/agents/command/session-store.test.ts @@ -337,7 +337,7 @@ describe("updateSessionStoreAfterAgentRun", () => { durationMs: 1, agentMeta: { sessionId, - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", contextTokens: 400_000, }, @@ -350,7 +350,7 @@ describe("updateSessionStoreAfterAgentRun", () => { sessionKey, storePath, sessionStore, - defaultProvider: "openai-codex", + defaultProvider: "openai", defaultModel: "gpt-5.5", result, }); diff --git a/src/agents/context.lookup.test.ts b/src/agents/context.lookup.test.ts index c20c98607730..199c3d3a013e 100644 --- a/src/agents/context.lookup.test.ts +++ b/src/agents/context.lookup.test.ts @@ -158,7 +158,7 @@ describe("lookupContextTokens", () => { mockContextModuleDeps(() => ({ models: { providers: { - "openai-codex": { + openai: { models: [{ id: "gpt-5.4", contextWindow: 1_050_000, contextTokens: 272_000 }], }, }, diff --git a/src/agents/context.test.ts b/src/agents/context.test.ts index 5a6f0b0c35a2..513120a7abd8 100644 --- a/src/agents/context.test.ts +++ b/src/agents/context.test.ts @@ -501,7 +501,7 @@ describe("resolveContextTokensForModel", () => { cfg: { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api", models: [ { @@ -519,7 +519,7 @@ describe("resolveContextTokensForModel", () => { }, }, }, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", fallbackContextTokens: 272_000, }); diff --git a/src/agents/copilot-routing.test.ts b/src/agents/copilot-routing.test.ts index c994a20f8701..e59e048aee81 100755 --- a/src/agents/copilot-routing.test.ts +++ b/src/agents/copilot-routing.test.ts @@ -125,7 +125,7 @@ describe("modelSelectionShouldEnsureCopilotRuntimePlugin", () => { ).toBe(false); expect( modelSelectionShouldEnsureCopilotRuntimePlugin({ - model: "openai-codex/gpt-4o", + model: "openai/gpt-4o", config: emptyCfg, }), ).toBe(false); diff --git a/src/agents/copilot-routing.ts b/src/agents/copilot-routing.ts index 1360e8d573c2..8fb2ff1785c3 100755 --- a/src/agents/copilot-routing.ts +++ b/src/agents/copilot-routing.ts @@ -1,6 +1,6 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolveModelRuntimePolicy } from "./model-runtime-policy.js"; -import { parseModelRefProvider } from "./openai-codex-routing.js"; +import { parseModelRefProvider } from "./openai-routing.js"; export const GITHUB_COPILOT_PROVIDER_ID = "github-copilot"; diff --git a/src/agents/embedded-agent-helpers.formatassistanterrortext.test.ts b/src/agents/embedded-agent-helpers.formatassistanterrortext.test.ts index 9fbc40d7d6e1..be31647e33c3 100644 --- a/src/agents/embedded-agent-helpers.formatassistanterrortext.test.ts +++ b/src/agents/embedded-agent-helpers.formatassistanterrortext.test.ts @@ -340,7 +340,7 @@ describe("formatAssistantErrorText", () => { it("returns an explicit re-authentication message for OAuth refresh failures", () => { const msg = makeAssistantError( - "OAuth token refresh failed for openai-codex: invalid_grant. Please try again or re-authenticate.", + "OAuth token refresh failed for openai: invalid_grant. Please try again or re-authenticate.", ); expect(formatAssistantErrorText(msg)).toBe( "Authentication refresh failed. Re-authenticate this provider and try again.", @@ -365,46 +365,46 @@ describe("formatAssistantErrorText", () => { it("returns a timeout-specific message for OAuth refresh hard timeouts", () => { const msg = makeAssistantError( - 'OAuth refresh call "refreshProviderOAuthCredentialWithPlugin(openai-codex)" exceeded hard timeout (120000ms)', + 'OAuth refresh call "refreshProviderOAuthCredentialWithPlugin(openai)" exceeded hard timeout (120000ms)', ); expect(formatAssistantErrorText(msg)).toBe( "Authentication refresh timed out before the provider completed. Retry in a moment; re-authenticate only if it keeps failing.", ); }); - it("returns a missing-scope message for OpenAI Codex scope failures", () => { + it("returns a missing-scope message for OpenAI ChatGPT scope failures", () => { const msg = makeAssistantError( '401 {"type":"error","error":{"type":"permission_error","message":"Missing scopes: api.responses.write model.request"}}', ); - expect(formatAssistantErrorText(msg, { provider: "openai-codex" })).toBe( - "Authentication is missing the required OpenAI Codex scopes. Re-run OpenAI/Codex login and try again.", + expect(formatAssistantErrorText(msg, { provider: "openai" })).toBe( + "Authentication is missing the required OpenAI ChatGPT scopes. Re-run OpenAI login and try again.", ); }); - it("returns a missing-scope message for raw OpenAI Codex scope payloads without an HTTP prefix", () => { + it("returns a missing-scope message for raw OpenAI ChatGPT scope payloads without an HTTP prefix", () => { const msg = makeAssistantError( '{"type":"error","error":{"type":"permission_error","message":"Missing scopes: api.responses.write model.request"},"code":401}', ); - expect(formatAssistantErrorText(msg, { provider: "openai-codex" })).toBe( - "Authentication is missing the required OpenAI Codex scopes. Re-run OpenAI/Codex login and try again.", + expect(formatAssistantErrorText(msg, { provider: "openai" })).toBe( + "Authentication is missing the required OpenAI ChatGPT scopes. Re-run OpenAI login and try again.", ); }); - it("does not misdiagnose non-Codex permission errors as missing-scope failures", () => { + it("does not misdiagnose other provider permission errors as OpenAI scope failures", () => { const msg = makeAssistantError( '401 {"type":"error","error":{"type":"permission_error","message":"Missing scopes: api.responses.write model.request"}}', ); - expect(formatAssistantErrorText(msg, { provider: "openai" })).not.toContain( - "required OpenAI Codex scopes", + expect(formatAssistantErrorText(msg, { provider: "anthropic" })).not.toContain( + "required OpenAI ChatGPT scopes", ); }); - it("does not misdiagnose generic Codex permission failures as missing-scope failures", () => { + it("does not misdiagnose generic OpenAI permission failures as missing-scope failures", () => { const msg = makeAssistantError( '403 {"type":"error","error":{"type":"permission_error","message":"Insufficient permissions for this organization"}}', ); - expect(formatAssistantErrorText(msg, { provider: "openai-codex" })).not.toContain( - "required OpenAI Codex scopes", + expect(formatAssistantErrorText(msg, { provider: "openai" })).not.toContain( + "required OpenAI ChatGPT scopes", ); }); diff --git a/src/agents/embedded-agent-helpers.isbillingerrormessage.test.ts b/src/agents/embedded-agent-helpers.isbillingerrormessage.test.ts index 0bc2441368ab..693f5e123314 100644 --- a/src/agents/embedded-agent-helpers.isbillingerrormessage.test.ts +++ b/src/agents/embedded-agent-helpers.isbillingerrormessage.test.ts @@ -990,7 +990,7 @@ describe("isFailoverErrorMessage", () => { ]); }); - it("matches shared model runtime openai-codex bare transport failures as timeout (#69368)", () => { + it("matches shared model runtime openai bare transport failures as timeout (#69368)", () => { expectTimeoutFailoverSamples([ "Request failed", "request failed", @@ -1428,7 +1428,7 @@ describe("classifyProviderRuntimeFailureKind", () => { it("classifies missing scope failures", () => { expect( classifyProviderRuntimeFailureKind({ - provider: "openai-codex", + provider: "openai", message: '401 {"type":"error","error":{"type":"permission_error","message":"Missing scopes: api.responses.write"}}', }), @@ -1438,27 +1438,27 @@ describe("classifyProviderRuntimeFailureKind", () => { it("classifies raw missing scope payloads without an HTTP prefix", () => { expect( classifyProviderRuntimeFailureKind({ - provider: "openai-codex", + provider: "openai", message: '{"type":"error","error":{"type":"permission_error","message":"Missing scopes: api.responses.write"},"code":401}', }), ).toBe("auth_scope"); }); - it("does not classify non-Codex permission errors as missing scope failures", () => { + it("does not classify other provider permission errors as OpenAI scope failures", () => { expect( classifyProviderRuntimeFailureKind({ - provider: "openai", + provider: "anthropic", message: '401 {"type":"error","error":{"type":"permission_error","message":"Missing scopes: api.responses.write"}}', }), ).not.toBe("auth_scope"); }); - it("does not treat generic Codex permission failures as missing scope failures", () => { + it("does not treat generic OpenAI permission failures as missing scope failures", () => { expect( classifyProviderRuntimeFailureKind({ - provider: "openai-codex", + provider: "openai", message: '403 {"type":"error","error":{"type":"permission_error","message":"Insufficient permissions for this organization"}}', }), @@ -1467,21 +1467,21 @@ describe("classifyProviderRuntimeFailureKind", () => { it("classifies OAuth refresh failures", () => { const refreshFailures = [ - "OAuth token refresh failed for openai-codex: invalid_grant. Please try again or re-authenticate.", + "OAuth token refresh failed for openai: invalid_grant. Please try again or re-authenticate.", "Your access token could not be refreshed because you have since logged out or signed in to another account. Please sign in again.", "Your authentication session could not be refreshed automatically. Please log out and sign in again.", ]; for (const message of refreshFailures) { expect(classifyProviderRuntimeFailureKind(message)).toBe("auth_refresh"); - expect(classifyFailoverReason(message, { provider: "openai-codex" })).toBe("auth_permanent"); + expect(classifyFailoverReason(message, { provider: "openai" })).toBe("auth_permanent"); } }); it("does not make uncertain OAuth refresh wrappers terminal", () => { const message = - "OAuth token refresh failed for openai-codex: file lock timeout for /tmp/agent/auth-profiles.json. Please try again or re-authenticate."; + "OAuth token refresh failed for openai: file lock timeout for /tmp/agent/auth-profiles.json. Please try again or re-authenticate."; expect(classifyProviderRuntimeFailureKind(message)).toBe("auth_refresh"); - expect(classifyFailoverReason(message, { provider: "openai-codex" })).toBe("auth"); + expect(classifyFailoverReason(message, { provider: "openai" })).toBe("auth"); }); it("keeps Codex entitlement and usage-limit payloads out of terminal auth", () => { @@ -1492,14 +1492,14 @@ describe("classifyProviderRuntimeFailureKind", () => { ]; for (const message of entitlementMessages) { expect(classifyProviderRuntimeFailureKind(message)).not.toBe("auth_refresh"); - expect(classifyFailoverReason(message, { provider: "openai-codex" })).toBe("rate_limit"); + expect(classifyFailoverReason(message, { provider: "openai" })).toBe("rate_limit"); } }); it("classifies OAuth refresh timeouts and lock contention distinctly", () => { expect( classifyProviderRuntimeFailureKind( - 'OAuth refresh call "refreshProviderOAuthCredentialWithPlugin(openai-codex)" exceeded hard timeout (120000ms)', + 'OAuth refresh call "refreshProviderOAuthCredentialWithPlugin(openai)" exceeded hard timeout (120000ms)', ), ).toBe("refresh_timeout"); expect( @@ -1509,12 +1509,12 @@ describe("classifyProviderRuntimeFailureKind", () => { classifyProviderRuntimeFailureKind({ code: "refresh_contention", message: - "OAuth token refresh failed for openai-codex: OAuth refresh failed (refresh_contention): another process is already refreshing openai-codex for openai-codex:default. Please wait for the in-flight refresh to finish and retry.", + "OAuth token refresh failed for openai: OAuth refresh failed (refresh_contention): another process is already refreshing openai for openai:default. Please wait for the in-flight refresh to finish and retry.", }), ).toBe("refresh_contention"); expect( classifyProviderRuntimeFailureKind( - "OAuth token refresh failed for openai-codex: file lock timeout for /tmp/agent/auth-profiles.json. Please try again or re-authenticate.", + "OAuth token refresh failed for openai: file lock timeout for /tmp/agent/auth-profiles.json. Please try again or re-authenticate.", ), ).toBe("auth_refresh"); }); diff --git a/src/agents/embedded-agent-helpers/errors.ts b/src/agents/embedded-agent-helpers/errors.ts index 9b00df2df64f..93fac4902f1e 100644 --- a/src/agents/embedded-agent-helpers/errors.ts +++ b/src/agents/embedded-agent-helpers/errors.ts @@ -425,7 +425,7 @@ function isTransportHtmlErrorStatus(status: number | undefined): boolean { function isOpenAICodexScopeContext(raw: string, provider?: string): boolean { const normalizedProvider = normalizeLowercaseStringOrEmpty(provider); return ( - normalizedProvider === "openai-codex" || + normalizedProvider === "openai" || /\bopenai\s+codex\b/i.test(raw) || /\bcodex\b.*\bscopes?\b/i.test(raw) ); @@ -1196,8 +1196,8 @@ export function formatAssistantErrorText( if (providerRuntimeFailureKind === "auth_scope") { return ( - "Authentication is missing the required OpenAI Codex scopes. " + - "Re-run OpenAI/Codex login and try again." + "Authentication is missing the required OpenAI ChatGPT scopes. " + + "Re-run OpenAI login and try again." ); } diff --git a/src/agents/embedded-agent-helpers/failover-matches.ts b/src/agents/embedded-agent-helpers/failover-matches.ts index 77eeac747b04..ad314cb51974 100644 --- a/src/agents/embedded-agent-helpers/failover-matches.ts +++ b/src/agents/embedded-agent-helpers/failover-matches.ts @@ -173,7 +173,7 @@ const ERROR_PATTERNS = { /^terminated$/i, /^stream_read_error$/i, /\bund_err_(?:socket|connect|headers?|body|req_content_length_mismatch|aborted|closed)\b/i, - // shared model runtime's openai-codex provider surfaces `Request failed` when the HTTP + // shared model runtime's openai provider surfaces `Request failed` when the HTTP // response has no body and no status text (typical of Cloudflare 502s // from the upstream Codex service). Treat it as a transport failure so // the configured fallback chain runs instead of surfacing the error. diff --git a/src/agents/embedded-agent-runner-extraparams.test.ts b/src/agents/embedded-agent-runner-extraparams.test.ts index a385a515695f..ecc7b885608c 100644 --- a/src/agents/embedded-agent-runner-extraparams.test.ts +++ b/src/agents/embedded-agent-runner-extraparams.test.ts @@ -69,11 +69,11 @@ vi.mock("./codex-native-web-search.js", () => ({ const search = params.config?.tools?.web?.search; const codex = search?.openaiCodex; const nativeEligible = - params.modelProvider === "openai-codex" || params.modelApi === "openai-codex-responses"; + params.modelProvider === "openai" || params.modelApi === "openai-chatgpt-responses"; const hasRequiredAuth = - params.modelProvider !== "openai-codex" || + params.modelProvider !== "openai" || Object.values(params.config?.auth?.profiles ?? {}).some( - (profile) => profile.provider === "openai-codex", + (profile) => profile.provider === "openai", ); const active = search?.enabled !== false && codex?.enabled === true && nativeEligible && hasRequiredAuth; @@ -322,7 +322,7 @@ type WrapProviderStreamFnParams = Parameters< function installFullProviderRuntimeDepsForTest() { extraParamsTesting.setProviderRuntimeDepsForTest({ prepareProviderExtraParams: (params) => { - if (params.provider !== "openai-codex") { + if (params.provider !== "openai") { return undefined; } const transport = params.context.extraParams?.transport; @@ -339,7 +339,7 @@ function installFullProviderRuntimeDepsForTest() { if (params.provider === "openai") { return createTestOpenAIProviderWrapper(params, true); } - if (params.provider === "openai-codex") { + if (params.provider === "openai") { return createTestOpenAIProviderWrapper(params, false); } if (params.provider === "azure-openai" || params.provider === "azure-openai-responses") { @@ -515,15 +515,15 @@ describe("applyExtraParamsToAgent", () => { const agent = { streamFn: (() => ({}) as ReturnType) as StreamFn }; const model = { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">; + } as Model<"openai-chatgpt-responses">; applyExtraParamsToAgent( agent, undefined, - "openai-codex", + "openai", "gpt-5.4", undefined, "high", @@ -543,7 +543,7 @@ describe("applyExtraParamsToAgent", () => { model: | Model<"openai-responses"> | Model<"azure-openai-responses"> - | Model<"openai-codex-responses"> + | Model<"openai-chatgpt-responses"> | Model<"openai-completions"> | Model<"anthropic-messages">; options?: SimpleStreamOptions; @@ -602,7 +602,7 @@ describe("applyExtraParamsToAgent", () => { model: | Model<"openai-completions"> | Model<"openai-responses"> - | Model<"openai-codex-responses"> + | Model<"openai-chatgpt-responses"> | Model<"azure-openai-responses"> | Model<"anthropic-messages">; cfg?: Record; @@ -1314,15 +1314,15 @@ describe("applyExtraParamsToAgent", () => { expect(payload.parallel_tool_calls).toBe(true); }); - it("injects parallel_tool_calls for openai-codex-responses payloads when configured", () => { + it("injects parallel_tool_calls for openai-chatgpt-responses payloads when configured", () => { const payload = runParallelToolCallsPayloadMutationCase({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "gpt-5.4", cfg: { agents: { defaults: { models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { parallelToolCalls: true, }, @@ -1332,11 +1332,11 @@ describe("applyExtraParamsToAgent", () => { }, }, model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", baseUrl: "https://chatgpt.com/backend-api/codex", - } as unknown as Model<"openai-codex-responses">, + } as unknown as Model<"openai-chatgpt-responses">, }); expect(payload.parallel_tool_calls).toBe(true); @@ -1969,7 +1969,7 @@ describe("applyExtraParamsToAgent", () => { agents: { defaults: { models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { transport: "websocket", }, @@ -1979,13 +1979,13 @@ describe("applyExtraParamsToAgent", () => { }, }; - applyExtraParamsToAgent(agent, cfg, "openai-codex", "gpt-5.4"); + applyExtraParamsToAgent(agent, cfg, "openai", "gpt-5.4"); const model = { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">; + } as Model<"openai-chatgpt-responses">; const context: Context = { messages: [] }; void agent.streamFn?.(model, context, {}); @@ -1993,13 +1993,13 @@ describe("applyExtraParamsToAgent", () => { expect(calls[0]?.transport).toBe("websocket"); }); - it("passes configured websocket transport through stream options for openai-codex gpt-5.4", () => { + it("passes configured websocket transport through stream options for openai gpt-5.4", () => { const { calls, agent } = createOptionsCaptureAgent(); const cfg = { agents: { defaults: { models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { transport: "websocket", }, @@ -2009,13 +2009,13 @@ describe("applyExtraParamsToAgent", () => { }, }; - applyExtraParamsToAgent(agent, cfg, "openai-codex", "gpt-5.4"); + applyExtraParamsToAgent(agent, cfg, "openai", "gpt-5.4"); const model = { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">; + } as Model<"openai-chatgpt-responses">; const context: Context = { messages: [] }; void agent.streamFn?.(model, context, {}); @@ -2056,13 +2056,13 @@ describe("applyExtraParamsToAgent", () => { it("defaults Codex transport to auto (WebSocket-first)", () => { const { calls, agent } = createOptionsCaptureAgent(); - applyExtraParamsToAgent(agent, undefined, "openai-codex", "gpt-5.4"); + applyExtraParamsToAgent(agent, undefined, "openai", "gpt-5.4"); const model = { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">; + } as Model<"openai-chatgpt-responses">; const context: Context = { messages: [] }; void agent.streamFn?.(model, context, {}); @@ -2105,13 +2105,13 @@ describe("applyExtraParamsToAgent", () => { it("injects GPT-5 default parallel tool calls for Codex Responses payloads", () => { const payload = runResponsesPayloadMutationCase({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "gpt-5.4", model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, payload: {}, }); @@ -2119,15 +2119,15 @@ describe("applyExtraParamsToAgent", () => { expect(payload.text).toEqual({ verbosity: "low" }); }); - it("injects native Codex web_search for direct openai-codex Responses models", () => { + it("injects native Codex web_search for direct openai Responses models", () => { const payload = runResponsesPayloadMutationCase({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "gpt-5.4", cfg: { auth: { profiles: { - "openai-codex:default": { - provider: "openai-codex", + "openai:default": { + provider: "openai", mode: "oauth", }, }, @@ -2146,10 +2146,10 @@ describe("applyExtraParamsToAgent", () => { }, }, model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, payload: { tools: [{ type: "function", name: "read" }] }, }); @@ -2181,10 +2181,10 @@ describe("applyExtraParamsToAgent", () => { }, }, model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "gateway", id: "gpt-5.4", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, payload: { tools: [{ type: "web_search" }] }, }); @@ -2242,7 +2242,7 @@ describe("applyExtraParamsToAgent", () => { agents: { defaults: { models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { transport: "sse", }, @@ -2252,13 +2252,13 @@ describe("applyExtraParamsToAgent", () => { }, }; - applyExtraParamsToAgent(agent, cfg, "openai-codex", "gpt-5.4"); + applyExtraParamsToAgent(agent, cfg, "openai", "gpt-5.4"); const model = { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">; + } as Model<"openai-chatgpt-responses">; const context: Context = { messages: [] }; void agent.streamFn?.(model, context, {}); @@ -2272,7 +2272,7 @@ describe("applyExtraParamsToAgent", () => { agents: { defaults: { models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { transport: "websocket", }, @@ -2282,13 +2282,13 @@ describe("applyExtraParamsToAgent", () => { }, }; - applyExtraParamsToAgent(agent, cfg, "openai-codex", "gpt-5.4"); + applyExtraParamsToAgent(agent, cfg, "openai", "gpt-5.4"); const model = { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">; + } as Model<"openai-chatgpt-responses">; const context: Context = { messages: [] }; void agent.streamFn?.(model, context, { transport: "sse" }); @@ -2302,7 +2302,7 @@ describe("applyExtraParamsToAgent", () => { agents: { defaults: { models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { transport: "udp", }, @@ -2312,13 +2312,13 @@ describe("applyExtraParamsToAgent", () => { }, }; - applyExtraParamsToAgent(agent, cfg, "openai-codex", "gpt-5.4"); + applyExtraParamsToAgent(agent, cfg, "openai", "gpt-5.4"); const model = { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">; + } as Model<"openai-chatgpt-responses">; const context: Context = { messages: [] }; void agent.streamFn?.(model, context, {}); @@ -2329,7 +2329,7 @@ describe("applyExtraParamsToAgent", () => { it("returns prepared Codex transport defaults for runtime sessions", () => { const effectiveExtraParams = resolvePreparedExtraParams({ cfg: undefined, - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", }); @@ -2524,7 +2524,7 @@ describe("applyExtraParamsToAgent", () => { it("uses prepared transport when session settings did not explicitly set one", () => { const effectiveExtraParams = resolvePreparedExtraParams({ cfg: undefined, - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", }); @@ -2979,13 +2979,13 @@ describe("applyExtraParamsToAgent", () => { it("injects configured text verbosity into Codex Responses payloads", () => { const payload = runResponsesPayloadMutationCase({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "gpt-5.4", cfg: { agents: { defaults: { models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { text_verbosity: "high", }, @@ -2995,11 +2995,11 @@ describe("applyExtraParamsToAgent", () => { }, }, model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", baseUrl: "https://chatgpt.com/backend-api/codex/responses", - } as unknown as Model<"openai-codex-responses">, + } as unknown as Model<"openai-chatgpt-responses">, payload: { store: false, text: { @@ -3081,13 +3081,13 @@ describe("applyExtraParamsToAgent", () => { it("injects configured OpenAI service_tier into Codex Responses payloads", () => { const payload = runResponsesPayloadMutationCase({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "gpt-5.4", cfg: { agents: { defaults: { models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { serviceTier: "priority", }, @@ -3097,11 +3097,11 @@ describe("applyExtraParamsToAgent", () => { }, }, model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", baseUrl: "https://chatgpt.com/backend-api", - } as unknown as Model<"openai-codex-responses">, + } as unknown as Model<"openai-chatgpt-responses">, }); expect(payload.service_tier).toBe("priority"); }); @@ -3663,17 +3663,17 @@ describe("applyExtraParamsToAgent", () => { expect(payload).not.toHaveProperty("service_tier"); }); - it("maps fast mode to priority service_tier for openai-codex responses", () => { + it("maps fast mode to priority service_tier for openai responses", () => { const payload = runResponsesPayloadMutationCase({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "gpt-5.4", extraParamsOverride: { fastMode: true }, model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", baseUrl: "https://chatgpt.com/backend-api", - } as unknown as Model<"openai-codex-responses">, + } as unknown as Model<"openai-chatgpt-responses">, payload: { store: false, }, @@ -4036,31 +4036,31 @@ describe("applyExtraParamsToAgent", () => { it.each([ { - name: "with openai-codex provider config", + name: "with openai provider config", run: () => runResponsesPayloadMutationCase({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "codex-mini-latest", model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "codex-mini-latest", baseUrl: "https://chatgpt.com/backend-api/codex/responses", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, }), }, { name: "without config via provider/model hints", run: () => runResponsesPayloadMutationCase({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "codex-mini-latest", model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "codex-mini-latest", baseUrl: "https://chatgpt.com/backend-api/codex/responses", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, options: {}, }), }, diff --git a/src/agents/embedded-agent-runner.e2e.test.ts b/src/agents/embedded-agent-runner.e2e.test.ts index 0ca9a4829ca2..5fe63aebe698 100644 --- a/src/agents/embedded-agent-runner.e2e.test.ts +++ b/src/agents/embedded-agent-runner.e2e.test.ts @@ -381,7 +381,7 @@ describe("runEmbeddedAgent", () => { }, auth: { order: { - openai: ["openai-codex:work", "openai:backup"], + openai: ["openai:work", "openai:backup"], }, }, }; @@ -410,7 +410,7 @@ describe("runEmbeddedAgent", () => { expect(resolveModelAsyncMock).toHaveBeenNthCalledWith( 1, - "openai-codex", + "openai", "mock-1", agentDir, cfg, @@ -419,7 +419,7 @@ describe("runEmbeddedAgent", () => { expect(resolveModelAsyncMock).toHaveBeenCalledTimes(1); expect( (firstRunEmbeddedAttemptParams() as { model?: { provider?: string } }).model?.provider, - ).toBe("openai-codex"); + ).toBe("openai"); }); it("resolves transport-owned OpenAI Codex runs against the runtime provider first", async () => { @@ -451,7 +451,7 @@ describe("runEmbeddedAgent", () => { }, }; resolveModelAsyncMock.mockImplementation(async (provider: string, modelId: string) => { - if (provider === "openai-codex" && modelId === "gpt-5.5") { + if (provider === "openai" && modelId === "gpt-5.5") { return createResolvedEmbeddedRunnerModel(provider, modelId); } return { @@ -488,7 +488,7 @@ describe("runEmbeddedAgent", () => { expect(resolveModelAsyncMock).toHaveBeenNthCalledWith( 1, - "openai-codex", + "openai", "gpt-5.5", agentDir, cfg, @@ -498,7 +498,7 @@ describe("runEmbeddedAgent", () => { expect(ensureOpenClawModelsJsonMock).not.toHaveBeenCalled(); expect( (firstRunEmbeddedAttemptParams() as { model?: { provider?: string } }).model?.provider, - ).toBe("openai-codex"); + ).toBe("openai"); }); it("backfills a trimmed session key from sessionId when the embedded run omits it", async () => { diff --git a/src/agents/embedded-agent-runner.run-embedded-agent.auth-profile-rotation.e2e.test.ts b/src/agents/embedded-agent-runner.run-embedded-agent.auth-profile-rotation.e2e.test.ts index f5de9c421ba5..003825fe4830 100644 --- a/src/agents/embedded-agent-runner.run-embedded-agent.auth-profile-rotation.e2e.test.ts +++ b/src/agents/embedded-agent-runner.run-embedded-agent.auth-profile-rotation.e2e.test.ts @@ -372,9 +372,9 @@ const writeOpenAiCodexAuthStore = async (agentDir: string) => { const payload = { version: 1, profiles: { - "openai-codex:work": { + "openai:work": { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "sk-codex", }, }, @@ -1224,7 +1224,7 @@ describe("runEmbeddedAgent auth profile rotation", () => { prompt: "hello", provider: "codex-cli", model: "gpt-5.4", - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "user", timeoutMs: 5_000, runId: "run:user-auth-alias", @@ -1235,7 +1235,7 @@ describe("runEmbeddedAgent auth profile rotation", () => { runEmbeddedAttemptMock.mock.calls.at(0)?.[0], "embedded attempt params", ); - expect(attemptParams.authProfileId).toBe("openai-codex:work"); + expect(attemptParams.authProfileId).toBe("openai:work"); expect(attemptParams.authProfileIdSource).toBe("user"); expect(attemptParams.provider).toBe("codex-cli"); }); diff --git a/src/agents/embedded-agent-runner.sanitize-session-history.test.ts b/src/agents/embedded-agent-runner.sanitize-session-history.test.ts index 6fba55397801..7777e8414acd 100644 --- a/src/agents/embedded-agent-runner.sanitize-session-history.test.ts +++ b/src/agents/embedded-agent-runner.sanitize-session-history.test.ts @@ -734,7 +734,7 @@ describe("sanitizeSessionHistory", () => { expect(JSON.stringify(result)).not.toContain("missing tool result"); }); - it("synthesizes Codex-style aborted tool results for openai-codex-responses", async () => { + it("synthesizes Codex-style aborted tool results for openai-chatgpt-responses", async () => { const messages: AgentMessage[] = [ makeAssistantMessage( [ @@ -749,8 +749,8 @@ describe("sanitizeSessionHistory", () => { const result = await sanitizeSessionHistory({ messages, - modelApi: "openai-codex-responses", - provider: "openai-codex", + modelApi: "openai-chatgpt-responses", + provider: "openai", sessionManager: mockSessionManager, sessionId: TEST_SESSION_ID, }); diff --git a/src/agents/embedded-agent-runner/compact.hooks.test.ts b/src/agents/embedded-agent-runner/compact.hooks.test.ts index 9a2381939af4..cc8cc5493717 100644 --- a/src/agents/embedded-agent-runner/compact.hooks.test.ts +++ b/src/agents/embedded-agent-runner/compact.hooks.test.ts @@ -601,7 +601,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { workspaceDir: "/tmp/workspace", provider: "openai", model: "gpt-primary", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", trigger: "overflow", modelFallbacksOverride: ["openai/gpt-fallback"], config: { @@ -627,7 +627,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { ([provider, modelId]) => provider === "openai" && modelId === "gpt-fallback", ); expectRecordFields(mockCallArg(resolveEmbeddedAgentStreamFnMock, 1), { - authProfileId: "openai-codex:default", + authProfileId: "openai:default", }); }); @@ -651,13 +651,12 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { config: { models: { providers: { - openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] }, - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, auth: { order: { - "openai-codex": ["openai-codex:work"], + openai: ["openai:work"], }, }, agents: { defaults: { embeddedHarness: { runtime: "codex" } } }, @@ -693,8 +692,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { config: { models: { providers: { - openai: { models: [{ id: "fake-model", contextWindow: 1_000_000 }] }, - "openai-codex": { models: [{ id: "fake-model", contextWindow: 350_000 }] }, + openai: { models: [{ id: "fake-model", contextWindow: 350_000 }] }, }, }, } as never, @@ -731,11 +729,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { config: { models: { providers: { - openai: { - baseUrl: "https://openai-compatible.example/v1", - models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }], - }, - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, agents: { defaults: { embeddedHarness: { runtime: "codex" } } }, @@ -803,9 +797,6 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { models: { providers: { openai: { - models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }, { id: "gpt-5.4-mini" }], - }, - "openai-codex": { models: [{ id: "gpt-5.5" }, { id: "gpt-5.4-mini", contextWindow: 350_000 }], }, }, @@ -844,17 +835,16 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { workspaceDir: "/tmp/workspace", provider: "openai", model: "gpt-5.5", - authProfileId: "openai-codex:work", + authProfileId: "openai:work", config: { models: { providers: { - openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] }, - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, auth: { order: { - "openai-codex": ["openai-codex:work"], + openai: ["openai:work"], }, }, } as never, @@ -1748,7 +1738,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { }, }, }, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", authProfileId: "openai:p1", }), @@ -1800,7 +1790,8 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { }); }); - it("passes resolved context-engine runtime context to harness compaction", async () => { + it("passes resolved OpenAI runtime context to context-engine compaction", async () => { + resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" }); maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({ ok: true, compacted: true, @@ -1813,7 +1804,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { const result = await compactEmbeddedAgentSession( wrappedCompactionArgs({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", authProfileId: "openai:p1", currentTokenCount: 333, @@ -1821,15 +1812,14 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { ); expect(result.ok).toBe(true); - const harnessArg = mockCallArg(maybeCompactAgentHarnessSessionMock) as Record; - if (harnessArg.contextEngine === undefined) { - throw new Error("Expected compact harness context engine"); - } - expect(harnessArg.contextTokenBudget).toBeTypeOf("number"); - expectRecordFields(harnessArg.contextEngineRuntimeContext, { + expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled(); + const compactArg = mockCallArg(contextEngineCompactMock) as { + runtimeContext?: Record; + }; + expectRecordFields(compactArg.runtimeContext, { sessionKey: TEST_SESSION_KEY, workspaceDir: TEST_WORKSPACE_DIR, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", authProfileId: "openai:p1", currentTokenCount: 333, @@ -1856,7 +1846,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { config: { models: { providers: { - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, }, @@ -1897,7 +1887,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { config: { models: { providers: { - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, }, @@ -1939,7 +1929,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { config: { models: { providers: { - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, }, @@ -1981,7 +1971,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { config: { models: { providers: { - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, }, @@ -2019,8 +2009,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { config: { models: { providers: { - openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] }, - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, }, @@ -2059,11 +2048,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { config: { models: { providers: { - openai: { - baseUrl: "https://openai-compatible.example/v1", - models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }], - }, - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, }, @@ -2120,7 +2105,8 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { expect(contextEngineCompactMock).not.toHaveBeenCalled(); }); - it("falls back to context-engine compaction for Codex native binding failures", async () => { + it("uses context-engine compaction when no Codex native binding is selected", async () => { + resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" }); maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({ ok: false, compacted: false, @@ -2130,7 +2116,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { const result = await compactEmbeddedAgentSession( wrappedCompactionArgs({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", agentHarnessId: "codex", currentTokenCount: 333, @@ -2140,7 +2126,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => { expect(result.ok).toBe(true); expect(result.compacted).toBe(true); expect(result.result?.summary).toBe("engine-summary"); - expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1); + expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled(); expect(contextEngineCompactMock).toHaveBeenCalledTimes(1); }); diff --git a/src/agents/embedded-agent-runner/compact.queued.ts b/src/agents/embedded-agent-runner/compact.queued.ts index 0e6387b9716a..025f5b8cffc3 100644 --- a/src/agents/embedded-agent-runner/compact.queued.ts +++ b/src/agents/embedded-agent-runner/compact.queued.ts @@ -28,7 +28,7 @@ import { maybeCompactAgentHarnessSession, resolveAgentHarnessPolicy, } from "../harness/selection.js"; -import { isOpenAICodexProvider, isOpenAIProvider } from "../openai-codex-routing.js"; +import { isOpenAIProvider } from "../openai-routing.js"; import { ensureRuntimePluginsLoaded } from "../runtime-plugins.js"; import { DEFERRED_CONTEXT_ENGINE_COMPACTION_REASON } from "./compact-reasons.js"; import type { CompactEmbeddedAgentSessionParams } from "./compact.types.js"; @@ -516,9 +516,6 @@ function shouldAttemptNativeHarnessCompaction(params: { contextProvider?: string; selectedHarnessRuntime?: string | null; }): boolean { - if (isOpenAICodexProvider(params.provider)) { - return true; - } const selectedRuntime = normalizeOptionalAgentRuntimeId(params.selectedHarnessRuntime); if (!selectedRuntime || selectedRuntime === "auto" || selectedRuntime === "openclaw") { return false; diff --git a/src/agents/embedded-agent-runner/compact.ts b/src/agents/embedded-agent-runner/compact.ts index 9ce08670f1a3..77d26e8a4f1c 100644 --- a/src/agents/embedded-agent-runner/compact.ts +++ b/src/agents/embedded-agent-runner/compact.ts @@ -1084,7 +1084,7 @@ async function compactEmbeddedAgentSessionDirectOnce( missingToolResultText: model.api === "openai-responses" || model.api === "azure-openai-responses" || - model.api === "openai-codex-responses" + model.api === "openai-chatgpt-responses" ? "aborted" : undefined, allowedToolNames, @@ -1256,7 +1256,7 @@ async function compactEmbeddedAgentSessionDirectOnce( erroredAssistantResultPolicy: "drop", ...(model.api === "openai-responses" || model.api === "azure-openai-responses" || - model.api === "openai-codex-responses" + model.api === "openai-chatgpt-responses" ? { missingToolResultText: "aborted" } : {}), }) diff --git a/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts b/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts index e51ddd85ea35..58a889ffa5df 100644 --- a/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts +++ b/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts @@ -28,7 +28,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { config: {} as unknown as OpenClawConfig, senderIsOwner: true, senderId: "user-123", - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", thinkLevel: "off", reasoningLevel: "on", @@ -48,7 +48,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { expect(result.agentDir).toBe("/tmp/agent"); expect(result.senderIsOwner).toBe(true); expect(result.senderId).toBe("user-123"); - expect(result.provider).toBe("openai-codex"); + expect(result.provider).toBe("openai"); expect(result.model).toBe("gpt-5.4"); }); @@ -200,10 +200,10 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { config: { agents: { defaults: { compaction: { model: "anthropic/" } } }, } as unknown as OpenClawConfig, - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", authProfileId: "openai:p1", - defaultProvider: "openai-codex", + defaultProvider: "openai", defaultModel: "gpt-5.4", }), ).toEqual({ @@ -217,7 +217,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { const result = resolveEmbeddedCompactionTarget({ provider: "openai", modelId: "gpt-5.4", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", defaultProvider: "openai", defaultModel: "gpt-5.4", }); @@ -225,13 +225,13 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { expect(result.runtimeProvider).toBeUndefined(); expect(result.contextProvider).toBeUndefined(); expect(result.model).toBe("gpt-5.4"); - expect(result.authProfileId).toBe("openai-codex:default"); + expect(result.authProfileId).toBe("openai:default"); }); it("keeps openai auth order with Codex profile on canonical OpenAI", () => { const result = resolveEmbeddedCompactionTarget({ config: { - auth: { order: { openai: ["openai-codex:default"] } }, + auth: { order: { openai: ["openai:default"] } }, } as unknown as OpenClawConfig, provider: "openai", modelId: "gpt-5.5", @@ -307,11 +307,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { config: { models: { providers: { - openai: { - baseUrl: "https://openai-compatible.example/v1", - models: [{ id: "gpt-5.5" }], - }, - "openai-codex": { models: [{ id: "gpt-5.5" }] }, + openai: { models: [{ id: "gpt-5.5" }] }, }, }, } as unknown as OpenClawConfig, @@ -335,7 +331,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { } as unknown as OpenClawConfig, provider: "openai", modelId: "gpt-5.5", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", defaultProvider: "openai", defaultModel: "gpt-5.5", }); @@ -343,7 +339,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { expect(result.runtimeProvider).toBeUndefined(); expect(result.contextProvider).toBeUndefined(); expect(result.model).toBe("gpt-5.4"); - expect(result.authProfileId).toBe("openai-codex:default"); + expect(result.authProfileId).toBe("openai:default"); }); it("keeps openai compaction overrides with legacy Codex auth on OpenAI", () => { @@ -353,7 +349,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { } as unknown as OpenClawConfig, provider: "openai", modelId: "gpt-5.5", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", defaultProvider: "openai", defaultModel: "gpt-5.5", }); @@ -361,7 +357,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { expect(result.runtimeProvider).toBeUndefined(); expect(result.contextProvider).toBeUndefined(); expect(result.model).toBe("gpt-5.4"); - expect(result.authProfileId).toBe("openai-codex:default"); + expect(result.authProfileId).toBe("openai:default"); }); it("keeps OpenAI compaction model overrides on canonical OpenAI with Codex runtime", () => { @@ -370,7 +366,6 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { models: { providers: { openai: { models: [{ id: "gpt-5.5" }, { id: "gpt-5.4-mini" }] }, - "openai-codex": { models: [{ id: "gpt-5.5" }, { id: "gpt-5.4-mini" }] }, }, }, agents: { defaults: { compaction: { model: "openai/gpt-5.4-mini" } } }, diff --git a/src/agents/embedded-agent-runner/compaction-runtime-context.ts b/src/agents/embedded-agent-runner/compaction-runtime-context.ts index db43b9e85911..bbc0a9d5b01e 100644 --- a/src/agents/embedded-agent-runner/compaction-runtime-context.ts +++ b/src/agents/embedded-agent-runner/compaction-runtime-context.ts @@ -11,7 +11,7 @@ import type { ExecElevatedDefaults } from "../bash-tools.js"; import { openAIProviderUsesCodexRuntimeByDefault, resolveSelectedOpenAIRuntimeProvider, -} from "../openai-codex-routing.js"; +} from "../openai-routing.js"; export type EmbeddedCompactionRuntimeContext = { sessionKey?: string; diff --git a/src/agents/embedded-agent-runner/extra-params.ts b/src/agents/embedded-agent-runner/extra-params.ts index ed754b69ec5e..0a13316e275d 100644 --- a/src/agents/embedded-agent-runner/extra-params.ts +++ b/src/agents/embedded-agent-runner/extra-params.ts @@ -369,7 +369,7 @@ function shouldApplyDefaultOpenAIGptRuntimeParams(params: { provider: string; modelId: string; }): boolean { - if (params.provider !== "openai" && params.provider !== "openai-codex") { + if (params.provider !== "openai") { return false; } return /^gpt-5(?:[.-]|$)/i.test(params.modelId); diff --git a/src/agents/embedded-agent-runner/model.forward-compat.errors-and-overrides.test.ts b/src/agents/embedded-agent-runner/model.forward-compat.errors-and-overrides.test.ts index 3d0c5515d185..218631592c1f 100644 --- a/src/agents/embedded-agent-runner/model.forward-compat.errors-and-overrides.test.ts +++ b/src/agents/embedded-agent-runner/model.forward-compat.errors-and-overrides.test.ts @@ -20,15 +20,11 @@ vi.mock("../../plugins/provider-runtime.js", async () => { vi.mock("../model-suppression.js", () => ({ shouldSuppressBuiltInModel: ({ provider, id }: { provider?: string; id?: string }) => - (provider === "openai" || - provider === "azure-openai-responses" || - provider === "openai-codex") && + (provider === "openai" || provider === "azure-openai-responses") && id?.trim().toLowerCase() === "gpt-5.3-codex-spark", buildSuppressedBuiltInModelError: ({ provider, id }: { provider?: string; id?: string }) => { if ( - (provider !== "openai" && - provider !== "azure-openai-responses" && - provider !== "openai-codex") || + (provider !== "openai" && provider !== "azure-openai-responses") || id?.trim().toLowerCase() !== "gpt-5.3-codex-spark" ) { return undefined; @@ -63,7 +59,7 @@ beforeEach(() => { function createRuntimeHooks() { return createProviderRuntimeTestMock({ - handledDynamicProviders: ["google-antigravity", "zai", "openai-codex"], + handledDynamicProviders: ["google-antigravity", "zai", "openai"], }); } @@ -131,10 +127,10 @@ describe("resolveModel forward-compat errors and overrides", () => { ); }); - it("keeps unknown-model errors for non-gpt-5 openai-codex ids", () => { + it("keeps unknown-model errors for non-gpt-5 openai ids", () => { expectUnknownModelErrorResult( - resolveModelForTest("openai-codex", "gpt-4.1-mini", "/tmp/agent"), - "openai-codex", + resolveModelForTest("openai", "gpt-4.1-mini", "/tmp/agent"), + "openai", "gpt-4.1-mini", ); }); @@ -182,11 +178,12 @@ describe("resolveModel forward-compat errors and overrides", () => { ); }); - it("uses codex fallback even when openai-codex provider is configured", () => { + it("uses codex fallback even when openai provider is configured", () => { const cfg: OpenClawConfig = { models: { providers: { - "openai-codex": { + openai: { + api: "openai-chatgpt-responses", baseUrl: "https://custom.example.com", }, }, @@ -194,11 +191,11 @@ describe("resolveModel forward-compat errors and overrides", () => { } as unknown as OpenClawConfig; expectResolvedForwardCompatFallbackResult({ - result: resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent", cfg), + result: resolveModelForTest("openai", "gpt-5.4", "/tmp/agent", cfg), expectedModel: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", id: "gpt-5.4", - provider: "openai-codex", + provider: "openai", }, }); }); @@ -209,7 +206,7 @@ describe("resolveModel forward-compat errors and overrides", () => { const cfg: OpenClawConfig = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://custom.example.com", headers: { "X-Custom-Auth": "token-123" }, models: [{ id: "gpt-5.4" }], @@ -218,24 +215,24 @@ describe("resolveModel forward-compat errors and overrides", () => { }, } as unknown as OpenClawConfig; - const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent", cfg); + const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent", cfg); expect(result.error).toBeUndefined(); - expect(result.model?.api).toBe("openai-codex-responses"); + expect(result.model?.api).toBe("openai-chatgpt-responses"); expect(result.model?.baseUrl).toBe("https://custom.example.com"); expect(result.model?.id).toBe("gpt-5.4"); - expect(result.model?.provider).toBe("openai-codex"); + expect(result.model?.provider).toBe("openai"); expect((result.model as unknown as { headers?: Record }).headers).toEqual({ "X-Custom-Auth": "token-123", }); }); - it("normalizes openai-codex gpt-5.4 overrides away from /v1/responses", () => { + it("keeps openai gpt-5.4 responses overrides on the OpenAI API transport", () => { mockOpenAICodexTemplateModel(discoverModels); const cfg: OpenClawConfig = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://api.openai.com/v1", api: "openai-responses", }, @@ -244,23 +241,23 @@ describe("resolveModel forward-compat errors and overrides", () => { } as unknown as OpenClawConfig; expectResolvedForwardCompatFallbackResult({ - result: resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent", cfg), + result: resolveModelForTest("openai", "gpt-5.4", "/tmp/agent", cfg), expectedModel: { - api: "openai-codex-responses", - baseUrl: "https://chatgpt.com/backend-api", + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", id: "gpt-5.4", - provider: "openai-codex", + provider: "openai", }, }); }); - it("normalizes openai-codex gpt-5.4 back to codex transport", () => { + it("normalizes openai gpt-5.4 completions overrides to the OpenAI API transport", () => { mockOpenAICodexTemplateModel(discoverModels); const cfg: OpenClawConfig = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://api.openai.com/v1", api: "openai-completions", }, @@ -269,12 +266,12 @@ describe("resolveModel forward-compat errors and overrides", () => { } as unknown as OpenClawConfig; expectResolvedForwardCompatFallbackResult({ - result: resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent", cfg), + result: resolveModelForTest("openai", "gpt-5.4", "/tmp/agent", cfg), expectedModel: { - api: "openai-codex-responses", - baseUrl: "https://chatgpt.com/backend-api", + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", id: "gpt-5.4", - provider: "openai-codex", + provider: "openai", }, }); }); diff --git a/src/agents/embedded-agent-runner/model.forward-compat.test.ts b/src/agents/embedded-agent-runner/model.forward-compat.test.ts index 97d172f4fd63..0ca220f2e47e 100644 --- a/src/agents/embedded-agent-runner/model.forward-compat.test.ts +++ b/src/agents/embedded-agent-runner/model.forward-compat.test.ts @@ -79,7 +79,7 @@ const ZAI_GLM5_CASE = { function createRuntimeHooks() { return createProviderRuntimeTestMock({ - handledDynamicProviders: ["anthropic", "claude-cli", "zai", "openai-codex"], + handledDynamicProviders: ["anthropic", "claude-cli", "zai", "openai"], }); } diff --git a/src/agents/embedded-agent-runner/model.inline-provider.ts b/src/agents/embedded-agent-runner/model.inline-provider.ts index a837010896a5..2ad2e5e2c59d 100644 --- a/src/agents/embedded-agent-runner/model.inline-provider.ts +++ b/src/agents/embedded-agent-runner/model.inline-provider.ts @@ -42,7 +42,7 @@ export function normalizeResolvedTransportApi( case "google-generative-ai": case "google-vertex": case "ollama": - case "openai-codex-responses": + case "openai-chatgpt-responses": case "openai-completions": case "openai-responses": case "azure-openai-responses": diff --git a/src/agents/embedded-agent-runner/model.provider-runtime.test-support.ts b/src/agents/embedded-agent-runner/model.provider-runtime.test-support.ts index 04df79023f50..58bfe25f54a1 100644 --- a/src/agents/embedded-agent-runner/model.provider-runtime.test-support.ts +++ b/src/agents/embedded-agent-runner/model.provider-runtime.test-support.ts @@ -37,6 +37,7 @@ type DynamicModelContext = { provider: string; modelId: string; modelRegistry: ModelRegistryLike; + providerConfig?: { api?: string | null; baseUrl?: string }; }; type ResolvedModelLike = Record; @@ -79,6 +80,13 @@ function cloneTemplate( } as ResolvedModelLike; } +function isOpenAIChatGptModelTemplate(model: ResolvedModelLike | null | undefined): boolean { + return ( + model?.api === "openai-chatgpt-responses" || + isNativeOpenAICodexBaseUrl(typeof model?.baseUrl === "string" ? model.baseUrl : undefined) + ); +} + function isNativeOpenAICodexBaseUrl(baseUrl?: string): boolean { return baseUrl === OPENAI_CODEX_BASE_URL || baseUrl === OPENAI_CODEX_LEGACY_BASE_URL; } @@ -119,18 +127,17 @@ function normalizeDynamicModel(params: { provider: string; model: ResolvedModelL } return undefined; } - if (params.provider !== "openai-codex") { + if (params.provider !== "openai") { return undefined; } const baseUrl = typeof params.model.baseUrl === "string" ? params.model.baseUrl : undefined; const useCodexTransport = - !baseUrl || baseUrl === OPENAI_BASE_URL || isNativeOpenAICodexBaseUrl(baseUrl); - const nextApi = - useCodexTransport && (!params.model.api || params.model.api === "openai-responses") - ? "openai-codex-responses" - : params.model.api; + (params.model.api === "openai-chatgpt-responses" && + (!baseUrl || baseUrl === OPENAI_BASE_URL || isNativeOpenAICodexBaseUrl(baseUrl))) || + isNativeOpenAICodexBaseUrl(baseUrl); + const nextApi = useCodexTransport ? "openai-chatgpt-responses" : params.model.api; const nextBaseUrl = - nextApi === "openai-codex-responses" && useCodexTransport ? OPENAI_CODEX_BASE_URL : baseUrl; + nextApi === "openai-chatgpt-responses" && useCodexTransport ? OPENAI_CODEX_BASE_URL : baseUrl; if (nextApi !== params.model.api || nextBaseUrl !== baseUrl) { return { ...params.model, api: nextApi, baseUrl: nextBaseUrl }; } @@ -150,13 +157,12 @@ function normalizeTransport(params: { (params.context.baseUrl === XAI_BASE_URL || (params.provider === "xai" && !params.context.baseUrl)); const isNativeOpenAICodexTransport = - params.provider === "openai-codex" && - ((!params.context.api && - (!params.context.baseUrl || isNativeOpenAICodexBaseUrl(params.context.baseUrl))) || - (params.context.api === "openai-responses" && - (!params.context.baseUrl || - params.context.baseUrl === OPENAI_BASE_URL || - isNativeOpenAICodexBaseUrl(params.context.baseUrl)))); + params.provider === "openai" && + ((params.context.api === "openai-chatgpt-responses" && + (!params.context.baseUrl || + params.context.baseUrl === OPENAI_BASE_URL || + isNativeOpenAICodexBaseUrl(params.context.baseUrl))) || + isNativeOpenAICodexBaseUrl(params.context.baseUrl)); if ( params.context.api === "google-generative-ai" && params.context.baseUrl === "https://generativelanguage.googleapis.com" @@ -180,7 +186,7 @@ function normalizeTransport(params: { } if (isNativeOpenAICodexTransport) { return { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: OPENAI_CODEX_BASE_URL, }; } @@ -258,13 +264,17 @@ function buildDynamicModel( maxTokens: DEFAULT_MAX_TOKENS, }; } - case "openai-codex": { + case "openai": { const isLegacyGpt54Alias = lower === "gpt-5.4-codex"; - if (lower === "gpt-5.5") { - const model = params.modelRegistry.find( - "openai-codex", - modelId, - ) as ResolvedModelLike | null; + const exactModel = params.modelRegistry.find("openai", modelId) as ResolvedModelLike | null; + const providerConfigSelectsChatGpt = + params.providerConfig?.api === "openai-chatgpt-responses" || + isNativeOpenAICodexBaseUrl(params.providerConfig?.baseUrl); + if ( + lower === "gpt-5.5" && + (providerConfigSelectsChatGpt || isOpenAIChatGptModelTemplate(exactModel)) + ) { + const model = exactModel; if (model) { const modelContextTokens = model.contextTokens; const modelContextWindow = model.contextWindow; @@ -281,8 +291,8 @@ function buildDynamicModel( undefined, modelId, { - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", baseUrl: OPENAI_CODEX_BASE_URL, reasoning: true, input: ["text", "image"], @@ -294,111 +304,121 @@ function buildDynamicModel( {}, ); } - const template = + const codexTemplate = lower === "gpt-5.5-pro" - ? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.4-pro", "gpt-5.3-codex"]) + ? findTemplate(params, "openai", ["gpt-5.4", "gpt-5.4-pro", "gpt-5.3-codex"]) : lower === "gpt-5.4" || isLegacyGpt54Alias || lower === "gpt-5.4-pro" || lower === "gpt-5.4-mini" - ? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex"]) + ? findTemplate(params, "openai", ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex"]) : lower === "gpt-5.3-codex-spark" - ? findTemplate(params, "openai-codex", ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex"]) - : findTemplate(params, "openai-codex", ["gpt-5.4"]); - const fallback = { - provider: "openai-codex", - api: "openai-codex-responses", - baseUrl: OPENAI_CODEX_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: OPENROUTER_FALLBACK_COST, - contextWindow: DEFAULT_CONTEXT_WINDOW, - maxTokens: DEFAULT_CONTEXT_WINDOW, - }; - if (lower === "gpt-5.5-pro") { - return cloneTemplate( - template, - modelId, - { - provider: "openai-codex", - api: "openai-codex-responses", - baseUrl: OPENAI_CODEX_BASE_URL, - cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 1_000_000, - contextTokens: 272_000, - maxTokens: 128_000, - }, - fallback, - ); + ? findTemplate(params, "openai", ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2-codex"]) + : findTemplate(params, "openai", ["gpt-5.4"]); + if ( + isLegacyGpt54Alias || + lower.includes("-codex") || + providerConfigSelectsChatGpt || + isOpenAIChatGptModelTemplate(codexTemplate) + ) { + const templateBaseUrl = + typeof codexTemplate?.baseUrl === "string" ? codexTemplate.baseUrl : undefined; + const chatGptBaseUrl = isNativeOpenAICodexBaseUrl(templateBaseUrl) + ? OPENAI_CODEX_BASE_URL + : (templateBaseUrl ?? OPENAI_CODEX_BASE_URL); + const fallback = { + provider: "openai", + api: "openai-chatgpt-responses", + baseUrl: chatGptBaseUrl, + reasoning: true, + input: ["text", "image"], + cost: OPENROUTER_FALLBACK_COST, + contextWindow: DEFAULT_CONTEXT_WINDOW, + maxTokens: DEFAULT_CONTEXT_WINDOW, + }; + if (lower === "gpt-5.5-pro") { + return cloneTemplate( + codexTemplate, + modelId, + { + provider: "openai", + api: "openai-chatgpt-responses", + baseUrl: chatGptBaseUrl, + cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1_000_000, + contextTokens: 272_000, + maxTokens: 128_000, + }, + fallback, + ); + } + if (lower === "gpt-5.4" || isLegacyGpt54Alias) { + return cloneTemplate( + codexTemplate, + "gpt-5.4", + { + provider: "openai", + api: "openai-chatgpt-responses", + baseUrl: chatGptBaseUrl, + cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }, + contextWindow: 1_050_000, + contextTokens: 272_000, + maxTokens: 128_000, + }, + fallback, + ); + } + if (lower === "gpt-5.4-pro") { + return cloneTemplate( + codexTemplate, + modelId, + { + provider: "openai", + api: "openai-chatgpt-responses", + baseUrl: chatGptBaseUrl, + cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1_050_000, + contextTokens: 272_000, + maxTokens: 128_000, + }, + fallback, + ); + } + if (lower === "gpt-5.4-mini") { + return cloneTemplate( + codexTemplate, + modelId, + { + provider: "openai", + api: "openai-chatgpt-responses", + baseUrl: chatGptBaseUrl, + cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }, + contextWindow: 400_000, + contextTokens: 272_000, + maxTokens: 128_000, + }, + fallback, + ); + } + if (lower === "gpt-5.3-codex-spark") { + return cloneTemplate( + codexTemplate, + modelId, + { + provider: "openai", + api: "openai-chatgpt-responses", + baseUrl: chatGptBaseUrl, + reasoning: true, + input: ["text"], + cost: OPENROUTER_FALLBACK_COST, + contextWindow: 128_000, + maxTokens: 128_000, + }, + fallback, + ); + } + return undefined; } - if (lower === "gpt-5.4" || isLegacyGpt54Alias) { - return cloneTemplate( - template, - "gpt-5.4", - { - provider: "openai-codex", - api: "openai-codex-responses", - baseUrl: OPENAI_CODEX_BASE_URL, - cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }, - contextWindow: 1_050_000, - contextTokens: 272_000, - maxTokens: 128_000, - }, - fallback, - ); - } - if (lower === "gpt-5.4-pro") { - return cloneTemplate( - template, - modelId, - { - provider: "openai-codex", - api: "openai-codex-responses", - baseUrl: OPENAI_CODEX_BASE_URL, - cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 1_050_000, - contextTokens: 272_000, - maxTokens: 128_000, - }, - fallback, - ); - } - if (lower === "gpt-5.4-mini") { - return cloneTemplate( - template, - modelId, - { - provider: "openai-codex", - api: "openai-codex-responses", - baseUrl: OPENAI_CODEX_BASE_URL, - cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }, - contextWindow: 400_000, - contextTokens: 272_000, - maxTokens: 128_000, - }, - fallback, - ); - } - if (lower === "gpt-5.3-codex-spark") { - return cloneTemplate( - template, - modelId, - { - provider: "openai-codex", - api: "openai-codex-responses", - baseUrl: OPENAI_CODEX_BASE_URL, - reasoning: true, - input: ["text"], - cost: OPENROUTER_FALLBACK_COST, - contextWindow: 128_000, - maxTokens: 128_000, - }, - fallback, - ); - } - return undefined; - } - case "openai": { const templateIds = lower === "gpt-5.5" ? ["gpt-5.5", "gpt-5.4", "gpt-5.4-pro"] @@ -415,6 +435,10 @@ function buildDynamicModel( return undefined; } const template = findTemplate(params, "openai", templateIds); + const preserveTemplateTransport = + template?.api === "openai-completions" && + typeof template.baseUrl === "string" && + template.baseUrl !== OPENAI_BASE_URL; const patch = lower === "gpt-5.5" ? { @@ -439,7 +463,7 @@ function buildDynamicModel( reasoning: true, input: ["text", "image"], cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }, - contextWindow: 272_000, + contextWindow: 1_050_000, maxTokens: 128_000, } : lower === "gpt-5.4-pro" @@ -474,16 +498,24 @@ function buildDynamicModel( contextWindow: 400_000, maxTokens: 128_000, }; - return cloneTemplate(template, modelId, patch, { - provider: "openai", - api: "openai-responses", - baseUrl: OPENAI_BASE_URL, - reasoning: true, - input: ["text", "image"], - cost: OPENROUTER_FALLBACK_COST, - contextWindow: patch.contextWindow ?? DEFAULT_CONTEXT_WINDOW, - maxTokens: patch.maxTokens ?? DEFAULT_CONTEXT_WINDOW, - }); + return cloneTemplate( + template, + modelId, + { + ...patch, + ...(preserveTemplateTransport ? { api: template.api, baseUrl: template.baseUrl } : {}), + }, + { + provider: "openai", + api: "openai-responses", + baseUrl: OPENAI_BASE_URL, + reasoning: true, + input: ["text", "image"], + cost: OPENROUTER_FALLBACK_COST, + contextWindow: patch.contextWindow ?? DEFAULT_CONTEXT_WINDOW, + maxTokens: patch.maxTokens ?? DEFAULT_CONTEXT_WINDOW, + }, + ); } case "anthropic": case "claude-cli": { @@ -578,7 +610,6 @@ export function createProviderRuntimeTestMock(options: ProviderRuntimeTestMockOp options.handledDynamicProviders ?? [ "openrouter", "github-copilot", - "openai-codex", "openai", "xai", "anthropic", @@ -631,7 +662,11 @@ export function createProviderRuntimeTestMock(options: ProviderRuntimeTestMockOp : undefined, runProviderDynamicModel: (params: { provider: string; - context: { modelId: string; modelRegistry: ModelRegistryLike }; + context: { + modelId: string; + modelRegistry: ModelRegistryLike; + providerConfig?: { api?: string | null; baseUrl?: string }; + }; }) => handledDynamicProviders.has(params.provider) ? buildDynamicModel( @@ -639,6 +674,7 @@ export function createProviderRuntimeTestMock(options: ProviderRuntimeTestMockOp provider: params.provider, modelId: params.context.modelId, modelRegistry: params.context.modelRegistry, + providerConfig: params.context.providerConfig, }, { getOpenRouterModelCapabilities, @@ -650,7 +686,7 @@ export function createProviderRuntimeTestMock(options: ProviderRuntimeTestMockOp provider: string; context: { modelId: string }; }) => - params.provider === "openai-codex" && + params.provider === "openai" && ["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro"].includes( params.context.modelId.trim().toLowerCase(), ), diff --git a/src/agents/embedded-agent-runner/model.startup-retry.test.ts b/src/agents/embedded-agent-runner/model.startup-retry.test.ts index 8cfe8d87a952..7ad8a4ff1214 100644 --- a/src/agents/embedded-agent-runner/model.startup-retry.test.ts +++ b/src/agents/embedded-agent-runner/model.startup-retry.test.ts @@ -14,8 +14,8 @@ const runProviderDynamicModelMock = vi.fn<(params: unknown) => unknown>(() => ? { id: "gpt-5.4", name: "gpt-5.4", - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text"], @@ -67,7 +67,7 @@ describe("resolveModelAsync startup retry", () => { const { resolveModelAsync } = await import("./model.js"); const result = await resolveModelAsync( - "openai-codex", + "openai", "gpt-5.4", "/tmp/agent", {}, @@ -78,9 +78,9 @@ describe("resolveModelAsync startup retry", () => { ); expect(result.error).toBeUndefined(); - expect(result.model?.provider).toBe("openai-codex"); + expect(result.model?.provider).toBe("openai"); expect(result.model?.id).toBe("gpt-5.4"); - expect(result.model?.api).toBe("openai-codex-responses"); + expect(result.model?.api).toBe("openai-chatgpt-responses"); expect(prepareProviderDynamicModelMock).toHaveBeenCalledTimes(2); expect(runProviderDynamicModelMock).toHaveBeenCalledTimes(2); }); @@ -88,16 +88,10 @@ describe("resolveModelAsync startup retry", () => { it("does not retry during steady-state misses", async () => { const { resolveModelAsync } = await import("./model.js"); - const result = await resolveModelAsync( - "openai-codex", - "gpt-5.4", - "/tmp/agent", - {}, - { runtimeHooks }, - ); + const result = await resolveModelAsync("openai", "gpt-5.4", "/tmp/agent", {}, { runtimeHooks }); expect(result.model).toBeUndefined(); - expect(result.error).toBe("Unknown model: openai-codex/gpt-5.4"); + expect(result.error).toBe("Unknown model: openai/gpt-5.4"); expect(prepareProviderDynamicModelMock).toHaveBeenCalledTimes(1); expect(runProviderDynamicModelMock).toHaveBeenCalledTimes(1); }); diff --git a/src/agents/embedded-agent-runner/model.test-harness.ts b/src/agents/embedded-agent-runner/model.test-harness.ts index d8eb84af1bf4..67fe0971b6ca 100644 --- a/src/agents/embedded-agent-runner/model.test-harness.ts +++ b/src/agents/embedded-agent-runner/model.test-harness.ts @@ -16,8 +16,8 @@ export const makeModel = (id: string): ModelDefinitionConfig => ({ export const OPENAI_CODEX_TEMPLATE_MODEL = { id: "gpt-5.3-codex", name: "GPT-5.3 Codex", - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text", "image"] as const, @@ -43,7 +43,7 @@ function mockTemplateModel( export function mockOpenAICodexTemplateModel(discoverModelsMock: DiscoverModelsMock): void { mockTemplateModel( discoverModelsMock, - "openai-codex", + "openai", OPENAI_CODEX_TEMPLATE_MODEL.id, OPENAI_CODEX_TEMPLATE_MODEL, ); @@ -62,9 +62,9 @@ export function buildOpenAICodexForwardCompatExpectation( const isGpt54Mini = id === "gpt-5.4-mini"; const isSpark = id === "gpt-5.3-codex-spark"; return { - provider: "openai-codex", + provider: "openai", id, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: isSpark ? ["text"] : ["text", "image"], diff --git a/src/agents/embedded-agent-runner/model.test.ts b/src/agents/embedded-agent-runner/model.test.ts index 42fe032817e1..16cc4443c071 100644 --- a/src/agents/embedded-agent-runner/model.test.ts +++ b/src/agents/embedded-agent-runner/model.test.ts @@ -70,9 +70,7 @@ vi.mock("../model-suppression.js", () => { config?: unknown; }) => { if ( - (provider === "openai" || - provider === "azure-openai-responses" || - provider === "openai-codex") && + (provider === "openai" || provider === "azure-openai-responses" || provider === "openai") && id?.trim().toLowerCase() === "gpt-5.3-codex-spark" ) { return true; @@ -85,9 +83,7 @@ vi.mock("../model-suppression.js", () => { }, shouldUnconditionallySuppress: ({ provider, id }: { provider?: string; id?: string }) => { if ( - (provider === "openai" || - provider === "azure-openai-responses" || - provider === "openai-codex") && + (provider === "openai" || provider === "azure-openai-responses" || provider === "openai") && id?.trim().toLowerCase() === "gpt-5.3-codex-spark" ) { return true; @@ -111,9 +107,7 @@ vi.mock("../model-suppression.js", () => { return "Unknown model: qwen/qwen3.6-plus. qwen3.6-plus is not supported on the Qwen Coding Plan endpoint; use a Standard pay-as-you-go Qwen endpoint or choose qwen/qwen3.5-plus."; } if ( - (provider === "openai" || - provider === "azure-openai-responses" || - provider === "openai-codex") && + (provider === "openai" || provider === "azure-openai-responses" || provider === "openai") && id?.trim().toLowerCase() === "gpt-5.3-codex-spark" ) { return `Unknown model: ${provider}/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.`; @@ -201,7 +195,7 @@ function createRuntimeHooks() { handledDynamicProviders: [ "openrouter", "github-copilot", - "openai-codex", + "openai", "openai", "anthropic", "zai", @@ -1118,17 +1112,17 @@ describe("resolveModel", () => { ); }); - it("#74451: resolves explicitly configured openai-codex/gpt-5.4-mini inline entries", () => { + it("#74451: resolves explicitly configured openai/gpt-5.4-mini inline entries", () => { const cfg = { models: { providers: { - "openai-codex": { - api: "openai-codex-responses", + openai: { + api: "openai-chatgpt-responses", models: [ { id: "gpt-5.4-mini", name: "GPT-5.4 mini", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", contextWindow: 400_000, maxTokens: 128_000, }, @@ -1138,12 +1132,12 @@ describe("resolveModel", () => { }, } as unknown as OpenClawConfig; - const result = resolveModelForTest("openai-codex", "gpt-5.4-mini", "/tmp/agent", cfg); + const result = resolveModelForTest("openai", "gpt-5.4-mini", "/tmp/agent", cfg); expectRecordFields(expectResolvedModel(result), { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4-mini", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", contextWindow: 400_000, maxTokens: 128_000, }); @@ -2449,19 +2443,19 @@ describe("resolveModel", () => { }); }); - it("builds an openai-codex fallback for gpt-5.4", () => { + it("builds an openai fallback for gpt-5.4", () => { mockOpenAICodexTemplateModel(discoverModels); - const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, buildOpenAICodexForwardCompatExpectation("gpt-5.4")); }); - it("upgrades stale exact openai-codex gpt-5.4 registry metadata via forward-compat", () => { + it("upgrades stale exact openai gpt-5.4 registry metadata via forward-compat", () => { vi.mocked(discoverModels).mockReturnValue({ find: vi.fn((provider: string, modelId: string) => { - if (provider !== "openai-codex") { + if (provider !== "openai") { return null; } if (modelId === "gpt-5.4") { @@ -2483,21 +2477,21 @@ describe("resolveModel", () => { }), } as unknown as ReturnType); - const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", contextWindow: 1_050_000, maxTokens: 128000, }); }); - it("accepts available exact openai-codex gpt-5.3-codex registry metadata", () => { + it("accepts available exact openai gpt-5.3-codex registry metadata", () => { vi.mocked(discoverModels).mockReturnValue({ find: vi.fn((provider: string, modelId: string) => { - if (provider !== "openai-codex") { + if (provider !== "openai") { return null; } if (modelId === "gpt-5.3-codex") { @@ -2512,20 +2506,20 @@ describe("resolveModel", () => { }), } as unknown as ReturnType); - const result = resolveModelForTest("openai-codex", "gpt-5.3-codex", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.3-codex", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.3-codex", contextWindow: 272000, }); }); - it("canonicalizes the legacy openai-codex gpt-5.4-codex alias at runtime", () => { + it("canonicalizes the legacy openai gpt-5.4-codex alias at runtime", () => { mockOpenAICodexTemplateModel(discoverModels); - const result = resolveModelForTest("openai-codex", "gpt-5.4-codex", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.4-codex", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, buildOpenAICodexForwardCompatExpectation("gpt-5.4")); @@ -2533,15 +2527,15 @@ describe("resolveModel", () => { expect(result.model?.name).toBe("gpt-5.4"); }); - it("applies canonical openai-codex overrides when resolving the gpt-5.4-codex alias", () => { + it("applies canonical openai overrides when resolving the gpt-5.4-codex alias", () => { mockOpenAICodexTemplateModel(discoverModels); const cfg = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://proxy.example.com/backend-api", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", models: [ { ...makeModel("gpt-5.4"), @@ -2556,13 +2550,13 @@ describe("resolveModel", () => { }, } as unknown as OpenClawConfig; - const result = resolveModelForTest("openai-codex", "gpt-5.4-codex", "/tmp/agent", cfg); + const result = resolveModelForTest("openai", "gpt-5.4-codex", "/tmp/agent", cfg); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://proxy.example.com/backend-api", contextWindow: 123456, contextTokens: 65432, @@ -2577,8 +2571,8 @@ describe("resolveModel", () => { const cfg = { models: { providers: { - "openai-codex": { - api: "openai-codex-responses", + openai: { + api: "openai-chatgpt-responses", models: [ { ...makeModel("gpt-5.4"), @@ -2596,21 +2590,21 @@ describe("resolveModel", () => { }, } as unknown as OpenClawConfig; - const result = resolveModelForTest("openai-codex", "gpt-5.4-codex", "/tmp/agent", cfg); + const result = resolveModelForTest("openai", "gpt-5.4-codex", "/tmp/agent", cfg); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", contextWindow: 111111, maxTokens: 11111, }); }); - it("builds an openai-codex fallback for gpt-5.4-mini", () => { + it("builds an openai fallback for gpt-5.4-mini", () => { mockOpenAICodexTemplateModel(discoverModels); - const result = resolveModelForTest("openai-codex", "gpt-5.4-mini", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.4-mini", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { @@ -2620,20 +2614,20 @@ describe("resolveModel", () => { }); }); - it("does not build an openai-codex fallback for removed gpt-5.3-codex-spark", () => { + it("does not build an openai fallback for removed gpt-5.3-codex-spark", () => { mockOpenAICodexTemplateModel(discoverModels); - const result = resolveModelForTest("openai-codex", "gpt-5.3-codex-spark", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.3-codex-spark", "/tmp/agent"); expect(result.model).toBeUndefined(); expect(result.error).toBe( - "Unknown model: openai-codex/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.", + "Unknown model: openai/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.", ); }); - it("rejects stale openai-codex gpt-5.3-codex-spark discovery rows", () => { + it("rejects stale openai gpt-5.3-codex-spark discovery rows", () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.3-codex-spark", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.3-codex-spark"), @@ -2642,17 +2636,17 @@ describe("resolveModel", () => { }, }); - const result = resolveModelForTest("openai-codex", "gpt-5.3-codex-spark", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.3-codex-spark", "/tmp/agent"); expect(result.model).toBeUndefined(); expect(result.error).toBe( - "Unknown model: openai-codex/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.", + "Unknown model: openai/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.", ); }); - it("prefers runtime-resolved openai-codex gpt-5.4 metadata when it has a larger context window", () => { + it("prefers runtime-resolved openai gpt-5.4 metadata when it has a larger context window", () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.4"), @@ -2663,22 +2657,22 @@ describe("resolveModel", () => { }, }); - const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", contextWindow: 1_050_000, contextTokens: 272_000, }); }); - it("lets official openai-codex metadata override stale configured model rows", () => { + it("lets official openai metadata override stale configured model rows", () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.4"), @@ -2689,13 +2683,13 @@ describe("resolveModel", () => { const cfg = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", models: [ { ...makeModel("gpt-5.5-pro"), - api: "openai-codex-responses", + api: "openai-chatgpt-responses", reasoning: false, input: ["text"], cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, @@ -2710,13 +2704,13 @@ describe("resolveModel", () => { }, } as unknown as OpenClawConfig; - const result = resolveModelForTest("openai-codex", "gpt-5.5-pro", "/tmp/agent", cfg); + const result = resolveModelForTest("openai", "gpt-5.5-pro", "/tmp/agent", cfg); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5-pro", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text", "image"], @@ -2727,26 +2721,26 @@ describe("resolveModel", () => { }); }); - it("resolves openai-codex gpt-5.5 even when discovery omits the OAuth catalog row", () => { - const result = resolveModelForTest("openai-codex", "gpt-5.5"); + it("resolves openai gpt-5.5 through the direct API fallback when discovery omits OAuth metadata", () => { + const result = resolveModelForTest("openai", "gpt-5.5"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", - api: "openai-codex-responses", - baseUrl: "https://chatgpt.com/backend-api", + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", reasoning: true, input: ["text", "image"], - contextWindow: 400_000, + contextWindow: 1_000_000, contextTokens: 272_000, maxTokens: 128_000, }); }); - it("preserves unmarked manual openai-codex metadata overrides", () => { + it("preserves unmarked manual openai metadata overrides", () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.5", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.5"), @@ -2759,13 +2753,13 @@ describe("resolveModel", () => { const cfg = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", models: [ { ...makeModel("gpt-5.5"), - api: "openai-codex-responses", + api: "openai-chatgpt-responses", reasoning: true, input: ["text", "image"], cost: { input: 9, output: 99, cacheRead: 0.9, cacheWrite: 0 }, @@ -2779,11 +2773,11 @@ describe("resolveModel", () => { }, } as unknown as OpenClawConfig; - const result = resolveModelForTest("openai-codex", "gpt-5.5", "/tmp/agent", cfg); + const result = resolveModelForTest("openai", "gpt-5.5", "/tmp/agent", cfg); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", cost: { input: 9, output: 99, cacheRead: 0.9, cacheWrite: 0 }, contextWindow: 555_555, @@ -2792,9 +2786,9 @@ describe("resolveModel", () => { }); }); - it("prefers runtime-resolved openai-codex gpt-5.4 metadata during async resolution too", async () => { + it("prefers runtime-resolved openai gpt-5.4 metadata during async resolution too", async () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.4"), @@ -2804,20 +2798,20 @@ describe("resolveModel", () => { }, }); - const result = await resolveModelAsyncForTest("openai-codex", "gpt-5.4", "/tmp/agent"); + const result = await resolveModelAsyncForTest("openai", "gpt-5.4", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", contextWindow: 1_050_000, contextTokens: 272_000, }); }); - it("normalizes stale discovered openai-codex /backend-api/v1 metadata", () => { + it("normalizes stale discovered openai /backend-api/v1 metadata", () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.4"), @@ -2826,13 +2820,13 @@ describe("resolveModel", () => { }, }); - const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", }); }); @@ -2866,9 +2860,9 @@ describe("resolveModel", () => { }); }); - it("normalizes discovered openai-codex metadata when api is missing", () => { + it("normalizes discovered openai metadata when api is missing", () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.4"), @@ -2877,20 +2871,20 @@ describe("resolveModel", () => { }, }); - const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", }); }); it("passes configured workspaceDir to runtime preference hooks", () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.4"), @@ -2907,7 +2901,7 @@ describe("resolveModel", () => { const runProviderDynamicModel = vi.fn( (params: { workspaceDir?: string; context: { provider: string; modelId: string } }) => params.workspaceDir === "/tmp/workspace" && - params.context.provider === "openai-codex" && + params.context.provider === "openai" && params.context.modelId === "gpt-5.4" ? ({ ...buildOpenAICodexForwardCompatExpectation("gpt-5.4"), @@ -2928,7 +2922,7 @@ describe("resolveModel", () => { }, } as OpenClawConfig; - const result = resolveModel("openai-codex", "gpt-5.4", "/tmp/agent-state", cfg, { + const result = resolveModel("openai", "gpt-5.4", "/tmp/agent-state", cfg, { authStorage: { mocked: true } as never, modelRegistry: discoverModels({ mocked: true } as never, "/tmp/agent-state"), runtimeHooks, @@ -2936,7 +2930,7 @@ describe("resolveModel", () => { const preferInput = mockCallArg(shouldPreferRuntimeResolvedModel); expectRecordFields(preferInput, { - provider: "openai-codex", + provider: "openai", workspaceDir: "/tmp/workspace", }); expectRecordFields(preferInput.context, { @@ -2945,17 +2939,17 @@ describe("resolveModel", () => { }); const dynamicInput = mockCallArg(runProviderDynamicModel); expectRecordFields(dynamicInput, { - provider: "openai-codex", + provider: "openai", workspaceDir: "/tmp/workspace", }); expectRecordFields(dynamicInput.context, { agentDir: "/tmp/agent-state", modelId: "gpt-5.4", - provider: "openai-codex", + provider: "openai", }); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", contextWindow: 1_050_000, contextTokens: 272_000, @@ -2970,7 +2964,7 @@ describe("resolveModel", () => { }) => params.workspaceDir === "/tmp/workspace" && params.context.workspaceDir === "/tmp/workspace" && - params.context.provider === "openai-codex" && + params.context.provider === "openai" && params.context.modelId === "gpt-5.4" ? ({ ...buildOpenAICodexForwardCompatExpectation("gpt-5.4"), @@ -2991,7 +2985,7 @@ describe("resolveModel", () => { } as OpenClawConfig; const result = resolveModelWithRegistry({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", agentDir: "/tmp/agent-state", cfg, @@ -3007,17 +3001,17 @@ describe("resolveModel", () => { workspaceDir: "/tmp/workspace", agentDir: "/tmp/agent-state", modelId: "gpt-5.4", - provider: "openai-codex", + provider: "openai", }); expectRecordFields(result, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", }); }); - it("resolves discovered openai-codex gpt-5.4-mini rows", () => { + it("resolves discovered openai gpt-5.4-mini rows", () => { mockDiscoveredModel(discoverModels, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4-mini", templateModel: { ...buildOpenAICodexForwardCompatExpectation("gpt-5.4-mini"), @@ -3027,11 +3021,11 @@ describe("resolveModel", () => { }, }); - const result = resolveModelForTest("openai-codex", "gpt-5.4-mini", "/tmp/agent"); + const result = resolveModelForTest("openai", "gpt-5.4-mini", "/tmp/agent"); expect(result.error).toBeUndefined(); expectRecordFields(result.model, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4-mini", name: "GPT-5.4 Mini", contextWindow: 64_000, diff --git a/src/agents/embedded-agent-runner/model.ts b/src/agents/embedded-agent-runner/model.ts index 95ef090363d7..570766aa87d9 100644 --- a/src/agents/embedded-agent-runner/model.ts +++ b/src/agents/embedded-agent-runner/model.ts @@ -27,7 +27,7 @@ import { shouldSuppressBuiltInModel, shouldUnconditionallySuppress, } from "../model-suppression.js"; -import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../openai-codex-routing.js"; +import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../openai-routing.js"; import { attachModelProviderLocalService } from "../provider-local-service.js"; import { attachModelProviderRequestTransport, @@ -162,7 +162,7 @@ function discoverCachedAgentStoresForAgent( function canonicalizeLegacyResolvedModel(params: { provider: string; model: Model }): Model { if ( - !["openai", "openai-codex"].includes(normalizeProviderId(params.provider)) || + normalizeProviderId(params.provider) !== "openai" || params.model.id.trim().toLowerCase() !== "gpt-5.4-codex" ) { return params.model; diff --git a/src/agents/embedded-agent-runner/replay-history.ts b/src/agents/embedded-agent-runner/replay-history.ts index 50f73df6116a..6cc8088fe6ec 100644 --- a/src/agents/embedded-agent-runner/replay-history.ts +++ b/src/agents/embedded-agent-runner/replay-history.ts @@ -701,7 +701,7 @@ export async function sanitizeSessionHistory(params: { }); const isOpenAIResponsesApi = params.modelApi === "openai-responses" || - params.modelApi === "openai-codex-responses" || + params.modelApi === "openai-chatgpt-responses" || params.modelApi === "azure-openai-responses"; const hasSnapshot = Boolean(params.provider || params.modelApi || params.modelId); const priorSnapshot = hasSnapshot ? readLastModelSnapshot(params.sessionManager) : null; diff --git a/src/agents/embedded-agent-runner/run.codex-app-server-recovery.test.ts b/src/agents/embedded-agent-runner/run.codex-app-server-recovery.test.ts index d27a07b03b3c..fd8037f92000 100644 --- a/src/agents/embedded-agent-runner/run.codex-app-server-recovery.test.ts +++ b/src/agents/embedded-agent-runner/run.codex-app-server-recovery.test.ts @@ -287,7 +287,7 @@ describe("runEmbeddedAgent Codex app-server recovery", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.5", + primary: "openai/gpt-5.5", fallbacks: ["anthropic/claude-opus-4-6"], }, }, diff --git a/src/agents/embedded-agent-runner/run.codex-server-error-fallback.test.ts b/src/agents/embedded-agent-runner/run.codex-server-error-fallback.test.ts index c02a17996bc0..ee3c68842706 100644 --- a/src/agents/embedded-agent-runner/run.codex-server-error-fallback.test.ts +++ b/src/agents/embedded-agent-runner/run.codex-server-error-fallback.test.ts @@ -38,7 +38,7 @@ describe("runEmbeddedAgent Codex server_error fallback handoff", () => { const currentAttemptAssistant = makeAssistantMessageFixture({ stopReason: "error", errorMessage: rawCodexError, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", }); mockedRunEmbeddedAttempt.mockResolvedValueOnce( @@ -56,7 +56,7 @@ describe("runEmbeddedAgent Codex server_error fallback handoff", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.4", + primary: "openai/gpt-5.4", fallbacks: ["anthropic/claude-opus-4-6"], }, }, diff --git a/src/agents/embedded-agent-runner/run.cross-provider-fallback-error-context.test.ts b/src/agents/embedded-agent-runner/run.cross-provider-fallback-error-context.test.ts index 9a7e5701aedc..d363a5201e24 100644 --- a/src/agents/embedded-agent-runner/run.cross-provider-fallback-error-context.test.ts +++ b/src/agents/embedded-agent-runner/run.cross-provider-fallback-error-context.test.ts @@ -69,7 +69,7 @@ function makeCrossProviderFallbackConfig() { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.4", + primary: "openai/gpt-5.4", fallbacks: ["deepseek/deepseek-chat", "google/gemini-2.5-flash"], }, }, @@ -108,7 +108,7 @@ describe("runEmbeddedAgent cross-provider fallback error handling", () => { lastAssistant: makeAssistantMessageFixture({ stopReason: "error", errorMessage: "You have hit your ChatGPT usage limit (plus plan).", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", content: [], }), @@ -181,7 +181,7 @@ describe("runEmbeddedAgent cross-provider fallback error handling", () => { lastAssistant: makeAssistantMessageFixture({ stopReason: "error", errorMessage: "You exceeded your current OpenAI quota.", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", content: [], }), @@ -213,7 +213,7 @@ describe("runEmbeddedAgent cross-provider fallback error handling", () => { lastAssistant: makeAssistantMessageFixture({ stopReason: "error", errorMessage: "You exceeded your current OpenAI quota.", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", content: [], }), diff --git a/src/agents/embedded-agent-runner/run.incomplete-turn.test.ts b/src/agents/embedded-agent-runner/run.incomplete-turn.test.ts index 470880457492..26fe9ac1622f 100644 --- a/src/agents/embedded-agent-runner/run.incomplete-turn.test.ts +++ b/src/agents/embedded-agent-runner/run.incomplete-turn.test.ts @@ -330,7 +330,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "stop", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [{ type: "text", text: finalText }], } as unknown as EmbeddedRunAttemptResult["lastAssistant"], @@ -339,7 +339,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { const result = await runEmbeddedAgent({ ...overflowBaseRunParams, - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", runId: "run-prompt-timeout-final-assistant-recovered", }); @@ -511,7 +511,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "end_turn", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [ { @@ -527,7 +527,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { const result = await runEmbeddedAgent({ ...overflowBaseRunParams, allowEmptyAssistantReplyAsSilent: true, - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", runId: "run-reasoning-only-silent", }); @@ -1713,11 +1713,11 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { expect(retryInstruction).toBeNull(); }); - it("retries empty openai-codex-responses turns with non-zero output tokens (#85364)", () => { + it("retries empty openai-chatgpt-responses turns with non-zero output tokens (#85364)", () => { const retryInstruction = resolveEmptyResponseRetryInstruction({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.5", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", payloadCount: 0, aborted: false, timedOut: false, @@ -1726,7 +1726,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "stop", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [], usage: { input: 24794, output: 111, cacheRead: 4608, totalTokens: 29513 }, @@ -2332,7 +2332,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { { role: "assistant", stopReason: "stop", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [{ type: "text", text: "" }], } as unknown as EmbeddedRunAttemptResult["messagesSnapshot"][number], @@ -2340,7 +2340,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "stop", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [{ type: "text", text: "" }], } as unknown as EmbeddedRunAttemptResult["lastAssistant"], @@ -2381,7 +2381,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "stop", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [{ type: "text", text: "" }], } as unknown as EmbeddedRunAttemptResult["lastAssistant"], @@ -2413,7 +2413,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "end_turn", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [ { @@ -2451,7 +2451,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "error", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [], } as unknown as EmbeddedRunAttemptResult["lastAssistant"], @@ -2463,7 +2463,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "stop", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [{ type: "text", text: "" }], } as unknown as EmbeddedRunAttemptResult["lastAssistant"], @@ -2497,7 +2497,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { lastAssistant: { role: "assistant", stopReason: "stop", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", content: [{ type: "text", text: "" }], } as unknown as EmbeddedRunAttemptResult["lastAssistant"], @@ -2507,7 +2507,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { const result = await runEmbeddedAgent({ ...overflowBaseRunParams, allowEmptyAssistantReplyAsSilent: true, - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", runId: "run-empty-assistant-silent", }); @@ -2624,7 +2624,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { ...overflowBaseRunParams, prompt: "made a bunch of improvements to the student's source code (openclaw) this weekend, along with a few other maintainers. hopefully he will be more proactive now", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", runId: "run-strict-agentic-casual-discord-status", config: { @@ -2671,7 +2671,7 @@ describe("runEmbeddedAgent incomplete-turn safety", () => { it("does not misclassify a direct answer that says 'i'm not going to' as planning-only", () => { const retryInstruction = resolvePlanningOnlyRetryInstruction({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", prompt: "What do you think lobstar should do to help the chart?", aborted: false, diff --git a/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts b/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts index e49ac007eb79..017bee9a0e52 100644 --- a/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts +++ b/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts @@ -254,7 +254,7 @@ export function resetRunOverflowCompactionHarnessMocks(): void { id: "codex", label: "Codex", supports: (ctx) => - ctx.provider === "codex" || ctx.provider === "openai-codex" || ctx.provider === "openai" + ctx.provider === "codex" || ctx.provider === "openai" || ctx.provider === "openai" ? { supported: true, priority: 100 } : { supported: false }, runAttempt: async (params) => await mockedRunEmbeddedAttempt(params), diff --git a/src/agents/embedded-agent-runner/run.overflow-compaction.test.ts b/src/agents/embedded-agent-runner/run.overflow-compaction.test.ts index cb832c7062c9..cdb95b67e679 100644 --- a/src/agents/embedded-agent-runner/run.overflow-compaction.test.ts +++ b/src/agents/embedded-agent-runner/run.overflow-compaction.test.ts @@ -78,7 +78,7 @@ function makeForwardingCase(internalEvents: AgentInternalEvent[]) { function codexHarnessSupportsKnownProviders( ctx: Parameters[0], ): ReturnType { - return ctx.provider === "codex" || ctx.provider === "openai" || ctx.provider === "openai-codex" + return ctx.provider === "codex" || ctx.provider === "openai" || ctx.provider === "openai" ? { supported: true, priority: 100 } : { supported: false }; } @@ -844,8 +844,8 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { harnessId: "codex", }, auth: { - harnessAuthProvider: "openai-codex", - forwardedAuthProfileId: "openai-codex:work", + harnessAuthProvider: "openai", + forwardedAuthProfileId: "openai:work", }, }); clearAgentHarnesses(); @@ -860,16 +860,16 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { const codexAuthStore = { version: 1, profiles: { - "openai-codex:work": { + "openai:work": { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "access", refresh: "refresh", expires: Date.now() + 60_000, }, - "openai-codex:other": { + "openai:other": { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "other-access", refresh: "other-refresh", expires: Date.now() + 60_000, @@ -902,9 +902,9 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, }, }, - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "user", - runId: "plugin-harness-forwards-openai-codex-auth", + runId: "plugin-harness-forwards-openai-chatgpt-auth", }); } finally { clearAgentHarnesses(); @@ -915,7 +915,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { expect(pluginRunAttempt).toHaveBeenCalledTimes(1); const pluginParams = expectMockCallFields(pluginRunAttempt, { provider: "codex", - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "user", }); expectRuntimePlanFields(pluginParams.runtimePlan, { @@ -925,8 +925,8 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { harnessId: "codex", }, auth: { - harnessAuthProvider: "openai-codex", - forwardedAuthProfileId: "openai-codex:work", + harnessAuthProvider: "openai", + forwardedAuthProfileId: "openai:work", }, }); const harnessParams = mockCallArg(pluginRunAttempt) as { @@ -937,9 +937,9 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { expect(harnessParams?.runtimePlan).toBe(runtimePlan); const forwardedAuthStore = expectRecordFields(harnessParams.authProfileStore, {}); const authProfiles = expectRecordFields(forwardedAuthStore.profiles, {}); - expect(Object.keys(authProfiles)).toEqual(["openai-codex:work"]); - expectRecordFields(authProfiles["openai-codex:work"], { - provider: "openai-codex", + expect(Object.keys(authProfiles)).toEqual(["openai:work"]); + expectRecordFields(authProfiles["openai:work"], { + provider: "openai", }); expect(harnessParams.toolAuthProfileStore).toBe(codexAuthStore); }); @@ -958,7 +958,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { auth: { providerForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:work", + forwardedAuthProfileId: "openai:work", }, }); clearAgentHarnesses(); @@ -983,9 +983,9 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, }, }, - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "user", - runId: "forced-codex-harness-forwards-openai-codex-auth", + runId: "forced-codex-harness-forwards-openai-chatgpt-auth", }); } finally { clearAgentHarnesses(); @@ -996,7 +996,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { expect(pluginRunAttempt).toHaveBeenCalledTimes(1); const pluginParams = expectMockCallFields(pluginRunAttempt, { provider: "openai", - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "user", }); expectRuntimePlanFields(pluginParams.runtimePlan, { @@ -1008,7 +1008,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { auth: { providerForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:work", + forwardedAuthProfileId: "openai:work", }, }); const harnessParams = mockCallArg(pluginRunAttempt) as { runtimePlan?: unknown }; @@ -1017,8 +1017,8 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { const [[successParams]] = mockedMarkAuthProfileSuccess.mock.calls as unknown as Array< [{ provider?: string; profileId?: string }] >; - expect(successParams.provider).toBe("openai-codex"); - expect(successParams.profileId).toBe("openai-codex:work"); + expect(successParams.provider).toBe("openai"); + expect(successParams.profileId).toBe("openai:work"); }); it("bootstraps OAuth credentials for forced openai/* Codex response runs", async () => { @@ -1040,13 +1040,13 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { providerForAuth: "openai", authProfileProviderForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:work", + forwardedAuthProfileId: "openai:work", }, }); const codexAuthStore = { version: 1 as const, profiles: { - "openai-codex:work": { + "openai:work": { type: "oauth" as const, provider: "openai", access: "access-token", @@ -1068,7 +1068,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { id: "gpt-5.5", provider: "openai", contextWindow: 200000, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", }, error: null, authStorage: codexAuthStorage, @@ -1088,9 +1088,9 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, }, }, - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "user", - runId: "forced-openai-codex-responses-bootstrap-oauth", + runId: "forced-openai-chatgpt-responses-bootstrap-oauth", }); } finally { clearAgentHarnesses(); @@ -1104,13 +1104,13 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }); expect(mockedEnsureAuthProfileStoreWithoutExternalProfiles).not.toHaveBeenCalled(); expectMockCallFields(mockedGetApiKeyForModel, { - profileId: "openai-codex:work", + profileId: "openai:work", }); expect(codexAuthStorage.setRuntimeApiKey).toHaveBeenCalledWith("openai", "test-key"); expect(pluginRunAttempt).toHaveBeenCalledTimes(1); expectMockCallFields(pluginRunAttempt, { provider: "openai", - authProfileId: "openai-codex:work", + authProfileId: "openai:work", authProfileIdSource: "user", resolvedApiKey: "test-key", }); @@ -1135,13 +1135,13 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { providerForAuth: "openai", authProfileProviderForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:default", + forwardedAuthProfileId: "openai:default", }, }); const codexAuthStore = { version: 1 as const, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth" as const, provider: "openai", access: "access-token", @@ -1174,16 +1174,14 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { provider?: string; store?: { profiles?: Record }; }; - return provider === "openai" && store?.profiles?.["openai-codex:default"] - ? ["openai-codex:default"] - : []; + return provider === "openai" && store?.profiles?.["openai:default"] ? ["openai:default"] : []; }); mockedResolveModelAsync.mockResolvedValueOnce({ model: { id: "gpt-5.5", provider: "openai", contextWindow: 200000, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", }, error: null, authStorage: codexAuthStorage, @@ -1193,7 +1191,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { mockedGetApiKeyForModel.mockImplementation( async ({ profileId }: { profileId?: string } = {}) => { if (!profileId) { - throw new Error('No API key found for provider "openai-codex"'); + throw new Error('No API key found for provider "openai"'); } return { apiKey: "test-key", @@ -1216,7 +1214,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, }, }, - runId: "forced-openai-codex-responses-auto-selects-external-overlay", + runId: "forced-openai-chatgpt-responses-auto-selects-external-overlay", }); } finally { clearAgentHarnesses(); @@ -1234,13 +1232,13 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }); expect(mockedGetApiKeyForModel).toHaveBeenCalledTimes(1); expectMockCallFields(mockedGetApiKeyForModel, { - profileId: "openai-codex:default", + profileId: "openai:default", }); expect(codexAuthStorage.setRuntimeApiKey).toHaveBeenCalledWith("openai", "test-key"); expect(pluginRunAttempt).toHaveBeenCalledTimes(1); const pluginParams = expectMockCallFields(pluginRunAttempt, { provider: "openai", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", authProfileIdSource: "auto", resolvedApiKey: "test-key", }); @@ -1252,7 +1250,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, auth: { harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:default", + forwardedAuthProfileId: "openai:default", }, }); const harnessParams = mockCallArg(pluginRunAttempt) as { @@ -1261,8 +1259,8 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }; const forwardedAuthStore = expectRecordFields(harnessParams.authProfileStore, {}); const authProfiles = expectRecordFields(forwardedAuthStore.profiles, {}); - expect(Object.keys(authProfiles)).toEqual(["openai-codex:default"]); - expectRecordFields(authProfiles["openai-codex:default"], { + expect(Object.keys(authProfiles)).toEqual(["openai:default"]); + expectRecordFields(authProfiles["openai:default"], { provider: "openai", }); expect(harnessParams.toolAuthProfileStore).toBe(codexAuthStore); @@ -1299,8 +1297,8 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { providerForAuth: "openai", authProfileProviderForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:sub", - forwardedAuthProfileCandidateIds: ["openai-codex:sub", "openai-codex:backup"], + forwardedAuthProfileId: "openai:sub", + forwardedAuthProfileCandidateIds: ["openai:sub", "openai:backup"], }, }); const secondRuntimePlan = makeForwardedRuntimePlan({ @@ -1313,21 +1311,21 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { providerForAuth: "openai", authProfileProviderForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:backup", - forwardedAuthProfileCandidateIds: ["openai-codex:sub", "openai-codex:backup"], + forwardedAuthProfileId: "openai:backup", + forwardedAuthProfileCandidateIds: ["openai:sub", "openai:backup"], }, }); const codexAuthStore = { version: 1 as const, profiles: { - "openai-codex:sub": { + "openai:sub": { type: "oauth" as const, provider: "openai", access: "sub-access-token", refresh: "sub-refresh-token", expires: Date.now() + 60_000, }, - "openai-codex:backup": { + "openai:backup": { type: "oauth" as const, provider: "openai", access: "backup-access-token", @@ -1344,13 +1342,13 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { runAttempt: pluginRunAttempt, }); mockedEnsureAuthProfileStore.mockReturnValueOnce(codexAuthStore); - mockedResolveAuthProfileOrder.mockReturnValueOnce(["openai-codex:sub", "openai-codex:backup"]); + mockedResolveAuthProfileOrder.mockReturnValueOnce(["openai:sub", "openai:backup"]); mockedResolveModelAsync.mockResolvedValueOnce({ model: { id: "gpt-5.5", provider: "openai", contextWindow: 200000, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", }, error: null, authStorage: codexAuthStorage, @@ -1361,8 +1359,8 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { .mockReturnValueOnce(secondRuntimePlan); mockedGetApiKeyForModel.mockImplementation( async ({ profileId }: { profileId?: string } = {}) => ({ - apiKey: profileId === "openai-codex:backup" ? "backup-token" : "sub-token", - profileId: profileId ?? "openai-codex:sub", + apiKey: profileId === "openai:backup" ? "backup-token" : "sub-token", + profileId: profileId ?? "openai:sub", source: "test", mode: "api-key", }), @@ -1387,7 +1385,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, }, }, - runId: "forced-openai-codex-responses-rotates-oauth", + runId: "forced-openai-chatgpt-responses-rotates-oauth", }); } finally { clearAgentHarnesses(); @@ -1399,14 +1397,14 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { expect(pluginRunAttempt).toHaveBeenCalledTimes(2); expectMockCallFields(pluginRunAttempt, { provider: "openai", - authProfileId: "openai-codex:sub", + authProfileId: "openai:sub", resolvedApiKey: "sub-token", }); expectMockCallFields( pluginRunAttempt, { provider: "openai", - authProfileId: "openai-codex:backup", + authProfileId: "openai:backup", resolvedApiKey: "backup-token", }, 1, @@ -1427,7 +1425,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { auth: { providerForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:default", + forwardedAuthProfileId: "openai:default", }, }); clearAgentHarnesses(); @@ -1452,9 +1450,9 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, }, }, - authProfileId: "openai-codex:default", + authProfileId: "openai:default", authProfileIdSource: "auto", - runId: "forced-codex-harness-keeps-auto-openai-codex-auth", + runId: "forced-codex-harness-keeps-auto-openai-chatgpt-auth", }); } finally { clearAgentHarnesses(); @@ -1465,7 +1463,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { expect(pluginRunAttempt).toHaveBeenCalledTimes(1); const pluginParams = expectMockCallFields(pluginRunAttempt, { provider: "openai", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", authProfileIdSource: "auto", }); expectRuntimePlanFields(pluginParams.runtimePlan, { @@ -1477,7 +1475,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { auth: { providerForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:default", + forwardedAuthProfileId: "openai:default", }, }); const harnessParams = mockCallArg(pluginRunAttempt) as { runtimePlan?: unknown }; @@ -1498,7 +1496,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { auth: { providerForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:default", + forwardedAuthProfileId: "openai:default", }, }); clearAgentHarnesses(); @@ -1510,7 +1508,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }); mockedBuildAgentRuntimePlan.mockReturnValueOnce(runtimePlan); mockedGetApiKeyForModel.mockRejectedValueOnce(new Error("generic auth should be skipped")); - mockedResolveAuthProfileOrder.mockReturnValueOnce(["openai-codex:default"]); + mockedResolveAuthProfileOrder.mockReturnValueOnce(["openai:default"]); try { await runEmbeddedAgent({ @@ -1524,7 +1522,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, }, }, - runId: "forced-codex-harness-auto-selects-openai-codex-auth", + runId: "forced-codex-harness-auto-selects-openai-chatgpt-auth", }); } finally { clearAgentHarnesses(); @@ -1538,7 +1536,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { expect(pluginRunAttempt).toHaveBeenCalledTimes(1); const pluginParams = expectMockCallFields(pluginRunAttempt, { provider: "openai", - authProfileId: "openai-codex:default", + authProfileId: "openai:default", authProfileIdSource: "auto", }); expectRuntimePlanFields(pluginParams.runtimePlan, { @@ -1550,7 +1548,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { auth: { providerForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:default", + forwardedAuthProfileId: "openai:default", }, }); const harnessParams = mockCallArg(pluginRunAttempt) as { runtimePlan?: unknown }; @@ -1677,8 +1675,8 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { auth: { providerForAuth: "openai", harnessAuthProvider: "openai", - forwardedAuthProfileId: "openai-codex:sub", - forwardedAuthProfileCandidateIds: ["openai-codex:sub", "openai:backup"], + forwardedAuthProfileId: "openai:sub", + forwardedAuthProfileCandidateIds: ["openai:sub", "openai:backup"], }, }); const secondRuntimePlan = makeForwardedRuntimePlan({ @@ -1691,7 +1689,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { providerForAuth: "openai", harnessAuthProvider: "openai", forwardedAuthProfileId: "openai:backup", - forwardedAuthProfileCandidateIds: ["openai-codex:sub", "openai:backup"], + forwardedAuthProfileCandidateIds: ["openai:sub", "openai:backup"], }, }); clearAgentHarnesses(); @@ -1705,11 +1703,11 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { .mockReturnValueOnce(firstRuntimePlan) .mockReturnValueOnce(secondRuntimePlan); mockedGetApiKeyForModel.mockRejectedValueOnce(new Error("generic auth should be skipped")); - mockedResolveAuthProfileOrder.mockReturnValueOnce(["openai-codex:sub", "openai:backup"]); + mockedResolveAuthProfileOrder.mockReturnValueOnce(["openai:sub", "openai:backup"]); mockedEnsureAuthProfileStoreWithoutExternalProfiles.mockReturnValue({ version: 1, profiles: { - "openai-codex:sub": { + "openai:sub": { type: "oauth", provider: "openai", access: "access", @@ -1744,7 +1742,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { }, }, runId: "forced-codex-harness-rotates-subscription-limit-auth", - authProfileId: "openai-codex:sub", + authProfileId: "openai:sub", authProfileIdSource: "auto", }); } finally { @@ -1755,7 +1753,7 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { expect(pluginRunAttempt).toHaveBeenCalledTimes(2); const firstAttempt = expectMockCallFields(pluginRunAttempt, { provider: "openai", - authProfileId: "openai-codex:sub", + authProfileId: "openai:sub", authProfileIdSource: "auto", }); const secondAttempt = expectMockCallFields( @@ -1769,19 +1767,19 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => { ); expectRuntimePlanFields(firstAttempt.runtimePlan, { auth: { - forwardedAuthProfileId: "openai-codex:sub", - forwardedAuthProfileCandidateIds: ["openai-codex:sub", "openai:backup"], + forwardedAuthProfileId: "openai:sub", + forwardedAuthProfileCandidateIds: ["openai:sub", "openai:backup"], }, }); expectRuntimePlanFields(secondAttempt.runtimePlan, { auth: { forwardedAuthProfileId: "openai:backup", - forwardedAuthProfileCandidateIds: ["openai-codex:sub", "openai:backup"], + forwardedAuthProfileCandidateIds: ["openai:sub", "openai:backup"], }, }); const firstAuthProfileStore = expectRecordFields(firstAttempt.authProfileStore, {}); const firstAuthProfiles = expectRecordFields(firstAuthProfileStore.profiles, {}); - expect(Object.keys(firstAuthProfiles)).toEqual(["openai-codex:sub", "openai:backup"]); + expect(Object.keys(firstAuthProfiles)).toEqual(["openai:sub", "openai:backup"]); expect(secondAttempt.authProfileStore).toBe(firstAttempt.authProfileStore); }); diff --git a/src/agents/embedded-agent-runner/run.ts b/src/agents/embedded-agent-runner/run.ts index e11e9d6bb8bc..09e2685be9af 100644 --- a/src/agents/embedded-agent-runner/run.ts +++ b/src/agents/embedded-agent-runner/run.ts @@ -91,7 +91,7 @@ import { listOpenAIAuthProfileProvidersForAgentRuntime, resolveContextConfigProviderForRuntime, resolveSelectedOpenAIRuntimeProvider, -} from "../openai-codex-routing.js"; +} from "../openai-routing.js"; import { resolveProviderIdForAuth } from "../provider-auth-aliases.js"; import { runAgentCleanupStep } from "../run-cleanup-timeout.js"; import { buildAgentRuntimeAuthPlan } from "../runtime-plan/auth.js"; @@ -783,11 +783,11 @@ export async function runEmbeddedAgent( const pluginHarnessNeedsOpenClawAuthBootstrap = pluginHarnessOwnsTransport && provider === OPENAI_PROVIDER_ID && - effectiveModel.api === "openai-codex-responses"; + effectiveModel.api === "openai-chatgpt-responses"; const openClawNativeCodexResponsesNeedsAuthBootstrap = !pluginHarnessOwnsTransport && provider === OPENAI_PROVIDER_ID && - effectiveModel.api === "openai-codex-responses"; + effectiveModel.api === "openai-chatgpt-responses"; let piExternalCliAuthScope = pluginHarnessOwnsTransport ? { ignoreAutoPreferredProfile: false } : openClawNativeCodexResponsesNeedsAuthBootstrap diff --git a/src/agents/embedded-agent-runner/run/attempt.run-decisions.test.ts b/src/agents/embedded-agent-runner/run/attempt.run-decisions.test.ts index 687f07b82d38..1d95764b8779 100644 --- a/src/agents/embedded-agent-runner/run/attempt.run-decisions.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt.run-decisions.test.ts @@ -23,14 +23,14 @@ describe("resolveAttemptStreamAuthProfileId", () => { it("uses only the runtime-forwarded auth profile for stream provenance", () => { expect( resolveAttemptStreamAuthProfileId({ - authProfileId: "openai-codex:raw-session-profile", + authProfileId: "openai:raw-session-profile", runtimePlan: { auth: { - forwardedAuthProfileId: "openai-codex:forwarded-profile", + forwardedAuthProfileId: "openai:forwarded-profile", }, } as never, }), - ).toBe("openai-codex:forwarded-profile"); + ).toBe("openai:forwarded-profile"); expect( resolveAttemptStreamAuthProfileId({ diff --git a/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.context-engine.test.ts b/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.context-engine.test.ts index b6a6d76f288b..5f065f3683a7 100644 --- a/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.context-engine.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.context-engine.test.ts @@ -356,7 +356,7 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { }, } as OpenClawConfig, model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "gateway", id: "gpt-5.5", contextWindow: 8192, diff --git a/src/agents/embedded-agent-runner/run/attempt.test.ts b/src/agents/embedded-agent-runner/run/attempt.test.ts index 7d5dcab92a44..cd98e8b624b8 100644 --- a/src/agents/embedded-agent-runner/run/attempt.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt.test.ts @@ -3282,7 +3282,7 @@ describe("buildAfterTurnRuntimeContext", () => { sessionId: "session-123", config: {} as OpenClawConfig, skillsSnapshot: undefined, - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", thinkLevel: "off", reasoningLevel: "on", @@ -3320,7 +3320,7 @@ describe("buildAfterTurnRuntimeContext", () => { authProfileId: "openai:p1", config: {} as OpenClawConfig, skillsSnapshot: undefined, - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", thinkLevel: "off", reasoningLevel: "on", @@ -3332,7 +3332,7 @@ describe("buildAfterTurnRuntimeContext", () => { agentDir: "/tmp/agent", }); - expect(legacy.provider).toBe("openai-codex"); + expect(legacy.provider).toBe("openai"); expect(legacy.model).toBe("gpt-5.4"); }); @@ -3354,7 +3354,7 @@ describe("buildAfterTurnRuntimeContext", () => { }, } as OpenClawConfig, skillsSnapshot: undefined, - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", thinkLevel: "off", reasoningLevel: "on", @@ -3371,7 +3371,7 @@ describe("buildAfterTurnRuntimeContext", () => { // compaction model in the runtime context. expect(legacy.provider).toBe("openrouter"); expect(legacy.model).toBe("anthropic/claude-sonnet-4-5"); - // Auth profile dropped because provider changed from openai-codex to openrouter. + // Auth profile dropped because provider changed from openai to openrouter. expect(legacy.authProfileId).toBeUndefined(); }); it("includes resolved auth profile fields for context-engine afterTurn compaction", () => { @@ -3393,7 +3393,7 @@ describe("buildAfterTurnRuntimeContext", () => { authProfileId: "openai:p1", config: { plugins: { slots: { contextEngine: "lossless-claw" } } } as OpenClawConfig, skillsSnapshot: undefined, - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", thinkLevel: "off", reasoningLevel: "on", @@ -3409,7 +3409,7 @@ describe("buildAfterTurnRuntimeContext", () => { }); expect(legacy.authProfileId).toBe("openai:p1"); - expect(legacy.provider).toBe("openai-codex"); + expect(legacy.provider).toBe("openai"); expect(legacy.model).toBe("gpt-5.4"); expect(legacy.workspaceDir).toBe("/tmp/workspace"); expect(legacy.cwd).toBe("/tmp/task-repo"); @@ -3437,7 +3437,7 @@ describe("buildAfterTurnRuntimeContext", () => { authProfileId: "openai:p1", config: { plugins: { slots: { contextEngine: "lossless-claw" } } } as OpenClawConfig, skillsSnapshot: undefined, - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", thinkLevel: "off", reasoningLevel: "on", @@ -3469,7 +3469,7 @@ describe("buildAfterTurnRuntimeContext", () => { config: {} as OpenClawConfig, skillsSnapshot: undefined, senderId: "user-123", - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", thinkLevel: "off", reasoningLevel: "on", diff --git a/src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.test.ts b/src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.test.ts index 78e86bbd2f4a..6b8c432a7b1c 100644 --- a/src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.test.ts +++ b/src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.test.ts @@ -191,8 +191,8 @@ describe("shouldRepairMalformedToolCallArguments", () => { it("enables the repair for Codex and Azure Responses transports", () => { expect( shouldRepairMalformedToolCallArguments({ - provider: "openai-codex", - modelApi: "openai-codex-responses", + provider: "openai", + modelApi: "openai-chatgpt-responses", }), ).toBe(true); expect( @@ -207,7 +207,7 @@ describe("shouldRepairMalformedToolCallArguments", () => { describe("openai-completions malformed tool-call argument repair", () => { it.each([ ["openai-completions", "sglang"], - ["openai-codex-responses", "openai-codex"], + ["openai-chatgpt-responses", "openai"], ["azure-openai-responses", "azure-openai-responses"], ])( "repairs fragmented %s function-call args before tool execution", diff --git a/src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.ts b/src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.ts index 1a70cec96de3..89fa948a249a 100644 --- a/src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.ts +++ b/src/agents/embedded-agent-runner/run/attempt.tool-call-argument-repair.ts @@ -20,7 +20,7 @@ const TOOLCALL_REPAIR_ALLOWED_LEADING_RE = /^[a-z0-9\s"'`.:/_\\-]+$/i; const TOOLCALL_REPAIR_ALLOWED_TRAILING_RE = /^[^\s{}[\]":,\\]{1,3}$/; const TOOLCALL_REPAIR_RESPONSES_APIS = new Set([ "azure-openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", ]); const TOOLCALL_REPAIR_SMART_QUOTES = new Set(["\u201c", "\u201d", "\u201e", "\u201f"]); const MAX_TOOLCALL_REPAIR_MEMBER_KEY_CHARS = 96; diff --git a/src/agents/embedded-agent-runner/run/attempt.ts b/src/agents/embedded-agent-runner/run/attempt.ts index 6eb08e13a991..bda9c30cf5d0 100644 --- a/src/agents/embedded-agent-runner/run/attempt.ts +++ b/src/agents/embedded-agent-runner/run/attempt.ts @@ -1885,7 +1885,7 @@ export async function runEmbeddedAttempt( missingToolResultText: params.model.api === "openai-responses" || params.model.api === "azure-openai-responses" || - params.model.api === "openai-codex-responses" + params.model.api === "openai-chatgpt-responses" ? "aborted" : undefined, allowedToolNames: replayAllowedToolNames, @@ -2564,7 +2564,7 @@ export async function runEmbeddedAttempt( const isOpenAIResponsesApi = params.model.api === "openai-responses" || params.model.api === "azure-openai-responses" || - params.model.api === "openai-codex-responses"; + params.model.api === "openai-chatgpt-responses"; const replayToolCallIdSanitizerDecision = { sanitizeToolCallIds: transcriptPolicy.sanitizeToolCallIds, diff --git a/src/agents/embedded-agent-runner/run/failover-observation.test.ts b/src/agents/embedded-agent-runner/run/failover-observation.test.ts index b411e3f22c4c..b7c6c67fc820 100644 --- a/src/agents/embedded-agent-runner/run/failover-observation.test.ts +++ b/src/agents/embedded-agent-runner/run/failover-observation.test.ts @@ -146,11 +146,11 @@ describe("createFailoverDecisionLogger", () => { rawError: "401 Unauthorized", failoverReason: "auth", profileFailureReason: "auth", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", - sourceProvider: "openai-codex", + sourceProvider: "openai", sourceModel: "gpt-5.4", - profileId: "openai-codex:p1", + profileId: "openai:p1", fallbackConfigured: true, timedOut: false, aborted: false, diff --git a/src/agents/embedded-agent-runner/run/incomplete-turn.ts b/src/agents/embedded-agent-runner/run/incomplete-turn.ts index 5ff69227c769..b1b5109a0512 100644 --- a/src/agents/embedded-agent-runner/run/incomplete-turn.ts +++ b/src/agents/embedded-agent-runner/run/incomplete-turn.ts @@ -144,7 +144,7 @@ const RETRY_GUARD_MODEL_APIS = new Set([ "anthropic-messages", "bedrock-converse-stream", "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "azure-openai-responses", "openclaw-openai-responses-transport", "openclaw-azure-openai-responses-transport", diff --git a/src/agents/embedded-agent-runner/run/setup.test.ts b/src/agents/embedded-agent-runner/run/setup.test.ts index e6586e860e7d..28f30a7e781e 100644 --- a/src/agents/embedded-agent-runner/run/setup.test.ts +++ b/src/agents/embedded-agent-runner/run/setup.test.ts @@ -118,7 +118,7 @@ describe("resolveEffectiveRuntimeModel", () => { const cfg = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api/codex", models: [createConfiguredModel()], }, @@ -128,8 +128,8 @@ describe("resolveEffectiveRuntimeModel", () => { const result = resolveEffectiveRuntimeModel({ cfg, - provider: "openai", - contextConfigProvider: "openai-codex", + provider: "codex", + contextConfigProvider: "openai", modelId: "gpt-5.5", runtimeModel: createRuntimeModel(), }); @@ -145,7 +145,7 @@ describe("resolveEffectiveRuntimeModel", () => { const cfg = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api/codex", models: [createConfiguredModel()], }, @@ -155,7 +155,7 @@ describe("resolveEffectiveRuntimeModel", () => { const result = resolveEffectiveRuntimeModel({ cfg, - provider: "openai", + provider: "codex", modelId: "gpt-5.5", runtimeModel: createRuntimeModel(), }); diff --git a/src/agents/embedded-agent-runner/stream-resolution.test.ts b/src/agents/embedded-agent-runner/stream-resolution.test.ts index 5981b90a9c46..01f157b0d03d 100644 --- a/src/agents/embedded-agent-runner/stream-resolution.test.ts +++ b/src/agents/embedded-agent-runner/stream-resolution.test.ts @@ -81,7 +81,7 @@ describe("describeEmbeddedAgentStreamStrategy", () => { describeEmbeddedAgentStreamStrategy({ currentStreamFn: undefined, model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "codex-mini-latest", } as never, @@ -154,7 +154,7 @@ describe("resolveEmbeddedAgentStreamFn", () => { currentStreamFn: undefined, sessionId: "session-1", model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "codex-mini-latest", } as never, @@ -406,7 +406,7 @@ describe("resolveEmbeddedAgentStreamFn", () => { currentStreamFn: undefined, sessionId: "session-1", model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "gpt-5.5", } as never, @@ -431,7 +431,7 @@ describe("resolveEmbeddedAgentStreamFn", () => { currentStreamFn: undefined, sessionId: "session-1", model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "gpt-5.5", } as never, @@ -455,7 +455,7 @@ describe("resolveEmbeddedAgentStreamFn", () => { sessionId: "session-1", signal: runSignal, model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "gpt-5.5", } as never, @@ -480,7 +480,7 @@ describe("resolveEmbeddedAgentStreamFn", () => { sessionId: "session-1", signal: runSignal, model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "gpt-5.5", } as never, @@ -505,7 +505,7 @@ describe("resolveEmbeddedAgentStreamFn", () => { sessionId: "session-1", signal: runSignal, model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "gpt-5.5", } as never, @@ -525,7 +525,7 @@ describe("resolveEmbeddedAgentStreamFn", () => { currentStreamFn: undefined, sessionId: "session-1", model: { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "gpt-5.5", } as never, diff --git a/src/agents/embedded-agent-runner/stream-resolution.ts b/src/agents/embedded-agent-runner/stream-resolution.ts index df4cd3e22e49..faee4cc2a073 100644 --- a/src/agents/embedded-agent-runner/stream-resolution.ts +++ b/src/agents/embedded-agent-runner/stream-resolution.ts @@ -50,7 +50,7 @@ function hasResolvedRuntimeApiKey(apiKey: string | undefined): boolean { } function isOpenAICodexResponsesModel(model: EmbeddedRunAttemptParams["model"]): boolean { - return model.provider === "openai" && model.api === "openai-codex-responses"; + return model.provider === "openai" && model.api === "openai-chatgpt-responses"; } function resolveOpenClawNativeCodexResponsesStreamFn(params: { diff --git a/src/agents/embedded-agent-subscribe.handlers.lifecycle.test.ts b/src/agents/embedded-agent-subscribe.handlers.lifecycle.test.ts index bfa1542f83ae..a2fd21f824a6 100644 --- a/src/agents/embedded-agent-subscribe.handlers.lifecycle.test.ts +++ b/src/agents/embedded-agent-subscribe.handlers.lifecycle.test.ts @@ -220,7 +220,7 @@ describe("handleAgentEnd", () => { const ctx = createContext({ role: "assistant", stopReason: "error", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", errorMessage: '401 {"type":"error","error":{"type":"permission_error","message":"Missing scopes: api.responses.write"}}', @@ -256,7 +256,7 @@ describe("handleAgentEnd", () => { const ctx = createContext({ role: "assistant", stopReason: "error", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", errorMessage, content: [{ type: "text", text: "" }], diff --git a/src/agents/execution-contract.test.ts b/src/agents/execution-contract.test.ts index 3030e32a058d..7d6374e8a593 100644 --- a/src/agents/execution-contract.test.ts +++ b/src/agents/execution-contract.test.ts @@ -74,8 +74,8 @@ describe("resolveEffectiveExecutionContract", () => { "openai/gpt-5.4", "openai:gpt-5.4", "openai/gpt-5o-mini", - "openai-codex/gpt-5.4", - "openai-codex:gpt-5.4", + "openai/gpt-5.4", + "openai:gpt-5.4", " openai/gpt-5.4 ", " OPENAI:GPT-5.4 ", ]) { diff --git a/src/agents/failover-error.test.ts b/src/agents/failover-error.test.ts index 854ef1eaafc8..5734574c3258 100644 --- a/src/agents/failover-error.test.ts +++ b/src/agents/failover-error.test.ts @@ -1140,7 +1140,7 @@ describe("failover-error", () => { status: 500, message: OPENAI_SERVER_ERROR_PAYLOAD, }, - { provider: "openai-codex", model: "gpt-5.4" }, + { provider: "openai", model: "gpt-5.4" }, ); expect(err?.reason).toBe("server_error"); expect(err?.status).toBe(500); diff --git a/src/agents/gpt5-prompt-overlay.ts b/src/agents/gpt5-prompt-overlay.ts index 39b396b411bb..63678dbbef67 100644 --- a/src/agents/gpt5-prompt-overlay.ts +++ b/src/agents/gpt5-prompt-overlay.ts @@ -7,7 +7,7 @@ const OPENAI_FAMILY_GPT5_PROMPT_OVERLAY_PROVIDERS = new Set([ "codex", "codex-cli", "openai", - "openai-codex", + "openai", "azure-openai", "azure-openai-responses", ]); diff --git a/src/agents/harness/policy.ts b/src/agents/harness/policy.ts index a687b1f460de..eb4158026d49 100644 --- a/src/agents/harness/policy.ts +++ b/src/agents/harness/policy.ts @@ -2,7 +2,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { AUTO_AGENT_RUNTIME_ID, type EmbeddedAgentRuntime } from "../agent-runtime-id.js"; import { normalizeOptionalAgentRuntimeId } from "../agent-runtime-id.js"; import { resolveModelRuntimePolicy } from "../model-runtime-policy.js"; -import { openAIProviderUsesCodexRuntimeByDefault } from "../openai-codex-routing.js"; +import { openAIProviderUsesCodexRuntimeByDefault } from "../openai-routing.js"; export type AgentHarnessPolicy = { runtime: EmbeddedAgentRuntime; diff --git a/src/agents/harness/runtime-plugin.test.ts b/src/agents/harness/runtime-plugin.test.ts index ed9f544ec34c..dd1462b8a2ea 100644 --- a/src/agents/harness/runtime-plugin.test.ts +++ b/src/agents/harness/runtime-plugin.test.ts @@ -28,8 +28,7 @@ describe("ensureSelectedAgentHarnessPlugin", () => { mocks.resolveBundledProviderCompatPluginIds.mockReset(); mocks.resolveOwningPluginIdsForProvider.mockReset(); mocks.resolveOwningPluginIdsForProvider.mockImplementation( - ({ provider }: { provider: string }) => - provider === "openai" || provider === "openai-codex" ? ["openai"] : undefined, + ({ provider }: { provider: string }) => (provider === "openai" ? ["openai"] : undefined), ); mocks.resolveBundledProviderCompatPluginIds.mockImplementation( ({ onlyPluginIds }: { onlyPluginIds?: readonly string[] }) => @@ -170,9 +169,9 @@ describe("ensureSelectedAgentHarnessPlugin", () => { ); }); - it("widens a scoped harness allowlist with the provider owner for openai-codex models", async () => { + it("widens a scoped harness allowlist with the provider owner for openai models", async () => { await ensureSelectedAgentHarnessPlugin({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.5-pro", config: { plugins: { @@ -209,7 +208,7 @@ describe("ensureSelectedAgentHarnessPlugin", () => { mocks.resolveActivatableProviderOwnerPluginIds.mockReturnValueOnce([]); await ensureSelectedAgentHarnessPlugin({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.5-pro", config: { plugins: { @@ -243,7 +242,7 @@ describe("ensureSelectedAgentHarnessPlugin", () => { it("does not bypass a restrictive allowlist that omits the Codex harness", async () => { await ensureSelectedAgentHarnessPlugin({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.5-pro", config: { plugins: { diff --git a/src/agents/harness/selection.test.ts b/src/agents/harness/selection.test.ts index f826236ff57e..d63cd92b858c 100644 --- a/src/agents/harness/selection.test.ts +++ b/src/agents/harness/selection.test.ts @@ -309,7 +309,7 @@ describe("runAgentHarnessAttempt", () => { expect(agentRunAttempt).not.toHaveBeenCalled(); }); - it.each(["openai", "openai-codex"])( + it.each(["openai", "openai"])( "does not override forced Codex harness support rejection for %s", (provider) => { registerFailingCodexHarness(); diff --git a/src/agents/live-model-filter.test.ts b/src/agents/live-model-filter.test.ts index ce3315539e60..df06138bc789 100644 --- a/src/agents/live-model-filter.test.ts +++ b/src/agents/live-model-filter.test.ts @@ -5,7 +5,7 @@ import { } from "./live-model-filter.js"; function resolveProviderOwners(provider: string): readonly string[] | undefined { - if (provider === "openai" || provider === "openai-codex") { + if (provider === "openai") { return ["openai"]; } if (provider === "codex" || provider === "codex-cli") { @@ -66,7 +66,7 @@ describe("shouldExcludeProviderFromDefaultHighSignalLiveSweep", () => { ).toBe(false); expect( shouldExcludeProviderFromDefaultHighSignalLiveSweep({ - provider: "openai-codex", + provider: "openai", useExplicitModels: false, providerFilter: null, resolveProviderOwners, diff --git a/src/agents/live-model-filter.ts b/src/agents/live-model-filter.ts index b21423fb6119..6b31c01a44a2 100644 --- a/src/agents/live-model-filter.ts +++ b/src/agents/live-model-filter.ts @@ -139,9 +139,6 @@ function isOpenAiFamilyLiveModel(provider: string, id: string): boolean { } function isUnsupportedOpenAiLiveModelRef(provider: string, id: string): boolean { - if (provider === "openai-codex") { - return true; - } if (!isOpenAiFamilyLiveModel(provider, id)) { return false; } diff --git a/src/agents/live-model-switch.test.ts b/src/agents/live-model-switch.test.ts index b233f6dfef3a..667079a8f4ce 100644 --- a/src/agents/live-model-switch.test.ts +++ b/src/agents/live-model-switch.test.ts @@ -286,8 +286,8 @@ describe("live model switch", () => { it("strips duplicated provider prefixes from persisted overrides", async () => { state.loadSessionStoreMock.mockReturnValue({ main: { - providerOverride: "openai-codex", - modelOverride: "openai-codex/gpt-5.4", + providerOverride: "openai", + modelOverride: "openai/gpt-5.4", }, }); @@ -302,7 +302,7 @@ describe("live model switch", () => { defaultModel: "claude-opus-4-6", }), ).toEqual({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", authProfileId: undefined, authProfileIdSource: undefined, @@ -361,13 +361,13 @@ describe("live model switch", () => { expect(state.embeddedAgentModuleImported).toBe(false); }); - it("treats active openai-codex as an already-applied openai runtime promotion", async () => { + it("treats active openai as an already-applied openai runtime promotion", async () => { const { hasDifferentLiveSessionModelSelection } = await loadModule(); expect( hasDifferentLiveSessionModelSelection( { - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", }, { @@ -381,19 +381,6 @@ describe("live model switch", () => { it("does not suppress explicit runtime provider switches with the same model", async () => { const { hasDifferentLiveSessionModelSelection } = await loadModule(); - expect( - hasDifferentLiveSessionModelSelection( - { - provider: "openai", - model: "gpt-5.5", - }, - { - provider: "openai-codex", - model: "gpt-5.5", - }, - ), - ).toBe(true); - expect( hasDifferentLiveSessionModelSelection( { @@ -414,7 +401,7 @@ describe("live model switch", () => { expect( hasDifferentLiveSessionModelSelection( { - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", }, { @@ -551,7 +538,7 @@ describe("live model switch", () => { expect(result).toBeUndefined(); }); - it("does not trigger switch when runtime promotes openai to openai-codex", async () => { + it("does not trigger switch when runtime promotes openai to openai", async () => { const sessionEntry = { liveModelSwitchPending: true, providerOverride: "openai", @@ -563,7 +550,7 @@ describe("live model switch", () => { const result = shouldSwitchToLiveModel( makeShouldSwitchParams({ - currentProvider: "openai-codex", + currentProvider: "openai", currentModel: "gpt-5.5", defaultProvider: "openai", defaultModel: "gpt-5.5", diff --git a/src/agents/live-model-switch.ts b/src/agents/live-model-switch.ts index 61657f2e526d..6e6d88ca6bdf 100644 --- a/src/agents/live-model-switch.ts +++ b/src/agents/live-model-switch.ts @@ -18,7 +18,7 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import { normalizeOptionalString } from "../shared/string-coerce.js"; const OPENAI_PROVIDER_ID = "openai"; -const OPENAI_CODEX_PROVIDER_ID = "openai-codex"; +const OPENAI_CODEX_PROVIDER_ID = "openai"; export function resolveLiveSessionModelSelection(params: { cfg?: { session?: { store?: string } } | undefined; @@ -98,7 +98,7 @@ function isAlreadyAppliedOpenAICodexRuntimePromotion( current: { provider: string; model: string }, next: LiveSessionModelSelection, ): boolean { - // The embedded Codex runtime reports openai-codex after applying a canonical + // The embedded Codex runtime reports openai after applying a canonical // openai selection. Other runtime aliases remain real live-switch targets. return ( normalizeProviderId(current.provider) === OPENAI_CODEX_PROVIDER_ID && diff --git a/src/agents/live-test-helpers.test.ts b/src/agents/live-test-helpers.test.ts index 85d58872bb5a..aad030860297 100644 --- a/src/agents/live-test-helpers.test.ts +++ b/src/agents/live-test-helpers.test.ts @@ -28,15 +28,15 @@ describe("isLiveProfileKeyModeEnabled", () => { }); describe("live credential precedence", () => { - it("uses profile-first auth for Codex even when the global live mode is env-first", () => { - expect(resolveLiveCredentialPrecedence("openai-codex", false)).toBe("profile-first"); - expect(requiresLiveProfileCredential("openai-codex", false)).toBe(true); + it("uses profile-first auth for OpenAI even when the global live mode is env-first", () => { + expect(resolveLiveCredentialPrecedence("openai", false)).toBe("profile-first"); + expect(requiresLiveProfileCredential("openai", false)).toBe(true); }); it("keeps env-first auth for normal providers unless profile keys are required", () => { - expect(resolveLiveCredentialPrecedence("openai", false)).toBe("env-first"); - expect(resolveLiveCredentialPrecedence("openai", true)).toBe("profile-first"); - expect(requiresLiveProfileCredential("openai", false)).toBe(false); - expect(requiresLiveProfileCredential("openai", true)).toBe(true); + expect(resolveLiveCredentialPrecedence("anthropic", false)).toBe("env-first"); + expect(resolveLiveCredentialPrecedence("anthropic", true)).toBe("profile-first"); + expect(requiresLiveProfileCredential("anthropic", false)).toBe(false); + expect(requiresLiveProfileCredential("anthropic", true)).toBe(true); }); }); diff --git a/src/agents/live-test-helpers.ts b/src/agents/live-test-helpers.ts index 938d668dc8b6..d8aa0a766709 100644 --- a/src/agents/live-test-helpers.ts +++ b/src/agents/live-test-helpers.ts @@ -21,7 +21,7 @@ export function requiresLiveProfileCredential( provider: string, requireProfileKeys: boolean, ): boolean { - return requireProfileKeys || provider === "openai-codex"; + return requireProfileKeys || provider === "openai"; } export function resolveLiveCredentialPrecedence( diff --git a/src/agents/model-auth-label.test.ts b/src/agents/model-auth-label.test.ts index 42fca8b91cff..7581e0793595 100644 --- a/src/agents/model-auth-label.test.ts +++ b/src/agents/model-auth-label.test.ts @@ -127,9 +127,9 @@ describe("resolveModelAuthLabel", () => { mocks.ensureAuthProfileStore.mockReturnValue({ version: 1, profiles: { - "openai-codex:user@example.com": { + "openai:user@example.com": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -137,9 +137,9 @@ describe("resolveModelAuthLabel", () => { }, } as never); mocks.resolveAuthProfileOrder.mockImplementation(({ provider }: { provider?: string }) => - provider === "openai-codex" ? ["openai-codex:user@example.com"] : [], + provider === "openai" ? ["openai:user@example.com"] : [], ); - mocks.resolveAuthProfileDisplayLabel.mockReturnValue("openai-codex:user@example.com"); + mocks.resolveAuthProfileDisplayLabel.mockReturnValue("openai:user@example.com"); mocks.resolveEnvApiKey.mockReturnValue({ apiKey: "env-key-placeholder", source: "env: OPENAI_API_KEY", @@ -147,11 +147,11 @@ describe("resolveModelAuthLabel", () => { const label = resolveModelAuthLabel({ provider: "openai", - acceptedProviderIds: ["openai-codex"], + acceptedProviderIds: ["openai"], cfg: {}, }); - expect(label).toBe("oauth (openai-codex:user@example.com)"); + expect(label).toBe("oauth (openai:user@example.com)"); expect(mocks.resolveEnvApiKey).not.toHaveBeenCalled(); }); @@ -163,7 +163,7 @@ describe("resolveModelAuthLabel", () => { mocks.resolveAuthProfileOrder.mockReturnValue([]); mocks.readCodexCliCredentialsCached.mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "token", refresh: "refresh", expires: Date.now() + 60_000, diff --git a/src/agents/model-auth.profiles.test.ts b/src/agents/model-auth.profiles.test.ts index 5d6edf89d267..ac68b105e480 100644 --- a/src/agents/model-auth.profiles.test.ts +++ b/src/agents/model-auth.profiles.test.ts @@ -197,7 +197,7 @@ vi.mock("../plugins/provider-runtime.js", () => ({ provider: string; context: { listProfileIds: (providerId: string) => string[] }; }) => { - if (params.provider === "openai" && params.context.listProfileIds("openai-codex").length > 0) { + if (params.provider === "openai" && params.context.listProfileIds("openai").length > 0) { return 'No API key found for provider "openai". Use openai/gpt-5.5.'; } return undefined; @@ -408,9 +408,9 @@ describe("getApiKeyForModel", () => { await state.writeAuthProfiles({ version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", ...oauthFixture, }, }, @@ -418,8 +418,8 @@ describe("getApiKeyForModel", () => { const model = { id: "codex-mini-latest", - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", } as Model; const store = ensureAuthProfileStore(process.env.OPENCLAW_AGENT_DIR, { @@ -427,7 +427,7 @@ describe("getApiKeyForModel", () => { }); const apiKey = await getApiKeyForModel({ model, - profileId: "openai-codex:default", + profileId: "openai:default", store, agentDir: process.env.OPENCLAW_AGENT_DIR, }); @@ -465,7 +465,7 @@ describe("getApiKeyForModel", () => { model: { id: "gpt-5.5", provider: "openai", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", } as Model, store, }); @@ -596,7 +596,7 @@ describe("getApiKeyForModel", () => { ); }); - it("suggests openai-codex when only Codex OAuth is configured", async () => { + it("uses OpenAI OAuth when it is configured for the provider", async () => { await withOpenClawTestState( { layout: "state-only", @@ -610,21 +610,21 @@ describe("getApiKeyForModel", () => { await state.writeAuthProfiles({ version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", ...oauthFixture, }, }, }); - let error: unknown = null; - try { - await resolveApiKeyForProvider({ provider: "openai" }); - } catch (err) { - error = err; - } - expect(String(error)).toContain("openai/gpt-5.5"); + const resolved = await resolveApiKeyForProvider({ provider: "openai" }); + + expect(resolved).toMatchObject({ + apiKey: oauthFixture.access, + mode: "oauth", + profileId: "openai:default", + }); }, ); }); diff --git a/src/agents/model-auth.test.ts b/src/agents/model-auth.test.ts index 7de2b857f0e1..23d31bc733ec 100644 --- a/src/agents/model-auth.test.ts +++ b/src/agents/model-auth.test.ts @@ -366,7 +366,7 @@ describe("resolveModelAuthMode", () => { .spyOn(cliCredentials, "readCodexCliCredentialsCached") .mockReturnValue({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "token", refresh: "refresh", expires: Date.now() + 60_000, diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 1d302337d7a0..d848d622e98d 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -74,7 +74,7 @@ export type RuntimeProviderAuthLookup = { const log = createSubsystemLogger("model-auth"); const OPENAI_PROVIDER_ID = "openai"; -const OPENAI_CODEX_RESPONSES_API = "openai-codex-responses"; +const OPENAI_CODEX_RESPONSES_API = "openai-chatgpt-responses"; function directOpenAIPlatformModelRequiresApiKey(params: { provider: string; diff --git a/src/agents/model-catalog-visibility.test.ts b/src/agents/model-catalog-visibility.test.ts index adb2445ce27e..d31a4ab21ed9 100644 --- a/src/agents/model-catalog-visibility.test.ts +++ b/src/agents/model-catalog-visibility.test.ts @@ -38,7 +38,9 @@ describe("resolveVisibleModelCatalog", () => { }); it("keeps Codex-routable canonical OpenAI rows visible through Codex OAuth auth", async () => { - const authChecker = vi.fn((provider: string, api?: string) => api === "openai-codex-responses"); + const authChecker = vi.fn( + (provider: string, api?: string) => api === "openai-chatgpt-responses", + ); const catalog: ModelCatalogEntry[] = [ { provider: "openai", @@ -64,7 +66,7 @@ describe("resolveVisibleModelCatalog", () => { expect(authChecker).toHaveBeenNthCalledWith(1, "openai", "openai-responses"); expect(authChecker).toHaveBeenNthCalledWith(2, "openai", "openai-responses"); - expect(authChecker).toHaveBeenNthCalledWith(3, "openai", "openai-codex-responses"); + expect(authChecker).toHaveBeenNthCalledWith(3, "openai", "openai-chatgpt-responses"); expect(authChecker).toHaveBeenCalledTimes(3); expect(result).toEqual([ { @@ -98,7 +100,7 @@ describe("resolveVisibleModelCatalog", () => { const authChecker = vi.fn((provider: string) => provider !== "blocked"); const catalog: ModelCatalogEntry[] = [ { provider: "anthropic", id: "claude-test", name: "Claude Test" }, - { provider: "openai-codex", id: "gpt-codex-test", name: "GPT Codex Test" }, + { provider: "openai", id: "gpt-codex-test", name: "GPT Codex Test" }, { provider: "vllm", id: "qwen-local", name: "Qwen Local" }, { provider: "blocked", id: "blocked-test", name: "Blocked Test" }, ]; @@ -108,7 +110,7 @@ describe("resolveVisibleModelCatalog", () => { defaults: { models: { "vllm/*": {}, - "openai-codex/*": {}, + "openai/*": {}, "blocked/*": {}, }, }, @@ -124,12 +126,12 @@ describe("resolveVisibleModelCatalog", () => { }); expect(authChecker).toHaveBeenNthCalledWith(1, "anthropic"); - expect(authChecker).toHaveBeenNthCalledWith(2, "openai-codex"); + expect(authChecker).toHaveBeenNthCalledWith(2, "openai"); expect(authChecker).toHaveBeenNthCalledWith(3, "vllm"); expect(authChecker).toHaveBeenNthCalledWith(4, "blocked"); expect(authChecker).toHaveBeenCalledTimes(4); expect(result).toEqual([ - { provider: "openai-codex", id: "gpt-codex-test", name: "GPT Codex Test" }, + { provider: "openai", id: "gpt-codex-test", name: "GPT Codex Test" }, { provider: "vllm", id: "qwen-local", name: "Qwen Local" }, ]); expect(normalizeProviderModelIdWithRuntimeMock).not.toHaveBeenCalled(); diff --git a/src/agents/model-catalog-visibility.ts b/src/agents/model-catalog-visibility.ts index f56d75c2a70c..49232ffc674c 100644 --- a/src/agents/model-catalog-visibility.ts +++ b/src/agents/model-catalog-visibility.ts @@ -11,7 +11,7 @@ import { type ModelCatalogVisibilityView = "default" | "configured" | "all"; type ProviderAuthChecker = (provider: string, modelApi?: string) => boolean | Promise; const OPENAI_PROVIDER_ID = "openai"; -const OPENAI_CODEX_RESPONSES_API = "openai-codex-responses"; +const OPENAI_CODEX_RESPONSES_API = "openai-chatgpt-responses"; const OPENAI_CODEX_ROUTABLE_MODEL_IDS = new Set([ "gpt-5.5", "gpt-5.5-pro", diff --git a/src/agents/model-catalog.test.ts b/src/agents/model-catalog.test.ts index 67b7d351adb7..f0a2372f1277 100644 --- a/src/agents/model-catalog.test.ts +++ b/src/agents/model-catalog.test.ts @@ -32,9 +32,7 @@ function isSuppressedModel(provider?: string, id?: string): boolean { return false; } return ( - (provider === "openai" || - provider === "azure-openai-responses" || - provider === "openai-codex") && + (provider === "openai" || provider === "azure-openai-responses" || provider === "openai") && modelId === "gpt-5.3-codex-spark" ); } @@ -495,7 +493,7 @@ describe("loadModelCatalog", () => { readFileMock.mockResolvedValueOnce( JSON.stringify({ providers: { - "openai-codex": { + openai: { models: [ { id: "gpt-5.3-codex-spark", @@ -513,14 +511,6 @@ describe("loadModelCatalog", () => { }, ], }, - openai: { - models: [ - { - id: "gpt-5.3-codex-spark", - name: "GPT-5.3 Codex Spark", - }, - ], - }, }, }), ); @@ -529,7 +519,7 @@ describe("loadModelCatalog", () => { expect(result).toEqual([ { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", name: "GPT-5.4", reasoning: true, @@ -911,11 +901,11 @@ describe("loadModelCatalog", () => { expect(augmentCatalogMock).not.toHaveBeenCalled(); }); - it("does not synthesize stale openai-codex/gpt-5.3-codex-spark entries from gpt-5.4", async () => { + it("does not synthesize stale openai/gpt-5.3-codex-spark entries from gpt-5.4", async () => { mockAgentDiscoveryModels([ { id: "gpt-5.4", - provider: "openai-codex", + provider: "openai", name: "GPT-5.3 Codex", reasoning: true, contextWindow: 200000, @@ -923,14 +913,14 @@ describe("loadModelCatalog", () => { }, { id: "gpt-5.2-codex", - provider: "openai-codex", + provider: "openai", name: "GPT-5.2 Codex", }, ]); const result = await loadModelCatalog({ config: {} as OpenClawConfig }); - expectNoCatalogEntry(result, "openai-codex", "gpt-5.3-codex-spark"); - const entry = requireCatalogEntry(result, "openai-codex", "gpt-5.4"); + expectNoCatalogEntry(result, "openai", "gpt-5.3-codex-spark"); + const entry = requireCatalogEntry(result, "openai", "gpt-5.4"); expect(entry.name).toBe("GPT-5.3 Codex"); }); @@ -954,7 +944,7 @@ describe("loadModelCatalog", () => { }, { id: "gpt-5.3-codex-spark", - provider: "openai-codex", + provider: "openai", name: "GPT-5.3 Codex Spark", reasoning: true, contextWindow: 128000, @@ -965,14 +955,14 @@ describe("loadModelCatalog", () => { const result = await loadModelCatalog({ config: {} as OpenClawConfig }); expectNoCatalogEntry(result, "openai", "gpt-5.3-codex-spark"); expectNoCatalogEntry(result, "azure-openai-responses", "gpt-5.3-codex-spark"); - expectNoCatalogEntry(result, "openai-codex", "gpt-5.3-codex-spark"); + expectNoCatalogEntry(result, "openai", "gpt-5.3-codex-spark"); }); - it("keeps available openai-codex 5.1/5.2/5.3 built-ins in the catalog", async () => { + it("keeps available openai 5.1/5.2/5.3 built-ins in the catalog", async () => { mockAgentDiscoveryModels([ { id: "gpt-5.1-codex-mini", - provider: "openai-codex", + provider: "openai", name: "GPT-5.1 Codex Mini", reasoning: true, contextWindow: 400000, @@ -980,7 +970,7 @@ describe("loadModelCatalog", () => { }, { id: "gpt-5.2-codex", - provider: "openai-codex", + provider: "openai", name: "GPT-5.2 Codex", reasoning: true, contextWindow: 400000, @@ -988,7 +978,7 @@ describe("loadModelCatalog", () => { }, { id: "gpt-5.3-codex", - provider: "openai-codex", + provider: "openai", name: "GPT-5.3 Codex", reasoning: true, contextWindow: 400000, @@ -996,7 +986,7 @@ describe("loadModelCatalog", () => { }, { id: "gpt-5.5", - provider: "openai-codex", + provider: "openai", name: "GPT-5.5", reasoning: true, contextWindow: 400000, @@ -1005,15 +995,15 @@ describe("loadModelCatalog", () => { ]); const result = await loadModelCatalog({ config: {} as OpenClawConfig }); - expect(requireCatalogEntry(result, "openai-codex", "gpt-5.1-codex-mini").name).toBe( + expect(requireCatalogEntry(result, "openai", "gpt-5.1-codex-mini").name).toBe( "GPT-5.1 Codex Mini", ); - expect(requireCatalogEntry(result, "openai-codex", "gpt-5.2-codex").name).toBe("GPT-5.2 Codex"); - expect(requireCatalogEntry(result, "openai-codex", "gpt-5.3-codex").name).toBe("GPT-5.3 Codex"); - expect(requireCatalogEntry(result, "openai-codex", "gpt-5.5").name).toBe("GPT-5.5"); + expect(requireCatalogEntry(result, "openai", "gpt-5.2-codex").name).toBe("GPT-5.2 Codex"); + expect(requireCatalogEntry(result, "openai", "gpt-5.3-codex").name).toBe("GPT-5.3 Codex"); + expect(requireCatalogEntry(result, "openai", "gpt-5.5").name).toBe("GPT-5.5"); }); - it("does not synthesize gpt-5.4 OpenAI forward-compat entries from template models", async () => { + it("keeps OpenAI forward-compat entries on the unified provider", async () => { mockAgentDiscoveryModels([ { id: "gpt-5.2", @@ -1049,7 +1039,7 @@ describe("loadModelCatalog", () => { }, { id: "gpt-5.4", - provider: "openai-codex", + provider: "openai", name: "GPT-5.3 Codex", reasoning: true, contextWindow: 272000, @@ -1059,14 +1049,11 @@ describe("loadModelCatalog", () => { const result = await loadModelCatalog({ config: {} as OpenClawConfig }); - expect( - result.some((entry) => entry.provider === "openai" && entry.id.startsWith("gpt-5.4")), - ).toBe(false); - const entry = requireCatalogEntry(result, "openai-codex", "gpt-5.4"); + const entry = requireCatalogEntry(result, "openai", "gpt-5.4"); expect(entry.name).toBe("GPT-5.3 Codex"); - expect( - result.some((entry) => entry.provider === "openai-codex" && entry.id === "gpt-5.4-mini"), - ).toBe(false); + expect(result.some((entry) => entry.provider === "openai" && entry.id === "gpt-5.4-mini")).toBe( + false, + ); }); it("merges provider-owned supplemental catalog entries", async () => { diff --git a/src/agents/model-compat.test.ts b/src/agents/model-compat.test.ts index 3df8f504cf3b..dca05f4e37b2 100644 --- a/src/agents/model-compat.test.ts +++ b/src/agents/model-compat.test.ts @@ -386,7 +386,7 @@ describe("isModernModelRef", () => { context.modelId, ) ? true - : provider === "openai-codex" && + : provider === "openai" && ["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini"].includes( context.modelId, ) @@ -404,11 +404,11 @@ describe("isModernModelRef", () => { expect(isModernModelRef({ provider: "openai", id: "gpt-5.4-pro" })).toBe(true); expect(isModernModelRef({ provider: "openai", id: "gpt-5.4-mini" })).toBe(true); expect(isModernModelRef({ provider: "openai", id: "gpt-5.4-nano" })).toBe(true); - expect(isModernModelRef({ provider: "openai-codex", id: "gpt-5.5" })).toBe(true); - expect(isModernModelRef({ provider: "openai-codex", id: "gpt-5.5-pro" })).toBe(true); - expect(isModernModelRef({ provider: "openai-codex", id: "gpt-5.4" })).toBe(true); - expect(isModernModelRef({ provider: "openai-codex", id: "gpt-5.4-pro" })).toBe(true); - expect(isModernModelRef({ provider: "openai-codex", id: "gpt-5.4-mini" })).toBe(true); + expect(isModernModelRef({ provider: "openai", id: "gpt-5.5" })).toBe(true); + expect(isModernModelRef({ provider: "openai", id: "gpt-5.5-pro" })).toBe(true); + expect(isModernModelRef({ provider: "openai", id: "gpt-5.4" })).toBe(true); + expect(isModernModelRef({ provider: "openai", id: "gpt-5.4-pro" })).toBe(true); + expect(isModernModelRef({ provider: "openai", id: "gpt-5.4-mini" })).toBe(true); expect(isModernModelRef({ provider: "opencode", id: "claude-opus-4-6" })).toBe(true); expect(isModernModelRef({ provider: "opencode", id: "gemini-3-pro" })).toBe(true); expect(isModernModelRef({ provider: "opencode-go", id: "kimi-k2.5" })).toBe(true); @@ -508,9 +508,7 @@ describe("isHighSignalLiveModelRef", () => { false, ); expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5.2" })).toBe(false); - expect(isHighSignalLiveModelRef({ provider: "openai-codex", id: "gpt-5.5" })).toBe(false); - expect(isHighSignalLiveModelRef({ provider: "openai-codex", id: "gpt-5.2" })).toBe(false); - expect(isHighSignalLiveModelRef({ provider: "openai-codex", id: "gpt-5.2-codex" })).toBe(false); + expect(isHighSignalLiveModelRef({ provider: "openai", id: "gpt-5.2-codex" })).toBe(false); expect(isHighSignalLiveModelRef({ provider: "openrouter", id: "openai/gpt-5.2-chat" })).toBe( true, ); diff --git a/src/agents/model-fallback.run-embedded.e2e.test.ts b/src/agents/model-fallback.run-embedded.e2e.test.ts index 66c9bc842ad4..804b5c8c63c0 100644 --- a/src/agents/model-fallback.run-embedded.e2e.test.ts +++ b/src/agents/model-fallback.run-embedded.e2e.test.ts @@ -408,7 +408,7 @@ describe("runWithModelFallback + runEmbeddedAgent failover behavior", () => { expect(result.meta.toolSummary?.tools).toEqual(["write"]); expect( classifyEmbeddedAgentRunResultForModelFallback({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", result, }), diff --git a/src/agents/model-fallback.test.ts b/src/agents/model-fallback.test.ts index 9519e80a013d..e5437f6bf3e0 100644 --- a/src/agents/model-fallback.test.ts +++ b/src/agents/model-fallback.test.ts @@ -1120,7 +1120,7 @@ describe("runWithModelFallback", () => { const rawError = "You exceeded your current OpenAI quota."; const run = vi.fn().mockRejectedValue( new FailoverError("LLM request timed out.", { - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", reason: "timeout", status: 408, @@ -1184,7 +1184,7 @@ describe("runWithModelFallback", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.4", + primary: "openai/gpt-5.4", fallbacks: ["anthropic/claude-haiku-3-5"], }, }, @@ -1208,7 +1208,7 @@ describe("runWithModelFallback", () => { const result = await runWithModelFallback({ cfg, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", run, classifyResult, @@ -1217,7 +1217,7 @@ describe("runWithModelFallback", () => { expect(result.result).toEqual({ payloads: [{ text: "fallback ok" }] }); expect(run).toHaveBeenCalledTimes(2); expect(requireMockCall(run, 1, "fallback run")).toEqual(["anthropic", "claude-haiku-3-5"]); - expect(result.attempts[0]?.provider).toBe("openai-codex"); + expect(result.attempts[0]?.provider).toBe("openai"); expect(result.attempts[0]?.model).toBe("gpt-5.4"); expect(result.attempts[0]?.reason).toBe("format"); expect(result.attempts[0]?.code).toBe("empty_result"); @@ -1277,7 +1277,7 @@ describe("runWithModelFallback", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.4", + primary: "openai/gpt-5.4", fallbacks: [], }, }, @@ -1289,7 +1289,7 @@ describe("runWithModelFallback", () => { await captureRejection( runWithModelFallback({ cfg, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", run, classifyResult: ({ result }) => { @@ -1307,20 +1307,20 @@ describe("runWithModelFallback", () => { ); expect(error.name).toBe("FailoverError"); expect(error.reason).toBe("format"); - expect(error.provider).toBe("openai-codex"); + expect(error.provider).toBe("openai"); expect(error.model).toBe("gpt-5.4"); expect(error.code).toBe("empty_result"); expect(run).toHaveBeenCalledTimes(1); }); it("does not classify successful results when the optional classifier returns null", async () => { - const cfg = makeProviderFallbackCfg("openai-codex"); + const cfg = makeProviderFallbackCfg("openai"); const run = vi.fn().mockResolvedValueOnce({ payloads: [{ text: "ok" }] }); const classifyResult = vi.fn(() => null); const result = await runWithModelFallback({ cfg, - provider: "openai-codex", + provider: "openai", model: "m1", run, classifyResult, @@ -1345,7 +1345,7 @@ describe("runWithModelFallback", () => { expect( classifyEmbeddedAgentRunResultForModelFallback({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", result: runResult, }), @@ -1363,7 +1363,7 @@ describe("runWithModelFallback", () => { expect( classifyEmbeddedAgentRunResultForModelFallback({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", result: runResult, }), @@ -2341,7 +2341,7 @@ describe("runWithModelFallback", () => { }); it("does not fall back when a timed-out caller abort is classified from the result", async () => { - const cfg = makeProviderFallbackCfg("openai-codex"); + const cfg = makeProviderFallbackCfg("openai"); const timeoutReason = new Error("chat run timed out"); timeoutReason.name = "TimeoutError"; const controller = new AbortController(); @@ -2359,7 +2359,7 @@ describe("runWithModelFallback", () => { await expect( runWithModelFallback({ cfg, - provider: "openai-codex", + provider: "openai", model: "m1", abortSignal: controller.signal, run, @@ -2767,14 +2767,14 @@ describe("runWithImageModelFallback", () => { agents: { defaults: { imageModel: { - primary: "openai-codex/gpt-5.4", - fallbacks: ["openai-codex/gpt-5.4-mini"], + primary: "openai/gpt-5.4", + fallbacks: ["openai/gpt-5.4-mini"], }, }, }, }), modelOverride: "gpt-5.4-mini", - expected: [["openai-codex", "gpt-5.4-mini"]], + expected: [["openai", "gpt-5.4-mini"]], }, { name: "qualified override keeps provider", @@ -2782,7 +2782,7 @@ describe("runWithImageModelFallback", () => { agents: { defaults: { imageModel: { - primary: "openai-codex/gpt-5.4", + primary: "openai/gpt-5.4", }, }, }, diff --git a/src/agents/model-runtime-aliases.ts b/src/agents/model-runtime-aliases.ts index 8ff6f73c63bd..78352e306179 100644 --- a/src/agents/model-runtime-aliases.ts +++ b/src/agents/model-runtime-aliases.ts @@ -10,7 +10,7 @@ import { import { resolveModelRuntimePolicy } from "./model-runtime-policy.js"; import { resolveProviderIdForAuth } from "./provider-auth-aliases.js"; -const RUNTIME_COMPARISON_PROVIDER_ALIASES = new Map([["openai-codex", "openai"]]); +const RUNTIME_COMPARISON_PROVIDER_ALIASES = new Map([["openai", "openai"]]); /** True for CLI runtime provider ids such as `claude-cli` and `google-gemini-cli`. */ export function isCliRuntimeProvider( diff --git a/src/agents/model-selection.test.ts b/src/agents/model-selection.test.ts index 71a10005d078..c98fe91fcaea 100644 --- a/src/agents/model-selection.test.ts +++ b/src/agents/model-selection.test.ts @@ -294,7 +294,7 @@ describe("model-selection", () => { describe("normalizeProviderIdForAuth", () => { it("only applies lowercase provider-id normalization before auth alias lookup", () => { expect(normalizeProviderIdForAuth("qwencloud")).toBe("qwencloud"); - expect(normalizeProviderIdForAuth("openai-codex")).toBe("openai-codex"); + expect(normalizeProviderIdForAuth("openai")).toBe("openai"); expect(normalizeProviderIdForAuth("openai")).toBe("openai"); }); }); @@ -588,7 +588,7 @@ describe("model-selection", () => { expect( resolvePersistedSelectedModelRef({ defaultProvider: "anthropic", - runtimeProvider: "openai-codex", + runtimeProvider: "openai", runtimeModel: "gpt-5.4", overrideProvider: "anthropic", overrideModel: "claude-opus-4-6", @@ -1122,7 +1122,7 @@ describe("model-selection", () => { agents: { defaults: { models: { - "openai-codex/*": {}, + "openai/*": {}, "vllm/*": {}, }, }, @@ -1133,8 +1133,8 @@ describe("model-selection", () => { cfg, catalog: [ { provider: "anthropic", id: "claude-sonnet-4-6", name: "Claude Sonnet" }, - { provider: "openai-codex", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, - { provider: "openai-codex", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, + { provider: "openai", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, + { provider: "openai", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, { provider: "vllm", id: "qwen3-local", name: "Qwen3 Local" }, { provider: "vllm", id: "local-added-after-startup", name: "Local Added After Startup" }, ], @@ -1144,13 +1144,13 @@ describe("model-selection", () => { expect(result.allowAny).toBe(false); expect(result.allowedCatalog).toEqual([ - { provider: "openai-codex", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, - { provider: "openai-codex", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, + { provider: "openai", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, + { provider: "openai", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, { provider: "vllm", id: "qwen3-local", name: "Qwen3 Local" }, { provider: "vllm", id: "local-added-after-startup", name: "Local Added After Startup" }, ]); - expect(result.allowedKeys.has("openai-codex/gpt-5.4-codex")).toBe(true); - expect(result.allowedKeys.has("openai-codex/gpt-5.5-codex")).toBe(true); + expect(result.allowedKeys.has("openai/gpt-5.4-codex")).toBe(true); + expect(result.allowedKeys.has("openai/gpt-5.5-codex")).toBe(true); expect(result.allowedKeys.has("vllm/local-added-after-startup")).toBe(true); expect(result.allowedKeys.has("anthropic/claude-sonnet-4-6")).toBe(false); }); @@ -1160,7 +1160,7 @@ describe("model-selection", () => { agents: { defaults: { models: { - "openai-codex/*": {}, + "openai/*": {}, }, }, }, @@ -1175,8 +1175,8 @@ describe("model-selection", () => { expect(result.allowAny).toBe(false); expect(result.allowedCatalog).toEqual([]); - expect(result.allowedKeys.has(providerWildcardModelKey("openai-codex"))).toBe(true); - expect(isModelKeyAllowedBySet(result.allowedKeys, "openai-codex/gpt-added-later")).toBe(true); + expect(result.allowedKeys.has(providerWildcardModelKey("openai"))).toBe(true); + expect(isModelKeyAllowedBySet(result.allowedKeys, "openai/gpt-added-later")).toBe(true); expect(isModelKeyAllowedBySet(result.allowedKeys, "anthropic/claude-sonnet-4-6")).toBe(false); }); @@ -1185,7 +1185,7 @@ describe("model-selection", () => { agents: { defaults: { models: { - "openai-codex/*": {}, + "openai/*": {}, "anthropic/claude-sonnet-4-6": {}, }, }, @@ -1196,7 +1196,7 @@ describe("model-selection", () => { cfg, catalog: [ { provider: "anthropic", id: "claude-sonnet-4-6", name: "Claude Sonnet" }, - { provider: "openai-codex", id: "gpt-added-later", name: "GPT Added Later" }, + { provider: "openai", id: "gpt-added-later", name: "GPT Added Later" }, { provider: "vllm", id: "qwen-local", name: "Qwen Local" }, ], defaultProvider: "anthropic", @@ -1204,18 +1204,18 @@ describe("model-selection", () => { }); expect(policy.hasProviderWildcards).toBe(true); - expect(policy.allows({ provider: "openai-codex", model: "future-model" })).toBe(true); + expect(policy.allows({ provider: "openai", model: "future-model" })).toBe(true); expect(policy.allows({ provider: "vllm", model: "qwen-local" })).toBe(false); expect( policy.visibleCatalog({ catalog: [], defaultVisibleCatalog: [ - { provider: "openai-codex", id: "gpt-added-later", name: "GPT Added Later" }, + { provider: "openai", id: "gpt-added-later", name: "GPT Added Later" }, { provider: "vllm", id: "qwen-local", name: "Qwen Local" }, ], }), ).toEqual([ - { provider: "openai-codex", id: "gpt-added-later", name: "GPT Added Later" }, + { provider: "openai", id: "gpt-added-later", name: "GPT Added Later" }, { provider: "anthropic", id: "claude-sonnet-4-6", name: "Claude Sonnet" }, ]); }); @@ -1255,7 +1255,7 @@ describe("model-selection", () => { agents: { defaults: { models: { - "openai-codex/*": {}, + "openai/*": {}, "google/gemini-test": {}, }, }, @@ -1266,7 +1266,7 @@ describe("model-selection", () => { cfg, catalog: [ { provider: "anthropic", id: "claude-sonnet-4-6", name: "Claude Sonnet" }, - { provider: "openai-codex", id: "gpt-codex", name: "GPT Codex" }, + { provider: "openai", id: "gpt-codex", name: "GPT Codex" }, { provider: "google", id: "gemini-test", name: "Gemini Test" }, ], defaultProvider: "anthropic", @@ -1274,11 +1274,11 @@ describe("model-selection", () => { }); expect(result.allowedCatalog).toEqual([ - { provider: "openai-codex", id: "gpt-codex", name: "GPT Codex" }, + { provider: "openai", id: "gpt-codex", name: "GPT Codex" }, { provider: "google", id: "gemini-test", name: "Gemini Test" }, ]); expect(result.allowedKeys.has("anthropic/claude-sonnet-4-6")).toBe(false); - expect(result.allowedKeys.has(providerWildcardModelKey("openai-codex"))).toBe(true); + expect(result.allowedKeys.has(providerWildcardModelKey("openai"))).toBe(true); }); it("unions exact model entries with provider wildcard entries", () => { @@ -1287,7 +1287,7 @@ describe("model-selection", () => { defaults: { models: { "anthropic/claude-sonnet-4-6": {}, - "openai-codex/*": {}, + "openai/*": {}, }, }, }, @@ -1297,8 +1297,8 @@ describe("model-selection", () => { cfg, catalog: [ { provider: "anthropic", id: "claude-sonnet-4-6", name: "Claude Sonnet" }, - { provider: "openai-codex", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, - { provider: "openai-codex", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, + { provider: "openai", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, + { provider: "openai", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, { provider: "vllm", id: "qwen-local", name: "Qwen Local" }, ], defaultProvider: "anthropic", @@ -1307,10 +1307,10 @@ describe("model-selection", () => { expect(result.allowAny).toBe(false); expect(result.allowedCatalog).toEqual([ { provider: "anthropic", id: "claude-sonnet-4-6", name: "Claude Sonnet" }, - { provider: "openai-codex", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, - { provider: "openai-codex", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, + { provider: "openai", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, + { provider: "openai", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, ]); - expect(result.allowedKeys.has("openai-codex/gpt-5.5-codex")).toBe(true); + expect(result.allowedKeys.has("openai/gpt-5.5-codex")).toBe(true); expect(result.allowedKeys.has("vllm/qwen-local")).toBe(false); }); @@ -1519,7 +1519,7 @@ describe("model-selection", () => { agents: { defaults: { models: { - "openai-codex/gpt-5.4": {}, + "openai/gpt-5.4": {}, "opencode-go/kimi-k2.6": {}, "opencode-go/glm-5": {}, }, @@ -1527,13 +1527,13 @@ describe("model-selection", () => { }, } as OpenClawConfig; - // When session default is openai-codex, switching to a bare "kimi-k2.6" - // should resolve to opencode-go/kimi-k2.6, not openai-codex/kimi-k2.6 + // When session default is openai, switching to a bare "kimi-k2.6" + // should resolve to opencode-go/kimi-k2.6, not openai/kimi-k2.6 const result = resolveAllowedModelRef({ cfg, catalog: [], raw: "kimi-k2.6", - defaultProvider: "openai-codex", // session's current provider + defaultProvider: "openai", // session's current provider }); expect(result).toEqual({ @@ -2665,7 +2665,7 @@ describe("resolveDefaultModelForAgent", () => { { id: "main", model: { - primary: "openai-codex/gpt-5.5", + primary: "openai/gpt-5.5", }, }, ], @@ -2673,7 +2673,7 @@ describe("resolveDefaultModelForAgent", () => { } as OpenClawConfig; expect(resolveDefaultModelForAgent({ cfg, agentId: "main" })).toEqual({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", }); }); diff --git a/src/agents/models-config.providers.live-filter.test.ts b/src/agents/models-config.providers.live-filter.test.ts index 81e3fabca40f..2a18ab66ae0a 100644 --- a/src/agents/models-config.providers.live-filter.test.ts +++ b/src/agents/models-config.providers.live-filter.test.ts @@ -150,17 +150,17 @@ describe("resolveProviderDiscoveryFilterForTest", () => { ).toEqual(["openai"]); }); - it("maps scoped startup provider aliases through model catalog owners", () => { + it("maps scoped startup provider ids through model catalog owners", () => { const snapshot = { owners: metadataOwners({ - modelCatalogProviders: new Map([["openai-codex", ["codex"]]]), + modelCatalogProviders: new Map([["openai", ["codex"]]]), }), }; expect( resolveProviderDiscoveryFilterForTest({ env: liveFilterEnv({}), - providerIds: ["OpenAI-Codex"], + providerIds: ["OpenAI"], resolveOwners: (provider) => resolvePluginMetadataProviderOwnersForTest(snapshot, provider), }), ).toEqual(["codex"]); diff --git a/src/agents/models.profiles.live.test.ts b/src/agents/models.profiles.live.test.ts index 7fdf8ac61fb2..6d85e44c1621 100644 --- a/src/agents/models.profiles.live.test.ts +++ b/src/agents/models.profiles.live.test.ts @@ -553,7 +553,7 @@ function resolveTestReasoning( if (model.provider === "xai" && id.startsWith("grok-4")) { return undefined; } - if (model.provider === "openai" || model.provider === "openai-codex") { + if (model.provider === "openai") { if (id.includes("pro")) { return "high"; } @@ -563,17 +563,17 @@ function resolveTestReasoning( } function resolveLiveSystemPrompt(model: Model): string | undefined { - if (model.provider === "openai-codex") { + if (model.provider === "openai") { return "You are a concise assistant. Follow the user's instruction exactly."; } return undefined; } describe("resolveLiveSystemPrompt", () => { - it("adds instructions for openai-codex probes", () => { + it("adds instructions for openai probes", () => { expect( resolveLiveSystemPrompt({ - provider: "openai-codex", + provider: "openai", } as Model), ).toContain("Follow the user's instruction exactly."); }); @@ -1350,7 +1350,7 @@ describeLive("live models (profile keys)", () => { (model.provider === "fireworks" || model.provider === "google-antigravity" || model.provider === "minimax" || - model.provider === "openai-codex" || + model.provider === "openai" || model.provider === "xai" || model.provider === "zai") ) { @@ -1431,18 +1431,14 @@ describeLive("live models (profile keys)", () => { logProgress(`${progressLabel}: skip (rate limit)`); break; } - if ( - allowNotFoundSkip && - model.provider === "openai-codex" && - isRefreshTokenReused(message) - ) { + if (allowNotFoundSkip && model.provider === "openai" && isRefreshTokenReused(message)) { skipped.push({ model: id, reason: message }); logProgress(`${progressLabel}: skip (codex refresh token reused)`); break; } if ( allowNotFoundSkip && - model.provider === "openai-codex" && + model.provider === "openai" && isAccountIdExtractionError(message) ) { skipped.push({ model: id, reason: message }); @@ -1451,7 +1447,7 @@ describeLive("live models (profile keys)", () => { } if ( allowNotFoundSkip && - model.provider === "openai-codex" && + model.provider === "openai" && isChatGPTUsageLimitErrorMessage(message) ) { skipped.push({ model: id, reason: message }); @@ -1460,7 +1456,7 @@ describeLive("live models (profile keys)", () => { } if ( allowNotFoundSkip && - model.provider === "openai-codex" && + model.provider === "openai" && isInstructionsRequiredError(message) ) { skipped.push({ model: id, reason: message }); @@ -1469,7 +1465,7 @@ describeLive("live models (profile keys)", () => { } if ( allowNotFoundSkip && - model.provider === "openai-codex" && + model.provider === "openai" && isOpenAiCodexHtmlInterruption(message) ) { skipped.push({ model: id, reason: message }); diff --git a/src/agents/openai-reasoning-compat.live.test.ts b/src/agents/openai-reasoning-compat.live.test.ts index 079e72a43a60..20f6d184ffdf 100644 --- a/src/agents/openai-reasoning-compat.live.test.ts +++ b/src/agents/openai-reasoning-compat.live.test.ts @@ -20,7 +20,7 @@ import { ensureOpenClawModelsJson } from "./models-config.js"; const LIVE = isLiveTestEnabled(); const REQUIRE_PROFILE_KEYS = isLiveProfileKeyModeEnabled(); -const DEFAULT_TARGET_MODEL_REF = "openai-codex/gpt-5.4-mini"; +const DEFAULT_TARGET_MODEL_REF = "openai/gpt-5.4-mini"; const TARGET_MODEL_REF = process.env.OPENCLAW_LIVE_OPENAI_REASONING_COMPAT_MODEL?.trim() || DEFAULT_TARGET_MODEL_REF; const describeLive = LIVE ? describe : describe.skip; @@ -237,7 +237,7 @@ describeLive("openai reasoning compat live", () => { provider: model.provider, modelId: model.id, sessionManager: SessionManager.inMemory(), - sessionId: "openai-codex-tool-replay-live", + sessionId: "openai-chatgpt-tool-replay-live", }); expect(sanitized.map((message) => message.role)).toEqual([ diff --git a/src/agents/openai-reasoning-effort.test.ts b/src/agents/openai-reasoning-effort.test.ts index c49175443fb4..9a9a44cc5781 100644 --- a/src/agents/openai-reasoning-effort.test.ts +++ b/src/agents/openai-reasoning-effort.test.ts @@ -7,7 +7,7 @@ import { describe("OpenAI reasoning effort support", () => { it.each([ { provider: "openai", id: "gpt-5.5" }, - { provider: "openai-codex", id: "gpt-5.5" }, + { provider: "openai", id: "gpt-5.5" }, ])("preserves xhigh for $provider/$id", (model) => { expect(resolveOpenAISupportedReasoningEfforts(model)).toContain("xhigh"); expect(resolveOpenAIReasoningEffortForModel({ model, effort: "xhigh" })).toBe("xhigh"); @@ -27,7 +27,7 @@ describe("OpenAI reasoning effort support", () => { it("does not downgrade xhigh when model compat metadata declares it explicitly", () => { const model = { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", compat: { supportedReasoningEfforts: ["low", "medium", "high", "xhigh"], diff --git a/src/agents/openai-responses-payload-policy.test.ts b/src/agents/openai-responses-payload-policy.test.ts index 0d22d062cd49..e1bd01c77b89 100644 --- a/src/agents/openai-responses-payload-policy.test.ts +++ b/src/agents/openai-responses-payload-policy.test.ts @@ -217,8 +217,8 @@ describe("openai responses payload policy", () => { it("emits store false for native OpenAI Codex responses disable mode", () => { const policy = resolveOpenAIResponsesPayloadPolicy( { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api/codex", }, { storeMode: "disable" }, @@ -233,7 +233,7 @@ describe("openai responses payload policy", () => { const policy = resolveOpenAIResponsesPayloadPolicy( { api: "openclaw-openai-responses-transport", - provider: "openai-codex", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api/codex", }, { storeMode: "disable" }, diff --git a/src/agents/openai-responses-payload-policy.ts b/src/agents/openai-responses-payload-policy.ts index d47575cd303c..c51a54c7ca58 100644 --- a/src/agents/openai-responses-payload-policy.ts +++ b/src/agents/openai-responses-payload-policy.ts @@ -31,7 +31,7 @@ type OpenAIResponsesEndpointClass = | "moonshot-native" | "modelstudio-native" | "openai-public" - | "openai-codex" + | "openai" | "opencode-native" | "azure-openai" | "openrouter" @@ -64,7 +64,7 @@ type OpenAIResponsesPayloadCapabilities = { const OPENAI_RESPONSES_APIS = new Set([ "openai-responses", "azure-openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "openclaw-openai-responses-transport", ]); const OPENAI_RESPONSES_PROVIDERS = new Set(["openai", "azure-openai", "azure-openai-responses"]); @@ -166,7 +166,7 @@ function resolveBundledOpenAIResponsesEndpointClass( case "api.openai.com": return "openai-public"; case "chatgpt.com": - return "openai-codex"; + return "openai"; case "generativelanguage.googleapis.com": return "google-generative-ai"; case "aiplatform.googleapis.com": @@ -223,13 +223,13 @@ function resolveOpenAIResponsesPayloadCapabilities( ): OpenAIResponsesPayloadCapabilities { const provider = normalizeLowercaseString(model.provider); const api = normalizeLowercaseString(model.api); - const isOpenAIProvider = provider === "openai" || provider === "openai-codex"; + const isOpenAIProvider = provider === "openai"; const endpointClass = resolveBundledOpenAIResponsesEndpointClass(model.baseUrl); const isResponsesApi = isOpenAIResponsesApi(api); const usesConfiguredBaseUrl = endpointClass !== "default"; const usesKnownNativeOpenAIEndpoint = endpointClass === "openai-public" || - endpointClass === "openai-codex" || + endpointClass === "openai" || endpointClass === "azure-openai"; const usesKnownNativeOpenAIRoute = endpointClass === "default" ? provider === "openai" : usesKnownNativeOpenAIEndpoint; @@ -250,13 +250,13 @@ function resolveOpenAIResponsesPayloadCapabilities( (api === "openai-responses" || api === "openclaw-openai-responses-transport") && endpointClass === "openai-public") || (isOpenAIProvider && - (api === "openai-codex-responses" || + (api === "openai-chatgpt-responses" || api === "openai-responses" || api === "openclaw-openai-responses-transport") && - endpointClass === "openai-codex"), + endpointClass === "openai"), allowsResponsesStore: supportsResponsesStoreField && - api !== "openai-codex-responses" && + api !== "openai-chatgpt-responses" && provider !== undefined && OPENAI_RESPONSES_PROVIDERS.has(provider) && usesKnownNativeOpenAIEndpoint, diff --git a/src/agents/openai-codex-routing.test.ts b/src/agents/openai-routing.test.ts similarity index 89% rename from src/agents/openai-codex-routing.test.ts rename to src/agents/openai-routing.test.ts index ec068d9919b2..4ff864f01ebd 100644 --- a/src/agents/openai-codex-routing.test.ts +++ b/src/agents/openai-routing.test.ts @@ -7,7 +7,7 @@ import { resolveContextConfigProviderForRuntime, resolveOpenAIRuntimeProvider, resolveSelectedOpenAIRuntimeProvider, -} from "./openai-codex-routing.js"; +} from "./openai-routing.js"; describe("OpenAI runtime routing policy", () => { it("uses Codex by default for official OpenAI agent model selections", () => { @@ -56,7 +56,7 @@ describe("OpenAI runtime routing policy", () => { const config = { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api/codex", models: [], }, @@ -70,7 +70,7 @@ describe("OpenAI runtime routing policy", () => { runtimeId: "codex", config, }), - ).toBe("openai-codex"); + ).toBe("openai"); }); it("keeps explicit OpenClaw plus Codex auth profile under the unified OpenAI provider", () => { @@ -79,13 +79,13 @@ describe("OpenAI runtime routing policy", () => { provider: "openai", harnessRuntime: "openclaw", }), - ).toEqual(["openai-codex", "openai"]); + ).toEqual(["openai"]); expect( resolveOpenAIRuntimeProvider({ provider: "openai", harnessRuntime: "openclaw", - authProfileProvider: "openai-codex", - authProfileId: "openai-codex:work", + authProfileProvider: "openai", + authProfileId: "openai:work", }), ).toBe("openai"); }); @@ -94,7 +94,7 @@ describe("OpenAI runtime routing policy", () => { const config = { auth: { order: { - openai: ["openai-codex:work", "openai:backup"], + openai: ["openai:work", "openai:backup"], }, }, } satisfies OpenClawConfig; @@ -105,7 +105,7 @@ describe("OpenAI runtime routing policy", () => { harnessRuntime: "openclaw", config, }), - ).toEqual(["openai-codex", "openai"]); + ).toEqual(["openai"]); expect( resolveSelectedOpenAIRuntimeProvider({ provider: "openai", @@ -126,7 +126,7 @@ describe("OpenAI runtime routing policy", () => { const config = { auth: { order: { - "openai-codex": ["openai-codex:work", "openai:backup"], + openai: ["openai:work", "openai:backup"], }, }, } satisfies OpenClawConfig; @@ -137,14 +137,14 @@ describe("OpenAI runtime routing policy", () => { harnessRuntime: "openclaw", config, }), - ).toEqual(["openai-codex", "openai"]); + ).toEqual(["openai"]); }); it("keeps explicit OpenAI OpenClaw API-key auth order ahead of Codex backups", () => { const config = { auth: { order: { - openai: ["openai:backup", "openai-codex:work"], + openai: ["openai:backup", "openai:work"], }, }, } satisfies OpenClawConfig; @@ -155,7 +155,7 @@ describe("OpenAI runtime routing policy", () => { harnessRuntime: "openclaw", config, }), - ).toEqual(["openai-codex", "openai"]); + ).toEqual(["openai"]); expect( resolveSelectedOpenAIRuntimeProvider({ provider: "openai", @@ -177,7 +177,7 @@ describe("OpenAI runtime routing policy", () => { }, auth: { order: { - openai: ["openai-codex:work", "openai:backup"], + openai: ["openai:work", "openai:backup"], }, }, } satisfies OpenClawConfig; @@ -204,7 +204,7 @@ describe("OpenAI runtime routing policy", () => { provider: "openai", harnessRuntime: "codex", }), - ).toEqual(["openai-codex", "openai"]); + ).toEqual(["openai"]); }); it("keeps OpenAI as the runtime provider when harness runtime is codex", () => { diff --git a/src/agents/openai-codex-routing.ts b/src/agents/openai-routing.ts similarity index 74% rename from src/agents/openai-codex-routing.ts rename to src/agents/openai-routing.ts index f07610bbec4b..2273b8f9d815 100644 --- a/src/agents/openai-codex-routing.ts +++ b/src/agents/openai-routing.ts @@ -2,7 +2,7 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import type { OpenClawConfig } from "../config/types.openclaw.js"; export const OPENAI_PROVIDER_ID = "openai"; -export const OPENAI_CODEX_PROVIDER_ID = "openai-codex"; +export const OPENAI_CODEX_PROVIDER_ID = OPENAI_PROVIDER_ID; function isOfficialOpenAIBaseUrl(baseUrl: unknown): boolean { if (typeof baseUrl !== "string" || !baseUrl.trim()) { @@ -27,17 +27,9 @@ function openAIProviderUsesCustomBaseUrl(config: OpenClawConfig | undefined): bo return !isOfficialOpenAIBaseUrl(config?.models?.providers?.openai?.baseUrl); } -function hasProviderConfig(config: OpenClawConfig | undefined, provider: string): boolean { - return Boolean(config?.models?.providers?.[provider]); -} - export function isOpenAIProvider(provider: string | undefined): boolean { const normalized = normalizeProviderId(provider ?? ""); - return normalized === OPENAI_PROVIDER_ID || normalized === OPENAI_CODEX_PROVIDER_ID; -} - -export function isOpenAICodexProvider(provider: string | undefined): boolean { - return normalizeProviderId(provider ?? "") === OPENAI_CODEX_PROVIDER_ID; + return normalized === OPENAI_PROVIDER_ID; } export function openAIProviderUsesCodexRuntimeByDefault(params: { @@ -67,9 +59,6 @@ export function modelSelectionShouldEnsureCodexPlugin(params: { config?: OpenClawConfig; }): boolean { const provider = parseModelRefProvider(params.model); - if (provider === OPENAI_CODEX_PROVIDER_ID) { - return true; - } return provider === OPENAI_PROVIDER_ID && !openAIProviderUsesCustomBaseUrl(params.config); } @@ -82,12 +71,7 @@ export function listOpenAIAuthProfileProvidersForAgentRuntime(params: { if (!isOpenAIProvider(params.provider)) { return [params.provider]; } - return openAIProviderUsesCodexRuntimeByDefault({ - provider: params.provider, - config: params.config, - }) - ? [OPENAI_CODEX_PROVIDER_ID, OPENAI_PROVIDER_ID] - : [OPENAI_PROVIDER_ID]; + return [OPENAI_PROVIDER_ID]; } export function resolveOpenAIRuntimeProvider(params: { @@ -119,15 +103,5 @@ export function resolveContextConfigProviderForRuntime(params: { runtimeId?: string; config?: OpenClawConfig; }): string { - if (!isOpenAIProvider(params.provider)) { - return params.provider; - } - if ( - params.runtimeId === "codex" && - !hasProviderConfig(params.config, OPENAI_PROVIDER_ID) && - hasProviderConfig(params.config, OPENAI_CODEX_PROVIDER_ID) - ) { - return OPENAI_CODEX_PROVIDER_ID; - } - return OPENAI_PROVIDER_ID; + return isOpenAIProvider(params.provider) ? OPENAI_PROVIDER_ID : params.provider; } diff --git a/src/agents/openai-thinking-contract.test.ts b/src/agents/openai-thinking-contract.test.ts index 848e9cac877f..9600ee34d927 100644 --- a/src/agents/openai-thinking-contract.test.ts +++ b/src/agents/openai-thinking-contract.test.ts @@ -9,7 +9,7 @@ import { } from "openclaw/plugin-sdk/llm"; import { describe, expect, it } from "vitest"; -type ResponsesModel = Model<"openai-responses"> | Model<"openai-codex-responses">; +type ResponsesModel = Model<"openai-responses"> | Model<"openai-chatgpt-responses">; const openaiModel = { api: "openai-responses", @@ -20,13 +20,13 @@ const openaiModel = { } as Model<"openai-responses">; const codexModel = { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.5", input: ["text"], reasoning: true, baseUrl: "https://chatgpt.com/backend-api", -} as Model<"openai-codex-responses">; +} as Model<"openai-chatgpt-responses">; const codexTestToken = [ "eyJhbGciOiJub25lIn0", @@ -154,7 +154,7 @@ function createAssistantMessage(model: ResponsesModel): AssistantMessage { } async function captureProviderPayload< - TApi extends "openai-responses" | "openai-codex-responses", + TApi extends "openai-responses" | "openai-chatgpt-responses", >(params: { model: Model; streamFn: ( @@ -175,7 +175,7 @@ async function captureProviderPayload< messages: [{ role: "user", content: "hello", timestamp: 0 }], }, { - apiKey: params.model.api === "openai-codex-responses" ? codexTestToken : "test-api-key", + apiKey: params.model.api === "openai-chatgpt-responses" ? codexTestToken : "test-api-key", cacheRetention: "none", ...params.options, onPayload: (payload) => { diff --git a/src/agents/openai-transport-stream.test.ts b/src/agents/openai-transport-stream.test.ts index b81b5b0345d3..951d7f86dcc5 100644 --- a/src/agents/openai-transport-stream.test.ts +++ b/src/agents/openai-transport-stream.test.ts @@ -281,15 +281,15 @@ describe("openai transport stream", () => { const model = { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://proxy.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">; + } satisfies Model<"openai-chatgpt-responses">; const output: OpenAIResponsesOutput = { role: "assistant" as const, content: [], @@ -324,11 +324,11 @@ describe("openai transport stream", () => { output, { push: vi.fn() }, model, - { authProfileId: "openai-codex:oauth", sessionId: "session-123" }, + { authProfileId: "openai:oauth", sessionId: "session-123" }, ); const expectedReplayMetadata = testing.buildOpenAIResponsesReasoningReplayMetadata(model, { - authProfileId: "openai-codex:oauth", + authProfileId: "openai:oauth", sessionId: "session-123", }); const thinkingBlock = output.content[0] as { @@ -490,7 +490,7 @@ describe("openai transport stream", () => { { id: "gpt-5.4-codex", name: "GPT-5.4 Codex", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", baseUrl: "https://chatgpt.com/backend-api", headers: { @@ -502,7 +502,7 @@ describe("openai transport stream", () => { cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: "", messages: [] } as never, ); @@ -583,7 +583,7 @@ describe("openai transport stream", () => { it("reports the supported transport-aware APIs", () => { expect(isTransportAwareApiSupported("openai-responses")).toBe(true); - expect(isTransportAwareApiSupported("openai-codex-responses")).toBe(true); + expect(isTransportAwareApiSupported("openai-chatgpt-responses")).toBe(true); expect(isTransportAwareApiSupported("openai-completions")).toBe(true); expect(isTransportAwareApiSupported("azure-openai-responses")).toBe(true); expect(isTransportAwareApiSupported("anthropic-messages")).toBe(true); @@ -623,15 +623,15 @@ describe("openai transport stream", () => { createBoundaryAwareStreamFnForModel({ id: "codex-mini-latest", name: "Codex Mini Latest", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">), + } satisfies Model<"openai-chatgpt-responses">), ).toBeTypeOf("function"); expect( createBoundaryAwareStreamFnForModel({ @@ -687,15 +687,15 @@ describe("openai transport stream", () => { { id: "codex-mini-latest", name: "Codex Mini Latest", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { proxy: { mode: "explicit-proxy", @@ -709,7 +709,7 @@ describe("openai transport stream", () => { expect(resolveTransportAwareSimpleApi(model.api)).toBe("openclaw-openai-responses-transport"); expectRecordFields(prepared, { api: "openclaw-openai-responses-transport", - provider: "openai-codex", + provider: "openai", id: "codex-mini-latest", }); expect(buildTransportAwareSimpleStreamFn(model)).toBeTypeOf("function"); @@ -2325,15 +2325,15 @@ describe("openai transport stream", () => { { id: "gpt-5.5", name: "GPT-5.5", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api/codex", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 400000, maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: "", messages: [{ role: "user", content: "Reply OK", timestamp: 1 }], @@ -2355,7 +2355,7 @@ describe("openai transport stream", () => { { id: "gpt-5.5", name: "GPT-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", baseUrl: "https://chatgpt.com/backend-api/codex", reasoning: true, @@ -2363,7 +2363,7 @@ describe("openai transport stream", () => { cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 400000, maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: "", messages: [{ role: "user", content: "Reply OK", timestamp: 1 }], @@ -2385,15 +2385,15 @@ describe("openai transport stream", () => { { id: "gpt-5.5", name: "GPT-5.5", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://proxy.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 400000, maxTokens: 128000, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: "", messages: [{ role: "user", content: "Reply OK", timestamp: 1 }], @@ -2414,15 +2414,15 @@ describe("openai transport stream", () => { { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: `Stable prefix${SYSTEM_PROMPT_CACHE_BOUNDARY}Dynamic suffix`, messages: [{ role: "user", content: "Hello", timestamp: 1 }], @@ -2467,7 +2467,7 @@ describe("openai transport stream", () => { id: "gpt-5.5", name: "GPT-5.5", api: "openclaw-openai-responses-transport" as Api, - provider: "openai-codex", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api/codex", reasoning: true, input: ["text"], @@ -2528,15 +2528,15 @@ describe("openai transport stream", () => { { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, payload, ); @@ -2555,15 +2555,15 @@ describe("openai transport stream", () => { { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://proxy.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: `Stable prefix${SYSTEM_PROMPT_CACHE_BOUNDARY}Dynamic suffix`, messages: [{ role: "user", content: "Hello", timestamp: 1 }], @@ -2655,15 +2655,15 @@ describe("openai transport stream", () => { { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://proxy.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, payload, ); @@ -2675,22 +2675,22 @@ describe("openai transport stream", () => { { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: "system", messages: [ { role: "assistant", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", model: "gpt-5.4", usage: { input: 0, @@ -2780,15 +2780,15 @@ describe("openai transport stream", () => { const model = { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://proxy.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">; + } satisfies Model<"openai-chatgpt-responses">; const params = buildOpenAIResponsesParams( model, @@ -2797,8 +2797,8 @@ describe("openai transport stream", () => { messages: [ { role: "assistant", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", model: "gpt-5.4", usage: { input: 0, @@ -2822,7 +2822,7 @@ describe("openai transport stream", () => { openclawReasoningReplay: testing.buildOpenAIResponsesReasoningReplayMetadata( model, { - authProfileId: "openai-codex:oauth", + authProfileId: "openai:oauth", sessionId: "session-123", }, ), @@ -2847,7 +2847,7 @@ describe("openai transport stream", () => { ], tools: [], } as never, - { authProfileId: "openai-codex:oauth", sessionId: "session-123" }, + { authProfileId: "openai:oauth", sessionId: "session-123" }, ) as { input?: Array<{ type?: string; @@ -2948,15 +2948,15 @@ describe("openai transport stream", () => { const model = { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://proxy.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">; + } satisfies Model<"openai-chatgpt-responses">; const params = buildOpenAIResponsesParams( model, @@ -2965,8 +2965,8 @@ describe("openai transport stream", () => { messages: [ { role: "assistant", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", model: "gpt-5.4", usage: { input: 0, @@ -2990,7 +2990,7 @@ describe("openai transport stream", () => { openclawReasoningReplay: testing.buildOpenAIResponsesReasoningReplayMetadata( model, { - authProfileId: "openai-codex:oauth", + authProfileId: "openai:oauth", sessionId: "different-session", }, ), @@ -3000,7 +3000,7 @@ describe("openai transport stream", () => { ], tools: [], } as never, - { authProfileId: "openai-codex:oauth", sessionId: "session-123" }, + { authProfileId: "openai:oauth", sessionId: "session-123" }, ) as { input?: Array<{ type?: string; @@ -3021,15 +3021,15 @@ describe("openai transport stream", () => { const model = { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://proxy.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">; + } satisfies Model<"openai-chatgpt-responses">; const params = buildOpenAIResponsesParams( model, @@ -3038,8 +3038,8 @@ describe("openai transport stream", () => { messages: [ { role: "assistant", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", model: "gpt-5.4", usage: { input: 0, @@ -3063,7 +3063,7 @@ describe("openai transport stream", () => { openclawReasoningReplay: testing.buildOpenAIResponsesReasoningReplayMetadata( model, { - authProfileId: "openai-codex:old-oauth", + authProfileId: "openai:old-oauth", sessionId: "session-123", }, ), @@ -3073,7 +3073,7 @@ describe("openai transport stream", () => { ], tools: [], } as never, - { authProfileId: "openai-codex:new-oauth", sessionId: "session-123" }, + { authProfileId: "openai:new-oauth", sessionId: "session-123" }, ) as { input?: Array<{ type?: string; @@ -3094,15 +3094,15 @@ describe("openai transport stream", () => { const model = { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://proxy.example.com/v1", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">; + } satisfies Model<"openai-chatgpt-responses">; const params = buildOpenAIResponsesParams( model, @@ -3111,8 +3111,8 @@ describe("openai transport stream", () => { messages: [ { role: "assistant", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", model: "gpt-5.4", usage: { input: 0, @@ -3137,7 +3137,7 @@ describe("openai transport stream", () => { }, model, { - authProfileId: "openai-codex:oauth", + authProfileId: "openai:oauth", sessionId: "session-123", }, ), @@ -3148,7 +3148,7 @@ describe("openai transport stream", () => { ], tools: [], } as never, - { authProfileId: "openai-codex:oauth", sessionId: "session-123" }, + { authProfileId: "openai:oauth", sessionId: "session-123" }, ) as { input?: Array<{ type?: string; @@ -3369,15 +3369,15 @@ describe("openai transport stream", () => { { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: `Stable prefix${SYSTEM_PROMPT_CACHE_BOUNDARY}Dynamic suffix`, messages: [], @@ -3615,15 +3615,15 @@ describe("openai transport stream", () => { { id: "gpt-5.1-codex-mini", name: "gpt-5.1-codex-mini", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 8192, - } satisfies Model<"openai-codex-responses">, + } satisfies Model<"openai-chatgpt-responses">, { systemPrompt: "system", messages: [], @@ -3639,7 +3639,7 @@ describe("openai transport stream", () => { it.each([ { - label: "openai", + label: "openai-platform", model: { id: "gpt-5.4", name: "GPT-5.4", @@ -3649,12 +3649,12 @@ describe("openai transport stream", () => { }, }, { - label: "openai-codex", + label: "openai-chatgpt", model: { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", }, }, @@ -3736,7 +3736,7 @@ describe("openai transport stream", () => { role: "assistant", phase: "commentary", }); - if (label === "openai-codex") { + if (model.api === "openai-chatgpt-responses") { expect(assistantItem?.id).toBeUndefined(); } else { expect(assistantItem?.id).toBe("msg_commentary"); diff --git a/src/agents/openai-transport-stream.ts b/src/agents/openai-transport-stream.ts index 273940706474..964cab66d84a 100644 --- a/src/agents/openai-transport-stream.ts +++ b/src/agents/openai-transport-stream.ts @@ -90,7 +90,7 @@ const MAX_OPENAI_STRICT_TOOL_DOWNGRADE_DIAGNOSTIC_KEYS = 256; const OPENAI_RESPONSES_REASONING_REPLAY_META_KEY = "__openclaw_replay"; const OPENAI_RESPONSES_REASONING_REPLAY_BLOCK_META_KEY = "openclawReasoningReplay"; const OPENAI_RESPONSES_REPLAY_ITEM_ID_MAX_LENGTH = 64; -const OPENAI_CODEX_RESPONSES_PROVIDERS = new Set(["openai", "openai-codex"]); +const OPENAI_CODEX_RESPONSES_PROVIDERS = new Set(["openai"]); const log = createSubsystemLogger("openai-transport"); const loggedOpenAIStrictToolDowngradeDiagnosticKeys = new Set(); @@ -1939,7 +1939,8 @@ function raiseMinimalReasoningForResponsesWebSearch(params: { function isOpenAICodexResponsesModel(model: Model): boolean { return ( OPENAI_CODEX_RESPONSES_PROVIDERS.has(model.provider) && - (model.api === "openai-codex-responses" || model.api === "openclaw-openai-responses-transport") + (model.api === "openai-chatgpt-responses" || + model.api === "openclaw-openai-responses-transport") ); } @@ -2076,7 +2077,7 @@ export function buildOpenAIResponsesParams( const messages = convertResponsesMessages( model, context, - new Set(["openai", "openai-codex", "opencode", "azure-openai-responses", "github-copilot"]), + new Set(["openai", "opencode", "azure-openai-responses", "github-copilot"]), { includeSystemPrompt: !isCodexResponses, supportsDeveloperRole, diff --git a/src/agents/openclaw-tools.media-factory-plan.test.ts b/src/agents/openclaw-tools.media-factory-plan.test.ts index 259dd4df8bc8..945aacea77f4 100644 --- a/src/agents/openclaw-tools.media-factory-plan.test.ts +++ b/src/agents/openclaw-tools.media-factory-plan.test.ts @@ -588,13 +588,13 @@ describe("optional media tool factory planning", () => { contracts: { imageGenerationProviders: ["openai"] }, imageGenerationProviderMetadata: { openai: { - aliases: ["openai-codex"], + aliases: ["openai"], authSignals: [ { provider: "openai", }, { - provider: "openai-codex", + provider: "openai", providerBaseUrl: { provider: "openai", defaultBaseUrl: "https://api.openai.com/v1", @@ -610,7 +610,7 @@ describe("optional media tool factory planning", () => { const plan = resolveOptionalMediaToolFactoryPlan({ config, - authStore: createAuthStore(["openai-codex"]), + authStore: createAuthStore(["openai"]), }); expect(plan.imageGenerate).toBe(true); installSnapshot(config, plugins, undefined, process.cwd()); @@ -619,7 +619,7 @@ describe("optional media tool factory planning", () => { await createOpenClawToolsForTest({ config, workspaceDir: process.cwd(), - authProfileStore: createAuthStore(["openai-codex"]), + authProfileStore: createAuthStore(["openai"]), pluginToolAllowlist: ["image_generate"], }) ).map((tool) => tool.name), @@ -993,10 +993,10 @@ describe("optional media tool factory planning", () => { contracts: { imageGenerationProviders: ["openai"] }, imageGenerationProviderMetadata: { openai: { - aliases: ["openai-codex"], + aliases: ["openai"], authSignals: [ { - provider: "openai-codex", + provider: "openai", providerBaseUrl: { provider: "openai", defaultBaseUrl: "https://api.openai.com/v1", @@ -1011,7 +1011,7 @@ describe("optional media tool factory planning", () => { const plan = resolveOptionalMediaToolFactoryPlan({ config, - authStore: createAuthStore(["openai-codex"]), + authStore: createAuthStore(["openai"]), }); expect(plan.imageGenerate).toBe(false); }); diff --git a/src/agents/openclaw-tools.session-status.test.ts b/src/agents/openclaw-tools.session-status.test.ts index 77620ec95e53..f165ffb98636 100644 --- a/src/agents/openclaw-tools.session-status.test.ts +++ b/src/agents/openclaw-tools.session-status.test.ts @@ -743,7 +743,7 @@ describe("session_status tool", () => { }); const tool = getSessionStatusTool("main", { - activeModelProvider: "openai-codex", + activeModelProvider: "openai", activeModelId: "gpt-5.2", }); @@ -761,7 +761,7 @@ describe("session_status tool", () => { }); const agent = statusArg.agent as Record; const model = agent.model as Record; - expect(model.primary).not.toBe("openai-codex/gpt-5.2"); + expect(model.primary).not.toBe("openai/gpt-5.2"); }); it("resolves sessionKey=current for a channel-plugin requester via implicit fallback", async () => { @@ -822,7 +822,7 @@ describe("session_status tool", () => { }); const tool = getSessionStatusTool("agent:main:scope:scopy:direct:scopy", { - activeModelProvider: "openai-codex", + activeModelProvider: "openai", activeModelId: "gpt-5.2", }); @@ -830,7 +830,7 @@ describe("session_status tool", () => { const statusArg = mockCallArg(buildStatusMessageMock) as Record; const agent = statusArg.agent as Record; - expectRecordFields(agent.model, { primary: "openai-codex/gpt-5.2" }); + expectRecordFields(agent.model, { primary: "openai/gpt-5.2" }); }); it("renders the active run model for omitted sessionKey lookups", async () => { @@ -842,7 +842,7 @@ describe("session_status tool", () => { }); const tool = getSessionStatusTool("agent:main:scope:scopy:direct:scopy", { - activeModelProvider: "openai-codex", + activeModelProvider: "openai", activeModelId: "gpt-5.2", }); @@ -850,7 +850,7 @@ describe("session_status tool", () => { const statusArg = mockCallArg(buildStatusMessageMock) as Record; const agent = statusArg.agent as Record; - expectRecordFields(agent.model, { primary: "openai-codex/gpt-5.2" }); + expectRecordFields(agent.model, { primary: "openai/gpt-5.2" }); }); it("renders the active run model for current lookups with persisted overrides", async () => { @@ -864,7 +864,7 @@ describe("session_status tool", () => { }); const tool = getSessionStatusTool("agent:main:scope:scopy:direct:scopy", { - activeModelProvider: "openai-codex", + activeModelProvider: "openai", activeModelId: "gpt-5.2", }); @@ -875,7 +875,7 @@ describe("session_status tool", () => { expect(sessionEntry.providerOverride).toBeUndefined(); expect(sessionEntry.modelOverride).toBeUndefined(); const agent = statusArg.agent as Record; - expectRecordFields(agent.model, { primary: "openai-codex/gpt-5.2" }); + expectRecordFields(agent.model, { primary: "openai/gpt-5.2" }); }); it("does not reuse the active run model after a semantic current reset", async () => { @@ -883,13 +883,13 @@ describe("session_status tool", () => { "agent:main:scope:scopy:direct:scopy": { sessionId: "current-reset-model", updatedAt: 10, - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.2", }, }); const tool = getSessionStatusTool("agent:main:scope:scopy:direct:scopy", { - activeModelProvider: "openai-codex", + activeModelProvider: "openai", activeModelId: "gpt-5.2", }); diff --git a/src/agents/openclaw-tools.sessions.test.ts b/src/agents/openclaw-tools.sessions.test.ts index 60553c198e85..7e5abeab2494 100644 --- a/src/agents/openclaw-tools.sessions.test.ts +++ b/src/agents/openclaw-tools.sessions.test.ts @@ -748,8 +748,8 @@ describe("sessions tools", () => { openclawReasoningReplay: { v: 1, source: "openai-responses", - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", model: "gpt-5.5", }, }, diff --git a/src/agents/provider-api-families.test.ts b/src/agents/provider-api-families.test.ts index fa77e7f53911..61daa179fac3 100644 --- a/src/agents/provider-api-families.test.ts +++ b/src/agents/provider-api-families.test.ts @@ -5,7 +5,7 @@ describe("provider api families", () => { it.each([ "openai-completions", "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "azure-openai-responses", ])("classifies %s as supporting the GPT parallel_tool_calls payload patch", (api) => { expect(supportsGptParallelToolCallsPayload(api)).toBe(true); diff --git a/src/agents/provider-api-families.ts b/src/agents/provider-api-families.ts index 6be3066056cd..1bdbed1dd47d 100644 --- a/src/agents/provider-api-families.ts +++ b/src/agents/provider-api-families.ts @@ -1,7 +1,7 @@ const GPT_PARALLEL_TOOL_CALLS_APIS = new Set([ "openai-completions", "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "azure-openai-responses", ]); diff --git a/src/agents/provider-attribution.test.ts b/src/agents/provider-attribution.test.ts index 05784da7ceda..61736353942c 100644 --- a/src/agents/provider-attribution.test.ts +++ b/src/agents/provider-attribution.test.ts @@ -15,7 +15,7 @@ const providerEndpointPlugins = vi.hoisted(() => [ { providerEndpoints: [ { endpointClass: "openai-public", hosts: ["api.openai.com"] }, - { endpointClass: "openai-codex", hosts: ["chatgpt.com"] }, + { endpointClass: "openai", hosts: ["chatgpt.com"] }, { endpointClass: "azure-openai", hostSuffixes: [".openai.azure.com"] }, { endpointClass: "anthropic-public", hosts: ["api.anthropic.com"] }, { endpointClass: "cerebras-native", hosts: ["api.cerebras.ai"] }, @@ -198,9 +198,7 @@ describe("provider attribution", () => { }); it("maps legacy OpenAI Codex attribution to canonical OpenAI policy", () => { - expect( - resolveProviderAttributionPolicy("openai-codex", { OPENCLAW_VERSION: "2026.3.22" }), - ).toEqual({ + expect(resolveProviderAttributionPolicy("openai", { OPENCLAW_VERSION: "2026.3.22" })).toEqual({ provider: "openai", enabledByDefault: true, verification: "vendor-hidden-api-spec", @@ -394,14 +392,14 @@ describe("provider attribution", () => { expectRecordFields( resolveProviderRequestPolicy({ - provider: "openai-codex", + provider: "openai", api: "openai-responses", baseUrl: "https://chatgpt.com/backend-api", transport: "stream", capability: "llm", }), { - endpointClass: "openai-codex", + endpointClass: "openai", attributionProvider: "openai", allowsHiddenAttribution: true, }, @@ -903,14 +901,14 @@ describe("provider attribution", () => { ); expectRecordFields( resolveProviderRequestCapabilities({ - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api/codex", capability: "llm", transport: "stream", }), { - endpointClass: "openai-codex", + endpointClass: "openai", attributionProvider: "openai", allowsOpenAIServiceTier: true, supportsOpenAIReasoningCompatPayload: true, @@ -1302,15 +1300,15 @@ describe("provider attribution", () => { { name: "native OpenAI Codex responses", input: { - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api/codex", capability: "llm" as const, transport: "stream" as const, }, expected: { knownProviderFamily: "openai-family", - endpointClass: "openai-codex", + endpointClass: "openai", isKnownNativeEndpoint: true, allowsOpenAIServiceTier: true, supportsOpenAIReasoningCompatPayload: true, diff --git a/src/agents/provider-attribution.ts b/src/agents/provider-attribution.ts index 008674c06c12..fb58a797fdd3 100644 --- a/src/agents/provider-attribution.ts +++ b/src/agents/provider-attribution.ts @@ -53,7 +53,7 @@ export type ProviderEndpointClass = | "modelstudio-native" | "nvidia-native" | "openai-public" - | "openai-codex" + | "openai" | "opencode-native" | "azure-openai" | "openrouter" @@ -134,14 +134,9 @@ const LOCAL_ENDPOINT_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]) const OPENAI_RESPONSES_APIS = new Set([ "openai-responses", "azure-openai-responses", - "openai-codex-responses", -]); -const OPENAI_RESPONSES_PROVIDERS = new Set([ - "openai", - "openai-codex", - "azure-openai", - "azure-openai-responses", + "openai-chatgpt-responses", ]); +const OPENAI_RESPONSES_PROVIDERS = new Set(["openai", "azure-openai", "azure-openai-responses"]); const MANIFEST_PROVIDER_ENDPOINT_CLASSES = new Set([ "anthropic-public", "cerebras-native", @@ -154,7 +149,7 @@ const MANIFEST_PROVIDER_ENDPOINT_CLASSES = new Set([ "modelstudio-native", "nvidia-native", "openai-public", - "openai-codex", + "openai", "opencode-native", "azure-openai", "openrouter", @@ -437,7 +432,6 @@ function resolveKnownProviderFamily(provider: string | undefined): string { } switch (provider) { case "openai": - case "openai-codex": case "azure-openai": case "azure-openai-responses": return "openai-family"; @@ -452,7 +446,7 @@ function isOpenAIResponsesApi(api: string | null | undefined): boolean { } function isCanonicalOrLegacyOpenAIProvider(provider: string | undefined): boolean { - return provider === "openai" || provider === "openai-codex"; + return provider === "openai"; } export function resolveProviderAttributionIdentity( @@ -603,7 +597,7 @@ export function resolveProviderAttributionPolicy( env: RuntimeVersionEnv = process.env as RuntimeVersionEnv, ): ProviderAttributionPolicy | undefined { const normalized = normalizeProviderId(provider ?? ""); - const canonical = normalized === "openai-codex" ? "openai" : normalized; + const canonical = normalized === "openai" ? "openai" : normalized; return listProviderAttributionPolicies(env).find((policy) => policy.provider === canonical); } @@ -629,10 +623,10 @@ export function resolveProviderRequestPolicy( const usesConfiguredBaseUrl = endpointClass !== "default"; const usesKnownNativeOpenAIEndpoint = endpointClass === "openai-public" || - endpointClass === "openai-codex" || + endpointClass === "openai" || endpointClass === "azure-openai"; const usesOpenAIPublicAttributionHost = endpointClass === "openai-public"; - const usesOpenAICodexAttributionHost = endpointClass === "openai-codex"; + const usesOpenAICodexAttributionHost = endpointClass === "openai"; const usesVerifiedOpenAIAttributionHost = usesOpenAIPublicAttributionHost || usesOpenAICodexAttributionHost; const usesXaiNativeAttributionHost = endpointClass === "xai-native"; @@ -712,7 +706,7 @@ export function resolveProviderRequestCapabilities( endpointClass === "modelstudio-native" || endpointClass === "nvidia-native" || endpointClass === "openai-public" || - endpointClass === "openai-codex" || + endpointClass === "openai" || endpointClass === "opencode-native" || endpointClass === "azure-openai" || endpointClass === "openrouter" || @@ -750,8 +744,8 @@ export function resolveProviderRequestCapabilities( api === "openai-responses" && endpointClass === "openai-public") || (isCanonicalOrLegacyOpenAIProvider(provider) && - (api === "openai-codex-responses" || api === "openai-responses") && - endpointClass === "openai-codex"), + (api === "openai-chatgpt-responses" || api === "openai-responses") && + endpointClass === "openai"), supportsOpenAIReasoningCompatPayload: provider !== undefined && api !== undefined && @@ -761,7 +755,7 @@ export function resolveProviderRequestCapabilities( provider === "azure-openai-responses") && (api === "openai-completions" || api === "openai-responses" || - api === "openai-codex-responses" || + api === "openai-chatgpt-responses" || api === "azure-openai-responses"), allowsAnthropicServiceTier: provider === "anthropic" && diff --git a/src/agents/provider-auth-aliases.test.ts b/src/agents/provider-auth-aliases.test.ts index a665b3154285..eda3b1d7f00a 100644 --- a/src/agents/provider-auth-aliases.test.ts +++ b/src/agents/provider-auth-aliases.test.ts @@ -61,10 +61,10 @@ describe("provider auth aliases", () => { origin: "bundled", providerAuthChoices: [ { - provider: "openai-codex", + provider: "openai", method: "oauth", - choiceId: "openai-codex", - deprecatedChoiceIds: ["codex-cli", "openai-codex-import"], + choiceId: "openai", + deprecatedChoiceIds: ["codex-cli", "openai-chatgpt-import"], }, ], }, @@ -72,9 +72,13 @@ describe("provider auth aliases", () => { diagnostics: [], }); - expect(resolveProviderIdForAuth("codex-cli")).toBe("openai-codex"); - expect(resolveProviderIdForAuth("openai-codex-import")).toBe("openai-codex"); - expect(resolveProviderIdForAuth("openai-codex")).toBe("openai-codex"); + expect(resolveProviderIdForAuth("codex-cli")).toBe("openai"); + expect(resolveProviderIdForAuth("openai-chatgpt-import")).toBe("openai"); + expect(resolveProviderIdForAuth("openai")).toBe("openai"); + }); + + it("maps retired persisted OpenAI auth provider ids to canonical OpenAI", () => { + expect(resolveProviderIdForAuth(["openai", "codex"].join("-"))).toBe("openai"); }); it("does not reuse aliases across env-resolved plugin roots", () => { diff --git a/src/agents/provider-auth-aliases.ts b/src/agents/provider-auth-aliases.ts index e141b624cb4e..3c5b1bdfdc97 100644 --- a/src/agents/provider-auth-aliases.ts +++ b/src/agents/provider-auth-aliases.ts @@ -25,6 +25,10 @@ type ProviderAuthAliasCandidate = { target: string; }; +const RETIRED_PROVIDER_AUTH_ALIASES: Readonly> = { + [["openai", "codex"].join("-")]: "openai", +}; + const PROVIDER_AUTH_ALIAS_ORIGIN_PRIORITY: Readonly> = { config: 0, bundled: 1, @@ -203,5 +207,9 @@ export function resolveProviderIdForAuth( if (!normalized) { return normalized; } - return resolveProviderAuthAliasMap(params)[normalized] ?? normalized; + return ( + resolveProviderAuthAliasMap(params)[normalized] ?? + RETIRED_PROVIDER_AUTH_ALIASES[normalized] ?? + normalized + ); } diff --git a/src/agents/provider-transport-stream.test.ts b/src/agents/provider-transport-stream.test.ts index 8409689ece93..dba64258997f 100644 --- a/src/agents/provider-transport-stream.test.ts +++ b/src/agents/provider-transport-stream.test.ts @@ -45,8 +45,8 @@ describe("provider transport stream contracts", () => { alias: "openclaw-openai-responses-transport", }, { - api: "openai-codex-responses" as const, - provider: "openai-codex", + api: "openai-chatgpt-responses" as const, + provider: "openai", id: "codex-mini-latest", baseUrl: "https://chatgpt.com/backend-api", alias: "openclaw-openai-responses-transport", @@ -198,9 +198,9 @@ describe("provider transport stream contracts", () => { }); it("keeps Codex defaults on the OpenClaw transport until OpenClaw preserves attribution", () => { - const model = buildModel("openai-codex-responses", { + const model = buildModel("openai-chatgpt-responses", { id: "gpt-5.4", - provider: "openai-codex", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", }); diff --git a/src/agents/provider-transport-stream.ts b/src/agents/provider-transport-stream.ts index bcba248ffda0..bee17926407d 100644 --- a/src/agents/provider-transport-stream.ts +++ b/src/agents/provider-transport-stream.ts @@ -13,7 +13,7 @@ import type { StreamFn } from "./runtime/index.js"; const SUPPORTED_TRANSPORT_APIS = new Set([ "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "openai-completions", "azure-openai-responses", "anthropic-messages", @@ -22,7 +22,7 @@ const SUPPORTED_TRANSPORT_APIS = new Set([ const SIMPLE_TRANSPORT_API_ALIAS: Record = { "openai-responses": "openclaw-openai-responses-transport", - "openai-codex-responses": "openclaw-openai-responses-transport", + "openai-chatgpt-responses": "openclaw-openai-responses-transport", "openai-completions": "openclaw-openai-completions-transport", "azure-openai-responses": "openclaw-azure-openai-responses-transport", "anthropic-messages": "openclaw-anthropic-messages-transport", @@ -79,7 +79,7 @@ function createSupportedTransportStreamFn( ): StreamFn | undefined { switch (model.api) { case "openai-responses": - case "openai-codex-responses": + case "openai-chatgpt-responses": return createOpenAIResponsesTransportStreamFn(); case "openai-completions": return createOpenAICompletionsTransportStreamFn(); diff --git a/src/agents/runtime-plan/build.test.ts b/src/agents/runtime-plan/build.test.ts index ed6fba5af79f..68032b15e235 100644 --- a/src/agents/runtime-plan/build.test.ts +++ b/src/agents/runtime-plan/build.test.ts @@ -122,8 +122,8 @@ describe("AgentRuntimePlan", () => { modelApi: "openai-responses", harnessId: "codex", harnessRuntime: "codex", - authProfileProvider: "openai-codex", - sessionAuthProfileId: "openai-codex:work", + authProfileProvider: "openai", + sessionAuthProfileId: "openai:work", config: {}, workspaceDir: "/tmp/openclaw-runtime-plan", model: { @@ -135,7 +135,7 @@ describe("AgentRuntimePlan", () => { expect(plan.auth.providerForAuth).toBe("openai"); expect(plan.auth.authProfileProviderForAuth).toBe("openai"); expect(plan.auth.harnessAuthProvider).toBe("openai"); - expect(plan.auth.forwardedAuthProfileId).toBe("openai-codex:work"); + expect(plan.auth.forwardedAuthProfileId).toBe("openai:work"); expect(plan.delivery.isSilentPayload({ text: "NO_REPLY\n\nNO_REPLY" })).toBe(true); expect(plan.delivery.isSilentPayload({ text: '{"action":"NO_REPLY"}' })).toBe(true); expect( @@ -230,19 +230,16 @@ describe("AgentRuntimePlan", () => { modelApi: "openai-responses", harnessId: "codex", harnessRuntime: "codex", - authProfileProvider: "openai-codex", + authProfileProvider: "openai", authProfileMode: "oauth", - sessionAuthProfileId: "openai-codex:work", - sessionAuthProfileCandidateIds: ["openai-codex:work", "openai:backup"], + sessionAuthProfileId: "openai:work", + sessionAuthProfileCandidateIds: ["openai:work", "openai:backup"], config: {}, workspaceDir: "/tmp/openclaw-runtime-plan", }); - expect(plan.auth.forwardedAuthProfileId).toBe("openai-codex:work"); - expect(plan.auth.forwardedAuthProfileCandidateIds).toEqual([ - "openai-codex:work", - "openai:backup", - ]); + expect(plan.auth.forwardedAuthProfileId).toBe("openai:work"); + expect(plan.auth.forwardedAuthProfileCandidateIds).toEqual(["openai:work", "openai:backup"]); }); it("forwards OpenAI OAuth profiles into the Codex harness auth slot", () => { @@ -269,15 +266,15 @@ describe("AgentRuntimePlan", () => { modelApi: "openai-responses", harnessId: "openclaw", harnessRuntime: "openclaw", - authProfileProvider: "openai-codex", - sessionAuthProfileId: "openai-codex:work", + authProfileProvider: "openai", + sessionAuthProfileId: "openai:work", config: {}, workspaceDir: "/tmp/openclaw-runtime-plan", }); expect(plan.auth.providerForAuth).toBe("openai"); expect(plan.auth.authProfileProviderForAuth).toBe("openai"); - expect(plan.auth.forwardedAuthProfileId).toBe("openai-codex:work"); + expect(plan.auth.forwardedAuthProfileId).toBe("openai:work"); }); it("resolves follow-up routes with the prepared provider handle", () => { diff --git a/src/agents/session-file-repair.test.ts b/src/agents/session-file-repair.test.ts index b1601075e8c1..9a2458eb7127 100644 --- a/src/agents/session-file-repair.test.ts +++ b/src/agents/session-file-repair.test.ts @@ -532,9 +532,9 @@ describe("repairSessionFileIfNeeded", () => { timestamp: new Date().toISOString(), message: { role: "assistant", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", content: [ { type: "text", text: "Process List" }, { @@ -583,6 +583,37 @@ describe("repairSessionFileIfNeeded", () => { expect(JSON.parse(lines[4])).toEqual(deliveryMirror); }); + it("repairs missing tool results in legacy OpenAI ChatGPT transcripts", async () => { + const { file } = await createTempSessionPath(); + const { header, message } = buildSessionHeaderAndMessage(); + const legacyProvider = ["openai", "codex"].join("-"); + const toolCallAssistant = { + type: "message", + id: "msg-asst-legacy-process", + parentId: "msg-1", + timestamp: new Date().toISOString(), + message: { + role: "assistant", + provider: legacyProvider, + model: "gpt-5.5", + api: `${legacyProvider}-responses`, + content: [{ type: "toolCall", id: "call_process|fc_1", name: "process", arguments: {} }], + stopReason: "toolUse", + }, + }; + const original = `${JSON.stringify(header)}\n${JSON.stringify(message)}\n${JSON.stringify(toolCallAssistant)}\n`; + await fs.writeFile(file, original, "utf-8"); + + const result = await repairSessionFileIfNeeded({ sessionFile: file }); + + expect(result.repaired).toBe(true); + expect(result.insertedToolResults).toBe(1); + const lines = (await fs.readFile(file, "utf-8")).trimEnd().split("\n"); + const inserted = JSON.parse(lines[3]); + expect(inserted.message.role).toBe("toolResult"); + expect(inserted.message.toolCallId).toBe("call_process|fc_1"); + }); + it("does not duplicate code-mode tool results that are already persisted", async () => { const { file } = await createTempSessionPath(); const { header, message } = buildSessionHeaderAndMessage(); @@ -593,9 +624,9 @@ describe("repairSessionFileIfNeeded", () => { timestamp: new Date().toISOString(), message: { role: "assistant", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", content: [{ type: "toolCall", id: "call_exec|fc_1", name: "exec", arguments: {} }], stopReason: "toolUse", }, @@ -636,9 +667,9 @@ describe("repairSessionFileIfNeeded", () => { timestamp: new Date().toISOString(), message: { role: "assistant", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", content: [ { type: "toolCall", id: `call_${stopReason}|fc_1`, name: "exec", arguments: {} }, ], diff --git a/src/agents/session-file-repair.ts b/src/agents/session-file-repair.ts index d3ad5f5b9725..f7a37250564d 100644 --- a/src/agents/session-file-repair.ts +++ b/src/agents/session-file-repair.ts @@ -206,10 +206,15 @@ function isCodeModeToolCallRepairCandidate(entry: unknown): entry is SessionMess provider?: unknown; stopReason?: unknown; }; + const legacyOpenAIProvider = ["openai", "codex"].join("-"); + const legacyOpenAIResponsesApi = `${legacyOpenAIProvider}-responses`; + const openAIProvider = message.provider === "openai" || message.provider === legacyOpenAIProvider; + const openAIResponsesApi = + message.api === "openai-chatgpt-responses" || message.api === legacyOpenAIResponsesApi; return ( message.role === "assistant" && - message.api === "openai-codex-responses" && - message.provider === "openai-codex" && + openAIResponsesApi && + openAIProvider && message.stopReason !== "error" && message.stopReason !== "aborted" ); diff --git a/src/agents/session-runtime-compat.ts b/src/agents/session-runtime-compat.ts index 6c2200cb83d3..ac7004d2bea7 100644 --- a/src/agents/session-runtime-compat.ts +++ b/src/agents/session-runtime-compat.ts @@ -3,7 +3,7 @@ import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { isDefaultAgentRuntimeId } from "./agent-runtime-id.js"; import { normalizeOptionalAgentRuntimeId } from "./agent-runtime-id.js"; import { resolveCliRuntimeModelBackendBinding } from "./cli-backends.js"; -import { resolveContextConfigProviderForRuntime } from "./openai-codex-routing.js"; +import { resolveContextConfigProviderForRuntime } from "./openai-routing.js"; export type SessionRuntimeCompatEntry = Pick< SessionEntry, diff --git a/src/agents/sessions-spawn-hooks.test.ts b/src/agents/sessions-spawn-hooks.test.ts index 6cdf0e5ef424..fcdd3404d820 100644 --- a/src/agents/sessions-spawn-hooks.test.ts +++ b/src/agents/sessions-spawn-hooks.test.ts @@ -282,7 +282,7 @@ describe("sessions_spawn subagent lifecycle hooks", () => { it("binds the subagent thread in core and emits subagent_spawned with requester metadata", async () => { const result = await spawn({ label: "research", - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", runTimeoutSeconds: 1, thread: true, agentAccountId: "work", @@ -296,8 +296,8 @@ describe("sessions_spawn subagent lifecycle hooks", () => { { status: "accepted", runId: "run-1", - resolvedModel: "openai-codex/gpt-5.4", - resolvedProvider: "openai-codex", + resolvedModel: "openai/gpt-5.4", + resolvedProvider: "openai", }, "spawn result", ); @@ -341,8 +341,8 @@ describe("sessions_spawn subagent lifecycle hooks", () => { label: "research", mode: "session", threadRequested: true, - resolvedModel: "openai-codex/gpt-5.4", - resolvedProvider: "openai-codex", + resolvedModel: "openai/gpt-5.4", + resolvedProvider: "openai", }, "spawned event", ); diff --git a/src/agents/simple-completion-runtime.ts b/src/agents/simple-completion-runtime.ts index 1a07a43f87f6..f4557052cd17 100644 --- a/src/agents/simple-completion-runtime.ts +++ b/src/agents/simple-completion-runtime.ts @@ -24,7 +24,7 @@ import { resolveDefaultModelForAgent, resolveModelRefFromString, } from "./model-selection.js"; -import { OPENAI_PROVIDER_ID, isOpenAIProvider } from "./openai-codex-routing.js"; +import { OPENAI_PROVIDER_ID, isOpenAIProvider } from "./openai-routing.js"; import { prepareModelForSimpleCompletion } from "./simple-completion-transport.js"; type SimpleCompletionAuthStorage = { diff --git a/src/agents/simple-completion-transport.test.ts b/src/agents/simple-completion-transport.test.ts index 282ac89340ba..bf7cfbc8ab60 100644 --- a/src/agents/simple-completion-transport.test.ts +++ b/src/agents/simple-completion-transport.test.ts @@ -187,10 +187,10 @@ describe("prepareModelForSimpleCompletion", () => { ])( "uses OpenClaw transport for OpenAI Codex-response simple completions with baseUrl %s", (baseUrl, expectedBaseUrl) => { - const model: Model<"openai-codex-responses"> = { + const model: Model<"openai-chatgpt-responses"> = { id: "gpt-5.5", name: "GPT-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", baseUrl, reasoning: true, diff --git a/src/agents/simple-completion-transport.ts b/src/agents/simple-completion-transport.ts index 8fc154e1d2e2..e36d9143a450 100644 --- a/src/agents/simple-completion-transport.ts +++ b/src/agents/simple-completion-transport.ts @@ -52,7 +52,7 @@ function prepareCodexSimpleTransportModel( model: Model, cfg?: OpenClawConfig, ): Model | undefined { - if (model.provider !== "openai" || model.api !== "openai-codex-responses") { + if (model.provider !== "openai" || model.api !== "openai-chatgpt-responses") { return undefined; } diff --git a/src/agents/subagent-spawn.model-session.test.ts b/src/agents/subagent-spawn.model-session.test.ts index 7313a6a67348..f16b5f7572ba 100644 --- a/src/agents/subagent-spawn.model-session.test.ts +++ b/src/agents/subagent-spawn.model-session.test.ts @@ -71,7 +71,7 @@ describe("spawnSubagentDirect runtime model persistence", () => { const result = await spawnSubagentDirect( { task: "test", - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", }, { agentSessionKey: "agent:main:main", @@ -81,13 +81,13 @@ describe("spawnSubagentDirect runtime model persistence", () => { expect(result.status).toBe("accepted"); expect(result.modelApplied).toBe(true); - expect(result.resolvedModel).toBe("openai-codex/gpt-5.4"); - expect(result.resolvedProvider).toBe("openai-codex"); + expect(result.resolvedModel).toBe("openai/gpt-5.4"); + expect(result.resolvedProvider).toBe("openai"); expect(updateSessionStoreMock).toHaveBeenCalledTimes(3); expectPersistedRuntimeModel({ persistedStore, sessionKey: /^agent:main:subagent:/, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", overrideSource: "user", }); diff --git a/src/agents/subagent-spawn.test.ts b/src/agents/subagent-spawn.test.ts index 8472bd64d16c..568b05d1f9a8 100644 --- a/src/agents/subagent-spawn.test.ts +++ b/src/agents/subagent-spawn.test.ts @@ -68,7 +68,7 @@ describe("spawnSubagentDirect seam flow", () => { registerSubagentRunMock: hoisted.registerSubagentRunMock, emitSessionLifecycleEventMock: hoisted.emitSessionLifecycleEventMock, resolveAgentConfig: hoisted.resolveAgentConfigMock, - resolveSubagentSpawnModelSelection: () => "openai-codex/gpt-5.4", + resolveSubagentSpawnModelSelection: () => "openai/gpt-5.4", resolveSandboxRuntimeStatus: () => ({ sandboxed: false }), sessionStorePath: "/tmp/subagent-spawn-session-store.json", resetModules: false, @@ -198,7 +198,7 @@ describe("spawnSubagentDirect seam flow", () => { const result = await spawnSubagentDirect( { task: "inspect the spawn seam", - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", }, { agentSessionKey: "agent:main:main", @@ -231,7 +231,7 @@ describe("spawnSubagentDirect seam flow", () => { expect(requesterOrigin.threadId).toBe(42); expect(registerInput.task).toBe("inspect the spawn seam"); expect(registerInput.cleanup).toBe("keep"); - expect(registerInput.model).toBe("openai-codex/gpt-5.4"); + expect(registerInput.model).toBe("openai/gpt-5.4"); expect(registerInput.workspaceDir).toBe("/tmp/requester-workspace"); expect(registerInput.expectsCompletionMessage).toBe(true); expect(registerInput.spawnMode).toBe("run"); @@ -245,7 +245,7 @@ describe("spawnSubagentDirect seam flow", () => { expectPersistedRuntimeModel({ persistedStore, sessionKey: childSessionKey, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", overrideSource: "user", }); @@ -325,7 +325,7 @@ describe("spawnSubagentDirect seam flow", () => { const result = await spawnSubagentDirect( { task: "inspect unthreaded spawn", - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", }, { agentSessionKey: "agent:main:main", @@ -364,7 +364,7 @@ describe("spawnSubagentDirect seam flow", () => { const result = await spawnSubagentDirect( { task: "verify per-method scope routing", - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", }, { agentSessionKey: "agent:main:main", diff --git a/src/agents/subagent-spawn.thread-binding.test.ts b/src/agents/subagent-spawn.thread-binding.test.ts index 59401378efd7..1939394ae5b5 100644 --- a/src/agents/subagent-spawn.thread-binding.test.ts +++ b/src/agents/subagent-spawn.thread-binding.test.ts @@ -70,7 +70,7 @@ describe("spawnSubagentDirect thread binding delivery", () => { registerSubagentRunMock: hoisted.registerSubagentRunMock, emitSessionLifecycleEventMock: hoisted.emitSessionLifecycleEventMock, hookRunner: hoisted.hookRunner, - resolveSubagentSpawnModelSelection: () => "openai-codex/gpt-5.4", + resolveSubagentSpawnModelSelection: () => "openai/gpt-5.4", resolveSandboxRuntimeStatus: () => ({ sandboxed: false }), getSessionBindingService: () => currentSessionBindingService, resolveConversationDeliveryTarget: (params) => currentDeliveryTargetResolver(params), diff --git a/src/agents/tool-replay-repair.live.test.ts b/src/agents/tool-replay-repair.live.test.ts index eb4e7a95e59b..0951a43a6878 100644 --- a/src/agents/tool-replay-repair.live.test.ts +++ b/src/agents/tool-replay-repair.live.test.ts @@ -22,7 +22,7 @@ import { transformTransportMessages } from "./transport-message-transform.js"; const LIVE = isLiveTestEnabled(); const REQUIRE_PROFILE_KEYS = isLiveProfileKeyModeEnabled(); -const DEFAULT_TARGET_MODEL_REFS = "openai-codex/gpt-5.5,google/gemini-3-flash-preview"; +const DEFAULT_TARGET_MODEL_REFS = "openai/gpt-5.5,google/gemini-3-flash-preview"; const TARGET_MODEL_REFS = parseTargetModelRefs( process.env.OPENCLAW_LIVE_TOOL_REPLAY_REPAIR_MODELS ?? DEFAULT_TARGET_MODEL_REFS, ); @@ -58,7 +58,7 @@ const logProgress = logLiveProgress; function isOpenAIResponsesFamily(api: string): boolean { return ( api === "openai-responses" || - api === "openai-codex-responses" || + api === "openai-chatgpt-responses" || api === "azure-openai-responses" ); } diff --git a/src/agents/tools-effective-inventory.test.ts b/src/agents/tools-effective-inventory.test.ts index 27106b6bc5b2..97bbf98e29c9 100644 --- a/src/agents/tools-effective-inventory.test.ts +++ b/src/agents/tools-effective-inventory.test.ts @@ -549,7 +549,7 @@ describe("resolveEffectiveToolInventory", () => { normalizeToolsMock, }); effectiveInventoryState.normalizeTransportMock.mockReturnValue({ - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api/codex", }); @@ -557,7 +557,7 @@ describe("resolveEffectiveToolInventory", () => { cfg: { models: { providers: { - "openai-codex": { + openai: { models: [ { id: "gpt-5.5-codex", @@ -572,7 +572,7 @@ describe("resolveEffectiveToolInventory", () => { }, }, } as never, - modelProvider: "openai-codex", + modelProvider: "openai", modelId: "gpt-5.5-codex", }); @@ -582,7 +582,7 @@ describe("resolveEffectiveToolInventory", () => { context: expect.objectContaining({ config: expect.any(Object), workspaceDir: "/tmp/workspace-main", - provider: "openai-codex", + provider: "openai", api: "openai-responses", baseUrl: undefined, }), @@ -590,14 +590,14 @@ describe("resolveEffectiveToolInventory", () => { ); expect(effectiveInventoryState.createToolsMock).toHaveBeenCalledWith( expect.objectContaining({ - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", }), ); expect(normalizeToolsMock).toHaveBeenCalledWith( expect.objectContaining({ - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", model: expect.objectContaining({ - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api/codex", }), }), diff --git a/src/agents/tools/image-generate-tool.test.ts b/src/agents/tools/image-generate-tool.test.ts index 4effc1aa4c14..93636be24909 100644 --- a/src/agents/tools/image-generate-tool.test.ts +++ b/src/agents/tools/image-generate-tool.test.ts @@ -510,7 +510,7 @@ describe("createImageGenerateTool", () => { }, { id: "openai", - aliases: ["openai-codex"], + aliases: ["openai"], defaultModel: "gpt-image-2", models: ["gpt-image-2"], isConfigured: () => true, @@ -530,7 +530,7 @@ describe("createImageGenerateTool", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.5", + primary: "openai/gpt-5.5", }, }, }, diff --git a/src/agents/tools/pdf-tool.test.ts b/src/agents/tools/pdf-tool.test.ts index d259f371fa04..5e8e747481dc 100644 --- a/src/agents/tools/pdf-tool.test.ts +++ b/src/agents/tools/pdf-tool.test.ts @@ -36,7 +36,7 @@ async function loadCreatePdfTool() { const ANTHROPIC_PDF_MODEL = "anthropic/claude-opus-4-6"; const OPENAI_PDF_MODEL = "openai/gpt-5.4-mini"; -const CODEX_PDF_MODEL = "openai-codex/gpt-5.4"; +const CODEX_PDF_MODEL = "openai/gpt-5.4"; const FAKE_PDF_MEDIA = { kind: "document", buffer: Buffer.from("%PDF-1.4 fake"), @@ -132,8 +132,8 @@ async function stubPdfToolInfra( provider: params?.provider ?? "anthropic", api: params?.api ?? - (params?.provider === "openai-codex" - ? "openai-codex-responses" + (params?.provider === "openai" + ? "openai-chatgpt-responses" : params?.provider === "openai" ? "openai-responses" : "anthropic-messages"), @@ -557,7 +557,11 @@ describe("createPdfTool", () => { it("uses extraction fallback for non-native models", async () => { await withTempPdfAgentDir(async (agentDir) => { - await stubPdfToolInfra(agentDir, { provider: "openai", input: ["text"] }); + await stubPdfToolInfra(agentDir, { + provider: "openai", + api: "openai-responses", + input: ["text"], + }); const extractSpy = vi.spyOn(pdfExtractModule, "extractPdfContent").mockResolvedValue({ text: "Extracted content", images: [], @@ -641,8 +645,8 @@ describe("createPdfTool", () => { it("adds Codex instructions for PDF extraction fallback requests", async () => { await withTempPdfAgentDir(async (agentDir) => { await stubPdfToolInfra(agentDir, { - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", input: ["text", "image"], }); @@ -678,8 +682,8 @@ describe("createPdfTool", () => { it("adds Codex instructions when extraction has images but the model only accepts text", async () => { await withTempPdfAgentDir(async (agentDir) => { await stubPdfToolInfra(agentDir, { - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", input: ["text"], }); diff --git a/src/agents/tools/pdf-tool.ts b/src/agents/tools/pdf-tool.ts index 7b9bf2b7e443..1807c5f35fbf 100644 --- a/src/agents/tools/pdf-tool.ts +++ b/src/agents/tools/pdf-tool.ts @@ -121,7 +121,8 @@ function buildPdfExtractionContext( // Add the user prompt content.push({ type: "text", text: prompt }); - const systemPrompt = model?.api === "openai-codex-responses" ? CODEX_PDF_INSTRUCTIONS : undefined; + const systemPrompt = + model?.api === "openai-chatgpt-responses" ? CODEX_PDF_INSTRUCTIONS : undefined; return { ...(systemPrompt ? { systemPrompt } : {}), diff --git a/src/agents/tools/video-generate-tool.test.ts b/src/agents/tools/video-generate-tool.test.ts index 42b86f73f101..fda076e73e99 100644 --- a/src/agents/tools/video-generate-tool.test.ts +++ b/src/agents/tools/video-generate-tool.test.ts @@ -351,7 +351,7 @@ describe("createVideoGenerateTool", () => { expectVideoGenerateTool( createVideoGenerateTool({ config: asConfig({}), - authProfileStore: createAuthStore(["openai-codex"]), + authProfileStore: createAuthStore(["openai"]), }), ); }); @@ -415,7 +415,7 @@ describe("createVideoGenerateTool", () => { config: asConfig({ agents: { defaults: { - videoGenerationModel: { primary: "openai-codex/sora-2" }, + videoGenerationModel: { primary: "openai/sora-2" }, }, }, }), @@ -571,7 +571,7 @@ describe("createVideoGenerateTool", () => { }, { id: "openai", - aliases: ["openai-codex"], + aliases: ["openai"], defaultModel: "sora-2", models: ["sora-2"], capabilities: {}, @@ -586,7 +586,7 @@ describe("createVideoGenerateTool", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.5", + primary: "openai/gpt-5.5", }, }, }, diff --git a/src/agents/transcript-policy.test.ts b/src/agents/transcript-policy.test.ts index e8a3a5792976..2727dd0779e5 100644 --- a/src/agents/transcript-policy.test.ts +++ b/src/agents/transcript-policy.test.ts @@ -24,7 +24,7 @@ vi.mock("../plugins/provider-hook-runtime.js", async () => { "mistral", "moonshot", "openai", - "openai-codex", + "openai", "opencode", "opencode-go", "ollama", @@ -132,7 +132,6 @@ vi.mock("../plugins/provider-hook-runtime.js", async () => { toolCallIdMode: "strict9", }; case "openai": - case "openai-codex": return { sanitizeMode: "images-only", sanitizeToolCallIds: context?.modelApi === "openai-completions", diff --git a/src/agents/transcript-policy.ts b/src/agents/transcript-policy.ts index 73b72af63f79..912ed0cefe20 100644 --- a/src/agents/transcript-policy.ts +++ b/src/agents/transcript-policy.ts @@ -80,7 +80,7 @@ function isAnthropicApi(modelApi?: string | null): boolean { function isOpenAiResponsesCompatibleApi(modelApi?: string | null): boolean { return ( modelApi === "openai-responses" || - modelApi === "openai-codex-responses" || + modelApi === "openai-chatgpt-responses" || modelApi === "azure-openai-responses" ); } @@ -113,7 +113,7 @@ function buildUnownedProviderTransportReplayFallback(params: { const requiresOpenAiCompatibleToolIdSanitization = params.modelApi === "openai-completions" || params.modelApi === "openai-responses" || - params.modelApi === "openai-codex-responses" || + params.modelApi === "openai-chatgpt-responses" || params.modelApi === "azure-openai-responses"; if ( diff --git a/src/agents/transport-message-transform.ts b/src/agents/transport-message-transform.ts index 7f7d134b7a82..cf68052e1517 100644 --- a/src/agents/transport-message-transform.ts +++ b/src/agents/transport-message-transform.ts @@ -8,7 +8,7 @@ const SYNTHETIC_TOOL_RESULT_APIS = new Set([ "google-generative-ai", "openclaw-google-generative-ai-transport", "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "azure-openai-responses", "openclaw-openai-responses-transport", "openclaw-azure-openai-responses-transport", @@ -20,7 +20,7 @@ const SYNTHETIC_TOOL_RESULT_APIS = new Set([ // tool-replay-repair.live.test.ts exercises both paths against real models. const CODEX_STYLE_ABORTED_OUTPUT_APIS = new Set([ "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "azure-openai-responses", "openclaw-openai-responses-transport", "openclaw-azure-openai-responses-transport", diff --git a/src/agents/transport-params-runtime-contract.test.ts b/src/agents/transport-params-runtime-contract.test.ts index c00394192173..f3513574f6ef 100644 --- a/src/agents/transport-params-runtime-contract.test.ts +++ b/src/agents/transport-params-runtime-contract.test.ts @@ -83,15 +83,15 @@ describe("transport params runtime contract (embedded OpenClaw/OpenAI path)", () }, ); - it("injects parallel_tool_calls into openai-codex Responses payloads", () => { + it("injects parallel_tool_calls into openai Responses payloads", () => { const payload = runPayloadMutation({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "gpt-5.4", model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, }); expect(payload.parallel_tool_calls).toBe(true); @@ -106,15 +106,15 @@ describe("transport params runtime contract (embedded OpenClaw/OpenAI path)", () }); const payload = runPayloadMutation({ - applyProvider: "openai-codex", + applyProvider: "openai", applyModelId: "gpt-5.4", thinkingLevel: "high", model: { - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", id: "gpt-5.4", baseUrl: "https://chatgpt.com/backend-api", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, payload: { reasoning: { effort: "none", summary: "auto" } }, }); @@ -170,7 +170,7 @@ describe("transport params runtime contract (embedded OpenClaw/OpenAI path)", () function runPayloadMutation(params: { applyProvider: string; applyModelId: string; - model: Model<"openai-codex-responses"> | Model<"openai-responses">; + model: Model<"openai-chatgpt-responses"> | Model<"openai-responses">; thinkingLevel?: Parameters[5]; payload?: Record; }): Record { diff --git a/src/auto-reply/fallback-state.test.ts b/src/auto-reply/fallback-state.test.ts index da05affc0924..3d20b128ee10 100644 --- a/src/auto-reply/fallback-state.test.ts +++ b/src/auto-reply/fallback-state.test.ts @@ -185,7 +185,7 @@ describe("fallback-state", () => { buildFallbackNotice({ selectedProvider: "openai", selectedModel: model, - activeProvider: "openai-codex", + activeProvider: "openai", activeModel: model, attempts: [], }), @@ -198,7 +198,7 @@ describe("fallback-state", () => { buildFallbackNotice({ selectedProvider: "openai", selectedModel: "gpt-5.5", - activeProvider: "openai-codex", + activeProvider: "openai", activeModel: "gpt-5.4", attempts: [], }), diff --git a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts index af23451a0ba0..a5feffc19086 100644 --- a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts +++ b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts @@ -32,9 +32,9 @@ const DEFAULT_TEST_MODEL_CATALOG: Array<{ { id: "gpt-5.4-pro", name: "GPT-5.4 Pro", provider: "openai" }, { id: "gpt-5.4-mini", name: "GPT-5.4 Mini", provider: "openai" }, { id: "gpt-5.4-nano", name: "GPT-5.4 Nano", provider: "openai" }, - { id: "gpt-5.4", name: "GPT-5.4 (Codex)", provider: "openai-codex" }, - { id: "gpt-5.4-pro", name: "GPT-5.4 Pro (Codex)", provider: "openai-codex" }, - { id: "gpt-5.4-mini", name: "GPT-5.4 Mini (Codex)", provider: "openai-codex" }, + { id: "gpt-5.4", name: "GPT-5.4 (Codex)", provider: "openai" }, + { id: "gpt-5.4-pro", name: "GPT-5.4 Pro (Codex)", provider: "openai" }, + { id: "gpt-5.4-mini", name: "GPT-5.4 Mini (Codex)", provider: "openai" }, { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, ]; @@ -82,7 +82,7 @@ function createDirectiveBehaviorProviderRegistry(): ReturnType { profiles: { [TEST_PRIMARY_PROFILE_ID]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "oauth-access-token-josh", }, [TEST_SECONDARY_PROFILE_ID]: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "oauth-access-token", }, }, @@ -799,7 +799,7 @@ describe("trigger handling", () => { { version: 1, order: { - "openai-codex": [TEST_PRIMARY_PROFILE_ID], + openai: [TEST_PRIMARY_PROFILE_ID], }, }, null, @@ -814,7 +814,7 @@ describe("trigger handling", () => { const res = await getReplyFromConfig( makeNativeTelegramCommandMessage({ - body: `/model openai-codex/gpt-5.4@${TEST_SECONDARY_PROFILE_ID}`, + body: `/model openai/gpt-5.4@${TEST_SECONDARY_PROFILE_ID}`, slashSessionKey, targetSessionKey, }), @@ -832,7 +832,7 @@ describe("trigger handling", () => { await expectNextRunUsesTargetSession( { cfg, targetSessionKey, runEmbeddedAgentMock }, { - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", authProfileId: TEST_SECONDARY_PROFILE_ID, authProfileIdSource: "user", diff --git a/src/auto-reply/reply/agent-runner-execution.test.ts b/src/auto-reply/reply/agent-runner-execution.test.ts index fb98bd6f192a..7e63650203ad 100644 --- a/src/auto-reply/reply/agent-runner-execution.test.ts +++ b/src/auto-reply/reply/agent-runner-execution.test.ts @@ -2962,7 +2962,7 @@ describe("runAgentTurnWithFallback", () => { const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); const followupRun = createFollowupRun(); - followupRun.run.provider = "openai-codex"; + followupRun.run.provider = "openai"; followupRun.run.model = "gpt-5.5"; const result = await runAgentTurnWithFallback({ @@ -2998,7 +2998,7 @@ describe("runAgentTurnWithFallback", () => { it("classifies GPT-5 plan-only terminal results as fallback-eligible", async () => { const followupRun = createFollowupRun(); - followupRun.run.provider = "openai-codex"; + followupRun.run.provider = "openai"; followupRun.run.model = "gpt-5.4"; state.runEmbeddedAgentMock.mockResolvedValueOnce({ payloads: [ @@ -3010,12 +3010,12 @@ describe("runAgentTurnWithFallback", () => { meta: {}, }); state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => { - const first = (await params.run("openai-codex", "gpt-5.4")) as { + const first = (await params.run("openai", "gpt-5.4")) as { payloads?: Array<{ text?: string; isError?: boolean; isReasoning?: boolean }>; }; const classification = await params.classifyResult?.({ result: first, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempt: 1, total: 2, @@ -3030,7 +3030,7 @@ describe("runAgentTurnWithFallback", () => { model: "claude", attempts: [ { - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", error: "planning-only", reason: "format", @@ -3056,7 +3056,7 @@ describe("runAgentTurnWithFallback", () => { expect( await params.classifyResult?.({ result, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempt: 1, total: 2, @@ -3064,7 +3064,7 @@ describe("runAgentTurnWithFallback", () => { ).toBeNull(); return { result, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempts: [], }; @@ -3078,7 +3078,7 @@ describe("runAgentTurnWithFallback", () => { it("does not classify empty final payloads after block replies were sent", async () => { const followupRun = createFollowupRun(); - followupRun.run.provider = "openai-codex"; + followupRun.run.provider = "openai"; followupRun.run.model = "gpt-5.4"; state.createBlockReplyDeliveryHandlerMock.mockImplementationOnce( (params: { directlySentBlockKeys?: Set }) => async () => { @@ -3090,13 +3090,13 @@ describe("runAgentTurnWithFallback", () => { return { payloads: [], meta: {} }; }); state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => { - const result = (await params.run("openai-codex", "gpt-5.4")) as { + const result = (await params.run("openai", "gpt-5.4")) as { payloads?: Array<{ text?: string; isError?: boolean; isReasoning?: boolean }>; }; expect( await params.classifyResult?.({ result, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempt: 1, total: 2, @@ -3104,7 +3104,7 @@ describe("runAgentTurnWithFallback", () => { ).toBeNull(); return { result, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempts: [], }; @@ -3123,7 +3123,7 @@ describe("runAgentTurnWithFallback", () => { it("does not classify empty final payloads while block replies are buffered", async () => { const followupRun = createFollowupRun(); - followupRun.run.provider = "openai-codex"; + followupRun.run.provider = "openai"; followupRun.run.model = "gpt-5.4"; const blockReplyPipeline = { enqueue: vi.fn(), @@ -3140,7 +3140,7 @@ describe("runAgentTurnWithFallback", () => { expect( await params.classifyResult?.({ result, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempt: 1, total: 2, @@ -3148,7 +3148,7 @@ describe("runAgentTurnWithFallback", () => { ).toBeNull(); return { result, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempts: [], }; @@ -3170,7 +3170,7 @@ describe("runAgentTurnWithFallback", () => { const result = { payloads: [], meta: {} }; const classification = await params.classifyResult?.({ result, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempt: 1, total: 1, @@ -3181,7 +3181,7 @@ describe("runAgentTurnWithFallback", () => { }); return { result, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempts: [], }; @@ -3206,12 +3206,12 @@ describe("runAgentTurnWithFallback", () => { const activeSessionStore = { main: sessionEntry }; state.runEmbeddedAgentMock.mockResolvedValueOnce({ payloads: [], meta: {} }); state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => { - const failedResult = await params.run("openai-codex", "gpt-5.4"); + const failedResult = await params.run("openai", "gpt-5.4"); expect(sessionEntry.providerOverride).toBe("openai"); expect(sessionEntry.modelOverride).toBe("gpt-5.4"); const classification = await params.classifyResult?.({ result: failedResult as { payloads?: [] }, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", attempt: 1, total: 2, @@ -5101,7 +5101,7 @@ describe("runAgentTurnWithFallback", () => { "keeps raw runner failure boilerplate out of Discord %s chats", async (chatType) => { state.runEmbeddedAgentMock.mockRejectedValueOnce( - new Error("openai-codex/gpt-5.5 ended with an incomplete terminal response"), + new Error("openai/gpt-5.5 ended with an incomplete terminal response"), ); const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); @@ -5129,7 +5129,7 @@ describe("runAgentTurnWithFallback", () => { "surfaces raw runner failure copy in Discord %s chats when silentReply.group is set to disallow", async (chatType) => { state.runEmbeddedAgentMock.mockRejectedValueOnce( - new Error("openai-codex/gpt-5.5 ended with an incomplete terminal response"), + new Error("openai/gpt-5.5 ended with an incomplete terminal response"), ); const followupRun = createFollowupRun(); @@ -5166,7 +5166,7 @@ describe("runAgentTurnWithFallback", () => { it("surfaces raw runner failure copy when per-surface silentReply.group is set to disallow", async () => { state.runEmbeddedAgentMock.mockRejectedValueOnce( - new Error("openai-codex/gpt-5.5 ended with an incomplete terminal response"), + new Error("openai/gpt-5.5 ended with an incomplete terminal response"), ); const followupRun = createFollowupRun(); @@ -5211,7 +5211,7 @@ describe("runAgentTurnWithFallback", () => { // to the documented default `group: "allow"` and produce a silent payload // — the new policy hookup must not regress the default behavior. state.runEmbeddedAgentMock.mockRejectedValueOnce( - new Error("openai-codex/gpt-5.5 ended with an incomplete terminal response"), + new Error("openai/gpt-5.5 ended with an incomplete terminal response"), ); const followupRun = createFollowupRun(); @@ -5330,7 +5330,7 @@ describe("runAgentTurnWithFallback", () => { it("uses compact generic copy for raw runner failures in normal Discord direct chats", async () => { state.runEmbeddedAgentMock.mockRejectedValueOnce( - new Error("openai-codex/gpt-5.5 ended with an incomplete terminal response"), + new Error("openai/gpt-5.5 ended with an incomplete terminal response"), ); const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); @@ -5353,7 +5353,7 @@ describe("runAgentTurnWithFallback", () => { it("keeps raw runner failure guidance visible in verbose Discord direct chats", async () => { state.runEmbeddedAgentMock.mockRejectedValueOnce( - new Error("openai-codex/gpt-5.5 ended with an incomplete terminal response"), + new Error("openai/gpt-5.5 ended with an incomplete terminal response"), ); const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); @@ -5542,7 +5542,7 @@ describe("runAgentTurnWithFallback", () => { it("surfaces gateway reauth guidance for known OAuth refresh failures", async () => { state.runEmbeddedAgentMock.mockRejectedValueOnce( new Error( - "OAuth token refresh failed for openai-codex: refresh_token_reused. Please try again or re-authenticate.", + "OAuth token refresh failed for openai: refresh_token_reused. Please try again or re-authenticate.", ), ); @@ -5612,14 +5612,14 @@ describe("runAgentTurnWithFallback", () => { expect(result.kind).toBe("final"); if (result.kind === "final") { expect(result.payload.text).toBe( - "⚠️ Missing API key for OpenAI on the gateway. Use `openai/gpt-5.5` with the Codex OAuth profile, or set `OPENAI_API_KEY` for direct OpenAI API-key runs.", + "⚠️ Missing API key for OpenAI on the gateway. Use `openai/gpt-5.5` with the OpenAI OAuth profile, or set `OPENAI_API_KEY` for direct OpenAI API-key runs.", ); } }); - it("points stale openai-codex missing-key failures at doctor repair with re-auth fallback", async () => { + it("points stale openai missing-key failures at doctor repair with re-auth fallback", async () => { state.runEmbeddedAgentMock.mockRejectedValueOnce( - new Error('No API key found for provider "openai-codex".'), + new Error('No API key found for provider "openai".'), ); const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); @@ -5628,7 +5628,7 @@ describe("runAgentTurnWithFallback", () => { expect(result.kind).toBe("final"); if (result.kind === "final") { expect(result.payload.text).toBe( - "⚠️ The session is pointing at a stale OpenAI Codex auth route. Run `openclaw doctor --fix` to repair Codex model/session routes, restart the gateway if doctor asks, then try again. If doctor has nothing to repair or the error persists, re-auth with `openclaw models auth login --provider openai` or run `openclaw configure`.", + '⚠️ Missing API key for provider "openai". Run `openclaw doctor --fix` to repair stale OpenAI model/session routes, restart the gateway if doctor asks, then try again. If doctor has nothing to repair or the error persists, re-auth with `openclaw models auth login --provider openai` or run `openclaw configure`.', ); } }); @@ -5673,7 +5673,7 @@ describe("runAgentTurnWithFallback", () => { it("falls back to a generic reauth command when the provider in the OAuth error is unsafe", async () => { state.runEmbeddedAgentMock.mockRejectedValueOnce( new Error( - "OAuth token refresh failed for openai-codex`\nrm -rf /: invalid_grant. Please try again or re-authenticate.", + "OAuth token refresh failed for openai`\nrm -rf /: invalid_grant. Please try again or re-authenticate.", ), ); @@ -6121,8 +6121,8 @@ describe("runAgentTurnWithFallback", () => { it("drops authProfileId when fallback switches providers", async () => { state.runWithModelFallbackMock.mockImplementation( async (params: { run: (provider: string, model: string) => Promise }) => ({ - result: await params.run("openai-codex", "gpt-5.4"), - provider: "openai-codex", + result: await params.run("openai", "gpt-5.4"), + provider: "openai", model: "gpt-5.4", attempts: [], }), @@ -6195,8 +6195,8 @@ describe("runAgentTurnWithFallback", () => { // session-reset-service. state.runWithModelFallbackMock.mockImplementation( async (params: { run: (provider: string, model: string) => Promise }) => ({ - result: await params.run("openai-codex", "gpt-5.4"), - provider: "openai-codex", + result: await params.run("openai", "gpt-5.4"), + provider: "openai", model: "gpt-5.4", attempts: [], }), @@ -6257,8 +6257,8 @@ describe("runAgentTurnWithFallback", () => { it("persists fallback selection for recovered auto overrides without modelOverrideSource", async () => { state.runWithModelFallbackMock.mockImplementation( async (params: { run: (provider: string, model: string) => Promise }) => ({ - result: await params.run("openai-codex", "gpt-5.4"), - provider: "openai-codex", + result: await params.run("openai", "gpt-5.4"), + provider: "openai", model: "gpt-5.4", attempts: [], }), @@ -6324,8 +6324,8 @@ describe("runAgentTurnWithFallback", () => { // should NOT clobber it even when the primary model fails. state.runWithModelFallbackMock.mockImplementation( async (params: { run: (provider: string, model: string) => Promise }) => ({ - result: await params.run("openai-codex", "gpt-5.4"), - provider: "openai-codex", + result: await params.run("openai", "gpt-5.4"), + provider: "openai", model: "gpt-5.4", attempts: [], }), diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index 9cdcf90cf27c..9f5862c312d3 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -42,7 +42,7 @@ import { resolveModelRefFromString, resolvePersistedOverrideModelRef, } from "../../agents/model-selection.js"; -import { resolveOpenAIRuntimeProvider } from "../../agents/openai-codex-routing.js"; +import { resolveOpenAIRuntimeProvider } from "../../agents/openai-routing.js"; import { buildAgentRuntimeOutcomePlan } from "../../agents/runtime-plan/build.js"; import { resolveGroupSessionKey, @@ -622,7 +622,7 @@ function collapseRepeatedFailureDetail(message: string): string { return message.trim(); } -const SAFE_MISSING_API_KEY_PROVIDERS = new Set(["anthropic", "google", "openai", "openai-codex"]); +const SAFE_MISSING_API_KEY_PROVIDERS = new Set(["anthropic", "google", "openai"]); const EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS = 900; const AGENT_FAILED_BEFORE_REPLY_TEXT = "Agent failed before reply:"; @@ -718,10 +718,10 @@ function buildMissingApiKeyFailureText(message: string): string | null { return null; } if (provider === "openai" && normalizedMessage.includes("OpenAI Codex OAuth")) { - return "⚠️ Missing API key for OpenAI on the gateway. Use `openai/gpt-5.5` with the Codex OAuth profile, or set `OPENAI_API_KEY` for direct OpenAI API-key runs."; + return "⚠️ Missing API key for OpenAI on the gateway. Use `openai/gpt-5.5` with the OpenAI OAuth profile, or set `OPENAI_API_KEY` for direct OpenAI API-key runs."; } - if (provider === "openai-codex") { - return "⚠️ The session is pointing at a stale OpenAI Codex auth route. Run `openclaw doctor --fix` to repair Codex model/session routes, restart the gateway if doctor asks, then try again. If doctor has nothing to repair or the error persists, re-auth with `openclaw models auth login --provider openai` or run `openclaw configure`."; + if (provider === "openai") { + return '⚠️ Missing API key for provider "openai". Run `openclaw doctor --fix` to repair stale OpenAI model/session routes, restart the gateway if doctor asks, then try again. If doctor has nothing to repair or the error persists, re-auth with `openclaw models auth login --provider openai` or run `openclaw configure`.'; } if (SAFE_MISSING_API_KEY_PROVIDERS.has(provider)) { return `⚠️ Missing API key for provider "${provider}". Configure the gateway auth for that provider, then try again.`; diff --git a/src/auto-reply/reply/agent-runner-memory.test.ts b/src/auto-reply/reply/agent-runner-memory.test.ts index d96260086836..ddcdf542cfdc 100644 --- a/src/auto-reply/reply/agent-runner-memory.test.ts +++ b/src/auto-reply/reply/agent-runner-memory.test.ts @@ -1296,7 +1296,6 @@ describe("runMemoryFlushIfNeeded", () => { models: { providers: { openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] }, - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, agents: { defaults: { compaction: { memoryFlush: {} } } }, @@ -1341,8 +1340,7 @@ describe("runMemoryFlushIfNeeded", () => { cfg: { models: { providers: { - openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] }, - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, + openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, agents: { defaults: { compaction: { memoryFlush: {} } } }, @@ -1388,7 +1386,6 @@ describe("runMemoryFlushIfNeeded", () => { models: { providers: { openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] }, - "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] }, }, }, agents: { defaults: { compaction: { memoryFlush: {} } } }, diff --git a/src/auto-reply/reply/agent-runner-memory.ts b/src/auto-reply/reply/agent-runner-memory.ts index dda908bc65c5..6efebc56c233 100644 --- a/src/auto-reply/reply/agent-runner-memory.ts +++ b/src/auto-reply/reply/agent-runner-memory.ts @@ -12,7 +12,7 @@ import { resolveAgentHarnessPolicy } from "../../agents/harness/policy.js"; import { ensureSelectedAgentHarnessPlugin } from "../../agents/harness/runtime-plugin.js"; import { runWithModelFallback } from "../../agents/model-fallback.js"; import { isCliProvider } from "../../agents/model-selection.js"; -import { resolveContextConfigProviderForRuntime } from "../../agents/openai-codex-routing.js"; +import { resolveContextConfigProviderForRuntime } from "../../agents/openai-routing.js"; import type { AgentMessage } from "../../agents/runtime/index.js"; import { resolveSandboxConfigForAgent, resolveSandboxRuntimeStatus } from "../../agents/sandbox.js"; import { diff --git a/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts b/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts index a0f89603b9c3..d934d4ac58fe 100644 --- a/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts +++ b/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts @@ -1142,7 +1142,7 @@ describe("runReplyAgent typing (heartbeat)", () => { const sessionEntry: SessionEntry = { sessionId: "session", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", responseUsage: "tokens", }; @@ -1177,7 +1177,7 @@ describe("runReplyAgent typing (heartbeat)", () => { model: "gemini-2.5-flash", attempts: [ { - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", error: "codex app-server attempt timed out", reason: "timeout", @@ -1202,7 +1202,7 @@ describe("runReplyAgent typing (heartbeat)", () => { }); const res = await run(); - expect(sessionEntry.modelProvider).toBe("openai-codex"); + expect(sessionEntry.modelProvider).toBe("openai"); expect(sessionEntry.model).toBe("gpt-5.5"); expect(sessionEntry.providerOverride).toBeUndefined(); expect(sessionEntry.modelOverride).toBeUndefined(); @@ -1211,7 +1211,7 @@ describe("runReplyAgent typing (heartbeat)", () => { expect(sessionEntry.fallbackNoticeActiveModel).toBeUndefined(); expect(sessionEntry.fallbackNoticeReason).toBeUndefined(); const persistedStore = JSON.parse(await readFile(storePath, "utf-8")); - expect(persistedStore.main.modelProvider).toBe("openai-codex"); + expect(persistedStore.main.modelProvider).toBe("openai"); expect(persistedStore.main.model).toBe("gpt-5.5"); expect(persistedStore.main.providerOverride).toBeUndefined(); expect(persistedStore.main.modelOverride).toBeUndefined(); @@ -1230,7 +1230,7 @@ describe("runReplyAgent typing (heartbeat)", () => { const sessionEntry: SessionEntry = { sessionId: "session", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", }; const sessionStore = { main: sessionEntry }; @@ -1253,7 +1253,7 @@ describe("runReplyAgent typing (heartbeat)", () => { model: "gemini-2.5-flash", attempts: [ { - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", error: "codex app-server attempt timed out", reason: "timeout", @@ -1280,7 +1280,7 @@ describe("runReplyAgent typing (heartbeat)", () => { const payload = Array.isArray(res) ? res[0] : res; expect(payload?.isError).toBe(true); expect(payload?.text).toContain("Fallback used google/gemini-2.5-flash"); - expect(sessionEntry.modelProvider).toBe("openai-codex"); + expect(sessionEntry.modelProvider).toBe("openai"); expect(sessionEntry.model).toBe("gpt-5.5"); expect(sessionEntry.providerOverride).toBeUndefined(); expect(sessionEntry.modelOverride).toBeUndefined(); @@ -1404,8 +1404,8 @@ describe("runReplyAgent typing (heartbeat)", () => { .spyOn(modelFallbackModule, "runWithModelFallback") .mockImplementationOnce( async ({ run }: { run: (provider: string, model: string) => Promise }) => ({ - result: await run("openai-codex", "gpt-5.5"), - provider: "openai-codex", + result: await run("openai", "gpt-5.5"), + provider: "openai", model: "gpt-5.5", attempts: [ { @@ -1435,7 +1435,7 @@ describe("runReplyAgent typing (heartbeat)", () => { expect(payload?.isError).toBe(true); expect(payload?.text).toContain("configured model backend lmstudio/gemma-4-e4b-it"); - expect(payload?.text).toContain("Fallback used openai-codex/gpt-5.5"); + expect(payload?.text).toContain("Fallback used openai/gpt-5.5"); expect(payload?.text).toContain("no visible reply"); } finally { fallbackSpy.mockRestore(); @@ -1451,8 +1451,8 @@ describe("runReplyAgent typing (heartbeat)", () => { .spyOn(modelFallbackModule, "runWithModelFallback") .mockImplementationOnce( async ({ run }: { run: (provider: string, model: string) => Promise }) => ({ - result: await run("openai-codex", "gpt-5.5"), - provider: "openai-codex", + result: await run("openai", "gpt-5.5"), + provider: "openai", model: "gpt-5.5", attempts: [ { @@ -1482,7 +1482,7 @@ describe("runReplyAgent typing (heartbeat)", () => { expect(payload?.isError).toBe(true); expect(payload?.text).toContain("configured model backend lmstudio/gemma-4-e4b-it"); - expect(payload?.text).toContain("Fallback used openai-codex/gpt-5.5"); + expect(payload?.text).toContain("Fallback used openai/gpt-5.5"); expect(payload?.text).toContain("no visible reply"); } finally { fallbackSpy.mockRestore(); @@ -1493,7 +1493,7 @@ describe("runReplyAgent typing (heartbeat)", () => { const sessionEntry: SessionEntry = { sessionId: "session", updatedAt: Date.now(), - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.5", modelOverrideSource: "auto", modelOverrideFallbackOriginProvider: "lmstudio", @@ -1507,7 +1507,7 @@ describe("runReplyAgent typing (heartbeat)", () => { const { run } = createMinimalRun({ runOverrides: { - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", }, sessionEntry, @@ -1523,7 +1523,7 @@ describe("runReplyAgent typing (heartbeat)", () => { expect(payload?.isError).toBe(true); expect(payload?.text).toContain("configured model backend lmstudio/gemma-4-e4b-it"); - expect(payload?.text).toContain("Fallback used openai-codex/gpt-5.5"); + expect(payload?.text).toContain("Fallback used openai/gpt-5.5"); expect(payload?.text).toContain("no visible reply"); }); @@ -1538,8 +1538,8 @@ describe("runReplyAgent typing (heartbeat)", () => { .spyOn(modelFallbackModule, "runWithModelFallback") .mockImplementationOnce( async ({ run }: { run: (provider: string, model: string) => Promise }) => ({ - result: await run("openai-codex", "gpt-5.5"), - provider: "openai-codex", + result: await run("openai", "gpt-5.5"), + provider: "openai", model: "gpt-5.5", attempts: [ { @@ -1593,8 +1593,8 @@ describe("runReplyAgent typing (heartbeat)", () => { .spyOn(modelFallbackModule, "runWithModelFallback") .mockImplementationOnce( async ({ run }: { run: (provider: string, model: string) => Promise }) => ({ - result: await run("openai-codex", "gpt-5.5"), - provider: "openai-codex", + result: await run("openai", "gpt-5.5"), + provider: "openai", model: "gpt-5.5", attempts: [ { @@ -1627,7 +1627,7 @@ describe("runReplyAgent typing (heartbeat)", () => { expect(payload?.isError).toBe(true); expect(payload?.text).toContain("configured model backend lmstudio/gemma-4-e4b-it"); - expect(payload?.text).toContain("Fallback used openai-codex/gpt-5.5"); + expect(payload?.text).toContain("Fallback used openai/gpt-5.5"); } finally { fallbackSpy.mockRestore(); } @@ -1643,8 +1643,8 @@ describe("runReplyAgent typing (heartbeat)", () => { .spyOn(modelFallbackModule, "runWithModelFallback") .mockImplementationOnce( async ({ run }: { run: (provider: string, model: string) => Promise }) => ({ - result: await run("openai-codex", "gpt-5.5"), - provider: "openai-codex", + result: await run("openai", "gpt-5.5"), + provider: "openai", model: "gpt-5.5", attempts: [ { @@ -1694,8 +1694,8 @@ describe("runReplyAgent typing (heartbeat)", () => { .spyOn(modelFallbackModule, "runWithModelFallback") .mockImplementationOnce( async ({ run }: { run: (provider: string, model: string) => Promise }) => ({ - result: await run("openai-codex", "gpt-5.5"), - provider: "openai-codex", + result: await run("openai", "gpt-5.5"), + provider: "openai", model: "gpt-5.5", attempts: [ { @@ -1745,8 +1745,8 @@ describe("runReplyAgent typing (heartbeat)", () => { .spyOn(modelFallbackModule, "runWithModelFallback") .mockImplementationOnce( async ({ run }: { run: (provider: string, model: string) => Promise }) => ({ - result: await run("openai-codex", "gpt-5.5"), - provider: "openai-codex", + result: await run("openai", "gpt-5.5"), + provider: "openai", model: "gpt-5.5", attempts: [ { @@ -1792,8 +1792,8 @@ describe("runReplyAgent typing (heartbeat)", () => { .spyOn(modelFallbackModule, "runWithModelFallback") .mockImplementationOnce( async ({ run }: { run: (provider: string, model: string) => Promise }) => ({ - result: await run("openai-codex", "gpt-5.5"), - provider: "openai-codex", + result: await run("openai", "gpt-5.5"), + provider: "openai", model: "gpt-5.5", attempts: [ { diff --git a/src/auto-reply/reply/commands-compact.test.ts b/src/auto-reply/reply/commands-compact.test.ts index 97eea4b03eb5..95c2fda9e5c8 100644 --- a/src/auto-reply/reply/commands-compact.test.ts +++ b/src/auto-reply/reply/commands-compact.test.ts @@ -437,7 +437,7 @@ describe("handleCompactCommand", () => { channels: { whatsapp: { allowFrom: ["*"] } }, models: { providers: { - "openai-codex": { + openai: { models: [{ id: "gpt-5.5", contextWindow: 258_000 }], }, }, diff --git a/src/auto-reply/reply/commands-compact.ts b/src/auto-reply/reply/commands-compact.ts index 44ed25e4af41..952e4e5d3175 100644 --- a/src/auto-reply/reply/commands-compact.ts +++ b/src/auto-reply/reply/commands-compact.ts @@ -6,7 +6,7 @@ import { OPENAI_CODEX_PROVIDER_ID, OPENAI_PROVIDER_ID, resolveContextConfigProviderForRuntime, -} from "../../agents/openai-codex-routing.js"; +} from "../../agents/openai-routing.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logVerbose } from "../../globals.js"; import { createLazyImportLoader } from "../../shared/lazy-promise.js"; diff --git a/src/auto-reply/reply/commands-models.test.ts b/src/auto-reply/reply/commands-models.test.ts index 41816230fb86..fa6c1503a625 100644 --- a/src/auto-reply/reply/commands-models.test.ts +++ b/src/auto-reply/reply/commands-models.test.ts @@ -352,12 +352,12 @@ describe("handleModelsCommand", () => { it("does not re-add the default provider when provider visibility is restricted", async () => { modelCatalogMocks.loadModelCatalog.mockResolvedValue([ { provider: "anthropic", id: "claude-opus-4-5", name: "Claude Opus" }, - { provider: "openai-codex", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, - { provider: "openai-codex", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, + { provider: "openai", id: "gpt-5.4-codex", name: "GPT-5.4 Codex" }, + { provider: "openai", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, { provider: "vllm", id: "llama-local", name: "Llama Local" }, { provider: "vllm", id: "qwen3-local", name: "Qwen3 Local" }, ]); - modelProviderAuthMocks.authenticatedProviders = new Set(["anthropic", "openai-codex", "vllm"]); + modelProviderAuthMocks.authenticatedProviders = new Set(["anthropic", "openai", "vllm"]); const result = await handleModelsCommand( buildParams("/models", { @@ -365,7 +365,7 @@ describe("handleModelsCommand", () => { defaults: { model: { primary: "anthropic/claude-opus-4-5" }, models: { - "openai-codex/*": {}, + "openai/*": {}, "vllm/*": {}, }, }, @@ -375,7 +375,7 @@ describe("handleModelsCommand", () => { ); expect(modelCatalogMocks.loadModelCatalog.mock.calls[0]?.[0]?.readOnly).toBe(false); - expect(result?.reply?.text).toContain("- openai-codex (2)"); + expect(result?.reply?.text).toContain("- openai (2)"); expect(result?.reply?.text).toContain("- vllm (2)"); expect(result?.reply?.text).not.toContain("- anthropic"); }); @@ -744,7 +744,7 @@ describe("handleModelsCommand", () => { it("does not list bare fallback models under the default provider when catalog ownership is unique", async () => { modelCatalogMocks.loadModelCatalog.mockResolvedValue([ - { provider: "openai-codex", id: "gpt-5.4", name: "GPT-5.4" }, + { provider: "openai", id: "gpt-5.4", name: "GPT-5.4" }, { provider: "deepseek", id: "deepseek-v4-flash", name: "DeepSeek V4 Flash" }, { provider: "deepseek", id: "deepseek-v4-pro", name: "DeepSeek V4 Pro" }, ]); @@ -752,11 +752,11 @@ describe("handleModelsCommand", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.4", + primary: "openai/gpt-5.4", fallbacks: ["deepseek-v4-flash", "deepseek-v4-pro"], }, models: { - "openai-codex/gpt-5.4": {}, + "openai/gpt-5.4": {}, }, }, }, @@ -764,7 +764,7 @@ describe("handleModelsCommand", () => { const data = await buildModelsProviderData(cfg as OpenClawConfig); - expect([...(data.byProvider.get("openai-codex") ?? [])]).toEqual(["gpt-5.4"]); + expect([...(data.byProvider.get("openai") ?? [])]).toEqual(["gpt-5.4"]); expect([...(data.byProvider.get("deepseek") ?? [])].toSorted()).toEqual([ "deepseek-v4-flash", "deepseek-v4-pro", @@ -803,31 +803,27 @@ describe("handleModelsCommand", () => { expect(authLabelParams.workspaceDir).toBe("/tmp"); }); - it("labels OpenAI provider pages with canonical and legacy auth provider ids", async () => { - modelAuthLabelMocks.resolveModelAuthLabel.mockReturnValue( - "oauth (openai-codex:user@example.com)", - ); + it("labels OpenAI provider pages with the canonical auth provider id", async () => { + modelAuthLabelMocks.resolveModelAuthLabel.mockReturnValue("oauth (openai:user@example.com)"); const result = await handleModelsCommand( buildParams("/models openai", { auth: { order: { - openai: ["openai-codex:user@example.com"], + openai: ["openai:user@example.com"], }, }, }), true, ); - expect(result?.reply?.text).toContain( - "Models (openai · 🔑 oauth (openai-codex:user@example.com))", - ); + expect(result?.reply?.text).toContain("Models (openai · 🔑 oauth (openai:user@example.com))"); const openaiAuthCall = modelAuthLabelMocks.resolveModelAuthLabel.mock.calls.find( ([params]) => (params as { provider?: string }).provider === "openai", ); expect(openaiAuthCall?.[0]).toMatchObject({ provider: "openai", - acceptedProviderIds: ["openai-codex", "openai"], + acceptedProviderIds: ["openai"], }); }); diff --git a/src/auto-reply/reply/commands-models.ts b/src/auto-reply/reply/commands-models.ts index 95229b8d7fa9..0df73d855091 100644 --- a/src/auto-reply/reply/commands-models.ts +++ b/src/auto-reply/reply/commands-models.ts @@ -23,7 +23,7 @@ import { RUNTIME_MODEL_VISIBILITY_NORMALIZATION, createModelVisibilityPolicy, } from "../../agents/model-visibility-policy.js"; -import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-codex-routing.js"; +import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-routing.js"; import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js"; import { getChannelPlugin } from "../../channels/plugins/index.js"; import type { SessionEntry } from "../../config/sessions.js"; diff --git a/src/auto-reply/reply/commands-plugin.test.ts b/src/auto-reply/reply/commands-plugin.test.ts index 070d84c45257..a66b31cd2fe5 100644 --- a/src/auto-reply/reply/commands-plugin.test.ts +++ b/src/auto-reply/reply/commands-plugin.test.ts @@ -100,7 +100,7 @@ describe("handlePluginCommand", () => { [params.sessionKey]: { sessionId: "target-session", sessionFile: "/tmp/target-session.jsonl", - authProfileOverride: "openai-codex:owner@example.com", + authProfileOverride: "openai:owner@example.com", updatedAt: Date.now(), }, }; @@ -113,7 +113,7 @@ describe("handlePluginCommand", () => { >; expect(commandParams.sessionId).toBe("target-session"); expect(commandParams.sessionFile).toBe("/tmp/target-session.jsonl"); - expect(commandParams.authProfileId).toBe("openai-codex:owner@example.com"); + expect(commandParams.authProfileId).toBe("openai:owner@example.com"); }); it("continues the agent without leaking continueAgent into the reply payload", async () => { diff --git a/src/auto-reply/reply/commands-status.test.ts b/src/auto-reply/reply/commands-status.test.ts index 150f26a4c146..866d342b6cc7 100644 --- a/src/auto-reply/reply/commands-status.test.ts +++ b/src/auto-reply/reply/commands-status.test.ts @@ -96,7 +96,7 @@ async function buildStatusReplyForTest(params: { sessionKey?: string; verbose?: } function registerStatusCodexHarness(): void { - const codexProviders = new Set(["codex", "openai", "openai-codex"]); + const codexProviders = new Set(["codex", "openai"]); const harness: AgentHarness = { id: "codex", label: "Codex", @@ -641,9 +641,9 @@ describe("buildStatusReply subagent summary", () => { JSON.stringify({ version: 1, profiles: { - "openai-codex:status": { + "openai:status": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60 * 60_000, @@ -657,7 +657,7 @@ describe("buildStatusReply subagent summary", () => { updatedAt: Date.now(), providers: [ { - provider: "openai-codex", + provider: "openai", displayName: "Codex", windows: [ { @@ -714,21 +714,21 @@ describe("buildStatusReply subagent summary", () => { const normalizedCodex = normalizeTestText(codexText); const normalizedImplicitCodex = normalizeTestText(implicitCodexText); expect(normalizedCodex).toContain("Model: openai/gpt-5.5"); - expect(normalizedCodex).toContain("oauth (openai-codex:status)"); - expect(normalizedCodex).toContain("openai-codex:status"); + expect(normalizedCodex).toContain("oauth (openai:status)"); + expect(normalizedCodex).toContain("openai:status"); expect(normalizedCodex).toContain("Usage: 5h 91% left"); expect(normalizedCodex).toContain("Week 70% left"); expect(normalizedImplicitCodex).toContain("Model: openai/gpt-5.5"); - expect(normalizedImplicitCodex).toContain("oauth (openai-codex:status)"); + expect(normalizedImplicitCodex).toContain("oauth (openai:status)"); expect(normalizedImplicitCodex).toContain("Runtime: OpenAI Codex"); expect(normalizedImplicitCodex).toContain("Usage: 5h 91% left"); const providerUsageCall = providerUsageMock.loadProviderUsageSummary.mock.calls.find( - ([params]) => params?.providers?.includes("openai-codex"), + ([params]) => params?.providers?.includes("openai"), ); if (!providerUsageCall) { - throw new Error("expected provider usage summary call for openai-codex"); + throw new Error("expected provider usage summary call for openai"); } - expect(providerUsageCall[0]?.providers).toEqual(["openai-codex"]); + expect(providerUsageCall[0]?.providers).toEqual(["openai"]); }, { env: { @@ -758,9 +758,9 @@ describe("buildStatusReply subagent summary", () => { JSON.stringify({ version: 1, profiles: { - "openai-codex:status": { + "openai:status": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60 * 60_000, @@ -774,7 +774,7 @@ describe("buildStatusReply subagent summary", () => { updatedAt: Date.now(), providers: [ { - provider: "openai-codex", + provider: "openai", displayName: "Codex", windows: [ { @@ -810,16 +810,16 @@ describe("buildStatusReply subagent summary", () => { const normalized = normalizeTestText(text); expect(normalized).toContain("Model: codex/gpt-5.5"); - expect(normalized).toContain("oauth (openai-codex:status)"); + expect(normalized).toContain("oauth (openai:status)"); expect(normalized).toContain("Runtime: OpenAI Codex"); expect(normalized).toContain("Usage: 5h 92% left"); const providerUsageCall = providerUsageMock.loadProviderUsageSummary.mock.calls.find( - ([params]) => params?.providers?.includes("openai-codex"), + ([params]) => params?.providers?.includes("openai"), ); if (!providerUsageCall) { - throw new Error("expected provider usage summary call for openai-codex"); + throw new Error("expected provider usage summary call for openai"); } - expect(providerUsageCall[0]?.providers).toEqual(["openai-codex"]); + expect(providerUsageCall[0]?.providers).toEqual(["openai"]); }, { env: { @@ -847,9 +847,9 @@ describe("buildStatusReply subagent summary", () => { JSON.stringify({ version: 1, profiles: { - "openai-codex:status": { + "openai:status": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60 * 60_000, @@ -878,7 +878,7 @@ describe("buildStatusReply subagent summary", () => { }, auth: { order: { - openai: ["openai-codex:status", "openai:backup"], + openai: ["openai:status", "openai:backup"], }, }, }, @@ -904,7 +904,7 @@ describe("buildStatusReply subagent summary", () => { const normalized = normalizeTestText(text); expect(normalized).toContain("Model: openai/gpt-5.5"); - expect(normalized).toContain("oauth (openai-codex:status)"); + expect(normalized).toContain("oauth (openai:status)"); expect(normalized).not.toContain("api-key (openai:backup)"); }, { env: { OPENAI_API_KEY: undefined } }, @@ -1024,7 +1024,7 @@ describe("buildStatusReply subagent summary", () => { ...baseCfg, models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api/codex", models: [codexStatusModel], }, @@ -1071,10 +1071,6 @@ describe("buildStatusReply subagent summary", () => { models: { providers: { openai: { - baseUrl: "https://api.openai.com/v1", - models: [{ ...codexStatusModel, contextWindow: 400_000 }], - }, - "openai-codex": { baseUrl: "https://chatgpt.com/backend-api/codex", models: [{ ...codexStatusModel, contextWindow: 258_000, contextTokens: 258_000 }], }, diff --git a/src/auto-reply/reply/conversation-label-generator.test.ts b/src/auto-reply/reply/conversation-label-generator.test.ts index 6f8e6e606794..7b5a25fd3484 100644 --- a/src/auto-reply/reply/conversation-label-generator.test.ts +++ b/src/auto-reply/reply/conversation-label-generator.test.ts @@ -135,9 +135,9 @@ describe("generateConversationLabel", () => { }); it("omits temperature for Codex Responses simple completions", async () => { - resolveDefaultModelForAgent.mockReturnValue({ provider: "openai-codex", model: "gpt-5.5" }); + resolveDefaultModelForAgent.mockReturnValue({ provider: "openai", model: "gpt-5.5" }); resolveModelAsync.mockResolvedValue({ - model: { provider: "openai-codex", api: "openai-codex-responses" }, + model: { provider: "openai", api: "openai-chatgpt-responses" }, authStorage: {}, modelRegistry: {}, }); diff --git a/src/auto-reply/reply/conversation-label-generator.ts b/src/auto-reply/reply/conversation-label-generator.ts index d8deb0a1735d..df9a734bf46b 100644 --- a/src/auto-reply/reply/conversation-label-generator.ts +++ b/src/auto-reply/reply/conversation-label-generator.ts @@ -25,7 +25,7 @@ function isTextContentBlock(block: { type: string }): block is TextContent { } function isCodexSimpleCompletionModel(model: { api?: string; provider?: string }): boolean { - return model.provider === "openai-codex" || model.api === "openai-codex-responses"; + return model.api === "openai-chatgpt-responses"; } function extractSimpleCompletionError(result: { diff --git a/src/auto-reply/reply/directive-handling.auth.ts b/src/auto-reply/reply/directive-handling.auth.ts index 1c107928c742..23def01cc0ad 100644 --- a/src/auto-reply/reply/directive-handling.auth.ts +++ b/src/auto-reply/reply/directive-handling.auth.ts @@ -5,6 +5,7 @@ import { resolveAuthProfileDisplayLabel, resolveAuthStorePathForDisplay, } from "../../agents/auth-profiles.js"; +import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js"; import { ensureAuthProfileStore, resolveAuthProfileOrder, @@ -51,6 +52,10 @@ function formatFlagsSuffix(flags: string[]) { return flags.length > 0 ? ` (${flags.join(", ")})` : ""; } +function isStoredAuthProfileType(value: unknown): value is AuthProfileCredential["type"] { + return value === "api_key" || value === "oauth" || value === "token"; +} + export const resolveAuthLabel = async ( provider: string, cfg: OpenClawConfig, @@ -58,12 +63,28 @@ export const resolveAuthLabel = async ( agentDir?: string, mode: ModelAuthDetailMode = "compact", workspaceDir?: string, + options?: { acceptedProfileTypes?: readonly AuthProfileCredential["type"][] }, ): Promise<{ label: string; source: string }> => { const formatPath = (value: string) => shortenHomePath(value); const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false, }); - const order = resolveAuthProfileOrder({ cfg, store, provider }); + const rawOrder = resolveAuthProfileOrder({ cfg, store, provider }); + const acceptedProfileTypes = options?.acceptedProfileTypes + ? new Set(options.acceptedProfileTypes) + : undefined; + const order = acceptedProfileTypes + ? rawOrder.filter((profileId) => { + const profile = store.profiles[profileId]; + if (profile) { + return acceptedProfileTypes.has(profile.type); + } + const configuredMode = cfg.auth?.profiles?.[profileId]?.mode; + return isStoredAuthProfileType(configuredMode) + ? acceptedProfileTypes.has(configuredMode) + : true; + }) + : rawOrder; const providerKey = normalizeProviderId(provider); const lastGood = findNormalizedProviderValue(store.lastGood, providerKey); const nextProfileId = order[0]; diff --git a/src/auto-reply/reply/directive-handling.model-picker.ts b/src/auto-reply/reply/directive-handling.model-picker.ts index 5382b113cbbe..65d67684e946 100644 --- a/src/auto-reply/reply/directive-handling.model-picker.ts +++ b/src/auto-reply/reply/directive-handling.model-picker.ts @@ -20,7 +20,7 @@ export type ModelPickerItem = ModelRef; const MODEL_PICK_PROVIDER_PREFERENCE = [ "anthropic", "openai", - "openai-codex", + "openai", "minimax", "synthetic", "google", diff --git a/src/auto-reply/reply/directive-handling.model.test.ts b/src/auto-reply/reply/directive-handling.model.test.ts index bf060fb91c10..bfbf611c2a82 100644 --- a/src/auto-reply/reply/directive-handling.model.test.ts +++ b/src/auto-reply/reply/directive-handling.model.test.ts @@ -12,6 +12,7 @@ const authProfilesStoreMock = vi.hoisted(() => ({ string, | { type: "api_key"; provider: string; key: string } | { type: "oauth"; provider: string; access: string; refresh: string; expires: number } + | { type: "token"; provider: string; token: string } >, })); const modelsCommandMock = vi.hoisted(() => ({ @@ -104,16 +105,26 @@ vi.mock("./directive-handling.auth.js", () => ({ _agentDir?: string, _mode?: unknown, workspaceDir?: string, + options?: { acceptedProfileTypes?: readonly string[] }, ) => { const providerKey = normalizeProviderForAuthTest(provider); + const acceptedProfileTypes = options?.acceptedProfileTypes + ? new Set(options.acceptedProfileTypes) + : undefined; const matchingProfiles = Object.entries(authProfilesStoreMock.profiles).filter( - ([, profile]) => normalizeProviderForAuthTest(profile.provider) === providerKey, + ([, profile]) => + normalizeProviderForAuthTest(profile.provider) === providerKey && + (!acceptedProfileTypes || acceptedProfileTypes.has(profile.type)), ); if (matchingProfiles.length > 0) { return { label: matchingProfiles .map(([profileId, profile]) => - profile.type === "oauth" ? `${profileId}=OAuth` : `${profileId}=${profile.key}`, + profile.type === "oauth" + ? `${profileId}=OAuth` + : profile.type === "token" + ? `${profileId}=token` + : `${profileId}=${profile.key}`, ) .join(", "), source: `auth-profiles.json: /tmp/auth-profiles.json`, @@ -238,7 +249,7 @@ vi.mock("../../agents/harness/selection.js", () => ({ ? undefined : (modelRuntime ?? (providerRuntime === "default" ? undefined : providerRuntime) ?? - (provider === "openai" || provider === "openai-codex" ? "codex" : "auto")); + (provider === "openai" ? "codex" : "auto")); return { runtime, runtimeSource: modelRuntime ? "model" : providerRuntime ? "provider" : "implicit", @@ -256,7 +267,7 @@ vi.mock("../../agents/runtime-plan/auth.js", () => ({ }) => ({ providerForAuth: provider, authProfileProviderForAuth: provider, - ...(harnessRuntime === "codex" ? { harnessAuthProvider: "openai-codex" } : {}), + ...(harnessRuntime === "codex" ? { harnessAuthProvider: "openai" } : {}), }), })); @@ -355,7 +366,8 @@ type OAuthProfileForTest = { refresh: string; expires: number; }; -type AuthProfileForTest = ApiKeyProfile | OAuthProfileForTest; +type TokenProfileForTest = { type: "token"; provider: string; token: string }; +type AuthProfileForTest = ApiKeyProfile | OAuthProfileForTest | TokenProfileForTest; function baseAliasIndex(): ModelAliasIndex { return { byAlias: new Map(), byKey: new Map() }; @@ -798,15 +810,20 @@ describe("/model chat UX", () => { expect(reply?.text).toContain("openrouter/google/gemini-3-flash-preview"); }); - it("reports Codex runtime auth for OpenAI status rows", async () => { + it("reports unified OpenAI OAuth auth for OpenAI status rows", async () => { setAuthProfiles({ - "openai-codex:patrick@example.test": { + "openai:patrick@example.test": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, }, + "openai:runtime-token": { + type: "token", + provider: "openai", + token: "token", + }, }); const reply = await resolveModelInfoReply({ @@ -832,8 +849,8 @@ describe("/model chat UX", () => { expect(reply?.text).toContain("[openai] endpoint: default auth:"); expect(reply?.text).not.toContain("[openai] endpoint: default auth: missing"); - expect(reply?.text).toContain("via codex runtime / openai-codex"); - expect(reply?.text).toContain("openai-codex:patrick@example.test=OAuth"); + expect(reply?.text).not.toContain("via codex runtime"); + expect(reply?.text).toContain("openai:patrick@example.test=OAuth"); }, 240_000); it("keeps direct provider auth labels when OpenAI API key auth exists", async () => { @@ -843,9 +860,9 @@ describe("/model chat UX", () => { provider: "openai", key: "sk-openai-direct", }, - "openai-codex:patrick@example.test": { + "openai:patrick@example.test": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -879,9 +896,9 @@ describe("/model chat UX", () => { it("does not borrow Codex auth when OpenAI model policy pins OpenClaw runtime", async () => { setAuthProfiles({ - "openai-codex:patrick@example.test": { + "openai:patrick@example.test": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -912,7 +929,49 @@ describe("/model chat UX", () => { expect(reply?.text).toContain("[openai] endpoint: default auth: missing"); expect(reply?.text).not.toContain("via codex runtime"); - expect(reply?.text).not.toContain("openai-codex:patrick@example.test=OAuth"); + expect(reply?.text).not.toContain("openai:patrick@example.test=OAuth"); + expect(reply?.text).not.toContain("openai:runtime-token=token"); + }); + + it("honors Codex session runtime overrides when labeling OpenAI status auth", async () => { + setAuthProfiles({ + "openai:patrick@example.test": { + type: "oauth", + provider: "openai", + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, + }); + + const reply = await resolveModelInfoReply({ + directives: parseInlineDirectives("/model status"), + provider: "openai", + model: "gpt-5.5", + defaultProvider: "openai", + defaultModel: "gpt-5.5", + sessionEntry: { + agentRuntimeOverride: "codex", + }, + cfg: { + commands: { text: true }, + agents: { + defaults: { + model: { primary: "openai/gpt-5.5" }, + models: { + "openai/gpt-5.5": { + agentRuntime: { id: "openclaw" }, + }, + }, + }, + }, + } as unknown as OpenClawConfig, + allowedModelCatalog: [{ provider: "openai", id: "gpt-5.5", name: "GPT-5.5" }], + }); + + expect(reply?.text).toContain("[openai] endpoint: default auth:"); + expect(reply?.text).not.toContain("[openai] endpoint: default auth: missing"); + expect(reply?.text).toContain("openai:patrick@example.test=OAuth"); }); it("uses workspace-scoped auth evidence in /model status labels", async () => { @@ -1219,7 +1278,7 @@ describe("/model chat UX", () => { ...baseConfig(), models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api/codex", models: [ { @@ -1734,7 +1793,7 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { it.each([ ["openai", "gpt-5.5"], - ["openai-codex", "gpt-5.5"], + ["openai", "gpt-5.5"], ])("accepts xhigh for %s/%s when catalog marks reasoning support", async (provider, model) => { setDirectiveTestProviders([ { diff --git a/src/auto-reply/reply/directive-handling.model.ts b/src/auto-reply/reply/directive-handling.model.ts index 77a690eb137f..88a39729d276 100644 --- a/src/auto-reply/reply/directive-handling.model.ts +++ b/src/auto-reply/reply/directive-handling.model.ts @@ -1,4 +1,6 @@ +import { normalizeOptionalAgentRuntimeId } from "../../agents/agent-runtime-id.js"; import { resolveAuthStorePathForDisplay } from "../../agents/auth-profiles.js"; +import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js"; import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js"; import { type ModelAliasIndex, @@ -40,10 +42,25 @@ function resolveStatusHarnessRuntime(params: { sessionEntry?: Pick; defaultRuntime: string; }): string { - void params.sessionEntry; + const sessionRuntime = normalizeOptionalAgentRuntimeId( + params.sessionEntry?.agentRuntimeOverride ?? params.sessionEntry?.agentHarnessId, + ); + if (sessionRuntime) { + return sessionRuntime; + } return params.defaultRuntime; } +function resolveStatusAcceptedProfileTypes(params: { + provider: string; + harnessRuntime: string; +}): readonly AuthProfileCredential["type"][] | undefined { + if (normalizeProviderId(params.provider) !== "openai" || params.harnessRuntime === "codex") { + return undefined; + } + return ["api_key"]; +} + async function resolveStatusAuthLabel(params: { provider: string; modelId: string; @@ -55,18 +72,6 @@ async function resolveStatusAuthLabel(params: { workspaceDir?: string; sessionEntry?: Pick; }): Promise { - const auth = await resolveAuthLabel( - params.provider, - params.cfg, - params.modelsPath, - params.agentDir, - params.authMode, - params.workspaceDir, - ); - if (!isMissingAuthLabel(auth)) { - return formatAuthLabel(auth); - } - const provider = normalizeProviderId(params.provider); const harnessPolicy = resolveAgentHarnessPolicy({ provider, @@ -78,6 +83,24 @@ async function resolveStatusAuthLabel(params: { sessionEntry: params.sessionEntry, defaultRuntime: harnessPolicy.runtime, }); + const auth = await resolveAuthLabel( + params.provider, + params.cfg, + params.modelsPath, + params.agentDir, + params.authMode, + params.workspaceDir, + { + acceptedProfileTypes: resolveStatusAcceptedProfileTypes({ + provider, + harnessRuntime, + }), + }, + ); + if (!isMissingAuthLabel(auth)) { + return formatAuthLabel(auth); + } + const runtimeAuthPlan = buildAgentRuntimeAuthPlan({ provider, config: params.cfg, diff --git a/src/auto-reply/reply/directive-handling.persist.ts b/src/auto-reply/reply/directive-handling.persist.ts index d4b7dcd762ce..d2dfcb4ecfc9 100644 --- a/src/auto-reply/reply/directive-handling.persist.ts +++ b/src/auto-reply/reply/directive-handling.persist.ts @@ -7,7 +7,7 @@ import { resolveCliRuntimeModelBackendBinding } from "../../agents/cli-backends. import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js"; import type { ModelCatalogEntry } from "../../agents/model-catalog.js"; import { normalizeProviderId, type ModelAliasIndex } from "../../agents/model-selection.js"; -import { resolveContextConfigProviderForRuntime } from "../../agents/openai-codex-routing.js"; +import { resolveContextConfigProviderForRuntime } from "../../agents/openai-routing.js"; import { updateSessionStore } from "../../config/sessions/store.js"; import type { SessionEntry } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index 4d2d7667885b..2bf37413fa7e 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -2825,7 +2825,7 @@ describe("createFollowupRunner messaging delivery and dedupe", () => { const sessionEntry: SessionEntry = { sessionId: "session", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", contextTokens: 200_000, inputTokens: 1_234, @@ -2854,7 +2854,7 @@ describe("createFollowupRunner messaging delivery and dedupe", () => { opts: { onBlockReply: createAsyncReplySpy() }, typing: createMockTypingController(), typingMode: "instant", - defaultModel: "openai-codex/gpt-5.5", + defaultModel: "openai/gpt-5.5", sessionEntry, sessionStore, sessionKey, @@ -2878,7 +2878,7 @@ describe("createFollowupRunner messaging delivery and dedupe", () => { const persistCall = requireMockCallArg(persistSpy, 0); expect(persistCall.preserveUserFacingSessionModelState).toBe(true); - expect(sessionStore[sessionKey]?.modelProvider).toBe("openai-codex"); + expect(sessionStore[sessionKey]?.modelProvider).toBe("openai"); expect(sessionStore[sessionKey]?.model).toBe("gpt-5.5"); expect(sessionStore[sessionKey]?.contextTokens).toBe(200_000); expect(sessionStore[sessionKey]?.inputTokens).toBe(1_234); diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index 20c2591264a5..e184d68c0f1d 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -12,7 +12,7 @@ import type { EmbeddedFullAccessBlockedReason } from "../../agents/embedded-agen import { resolveFastModeState } from "../../agents/fast-mode.js"; import { runAgentHarnessBeforeMessageWriteHook } from "../../agents/harness/hook-helpers.js"; import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js"; -import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-codex-routing.js"; +import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-routing.js"; import { resolveIngressWorkspaceOverrideForSpawnedRun } from "../../agents/spawned-context.js"; import type { SilentReplyPromptMode } from "../../agents/system-prompt.types.js"; import { normalizeChatType } from "../../channels/chat-type.js"; diff --git a/src/auto-reply/reply/model-selection.test.ts b/src/auto-reply/reply/model-selection.test.ts index 65416852b8da..892249475209 100644 --- a/src/auto-reply/reply/model-selection.test.ts +++ b/src/auto-reply/reply/model-selection.test.ts @@ -78,13 +78,13 @@ describe("createModelSelectionState catalog loading", () => { defaults: { thinkingDefault: "low", models: { - "openai-codex/gpt-5.4": {}, + "openai/gpt-5.4": {}, }, }, }, models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://api.openai.com/v1", models: [makeConfiguredModel()], }, @@ -95,14 +95,14 @@ describe("createModelSelectionState catalog loading", () => { const state = await createModelSelectionState({ cfg, agentCfg: cfg.agents?.defaults, - defaultProvider: "openai-codex", + defaultProvider: "openai", defaultModel: "gpt-5.4", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", hasModelDirective: false, }); - expect(state.allowedModelKeys.has("openai-codex/gpt-5.4")).toBe(true); + expect(state.allowedModelKeys.has("openai/gpt-5.4")).toBe(true); await expect(state.resolveDefaultThinkingLevel()).resolves.toBe("low"); await expect(state.resolveDefaultReasoningLevel()).resolves.toBe("on"); expect(loadModelCatalog).not.toHaveBeenCalled(); @@ -114,13 +114,13 @@ describe("createModelSelectionState catalog loading", () => { agents: { defaults: { models: { - "openai-codex/gpt-5.4": {}, + "openai/gpt-5.4": {}, }, }, }, models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://api.openai.com/v1", models: [makeConfiguredModel()], }, @@ -131,9 +131,9 @@ describe("createModelSelectionState catalog loading", () => { const state = await createModelSelectionState({ cfg, agentCfg: cfg.agents?.defaults, - defaultProvider: "openai-codex", + defaultProvider: "openai", defaultModel: "gpt-5.4", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", hasModelDirective: false, }); @@ -145,19 +145,19 @@ describe("createModelSelectionState catalog loading", () => { it("hydrates runtime catalog metadata when the configured allowlist entry lacks reasoning", async () => { vi.mocked(loadModelCatalog).mockClear(); vi.mocked(loadModelCatalog).mockResolvedValueOnce([ - { provider: "openai-codex", id: "gpt-5.4", name: "GPT-5.4", reasoning: true }, + { provider: "openai", id: "gpt-5.4", name: "GPT-5.4", reasoning: true }, ]); const cfg = { agents: { defaults: { models: { - "openai-codex/gpt-5.4": {}, + "openai/gpt-5.4": {}, }, }, }, models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://api.openai.com/v1", models: [makeConfiguredModel({ reasoning: undefined })], }, @@ -168,9 +168,9 @@ describe("createModelSelectionState catalog loading", () => { const state = await createModelSelectionState({ cfg, agentCfg: cfg.agents?.defaults, - defaultProvider: "openai-codex", + defaultProvider: "openai", defaultModel: "gpt-5.4", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", hasModelDirective: false, }); @@ -331,7 +331,7 @@ describe("createModelSelectionState catalog loading", () => { defaults: { thinkingDefault: "low", models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { thinking: "high" }, }, }, @@ -349,9 +349,9 @@ describe("createModelSelectionState catalog loading", () => { cfg, agentId: "alpha", agentCfg: cfg.agents?.defaults, - defaultProvider: "openai-codex", + defaultProvider: "openai", defaultModel: "gpt-5.4", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", hasModelDirective: false, }); @@ -388,7 +388,7 @@ describe("createModelSelectionState catalog loading", () => { vi.mocked(loadModelCatalog).mockClear(); vi.mocked(loadModelCatalog).mockResolvedValueOnce([ { provider: "anthropic", id: "claude-opus-4-5", name: "Claude Opus" }, - { provider: "openai-codex", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, + { provider: "openai", id: "gpt-5.5-codex", name: "GPT-5.5 Codex" }, { provider: "vllm", id: "qwen3-local", name: "Qwen3 Local" }, ]); const cfg = { @@ -396,7 +396,7 @@ describe("createModelSelectionState catalog loading", () => { defaults: { model: { primary: "anthropic/claude-opus-4-5" }, models: { - "openai-codex/*": {}, + "openai/*": {}, "vllm/*": {}, }, }, @@ -413,7 +413,7 @@ describe("createModelSelectionState catalog loading", () => { hasModelDirective: false, }); - expect(state.provider).toBe("openai-codex"); + expect(state.provider).toBe("openai"); expect(state.model).toBe("gpt-5.5-codex"); expect(state.allowedModelKeys.has("anthropic/claude-opus-4-5")).toBe(false); expect(loadModelCatalog).toHaveBeenCalledOnce(); @@ -920,14 +920,14 @@ describe("createModelSelectionState respects session model override", () => { model: { primary: "anthropic/claude-sonnet-4-6" }, models: { "anthropic/claude-sonnet-4-6": {}, - "openai-codex/*": {}, + "openai/*": {}, }, }, }, } as OpenClawConfig; const sessionKey = "agent:main:telegram:direct:1"; const sessionEntry = makeEntry({ - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-added-after-startup", }); const sessionStore = { [sessionKey]: sessionEntry }; @@ -945,10 +945,10 @@ describe("createModelSelectionState respects session model override", () => { hasModelDirective: false, }); - expect(state.provider).toBe("openai-codex"); + expect(state.provider).toBe("openai"); expect(state.model).toBe("gpt-added-after-startup"); expect(state.resetModelOverride).toBe(false); - expect(sessionStore[sessionKey]?.providerOverride).toBe("openai-codex"); + expect(sessionStore[sessionKey]?.providerOverride).toBe("openai"); expect(sessionStore[sessionKey]?.modelOverride).toBe("gpt-added-after-startup"); }); @@ -1058,15 +1058,15 @@ describe("createModelSelectionState auto-failover overrides", () => { expect(state.resetModelOverride).toBe(false); }); - it("clears stale auto-created legacy openai-codex route pins when primary is canonical openai", async () => { + it("clears stale auto-created legacy openai route pins when primary is canonical openai", async () => { const sessionEntry = makeEntry({ - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.5", modelOverrideSource: "auto", - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", contextTokens: 350_000, - authProfileOverride: "openai-codex:default", + authProfileOverride: "openai:default", authProfileOverrideSource: "auto", }); const sessionStore = { [sessionKey]: sessionEntry }; @@ -1081,7 +1081,7 @@ describe("createModelSelectionState auto-failover overrides", () => { defaultModel: "gpt-5.5", primaryProvider: "openai", primaryModel: "gpt-5.5", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", hasModelDirective: false, }); @@ -1089,7 +1089,7 @@ describe("createModelSelectionState auto-failover overrides", () => { expect(state.provider).toBe("openai"); expect(state.model).toBe("gpt-5.5"); expect(state.resetModelOverride).toBe(true); - expect(state.resetModelOverrideRef).toBe("openai-codex/gpt-5.5"); + expect(state.resetModelOverrideRef).toBe("openai/gpt-5.5"); expect(sessionStore[sessionKey]?.providerOverride).toBeUndefined(); expect(sessionStore[sessionKey]?.modelOverride).toBeUndefined(); expect(sessionStore[sessionKey]?.modelOverrideSource).toBeUndefined(); @@ -1100,22 +1100,22 @@ describe("createModelSelectionState auto-failover overrides", () => { expect(sessionStore[sessionKey]?.authProfileOverrideSource).toBeUndefined(); }); - it("preserves usable Codex auth while clearing stale legacy openai-codex route pins", async () => { + it("preserves usable Codex auth while clearing stale legacy openai route pins", async () => { authProfileStoreMock.store = { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "test-key", }, }, }; const sessionEntry = makeEntry({ - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.5", modelOverrideSource: "auto", - authProfileOverride: "openai-codex:default", + authProfileOverride: "openai:default", authProfileOverrideSource: "auto", }); const sessionStore = { [sessionKey]: sessionEntry }; @@ -1130,7 +1130,7 @@ describe("createModelSelectionState auto-failover overrides", () => { defaultModel: "gpt-5.5", primaryProvider: "openai", primaryModel: "gpt-5.5", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", hasModelDirective: false, }); @@ -1140,11 +1140,11 @@ describe("createModelSelectionState auto-failover overrides", () => { expect(state.resetModelOverride).toBe(true); expect(sessionStore[sessionKey]?.providerOverride).toBeUndefined(); expect(sessionStore[sessionKey]?.modelOverride).toBeUndefined(); - expect(sessionStore[sessionKey]?.authProfileOverride).toBe("openai-codex:default"); + expect(sessionStore[sessionKey]?.authProfileOverride).toBe("openai:default"); expect(sessionStore[sessionKey]?.authProfileOverrideSource).toBe("auto"); }); - it("keeps auto openai-codex pins when canonical openai uses a custom API route", async () => { + it("keeps auto openai pins when canonical openai uses a custom API route", async () => { const cfg = { models: { providers: { @@ -1156,7 +1156,7 @@ describe("createModelSelectionState auto-failover overrides", () => { }, } as OpenClawConfig; const sessionEntry = makeEntry({ - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.5", modelOverrideSource: "auto", }); @@ -1172,22 +1172,22 @@ describe("createModelSelectionState auto-failover overrides", () => { defaultModel: "gpt-5.5", primaryProvider: "openai", primaryModel: "gpt-5.5", - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", hasModelDirective: false, }); - expect(state.provider).toBe("openai-codex"); + expect(state.provider).toBe("openai"); expect(state.model).toBe("gpt-5.5"); expect(state.resetModelOverride).toBe(false); - expect(sessionStore[sessionKey]?.providerOverride).toBe("openai-codex"); + expect(sessionStore[sessionKey]?.providerOverride).toBe("openai"); expect(sessionStore[sessionKey]?.modelOverride).toBe("gpt-5.5"); expect(sessionStore[sessionKey]?.modelOverrideSource).toBe("auto"); }); - it("keeps explicit user openai-codex route overrides", async () => { + it("keeps explicit user openai route overrides", async () => { const sessionEntry = makeEntry({ - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.5", modelOverrideSource: "user", }); @@ -1208,10 +1208,10 @@ describe("createModelSelectionState auto-failover overrides", () => { hasModelDirective: false, }); - expect(state.provider).toBe("openai-codex"); + expect(state.provider).toBe("openai"); expect(state.model).toBe("gpt-5.5"); expect(state.resetModelOverride).toBe(false); - expect(sessionStore[sessionKey]?.providerOverride).toBe("openai-codex"); + expect(sessionStore[sessionKey]?.providerOverride).toBe("openai"); expect(sessionStore[sessionKey]?.modelOverride).toBe("gpt-5.5"); expect(sessionStore[sessionKey]?.modelOverrideSource).toBe("user"); }); @@ -1308,7 +1308,7 @@ describe("createModelSelectionState auto-failover overrides", () => { providerOverride: "openrouter", modelOverride: "minimax/minimax-m2.7", modelOverrideSource: "auto", - modelOverrideFallbackOriginProvider: "openai-codex", + modelOverrideFallbackOriginProvider: "openai", modelOverrideFallbackOriginModel: "gpt-5.3", provider: "openrouter", model: "minimax/minimax-m2.7", @@ -1341,7 +1341,7 @@ describe("createModelSelectionState auto-failover overrides", () => { providerOverride: "openrouter", modelOverride: "minimax/minimax-m2.7", modelOverrideSource: "auto", - modelOverrideFallbackOriginProvider: "openai-codex", + modelOverrideFallbackOriginProvider: "openai", modelOverrideFallbackOriginModel: "gpt-5.3", authProfileOverride: "mac-studio:local", authProfileOverrideSource: "user", diff --git a/src/auto-reply/reply/model-selection.ts b/src/auto-reply/reply/model-selection.ts index c53c514f81ee..618bbee23c7c 100644 --- a/src/auto-reply/reply/model-selection.ts +++ b/src/auto-reply/reply/model-selection.ts @@ -23,7 +23,7 @@ import { OPENAI_CODEX_PROVIDER_ID, OPENAI_PROVIDER_ID, listOpenAIAuthProfileProvidersForAgentRuntime, -} from "../../agents/openai-codex-routing.js"; +} from "../../agents/openai-routing.js"; import type { SessionEntry } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides.js"; diff --git a/src/auto-reply/reply/reply-utils.test.ts b/src/auto-reply/reply/reply-utils.test.ts index 3bd6c59f2bea..9777fdede4de 100644 --- a/src/auto-reply/reply/reply-utils.test.ts +++ b/src/auto-reply/reply/reply-utils.test.ts @@ -620,8 +620,8 @@ describe("resolveResponsePrefixTemplate", () => { { name: "modelFull", template: "[{modelFull}]", - values: { modelFull: "openai-codex/gpt-5.4" }, - expected: "[openai-codex/gpt-5.4]", + values: { modelFull: "openai/gpt-5.4" }, + expected: "[openai/gpt-5.4]", }, { name: "provider", @@ -1388,7 +1388,7 @@ describe("createStreamingDirectiveAccumulator", () => { describe("extractShortModelName", () => { it("normalizes provider/date/latest suffixes while preserving other IDs", () => { const cases = [ - ["openai-codex/gpt-5.4", "gpt-5.4"], + ["openai/gpt-5.4", "gpt-5.4"], ["claude-opus-4-6-20251101", "claude-opus-4-6"], ["gpt-5.4-latest", "gpt-5.4"], // Date suffix must be exactly 8 digits at the end. diff --git a/src/auto-reply/reply/response-prefix-template.ts b/src/auto-reply/reply/response-prefix-template.ts index be0d593ddbe0..e8098757d89c 100644 --- a/src/auto-reply/reply/response-prefix-template.ts +++ b/src/auto-reply/reply/response-prefix-template.ts @@ -12,7 +12,7 @@ export type ResponsePrefixContext = { model?: string; /** Full model ID including provider (e.g., "openai/gpt-5.5") */ modelFull?: string; - /** Provider name (e.g., "openai-codex", "anthropic") */ + /** Provider name (e.g., "openai", "anthropic") */ provider?: string; /** Current thinking level (e.g., "high", "low", "off") */ thinkingLevel?: string; diff --git a/src/auto-reply/reply/session.test.ts b/src/auto-reply/reply/session.test.ts index e4916914032d..1d31ba5289bc 100644 --- a/src/auto-reply/reply/session.test.ts +++ b/src/auto-reply/reply/session.test.ts @@ -141,7 +141,7 @@ async function makeStorePath(prefix: string): Promise { } const createStorePath = makeStorePath; -const TEST_NATIVE_MODEL_PROFILE_ID = "openai-codex:secondary@example.test"; +const TEST_NATIVE_MODEL_PROFILE_ID = "openai:secondary@example.test"; function requireString(value: string | undefined, label: string): string { if (!value) { @@ -1253,8 +1253,8 @@ describe("initSessionState RawBody", () => { const result = await initSessionState({ ctx: { - Body: `/model openai-codex/gpt-5.4@${TEST_NATIVE_MODEL_PROFILE_ID}`, - CommandBody: `/model openai-codex/gpt-5.4@${TEST_NATIVE_MODEL_PROFILE_ID}`, + Body: `/model openai/gpt-5.4@${TEST_NATIVE_MODEL_PROFILE_ID}`, + CommandBody: `/model openai/gpt-5.4@${TEST_NATIVE_MODEL_PROFILE_ID}`, Provider: "slack", Surface: "slack", AccountId: "default", @@ -2483,10 +2483,10 @@ describe("initSessionState preserves behavior overrides across /new and /reset", const sessionKey = "agent:main:telegram:direct:6761477233"; const existingSessionId = "existing-session-auto-overrides"; const autoOverrides = { - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.4", modelOverrideSource: "auto", - authProfileOverride: "openai-codex:default", + authProfileOverride: "openai:default", authProfileOverrideSource: "auto", authProfileOverrideCompactionCount: 1, verboseLevel: "on", @@ -2543,7 +2543,7 @@ describe("initSessionState preserves behavior overrides across /new and /reset", const sessionKey = "agent:main:telegram:direct:6761477233"; const existingSessionId = "existing-session-recovered-auto-fallback"; const autoOverrides = { - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.4", modelOverrideFallbackOriginProvider: "anthropic", modelOverrideFallbackOriginModel: "claude-opus-4-6", @@ -3647,7 +3647,7 @@ describe("persistSessionUsageUpdate", () => { entry: { sessionId: "s1", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.4", }, }); @@ -3658,13 +3658,13 @@ describe("persistSessionUsageUpdate", () => { isHeartbeat: true, usage: { input: 1_200, output: 100, cacheRead: 300, cacheWrite: 10 }, lastCallUsage: { input: 900, output: 80, cacheRead: 200, cacheWrite: 5 }, - providerUsed: "openai-codex", + providerUsed: "openai", modelUsed: "gpt-5.1-codex-mini", contextTokensUsed: 128_000, }); const stored = JSON.parse(await fs.readFile(storePath, "utf-8")); - expect(stored[sessionKey].modelProvider).toBe("openai-codex"); + expect(stored[sessionKey].modelProvider).toBe("openai"); expect(stored[sessionKey].model).toBe("gpt-5.4"); expect(stored[sessionKey].inputTokens).toBe(1_200); expect(stored[sessionKey].outputTokens).toBe(100); @@ -3681,7 +3681,7 @@ describe("persistSessionUsageUpdate", () => { entry: { sessionId: "s1", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", contextTokens: 200_000, inputTokens: 1_234, @@ -3728,7 +3728,7 @@ describe("persistSessionUsageUpdate", () => { }); const stored = JSON.parse(await fs.readFile(storePath, "utf-8")); - expect(stored[sessionKey].modelProvider).toBe("openai-codex"); + expect(stored[sessionKey].modelProvider).toBe("openai"); expect(stored[sessionKey].model).toBe("gpt-5.5"); expect(stored[sessionKey].contextTokens).toBe(200_000); expect(stored[sessionKey].inputTokens).toBe(1_234); @@ -3764,7 +3764,7 @@ describe("persistSessionUsageUpdate", () => { cfg: { models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://api.openai.com/v1", models: [ { @@ -3783,7 +3783,7 @@ describe("persistSessionUsageUpdate", () => { } satisfies OpenClawConfig, usage: { input: 5_107, output: 1_827, cacheRead: 1_536, cacheWrite: 0 }, lastCallUsage: { input: 5_107, output: 1_827, cacheRead: 1_536, cacheWrite: 0 }, - providerUsed: "openai-codex", + providerUsed: "openai", modelUsed: "gpt-5.3-codex-spark", contextTokensUsed: 200_000, }); diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index b3343c15b949..2980ccf52662 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -730,9 +730,9 @@ describe("buildStatusMessage", () => { config: { agents: { defaults: { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { textVerbosity: "low", }, @@ -742,7 +742,7 @@ describe("buildStatusMessage", () => { }, } as unknown as OpenClawConfig, agent: { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", }, sessionEntry: { sessionId: "abc", @@ -760,9 +760,9 @@ describe("buildStatusMessage", () => { config: { agents: { defaults: { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", models: { - "openai-codex/gpt-5.4": { + "openai/gpt-5.4": { params: { textVerbosity: "high", }, @@ -781,7 +781,7 @@ describe("buildStatusMessage", () => { } as unknown as OpenClawConfig, agentId: "main", agent: { - model: "openai-codex/gpt-5.4", + model: "openai/gpt-5.4", }, sessionEntry: { sessionId: "abc", @@ -2372,7 +2372,7 @@ describe("buildStatusMessage", () => { contextTokens: 1_000_000, }, sessionEntry: { - sessionId: "sess-openai-codex-cap-context", + sessionId: "sess-openai-chatgpt-cap-context", updatedAt: 0, totalTokens: 25_000, }, @@ -2396,7 +2396,7 @@ describe("buildStatusMessage", () => { explicitConfiguredContextTokens: 1_000_000, runtimeContextTokens: 272_000, sessionEntry: { - sessionId: "sess-openai-codex-runtime-cap-context", + sessionId: "sess-openai-chatgpt-runtime-cap-context", updatedAt: 0, totalTokens: 25_000, }, diff --git a/src/auto-reply/thinking.test.ts b/src/auto-reply/thinking.test.ts index 37be5e8b8bad..0aa034dbf5b2 100644 --- a/src/auto-reply/thinking.test.ts +++ b/src/auto-reply/thinking.test.ts @@ -93,21 +93,21 @@ describe("listThinkingLevels", () => { it("includes xhigh for provider-advertised models", () => { providerRuntimeMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) => (provider === "openai" && ["gpt-5.4", "gpt-5.4", "gpt-5.4-pro"].includes(context.modelId)) || - (provider === "openai-codex" && + (provider === "openai" && ["gpt-5.4", "gpt-5.4-pro", "gpt-5.3-codex-spark"].includes(context.modelId)) || (provider === "github-copilot" && ["gpt-5.4", "gpt-5.4"].includes(context.modelId)) ? true : undefined, ); - expect(listThinkingLevels("openai-codex", "gpt-5.4")).toContain("xhigh"); - expect(listThinkingLevels("openai-codex", "gpt-5.4")).toContain("xhigh"); - expect(listThinkingLevels("openai-codex", "gpt-5.3-codex-spark")).toContain("xhigh"); - expect(listThinkingLevels("openai-codex", "gpt-5.4-pro")).toContain("xhigh"); + expect(listThinkingLevels("openai", "gpt-5.4")).toContain("xhigh"); + expect(listThinkingLevels("openai", "gpt-5.4")).toContain("xhigh"); + expect(listThinkingLevels("openai", "gpt-5.3-codex-spark")).toContain("xhigh"); + expect(listThinkingLevels("openai", "gpt-5.4-pro")).toContain("xhigh"); expect(listThinkingLevels("openai", "gpt-5.4")).toContain("xhigh"); expect(listThinkingLevels("openai", "gpt-5.4")).toContain("xhigh"); expect(listThinkingLevels("openai", "gpt-5.4-pro")).toContain("xhigh"); - expect(listThinkingLevels("openai-codex", "gpt-5.4")).toContain("xhigh"); + expect(listThinkingLevels("openai", "gpt-5.4")).toContain("xhigh"); expect(listThinkingLevels("github-copilot", "gpt-5.4")).toContain("xhigh"); expect(listThinkingLevels("github-copilot", "gpt-5.4")).toContain("xhigh"); }); diff --git a/src/cli/capability-cli.test.ts b/src/cli/capability-cli.test.ts index e74960909eaa..393139725064 100644 --- a/src/cli/capability-cli.test.ts +++ b/src/cli/capability-cli.test.ts @@ -787,17 +787,17 @@ describe("capability cli", () => { expect(inputs[0]?.mimeType).toBe("image/png"); }); - it("adds minimal instructions only for openai-codex local model probes", async () => { + it("adds minimal instructions only for openai local model probes", async () => { mocks.prepareSimpleCompletionModelForAgent.mockResolvedValueOnce({ selection: { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.5", agentDir: "/tmp/agent", }, model: { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", maxTokens: 128, }, auth: { @@ -814,7 +814,7 @@ describe("capability cli", () => { "model", "run", "--model", - "openai-codex/gpt-5.5", + "openai/gpt-5.5", "--prompt", "hello", "--json", @@ -976,7 +976,7 @@ describe("capability cli", () => { model: { provider: "codex", id: "gpt-5.4", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", }, auth: { apiKey: "codex-app-server", diff --git a/src/cli/capability-cli.ts b/src/cli/capability-cli.ts index d7ebc4628ab1..b5eccfb3e0b3 100644 --- a/src/cli/capability-cli.ts +++ b/src/cli/capability-cli.ts @@ -744,10 +744,7 @@ async function runModelRun(params: { ); } const localModelRunSystemPrompt = - prepared.selection.provider === "openai-codex" || - prepared.model.api === "openai-codex-responses" - ? LOCAL_MODEL_RUN_SYSTEM_PROMPT - : undefined; + prepared.model.api === "openai-chatgpt-responses" ? LOCAL_MODEL_RUN_SYSTEM_PROMPT : undefined; const result = await completeWithPreparedSimpleCompletionModel({ model: prepared.model, auth: prepared.auth, diff --git a/src/cli/models-cli.test.ts b/src/cli/models-cli.test.ts index 19693e7550aa..c10182764ce9 100644 --- a/src/cli/models-cli.test.ts +++ b/src/cli/models-cli.test.ts @@ -162,15 +162,15 @@ describe("models cli", () => { }, { label: "list", - args: ["models", "auth", "--agent", "poe", "list", "--provider", "openai-codex"], + args: ["models", "auth", "--agent", "poe", "list", "--provider", "openai"], command: modelsAuthListCommand, - expected: { agent: "poe", provider: "openai-codex" }, + expected: { agent: "poe", provider: "openai" }, }, { label: "login", - args: ["models", "auth", "--agent", "poe", "login", "--provider", "openai-codex"], + args: ["models", "auth", "--agent", "poe", "login", "--provider", "openai"], command: modelsAuthLoginCommand, - expected: { agent: "poe", provider: "openai-codex" }, + expected: { agent: "poe", provider: "openai" }, }, { label: "setup-token", @@ -186,9 +186,9 @@ describe("models cli", () => { }, { label: "paste-api-key", - args: ["models", "auth", "--agent", "poe", "paste-api-key", "--provider", "openai-codex"], + args: ["models", "auth", "--agent", "poe", "paste-api-key", "--provider", "openai"], command: modelsAuthPasteApiKeyCommand, - expected: { agent: "poe", provider: "openai-codex" }, + expected: { agent: "poe", provider: "openai" }, }, { label: "login-github-copilot", @@ -220,17 +220,10 @@ describe("models cli", () => { }); it("maps --device-code to the provider device-code auth method", async () => { - await runModelsCommand([ - "models", - "auth", - "login", - "--provider", - "openai-codex", - "--device-code", - ]); + await runModelsCommand(["models", "auth", "login", "--provider", "openai", "--device-code"]); expectCommandOptions(modelsAuthLoginCommand, { - provider: "openai-codex", + provider: "openai", method: "device-code", }); }); diff --git a/src/cli/models-cli.ts b/src/cli/models-cli.ts index 7f3a1d66ced8..cdccc61481df 100644 --- a/src/cli/models-cli.ts +++ b/src/cli/models-cli.ts @@ -418,7 +418,7 @@ export function registerModelsCli(program: Command) { auth .command("paste-api-key") .description("Paste an API key into auth-profiles.json and update config") - .requiredOption("--provider ", "Provider id (e.g. openai-codex)") + .requiredOption("--provider ", "Provider id (e.g. openai)") .option("--profile-id ", "Auth profile id (default: :manual)") .action(async (opts, command) => { await withModelsRuntime(async ({ defaultRuntime, resolveModelAgentOption }) => { diff --git a/src/cli/plugins-cli.policy.test.ts b/src/cli/plugins-cli.policy.test.ts index 092e5df49482..8ee64e12eab7 100644 --- a/src/cli/plugins-cli.policy.test.ts +++ b/src/cli/plugins-cli.policy.test.ts @@ -15,7 +15,6 @@ const ORIGINAL_OPENCLAW_NIX_MODE = process.env.OPENCLAW_NIX_MODE; describe("plugins cli policy mutations", () => { const compatibilityPluginIds = [ - { alias: "openai-codex", pluginId: "openai" }, { alias: "google-gemini-cli", pluginId: "google" }, { alias: "minimax-portal-auth", pluginId: "minimax" }, ] as const; diff --git a/src/commands/agents.add.test.ts b/src/commands/agents.add.test.ts index 2eef86e9f62f..c51c5e6cc940 100644 --- a/src/commands/agents.add.test.ts +++ b/src/commands/agents.add.test.ts @@ -3,10 +3,8 @@ import os from "node:os"; import path from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { AUTH_STORE_VERSION } from "../agents/auth-profiles/constants.js"; -import { legacyOAuthSidecarTestUtils } from "../agents/auth-profiles/legacy-oauth-sidecar.js"; import { saveAuthProfileStore } from "../agents/auth-profiles/store.js"; import { formatCliCommand } from "../cli/command-format.js"; -import { resolveOAuthDir } from "../config/paths.js"; import { baseConfigSnapshot, createTestRuntime } from "./test-runtime-config-helpers.js"; const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn()); @@ -161,9 +159,9 @@ describe("agents add command", () => { provider: "github-copilot", token: "gho-test", }, - "openai-codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "codex-access", refresh: "codex-refresh", expires: Date.now() + 60_000, @@ -208,9 +206,9 @@ describe("agents add command", () => { { version: AUTH_STORE_VERSION, profiles: { - "openai-codex:default": { + "openai:oauth": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "codex-copy-access-token", refresh: "codex-copy-refresh-token", expires, @@ -233,10 +231,10 @@ describe("agents add command", () => { const copied = JSON.parse(copiedRaw) as { profiles: Record>; }; - const credential = copied.profiles["openai-codex:default"]; + const credential = copied.profiles["openai:oauth"]; expect(credential).toStrictEqual({ type: "oauth", - provider: "openai-codex", + provider: "openai", access: "codex-copy-access-token", refresh: "codex-copy-refresh-token", expires, @@ -252,20 +250,16 @@ describe("agents add command", () => { } }); - it("skips legacy sidecar-backed Codex OAuth profiles when seeding a new agent store", async () => { + it("skips unresolved OAuth profiles when seeding a new agent store", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agents-add-oauth-ref-skip-")); - const previousOAuthDir = process.env.OPENCLAW_OAUTH_DIR; - const previousSecretKey = process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY; - process.env.OPENCLAW_OAUTH_DIR = path.join(root, "credentials"); - process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY = "legacy-seed"; try { const sourceAgentDir = path.join(root, "main", "agent"); const destAgentDir = path.join(root, "work", "agent"); const destAuthPath = path.join(destAgentDir, "auth-profiles.json"); - const profileId = "openai-codex:default"; + const profileId = "openai:oauth"; const ref = { source: "openclaw-credentials" as const, - provider: "openai-codex" as const, + provider: "openai" as const, id: "0123456789abcdef0123456789abcdef", }; await fs.mkdir(sourceAgentDir, { recursive: true }); @@ -277,7 +271,7 @@ describe("agents add command", () => { profiles: { [profileId]: { type: "oauth", - provider: "openai-codex", + provider: "openai", copyToAgents: true, expires: Date.now() + 60_000, oauthRef: ref, @@ -289,32 +283,6 @@ describe("agents add command", () => { )}\n`, "utf8", ); - const sidecarPath = path.join(resolveOAuthDir(), "auth-profiles", `${ref.id}.json`); - await fs.mkdir(path.dirname(sidecarPath), { recursive: true }); - await fs.writeFile( - sidecarPath, - `${JSON.stringify( - { - version: 1, - profileId, - provider: "openai-codex", - encrypted: legacyOAuthSidecarTestUtils.encryptLegacyOAuthMaterial({ - ref, - profileId, - provider: "openai-codex", - seed: "legacy-seed", - material: { - access: "legacy-sidecar-access-token", - refresh: "legacy-sidecar-refresh-token", - }, - }), - }, - null, - 2, - )}\n`, - "utf8", - ); - const result = await testing.copyPortableAuthProfiles({ sourceAgentDir, destAuthPath, @@ -323,16 +291,6 @@ describe("agents add command", () => { expect(result).toEqual({ copied: 0, skipped: 1 }); await expect(fs.stat(destAuthPath)).rejects.toMatchObject({ code: "ENOENT" }); } finally { - if (previousOAuthDir === undefined) { - delete process.env.OPENCLAW_OAUTH_DIR; - } else { - process.env.OPENCLAW_OAUTH_DIR = previousOAuthDir; - } - if (previousSecretKey === undefined) { - delete process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY; - } else { - process.env.OPENCLAW_AUTH_PROFILE_SECRET_KEY = previousSecretKey; - } await fs.rm(root, { recursive: true, force: true }); } }); diff --git a/src/commands/agents.commands.add.ts b/src/commands/agents.commands.add.ts index 55e5918b3cad..b54352e06884 100644 --- a/src/commands/agents.commands.add.ts +++ b/src/commands/agents.commands.add.ts @@ -78,9 +78,7 @@ async function copyPortableAuthProfiles(params: { destAuthPath: string; sourceAgentDir: string; }): Promise<{ copied: number; skipped: number }> { - const sourceStore = loadPersistedAuthProfileStore(params.sourceAgentDir, { - resolveLegacyOAuthSidecars: false, - }); + const sourceStore = loadPersistedAuthProfileStore(params.sourceAgentDir); if (!sourceStore || Object.keys(sourceStore.profiles).length === 0) { return { copied: 0, skipped: 0 }; } @@ -339,9 +337,7 @@ export async function agentsAddCommand( (await pathExists(sourceAuthPath)) && !(await pathExists(destAuthPath)) ) { - const sourceStore = loadPersistedAuthProfileStore(sourceAgentDir, { - resolveLegacyOAuthSidecars: false, - }); + const sourceStore = loadPersistedAuthProfileStore(sourceAgentDir); const portable = sourceStore ? buildPortableAuthProfileSecretsStoreForAgentCopy(sourceStore) : undefined; diff --git a/src/commands/auth-choice-legacy.test.ts b/src/commands/auth-choice-legacy.test.ts index 2142e31f08b5..342fadaf35ae 100644 --- a/src/commands/auth-choice-legacy.test.ts +++ b/src/commands/auth-choice-legacy.test.ts @@ -15,14 +15,13 @@ const manifestAuthChoices = vi.hoisted(() => [ methodId: "oauth", choiceId: "openai", choiceLabel: "ChatGPT Login", - deprecatedChoiceIds: ["openai-codex", "codex-cli"], }, ]); vi.mock("../plugins/provider-auth-choices.js", () => ({ resolveManifestProviderAuthChoices: () => manifestAuthChoices, resolveManifestDeprecatedProviderAuthChoice: (choiceId: string) => - manifestAuthChoices.find((choice) => choice.deprecatedChoiceIds.includes(choiceId)), + manifestAuthChoices.find((choice) => choice.deprecatedChoiceIds?.includes(choiceId) === true), })); import { @@ -57,14 +56,25 @@ describe("auth choice legacy aliases", () => { it("sources deprecated cli aliases from plugin manifests", () => { expect(resolveLegacyAuthChoiceAliasesForCli({ env: authChoiceManifestEnv() })).toEqual([ "claude-cli", - "codex-cli", - "openai-codex", ]); }); - it("maps the old OpenAI Codex setup choice to OpenAI login", () => { - expect(normalizeLegacyOnboardAuthChoice("openai-codex", { env: authChoiceManifestEnv() })).toBe( - "openai", + it("does not keep old OpenAI Codex setup choices alive outside doctor", () => { + const legacyChoice = ["openai", "codex"].join("-"); + expect(normalizeLegacyOnboardAuthChoice(legacyChoice, { env: authChoiceManifestEnv() })).toBe( + legacyChoice, + ); + expect(normalizeLegacyOnboardAuthChoice("codex-cli", { env: authChoiceManifestEnv() })).toBe( + "codex-cli", + ); + expect( + resolveDeprecatedAuthChoiceReplacement(legacyChoice, { env: authChoiceManifestEnv() }), + ).toBeUndefined(); + expect(normalizeLegacyOnboardAuthChoice(`${legacyChoice}-device-code`)).toBe( + `${legacyChoice}-device-code`, + ); + expect(normalizeLegacyOnboardAuthChoice(`${legacyChoice}-api-key`)).toBe( + `${legacyChoice}-api-key`, ); }); }); diff --git a/src/commands/auth-choice-legacy.ts b/src/commands/auth-choice-legacy.ts index f073ad5b73aa..dfb5fe93c984 100644 --- a/src/commands/auth-choice-legacy.ts +++ b/src/commands/auth-choice-legacy.ts @@ -5,12 +5,7 @@ import { } from "../plugins/provider-auth-choices.js"; import type { AuthChoice } from "./onboard-types.js"; -const LEGACY_REPLACEMENT_AUTH_CHOICES = new Set([ - "claude-cli", - "codex-cli", - "openai-codex", - "openai-codex-device-code", -]); +const LEGACY_REPLACEMENT_AUTH_CHOICES = new Set(["claude-cli"]); function resolveLegacyCliBackendChoice( choice: string, @@ -39,7 +34,7 @@ export function resolveLegacyAuthChoiceAliasesForCli(params?: { .flatMap((choice) => choice.deprecatedChoiceIds ?? []) .filter((choice): choice is AuthChoice => LEGACY_REPLACEMENT_AUTH_CHOICES.has(choice)) .toSorted((left, right) => left.localeCompare(right)); - return manifestCliAliases; + return Array.from(new Set(manifestCliAliases)); } export function normalizeLegacyOnboardAuthChoice( diff --git a/src/commands/auth-choice-options.test.ts b/src/commands/auth-choice-options.test.ts index 9592999db6e6..0652d28125b1 100644 --- a/src/commands/auth-choice-options.test.ts +++ b/src/commands/auth-choice-options.test.ts @@ -587,7 +587,7 @@ describe("buildAuthChoiceOptions", () => { assistantPriority: 5, }, { - value: "openai-codex", + value: "openai", label: "ChatGPT/Codex Browser Login", groupId: "openai", groupLabel: "OpenAI", @@ -595,7 +595,7 @@ describe("buildAuthChoiceOptions", () => { onboardingFeatured: true, }, { - value: "openai-codex-device-code", + value: "openai-chatgpt-device-code", label: "ChatGPT/Codex Device Pairing", groupId: "openai", groupLabel: "OpenAI", @@ -610,8 +610,8 @@ describe("buildAuthChoiceOptions", () => { const openAIGroup = requireChoiceGroup(groups, "openai"); expect(openAIGroup.options.map((option) => option.value)).toEqual([ - "openai-codex", - "openai-codex-device-code", + "openai", + "openai-chatgpt-device-code", "openai-api-key", ]); expect(openAIGroup.options[0]?.onboardingFeatured).toBe(true); diff --git a/src/commands/auth-choice.model-check.test.ts b/src/commands/auth-choice.model-check.test.ts index 2926e7708b20..a09bfd4f938a 100644 --- a/src/commands/auth-choice.model-check.test.ts +++ b/src/commands/auth-choice.model-check.test.ts @@ -37,7 +37,7 @@ describe("warnIfModelConfigLooksOff", () => { const config = { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, }, } as OpenClawConfig; @@ -46,14 +46,10 @@ describe("warnIfModelConfigLooksOff", () => { expect(loadModelCatalog).not.toHaveBeenCalled(); expect(ensureAuthProfileStore).toHaveBeenCalledOnce(); - expect(listProfilesForProvider).toHaveBeenCalledTimes(2); - expect(listProfilesForProvider).toHaveBeenCalledWith( - { version: 1, profiles: {} }, - "openai-codex", - ); + expect(listProfilesForProvider).toHaveBeenCalledOnce(); expect(listProfilesForProvider).toHaveBeenCalledWith({ version: 1, profiles: {} }, "openai"); expect(note).toHaveBeenCalledWith( - 'No auth configured for provider "openai-codex". The agent may fail until credentials are added. Run `openclaw models auth login --provider openai`, `openclaw configure`, or set an API key env var.', + 'No auth configured for provider "openai". The agent may fail until credentials are added. Run `openclaw models auth login --provider openai`, `openclaw configure`, or set an API key env var.', "Model check", ); }); @@ -64,9 +60,9 @@ describe("warnIfModelConfigLooksOff", () => { const store = { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -75,7 +71,7 @@ describe("warnIfModelConfigLooksOff", () => { } satisfies AuthProfileStore; ensureAuthProfileStore.mockReturnValue(store); listProfilesForProvider.mockImplementation((_store, provider) => - provider === "openai-codex" ? ["openai-codex:default"] : [], + provider === "openai" ? ["openai:default"] : [], ); const config = { agents: { @@ -91,7 +87,6 @@ describe("warnIfModelConfigLooksOff", () => { expect(note).not.toHaveBeenCalled(); expect(listProfilesForProvider).toHaveBeenCalledWith(store, "openai"); - expect(listProfilesForProvider).toHaveBeenCalledWith(store, "openai-codex"); expect(resolveEnvApiKey).not.toHaveBeenCalled(); expect(hasUsableCustomProviderApiKey).not.toHaveBeenCalled(); }); @@ -99,8 +94,21 @@ describe("warnIfModelConfigLooksOff", () => { it("keeps custom OpenAI-compatible provider auth separate from Codex OAuth profiles", async () => { const note = vi.fn(async () => {}); const prompter = makePrompter({ note }); + const store = { + version: 1, + profiles: { + "openai:default": { + type: "oauth", + provider: "openai", + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, + }, + } satisfies AuthProfileStore; + ensureAuthProfileStore.mockReturnValue(store); listProfilesForProvider.mockImplementation((_store, provider) => - provider === "openai-codex" ? ["openai-codex:default"] : [], + provider === "openai" ? ["openai:default"] : [], ); const config = { agents: { @@ -122,7 +130,7 @@ describe("warnIfModelConfigLooksOff", () => { await warnIfModelConfigLooksOff(config, prompter, { validateCatalog: false }); - expect(listProfilesForProvider.mock.calls.map(([, provider]) => provider)).toEqual(["openai"]); + expect(listProfilesForProvider).toHaveBeenCalledWith(store, "openai"); expect(note).toHaveBeenCalledWith( 'No auth configured for provider "openai". The agent may fail until credentials are added. Run `openclaw models auth login --provider openai`, `openclaw configure`, or set an API key env var.', "Model check", @@ -135,7 +143,7 @@ describe("warnIfModelConfigLooksOff", () => { const config = { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, }, } as OpenClawConfig; diff --git a/src/commands/auth-choice.model-check.ts b/src/commands/auth-choice.model-check.ts index 49952c2a87d3..0632706a1c2c 100644 --- a/src/commands/auth-choice.model-check.ts +++ b/src/commands/auth-choice.model-check.ts @@ -1,9 +1,13 @@ import { ensureAuthProfileStore, listProfilesForProvider } from "../agents/auth-profiles.js"; +import type { AuthProfileCredential } from "../agents/auth-profiles/types.js"; import { resolveAgentHarnessPolicy } from "../agents/harness/policy.js"; import { hasUsableCustomProviderApiKey, resolveEnvApiKey } from "../agents/model-auth.js"; import { loadModelCatalog } from "../agents/model-catalog.js"; import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; -import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../agents/openai-codex-routing.js"; +import { + listOpenAIAuthProfileProvidersForAgentRuntime, + openAIProviderUsesCodexRuntimeByDefault, +} from "../agents/openai-routing.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { WizardPrompter } from "../wizard/prompts.js"; import { buildProviderAuthRecoveryHint } from "./provider-auth-guidance.js"; @@ -32,6 +36,37 @@ function resolveAuthProviderCandidates(params: { ]; } +function resolveAcceptedAuthProfileTypes(params: { + config: OpenClawConfig; + provider: string; +}): readonly AuthProfileCredential["type"][] | undefined { + if ( + openAIProviderUsesCodexRuntimeByDefault({ + provider: params.provider, + config: params.config, + }) + ) { + return undefined; + } + return params.provider === "openai" ? ["api_key"] : undefined; +} + +function hasProfileForProvider(params: { + store: ReturnType; + provider: string; + acceptedTypes?: readonly AuthProfileCredential["type"][]; +}): boolean { + const profileIds = listProfilesForProvider(params.store, params.provider); + if (!params.acceptedTypes) { + return profileIds.length > 0; + } + const acceptedTypes = new Set(params.acceptedTypes); + return profileIds.some((profileId) => { + const profile = params.store.profiles[profileId]; + return profile ? acceptedTypes.has(profile.type) : false; + }); +} + export async function warnIfModelConfigLooksOff( config: OpenClawConfig, prompter: WizardPrompter, @@ -66,8 +101,12 @@ export async function warnIfModelConfigLooksOff( modelId: ref.model, agentId: options?.agentId, }); + const acceptedTypes = resolveAcceptedAuthProfileTypes({ + config, + provider: ref.provider, + }); const hasAuth = - authProviders.some((provider) => listProfilesForProvider(store, provider).length > 0) || + authProviders.some((provider) => hasProfileForProvider({ store, provider, acceptedTypes })) || authProviders.some((provider) => resolveEnvApiKey(provider)) || authProviders.some((provider) => hasUsableCustomProviderApiKey(config, provider)); if (!hasAuth) { diff --git a/src/commands/auth-choice.test.ts b/src/commands/auth-choice.test.ts index 6c3e897c410e..2eddccfcd425 100644 --- a/src/commands/auth-choice.test.ts +++ b/src/commands/auth-choice.test.ts @@ -751,19 +751,19 @@ describe("applyAuthChoice", () => { const spy = vi .spyOn(providerAuthChoices, "resolveManifestDeprecatedProviderAuthChoice") .mockReturnValueOnce({ - choiceId: "openai-codex", + choiceId: "openai", } as never); try { await expect( applyAuthChoice({ - authChoice: "openai-codex-import", + authChoice: "openai-chatgpt-import", config: {}, prompter: createPrompter({}), runtime: createExitThrowingRuntime(), setDefaultModel: true, }), ).rejects.toThrow( - 'Auth choice "openai-codex-import" is no longer supported. Use "openai-codex" instead, or run openclaw onboard to choose interactively.', + 'Auth choice "openai-chatgpt-import" is no longer supported. Use "openai" instead, or run openclaw onboard to choose interactively.', ); } finally { spy.mockRestore(); diff --git a/src/commands/codex-runtime-plugin-install.ts b/src/commands/codex-runtime-plugin-install.ts index f679c7b10141..3e6fc0db0f40 100644 --- a/src/commands/codex-runtime-plugin-install.ts +++ b/src/commands/codex-runtime-plugin-install.ts @@ -1,4 +1,4 @@ -import { modelSelectionShouldEnsureCodexPlugin } from "../agents/openai-codex-routing.js"; +import { modelSelectionShouldEnsureCodexPlugin } from "../agents/openai-routing.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { createRuntimePluginModelSelectionHelpers, diff --git a/src/commands/configure.gateway-auth.prompt-auth-config.test.ts b/src/commands/configure.gateway-auth.prompt-auth-config.test.ts index daea72e8f944..f3b0aaac8712 100644 --- a/src/commands/configure.gateway-auth.prompt-auth-config.test.ts +++ b/src/commands/configure.gateway-auth.prompt-auth-config.test.ts @@ -649,10 +649,10 @@ describe("promptAuthConfig", () => { it("lets skip-auth model browsing scope the allowlist to the selected model provider", async () => { vi.clearAllMocks(); mocks.promptAuthChoiceGrouped.mockResolvedValue("skip"); - mocks.promptDefaultModel.mockResolvedValue({ model: "openai-codex/gpt-5.5" }); + mocks.promptDefaultModel.mockResolvedValue({ model: "openai/gpt-5.5" }); mocks.promptModelAllowlist.mockResolvedValue({ - models: ["openai-codex/gpt-5.5"], - scopeKeys: ["openai-codex/gpt-5.5", "openai-codex/gpt-5.5-pro"], + models: ["openai/gpt-5.5"], + scopeKeys: ["openai/gpt-5.5", "openai/gpt-5.5-pro"], }); mocks.resolveProviderPluginChoice.mockReturnValue(null); @@ -670,9 +670,9 @@ describe("promptAuthConfig", () => { expect(promptDefaultModelOptions()?.loadCatalog).toBe(true); expect(promptDefaultModelOptions()?.browseCatalogOnDemand).toBe(true); - expect(promptModelAllowlistOptions()?.preferredProvider).toBe("openai-codex"); - expect(result.agents?.defaults?.model).toEqual({ primary: "openai-codex/gpt-5.5" }); - expect(Object.keys(result.agents?.defaults?.models ?? {})).toEqual(["openai-codex/gpt-5.5"]); + expect(promptModelAllowlistOptions()?.preferredProvider).toBe("openai"); + expect(result.agents?.defaults?.model).toEqual({ primary: "openai/gpt-5.5" }); + expect(Object.keys(result.agents?.defaults?.models ?? {})).toEqual(["openai/gpt-5.5"]); }); it("returns to auth selection when plugin install onboarding asks for a retry", async () => { diff --git a/src/commands/configure.wizard.test.ts b/src/commands/configure.wizard.test.ts index aff2ed58723c..f4ba30d6ba58 100644 --- a/src/commands/configure.wizard.test.ts +++ b/src/commands/configure.wizard.test.ts @@ -33,7 +33,7 @@ const mocks = vi.hoisted(() => { gateway: { mode: "remote", remote: { url: "wss://gateway.example.test" } }, })), isCodexNativeWebSearchRelevant: vi.fn(({ config }: { config: OpenClawConfig }) => - Boolean(config.auth?.profiles?.["openai-codex:default"]), + Boolean(config.auth?.profiles?.["openai:default"]), ), setupChannels: vi.fn(async (cfg: OpenClawConfig) => cfg), }; @@ -502,8 +502,8 @@ describe("runConfigureWizard", () => { setupBaseWizardState({ auth: { profiles: { - "openai-codex:default": { - provider: "openai-codex", + "openai:default": { + provider: "openai", mode: "oauth", }, }, @@ -528,8 +528,8 @@ describe("runConfigureWizard", () => { setupBaseWizardState({ auth: { profiles: { - "openai-codex:default": { - provider: "openai-codex", + "openai:default": { + provider: "openai", mode: "oauth", }, }, diff --git a/src/commands/doctor-auth-oauth-sidecar.ts b/src/commands/doctor-auth-oauth-sidecar.ts index a0c4b15a584b..b7d929337f00 100644 --- a/src/commands/doctor-auth-oauth-sidecar.ts +++ b/src/commands/doctor-auth-oauth-sidecar.ts @@ -3,15 +3,6 @@ import path from "node:path"; import { note } from "../../packages/terminal-core/src/note.js"; import { listAgentIds, resolveAgentDir, resolveDefaultAgentDir } from "../agents/agent-scope.js"; import { AUTH_STORE_VERSION } from "../agents/auth-profiles/constants.js"; -import { - isLegacyOAuthRef, - isLegacyOAuthSidecarPayload, - legacyOAuthSidecarTestUtils, - loadLegacyOAuthSidecarMaterial, - resolveLegacyOAuthSidecarPath, - type LegacyOAuthRef, - type LegacyOAuthSecretMaterial, -} from "../agents/auth-profiles/legacy-oauth-sidecar.js"; import { resolveAuthStorePath } from "../agents/auth-profiles/paths.js"; import { clearRuntimeAuthProfileStoreSnapshots } from "../agents/auth-profiles/store.js"; import { formatCliCommand } from "../cli/command-format.js"; @@ -21,6 +12,15 @@ import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; import { isRecord } from "../shared/record-coerce.js"; import { shortenHomePath } from "../utils.js"; import type { DoctorPrompter } from "./doctor-prompter.js"; +import { + isLegacyOAuthRef, + isLegacyOAuthSidecarPayload, + legacyOAuthSidecarTestUtils, + loadLegacyOAuthSidecarMaterial, + resolveLegacyOAuthSidecarPath, + type LegacyOAuthRef, + type LegacyOAuthSecretMaterial, +} from "./doctor/shared/legacy-oauth-sidecar.js"; const LEGACY_OAUTH_SECRET_DIRNAME = "auth-profiles"; diff --git a/src/commands/doctor-auth.hints.test.ts b/src/commands/doctor-auth.hints.test.ts index 677e1e764cfa..03f899ce71e3 100644 --- a/src/commands/doctor-auth.hints.test.ts +++ b/src/commands/doctor-auth.hints.test.ts @@ -1,7 +1,40 @@ -import { describe, expect, it } from "vitest"; -import { formatOAuthRefreshFailureDoctorLine, resolveUnusableProfileHint } from "./doctor-auth.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { + formatOAuthRefreshFailureDoctorLine, + noteLegacyCodexProviderOverride, + resolveUnusableProfileHint, +} from "./doctor-auth.js"; + +const mocks = vi.hoisted(() => ({ + ensureAuthProfileStore: vi.fn(), + note: vi.fn(), +})); + +vi.mock("../../packages/terminal-core/src/note.js", () => ({ + note: mocks.note, +})); + +vi.mock("../agents/auth-profiles.js", async () => { + const actual = await vi.importActual( + "../agents/auth-profiles.js", + ); + return { + ...actual, + ensureAuthProfileStore: mocks.ensureAuthProfileStore, + }; +}); + +function doctorFixtureConfig(config: unknown): OpenClawConfig { + return config as OpenClawConfig; +} describe("resolveUnusableProfileHint", () => { + beforeEach(() => { + mocks.ensureAuthProfileStore.mockReset().mockReturnValue({ version: 1, profiles: {} }); + mocks.note.mockClear(); + }); + it("returns billing guidance for disabled billing profiles", () => { expect(resolveUnusableProfileHint({ kind: "disabled", reason: "billing" })).toBe( "Top up credits (provider billing) or switch provider.", @@ -64,4 +97,64 @@ describe("resolveUnusableProfileHint", () => { "- openai-codex:default: re-auth required [invalid_grant] — Run `openclaw models auth login --provider openai`.", ); }); + + it("warns when a legacy Codex override shadows canonical OpenAI OAuth config", () => { + noteLegacyCodexProviderOverride( + doctorFixtureConfig({ + auth: { + profiles: { + "openai:default": { + provider: "openai", + mode: "oauth", + }, + }, + }, + models: { + providers: { + "openai-codex": { + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", + }, + }, + }, + }), + ); + + expect(mocks.note).toHaveBeenCalledWith( + expect.stringContaining("models.providers.openai-codex"), + "Codex OAuth", + ); + }); + + it("warns when a legacy Codex override shadows stored legacy OAuth state", () => { + mocks.ensureAuthProfileStore.mockReturnValue({ + version: 1, + profiles: { + "openai-codex:default": { + type: "oauth", + provider: "openai-codex", + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 60_000, + }, + }, + }); + + noteLegacyCodexProviderOverride( + doctorFixtureConfig({ + models: { + providers: { + "openai-codex": { + models: [{ id: "gpt-5.5", api: "openai-responses" }], + }, + }, + }, + }), + ); + + expect(mocks.note).toHaveBeenCalledWith( + expect.stringContaining("legacy transport override"), + "Codex OAuth", + ); + }); }); diff --git a/src/commands/doctor-auth.ts b/src/commands/doctor-auth.ts index b2b043a7923c..01f7b0ccdd23 100644 --- a/src/commands/doctor-auth.ts +++ b/src/commands/doctor-auth.ts @@ -36,21 +36,26 @@ import { isRecord } from "../utils.js"; import type { DoctorPrompter } from "./doctor-prompter.js"; import { buildProviderAuthRecoveryHint } from "./provider-auth-guidance.js"; -const CODEX_PROVIDER_ID = "openai"; +const OPENAI_PROVIDER_ID = "openai"; +const LEGACY_CODEX_PROVIDER_ID = "openai-codex"; const CODEX_OAUTH_WARNING_TITLE = "Codex OAuth"; const OPENAI_BASE_URL = "https://api.openai.com/v1"; const LEGACY_CODEX_APIS = new Set(["openai-responses", "openai-completions"]); function hasConfiguredCodexOAuthProfile(cfg: OpenClawConfig): boolean { return Object.values(cfg.auth?.profiles ?? {}).some( - (profile) => profile.provider === CODEX_PROVIDER_ID && profile.mode === "oauth", + (profile) => + (profile.provider === OPENAI_PROVIDER_ID || profile.provider === LEGACY_CODEX_PROVIDER_ID) && + profile.mode === "oauth", ); } function hasStoredCodexOAuthProfile(): boolean { const store = ensureAuthProfileStore(undefined, { allowKeychainPrompt: false }); return Object.values(store.profiles).some( - (profile) => profile.provider === CODEX_PROVIDER_ID && profile.type === "oauth", + (profile) => + (profile.provider === OPENAI_PROVIDER_ID || profile.provider === LEGACY_CODEX_PROVIDER_ID) && + profile.type === "oauth", ); } @@ -89,16 +94,16 @@ function hasLegacyCodexTransportOverride(providerOverride: unknown): boolean { function buildCodexProviderOverrideWarning(providerOverride: unknown): string { const lines = [ - `- models.providers.${CODEX_PROVIDER_ID} contains a legacy transport override while Codex OAuth is configured.`, + `- models.providers.${LEGACY_CODEX_PROVIDER_ID} contains a legacy transport override while Codex OAuth is configured.`, "- Older OpenAI transport settings can shadow the built-in Codex OAuth provider path.", ]; if (isRecord(providerOverride)) { const record = providerOverride; if (typeof record.api === "string") { - lines.push(`- models.providers.${CODEX_PROVIDER_ID}.api=${record.api}`); + lines.push(`- models.providers.${LEGACY_CODEX_PROVIDER_ID}.api=${record.api}`); } if (typeof record.baseUrl === "string") { - lines.push(`- models.providers.${CODEX_PROVIDER_ID}.baseUrl=${record.baseUrl}`); + lines.push(`- models.providers.${LEGACY_CODEX_PROVIDER_ID}.baseUrl=${record.baseUrl}`); } } lines.push( @@ -111,7 +116,7 @@ function buildCodexProviderOverrideWarning(providerOverride: unknown): string { } export function noteLegacyCodexProviderOverride(cfg: OpenClawConfig): void { - const providerOverride = cfg.models?.providers?.[CODEX_PROVIDER_ID]; + const providerOverride = cfg.models?.providers?.[LEGACY_CODEX_PROVIDER_ID]; if (!providerOverride) { return; } diff --git a/src/commands/doctor-legacy-config.migrations.test.ts b/src/commands/doctor-legacy-config.migrations.test.ts index c4db7f1c96e1..cb64ef99b42f 100644 --- a/src/commands/doctor-legacy-config.migrations.test.ts +++ b/src/commands/doctor-legacy-config.migrations.test.ts @@ -515,12 +515,12 @@ describe("normalizeCompatibilityConfigValues", () => { providers: { "openai-codex": { baseUrl: "https://chatgpt.com/backend-api", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", models: [ { id: "gpt-5.5", name: "gpt-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", reasoning: true, input: ["text", "image"], cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, @@ -548,12 +548,12 @@ describe("normalizeCompatibilityConfigValues", () => { providers: { "openai-codex": { baseUrl: "https://chatgpt.com/backend-api", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", models: [ { id: "gpt-5.5", name: "gpt-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", reasoning: true, input: ["text", "image"], cost: { input: 9, output: 99, cacheRead: 0.9, cacheWrite: 0 }, @@ -572,12 +572,12 @@ describe("normalizeCompatibilityConfigValues", () => { providers: { "openai-codex": { baseUrl: "https://chatgpt.com/backend-api", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", models: [ { id: "gpt-5.5", name: "gpt-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", reasoning: true, input: ["text", "image"], cost: { input: 9, output: 99, cacheRead: 0.9, cacheWrite: 0 }, diff --git a/src/commands/doctor-memory-search.ts b/src/commands/doctor-memory-search.ts index d954b896e28a..abad6a2c43a7 100644 --- a/src/commands/doctor-memory-search.ts +++ b/src/commands/doctor-memory-search.ts @@ -667,6 +667,9 @@ async function hasApiKeyForProvider( } function resolvePrimaryMemoryProviderEnvVar(provider: string): string { + if (provider === "openai") { + return "OPENAI_API_KEY"; + } const metadata = resolveMemoryEmbeddingProviderDoctorMetadata(provider); return metadata?.envVars[0] ?? `${provider.toUpperCase()}_API_KEY`; } diff --git a/src/commands/doctor.e2e-harness.ts b/src/commands/doctor.e2e-harness.ts index e42f3e6174ea..cd25c0782f27 100644 --- a/src/commands/doctor.e2e-harness.ts +++ b/src/commands/doctor.e2e-harness.ts @@ -591,6 +591,14 @@ beforeEach(() => { serviceUninstall.mockReset().mockResolvedValue(undefined); serviceReadCommand.mockReset().mockResolvedValue(null); callGateway.mockReset().mockRejectedValue(new Error("gateway closed")); + autoMigrateLegacyStateDir.mockReset().mockResolvedValue({ + migrated: false, + skipped: false, + changes: [], + warnings: [], + }); + autoMigrateLegacyState.mockReset().mockResolvedValue({ changes: [], warnings: [] }); + autoMigrateLegacyTaskStateSidecars.mockReset().mockResolvedValue({ changes: [], warnings: [] }); runChannelPluginStartupMaintenance.mockReset().mockResolvedValue(undefined); originalIsTTY = process.stdin.isTTY; diff --git a/src/commands/doctor.fast-path-mocks.ts b/src/commands/doctor.fast-path-mocks.ts index a909674eccbf..2ff3e73de2c5 100644 --- a/src/commands/doctor.fast-path-mocks.ts +++ b/src/commands/doctor.fast-path-mocks.ts @@ -8,6 +8,21 @@ vi.mock("./doctor-bootstrap-size.js", () => ({ noteBootstrapFileSize: vi.fn().mockResolvedValue(undefined), })); +vi.mock("./doctor-auth-flat-profiles.js", () => ({ + maybeRepairCanonicalApiKeyFieldAlias: vi.fn(async (params: { cfg: unknown }) => params.cfg), + maybeRepairLegacyFlatAuthProfileStores: vi.fn().mockResolvedValue(undefined), + maybeRepairOpenAICodexAuthConfig: vi.fn((cfg: unknown) => cfg), + maybeRepairOpenAICodexAuthProfileStores: vi.fn().mockResolvedValue(undefined), +})); + +vi.mock("./doctor-auth-legacy-oauth.js", () => ({ + maybeRepairLegacyOAuthProfileIds: vi.fn(async (cfg: unknown) => cfg), +})); + +vi.mock("./doctor-auth-oauth-sidecar.js", () => ({ + maybeRepairLegacyOAuthSidecarProfiles: vi.fn().mockResolvedValue(undefined), +})); + vi.mock("./doctor-browser.js", () => ({ detectLegacyClawdBrowserProfileResidue: vi.fn().mockResolvedValue(null), maybeArchiveLegacyClawdBrowserProfileResidue: vi.fn().mockResolvedValue({ @@ -21,6 +36,23 @@ vi.mock("./doctor-claude-cli.js", () => ({ noteClaudeCliHealth: vi.fn(), })); +vi.mock("./doctor-command-owner.js", () => ({ + noteCommandOwnerHealth: vi.fn(), +})); + +vi.mock("./doctor-config-audit-scrub.js", () => ({ + maybeScrubConfigAuditLog: vi.fn().mockResolvedValue(undefined), +})); + +vi.mock("./doctor-cron.js", () => ({ + maybeRepairLegacyCronStore: vi.fn().mockResolvedValue(undefined), + noteLegacyWhatsAppCrontabHealthCheck: vi.fn().mockResolvedValue(undefined), +})); + +vi.mock("./doctor-device-pairing.js", () => ({ + noteDevicePairingHealth: vi.fn().mockResolvedValue(undefined), +})); + vi.mock("./doctor-gateway-daemon-flow.js", () => ({ maybeRepairGatewayDaemon: vi.fn().mockResolvedValue(undefined), })); @@ -38,6 +70,14 @@ vi.mock("./doctor-memory-search.js", () => ({ noteMemorySearchHealth: vi.fn().mockResolvedValue(undefined), })); +vi.mock("./doctor-plugin-manifests.js", () => ({ + maybeRepairLegacyPluginManifestContracts: vi.fn().mockResolvedValue(undefined), +})); + +vi.mock("./doctor-plugin-registry.js", () => ({ + maybeRepairPluginRegistryState: vi.fn(async ({ config }: { config: unknown }) => config), +})); + vi.mock("./doctor-platform-notes.js", () => ({ noteStartupOptimizationHints: vi.fn(), noteMacLaunchAgentOverrides: vi.fn().mockResolvedValue(undefined), @@ -63,6 +103,14 @@ vi.mock("./doctor-session-transcripts.js", () => ({ noteSessionTranscriptHealth: vi.fn().mockResolvedValue(undefined), })); +vi.mock("./doctor-session-snapshots.js", () => ({ + noteSessionSnapshotHealth: vi.fn().mockResolvedValue(undefined), +})); + +vi.mock("./doctor-skills.js", () => ({ + maybeRepairSkillReadiness: vi.fn(async ({ cfg }: { cfg: unknown }) => cfg), +})); + vi.mock("./doctor-state-integrity.js", () => ({ noteStateIntegrity: vi.fn().mockResolvedValue(undefined), noteWorkspaceBackupTip: vi.fn(), @@ -72,10 +120,22 @@ vi.mock("./doctor-ui.js", () => ({ maybeRepairUiProtocolFreshness: vi.fn().mockResolvedValue(undefined), })); +vi.mock("./doctor-whatsapp-responsiveness.js", () => ({ + noteWhatsappResponsivenessHealth: vi.fn().mockResolvedValue(undefined), +})); + vi.mock("./doctor-workspace-status.js", () => ({ noteWorkspaceStatus: vi.fn(), })); +vi.mock("../flows/doctor-startup-channel-maintenance.js", () => ({ + maybeRunDoctorStartupChannelMaintenance: vi.fn().mockResolvedValue(undefined), +})); + +vi.mock("./doctor-heartbeat-template-repair.js", () => ({ + maybeRepairHeartbeatTemplate: vi.fn().mockResolvedValue(undefined), +})); + vi.mock("./oauth-tls-preflight.js", () => ({ noteOpenAIOAuthTlsPrerequisites: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/commands/doctor/shared/codex-route-warnings.test.ts b/src/commands/doctor/shared/codex-route-warnings.test.ts index eca6bf8dfe03..d2dee8b466be 100644 --- a/src/commands/doctor/shared/codex-route-warnings.test.ts +++ b/src/commands/doctor/shared/codex-route-warnings.test.ts @@ -3474,7 +3474,7 @@ describe("collectCodexRouteWarnings", () => { agentId: "worker", config: cfg, }).runtime, - ).toBe("codex"); + ).toBe("auto"); const result = maybeRepairCodexRoutes({ cfg, diff --git a/src/commands/doctor/shared/codex-route-warnings.ts b/src/commands/doctor/shared/codex-route-warnings.ts index 8ab9a5445de6..0e4f496ad69d 100644 --- a/src/commands/doctor/shared/codex-route-warnings.ts +++ b/src/commands/doctor/shared/codex-route-warnings.ts @@ -7,7 +7,7 @@ import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../../agents/defaults.js"; import { splitTrailingAuthProfile } from "../../../agents/model-ref-profile.js"; import { normalizeConfiguredProviderCatalogModelId } from "../../../agents/model-ref-shared.js"; import { resolveModelRuntimePolicy } from "../../../agents/model-runtime-policy.js"; -import { openAIProviderUsesCodexRuntimeByDefault } from "../../../agents/openai-codex-routing.js"; +import { openAIProviderUsesCodexRuntimeByDefault } from "../../../agents/openai-routing.js"; import { loadSessionStore, updateSessionStore } from "../../../config/sessions/store.js"; import { resolveAllAgentSessionStoreTargetsSync } from "../../../config/sessions/targets.js"; import type { SessionEntry } from "../../../config/sessions/types.js"; diff --git a/src/commands/doctor/shared/legacy-models-add-metadata.test.ts b/src/commands/doctor/shared/legacy-models-add-metadata.test.ts index 862e47bac431..2a65aa84991b 100644 --- a/src/commands/doctor/shared/legacy-models-add-metadata.test.ts +++ b/src/commands/doctor/shared/legacy-models-add-metadata.test.ts @@ -2,10 +2,13 @@ import { describe, expect, it } from "vitest"; import type { ModelDefinitionConfig } from "../../../config/types.models.js"; import { isLegacyModelsAddCodexMetadataModel } from "./legacy-models-add-metadata.js"; -function buildLegacyModel(id: string): Partial { +function buildLegacyModel( + id: string, + api: "openai-codex-responses" | "openai-chatgpt-responses" = "openai-codex-responses", +): Partial { return { id, - api: "openai-codex-responses", + api: api as ModelDefinitionConfig["api"], reasoning: true, input: ["text", "image"], cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, @@ -25,6 +28,15 @@ describe("isLegacyModelsAddCodexMetadataModel", () => { ).toBe(true); }); + it("also matches the normalized ChatGPT API value before rewrite", () => { + expect( + isLegacyModelsAddCodexMetadataModel({ + provider: "openai-codex", + model: buildLegacyModel("gpt-5.5-pro", "openai-chatgpt-responses"), + }), + ).toBe(true); + }); + it("does not match current pro pricing as legacy models-add metadata", () => { expect( isLegacyModelsAddCodexMetadataModel({ diff --git a/src/commands/doctor/shared/legacy-models-add-metadata.ts b/src/commands/doctor/shared/legacy-models-add-metadata.ts index 270b4fd29cbc..32aa646107b8 100644 --- a/src/commands/doctor/shared/legacy-models-add-metadata.ts +++ b/src/commands/doctor/shared/legacy-models-add-metadata.ts @@ -2,6 +2,10 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import type { ModelDefinitionConfig } from "../../../config/types.models.js"; const LEGACY_MODELS_ADD_CODEX_MODEL_IDS = new Set(["gpt-5.5", "gpt-5.5-pro"]); +const LEGACY_MODELS_ADD_CODEX_APIS = new Set([ + "openai-codex-responses", + "openai-chatgpt-responses", +]); export function isLegacyModelsAddCodexMetadataModel(params: { provider: string; @@ -16,7 +20,8 @@ export function isLegacyModelsAddCodexMetadataModel(params: { return false; } return ( - model.api === "openai-codex-responses" && + typeof model.api === "string" && + LEGACY_MODELS_ADD_CODEX_APIS.has(model.api) && model.reasoning === true && Array.isArray(model.input) && model.input.length === 2 && diff --git a/src/agents/auth-profiles/legacy-oauth-sidecar.test.ts b/src/commands/doctor/shared/legacy-oauth-sidecar.test.ts similarity index 95% rename from src/agents/auth-profiles/legacy-oauth-sidecar.test.ts rename to src/commands/doctor/shared/legacy-oauth-sidecar.test.ts index d23b885f18b7..1637325fe96c 100644 --- a/src/agents/auth-profiles/legacy-oauth-sidecar.test.ts +++ b/src/commands/doctor/shared/legacy-oauth-sidecar.test.ts @@ -1,10 +1,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { resetLogger, setLoggerOverride } from "../../logging/logger.js"; -import { loggingState } from "../../logging/state.js"; +import { resetLogger, setLoggerOverride } from "../../../logging/logger.js"; +import { loggingState } from "../../../logging/state.js"; import { createOpenClawTestState, type OpenClawTestState, -} from "../../test-utils/openclaw-test-state.js"; +} from "../../../test-utils/openclaw-test-state.js"; import { legacyOAuthSidecarInternalTestUtils, legacyOAuthSidecarTestUtils, diff --git a/src/agents/auth-profiles/legacy-oauth-sidecar.ts b/src/commands/doctor/shared/legacy-oauth-sidecar.ts similarity index 97% rename from src/agents/auth-profiles/legacy-oauth-sidecar.ts rename to src/commands/doctor/shared/legacy-oauth-sidecar.ts index f01c3cb6bb70..f9e848439c1f 100644 --- a/src/agents/auth-profiles/legacy-oauth-sidecar.ts +++ b/src/commands/doctor/shared/legacy-oauth-sidecar.ts @@ -3,11 +3,11 @@ import { createCipheriv, createDecipheriv, hash } from "node:crypto"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { resolveOAuthDir, resolveStateDir } from "../../config/paths.js"; -import { loadJsonFile } from "../../infra/json-file.js"; -import { isRecord } from "../../shared/record-coerce.js"; -import { uniqueStrings } from "../../shared/string-normalization.js"; -import { log } from "./constants.js"; +import { log } from "../../../agents/auth-profiles/constants.js"; +import { resolveOAuthDir, resolveStateDir } from "../../../config/paths.js"; +import { loadJsonFile } from "../../../infra/json-file.js"; +import { isRecord } from "../../../shared/record-coerce.js"; +import { uniqueStrings } from "../../../shared/string-normalization.js"; const LEGACY_OAUTH_REF_SOURCE = "openclaw-credentials"; const LEGACY_OAUTH_REF_PROVIDER = "openai-codex"; diff --git a/src/commands/doctor/shared/stale-plugin-config.test.ts b/src/commands/doctor/shared/stale-plugin-config.test.ts index 4bb16cfc88bf..5727266cb712 100644 --- a/src/commands/doctor/shared/stale-plugin-config.test.ts +++ b/src/commands/doctor/shared/stale-plugin-config.test.ts @@ -380,7 +380,7 @@ describe("doctor stale plugin config helpers", () => { expect(warnings[2]).toContain("Auto-removal is paused"); }); - it("treats legacy plugin aliases as valid ids during scan and repair", () => { + it("treats legacy OpenAI Codex plugin ids as stale during scan and repair", () => { const cfg = { plugins: { allow: ["openai-codex", "acpx"], @@ -392,11 +392,21 @@ describe("doctor stale plugin config helpers", () => { } as OpenClawConfig; expect(scanStalePluginConfig(cfg)).toEqual([ + { + pluginId: "openai-codex", + pathLabel: "plugins.allow", + surface: "allow", + }, { pluginId: "acpx", pathLabel: "plugins.allow", surface: "allow", }, + { + pluginId: "openai-codex", + pathLabel: "plugins.entries.openai-codex", + surface: "entries", + }, { pluginId: "acpx", pathLabel: "plugins.entries.acpx", @@ -405,9 +415,7 @@ describe("doctor stale plugin config helpers", () => { ]); const result = maybeRepairStalePluginConfig(cfg); - expect(result.config.plugins?.allow).toEqual(["openai-codex"]); - expect(result.config.plugins?.entries).toEqual({ - "openai-codex": { enabled: true }, - }); + expect(result.config.plugins?.allow).toEqual([]); + expect(result.config.plugins?.entries).toEqual({}); }); }); diff --git a/src/commands/model-picker.test.ts b/src/commands/model-picker.test.ts index 4c1e8d7d4149..76a2b3559e9f 100644 --- a/src/commands/model-picker.test.ts +++ b/src/commands/model-picker.test.ts @@ -349,18 +349,13 @@ afterEach(() => { }); describe("promptDefaultModel", () => { - it("adds runtime-route hints for canonical and legacy OpenAI Codex models", async () => { + it("adds runtime-route hints for canonical OpenAI models", async () => { loadModelCatalog.mockResolvedValue([ { provider: "openai", id: "gpt-5.5", name: "GPT-5.5", }, - { - provider: "openai-codex", - id: "gpt-5.5", - name: "GPT-5.5", - }, ]); const select = vi.fn(async (params) => params.initialValue as never); @@ -377,8 +372,6 @@ describe("promptDefaultModel", () => { const options = pickerOptions(select as MockCallSource); const canonical = requireOption(options, "openai/gpt-5.5"); expect(canonical.hint).toContain("Codex runtime route"); - const legacy = requireOption(options, "openai-codex/gpt-5.5"); - expect(legacy.hint).toContain("legacy Codex OAuth route"); }); it("hides unauthenticated catalog entries from default model choices", async () => { @@ -434,7 +427,7 @@ describe("promptDefaultModel", () => { { provider: "openai", id: "gpt-5.5", name: "GPT-5.5" }, { provider: "anthropic", id: "claude-sonnet-4-6", name: "Claude Sonnet" }, { provider: "google", id: "gemini-3-pro-preview", name: "Gemini 3 Pro" }, - { provider: "openai-codex", id: "gpt-5.5", name: "GPT-5.5" }, + { provider: "openai", id: "gpt-5.5", name: "GPT-5.5" }, ]); const select = vi.fn(async (params) => params.initialValue as never); @@ -453,7 +446,6 @@ describe("promptDefaultModel", () => { "openai/gpt-5.5", "anthropic/claude-sonnet-4-6", "google/gemini-3.1-pro-preview", - "openai-codex/gpt-5.5", ]); }); @@ -703,7 +695,7 @@ describe("promptDefaultModel", () => { const config = { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, }, } as OpenClawConfig; @@ -714,7 +706,7 @@ describe("promptDefaultModel", () => { allowKeep: true, includeManual: true, ignoreAllowlist: true, - preferredProvider: "openai-codex", + preferredProvider: "openai", browseCatalogOnDemand: true, }); @@ -766,12 +758,12 @@ describe("promptDefaultModel", () => { it("loads the full model catalog when browsing without a preferred provider", async () => { loadModelCatalog.mockResolvedValue([ { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", name: "GPT-5.5", }, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5-pro", name: "GPT-5.5 Pro", }, @@ -781,7 +773,7 @@ describe("promptDefaultModel", () => { .mockResolvedValueOnce("__browse__") .mockImplementationOnce(async (params) => { const option = params.options.find( - (entry: { value: string }) => entry.value === "openai-codex/gpt-5.5-pro", + (entry: { value: string }) => entry.value === "openai/gpt-5.5-pro", ); return option?.value ?? params.initialValue; }); @@ -789,7 +781,7 @@ describe("promptDefaultModel", () => { const config = { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, }, } as OpenClawConfig; @@ -803,7 +795,7 @@ describe("promptDefaultModel", () => { browseCatalogOnDemand: true, }); - expect(result.model).toBe("openai-codex/gpt-5.5-pro"); + expect(result.model).toBe("openai/gpt-5.5-pro"); expect(loadModelCatalog).toHaveBeenCalledOnce(); expect(loadPreferredProviderPickerCatalog).not.toHaveBeenCalled(); expect(select).toHaveBeenCalledTimes(2); @@ -813,12 +805,12 @@ describe("promptDefaultModel", () => { it("loads the preferred provider catalog when the user chooses to browse", async () => { loadPreferredProviderPickerCatalog.mockResolvedValue([ { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", name: "GPT-5.5", }, { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5-pro", name: "GPT-5.5 Pro", }, @@ -828,7 +820,7 @@ describe("promptDefaultModel", () => { .mockResolvedValueOnce("__browse__") .mockImplementationOnce(async (params) => { const option = params.options.find( - (entry: { value: string }) => entry.value === "openai-codex/gpt-5.5-pro", + (entry: { value: string }) => entry.value === "openai/gpt-5.5-pro", ); return option?.value ?? params.initialValue; }); @@ -836,7 +828,7 @@ describe("promptDefaultModel", () => { const config = { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, }, } as OpenClawConfig; @@ -847,14 +839,14 @@ describe("promptDefaultModel", () => { allowKeep: true, includeManual: true, ignoreAllowlist: true, - preferredProvider: "openai-codex", + preferredProvider: "openai", browseCatalogOnDemand: true, }); - expect(result.model).toBe("openai-codex/gpt-5.5-pro"); + expect(result.model).toBe("openai/gpt-5.5-pro"); expect(loadPreferredProviderPickerCatalog).toHaveBeenCalledWith({ cfg: config, - preferredProvider: "openai-codex", + preferredProvider: "openai", agentDir: expect.stringContaining("agents/main/agent"), }); expect(loadModelCatalog).not.toHaveBeenCalled(); @@ -1966,7 +1958,7 @@ describe("promptModelAllowlist", () => { const config = { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, }, } as OpenClawConfig; @@ -1974,20 +1966,16 @@ describe("promptModelAllowlist", () => { const result = await promptModelAllowlist({ config, prompter, - preferredProvider: "openai-codex", + preferredProvider: "openai", loadCatalog: false, }); expect(loadModelCatalog).not.toHaveBeenCalled(); - expect(optionValues(pickerOptions(multiselect as MockCallSource))).toEqual([ - "openai-codex/gpt-5.5", - ]); - expect(pickerParams(multiselect as MockCallSource).initialValues).toEqual([ - "openai-codex/gpt-5.5", - ]); + expect(optionValues(pickerOptions(multiselect as MockCallSource))).toEqual(["openai/gpt-5.5"]); + expect(pickerParams(multiselect as MockCallSource).initialValues).toEqual(["openai/gpt-5.5"]); expect(result).toEqual({ - models: ["openai-codex/gpt-5.5"], - scopeKeys: ["openai-codex/gpt-5.5"], + models: ["openai/gpt-5.5"], + scopeKeys: ["openai/gpt-5.5"], }); }); @@ -1997,7 +1985,7 @@ describe("promptModelAllowlist", () => { const config = { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, }, } as OpenClawConfig; @@ -2005,21 +1993,19 @@ describe("promptModelAllowlist", () => { const result = await promptModelAllowlist({ config, prompter, - allowedKeys: ["openai-codex/gpt-5.5", "openai-codex/gpt-5.4"], - preferredProvider: "openai-codex", + allowedKeys: ["openai/gpt-5.5", "openai/gpt-5.4"], + preferredProvider: "openai", }); expect(loadModelCatalog).not.toHaveBeenCalled(); expect(optionValues(pickerOptions(multiselect as MockCallSource))).toEqual([ - "openai-codex/gpt-5.5", - "openai-codex/gpt-5.4", - ]); - expect(pickerParams(multiselect as MockCallSource).initialValues).toEqual([ - "openai-codex/gpt-5.5", + "openai/gpt-5.5", + "openai/gpt-5.4", ]); + expect(pickerParams(multiselect as MockCallSource).initialValues).toEqual(["openai/gpt-5.5"]); expect(result).toEqual({ - models: ["openai-codex/gpt-5.5", "openai-codex/gpt-5.4"], - scopeKeys: ["openai-codex/gpt-5.5", "openai-codex/gpt-5.4"], + models: ["openai/gpt-5.5", "openai/gpt-5.4"], + scopeKeys: ["openai/gpt-5.5", "openai/gpt-5.4"], }); }); }); diff --git a/src/commands/models.auth.provider-resolution.test.ts b/src/commands/models.auth.provider-resolution.test.ts index f120526b6874..a5c30542ca29 100644 --- a/src/commands/models.auth.provider-resolution.test.ts +++ b/src/commands/models.auth.provider-resolution.test.ts @@ -15,14 +15,14 @@ describe("resolveRequestedLoginProviderOrThrow", () => { it("returns null and resolves provider by id/alias", () => { const providers = [ makeProvider({ id: "google-gemini-cli", aliases: ["gemini-cli"] }), - makeProvider({ id: "openai", aliases: ["openai-codex"] }), + makeProvider({ id: "openai", aliases: ["openai"] }), makeProvider({ id: "minimax-portal" }), ]; const scenarios = [ { requested: undefined, expectedId: null }, { requested: "google-gemini-cli", expectedId: "google-gemini-cli" }, { requested: "gemini-cli", expectedId: "google-gemini-cli" }, - { requested: "openai-codex", expectedId: "openai" }, + { requested: "openai", expectedId: "openai" }, ] as const; for (const scenario of scenarios) { diff --git a/src/commands/models.list.e2e.test.ts b/src/commands/models.list.e2e.test.ts index a7b2bfe6dd01..67e8dec51f35 100644 --- a/src/commands/models.list.e2e.test.ts +++ b/src/commands/models.list.e2e.test.ts @@ -673,9 +673,7 @@ describe("models list/status", () => { it("filters stale spark rows from models list and registry views", async () => { const suppressSpark = ({ provider, id }: { provider?: string | null; id?: string | null }) => id === "gpt-5.3-codex-spark" && - (provider === "openai" || - provider === "azure-openai-responses" || - provider === "openai-codex"); + (provider === "openai" || provider === "azure-openai-responses" || provider === "openai"); shouldSuppressBuiltInModel.mockImplementation(suppressSpark); shouldSuppressBuiltInModelFromManifest.mockImplementation(suppressSpark); setDefaultModel("openai/gpt-5.5"); diff --git a/src/commands/models/auth-list.test.ts b/src/commands/models/auth-list.test.ts index 5a9017e77302..84cda3914955 100644 --- a/src/commands/models/auth-list.test.ts +++ b/src/commands/models/auth-list.test.ts @@ -65,9 +65,9 @@ describe("modelsAuthListCommand", () => { const store: AuthProfileStore = { version: 1, profiles: { - "openai-codex:user@example.com": { + "openai:user@example.com": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-secret", refresh: "refresh-secret", expires: 1_800_000_000_000, @@ -80,7 +80,7 @@ describe("modelsAuthListCommand", () => { }, }, usageStats: { - "openai-codex:user@example.com": { + "openai:user@example.com": { cooldownUntil: 1_800_000_010_000, }, }, @@ -92,7 +92,7 @@ describe("modelsAuthListCommand", () => { expect(mocks.externalCliDiscoveryForProviderAuth).toHaveBeenCalledWith({ cfg: {}, - provider: "openai-codex", + provider: "openai", }); expect(runtime.jsonPayloads).toStrictEqual([ { @@ -104,25 +104,35 @@ describe("modelsAuthListCommand", () => { cooldownUntil: "2027-01-15T08:00:10.000Z", email: "user@example.com", expiresAt: "2027-01-15T08:00:00.000Z", - id: "openai-codex:user@example.com", - label: "openai-codex:user@example.com", - provider: "openai-codex", + id: "openai:user@example.com", + label: "openai:user@example.com", + provider: "openai", type: "oauth", }, ], - provider: "openai-codex", + provider: "openai", }, ]); expect(JSON.stringify(runtime.jsonPayloads[0])).not.toContain("secret"); }); it("treats the OpenAI filter as the friendly view over API-key and Codex subscription profiles", async () => { + const legacyOpenAIProvider = ["openai", "codex"].join("-"); + const legacyProfileId = `${legacyOpenAIProvider}:legacy@example.com`; const store: AuthProfileStore = { version: 1, profiles: { - "openai-codex:user@example.com": { + [legacyProfileId]: { type: "oauth", - provider: "openai-codex", + provider: legacyOpenAIProvider, + access: "legacy-access-secret", + refresh: "legacy-refresh-secret", + expires: 1_800_000_000_000, + email: "legacy@example.com", + }, + "openai:user@example.com": { + type: "oauth", + provider: "openai", access: "access-secret", refresh: "refresh-secret", expires: 1_800_000_000_000, @@ -155,6 +165,14 @@ describe("modelsAuthListCommand", () => { agentId: "main", authStatePath: "/tmp/openclaw/agents/main/auth-state.json", profiles: [ + { + email: "legacy@example.com", + expiresAt: "2027-01-15T08:00:00.000Z", + id: legacyProfileId, + label: legacyProfileId, + provider: "openai", + type: "oauth", + }, { id: "openai:api-key-backup", label: "openai:api-key-backup", @@ -164,9 +182,9 @@ describe("modelsAuthListCommand", () => { { email: "user@example.com", expiresAt: "2027-01-15T08:00:00.000Z", - id: "openai-codex:user@example.com", - label: "openai-codex:user@example.com", - provider: "openai-codex", + id: "openai:user@example.com", + label: "openai:user@example.com", + provider: "openai", type: "oauth", }, ], @@ -193,9 +211,9 @@ describe("modelsAuthListCommand", () => { const store: AuthProfileStore = { version: 1, profiles: { - "openai-codex:user@example.com": { + "openai:user@example.com": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-secret", refresh: "refresh-secret", expires: 8_700_000_000_000_000, @@ -203,7 +221,7 @@ describe("modelsAuthListCommand", () => { }, }, usageStats: { - "openai-codex:user@example.com": { + "openai:user@example.com": { cooldownUntil: 8_700_000_000_000_000, }, }, @@ -211,7 +229,7 @@ describe("modelsAuthListCommand", () => { mocks.ensureAuthProfileStore.mockReturnValue(store); const runtime = createRuntime(); - await modelsAuthListCommand({ provider: "openai-codex", json: true }, runtime); + await modelsAuthListCommand({ provider: "openai", json: true }, runtime); expect(runtime.jsonPayloads).toStrictEqual([ { @@ -221,13 +239,13 @@ describe("modelsAuthListCommand", () => { profiles: [ { email: "user@example.com", - id: "openai-codex:user@example.com", - label: "openai-codex:user@example.com", - provider: "openai-codex", + id: "openai:user@example.com", + label: "openai:user@example.com", + provider: "openai", type: "oauth", }, ], - provider: "openai-codex", + provider: "openai", }, ]); }); diff --git a/src/commands/models/auth-list.ts b/src/commands/models/auth-list.ts index 9db4158bdba3..e65cd65420d2 100644 --- a/src/commands/models/auth-list.ts +++ b/src/commands/models/auth-list.ts @@ -8,7 +8,7 @@ import { type AuthProfileStore, type ProfileUsageStats, } from "../../agents/auth-profiles.js"; -import { normalizeProviderId } from "../../agents/model-selection.js"; +import { resolveProviderIdForAuth } from "../../agents/provider-auth-aliases.js"; import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js"; import { timestampMsToIsoString } from "../../shared/number-coercion.js"; import { shortenHomePath } from "../../utils.js"; @@ -32,7 +32,7 @@ function resolveProviderFilter(rawProvider: string | undefined): { externalCliProvider: string | undefined; matches: (profile: AuthProfileSummary) => boolean; } { - const provider = rawProvider?.trim() ? normalizeProviderId(rawProvider) : undefined; + const provider = rawProvider?.trim() ? resolveProviderIdForAuth(rawProvider) : undefined; if (!provider) { return { provider: undefined, @@ -40,13 +40,6 @@ function resolveProviderFilter(rawProvider: string | undefined): { matches: () => true, }; } - if (provider === "openai") { - return { - provider, - externalCliProvider: "openai", - matches: (profile) => profile.provider === "openai" || profile.provider === "openai-codex", - }; - } return { provider, externalCliProvider: provider, @@ -86,7 +79,7 @@ function summarizeProfile(params: { const disabledUntil = formatTimestamp(params.usage?.disabledUntil); return { id: params.profileId, - provider: normalizeProviderId(params.profile.provider), + provider: resolveProviderIdForAuth(params.profile.provider), type: params.profile.type, label: resolveAuthProfileDisplayLabel({ cfg: params.cfg, diff --git a/src/commands/models/auth.login-profiles.test.ts b/src/commands/models/auth.login-profiles.test.ts index cc4e434d4028..9b9bde15fe08 100644 --- a/src/commands/models/auth.login-profiles.test.ts +++ b/src/commands/models/auth.login-profiles.test.ts @@ -5,10 +5,10 @@ describe("resolveLoginProfiles", () => { it("returns original profiles when --profile-id is not provided", () => { const profiles = [ { - profileId: "openai-codex:default", + profileId: "openai:default", credential: { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "a", refresh: "r", expires: Date.now() + 60_000, @@ -25,14 +25,14 @@ describe("resolveLoginProfiles", () => { it("overrides profile id when exactly one profile is returned", () => { const resolved = resolveLoginProfiles({ - requestedProfileId: "openai-codex:work", + requestedProfileId: "openai:work", result: { profiles: [ { - profileId: "openai-codex:default", + profileId: "openai:default", credential: { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "a", refresh: "r", expires: Date.now() + 60_000, @@ -43,7 +43,7 @@ describe("resolveLoginProfiles", () => { }); expect(resolved).toHaveLength(1); - expect(resolved[0]?.profileId).toBe("openai-codex:work"); + expect(resolved[0]?.profileId).toBe("openai:work"); }); it("throws when --profile-id is used with multi-profile auth responses", () => { diff --git a/src/commands/models/auth.test.ts b/src/commands/models/auth.test.ts index 5d2c5d7f1867..ef94064057d4 100644 --- a/src/commands/models/auth.test.ts +++ b/src/commands/models/auth.test.ts @@ -391,10 +391,10 @@ describe("modelsAuthLoginCommand", () => { runProviderAuth = vi.fn().mockResolvedValue({ profiles: [ { - profileId: "openai-codex:user@example.com", + profileId: "openai:user@example.com", credential: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -402,11 +402,11 @@ describe("modelsAuthLoginCommand", () => { }, }, ], - defaultModel: "openai-codex/gpt-5.5", + defaultModel: "openai/gpt-5.5", }); mocks.resolvePluginProviders.mockReturnValue([ createProvider({ - id: "openai-codex", + id: "openai", label: "OpenAI Codex", run: runProviderAuth as ProviderPlugin["auth"][number]["run"], }), @@ -437,17 +437,17 @@ describe("modelsAuthLoginCommand", () => { return originalConfig; } - it("runs plugin-owned openai-codex login", async () => { + it("runs plugin-owned openai login", async () => { const runtime = createRuntime(); const fakeStore = { profiles: { - "openai-codex:user@example.com": { + "openai:user@example.com": { type: "oauth", - provider: "openai-codex", + provider: "openai", }, }, usageStats: { - "openai-codex:user@example.com": { + "openai:user@example.com": { disabledUntil: Date.now() + 3_600_000, disabledReason: "auth_permanent", errorCount: 3, @@ -455,20 +455,20 @@ describe("modelsAuthLoginCommand", () => { }, }; mocks.loadAuthProfileStoreForRuntime.mockReturnValue(fakeStore); - mocks.listProfilesForProvider.mockReturnValue(["openai-codex:user@example.com"]); + mocks.listProfilesForProvider.mockReturnValue(["openai:user@example.com"]); - await modelsAuthLoginCommand({ provider: "openai-codex" }, runtime); + await modelsAuthLoginCommand({ provider: "openai" }, runtime); expect(mocks.loadAuthProfileStoreForRuntime).toHaveBeenCalledWith("/tmp/openclaw/agents/main", { externalCli: { mode: "scoped", allowKeychainPrompt: false, - providerIds: ["openai-codex"], + providerIds: ["openai"], }, }); expect(mocks.clearAuthProfileCooldown).toHaveBeenCalledWith({ store: fakeStore, - profileId: "openai-codex:user@example.com", + profileId: "openai:user@example.com", agentDir: "/tmp/openclaw/agents/main", }); expect(mocks.clearAuthProfileCooldown.mock.invocationCallOrder[0]).toBeLessThan( @@ -476,23 +476,23 @@ describe("modelsAuthLoginCommand", () => { ); expect(runProviderAuth).toHaveBeenCalledOnce(); const upsertCall = readMockCallArg(mocks.upsertAuthProfileWithLock) as UpsertAuthProfileCall; - expect(upsertCall.profileId).toBe("openai-codex:user@example.com"); + expect(upsertCall.profileId).toBe("openai:user@example.com"); expect(upsertCall.credential?.type).toBe("oauth"); - expect(upsertCall.credential?.provider).toBe("openai-codex"); + expect(upsertCall.credential?.provider).toBe("openai"); expect(upsertCall.agentDir).toBe("/tmp/openclaw/agents/main"); expect(mocks.promoteAuthProfileInOrder).toHaveBeenCalledWith({ agentDir: "/tmp/openclaw/agents/main", - provider: "openai-codex", - profileId: "openai-codex:user@example.com", + provider: "openai", + profileId: "openai:user@example.com", }); - const savedProfile = lastUpdatedConfig?.auth?.profiles?.["openai-codex:user@example.com"]; - expect(savedProfile?.provider).toBe("openai-codex"); + const savedProfile = lastUpdatedConfig?.auth?.profiles?.["openai:user@example.com"]; + expect(savedProfile?.provider).toBe("openai"); expect(savedProfile?.mode).toBe("oauth"); expect(runtime.log).toHaveBeenCalledWith( - "Auth profile: openai-codex:user@example.com (openai-codex/oauth)", + "Auth profile: openai:user@example.com (openai/oauth)", ); expect(runtime.log).toHaveBeenCalledWith( - "Default model available: openai-codex/gpt-5.5 (use --set-default to apply)", + "Default model available: openai/gpt-5.5 (use --set-default to apply)", ); }); @@ -505,13 +505,13 @@ describe("modelsAuthLoginCommand", () => { type: "api_key", provider: "openai", }, - "openai-codex:user@example.com": { + "openai:user@example.com": { type: "oauth", - provider: "openai-codex", + provider: "openai", }, }, usageStats: { - "openai-codex:user@example.com": { + "openai:user@example.com": { disabledUntil: Date.now() + 3_600_000, disabledReason: "rate_limit", errorCount: 1, @@ -521,10 +521,10 @@ describe("modelsAuthLoginCommand", () => { const runOauthAuth = vi.fn().mockResolvedValue({ profiles: [ { - profileId: "openai-codex:user@example.com", + profileId: "openai:user@example.com", credential: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -543,8 +543,8 @@ describe("modelsAuthLoginCommand", () => { mocks.listProfilesForProvider.mockImplementation((_store: unknown, provider: string) => provider === "openai" ? ["openai:api-key-backup"] - : provider === "openai-codex" - ? ["openai-codex:user@example.com"] + : provider === "openai" + ? ["openai:user@example.com"] : [], ); mocks.resolvePluginProviders.mockReturnValue([ @@ -599,11 +599,7 @@ describe("modelsAuthLoginCommand", () => { profileId: "openai:api-key-backup", agentDir: "/tmp/openclaw/agents/main", }); - expect(mocks.clearAuthProfileCooldown).toHaveBeenCalledWith({ - store: fakeStore, - profileId: "openai-codex:user@example.com", - agentDir: "/tmp/openclaw/agents/main", - }); + expect(mocks.clearAuthProfileCooldown).toHaveBeenCalledOnce(); }); it("honors --method api-key for OpenAI login", async () => { @@ -743,9 +739,9 @@ describe("modelsAuthLoginCommand", () => { const runtime = createRuntime(); const coderStore = { profiles: { - "openai-codex:coder@example.com": { + "openai:coder@example.com": { type: "oauth", - provider: "openai-codex", + provider: "openai", }, }, usageStats: {}, @@ -753,7 +749,7 @@ describe("modelsAuthLoginCommand", () => { const originalConfig = useCoderAgentConfig(); mocks.loadAuthProfileStoreForRuntime.mockReturnValue(coderStore); - await modelsAuthLoginCommand({ provider: "openai-codex", agent: "coder" }, runtime); + await modelsAuthLoginCommand({ provider: "openai", agent: "coder" }, runtime); expect(mocks.resolveDefaultAgentId).not.toHaveBeenCalled(); expect(mocks.resolveAgentDir).toHaveBeenCalledWith(originalConfig, "coder"); @@ -763,7 +759,7 @@ describe("modelsAuthLoginCommand", () => { externalCli: { mode: "scoped", allowKeychainPrompt: false, - providerIds: ["openai-codex"], + providerIds: ["openai"], }, }, ); @@ -775,6 +771,21 @@ describe("modelsAuthLoginCommand", () => { ).toBe("/tmp/openclaw/agents/coder"); }); + it("normalizes legacy OpenAI auth provider requests to the OpenAI plugin", async () => { + const runtime = createRuntime(); + const legacyProvider = ["openai", "codex"].join("-"); + + await modelsAuthLoginCommand({ provider: legacyProvider }, runtime); + + const providerResolutionCall = readMockCallArg( + mocks.resolvePluginProviders, + ) as ResolvePluginProvidersCall; + expect(providerResolutionCall.providerRefs).toEqual(["openai"]); + expect(runProviderAuth).toHaveBeenCalledOnce(); + const upsertCall = readMockCallArg(mocks.upsertAuthProfileWithLock) as UpsertAuthProfileCall; + expect(upsertCall.credential?.provider).toBe("openai"); + }); + it("loads the owning plugin for an explicit provider even in a clean config", async () => { const runtime = createRuntime(); const runClaudeCliMigration = vi.fn().mockResolvedValue({ @@ -982,7 +993,7 @@ describe("modelsAuthLoginCommand", () => { expect(runtime.log).toHaveBeenCalledWith("Default model set to claude-cli/claude-sonnet-4-6"); }); - it("preserves other providers' allowlist entries on an openai-codex OAuth login", async () => { + it("preserves other providers' allowlist entries on an openai OAuth login", async () => { const runtime = createRuntime(); const existingModels = { "anthropic/claude-sonnet-4-6": { alias: "sonnet" }, @@ -994,10 +1005,10 @@ describe("modelsAuthLoginCommand", () => { runProviderAuth.mockResolvedValue({ profiles: [ { - profileId: "openai-codex:user@example.com", + profileId: "openai:user@example.com", credential: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "a", refresh: "r", expires: Date.now() + 60_000, @@ -1005,16 +1016,13 @@ describe("modelsAuthLoginCommand", () => { }, }, ], - configPatch: { agents: { defaults: { models: { "openai-codex/gpt-5.5": {} } } } }, - defaultModel: "openai-codex/gpt-5.5", + configPatch: { agents: { defaults: { models: { "openai/gpt-5.5": {} } } } }, + defaultModel: "openai/gpt-5.5", }); - await modelsAuthLoginCommand({ provider: "openai-codex" }, runtime); + await modelsAuthLoginCommand({ provider: "openai" }, runtime); - expect(lastUpdatedConfig?.agents?.defaults?.models).toEqual({ - ...existingModels, - "openai-codex/gpt-5.5": {}, - }); + expect(lastUpdatedConfig?.agents?.defaults?.models).toEqual(existingModels); }); it("keeps an existing primary when login omits --set-default and the patch recommends another", async () => { @@ -1022,9 +1030,9 @@ describe("modelsAuthLoginCommand", () => { currentConfig = { agents: { defaults: { - model: { primary: "openai-codex/gpt-5.4", fallbacks: [] }, + model: { primary: "openai/gpt-5.4", fallbacks: [] }, models: { - "openai-codex/gpt-5.4": {}, + "openai/gpt-5.4": {}, "anthropic/claude-sonnet-4-6": {}, }, }, @@ -1058,11 +1066,11 @@ describe("modelsAuthLoginCommand", () => { await modelsAuthLoginCommand({ provider: "openai" }, runtime); expect(lastUpdatedConfig?.agents?.defaults?.model).toEqual({ - primary: "openai-codex/gpt-5.4", + primary: "openai/gpt-5.4", fallbacks: [], }); expect(lastUpdatedConfig?.agents?.defaults?.models).toEqual({ - "openai-codex/gpt-5.4": {}, + "openai/gpt-5.4": {}, "anthropic/claude-sonnet-4-6": {}, "openai/gpt-5.5": { alias: "GPT" }, }); @@ -1112,16 +1120,16 @@ describe("modelsAuthLoginCommand", () => { }, }; - await modelsAuthLoginCommand({ provider: "openai-codex", setDefault: true }, runtime); + await modelsAuthLoginCommand({ provider: "openai", setDefault: true }, runtime); expect(lastUpdatedConfig?.agents?.defaults?.model).toEqual({ - primary: "openai-codex/gpt-5.5", + primary: "openai/gpt-5.5", }); expect(lastUpdatedConfig?.agents?.defaults?.models).toEqual({ "anthropic/claude-opus-4-6": {}, - "openai-codex/gpt-5.5": {}, + "openai/gpt-5.5": {}, }); - expect(runtime.log).toHaveBeenCalledWith("Default model set to openai-codex/gpt-5.5"); + expect(runtime.log).toHaveBeenCalledWith("Default model set to openai/gpt-5.5"); }); it("survives lockout clearing failure without blocking login", async () => { @@ -1130,7 +1138,7 @@ describe("modelsAuthLoginCommand", () => { throw new Error("corrupt auth-profiles.json"); }); - await modelsAuthLoginCommand({ provider: "openai-codex" }, runtime); + await modelsAuthLoginCommand({ provider: "openai" }, runtime); expect(runProviderAuth).toHaveBeenCalledOnce(); }); @@ -1139,7 +1147,7 @@ describe("modelsAuthLoginCommand", () => { const runtime = createRuntime(); await expect(modelsAuthLoginCommand({ provider: "anthropic" }, runtime)).rejects.toThrow( - 'Unknown provider "anthropic". Loaded providers: openai-codex. Verify plugins via `openclaw plugins list --json`.', + 'Unknown provider "anthropic". Loaded providers: openai. Verify plugins via `openclaw plugins list --json`.', ); }); @@ -1233,18 +1241,18 @@ describe("modelsAuthLoginCommand", () => { const validateMessages: string[] = []; mocks.clackText.mockImplementation( async (params: { validate?: (value: string) => string | undefined }) => { - const message = params.validate?.("sk-openai-codex-api-key-value"); + const message = params.validate?.("sk-openai-chatgpt-api-key-value"); if (message) { validateMessages.push(message); throw new Error(message); } - return "sk-openai-codex-api-key-value"; + return "sk-openai-chatgpt-api-key-value"; }, ); - await expect( - modelsAuthPasteTokenCommand({ provider: "openai-codex" }, runtime), - ).rejects.toThrow("paste-api-key --provider openai"); + await expect(modelsAuthPasteTokenCommand({ provider: "openai" }, runtime)).rejects.toThrow( + "paste-api-key --provider openai", + ); expect(validateMessages).toEqual([ "That looks like an OpenAI API key. Use openclaw models auth paste-api-key --provider openai for API-key auth.", @@ -1256,11 +1264,11 @@ describe("modelsAuthLoginCommand", () => { it("rejects piped OpenAI API keys as OpenAI Codex token material", async () => { const runtime = createRuntime(); restoreStdin?.(); - restoreStdin = withPipedStdin("sk-openai-codex-api-key-value\n"); + restoreStdin = withPipedStdin("sk-openai-chatgpt-api-key-value\n"); - await expect( - modelsAuthPasteTokenCommand({ provider: "openai-codex" }, runtime), - ).rejects.toThrow("paste-api-key --provider openai"); + await expect(modelsAuthPasteTokenCommand({ provider: "openai" }, runtime)).rejects.toThrow( + "paste-api-key --provider openai", + ); expect(mocks.clackText).not.toHaveBeenCalled(); expect(mocks.upsertAuthProfileWithLock).not.toHaveBeenCalled(); @@ -1270,11 +1278,11 @@ describe("modelsAuthLoginCommand", () => { it("rejects line-wrapped piped OpenAI API keys as OpenAI Codex token material", async () => { const runtime = createRuntime(); restoreStdin?.(); - restoreStdin = withPipedStdin("sk-openai-\ncodex-api-key-value\n"); + restoreStdin = withPipedStdin("sk-openai-\nchat-api-key-value\n"); - await expect( - modelsAuthPasteTokenCommand({ provider: "openai-codex" }, runtime), - ).rejects.toThrow("paste-api-key --provider openai"); + await expect(modelsAuthPasteTokenCommand({ provider: "openai" }, runtime)).rejects.toThrow( + "paste-api-key --provider openai", + ); expect(mocks.clackText).not.toHaveBeenCalled(); expect(mocks.upsertAuthProfileWithLock).not.toHaveBeenCalled(); @@ -1284,9 +1292,9 @@ describe("modelsAuthLoginCommand", () => { it("writes pasted API keys to the requested agent store", async () => { const runtime = createRuntime(); useCoderAgentConfig(); - mocks.clackPassword.mockResolvedValue("sk-openai-codex-api-key-value"); + mocks.clackPassword.mockResolvedValue("sk-openai-chatgpt-api-key-value"); - await modelsAuthPasteApiKeyCommand({ provider: "openai-codex", agent: "coder" }, runtime); + await modelsAuthPasteApiKeyCommand({ provider: "openai", agent: "coder" }, runtime); expect(mocks.resolveDefaultAgentId).not.toHaveBeenCalled(); expect(mocks.upsertAuthProfileWithLock).toHaveBeenCalledWith({ @@ -1294,7 +1302,7 @@ describe("modelsAuthLoginCommand", () => { credential: { type: "api_key", provider: "openai", - key: "sk-openai-codex-api-key-value", + key: "sk-openai-chatgpt-api-key-value", }, agentDir: "/tmp/openclaw/agents/coder", }); @@ -1308,9 +1316,9 @@ describe("modelsAuthLoginCommand", () => { it("writes piped OpenAI Codex API keys to API-key profiles", async () => { const runtime = createRuntime(); restoreStdin?.(); - restoreStdin = withPipedStdin("sk-openai-codex-api-key-value\n"); + restoreStdin = withPipedStdin("sk-openai-chatgpt-api-key-value\n"); - await modelsAuthPasteApiKeyCommand({ provider: "openai-codex" }, runtime); + await modelsAuthPasteApiKeyCommand({ provider: "openai" }, runtime); expect(mocks.clackPassword).not.toHaveBeenCalled(); expect(mocks.upsertAuthProfileWithLock).toHaveBeenCalledWith({ @@ -1318,7 +1326,7 @@ describe("modelsAuthLoginCommand", () => { credential: { type: "api_key", provider: "openai", - key: "sk-openai-codex-api-key-value", + key: "sk-openai-chatgpt-api-key-value", }, agentDir: "/tmp/openclaw/agents/main", }); @@ -1331,9 +1339,9 @@ describe("modelsAuthLoginCommand", () => { it("normalizes line-wrapped piped OpenAI Codex API keys before storing", async () => { const runtime = createRuntime(); restoreStdin?.(); - restoreStdin = withPipedStdin("sk-openai-\ncodex-api-key-value\n"); + restoreStdin = withPipedStdin("sk-openai-\nchat-api-key-value\n"); - await modelsAuthPasteApiKeyCommand({ provider: "openai-codex" }, runtime); + await modelsAuthPasteApiKeyCommand({ provider: "openai" }, runtime); expect(mocks.clackPassword).not.toHaveBeenCalled(); expect(mocks.upsertAuthProfileWithLock).toHaveBeenCalledWith({ @@ -1341,7 +1349,7 @@ describe("modelsAuthLoginCommand", () => { credential: { type: "api_key", provider: "openai", - key: "sk-openai-codex-api-key-value", + key: "sk-openai-chat-api-key-value", }, agentDir: "/tmp/openclaw/agents/main", }); @@ -1366,9 +1374,9 @@ describe("modelsAuthLoginCommand", () => { }, ); - await expect( - modelsAuthPasteApiKeyCommand({ provider: "openai-codex" }, runtime), - ).rejects.toThrow("paste-token --provider openai"); + await expect(modelsAuthPasteApiKeyCommand({ provider: "openai" }, runtime)).rejects.toThrow( + "paste-token --provider openai", + ); expect(validateMessages).toEqual([ "That looks like token or OAuth material, not an OpenAI API key. Use openclaw models auth paste-token --provider openai for token auth material.", diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index a322fcc5d16a..8dae3a21e0f8 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -65,6 +65,7 @@ import { isRemoteEnvironment } from "../oauth-env.js"; import { loadValidConfigOrThrow, resolveKnownAgentId, updateConfig } from "./shared.js"; type UpsertAuthProfileParams = Parameters[0]; +const LEGACY_OPENAI_AUTH_PROVIDER_ID = ["openai", "codex"].join("-"); function resolveManualTokenExpiryMs(expiresIn: string | undefined): number | undefined { const normalizedExpiresIn = normalizeStringifiedOptionalString(expiresIn); @@ -151,11 +152,13 @@ function resolveDefaultTokenProfileId(provider: string): string { function normalizeManualAuthProvider(provider: string): string { const normalized = normalizeProviderId(provider); - return normalized === "openai-codex" ? "openai" : normalized; + return normalized === "openai" || normalized === LEGACY_OPENAI_AUTH_PROVIDER_ID + ? "openai" + : normalized; } function isOpenAIProvider(provider: string): boolean { - return normalizeProviderId(provider) === "openai"; + return normalizeManualAuthProvider(provider) === "openai"; } function stripBearerPrefix(value: string): string { @@ -241,7 +244,9 @@ function preferSetupAuthProviders(params: { workspaceDir: string; requestedProvider?: string; }): ProviderPlugin[] { - const requestedProvider = params.requestedProvider?.trim(); + const requestedProvider = params.requestedProvider + ? normalizeManualAuthProvider(params.requestedProvider) + : undefined; if (requestedProvider) { const setupProvider = resolvePluginSetupProvider({ provider: requestedProvider, @@ -270,15 +275,18 @@ async function resolveModelsAuthContext(params?: { const workspaceDir = resolveAgentWorkspaceDir(config, agentId) ?? resolveDefaultAgentWorkspaceDir(); const requestedProvider = params?.requestedProvider?.trim(); + const providerRef = requestedProvider + ? normalizeManualAuthProvider(requestedProvider) + : undefined; const providers = resolvePluginProviders({ config, workspaceDir, mode: "setup", includeUntrustedWorkspacePlugins: false, bundledProviderVitestCompat: true, - ...(requestedProvider + ...(providerRef ? { - providerRefs: [requestedProvider], + providerRefs: [providerRef], activate: true, } : {}), @@ -287,7 +295,7 @@ async function resolveModelsAuthContext(params?: { providers, config, workspaceDir, - requestedProvider: params?.requestedProvider, + requestedProvider: providerRef, }); return { config, @@ -921,7 +929,10 @@ export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: Runtim ); } - const requestedProvider = resolveRequestedLoginProviderOrThrow(authProviders, opts.provider); + const requestedProvider = resolveRequestedLoginProviderOrThrow( + authProviders, + opts.provider ? normalizeManualAuthProvider(opts.provider) : undefined, + ); const selectedProvider = requestedProvider ?? (await prompter diff --git a/src/commands/models/list.auth-index.test.ts b/src/commands/models/list.auth-index.test.ts index fc66caa8caa0..38b259f856da 100644 --- a/src/commands/models/list.auth-index.test.ts +++ b/src/commands/models/list.auth-index.test.ts @@ -242,19 +242,24 @@ describe("createModelListAuthIndex", () => { expect(index.hasProviderAuth("custom-openai")).toBe(true); }); - it("treats OpenAI Codex auth as usable for canonical OpenAI agent routes", () => { + it("treats OpenAI OAuth auth as usable for canonical OpenAI agent routes", () => { const index = createModelListAuthIndex({ cfg: {}, authStore: { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, }, + "openai:token": { + type: "token", + provider: "openai", + token: "token", + }, }, }, env: {}, @@ -263,7 +268,26 @@ describe("createModelListAuthIndex", () => { expect(index.hasProviderAuth("openai")).toBe(true); }); - it("does not treat OpenAI Codex auth as usable for custom OpenAI-compatible routes", () => { + it("treats OpenAI token auth as usable for canonical OpenAI agent routes", () => { + const index = createModelListAuthIndex({ + cfg: {}, + authStore: { + version: 1, + profiles: { + "openai:token": { + type: "token", + provider: "openai", + token: "token", + }, + }, + }, + env: {}, + }); + + expect(index.hasProviderAuth("openai")).toBe(true); + }); + + it("does not treat OpenAI OAuth auth as usable for custom OpenAI-compatible routes", () => { const index = createModelListAuthIndex({ cfg: { models: { @@ -279,9 +303,9 @@ describe("createModelListAuthIndex", () => { authStore: { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, diff --git a/src/commands/models/list.auth-index.ts b/src/commands/models/list.auth-index.ts index d58010c46832..a36f3d57728d 100644 --- a/src/commands/models/list.auth-index.ts +++ b/src/commands/models/list.auth-index.ts @@ -1,5 +1,6 @@ import { normalizeProviderIdForAuth } from "@openclaw/model-catalog-core/provider-id"; import type { AuthProfileStore } from "../../agents/auth-profiles/types.js"; +import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js"; import { listProviderEnvAuthLookupKeys, resolveProviderEnvAuthLookupMaps, @@ -14,7 +15,7 @@ import { OPENAI_CODEX_PROVIDER_ID, OPENAI_PROVIDER_ID, openAIProviderUsesCodexRuntimeByDefault, -} from "../../agents/openai-codex-routing.js"; +} from "../../agents/openai-routing.js"; import { resolveAgentModelPrimaryValue } from "../../config/model-input.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { PluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.types.js"; @@ -92,6 +93,22 @@ export function createModelListAuthIndex( const authenticatedProviders = new Set(); const syntheticAuthProviders = new Set(); const envProviderAuthCache = new Map(); + const credentialAuthsProvider = (credential: AuthProfileCredential): boolean => { + const normalizedProvider = normalizeStoredAuthProvider(credential.provider, aliasMap); + if (normalizedProvider !== OPENAI_PROVIDER_ID) { + return true; + } + if (credential.type === "api_key") { + return true; + } + if (credential.type !== "oauth" && credential.type !== "token") { + return false; + } + return openAIProviderUsesCodexRuntimeByDefault({ + provider: normalizedProvider, + config: params.cfg, + }); + }; const addProvider = (provider: string | undefined) => { if (!provider?.trim()) { return; @@ -107,7 +124,9 @@ export function createModelListAuthIndex( }; for (const credential of Object.values(params.authStore.profiles ?? {})) { - addProvider(credential.provider); + if (credentialAuthsProvider(credential)) { + addProvider(credential.provider); + } } for (const provider of listProviderEnvAuthLookupKeys({ envCandidateMap, authEvidenceMap })) { diff --git a/src/commands/models/list.auth-overview.test.ts b/src/commands/models/list.auth-overview.test.ts index 33a68561cd64..a69fe882aebb 100644 --- a/src/commands/models/list.auth-overview.test.ts +++ b/src/commands/models/list.auth-overview.test.ts @@ -121,18 +121,18 @@ describe("resolveProviderAuthOverview", () => { it("reports the selected agent auth store when profiles are effective", () => { persistedStores.set("/tmp/openclaw-agent-custom", { profiles: { - "openai-codex:peter@example.test": {}, + "openai:peter@example.test": {}, }, }); const overview = resolveProviderAuthOverview({ - provider: "openai-codex", + provider: "openai", cfg: {}, store: { version: 1, profiles: { - "openai-codex:peter@example.test": { + "openai:peter@example.test": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -152,18 +152,18 @@ describe("resolveProviderAuthOverview", () => { it("reports the main auth store for inherited profiles", () => { persistedStores.set("__main__", { profiles: { - "openai-codex:peter@example.test": {}, + "openai:peter@example.test": {}, }, }); const overview = resolveProviderAuthOverview({ - provider: "openai-codex", + provider: "openai", cfg: {}, store: { version: 1, profiles: { - "openai-codex:peter@example.test": { + "openai:peter@example.test": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -192,14 +192,14 @@ describe("resolveProviderAuthOverview", () => { it("treats OAuth delegation markers as effective models.json auth", () => { const overview = withEnv({ OPENAI_API_KEY: undefined }, () => - resolveOpenAiOverview("oauth:openai-codex"), + resolveOpenAiOverview("oauth:openai"), ); expect(overview.effective).toEqual({ kind: "models.json", - detail: "marker(oauth:openai-codex)", + detail: "marker(oauth:openai)", }); - expect(overview.modelsJson?.value).toBe("marker(oauth:openai-codex)"); + expect(overview.modelsJson?.value).toBe("marker(oauth:openai)"); }); it("keeps env-var-shaped models.json values masked to avoid accidental plaintext exposure", () => { diff --git a/src/commands/models/list.configured.test.ts b/src/commands/models/list.configured.test.ts index fa578e6e3c53..3ffa5246a77c 100644 --- a/src/commands/models/list.configured.test.ts +++ b/src/commands/models/list.configured.test.ts @@ -84,7 +84,7 @@ describe("resolveConfiguredEntries", () => { defaults: { model: "openai/gpt-5.5", models: { - "openai-codex/*": {}, + "openai/*": {}, "openai/gpt-5.5": { alias: "Primary" }, }, }, diff --git a/src/commands/models/list.list-command.forward-compat.test.ts b/src/commands/models/list.list-command.forward-compat.test.ts index 64e6371571ef..fae86ac26f63 100644 --- a/src/commands/models/list.list-command.forward-compat.test.ts +++ b/src/commands/models/list.list-command.forward-compat.test.ts @@ -1,10 +1,10 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const OPENAI_CODEX_MODEL = { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", input: ["text"], contextWindow: 1_050_000, @@ -60,7 +60,7 @@ const mocks = vi.hoisted(() => { }, }; const sourceConfig = { - agents: { defaults: { model: { primary: "openai-codex/gpt-5.4" } } }, + agents: { defaults: { model: { primary: "openai/gpt-5.4" } } }, models: { providers: { openai: { @@ -70,7 +70,7 @@ const mocks = vi.hoisted(() => { }, }; const resolvedConfig = { - agents: { defaults: { model: { primary: "openai-codex/gpt-5.4" } } }, + agents: { defaults: { model: { primary: "openai/gpt-5.4" } } }, models: { providers: { openai: { @@ -128,8 +128,8 @@ function resetMocks() { mocks.resolveConfiguredEntries.mockReturnValue({ entries: [ { - key: "openai-codex/gpt-5.4", - ref: { provider: "openai-codex", model: "gpt-5.4" }, + key: "openai/gpt-5.4", + ref: { provider: "openai", model: "gpt-5.4" }, tags: new Set(["configured"]), aliases: [], }, @@ -342,7 +342,7 @@ async function buildAllOpenAiCodexRows(opts: { supplementCatalog?: boolean } = { cfg: mocks.resolvedConfig, agentDir: "/tmp/openclaw-agent", authIndex: { - hasProviderAuth: (provider: string) => provider === "openai-codex", + hasProviderAuth: (provider: string) => provider === "openai", allowsProviderAuthAvailabilityFallback: () => false, }, availableKeys: loaded.availableKeys, @@ -352,7 +352,7 @@ async function buildAllOpenAiCodexRows(opts: { supplementCatalog?: boolean } = { (model: { provider: string; id: string }) => `${model.provider}/${model.id}`, ), ), - filter: { provider: "openai-codex" }, + filter: { provider: "openai" }, }; const seenKeys = await listRowsModule.appendDiscoveredRows({ rows: rows as never, @@ -700,7 +700,7 @@ describe("modelsListCommand forward-compat", () => { missing: boolean; }>(); - const codex = requireRow(rows, "openai-codex/gpt-5.4"); + const codex = requireRow(rows, "openai/gpt-5.4"); expect(codex.missing).toBe(false); expect(codex.tags).not.toContain("missing"); }); @@ -709,8 +709,8 @@ describe("modelsListCommand forward-compat", () => { mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [ { - key: "openai-codex/gpt-5.4-mini", - ref: { provider: "openai-codex", model: "gpt-5.4-mini" }, + key: "openai/gpt-5.4-mini", + ref: { provider: "openai", model: "gpt-5.4-mini" }, tags: new Set(["configured"]), aliases: [], }, @@ -727,7 +727,7 @@ describe("modelsListCommand forward-compat", () => { missing: boolean; }>(); - const codexMini = requireRow(rows, "openai-codex/gpt-5.4-mini"); + const codexMini = requireRow(rows, "openai/gpt-5.4-mini"); expect(codexMini.missing).toBe(false); expect(codexMini.tags).not.toContain("missing"); }); @@ -736,8 +736,8 @@ describe("modelsListCommand forward-compat", () => { mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [ { - key: "openai-codex/gpt-5.4-pro", - ref: { provider: "openai-codex", model: "gpt-5.4-pro" }, + key: "openai/gpt-5.4-pro", + ref: { provider: "openai", model: "gpt-5.4-pro" }, tags: new Set(["configured"]), aliases: [], }, @@ -754,7 +754,7 @@ describe("modelsListCommand forward-compat", () => { missing: boolean; }>(); - const codexPro = requireRow(rows, "openai-codex/gpt-5.4-pro"); + const codexPro = requireRow(rows, "openai/gpt-5.4-pro"); expect(codexPro.missing).toBe(false); expect(codexPro.tags).not.toContain("missing"); }); @@ -803,9 +803,9 @@ describe("modelsListCommand forward-compat", () => { mocks.ensureAuthProfileStore.mockReturnValueOnce({ version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "token", - provider: "openai-codex", + provider: "openai", token: "codex-app-server", }, }, @@ -816,13 +816,9 @@ describe("modelsListCommand forward-compat", () => { await modelsListCommand({ json: true }, runtime as never); expect(mocks.printModelTable).toHaveBeenCalled(); - expectRowFields( - lastPrintedRows<{ key: string; available: boolean }>(), - "openai-codex/gpt-5.4", - { - available: true, - }, - ); + expectRowFields(lastPrintedRows<{ key: string; available: boolean }>(), "openai/gpt-5.4", { + available: true, + }); }); it("does not require the all-model registry result for configured-mode listing", async () => { @@ -854,7 +850,7 @@ describe("modelsListCommand forward-compat", () => { provider: "codex", id: "gpt-5.4", name: "gpt-5.4", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", input: ["text", "image"], contextWindow: 272_000, @@ -1008,7 +1004,7 @@ describe("modelsListCommand forward-compat", () => { mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] }); mocks.loadModelRegistry.mockResolvedValueOnce({ models: [{ ...OPENAI_CODEX_MODEL }], - availableKeys: new Set(["openai-codex/gpt-5.4"]), + availableKeys: new Set(["openai/gpt-5.4"]), registry: { getAll: () => [{ ...OPENAI_CODEX_MODEL }], }, @@ -1039,10 +1035,7 @@ describe("modelsListCommand forward-compat", () => { expect(mocks.loadProviderCatalogModelsForList).not.toHaveBeenCalled(); expect(mocks.resolveModelWithRegistry).not.toHaveBeenCalled(); expect(mocks.loadModelCatalog).not.toHaveBeenCalled(); - expectRowKeys(lastPrintedRows<{ key: string }>(), [ - "openai-codex/gpt-5.4", - "moonshot/kimi-k2.6", - ]); + expectRowKeys(lastPrintedRows<{ key: string }>(), ["openai/gpt-5.4", "moonshot/kimi-k2.6"]); }); it("falls back to registry-backed rows when the fast-path catalog is empty", async () => { @@ -1051,27 +1044,24 @@ describe("modelsListCommand forward-compat", () => { mocks.loadProviderCatalogModelsForList.mockResolvedValueOnce([]).mockResolvedValueOnce([]); mocks.loadModelRegistry.mockResolvedValueOnce({ models: [{ ...OPENAI_CODEX_MODEL }], - availableKeys: new Set(["openai-codex/gpt-5.4"]), + availableKeys: new Set(["openai/gpt-5.4"]), registry: { getAll: () => [{ ...OPENAI_CODEX_MODEL }], }, }); const runtime = createRuntime(); - await modelsListCommand( - { all: true, provider: "openai-codex", json: true }, - runtime as never, - ); + await modelsListCommand({ all: true, provider: "openai", json: true }, runtime as never); expectFirstRegistryConfig(); - expect(modelRegistryOptions().providerFilter).toBe("openai-codex"); + expect(modelRegistryOptions().providerFilter).toBe("openai"); expect(modelRegistryOptions().normalizeModels).toBe(true); expect(mocks.loadProviderCatalogModelsForList).toHaveBeenNthCalledWith( 1, expect.objectContaining({ cfg: mocks.resolvedConfig, agentDir: "/tmp/openclaw-agent", - providerFilter: "openai-codex", + providerFilter: "openai", staticOnly: true, }), ); @@ -1080,13 +1070,13 @@ describe("modelsListCommand forward-compat", () => { expect.objectContaining({ cfg: mocks.resolvedConfig, agentDir: "/tmp/openclaw-agent", - providerFilter: "openai-codex", + providerFilter: "openai", staticOnly: undefined, }), ); const rows = lastPrintedRows<{ key: string; available: boolean }>(); - expectRowKeys(rows, ["openai-codex/gpt-5.4"]); - expectRowFields(rows, "openai-codex/gpt-5.4", { available: true }); + expectRowKeys(rows, ["openai/gpt-5.4"]); + expectRowFields(rows, "openai/gpt-5.4", { available: true }); }); it("falls back to registry rows for provider filters without catalog coverage", async () => { @@ -1180,14 +1170,14 @@ describe("modelsListCommand forward-compat", () => { mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] }); mocks.loadModelRegistry.mockResolvedValueOnce({ models: [], - availableKeys: new Set(["openai-codex/gpt-5.4"]), + availableKeys: new Set(["openai/gpt-5.4"]), registry: { getAll: () => [], }, }); mocks.loadModelCatalog.mockResolvedValueOnce([ { - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", name: "GPT-5.3 Codex", input: ["text"], @@ -1196,7 +1186,7 @@ describe("modelsListCommand forward-compat", () => { ]); mocks.resolveModelWithRegistry.mockImplementation( ({ provider, modelId }: { provider: string; modelId: string }) => { - if (provider !== "openai-codex") { + if (provider !== "openai") { return undefined; } if (modelId === "gpt-5.4") { @@ -1207,13 +1197,11 @@ describe("modelsListCommand forward-compat", () => { ); mocks.resolveModelWithRegistry.mockImplementationOnce( ({ provider, modelId }: { provider: string; modelId: string }) => - provider === "openai-codex" && modelId === "gpt-5.4" - ? { ...OPENAI_CODEX_53_MODEL } - : undefined, + provider === "openai" && modelId === "gpt-5.4" ? { ...OPENAI_CODEX_53_MODEL } : undefined, ); const rows = await buildAllOpenAiCodexRows(); - expectRowKeys(rows as Array<{ key: string }>, ["openai-codex/gpt-5.4"]); - expectRowFields(rows as Array<{ key: string; available: boolean }>, "openai-codex/gpt-5.4", { + expectRowKeys(rows as Array<{ key: string }>, ["openai/gpt-5.4"]); + expectRowFields(rows as Array<{ key: string; available: boolean }>, "openai/gpt-5.4", { available: true, }); }); @@ -1224,10 +1212,10 @@ describe("modelsListCommand forward-compat", () => { mocks.loadModelRegistry.mockResolvedValueOnce({ models: [ { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", name: "GPT-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", input: ["text", "image"], contextWindow: 272000, @@ -1235,14 +1223,14 @@ describe("modelsListCommand forward-compat", () => { cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, }, ], - availableKeys: new Set(["openai-codex/gpt-5.5"]), + availableKeys: new Set(["openai/gpt-5.5"]), registry: { getAll: () => [ { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", name: "GPT-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", input: ["text", "image"], contextWindow: 272000, @@ -1254,12 +1242,12 @@ describe("modelsListCommand forward-compat", () => { }); mocks.resolveModelWithRegistry.mockImplementation( ({ provider, modelId }: { provider: string; modelId: string }) => - provider === "openai-codex" && modelId === "gpt-5.5" + provider === "openai" && modelId === "gpt-5.5" ? { - provider: "openai-codex", + provider: "openai", id: "gpt-5.5", name: "GPT-5.5", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", input: ["text", "image"], contextWindow: 400000, @@ -1271,18 +1259,15 @@ describe("modelsListCommand forward-compat", () => { ); const runtime = createRuntime(); - await modelsListCommand( - { all: true, provider: "openai-codex", json: true }, - runtime as never, - ); + await modelsListCommand({ all: true, provider: "openai", json: true }, runtime as never); const rows = lastPrintedRows<{ key: string; contextWindow: number; contextTokens?: number; }>(); - expectRowKeys(rows, ["openai-codex/gpt-5.5"]); - expectRowFields(rows, "openai-codex/gpt-5.5", { + expectRowKeys(rows, ["openai/gpt-5.5"]); + expectRowFields(rows, "openai/gpt-5.5", { contextWindow: 400000, contextTokens: 272000, }); @@ -1324,14 +1309,14 @@ describe("modelsListCommand forward-compat", () => { hasProviderAuth: () => false, allowsProviderAuthAvailabilityFallback: () => false, }, - availableKeys: new Set(["openai-codex/gpt-5.4"]), + availableKeys: new Set(["openai/gpt-5.4"]), configuredByKey: new Map(), discoveredKeys: new Set(), filter: {}, } as never, }); - expectRowKeys(rows as Array<{ key: string }>, ["openai-codex/gpt-5.4"]); + expectRowKeys(rows as Array<{ key: string }>, ["openai/gpt-5.4"]); }); }); diff --git a/src/commands/models/list.probe.test.ts b/src/commands/models/list.probe.test.ts index 710ee088100e..72afa4d4f05f 100644 --- a/src/commands/models/list.probe.test.ts +++ b/src/commands/models/list.probe.test.ts @@ -48,9 +48,9 @@ describe("runAuthProbes", () => { ensureAuthProfileStore: () => ({ version: 1, profiles: { - "openai-codex:profile": { + "openai:profile": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -58,17 +58,17 @@ describe("runAuthProbes", () => { }, order: {}, }), - listProfilesForProvider: () => ["openai-codex:profile"], + listProfilesForProvider: () => ["openai:profile"], resolveAuthProfileDisplayLabel: ({ profileId }: { profileId: string }) => profileId, resolveAuthProfileEligibility: () => ({ eligible: true }), - resolveAuthProfileOrder: () => ["openai-codex:profile"], + resolveAuthProfileOrder: () => ["openai:profile"], })); vi.doMock("../../agents/model-auth.js", () => ({ hasUsableCustomProviderApiKey: () => false, resolveEnvApiKey: () => null, })); vi.doMock("../../agents/model-catalog.js", () => ({ - loadModelCatalog: async () => [{ provider: "openai-codex", id: "gpt-5.5" }], + loadModelCatalog: async () => [{ provider: "openai", id: "gpt-5.5" }], })); try { const module = await importFreshModule( @@ -80,11 +80,11 @@ describe("runAuthProbes", () => { agentId: "probe-agent", agentDir: "/tmp/openclaw-probe-agent", workspaceDir: "/tmp/openclaw-probe-workspace", - providers: ["openai-codex"], - modelCandidates: ["openai-codex/gpt-5.5"], + providers: ["openai"], + modelCandidates: ["openai/gpt-5.5"], options: { - provider: "openai-codex", - profileIds: ["openai-codex:profile"], + provider: "openai", + profileIds: ["openai:profile"], timeoutMs: 5_000, concurrency: 1, maxTokens: 8, @@ -96,7 +96,7 @@ describe("runAuthProbes", () => { expect.objectContaining({ modelRun: true, disableTools: true, - authProfileId: "openai-codex:profile", + authProfileId: "openai:profile", authProfileIdSource: "user", }), ); diff --git a/src/commands/models/list.rows.test.ts b/src/commands/models/list.rows.test.ts index a4d74f290342..7e0f213026f1 100644 --- a/src/commands/models/list.rows.test.ts +++ b/src/commands/models/list.rows.test.ts @@ -46,7 +46,7 @@ describe("appendProviderCatalogRows", () => { id: "gpt-5.5", name: "gpt-5.5", provider: "codex", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", input: ["text"], reasoning: false, diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts index b91731f51641..2de11ebb696b 100644 --- a/src/commands/models/list.status-command.ts +++ b/src/commands/models/list.status-command.ts @@ -34,7 +34,7 @@ import { OPENAI_CODEX_PROVIDER_ID, OPENAI_PROVIDER_ID, openAIProviderUsesCodexRuntimeByDefault, -} from "../../agents/openai-codex-routing.js"; +} from "../../agents/openai-routing.js"; import { resolveProviderIdForAuth } from "../../agents/provider-auth-aliases.js"; import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js"; import { createConfigIO } from "../../config/config.js"; diff --git a/src/commands/models/list.status.test.ts b/src/commands/models/list.status.test.ts index 5433ec2506ea..aabcd4baf7b7 100644 --- a/src/commands/models/list.status.test.ts +++ b/src/commands/models/list.status.test.ts @@ -18,14 +18,14 @@ const mocks = vi.hoisted(() => { provider: "anthropic", key: "sk-ant-api-0123456789abcdefghijklmnopqrstuvwxyz", // pragma: allowlist secret }, - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "eyJhbGciOi-ACCESS", refresh: "oai-refresh-1234567890", expires: Date.now() + 60_000, }, - "openai:default": { + "openai:api-key": { type: "api_key", provider: "openai", key: "abc123", // pragma: allowlist secret @@ -87,20 +87,18 @@ const mocks = vi.hoisted(() => { google: ["GEMINI_API_KEY", "GOOGLE_API_KEY"], minimax: ["MINIMAX_API_KEY"], "minimax-portal": ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"], - openai: ["OPENAI_API_KEY"], - "openai-codex": ["OPENAI_OAUTH_TOKEN"], + openai: ["OPENAI_OAUTH_TOKEN", "OPENAI_API_KEY"], fal: ["FAL_KEY"], }), resolveProviderEnvAuthEvidence: vi.fn().mockReturnValue({}), resolveProviderEnvAuthLookupMaps: vi.fn().mockReturnValue({ - aliasMap: { "codex-cli": "openai-codex" }, + aliasMap: { "codex-cli": "openai" }, envCandidateMap: { anthropic: ["ANTHROPIC_API_KEY"], google: ["GEMINI_API_KEY", "GOOGLE_API_KEY"], minimax: ["MINIMAX_API_KEY"], "minimax-portal": ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"], - openai: ["OPENAI_API_KEY"], - "openai-codex": ["OPENAI_OAUTH_TOKEN"], + openai: ["OPENAI_OAUTH_TOKEN", "OPENAI_API_KEY"], fal: ["FAL_KEY"], }, authEvidenceMap: {}, @@ -113,7 +111,7 @@ const mocks = vi.hoisted(() => { "minimax", "minimax-portal", "openai", - "openai-codex", + "openai", "fal", ]), listKnownProviderEnvApiKeyNames: vi @@ -223,9 +221,9 @@ vi.mock("../../agents/model-auth-env-vars.js", () => ({ listKnownProviderEnvApiKeyNames: mocks.listKnownProviderEnvApiKeyNames, })); vi.mock("../../agents/provider-auth-aliases.js", () => ({ - resolveProviderAuthAliasMap: vi.fn(() => ({ "codex-cli": "openai-codex" })), + resolveProviderAuthAliasMap: vi.fn(() => ({ "codex-cli": "openai" })), resolveProviderIdForAuth: vi.fn((provider: string) => - provider === "codex-cli" ? "openai-codex" : provider, + provider === "codex-cli" ? "openai" : provider, ), })); vi.mock("../../agents/model-selection-cli.js", () => ({ @@ -417,11 +415,7 @@ describe("modelsStatusCommand auth overview", () => { expect(openai?.env?.value).toContain("..."); expect(openai?.profiles.labels.join(" ")).toContain("..."); expect(openai?.profiles.labels.join(" ")).not.toContain("abc123"); - expect( - (payload.auth.providersWithOAuth as string[]).some((provider) => - provider.startsWith("openai "), - ), - ).toBe(false); + expect(payload.auth.providersWithOAuth).toContain("openai (1)"); expect( requireRecord(requireProvider(providers, "minimax").effective, "minimax effective").kind, ).toBe("env"); @@ -432,9 +426,9 @@ describe("modelsStatusCommand auth overview", () => { expect( (payload.auth.providersWithOAuth as string[]).some((e) => e.startsWith("anthropic")), ).toBe(true); - expect( - (payload.auth.providersWithOAuth as string[]).some((e) => e.startsWith("openai-codex")), - ).toBe(true); + expect((payload.auth.providersWithOAuth as string[]).some((e) => e.startsWith("openai"))).toBe( + true, + ); }); it("honors OPENCLAW_AGENT_DIR when no --agent override is provided", async () => { @@ -512,7 +506,7 @@ describe("modelsStatusCommand auth overview", () => { provider: string; effective?: { kind: string; detail?: string }; }> - ).find((provider) => provider.provider === "openai-codex"); + ).find((provider) => provider.provider === "openai"); expect(openAiCodex?.effective).toEqual({ kind: "profiles", detail: "/tmp/openclaw-agent-custom/auth-profiles.json", @@ -537,10 +531,10 @@ describe("modelsStatusCommand auth overview", () => { env: { shellEnv: { enabled: true } }, }); mocks.store.profiles = { - "openai-codex:default": originalProfiles["openai-codex:default"], + "openai:default": originalProfiles["openai:default"], }; mocks.resolveEnvApiKey.mockImplementation((provider: string) => - provider === "openai-codex" + provider === "openai" ? { apiKey: "oauth-token", source: "env: OPENAI_OAUTH_TOKEN", @@ -586,7 +580,7 @@ describe("modelsStatusCommand auth overview", () => { models: { providers: { openai: { - apiKey: "oauth:openai-codex", + apiKey: "oauth:openai", }, }, }, @@ -595,7 +589,7 @@ describe("modelsStatusCommand auth overview", () => { mocks.store.profiles = {}; mocks.resolveEnvApiKey.mockImplementation(() => null); mocks.getCustomProviderApiKey.mockImplementation((_cfg: unknown, provider: string) => - provider === "openai" ? "oauth:openai-codex" : undefined, + provider === "openai" ? "oauth:openai" : undefined, ); mocks.resolveUsableCustomProviderApiKey.mockImplementation(() => null); @@ -605,7 +599,7 @@ describe("modelsStatusCommand auth overview", () => { const openai = requireProvider(payload.auth.providers, "openai"); expect(openai.effective).toEqual({ kind: "models.json", - detail: "marker(oauth:openai-codex)", + detail: "marker(oauth:openai)", }); expect(payload.auth.runtimeAuthRoutes).toEqual([ { @@ -615,7 +609,7 @@ describe("modelsStatusCommand auth overview", () => { status: "missing", effective: { kind: "models.json", - detail: "marker(oauth:openai-codex)", + detail: "marker(oauth:openai)", }, }, ]); @@ -797,14 +791,14 @@ describe("modelsStatusCommand auth overview", () => { const originalHealthImpl = buildAuthHealthSummaryMock.getMockImplementation(); const expiredProfile = { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "expired-access", refresh: "expired-refresh", expires: Date.now() - 60_000, }; const usableProfile = { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "usable-access", refresh: "usable-refresh", expires: Date.now() + 60_000, @@ -820,11 +814,11 @@ describe("modelsStatusCommand auth overview", () => { env: { shellEnv: { enabled: true } }, }); mocks.store.profiles = { - "openai-codex:default": expiredProfile, - "openai-codex:named": usableProfile, + "openai:default": expiredProfile, + "openai:named": usableProfile, }; mocks.store.order = { - "openai-codex": ["openai-codex:named"], + openai: ["openai:named"], }; mocks.resolveEnvApiKey.mockImplementation(() => null); buildAuthHealthSummaryMock.mockReturnValue({ @@ -832,27 +826,27 @@ describe("modelsStatusCommand auth overview", () => { warnAfterMs: 86_400_000, profiles: [ { - profileId: "openai-codex:default", - provider: "openai-codex", + profileId: "openai:default", + provider: "openai", type: "oauth", status: "expired", source: "store", - label: "openai-codex:default", + label: "openai:default", }, { - profileId: "openai-codex:named", - provider: "openai-codex", + profileId: "openai:named", + provider: "openai", type: "oauth", status: "ok", expiresAt: Date.now() + 60_000, remainingMs: 60_000, source: "store", - label: "openai-codex:named", + label: "openai:named", }, ], providers: [ { - provider: "openai-codex", + provider: "openai", status: "ok", expiresAt: Date.now() + 60_000, remainingMs: 60_000, @@ -865,11 +859,9 @@ describe("modelsStatusCommand auth overview", () => { await modelsStatusCommand({ json: true, check: true }, localRuntime as never); const payload = parseFirstJsonLog(localRuntime); expect(payload.auth.missingProvidersInUse).toEqual([]); - expect(requireProfile(payload.auth.oauth.profiles, "openai-codex:default").status).toBe( - "expired", - ); - expect(requireProfile(payload.auth.oauth.profiles, "openai-codex:named").status).toBe("ok"); - expect(requireProvider(payload.auth.oauth.providers, "openai-codex").status).toBe("ok"); + expect(requireProfile(payload.auth.oauth.profiles, "openai:default").status).toBe("expired"); + expect(requireProfile(payload.auth.oauth.profiles, "openai:named").status).toBe("ok"); + expect(requireProvider(payload.auth.oauth.providers, "openai").status).toBe("ok"); expect(localRuntime.exit).not.toHaveBeenCalledWith(1); } finally { mocks.store.profiles = originalProfiles; @@ -908,9 +900,9 @@ describe("modelsStatusCommand auth overview", () => { env: { shellEnv: { enabled: true } }, }); mocks.store.profiles = { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "expired-access", refresh: "expired-refresh", expires: Date.now() - 60_000, @@ -922,17 +914,17 @@ describe("modelsStatusCommand auth overview", () => { warnAfterMs: 86_400_000, profiles: [ { - profileId: "openai-codex:default", - provider: "openai-codex", + profileId: "openai:default", + provider: "openai", type: "oauth", status: "expired", source: "store", - label: "openai-codex:default", + label: "openai:default", }, ], providers: [ { - provider: "openai-codex", + provider: "openai", status: "expired", expiresAt: Date.now() - 60_000, remainingMs: -60_000, @@ -1237,7 +1229,7 @@ describe("modelsStatusCommand auth overview", () => { } }); - it("keeps Codex auth fallback scoped away from OpenAI image routes", async () => { + it("uses unified OpenAI auth for OpenAI image routes", async () => { const localRuntime = createRuntime(); const originalLoadConfig = mocks.loadConfig.getMockImplementation(); const originalProfiles = { ...mocks.store.profiles }; @@ -1255,10 +1247,10 @@ describe("modelsStatusCommand auth overview", () => { }); mocks.store.profiles = { "anthropic:default": originalProfiles["anthropic:default"], - "openai-codex:default": originalProfiles["openai-codex:default"], + "openai:default": originalProfiles["openai:default"], }; mocks.resolveEnvApiKey.mockImplementation((provider: string) => - provider === "openai-codex" + provider === "openai" ? { apiKey: "oauth-token", source: "env: OPENAI_OAUTH_TOKEN", @@ -1269,8 +1261,8 @@ describe("modelsStatusCommand auth overview", () => { try { await modelsStatusCommand({ json: true, check: true }, localRuntime as never); const payload = parseFirstJsonLog(localRuntime); - expect(payload.auth.missingProvidersInUse).toEqual(["openai"]); - expect(localRuntime.exit).toHaveBeenCalledWith(1); + expect(payload.auth.missingProvidersInUse).toEqual([]); + expect(localRuntime.exit).toHaveBeenCalledWith(0); } finally { mocks.store.profiles = originalProfiles; if (originalLoadConfig) { diff --git a/src/commands/models/list.table.test.ts b/src/commands/models/list.table.test.ts index 7d5a38f2ddd3..711126b39a7e 100644 --- a/src/commands/models/list.table.test.ts +++ b/src/commands/models/list.table.test.ts @@ -7,7 +7,7 @@ describe("printModelTable", () => { const runtime = { log: vi.fn(), error: vi.fn() }; const rows: ModelRow[] = [ { - key: "openai-codex/gpt-5.5", + key: "openai/gpt-5.5", name: "GPT-5.5", input: "text+image", contextWindow: 400_000, @@ -23,7 +23,7 @@ describe("printModelTable", () => { expect(runtime.log.mock.calls).toEqual([ ["Model Input Ctx Local Auth Tags"], - ["openai-codex/gpt-5.5 text+image 266k/391k no yes "], + ["openai/gpt-5.5 text+image 266k/391k no yes "], ]); }); }); diff --git a/src/commands/oauth-tls-preflight.doctor.test.ts b/src/commands/oauth-tls-preflight.doctor.test.ts index ca9c50b6ad99..01b014e26e52 100644 --- a/src/commands/oauth-tls-preflight.doctor.test.ts +++ b/src/commands/oauth-tls-preflight.doctor.test.ts @@ -13,8 +13,8 @@ function buildOpenAICodexOAuthConfig(): OpenClawConfig { return { auth: { profiles: { - "openai-codex:user@example.com": { - provider: "openai-codex", + "openai:user@example.com": { + provider: "openai", mode: "oauth", email: "user@example.com", }, diff --git a/src/commands/oauth-tls-preflight.test.ts b/src/commands/oauth-tls-preflight.test.ts index afcbe8806344..01977f06c9e3 100644 --- a/src/commands/oauth-tls-preflight.test.ts +++ b/src/commands/oauth-tls-preflight.test.ts @@ -3,6 +3,7 @@ import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; import { formatOpenAIOAuthTlsPreflightFix, runOpenAIOAuthTlsPreflight, + shouldRunOpenAIOAuthTlsPrerequisites, } from "./oauth-tls-preflight.js"; describe("runOpenAIOAuthTlsPreflight", () => { @@ -94,3 +95,24 @@ describe("formatOpenAIOAuthTlsPreflightFix", () => { expect(text).toContain("- Retry the OAuth login flow."); }); }); + +describe("shouldRunOpenAIOAuthTlsPrerequisites", () => { + it("runs for pre-doctor legacy OpenAI OAuth profiles", () => { + const legacyOpenAIProvider = ["openai", "codex"].join("-"); + + expect( + shouldRunOpenAIOAuthTlsPrerequisites({ + cfg: { + auth: { + profiles: { + [`${legacyOpenAIProvider}:default`]: { + provider: legacyOpenAIProvider, + mode: "oauth", + }, + }, + }, + }, + }), + ).toBe(true); + }); +}); diff --git a/src/commands/oauth-tls-preflight.ts b/src/commands/oauth-tls-preflight.ts index 6852c58ad5cc..ab07319accd8 100644 --- a/src/commands/oauth-tls-preflight.ts +++ b/src/commands/oauth-tls-preflight.ts @@ -1 +1 @@ -export * from "../plugins/provider-openai-codex-oauth-tls.js"; +export * from "../plugins/provider-openai-chatgpt-oauth-tls.js"; diff --git a/src/commands/onboard-auth.test.ts b/src/commands/onboard-auth.test.ts index 946d71f3b71f..0b0fd1dc6957 100644 --- a/src/commands/onboard-auth.test.ts +++ b/src/commands/onboard-auth.test.ts @@ -145,12 +145,12 @@ describe("writeOAuthCredentials", () => { expires: Date.now() + 60_000, } satisfies OAuthCredentials; - await writeOAuthCredentials("openai-codex", creds); + await writeOAuthCredentials("openai", creds); const parsed = await readAuthProfilesForAgent<{ profiles?: Record; }>(defaultAgentDir); - expectFields(parsed.profiles?.["openai-codex:default"], { + expectFields(parsed.profiles?.["openai:default"], { refresh: "refresh-token", access: "access-token", type: "oauth", @@ -178,7 +178,7 @@ describe("writeOAuthCredentials", () => { expires: Date.now() + 60_000, } satisfies OAuthCredentials; - await writeOAuthCredentials("openai-codex", creds, undefined, { + await writeOAuthCredentials("openai", creds, undefined, { syncSiblingAgents: true, }); @@ -187,7 +187,7 @@ describe("writeOAuthCredentials", () => { const parsed = JSON.parse(raw) as { profiles?: Record; }; - expectFields(parsed.profiles?.["openai-codex:default"], { + expectFields(parsed.profiles?.["openai:default"], { refresh: "refresh-sync", access: "access-sync", type: "oauth", @@ -212,13 +212,13 @@ describe("writeOAuthCredentials", () => { expires: Date.now() + 60_000, } satisfies OAuthCredentials; - await writeOAuthCredentials("openai-codex", creds, kidAgentDir); + await writeOAuthCredentials("openai", creds, kidAgentDir); const kidRaw = await fs.readFile(authProfilePathFor(kidAgentDir), "utf8"); const kidParsed = JSON.parse(kidRaw) as { profiles?: Record; }; - expectFields(kidParsed.profiles?.["openai-codex:default"], { + expectFields(kidParsed.profiles?.["openai:default"], { access: "access-kid", type: "oauth", }); @@ -245,7 +245,7 @@ describe("writeOAuthCredentials", () => { expires: Date.now() + 60_000, } satisfies OAuthCredentials; - await writeOAuthCredentials("openai-codex", creds, extKid, { + await writeOAuthCredentials("openai", creds, extKid, { syncSiblingAgents: true, }); @@ -255,7 +255,7 @@ describe("writeOAuthCredentials", () => { const parsed = JSON.parse(raw) as { profiles?: Record; }; - expectFields(parsed.profiles?.["openai-codex:default"], { + expectFields(parsed.profiles?.["openai:default"], { refresh: "refresh-ext", access: "access-ext", type: "oauth", @@ -551,15 +551,15 @@ describe("applyAuthProfileConfig", () => { const next = applyAuthProfileConfig( {}, { - profileId: "openai-codex:id-abc", - provider: "openai-codex", + profileId: "openai:id-abc", + provider: "openai", mode: "oauth", displayName: "Work account", }, ); - expect(next.auth?.profiles?.["openai-codex:id-abc"]).toEqual({ - provider: "openai-codex", + expect(next.auth?.profiles?.["openai:id-abc"]).toEqual({ + provider: "openai", mode: "oauth", displayName: "Work account", }); diff --git a/src/commands/onboard-non-interactive/local/auth-choice.test.ts b/src/commands/onboard-non-interactive/local/auth-choice.test.ts index 248274775fff..366d7c8f7394 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.test.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.test.ts @@ -81,37 +81,6 @@ describe("applyNonInteractiveAuthChoice", () => { expect(applyNonInteractivePluginProviderChoice).toHaveBeenCalledOnce(); }); - it("normalizes legacy OpenAI Codex setup choice before provider auth", async () => { - const runtime = createRuntime(); - const nextConfig = { agents: { defaults: {} } } as OpenClawConfig; - const resolvedConfig = { auth: { profiles: { "openai:default": { mode: "oauth" } } } }; - resolveManifestDeprecatedProviderAuthChoice.mockImplementation(((choiceId: string) => - choiceId === "openai-codex" - ? { - choiceId: "openai", - choiceLabel: "ChatGPT Login", - } - : undefined) as never); - applyNonInteractivePluginProviderChoice.mockResolvedValueOnce(resolvedConfig as never); - - const result = await applyNonInteractiveAuthChoice({ - nextConfig, - authChoice: "openai-codex", - opts: {} as never, - runtime: runtime as never, - baseConfig: nextConfig, - }); - - expect(result).toBe(resolvedConfig); - expect(runtime.log).toHaveBeenCalledWith( - 'Auth choice "openai-codex" is deprecated; using ChatGPT Login setup instead.', - ); - expect(runtime.error).not.toHaveBeenCalled(); - expect(applyNonInteractivePluginProviderChoice).toHaveBeenCalledWith( - expect.objectContaining({ authChoice: "openai" }), - ); - }); - it("escapes deprecated auth choice guidance for terminal output", async () => { const runtime = createRuntime(); const nextConfig = { agents: { defaults: {} } } as OpenClawConfig; diff --git a/src/commands/sessions.model-resolution.test.ts b/src/commands/sessions.model-resolution.test.ts index bbbab40ea227..9c597bbe979d 100644 --- a/src/commands/sessions.model-resolution.test.ts +++ b/src/commands/sessions.model-resolution.test.ts @@ -53,7 +53,7 @@ describe("sessionsCommand model resolution", () => { it("prefers the persisted override model for subagent sessions in JSON output", async () => { const model = await resolveSubagentModel( { - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.4", modelOverride: "test:opus", }, @@ -63,10 +63,7 @@ describe("sessionsCommand model resolution", () => { }); it("falls back to modelOverride when runtime model is missing", async () => { - const model = await resolveSubagentModel( - { modelOverride: "openai-codex/gpt-5.4" }, - "subagent-2", - ); + const model = await resolveSubagentModel({ modelOverride: "openai/gpt-5.4" }, "subagent-2"); expect(model).toBe("gpt-5.4"); }); diff --git a/src/commands/status-all/gateway.test.ts b/src/commands/status-all/gateway.test.ts index 5b24c0547892..1953312e01a7 100644 --- a/src/commands/status-all/gateway.test.ts +++ b/src/commands/status-all/gateway.test.ts @@ -4,11 +4,11 @@ import { summarizeLogTail } from "./gateway.js"; describe("summarizeLogTail", () => { it("marks permanent OAuth refresh failures as reauth-required", () => { const lines = summarizeLogTail([ - "[openai-codex] Token refresh failed: 401 {", + "[openai] Token refresh failed: 401 {", '"error":{"code":"invalid_grant","message":"Session invalidated due to signing in again"}', "}", ]); - expect(lines).toEqual(["[openai-codex] token refresh 401 invalid_grant · re-auth required"]); + expect(lines).toEqual(["[openai] token refresh 401 invalid_grant · re-auth required"]); }); }); diff --git a/src/commands/status-all/gateway.ts b/src/commands/status-all/gateway.ts index a1d1b60ebbd8..7df4ad3ddd25 100644 --- a/src/commands/status-all/gateway.ts +++ b/src/commands/status-all/gateway.ts @@ -99,7 +99,7 @@ export function summarizeLogTail(rawLines: string[], opts?: { maxLines?: number continue; } - // "[openai-codex] Token refresh failed: 401 { ...json... }" + // "[openai] Token refresh failed: 401 { ...json... }" const tokenRefresh = line.match(/^\[([^\]]+)\]\s+Token refresh failed:\s*(\d+)\s*(\{)?\s*$/); if (tokenRefresh) { const tag = tokenRefresh[1] ?? "unknown"; @@ -126,7 +126,7 @@ export function summarizeLogTail(rawLines: string[], opts?: { maxLines?: number } } - // "Embedded agent failed before reply: OAuth token refresh failed for openai-codex: ..." + // "Embedded agent failed before reply: OAuth token refresh failed for openai: ..." const embedded = line.match( /^Embedded agent failed before reply:\s+OAuth token refresh failed for ([^:]+):/, ); diff --git a/src/commands/status.summary.runtime.test.ts b/src/commands/status.summary.runtime.test.ts index 6fc9f4e90699..b6b51a15fd9a 100644 --- a/src/commands/status.summary.runtime.test.ts +++ b/src/commands/status.summary.runtime.test.ts @@ -26,13 +26,13 @@ describe("statusSummaryRuntime.resolveContextTokensForModel", () => { cfg: { models: { providers: { - "openai-codex": { + openai: { models: [{ id: "gpt-5.4", contextWindow: 1_050_000, contextTokens: 272_000 }], }, }, }, } as never, - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", fallbackContextTokens: 999, }); @@ -177,13 +177,13 @@ describe("statusSummaryRuntime.resolveSessionModelRef", () => { it("prefers explicit overrides ahead of fallback runtime fields", () => { expect( statusSummaryRuntime.resolveSessionModelRef(cfg, { - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.4", modelProvider: "amazon-bedrock", model: "minimax.minimax-m2.5", }), ).toEqual({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", }); }); diff --git a/src/commands/status.summary.test.ts b/src/commands/status.summary.test.ts index 41fc1b122bd9..8d648b0b9261 100644 --- a/src/commands/status.summary.test.ts +++ b/src/commands/status.summary.test.ts @@ -438,14 +438,14 @@ describe("getStatusSummary", () => { model: "gpt-5.5-codex", }); vi.mocked(statusSummaryRuntime.resolveSessionModelRef).mockReturnValue({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.5-codex", }); statusSummaryMocks.readSessionStoreReadOnly.mockReturnValue({ "agent:main:main": { sessionId: "session-1", updatedAt: Date.now(), - providerOverride: "openai-codex", + providerOverride: "openai", modelOverride: "gpt-5.5-codex", modelOverrideSource: "user", }, @@ -454,7 +454,7 @@ describe("getStatusSummary", () => { const summary = await getStatusSummary(); expect(summary.sessions.recent[0]?.configuredModel).toBe("openai/gpt-5.5-codex"); - expect(summary.sessions.recent[0]?.selectedModel).toBe("openai-codex/gpt-5.5-codex"); + expect(summary.sessions.recent[0]?.selectedModel).toBe("openai/gpt-5.5-codex"); expect(summary.sessions.recent[0]?.modelSelectionReason).toBeNull(); }); }); diff --git a/src/commitments/runtime.test.ts b/src/commitments/runtime.test.ts index e970e0b20f97..2e853779f14d 100644 --- a/src/commitments/runtime.test.ts +++ b/src/commitments/runtime.test.ts @@ -188,7 +188,7 @@ describe("commitment extraction runtime", () => { cfg.agents = { defaults: { model: { - primary: "openai-codex/gpt-5.5", + primary: "openai/gpt-5.5", }, }, }; @@ -196,7 +196,7 @@ describe("commitment extraction runtime", () => { payloads: [{ text: '{"candidates":[]}' }], }); resolveDefaultModelMock.mockReturnValue({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", }); configureCommitmentExtractionRuntime({ @@ -221,7 +221,7 @@ describe("commitment extraction runtime", () => { expect(resolveDefaultModelMock).toHaveBeenCalledWith({ cfg, agentId: "main" }); expect(runEmbeddedAgentMock).toHaveBeenCalledTimes(1); const request = requireFirstEmbeddedAgentRequest(); - expect(request.provider).toBe("openai-codex"); + expect(request.provider).toBe("openai"); expect(request.model).toBe("gpt-5.5"); expect(request.disableTools).toBe(true); }); diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index 7abf81f05ac9..9f5128a298c5 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -18,7 +18,7 @@ const RAW_BUNDLED_CHANNEL_CONFIG_METADATA = [ '[{"pluginId":"clickclack","channelId":"clickclack","order":85,"channelEnvVars":["CLICKCLACK_BOT_TOKEN"],"label":"ClickClack","description":"self-hosted chat via first-class ClickClack bot tokens.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"timeoutSeconds":{"type":"integer","minimum":1,"maximum":3600},"toolsAllow":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"baseUrl":{"type":"string","format":"uri"},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"workspace":{"type":"string"},"botUserId":{"type":"string"},"agentId":{"type":"string"},"replyMode":{"type":"string","enum":["agent","model"]},"model":{"type":"string"},"systemPrompt":{"type":"string"},"timeoutSeconds":{"type":"integer","minimum":1,"maximum":3600},"toolsAllow":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"allowFrom":{"type":"array","items":{"type":"string"}},"reconnectMs":{"type":"integer","minimum":100,"maximum":60000}},"additionalProperties":false}},"defaultAccount":{"type":"string"}},"additionalProperties":false}},{"pluginId":"discord","channelId":"discord","channelEnvVars":["DISCORD_BOT_TOKEN"],"label":"Discord","description":"very well supported right now.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"applicationId":{"type":"string"},"proxy":{"type":"string"},"gatewayInfoTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayRuntimeReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"mentionAliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","pattern":"^\\\\d+$"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"suppressEmbeds":{"type":"boolean"},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"maxLinesPerMessage":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"stickers":{"type":"boolean"},"emojiUploads":{"type":"boolean"},"stickerUploads":{"type":"boolean"},"polls":{"type":"boolean"},"permissions":{"type":"boolean"},"messages":{"type":"boolean"},"threads":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"memberInfo":{"type":"boolean"},"roleInfo":{"type":"boolean"},"roles":{"type":"boolean"},"channelInfo":{"type":"boolean"},"voiceStatus":{"type":"boolean"},"events":{"type":"boolean"},"moderation":{"type":"boolean"},"channels":{"type":"boolean"},"presence":{"type":"boolean"}},"additionalProperties":false},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"thread":{"type":"object","properties":{"inheritParent":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"guilds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"slug":{"type":"string"},"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"},"includeThreadStarter":{"type":"boolean"},"autoThread":{"type":"boolean"},"autoThreadName":{"type":"string","enum":["message","generated"]},"autoArchiveDuration":{"anyOf":[{"type":"string","enum":["60","1440","4320","10080"]},{"type":"number","const":60},{"type":"number","const":1440},{"type":"number","const":4320},{"type":"number","const":10080}]}},"additionalProperties":false}}},"additionalProperties":false}},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"type":"string"}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"cleanupAfterResolve":{"type":"boolean"},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"agentComponents":{"type":"object","properties":{"enabled":{"type":"boolean"},"ttlMs":{"type":"integer","exclusiveMinimum":0,"maximum":86400000}},"additionalProperties":false},"ui":{"type":"object","properties":{"components":{"type":"object","properties":{"accentColor":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$"}},"additionalProperties":false}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"ephemeral":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"intents":{"type":"object","properties":{"presence":{"type":"boolean"},"guildMembers":{"type":"boolean"},"voiceStates":{"type":"boolean"}},"additionalProperties":false},"voice":{"type":"object","properties":{"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["stt-tts","agent-proxy","bidi"]},"agentSession":{"type":"object","properties":{"mode":{"type":"string","enum":["voice","target"]},"target":{"type":"string","minLength":1}},"additionalProperties":false},"model":{"type":"string","minLength":1},"realtime":{"type":"object","properties":{"provider":{"type":"string","minLength":1},"model":{"type":"string","minLength":1},"speakerVoice":{"type":"string","minLength":1},"speakerVoiceId":{"type":"string","minLength":1},"voice":{"type":"string","minLength":1},"instructions":{"type":"string","minLength":1},"toolPolicy":{"type":"string","enum":["safe-read-only","owner","none"]},"consultPolicy":{"type":"string","enum":["auto","always"]},"requireWakeName":{"type":"boolean"},"wakeNames":{"minItems":1,"type":"array","items":{"type":"string","minLength":1,"pattern":"^\\\\s*[^a-z0-9]*[a-z0-9]+(?:[^a-z0-9]+[a-z0-9]+)?[^a-z0-9]*\\\\s*$"}},"bootstrapContextFiles":{"type":"array","items":{"type":"string","enum":["IDENTITY.md","USER.md","SOUL.md"]}},"bargeIn":{"type":"boolean"},"minBargeInAudioEndMs":{"type":"integer","minimum":0,"maximum":10000},"debounceMs":{"type":"integer","exclusiveMinimum":0,"maximum":10000},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"additionalProperties":false},"autoJoin":{"type":"array","items":{"type":"object","properties":{"guildId":{"type":"string","minLength":1},"channelId":{"type":"string","minLength":1}},"required":["guildId","channelId"],"additionalProperties":false}},"followUsersEnabled":{"type":"boolean"},"followUsers":{"type":"array","items":{"type":"string","minLength":1}},"allowedChannels":{"type":"array","items":{"type":"object","properties":{"guildId":{"type":"string","minLength":1},"channelId":{"type":"string","minLength":1}},"required":["guildId","channelId"],"additionalProperties":false}},"daveEncryption":{"type":"boolean"},"decryptionFailureTolerance":{"type":"integer","minimum":0,"maximum":9007199254740991},"connectTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"reconnectGraceMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"captureSilenceGraceMs":{"type":"integer","exclusiveMinimum":0,"maximum":30000},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":[', '"final","all"]},"provider":{"type":"string","minLength":1},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":"string"},"provider":{"type":"string","minLength":1},"fallbackPolicy":{"anyOf":[{"type":"string","const":"preserve-persona"},{"type":"string","const":"provider-defaults"},{"type":"string","const":"fail"}]},"prompt":{"type":"object","properties":{"profile":{"type":"string"},"scene":{"type":"string"},"sampleContext":{"type":"string"},"style":{"type":"string"},"accent":{"type":"string"},"pacing":{"type":"string"},"constraints":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"apiKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}}},"additionalProperties":false}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowText":{"type":"boolean"},"allowProvider":{"type":"boolean"},"allowVoice":{"type":"boolean"},"allowModelId":{"type":"boolean"},"allowVoiceSettings":{"type":"boolean"},"allowNormalization":{"type":"boolean"},"allowSeed":{"type":"boolean"}},"additionalProperties":false},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"apiKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false}},"additionalProperties":false},"pluralkit":{"type":"object","properties":{"enabled":{"type":"boolean"},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","off","none"]},"activity":{"type":"string"},"status":{"type":"string","enum":["online","dnd","idle","invisible"]},"autoPresence":{"type":"object","properties":{"enabled":{"type":"boolean"},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"minUpdateIntervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"healthyText":{"type":"string"},"degradedText":{"type":"string"},"exhaustedText":{"type":"string"}},"additionalProperties":false},"activityType":{"anyOf":[{"type":"number","const":0},{"type":"number","const":1},{"type":"number","const":2},{"type":"number","const":3},{"type":"number","const":4},{"type":"number","const":5}]},"activityUrl":{"type":"string","format":"uri"},"inboundWorker":{"type":"object","properties":{"runTimeoutMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"eventQueue":{"type":"object","properties":{"listenerTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxQueueSize":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxConcurrency":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"commands":{"type":"object","properties":{"native":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"nativeSkills":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"applicationId":{"type":"string"},"proxy":{"type":"string"},"gatewayInfoTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"gatewayRuntimeReadyTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"mentionAliases":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","pattern":"^\\\\d+$"}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"suppressEmbeds":{"type":"boolean"},"streaming":{"type":"object","properties":{"mode":{"type":"string","enum":["off","partial","block","progress"]},"chunkMode":{"type":"string","enum":["length","newline"]},"preview":{"type":"object","properties":{"chunk":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"breakPreference":{"anyOf":[{"type":"string","const":"paragraph"},{"type":"string","const":"newline"},{"type":"string","const":"sentence"}]}},"additionalProperties":false},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]}},"additionalProperties":false},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"render":{"type":"string","enum":["text","rich"]},"toolProgress":{"type":"boolean"},"commandText":{"type":"string","enum":["raw","status"]},"commentary":{"type":"boolean"}},"additionalProperties":false},"block":{"type":"object","properties":{"enabled":{"type":"boolean"},"coalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false},"maxLinesPerMessage":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"retry":{"type":"object","properties":{"attempts":{"type":"integer","minimum":1,"maximum":9007199254740991},"minDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"jitter":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"stickers":{"type":"boolean"},"emojiUploads":{"type":"boolean"},"stickerUploads":{"type":"boolean"},"polls":{"type":"boolean"},"permissions":{"type":"boolean"},"messages":{"type":"boolean"},"threads":{"type":"boolean"},"pins":{"type":"boolean"},"search":{"type":"boolean"},"memberInfo":{"type":"boolean"},"roleInfo":{"type":"boolean"},"roles":{"type":"boolean"},"channelInfo":{"type":"boolean"},"voiceStatus":{"type":"boolean"},"events":{"type":"boolean"},"moderation":{"type":"boolean"},"channels":{"type":"boolean"},"presence":{"type":"boolean"}},"additionalProperties":false},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"thread":{"type":"object","properties":{"inheritParent":{"type":"boolean"}},"additionalProperties":false},"dmPolicy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"defaultTo":{"type":"string"},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"type":"string"}},"groupEnabled":{"type":"boolean"},"groupChannels":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"guilds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"slug":{"type":"string"},"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"reactionNotifications":{"type":"string","enum":["off","own","all","allowlist"]},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"channels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"ignoreOtherMentions":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"users":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}},"systemPrompt":{"type":"string"},"includeThreadStarter":{"type":"boolean"},"autoThread":{"type":"boolean"},"autoThreadName":{"type":"string","enum":["message","generated"]},"autoArchiveDuration":{"anyOf":[{"type":"string","enum":["60","1440","4320","10080"]},{"type":"number","const":60},{"type":"number","const":1440},{"type":"number","const":4320},{"type":"number","const":10080}]}},"additionalProperties":false}}},"additionalProperties":false}},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"string","const":"auto"}]},"approvers":{"type":"array","items":{"type":"string"}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"cleanupAfterResolve":{"type":"boolean"},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"agentComponents":{"type":"object","properties":{"enabled":{"type":"boolean"},"ttlMs":{"type":"integer","exclusiveMinimum":0,"maximum":86400000}},"additionalProperties":false},"ui":{"type":"object","properties":{"components":{"type":"object","properties":{"accentColor":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$"}},"additionalProperties":false}},"additionalProperties":false},"slashCommand":{"type":"object","properties":{"ephemeral":{"type":"boolean"}},"additionalProperties":false},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"intents":{"type":"object","properties":{"presence":{"type":"boolean"},"guildMembers":{"type":"boolean"},"voiceStates":{"type":"boolean"}},"additionalPr', 'operties":false},"voice":{"type":"object","properties":{"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["stt-tts","agent-proxy","bidi"]},"agentSession":{"type":"object","properties":{"mode":{"type":"string","enum":["voice","target"]},"target":{"type":"string","minLength":1}},"additionalProperties":false},"model":{"type":"string","minLength":1},"realtime":{"type":"object","properties":{"provider":{"type":"string","minLength":1},"model":{"type":"string","minLength":1},"speakerVoice":{"type":"string","minLength":1},"speakerVoiceId":{"type":"string","minLength":1},"voice":{"type":"string","minLength":1},"instructions":{"type":"string","minLength":1},"toolPolicy":{"type":"string","enum":["safe-read-only","owner","none"]},"consultPolicy":{"type":"string","enum":["auto","always"]},"requireWakeName":{"type":"boolean"},"wakeNames":{"minItems":1,"type":"array","items":{"type":"string","minLength":1,"pattern":"^\\\\s*[^a-z0-9]*[a-z0-9]+(?:[^a-z0-9]+[a-z0-9]+)?[^a-z0-9]*\\\\s*$"}},"bootstrapContextFiles":{"type":"array","items":{"type":"string","enum":["IDENTITY.md","USER.md","SOUL.md"]}},"bargeIn":{"type":"boolean"},"minBargeInAudioEndMs":{"type":"integer","minimum":0,"maximum":10000},"debounceMs":{"type":"integer","exclusiveMinimum":0,"maximum":10000},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"additionalProperties":false},"autoJoin":{"type":"array","items":{"type":"object","properties":{"guildId":{"type":"string","minLength":1},"channelId":{"type":"string","minLength":1}},"required":["guildId","channelId"],"additionalProperties":false}},"followUsersEnabled":{"type":"boolean"},"followUsers":{"type":"array","items":{"type":"string","minLength":1}},"allowedChannels":{"type":"array","items":{"type":"object","properties":{"guildId":{"type":"string","minLength":1},"channelId":{"type":"string","minLength":1}},"required":["guildId","channelId"],"additionalProperties":false}},"daveEncryption":{"type":"boolean"},"decryptionFailureTolerance":{"type":"integer","minimum":0,"maximum":9007199254740991},"connectTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"reconnectGraceMs":{"type":"integer","exclusiveMinimum":0,"maximum":120000},"captureSilenceGraceMs":{"type":"integer","exclusiveMinimum":0,"maximum":30000},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string","minLength":1},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":"string"},"provider":{"type":"string","minLength":1},"fallbackPolicy":{"anyOf":[{"type":"string","const":"preserve-persona"},{"type":"string","const":"provider-defaults"},{"type":"string","const":"fail"}]},"prompt":{"type":"object","properties":{"profile":{"type":"string"},"scene":{"type":"string"},"sampleContext":{"type":"string"},"style":{"type":"string"},"accent":{"type":"string"},"pacing":{"type":"string"},"constraints":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"apiKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}}},"additionalProperties":false}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowText":{"type":"boolean"},"allowProvider":{"type":"boolean"},"allowVoice":{"type":"boolean"},"allowModelId":{"type":"boolean"},"allowVoiceSettings":{"type":"boolean"},"allowNormalization":{"type":"boolean"},"allowSeed":{"type":"boolean"}},"additionalProperties":false},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"apiKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}]}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false}},"additionalProperties":false},"pluralkit":{"type":"object","properties":{"enabled":{"type":"boolean"},"token":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]}},"additionalProperties":false},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","off","none"]},"activity":{"type":"string"},"status":{"type":"string","enum":["online","dnd","idle","invisible"]},"autoPresence":{"type":"object","properties":{"enabled":{"type":"boolean"},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"minUpdateIntervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"healthyText":{"type":"string"},"degradedText":{"type":"string"},"exhaustedText":{"type":"string"}},"additionalProperties":false},"activityType":{"anyOf":[{"type":"number","const":0},{"type":"number","const":1},{"type":"number","const":2},{"type":"number","const":3},{"type":"number","const":4},{"type":"number","const":5}]},"activityUrl":{"type":"string","format":"uri"},"inboundWorker":{"type":"object","properties":{"runTimeoutMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"eventQueue":{"type":"object","properties":{"listenerTimeout":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxQueueSize":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxConcurrency":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"Discord","help":"Discord channel provider configuration for bot auth, retry policy, streaming, thread bindings, and optional voice capabilities. Keep privileged intents and advanced features disabled unless needed."},"dmPolicy":{"label":"Discord DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.discord.allowFrom=[\\"*\\"]."},"dm.policy":{"label":"Discord DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.discord.allowFrom=[\\"*\\"] (legacy: channels.discord.dm.allowFrom)."},"configWrites":{"label":"Discord Config Writes","help":"Allow Discord to write config in response to channel events/commands (default: true)."},"proxy":{"label":"Discord Proxy URL","help":"Proxy URL for Discord gateway + API requests (app-id lookup and allowlist resolution). Set per account via channels.discord.accounts..proxy."},"commands.native":{"label":"Discord Native Commands","help":"Override native commands for Discord (bool or \\"auto\\")."},"commands.nativeSkills":{"label":"Discord Native Skill Commands","help":"Override native skill commands for Discord (bool or \\"auto\\")."},"streaming":{"label":"Discord Streaming Mode","help":"Unified Discord stream preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\". \\"progress\\" keeps a single editable progress draft until final delivery. Legacy boolean/streamMode keys are auto-mapped."},"streaming.mode":{"label":"Discord Streaming Mode","help":"Canonical Discord preview mode: \\"off\\" | \\"partial\\" | \\"block\\" | \\"progress\\"."},"streaming.chunkMode":{"label":"Discord Chunk Mode","help":"Chunking mode for outbound Discord text delivery: \\"length\\" (default) or \\"newline\\"."},"streaming.block.enabled":{"label":"Discord Block Streaming Enabled","help":"Enable chunked block-style Discord preview delivery when channels.discord.streaming.mode=\\"block\\"."},"streaming.block.coalesce":{"label":"Discord Block Streaming Coalesce","help":"Merge streamed Discord block replies before final delivery."},"streaming.preview.chunk.minChars":{"label":"Discord Draft Chunk Min Chars","help":"Minimum chars before emitting a Discord stream preview update when channels.discord.streaming.mode=\\"block\\" (default: 200)."},"streaming.preview.chunk.maxChars":{"label":"Discord Draft Chunk Max Chars","help":"Target max size for a Discord stream preview chunk when channels.discord.streaming.mode=\\"block\\" (default: 800; clamped to channels.discord.textChunkLimit)."},"streaming.preview.chunk.breakPreference":{"label":"Discord Draft Chunk Break Preference","help":"Preferred breakpoints for Discord draft chunks (paragraph | newline | sentence). Default: paragraph."},"streaming.preview.toolProgress":{"label":"Discord Draft Tool Progress","help":"Show tool/progress activity in the live draft preview message (default: true). Set false to hide interim tool updates while the draft preview stays active."},"streaming.preview.commandText":{"label":"Discord Draft Command Text","help":"Command/exec detail in preview tool-progress lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"streaming.progress.label":{"label":"Discord Progress Label","help":"Initial progress draft title. Use \\"auto\\" for built-in single-word labels, a custom string, or false to hide the title."},"streaming.progress.labels":{"label":"Discord Progress Label Pool","help":"Candidate labels for streaming.progress.label=\\"auto\\". Leave unset to use OpenClaw built-in progress labels."},"streaming.progress.maxLines":{"label":"Discord Progress Max Lines","help":"Maximum number of compact progress lines to keep below the draft label (default: 8)."},"streaming.progress.maxLineChars":{"label":"Discord Progress Max Line Chars","help":"Maximum characters per compact progress line before truncation (default: 120). Prose cuts at word boundaries; commands and paths keep useful suffixes."},"streaming.progress.toolProgress":{"label":"Discord Progress Tool Lines","help":"Show compact tool/progress lines in progress draft mode (default: true). Set false to keep only the label until final delivery."},"streaming.progress.commentary":{"label":"Discord Progress Commentary","help":"Show assistant commentary/preamble text in the temporary progress draft. Final answer delivery is unchanged."},"streaming.progress.commandText":{"label":"Discord Progress Command Text","help":"Command/exec detail in progress draft lines: \\"raw\\" preserves released behavior; \\"status\\" shows only the tool label."},"retry.attempts":{"label":"Discord Retry Attempts","help":"Max retry attempts for outbound Discord API calls (default: 3)."},"retry.minDelayMs":{"label":"Discord Retry Min Delay (ms)","help":"Minimum retry delay in ms for Discord outbound calls."},"retry.maxDelayMs":{"label":"Discord Retry Max Delay (ms)","help":"Maximum retry delay cap in ms for Discord outbound calls."},"retry.jitter":{"label":"Discord Retry Jitter","help":"Jitter factor (0-1) applied to Discord retry delays."},"maxLinesPerMessage":{"label":"Discord Max Lines Per Message","help":"Soft max line count per Discord message (default: 17)."},"suppressEmbeds":{"label":"Discord Suppress Link Embeds","help":"Suppress Discord-generated link embeds on outbound messages by default. Explicit embeds still send normally. Default: true."},"thread.inheritParent":{"label":"Discord Thread Parent Inheritance","help":"If true, Discord thread sessions inherit the parent channel transcript (default: false)."},"eventQueue.listenerTimeout":{"label":"Discord EventQueue Listener Timeout (ms)","help":"Canonical Discord listener timeout control in ms for gateway normalization/enqueue handlers. Default is 120000 in OpenClaw; set per account via channels.discord.accounts..eventQueue.listenerTimeout."},"eventQueue.maxQueueSize":{"label":"Discord EventQueue Max Queue Size","help":"Optional Discord EventQueue capacity override (max queued events before backpressure). Set per account via channels.discord.accounts..eventQueue.maxQueueSize."},"eventQueue.maxConcurrency":{"label":"Discord EventQueue Max Concurrency","help":"Optional Discord EventQueue concurrency override (max concurrent handler executions). Set per account via channels.discord.accounts..eventQueue.maxConcurrency."},"threadBindings.enabled":{"label":"Discord Thread Binding Enabled","help":"Enable Discord thread binding features (/focus, bound-thread routing/delivery, and thread-bound subagent sessions). Overrides session.threadBindings.enabled when set."},"threadBindings.idleHours":{"label":"Discord Thread Binding Idle Timeout (hours)","help":"Inactivity window in hours for Discord thread-bound sessions (/focus and spawned thread sessions). Set 0 to disable idle auto-unfocus (default: 24). Overrides session.threadBindings.idleHours when set."},"threadBindings.maxAgeHours":{"label":"Discord Thread Binding Max Age (hours)","help":"Optional hard max age in hours for Discord thread-bound sessions. Set 0 to disable hard cap (default: 0). Overrides session.threadBindings.maxAgeHours when set."},"threadBindings.spawnSessions":{"label":"Discord Thread-Bound Session Spawn","help":"Allow sessions_spawn(thread=true) and ACP thread spawns to auto-create and bind Discord threads (default: true). Set false to disable for this account/channel."},"threadBindings.defaultSpawnContext":{"label":"Discord Thread Spawn Context","help":"Default native subagent context for thread-bound spawns. \\"fork\\" starts from the requester transcript; \\"isolated\\" starts clean. Default: \\"fork\\"."},"ui.components.accentColor":{"label":"Discord Component Accent Color","help":"Accent color for Discord component containers (hex). Set per account via channels.discord.accounts..ui.components.accentColor."},"agentComponents.ttlMs":{"label":"', - 'Discord Component TTL (ms)","help":"How long sent Discord component callbacks remain registered. Default is 1800000 (30 minutes); maximum is 86400000 (24 hours)."},"intents.presence":{"label":"Discord Presence Intent","help":"Enable the Guild Presences privileged intent. Must also be enabled in the Discord Developer Portal. Allows tracking user activities (e.g. Spotify). Default: false."},"intents.guildMembers":{"label":"Discord Guild Members Intent","help":"Enable the Guild Members privileged intent. Must also be enabled in the Discord Developer Portal. Default: false."},"intents.voiceStates":{"label":"Discord Voice States Intent","help":"Enable the Guild Voice States intent. Defaults to the effective Discord voice setting; set true only for Discord voice channel conversations."},"gatewayInfoTimeoutMs":{"label":"Discord Gateway Metadata Timeout (ms)","help":"Timeout for Discord /gateway/bot metadata lookup before falling back to the default gateway URL. Default is 30000; OPENCLAW_DISCORD_GATEWAY_INFO_TIMEOUT_MS can override when config is unset."},"gatewayReadyTimeoutMs":{"label":"Discord Gateway READY Timeout (ms)","help":"Startup wait for the Discord gateway READY event before restarting the socket. Default is 15000; OPENCLAW_DISCORD_READY_TIMEOUT_MS can override when config is unset."},"gatewayRuntimeReadyTimeoutMs":{"label":"Discord Gateway Runtime READY Timeout (ms)","help":"Runtime reconnect wait for the Discord gateway READY event before force-stopping the lifecycle. Default is 30000; OPENCLAW_DISCORD_RUNTIME_READY_TIMEOUT_MS can override when config is unset."},"voice.enabled":{"label":"Discord Voice Enabled","help":"Enable Discord voice channel conversations. Text-only Discord configs leave voice off by default; set true to enable /vc commands and the Guild Voice States intent."},"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."},"voice.mode":{"label":"Discord Voice Mode","help":"Conversation mode: agent-proxy (default) uses realtime voice as the microphone/speaker for the routed OpenClaw agent, stt-tts uses batch speech-to-text plus TTS, and bidi lets the realtime provider converse directly with the OpenClaw consult tool."},"voice.agentSession":{"label":"Discord Voice Agent Session","help":"Controls which OpenClaw conversation receives voice turns. Leave unset for the voice channel session, or set mode=\\"target\\" with a Discord target such as channel:123 to make voice an extension of an existing text channel session."},"voice.agentSession.target":{"label":"Discord Voice Agent Session Target","help":"Discord target used when voice.agentSession.mode=\\"target\\", for example channel:123."},"voice.followUsersEnabled":{"label":"Discord Voice Follow Users Enabled","help":"Toggle Discord voice follow-users behavior without removing the saved voice.followUsers list. Defaults to true when followUsers is configured."},"voice.followUsers":{"label":"Discord Voice Follow Users","help":"Discord user IDs to follow into voice channels. The bot joins when a followed user joins or moves, and leaves when that user disconnects."},"voice.realtime.provider":{"label":"Discord Realtime Provider","help":"Realtime voice provider for agent-proxy or bidi Discord voice modes, such as openai."},"voice.realtime.model":{"label":"Discord Realtime Model","help":"Provider realtime session model, such as gpt-realtime-2. This is separate from voice.model, which remains the OpenClaw agent brain model."},"voice.realtime.speakerVoice":{"label":"Discord Realtime Speaker Voice","help":"Provider realtime output voice name, such as cedar."},"voice.realtime.speakerVoiceId":{"label":"Discord Realtime Speaker Voice ID","help":"Provider realtime output voice id."},"voice.realtime.voice":{"label":"Discord Realtime Voice","help":"Deprecated provider realtime output voice. Use voice.realtime.speakerVoice."},"voice.realtime.toolPolicy":{"label":"Discord Realtime Tool Policy","help":"Tool policy for the OpenClaw agent consult tool in realtime voice modes: safe-read-only, owner, or none. Default is owner for agent-proxy and safe-read-only for bidi."},"voice.realtime.consultPolicy":{"label":"Discord Realtime Consult Policy","help":"Use always to strongly prefer the OpenClaw agent brain for substantive realtime turns. agent-proxy defaults to always."},"voice.realtime.requireWakeName":{"label":"Discord Realtime Require Wake Name","help":"Require a configured wake name before OpenAI agent-proxy Discord realtime voice responds. If wakeNames is unset, the routed agent name is used, falling back to the agent id."},"voice.realtime.wakeNames":{"label":"Discord Realtime Wake Names","help":"One- or two-word activation names that allow OpenAI agent-proxy Discord realtime voice to respond when requireWakeName is enabled."},"voice.realtime.bootstrapContextFiles":{"label":"Discord Realtime Bootstrap Context Files","help":"Agent profile bootstrap files included in realtime provider instructions for direct voice identity/persona grounding. Defaults to IDENTITY.md, USER.md, and SOUL.md; set [] to disable."},"voice.realtime.bargeIn":{"label":"Discord Realtime Barge-In","help":"Allow Discord speaker-start events to interrupt active realtime playback. Set true to keep manual interruption when provider input-audio interruption is disabled for echo control."},"voice.realtime.minBargeInAudioEndMs":{"label":"Discord Realtime Minimum Barge-In Audio (ms)","help":"Minimum assistant playback duration before a Discord barge-in truncates realtime audio. Default: 250; set 0 for immediate interruption in low-echo rooms."},"voice.realtime.providers":{"label":"Discord Realtime Provider Settings","help":"Provider-specific realtime voice settings keyed by provider id.","advanced":true},"voice.autoJoin":{"label":"Discord Voice Auto-Join","help":"Voice channels to auto-join on startup (list of guildId/channelId entries)."},"voice.allowedChannels":{"label":"Discord Voice Allowed Channels","help":"Optional voice channel residency allowlist. When set, /vc join, auto-join, and bot voice-state moves are restricted to these guildId/channelId entries. Leave unset to allow any voice channel."},"voice.daveEncryption":{"label":"Discord Voice DAVE Encryption","help":"Toggle DAVE end-to-end encryption for Discord voice joins (default: true in @discordjs/voice; Discord may require this)."},"voice.decryptionFailureTolerance":{"label":"Discord Voice Decrypt Failure Tolerance","help":"Consecutive decrypt failures before DAVE attempts session recovery (passed to @discordjs/voice; default: 24)."},"voice.connectTimeoutMs":{"label":"Discord Voice Connect Timeout (ms)","help":"Initial @discordjs/voice Ready wait before a join is treated as failed. Default: 30000."},"voice.reconnectGraceMs":{"label":"Discord Voice Reconnect Grace (ms)","help":"Grace period for a disconnected Discord voice session to enter Signalling or Connecting before OpenClaw destroys it. Default: 15000."},"voice.captureSilenceGraceMs":{"label":"Discord Voice Capture Silence Grace (ms)","help":"Silence window after Discord reports a speaker ended before OpenClaw finalizes the audio segment for transcription. Default: 2000."},"voice.tts":{"label":"Discord Voice Text-to-Speech","help":"Optional TTS overrides for Discord voice playback (merged with messages.tts)."},"pluralkit.enabled":{"label":"Discord PluralKit Enabled","help":"Resolve PluralKit proxied messages and treat system members as distinct senders."},"pluralkit.token":{"label":"Discord PluralKit Token","help":"Optional PluralKit token for resolving private systems or members."},"activity":{"label":"Discord Presence Activity","help":"Discord presence activity text (defaults to custom status)."},"status":{"label":"Discord Presence Status","help":"Discord presence status (online, dnd, idle, invisible)."},"autoPresence.enabled":{"label":"Discord Auto Presence Enabled","help":"Enable automatic Discord bot presence updates based on runtime/model availability signals. When enabled: healthy=>online, degraded/unknown=>idle, exhausted/unavailable=>dnd."},"autoPresence.intervalMs":{"label":"Discord Auto Presence Check Interval (ms)","help":"How often to evaluate Discord auto-presence state in milliseconds (default: 30000)."},"autoPresence.minUpdateIntervalMs":{"label":"Discord Auto Presence Min Update Interval (ms)","help":"Minimum time between actual Discord presence update calls in milliseconds (default: 15000). Prevents status spam on noisy state changes."},"autoPresence.healthyText":{"label":"Discord Auto Presence Healthy Text","help":"Optional custom status text while runtime is healthy (online). If omitted, falls back to static channels.discord.activity when set."},"autoPresence.degradedText":{"label":"Discord Auto Presence Degraded Text","help":"Optional custom status text while runtime/model availability is degraded or unknown (idle)."},"autoPresence.exhaustedText":{"label":"Discord Auto Presence Exhausted Text","help":"Optional custom status text while runtime detects exhausted/unavailable model quota (dnd). Supports {reason} template placeholder."},"activityType":{"label":"Discord Presence Activity Type","help":"Discord presence activity type (0=Playing,1=Streaming,2=Listening,3=Watching,4=Custom,5=Competing)."},"activityUrl":{"label":"Discord Presence Activity URL","help":"Discord presence streaming URL (required for activityType=1)."},"allowBots":{"label":"Discord Allow Bot Messages","help":"Allow bot-authored messages to trigger Discord replies (default: false). Set \\"mentions\\" to only accept bot messages that mention the bot."},"botLoopProtection":{"label":"Discord Bot Loop Protection","help":"Sliding-window guard for bot-to-bot Discord loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Discord Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Discord Bot Pair Events Per Window","help":"Maximum messages a single Discord bot pair may exchange in the configured window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Discord Bot Loop Window Seconds","help":"Sliding window length in seconds for Discord bot-pair loop budgets. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Discord Bot Loop Cooldown Seconds","help":"Seconds to suppress a Discord bot pair after it exceeds the loop budget. Default: 60."},"mentionAliases":{"label":"Discord Mention Aliases","help":"Map outbound @handle text to stable Discord user IDs before sending. Set per account via channels.discord.accounts..mentionAliases."},"token":{"label":"Discord Bot Token","help":"Discord bot token used for gateway and REST API authentication for this provider account. Keep this secret out of committed config and rotate immediately after any leak.","sensitive":true},"applicationId":{"label":"Discord Application ID","help":"Optional Discord application/client ID. Set this when hosted environments cannot reach Discord\'s application lookup endpoint during startup."}},"unsupportedSecretRefSurfacePatterns":["channels.discord.accounts.*.threadBindings.webhookToken","channels.discord.threadBindings.webhookToken"]},{"pluginId":"feishu","channelId":"feishu","aliases":["lark"],"order":35,"channelEnvVars":["FEISHU_APP_ID","FEISHU_APP_SECRET","FEISHU_ENCRYPT_KEY","FEISHU_VERIFICATION_TOKEN"],"label":"Feishu","description":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"defaultAccount":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"default":"feishu","anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"default":"websocket","type":"string","enum":["websocket","webhook"]},"webhookPath":{"default":"/feishu/events","type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["open","pairing","allowlist"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","anyOf":[{"type":"string","enum":["open","allowlist","disabled"]},{}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":900', + 'Discord Component TTL (ms)","help":"How long sent Discord component callbacks remain registered. Default is 1800000 (30 minutes); maximum is 86400000 (24 hours)."},"intents.presence":{"label":"Discord Presence Intent","help":"Enable the Guild Presences privileged intent. Must also be enabled in the Discord Developer Portal. Allows tracking user activities (e.g. Spotify). Default: false."},"intents.guildMembers":{"label":"Discord Guild Members Intent","help":"Enable the Guild Members privileged intent. Must also be enabled in the Discord Developer Portal. Default: false."},"intents.voiceStates":{"label":"Discord Voice States Intent","help":"Enable the Guild Voice States intent. Defaults to the effective Discord voice setting; set true only for Discord voice channel conversations."},"gatewayInfoTimeoutMs":{"label":"Discord Gateway Metadata Timeout (ms)","help":"Timeout for Discord /gateway/bot metadata lookup before falling back to the default gateway URL. Default is 30000; OPENCLAW_DISCORD_GATEWAY_INFO_TIMEOUT_MS can override when config is unset."},"gatewayReadyTimeoutMs":{"label":"Discord Gateway READY Timeout (ms)","help":"Startup wait for the Discord gateway READY event before restarting the socket. Default is 15000; OPENCLAW_DISCORD_READY_TIMEOUT_MS can override when config is unset."},"gatewayRuntimeReadyTimeoutMs":{"label":"Discord Gateway Runtime READY Timeout (ms)","help":"Runtime reconnect wait for the Discord gateway READY event before force-stopping the lifecycle. Default is 30000; OPENCLAW_DISCORD_RUNTIME_READY_TIMEOUT_MS can override when config is unset."},"voice.enabled":{"label":"Discord Voice Enabled","help":"Enable Discord voice channel conversations. Text-only Discord configs leave voice off by default; set true to enable /vc commands and the Guild Voice States intent."},"voice.model":{"label":"Discord Voice 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","help":"Conversation mode: agent-proxy (default) uses realtime voice as the microphone/speaker for the routed OpenClaw agent, stt-tts uses batch speech-to-text plus TTS, and bidi lets the realtime provider converse directly with the OpenClaw consult tool."},"voice.agentSession":{"label":"Discord Voice Agent Session","help":"Controls which OpenClaw conversation receives voice turns. Leave unset for the voice channel session, or set mode=\\"target\\" with a Discord target such as channel:123 to make voice an extension of an existing text channel session."},"voice.agentSession.target":{"label":"Discord Voice Agent Session Target","help":"Discord target used when voice.agentSession.mode=\\"target\\", for example channel:123."},"voice.followUsersEnabled":{"label":"Discord Voice Follow Users Enabled","help":"Toggle Discord voice follow-users behavior without removing the saved voice.followUsers list. Defaults to true when followUsers is configured."},"voice.followUsers":{"label":"Discord Voice Follow Users","help":"Discord user IDs to follow into voice channels. The bot joins when a followed user joins or moves, and leaves when that user disconnects."},"voice.realtime.provider":{"label":"Discord Realtime Provider","help":"Realtime voice provider for agent-proxy or bidi Discord voice modes, such as openai."},"voice.realtime.model":{"label":"Discord Realtime Model","help":"Provider realtime session model, such as gpt-realtime-2. This is separate from voice.model, which remains the OpenClaw agent brain model."},"voice.realtime.speakerVoice":{"label":"Discord Realtime Speaker Voice","help":"Provider realtime output voice name, such as cedar."},"voice.realtime.speakerVoiceId":{"label":"Discord Realtime Speaker Voice ID","help":"Provider realtime output voice id."},"voice.realtime.voice":{"label":"Discord Realtime Voice","help":"Deprecated provider realtime output voice. Use voice.realtime.speakerVoice."},"voice.realtime.toolPolicy":{"label":"Discord Realtime Tool Policy","help":"Tool policy for the OpenClaw agent consult tool in realtime voice modes: safe-read-only, owner, or none. Default is owner for agent-proxy and safe-read-only for bidi."},"voice.realtime.consultPolicy":{"label":"Discord Realtime Consult Policy","help":"Use always to strongly prefer the OpenClaw agent brain for substantive realtime turns. agent-proxy defaults to always."},"voice.realtime.requireWakeName":{"label":"Discord Realtime Require Wake Name","help":"Require a configured wake name before OpenAI agent-proxy Discord realtime voice responds. If wakeNames is unset, the routed agent name is used, falling back to the agent id."},"voice.realtime.wakeNames":{"label":"Discord Realtime Wake Names","help":"One- or two-word activation names that allow OpenAI agent-proxy Discord realtime voice to respond when requireWakeName is enabled."},"voice.realtime.bootstrapContextFiles":{"label":"Discord Realtime Bootstrap Context Files","help":"Agent profile bootstrap files included in realtime provider instructions for direct voice identity/persona grounding. Defaults to IDENTITY.md, USER.md, and SOUL.md; set [] to disable."},"voice.realtime.bargeIn":{"label":"Discord Realtime Barge-In","help":"Allow Discord speaker-start events to interrupt active realtime playback. Set true to keep manual interruption when provider input-audio interruption is disabled for echo control."},"voice.realtime.minBargeInAudioEndMs":{"label":"Discord Realtime Minimum Barge-In Audio (ms)","help":"Minimum assistant playback duration before a Discord barge-in truncates realtime audio. Default: 250; set 0 for immediate interruption in low-echo rooms."},"voice.realtime.providers":{"label":"Discord Realtime Provider Settings","help":"Provider-specific realtime voice settings keyed by provider id.","advanced":true},"voice.autoJoin":{"label":"Discord Voice Auto-Join","help":"Voice channels to auto-join on startup (list of guildId/channelId entries)."},"voice.allowedChannels":{"label":"Discord Voice Allowed Channels","help":"Optional voice channel residency allowlist. When set, /vc join, auto-join, and bot voice-state moves are restricted to these guildId/channelId entries. Leave unset to allow any voice channel."},"voice.daveEncryption":{"label":"Discord Voice DAVE Encryption","help":"Toggle DAVE end-to-end encryption for Discord voice joins (default: true in @discordjs/voice; Discord may require this)."},"voice.decryptionFailureTolerance":{"label":"Discord Voice Decrypt Failure Tolerance","help":"Consecutive decrypt failures before DAVE attempts session recovery (passed to @discordjs/voice; default: 24)."},"voice.connectTimeoutMs":{"label":"Discord Voice Connect Timeout (ms)","help":"Initial @discordjs/voice Ready wait before a join is treated as failed. Default: 30000."},"voice.reconnectGraceMs":{"label":"Discord Voice Reconnect Grace (ms)","help":"Grace period for a disconnected Discord voice session to enter Signalling or Connecting before OpenClaw destroys it. Default: 15000."},"voice.captureSilenceGraceMs":{"label":"Discord Voice Capture Silence Grace (ms)","help":"Silence window after Discord reports a speaker ended before OpenClaw finalizes the audio segment for transcription. Default: 2000."},"voice.tts":{"label":"Discord Voice Text-to-Speech","help":"Optional TTS overrides for Discord voice playback (merged with messages.tts)."},"pluralkit.enabled":{"label":"Discord PluralKit Enabled","help":"Resolve PluralKit proxied messages and treat system members as distinct senders."},"pluralkit.token":{"label":"Discord PluralKit Token","help":"Optional PluralKit token for resolving private systems or members."},"activity":{"label":"Discord Presence Activity","help":"Discord presence activity text (defaults to custom status)."},"status":{"label":"Discord Presence Status","help":"Discord presence status (online, dnd, idle, invisible)."},"autoPresence.enabled":{"label":"Discord Auto Presence Enabled","help":"Enable automatic Discord bot presence updates based on runtime/model availability signals. When enabled: healthy=>online, degraded/unknown=>idle, exhausted/unavailable=>dnd."},"autoPresence.intervalMs":{"label":"Discord Auto Presence Check Interval (ms)","help":"How often to evaluate Discord auto-presence state in milliseconds (default: 30000)."},"autoPresence.minUpdateIntervalMs":{"label":"Discord Auto Presence Min Update Interval (ms)","help":"Minimum time between actual Discord presence update calls in milliseconds (default: 15000). Prevents status spam on noisy state changes."},"autoPresence.healthyText":{"label":"Discord Auto Presence Healthy Text","help":"Optional custom status text while runtime is healthy (online). If omitted, falls back to static channels.discord.activity when set."},"autoPresence.degradedText":{"label":"Discord Auto Presence Degraded Text","help":"Optional custom status text while runtime/model availability is degraded or unknown (idle)."},"autoPresence.exhaustedText":{"label":"Discord Auto Presence Exhausted Text","help":"Optional custom status text while runtime detects exhausted/unavailable model quota (dnd). Supports {reason} template placeholder."},"activityType":{"label":"Discord Presence Activity Type","help":"Discord presence activity type (0=Playing,1=Streaming,2=Listening,3=Watching,4=Custom,5=Competing)."},"activityUrl":{"label":"Discord Presence Activity URL","help":"Discord presence streaming URL (required for activityType=1)."},"allowBots":{"label":"Discord Allow Bot Messages","help":"Allow bot-authored messages to trigger Discord replies (default: false). Set \\"mentions\\" to only accept bot messages that mention the bot."},"botLoopProtection":{"label":"Discord Bot Loop Protection","help":"Sliding-window guard for bot-to-bot Discord loops. Default is enabled whenever allowBots lets bot-authored messages reach dispatch."},"botLoopProtection.enabled":{"label":"Discord Bot Loop Protection Enabled","help":"Enable the bot-pair loop guard. Defaults to true when allowBots is true or \\"mentions\\", and false when bot messages are ignored."},"botLoopProtection.maxEventsPerWindow":{"label":"Discord Bot Pair Events Per Window","help":"Maximum messages a single Discord bot pair may exchange in the configured window before suppression starts. Default: 20."},"botLoopProtection.windowSeconds":{"label":"Discord Bot Loop Window Seconds","help":"Sliding window length in seconds for Discord bot-pair loop budgets. Default: 60."},"botLoopProtection.cooldownSeconds":{"label":"Discord Bot Loop Cooldown Seconds","help":"Seconds to suppress a Discord bot pair after it exceeds the loop budget. Default: 60."},"mentionAliases":{"label":"Discord Mention Aliases","help":"Map outbound @handle text to stable Discord user IDs before sending. Set per account via channels.discord.accounts..mentionAliases."},"token":{"label":"Discord Bot Token","help":"Discord bot token used for gateway and REST API authentication for this provider account. Keep this secret out of committed config and rotate immediately after any leak.","sensitive":true},"applicationId":{"label":"Discord Application ID","help":"Optional Discord application/client ID. Set this when hosted environments cannot reach Discord\'s application lookup endpoint during startup."}},"unsupportedSecretRefSurfacePatterns":["channels.discord.accounts.*.threadBindings.webhookToken","channels.discord.threadBindings.webhookToken"]},{"pluginId":"feishu","channelId":"feishu","aliases":["lark"],"order":35,"channelEnvVars":["FEISHU_APP_ID","FEISHU_APP_SECRET","FEISHU_ENCRYPT_KEY","FEISHU_VERIFICATION_TOKEN"],"label":"Feishu","description":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"defaultAccount":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"default":"feishu","anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"default":"websocket","type":"string","enum":["websocket","webhook"]},"webhookPath":{"default":"/feishu/events","type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"default":"pairing","type":"string","enum":["open","pairing","allowlist"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","anyOf":[{"type":"string","enum":["open","allowlist","disabled"]},{}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":900', '7199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"enabled":{"type":"boolean"},"minDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeat":{"type":"object","properties":{"visibility":{"type":"string","enum":["visible","hidden"]},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"renderMode":{"type":"string","enum":["auto","raw","card"]},"streaming":{"type":"boolean"},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"type":"boolean"}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"replyInThread":{"type":"string","enum":["disabled","enabled"]},"reactionNotifications":{"default":"own","type":"string","enum":["off","own","all"]},"typingIndicator":{"default":true,"type":"boolean"},"resolveSenderNames":{"default":true,"type":"boolean"},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string"},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"dynamicAgentCreation":{"type":"object","properties":{"enabled":{"type":"boolean"},"workspaceTemplate":{"type":"string"},"agentDirTemplate":{"type":"string"},"maxAgents":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"name":{"type":"string"},"appId":{"type":"string"},"appSecret":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"encryptKey":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"verificationToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"domain":{"anyOf":[{"type":"string","enum":["feishu","lark"]},{"type":"string","format":"uri","pattern":"^https:\\\\/\\\\/.*"}]},"connectionMode":{"type":"string","enum":["websocket","webhook"]},"webhookPath":{"type":"string"},"webhookHost":{"type":"string"},"webhookPort":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"mode":{"type":"string","enum":["native","escape","strip"]},"tableMode":{"type":"string","enum":["native","ascii","simple"]}},"additionalProperties":false},"configWrites":{"type":"boolean"},"dmPolicy":{"type":"string","enum":["open","pairing","allowlist"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"anyOf":[{"type":"string","enum":["open","allowlist","disabled"]},{}]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupSenderAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]},"replyInThread":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"enabled":{"type":"boolean"},"minDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxDelayMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"httpTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":300000},"heartbeat":{"type":"object","properties":{"visibility":{"type":"string","enum":["visible","hidden"]},"intervalMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"renderMode":{"type":"string","enum":["auto","raw","card"]},"streaming":{"type":"boolean"},"tools":{"type":"object","properties":{"doc":{"type":"boolean"},"chat":{"type":"boolean"},"wiki":{"type":"boolean"},"drive":{"type":"boolean"},"perm":{"type":"boolean"},"scopes":{"type":"boolean"}},"additionalProperties":false},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"replyInThread":{"type":"string","enum":["disabled","enabled"]},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"typingIndicator":{"type":"boolean"},"resolveSenderNames":{"type":"boolean"},"tts":{"type":"object","properties":{"auto":{"type":"string","enum":["off","always","inbound","tagged"]},"enabled":{"type":"boolean"},"mode":{"type":"string","enum":["final","all"]},"provider":{"type":"string"},"persona":{"type":"string"},"personas":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"summaryModel":{"type":"string"},"modelOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"providers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"prefsPath":{"type":"string"},"maxTextLength":{"type":"integer","minimum":1,"maximum":9007199254740991},"timeoutMs":{"type":"integer","minimum":1000,"maximum":120000}},"additionalProperties":false},"groupSessionScope":{"type":"string","enum":["group","group_sender","group_topic","group_topic_sender"]},"topicSessionMode":{"type":"string","enum":["disabled","enabled"]}},"additionalProperties":false}}},"required":["domain","connectionMode","webhookPath","dmPolicy","groupPolicy","reactionNotifications","typingIndicator","resolveSenderNames"],"additionalProperties":false}},{"pluginId":"googlechat","channelId":"googlechat","aliases":["gchat","google-chat"],"order":55,"channelEnvVars":["GOOGLE_CHAT_SERVICE_ACCOUNT","GOOGLE_CHAT_SERVICE_ACCOUNT_FILE"],"label":"Google Chat","description":"Google Workspace Chat app with HTTP webhook.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"allowBots":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"defaultTo":{"type":"string"},"serviceAccount":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"serviceAccountRef":{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]},"serviceAccountFile":{"type":"string"},"audienceType":{"type":"string","enum":["app-url","project-number"]},"audience":{"type":"string"},"appPrincipal":{"type":"string"},"webhookPath":{"type":"string"},"webhookUrl":{"type":"string"},"botUser":{"type":"string"},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"required":["policy"],"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"responsePrefix":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"allowBots":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":900', '7199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"dangerouslyAllowNameMatching":{"type":"boolean"},"requireMention":{"type":"boolean"},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"users":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"defaultTo":{"type":"string"},"serviceAccount":{"anyOf":[{"type":"string"},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"serviceAccountRef":{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]},"serviceAccountFile":{"type":"string"},"audienceType":{"type":"string","enum":["app-url","project-number"]},"audience":{"type":"string"},"appPrincipal":{"type":"string"},"webhookPath":{"type":"string"},"webhookUrl":{"type":"string"},"botUser":{"type":"string"},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"replyToMode":{"anyOf":[{"type":"string","const":"off"},{"type":"string","const":"first"},{"type":"string","const":"all"},{"type":"string","const":"batched"}]},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"}},"additionalProperties":false},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"required":["policy"],"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"typingIndicator":{"type":"string","enum":["none","message","reaction"]},"responsePrefix":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["groupPolicy"],"additionalProperties":false}},{"pluginId":"imessage","channelId":"imessage","aliases":["imsg"],"label":"iMessage","description":"Local iMessage/SMS through the imsg bridge, including private API message actions when enabled.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"cliPath":{"type":"string"},"dbPath":{"type":"string"},"remoteHost":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"edit":{"type":"boolean"},"unsend":{"type":"boolean"},"reply":{"type":"boolean"},"sendWithEffect":{"type":"boolean"},"renameGroup":{"type":"boolean"},"setGroupIcon":{"type":"boolean"},"addParticipant":{"type":"boolean"},"removeParticipant":{"type":"boolean"},"leaveGroup":{"type":"boolean"},"sendAttachment":{"type":"boolean"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"region":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"coalesceSameSenderDms":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"catchup":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxAgeMinutes":{"type":"integer","minimum":1,"maximum":720},"perRunLimit":{"type":"integer","minimum":1,"maximum":500},"firstRunLookbackMinutes":{"type":"integer","minimum":1,"maximum":720},"maxFailureRetries":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"enabled":{"type":"boolean"},"configWrites":{"type":"boolean"},"cliPath":{"type":"string"},"dbPath":{"type":"string"},"remoteHost":{"type":"string"},"actions":{"type":"object","properties":{"reactions":{"type":"boolean"},"edit":{"type":"boolean"},"unsend":{"type":"boolean"},"reply":{"type":"boolean"},"sendWithEffect":{"type":"boolean"},"renameGroup":{"type":"boolean"},"setGroupIcon":{"type":"boolean"},"addParticipant":{"type":"boolean"},"removeParticipant":{"type":"boolean"},"leaveGroup":{"type":"boolean"},"sendAttachment":{"type":"boolean"}},"additionalProperties":false},"service":{"anyOf":[{"type":"string","const":"imessage"},{"type":"string","const":"sms"},{"type":"string","const":"auto"}]},"region":{"type":"string"},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"defaultTo":{"type":"string"},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"includeAttachments":{"type":"boolean"},"attachmentRoots":{"type":"array","items":{"type":"string"}},"remoteAttachmentRoots":{"type":"array","items":{"type":"string"}},"mediaMaxMb":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"probeTimeoutMs":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"sendReadReceipts":{"type":"boolean"},"reactionNotifications":{"type":"string","enum":["off","own","all"]},"coalesceSameSenderDms":{"type":"boolean"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"catchup":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxAgeMinutes":{"type":"integer","minimum":1,"maximum":720},"perRunLimit":{"type":"integer","minimum":1,"maximum":500},"firstRunLookbackMinutes":{"type":"integer","minimum":1,"maximum":720},"maxFailureRetries":{"type":"integer","minimum":1,"maximum":1000}},"additionalProperties":false},"heartbeat":{"type":"object","properties":{"showOk":{"type":"boolean"},"showAlerts":{"type":"boolean"},"useIndicator":{"type":"boolean"}},"additionalProperties":false},"healthMonitor":{"type":"object","properties":{"enabled":{"type":"boolean"}},"additionalProperties":false},"responsePrefix":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"iMessage","help":"iMessage channel provider configuration for CLI integration and DM access policy handling. Use explicit CLI paths when runtime environments have non-standard binary locations."},"dmPolicy":{"label":"iMessage DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.imessage.allowFrom=[\\"*\\"]."},"configWrites":{"label":"iMessage Config Writes","help":"Allow iMessage to write config in response to channel events/commands (default: true)."},"cliPath":{"label":"iMessage CLI Path","help":"Filesystem path to the iMessage bridge CLI binary used for send/receive operations. Set explicitly when the binary is not on PATH in service runtime environments."}}},{"pluginId":"irc","channelId":"irc","aliases":["internet-relay-chat"],"channelEnvVars":["IRC_CHANNELS","IRC_HOST","IRC_NICK","IRC_NICKSERV_PASSWORD","IRC_NICKSERV_REGISTER_EMAIL","IRC_PASSWORD","IRC_PORT","IRC_REALNAME","IRC_TLS","IRC_USERNAME"],"label":"IRC","description":"classic IRC networks with DM/channel routing and pairing controls.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalPropertie', 's":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"host":{"type":"string"},"port":{"type":"integer","minimum":1,"maximum":65535},"tls":{"type":"boolean"},"nick":{"type":"string"},"username":{"type":"string"},"realname":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"nickserv":{"type":"object","properties":{"enabled":{"type":"boolean"},"service":{"type":"string"},"password":{"type":"string"},"passwordFile":{"type":"string"},"register":{"type":"boolean"},"registerEmail":{"type":"string"}},"additionalProperties":false},"dmPolicy":{"default":"pairing","type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","disabled","allowlist"]},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"requireMention":{"type":"boolean"},"tools":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"toolsBySender":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"allow":{"type":"array","items":{"type":"string"}},"alsoAllow":{"type":"array","items":{"type":"string"}},"deny":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}},"skills":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"systemPrompt":{"type":"string"}},"additionalProperties":false}},"channels":{"type":"array","items":{"type":"string"}},"mentionPatterns":{"type":"array","items":{"type":"string"}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"dmHistoryLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"dms":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},"textChunkLimit":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunkMode":{"type":"string","enum":["length","newline"]},"blockStreaming":{"type":"boolean"},"blockStreamingCoalesce":{"type":"object","properties":{"minChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"idleMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number","exclusiveMinimum":0}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false},"uiHints":{"":{"label":"IRC","help":"IRC channel provider configuration and compatibility settings for classic IRC transport workflows. Use this section when bridging legacy chat infrastructure into OpenClaw."},"dmPolicy":{"label":"IRC DM Policy","help":"Direct message access control (\\"pairing\\" recommended). \\"open\\" requires channels.irc.allowFrom=[\\"*\\"]."},"nickserv.enabled":{"label":"IRC NickServ Enabled","help":"Enable NickServ identify/register after connect (defaults to enabled when password is configured)."},"nickserv.service":{"label":"IRC NickServ Service","help":"NickServ service nick (default: NickServ)."},"nickserv.password":{"label":"IRC NickServ Password","help":"NickServ password used for IDENTIFY/REGISTER (sensitive)."},"nickserv.passwordFile":{"label":"IRC NickServ Password File","help":"Optional file path containing NickServ password."},"nickserv.register":{"label":"IRC NickServ Register","help":"If true, send NickServ REGISTER on every connect. Use once for initial registration, then disable."},"nickserv.registerEmail":{"label":"IRC NickServ Register Email","help":"Email used with NickServ REGISTER (required when register=true)."},"configWrites":{"label":"IRC Config Writes","help":"Allow IRC to write config in response to channel events/commands (default: true)."}}},{"pluginId":"line","channelId":"line","order":75,"channelEnvVars":["LINE_CHANNEL_ACCESS_TOKEN","LINE_CHANNEL_SECRET"],"label":"LINE","description":"LINE Messaging API webhook bot.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"enabled":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["open","allowlist","pairing","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","allowlist","disabled"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"channelAccessToken":{"type":"string"},"channelSecret":{"type":"string"},"tokenFile":{"type":"string"},"secretFile":{"type":"string"},"name":{"type":"string"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dmPolicy":{"default":"pairing","type":"string","enum":["open","allowlist","pairing","disabled"]},"groupPolicy":{"default":"allowlist","type":"string","enum":["open","allowlist","disabled"]},"responsePrefix":{"type":"string"},"mediaMaxMb":{"type":"number"},"webhookPath":{"type":"string"},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number"},"maxAgeHours":{"type":"number"},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"systemPrompt":{"type":"string"},"skills":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},"defaultAccount":{"type":"string"},"groups":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"requireMention":{"type":"boolean"},"systemPrompt":{"type":"string"},"skills":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"required":["dmPolicy","groupPolicy"],"additionalProperties":false}},{"pluginId":"matrix","channelId":"matrix","order":70,"channelEnvVars":["MATRIX_ACCESS_TOKEN","MATRIX_DEVICE_ID","MATRIX_DEVICE_NAME","MATRIX_HOMESERVER","MATRIX_OPS_ACCESS_TOKEN","MATRIX_OPS_DEVICE_ID","MATRIX_OPS_DEVICE_NAME","MATRIX_OPS_HOMESERVER","MATRIX_PASSWORD","MATRIX_USER_ID"],"label":"Matrix","description":"open protocol; install the plugin to enable.","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"defaultAccount":{"type":"string"},"accounts":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"markdown":{"type":"object","properties":{"tables":{"type":"string","enum":["off","bullets","code","block"]}},"additionalProperties":false},"homeserver":{"type":"string"},"network":{"type":"object","properties":{"dangerouslyAllowPrivateNetwork":{"type":"boolean"}},"additionalProperties":false},"proxy":{"type":"string"},"userId":{"type":"string"},"accessToken":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"password":{"anyOf":[{"type":"string"},{"oneOf":[{"type":"object","properties":{"source":{"type":"string","const":"env"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string","pattern":"^[A-Z][A-Z0-9_]{0,127}$"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"file"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false},{"type":"object","properties":{"source":{"type":"string","const":"exec"},"provider":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$"},"id":{"type":"string"}},"required":["source","provider","id"],"additionalProperties":false}]}]},"deviceId":{"type":"string"},"deviceName":{"type":"string"},"avatarUrl":{"type":"string"},"initialSyncLimit":{"type":"number"},"encryption":{"type":"boolean"},"allowlistOnly":{"type":"boolean"},"dangerouslyAllowNameMatching":{"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolean"},{"type":"string","const":"mentions"}]},"botLoopProtection":{"type":"object","properties":{"enabled":{"type":"boolean"},"maxEventsPerWindow":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"windowSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"cooldownSeconds":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"additionalProperties":false},"groupPolicy":{"type":"string","enum":["open","disabled","allowlist"]},"contextVisibility":{"type":"string","enum":["all","allowlist","allowlist_quote"]},"blockStreaming":{"type":"boolean"},"streaming":{"anyOf":[{"type":"string","enum":["partial","quiet","progress","off"]},{"type":"boolean"},{"type":"object","properties":{"mode":{"type":"string","enum":["partial","quiet","progress","off"]},"progress":{"type":"object","properties":{"label":{"anyOf":[{"type":"string"},{"type":"boolean","const":false}]},"labels":{"type":"array","items":{"type":"string"}},"maxLines":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"maxLineChars":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"toolProgress":{"type":"boolean"}},"additionalProperties":false},"preview":{"type":"object","properties":{"toolProgress":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false}]},"replyToMode":{"type":"string","enum":["off","first","all","batched"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]},"textChunkLimit":{"type":"number"},"chunkMode":{"type":"string","enum":["length","newline"]},"responsePrefix":{"type":"string"},"ackReaction":{"type":"string"},"ackReactionScope":{"type":"string","enum":["group-mentions","group-all","direct","all","none","off"]},"reactionNotifications":{"type":"string","enum":["off","own"]},"threadBindings":{"type":"object","properties":{"enabled":{"type":"boolean"},"idleHours":{"type":"number","minimum":0},"maxAgeHours":{"type":"number","minimum":0},"spawnSessions":{"type":"boolean"},"defaultSpawnContext":{"type":"string","enum":["isolated","fork"]},"spawnSubagentSessions":{"type":"boolean"},"spawnAcpSessions":{"type":"boolean"}},"additionalProperties":false},"startupVerification":{"type":"string","enum":["off","if-unverified"]},"startupVerificationCooldownHours":{"type":"number"},"mediaMaxMb":{"type":"number"},"historyLimit":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoJoin":{"type":"string","enum":["always","allowlist","off"]},"autoJoinAllowlist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"groupAllowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"dm":{"type":"object","properties":{"enabled":{"type":"boolean"},"policy":{"type":"string","enum":["pairing","allowlist","open","disabled"]},"allowFrom":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"sessionScope":{"type":"string","enum":["per-user","per-room"]},"threadReplies":{"type":"string","enum":["off","inbound","always"]}},"additionalProperties":false},"execApprovals":{"type":"object","properties":{"enabled":{"type":"boolean"},"approvers":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]}},"agentFilter":{"type":"array","items":{"type":"string"}},"sessionFilter":{"type":"array","items":{"type":"string"}},"target":{"type":"string","enum":["dm","channel","both"]}},"additionalProperties":false},"groups":{"type":"object","properties":{},"additionalProperties":{"type":"object","properties":{"account":{"type":"string"},"enabled":{"type":"boolean"},"requireMention":{"type":"boolean"},"allowBots":{"anyOf":[{"type":"boolea', diff --git a/src/config/config.model-ref-validation.test.ts b/src/config/config.model-ref-validation.test.ts index 3b7a482510ee..a8393b739d69 100644 --- a/src/config/config.model-ref-validation.test.ts +++ b/src/config/config.model-ref-validation.test.ts @@ -10,7 +10,7 @@ function createModelSuppressionRegistry(): PluginManifestRegistry { id: "openai", origin: "bundled", channels: [], - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], contracts: {}, cliBackends: [], skills: [], @@ -21,7 +21,7 @@ function createModelSuppressionRegistry(): PluginManifestRegistry { modelCatalog: { suppressions: [ { - provider: "openai-codex", + provider: "openai", model: "gpt-5.3-codex-spark", reason: "gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.", @@ -40,7 +40,7 @@ describe("config model reference validation", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.3-codex-spark", + primary: "openai/gpt-5.3-codex-spark", }, }, }, @@ -60,18 +60,18 @@ describe("config model reference validation", () => { { path: "agents.defaults.model.primary", message: - "Unknown model: openai-codex/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.", + "Unknown model: openai/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.", }, ]); }); - it("accepts supported openai-codex provider/model pairs", () => { + it("accepts supported openai provider/model pairs", () => { const res = validateConfigObjectWithPlugins( { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.4-mini", + primary: "openai/gpt-5.4-mini", }, }, }, @@ -86,14 +86,14 @@ describe("config model reference validation", () => { expect(res.ok).toBe(true); }); - it("accepts available openai-codex fallback model pairs", () => { + it("accepts available openai fallback model pairs", () => { const res = validateConfigObjectWithPlugins( { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.4-mini", - fallbacks: ["openai-codex/gpt-5.2-codex", "openai-codex/gpt-5.3-codex"], + primary: "openai/gpt-5.4-mini", + fallbacks: ["openai/gpt-5.2-codex", "openai/gpt-5.3-codex"], }, }, }, diff --git a/src/config/config.secrets-schema.test.ts b/src/config/config.secrets-schema.test.ts index a9ea0eb2bc4d..110c9fb1fe69 100644 --- a/src/config/config.secrets-schema.test.ts +++ b/src/config/config.secrets-schema.test.ts @@ -54,13 +54,13 @@ describe("config secret refs schema", () => { expect(result.ok).toBe(true); }); - it("accepts openai-codex-responses as a model api value", () => { + it("accepts openai-chatgpt-responses as a model api value", () => { const result = validateConfigObjectRaw({ models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", models: [{ id: "gpt-5.4", name: "gpt-5.4" }], }, }, diff --git a/src/config/plugin-auto-enable.core.test.ts b/src/config/plugin-auto-enable.core.test.ts index 0968df3c7fa2..eefe6b8cab8d 100644 --- a/src/config/plugin-auto-enable.core.test.ts +++ b/src/config/plugin-auto-enable.core.test.ts @@ -587,7 +587,7 @@ describe("applyPluginAutoEnable core", () => { }, env, manifestRegistry: makeRegistry([ - { id: "openai", channels: [], providers: ["openai", "openai-codex"] }, + { id: "openai", channels: [], providers: ["openai", "openai"] }, { id: "codex", channels: [], @@ -607,13 +607,13 @@ describe("applyPluginAutoEnable core", () => { config: { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, }, }, env, manifestRegistry: makeRegistry([ - { id: "openai", channels: [], providers: ["openai", "openai-codex"] }, + { id: "openai", channels: [], providers: ["openai", "openai"] }, { id: "codex", channels: [], @@ -626,7 +626,7 @@ describe("applyPluginAutoEnable core", () => { expect(result.config.plugins?.entries?.openai?.enabled).toBe(true); expect(result.config.plugins?.entries?.codex?.enabled).toBe(true); expect(result.changes).toEqual([ - "openai-codex/gpt-5.5 model configured, enabled automatically.", + "openai/gpt-5.5 model configured, enabled automatically.", "codex agent runtime configured, enabled automatically.", ]); }); @@ -649,7 +649,7 @@ describe("applyPluginAutoEnable core", () => { }, env, manifestRegistry: makeRegistry([ - { id: "openai", channels: [], providers: ["openai", "openai-codex"] }, + { id: "openai", channels: [], providers: ["openai", "openai"] }, { id: "codex", channels: [], @@ -678,7 +678,7 @@ describe("applyPluginAutoEnable core", () => { }, env, manifestRegistry: makeRegistry([ - { id: "openai", channels: [], providers: ["openai", "openai-codex"] }, + { id: "openai", channels: [], providers: ["openai", "openai"] }, { id: "codex", channels: [], @@ -716,7 +716,7 @@ describe("applyPluginAutoEnable core", () => { }, env, manifestRegistry: makeRegistry([ - { id: "openai", channels: [], providers: ["openai", "openai-codex"] }, + { id: "openai", channels: [], providers: ["openai", "openai"] }, { id: "codex", channels: [], diff --git a/src/config/sessions/sessions.test.ts b/src/config/sessions/sessions.test.ts index 50b680160b60..970c71ac1468 100644 --- a/src/config/sessions/sessions.test.ts +++ b/src/config/sessions/sessions.test.ts @@ -933,14 +933,14 @@ describe("session store writer queue", () => { store[key] = { sessionId: "sess-acp", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.4", }; }); const store = loadSessionStore(storePath); expect(store[key]?.acp).toEqual(acp); - expect(store[key]?.modelProvider).toBe("openai-codex"); + expect(store[key]?.modelProvider).toBe("openai"); expect(store[key]?.model).toBe("gpt-5.4"); }); diff --git a/src/config/types.models.ts b/src/config/types.models.ts index b1823fe94007..307188c91a34 100644 --- a/src/config/types.models.ts +++ b/src/config/types.models.ts @@ -11,7 +11,7 @@ import type { SecretInput } from "./types.secrets.js"; export const MODEL_APIS = [ "openai-completions", "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "anthropic-messages", "google-generative-ai", "google-vertex", diff --git a/src/config/validation.ts b/src/config/validation.ts index 9fdbc3bc287c..cdd3012702da 100644 --- a/src/config/validation.ts +++ b/src/config/validation.ts @@ -50,6 +50,10 @@ import { OpenClawSchema } from "./zod-schema.js"; const LEGACY_REMOVED_PLUGIN_IDS = new Set(["google-antigravity-auth", "google-gemini-cli-auth"]); const BLOCKED_PLUGIN_CANDIDATE_PREFIX = "blocked plugin candidate:"; +const LEGACY_CHATGPT_PROVIDER_ID = ["openai", "codex"].join("-"); +const LEGACY_CHATGPT_RESPONSES_API = `${LEGACY_CHATGPT_PROVIDER_ID}-responses`; +const OPENAI_PROVIDER_ID = "openai"; +const OPENAI_CHATGPT_RESPONSES_API = "openai-chatgpt-responses"; type UnknownIssueRecord = Record; type ConfigPathSegment = string | number; @@ -66,14 +70,79 @@ type AllowedValuesCollection = { }; type JsonSchemaLike = Record; -function stripDeprecatedValidationKeys(raw: unknown): unknown { - if (!isRecord(raw) || !isRecord(raw.commands) || !Object.hasOwn(raw.commands, "modelsWrite")) { +function normalizeLegacyOpenAIProviderForValidation(raw: unknown): unknown { + if (!isRecord(raw) || !isRecord(raw.models) || !isRecord(raw.models.providers)) { + return raw; + } + let providersChanged = false; + const providers = { ...raw.models.providers }; + const normalizeProviderConfig = (providerConfig: unknown): unknown => { + if (!isRecord(providerConfig)) { + return providerConfig; + } + let providerChanged = false; + const nextProvider = { ...providerConfig }; + if (nextProvider.api === LEGACY_CHATGPT_RESPONSES_API) { + nextProvider.api = OPENAI_CHATGPT_RESPONSES_API; + providerChanged = true; + } + if (Array.isArray(nextProvider.models)) { + const nextModels = nextProvider.models.map((model) => { + if (!isRecord(model) || model.api !== LEGACY_CHATGPT_RESPONSES_API) { + return model; + } + providerChanged = true; + return { ...model, api: OPENAI_CHATGPT_RESPONSES_API }; + }); + if (providerChanged) { + nextProvider.models = nextModels; + } + } + return providerChanged ? nextProvider : providerConfig; + }; + + for (const [providerId, providerConfig] of Object.entries(providers)) { + const normalizedProvider = normalizeLowercaseStringOrEmpty(providerId); + const normalizedConfig = normalizeProviderConfig(providerConfig); + if (normalizedProvider !== LEGACY_CHATGPT_PROVIDER_ID) { + if (normalizedConfig !== providerConfig) { + providers[providerId] = normalizedConfig; + providersChanged = true; + } + continue; + } + if (!Object.hasOwn(providers, OPENAI_PROVIDER_ID)) { + providers[OPENAI_PROVIDER_ID] = normalizedConfig; + } + delete providers[providerId]; + providersChanged = true; + } + + if (!providersChanged) { return raw; } - const commands = { ...raw.commands }; - delete commands.modelsWrite; return { ...raw, + models: { + ...raw.models, + providers, + }, + }; +} + +function stripDeprecatedValidationKeys(raw: unknown): unknown { + const normalizedRaw = normalizeLegacyOpenAIProviderForValidation(raw); + if ( + !isRecord(normalizedRaw) || + !isRecord(normalizedRaw.commands) || + !Object.hasOwn(normalizedRaw.commands, "modelsWrite") + ) { + return normalizedRaw; + } + const commands = { ...normalizedRaw.commands }; + delete commands.modelsWrite; + return { + ...normalizedRaw, commands, }; } diff --git a/src/config/zod-schema.core.ts b/src/config/zod-schema.core.ts index 46656e33e0a7..3b618cd91f26 100644 --- a/src/config/zod-schema.core.ts +++ b/src/config/zod-schema.core.ts @@ -439,7 +439,7 @@ const BUILT_IN_MODEL_PROVIDER_OVERLAY_IDS = new Set([ "nvidia", "ollama", "openai", - "openai-codex", + "openai", "opencode", "opencode-go", "openrouter", diff --git a/src/config/zod-schema.models.test.ts b/src/config/zod-schema.models.test.ts index aad35819d928..69a196391945 100644 --- a/src/config/zod-schema.models.test.ts +++ b/src/config/zod-schema.models.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import { validateConfigObjectRaw } from "./validation.js"; import { ModelsConfigSchema } from "./zod-schema.core.js"; describe("ModelsConfigSchema", () => { @@ -22,4 +23,36 @@ describe("ModelsConfigSchema", () => { expect(result.success).toBe(true); }); + + it("canonicalizes legacy OpenAI ChatGPT response config before validation", () => { + const legacyProvider = ["openai", "codex"].join("-"); + const legacyApi = `${legacyProvider}-responses`; + const result = validateConfigObjectRaw({ + models: { + providers: { + [legacyProvider]: { + baseUrl: "https://chatgpt.com/backend-api/codex", + api: legacyApi, + models: [ + { + id: "gpt-5.5", + name: "GPT-5.5", + api: legacyApi, + }, + ], + }, + }, + }, + }); + + expect(result.ok).toBe(true); + if (!result.ok) { + return; + } + expect(result.config.models?.providers?.openai?.api).toBe("openai-chatgpt-responses"); + expect(result.config.models?.providers?.openai?.models?.[0]?.api).toBe( + "openai-chatgpt-responses", + ); + expect(result.config.models?.providers).not.toHaveProperty(legacyProvider); + }); }); diff --git a/src/cron/isolated-agent/run-fallback-policy.test.ts b/src/cron/isolated-agent/run-fallback-policy.test.ts index c049a68ed5ea..18a74f531485 100644 --- a/src/cron/isolated-agent/run-fallback-policy.test.ts +++ b/src/cron/isolated-agent/run-fallback-policy.test.ts @@ -88,7 +88,7 @@ describe("resolveCronFallbacksOverride", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.2", "zai/glm-5"], + fallbacks: ["openai/gpt-5.2", "zai/glm-5"], }, }, }, @@ -101,7 +101,7 @@ describe("resolveCronFallbacksOverride", () => { message: "summarize", }), }), - ).toEqual(["openai-codex/gpt-5.2", "zai/glm-5"]); + ).toEqual(["openai/gpt-5.2", "zai/glm-5"]); }); it("keeps a selected agent primary model strict ahead of default subagent fallbacks", () => { @@ -113,7 +113,7 @@ describe("resolveCronFallbacksOverride", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.2"], + fallbacks: ["openai/gpt-5.2"], }, }, }, @@ -178,7 +178,7 @@ describe("resolveCronFallbacksOverride", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.2"], + fallbacks: ["openai/gpt-5.2"], }, }, }, @@ -233,7 +233,7 @@ describe("resolveCronFallbacksOverride", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.4", "zai/glm-5"], + fallbacks: ["openai/gpt-5.4", "zai/glm-5"], }, }, }, diff --git a/src/cron/isolated-agent/run.meta-error-status.test.ts b/src/cron/isolated-agent/run.meta-error-status.test.ts index 839efde6d3ab..9f495aa9c6ce 100644 --- a/src/cron/isolated-agent/run.meta-error-status.test.ts +++ b/src/cron/isolated-agent/run.meta-error-status.test.ts @@ -71,7 +71,7 @@ describe("runCronIsolatedAgentTurn - meta.error status propagation", () => { abortController.abort("cron: job execution timed out (last phase: model_call_started)"); runWithModelFallbackMock.mockRejectedValueOnce( new Error( - 'All models failed (2): openai-codex/gpt-5.5: Command lane "cron-nested" task timed out after 330000ms (timeout)', + 'All models failed (2): openai/gpt-5.5: Command lane "cron-nested" task timed out after 330000ms (timeout)', ), ); diff --git a/src/cron/isolated-agent/run.payload-fallbacks.test.ts b/src/cron/isolated-agent/run.payload-fallbacks.test.ts index 1bbf23a6acd8..5f5b02bec48e 100644 --- a/src/cron/isolated-agent/run.payload-fallbacks.test.ts +++ b/src/cron/isolated-agent/run.payload-fallbacks.test.ts @@ -160,7 +160,7 @@ describe("runCronIsolatedAgentTurn — payload.fallbacks", () => { subagents: { model: { primary: "kimi/kimi-code", - fallbacks: ["openai-codex/gpt-5.2", "zai/glm-5"], + fallbacks: ["openai/gpt-5.2", "zai/glm-5"], }, }, }, @@ -171,12 +171,12 @@ describe("runCronIsolatedAgentTurn — payload.fallbacks", () => { expect(result.status).toBe("ok"); expect(requireModelFallbackRequest().fallbacksOverride).toEqual([ - "openai-codex/gpt-5.2", + "openai/gpt-5.2", "zai/glm-5", ]); expect(runEmbeddedAgentMock).toHaveBeenCalledOnce(); expect(runEmbeddedAgentMock.mock.calls[0]?.[0]).toMatchObject({ - modelFallbacksOverride: ["openai-codex/gpt-5.2", "zai/glm-5"], + modelFallbacksOverride: ["openai/gpt-5.2", "zai/glm-5"], }); }); }); diff --git a/src/cron/isolated-agent/run.skill-filter.test.ts b/src/cron/isolated-agent/run.skill-filter.test.ts index c5d2255d57e7..15d0ec087358 100644 --- a/src/cron/isolated-agent/run.skill-filter.test.ts +++ b/src/cron/isolated-agent/run.skill-filter.test.ts @@ -205,7 +205,7 @@ describe("runCronIsolatedAgentTurn — skill filter", () => { cfg: { agents: { defaults: { - model: { primary: "openai-codex/gpt-5.4", fallbacks: defaultFallbacks }, + model: { primary: "openai/gpt-5.4", fallbacks: defaultFallbacks }, }, }, }, @@ -257,8 +257,8 @@ describe("runCronIsolatedAgentTurn — skill filter", () => { cfg: { agents: { defaults: { - model: { primary: "openai-codex/gpt-5.4", fallbacks: defaultFallbacks }, - models: { "openai-codex/gpt-5.4": {} }, + model: { primary: "openai/gpt-5.4", fallbacks: defaultFallbacks }, + models: { "openai/gpt-5.4": {} }, }, }, }, @@ -274,7 +274,7 @@ describe("runCronIsolatedAgentTurn — skill filter", () => { expect(result.status).toBe("error"); expect(result.error).toBe( - "cron payload.model 'anthropic/claude-sonnet-4-6' rejected by agents.defaults.models allowlist: anthropic/claude-sonnet-4-6 is not in [openai-codex/gpt-5.4]", + "cron payload.model 'anthropic/claude-sonnet-4-6' rejected by agents.defaults.models allowlist: anthropic/claude-sonnet-4-6 is not in [openai/gpt-5.4]", ); expect(logWarnMock).not.toHaveBeenCalled(); expect(runWithModelFallbackMock).not.toHaveBeenCalled(); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 54ffb0bc64cb..3e525c7467cf 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -1,7 +1,7 @@ import { retireSessionMcpRuntime } from "../../agents/agent-bundle-mcp-tools.js"; import { hasAnyAuthProfileStoreSource } from "../../agents/auth-profiles/source-check.js"; import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js"; -import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-codex-routing.js"; +import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-routing.js"; import { expandToolGroups, normalizeToolName } from "../../agents/tool-policy.js"; import type { ThinkLevel } from "../../auto-reply/thinking.js"; import type { CliDeps } from "../../cli/outbound-send-deps.js"; diff --git a/src/flows/doctor-core-checks.runtime.test.ts b/src/flows/doctor-core-checks.runtime.test.ts index 842cd251b523..c106b8280cd4 100644 --- a/src/flows/doctor-core-checks.runtime.test.ts +++ b/src/flows/doctor-core-checks.runtime.test.ts @@ -6,13 +6,17 @@ const mocks = vi.hoisted(() => ({ createBundleMcpToolRuntime: vi.fn(), createOpenClawCodingTools: vi.fn(), disposeBundleRuntime: vi.fn(), - loadModelCatalog: vi.fn(async () => []), + loadModelCatalog: vi.fn(async (): Promise>> => []), normalizeProviderToolSchemasWithPlugin: vi.fn(), resolveDefaultModelForAgent: vi.fn(() => ({ provider: "openai", model: "gpt-5.5" })), })); vi.mock("../agents/model-catalog.js", () => ({ - findModelInCatalog: () => undefined, + findModelInCatalog: ( + catalog: Array<{ provider?: string; id?: string }>, + provider: string, + modelId: string, + ) => catalog.find((entry) => entry.provider === provider && entry.id === modelId), loadModelCatalog: mocks.loadModelCatalog, })); @@ -101,6 +105,66 @@ describe("doctor runtime tool schema checks", () => { expect(mocks.disposeBundleRuntime).toHaveBeenCalledTimes(1); }); + it("preserves direct OpenAI catalog transport while building doctor runtime models", async () => { + mocks.loadModelCatalog.mockResolvedValueOnce([ + { + provider: "openai", + id: "gpt-5.5", + name: "GPT-5.5", + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", + compat: { supportsTools: true }, + }, + ]); + mocks.createOpenClawCodingTools.mockReturnValueOnce([ + tool("healthy", { type: "object", properties: {} }), + ]); + + await collectRuntimeToolSchemaFindings({}); + + expect(mocks.normalizeProviderToolSchemasWithPlugin).toHaveBeenCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ + modelApi: "openai-responses", + model: expect.objectContaining({ + api: "openai-responses", + baseUrl: "https://api.openai.com/v1", + }), + }), + }), + ); + }); + + it("preserves ChatGPT OpenAI catalog transport while building doctor runtime models", async () => { + mocks.loadModelCatalog.mockResolvedValueOnce([ + { + provider: "openai", + id: "gpt-5.5", + name: "GPT-5.5", + api: "openai-chatgpt-responses", + baseUrl: "https://chatgpt.com/backend-api", + compat: { supportsTools: true }, + }, + ]); + mocks.createOpenClawCodingTools.mockReturnValueOnce([ + tool("healthy", { type: "object", properties: {} }), + ]); + + await collectRuntimeToolSchemaFindings({}); + + expect(mocks.normalizeProviderToolSchemasWithPlugin).toHaveBeenCalledWith( + expect.objectContaining({ + context: expect.objectContaining({ + modelApi: "openai-chatgpt-responses", + model: expect.objectContaining({ + api: "openai-chatgpt-responses", + baseUrl: "https://chatgpt.com/backend-api", + }), + }), + }), + ); + }); + it("reports bundle MCP runtime diagnostics when tool listing fails schema validation", async () => { mocks.createBundleMcpToolRuntime.mockResolvedValueOnce({ tools: [], diff --git a/src/flows/doctor-core-checks.runtime.ts b/src/flows/doctor-core-checks.runtime.ts index 0204fcd1b7ad..d95af1a7ea91 100644 --- a/src/flows/doctor-core-checks.runtime.ts +++ b/src/flows/doctor-core-checks.runtime.ts @@ -56,18 +56,15 @@ function buildDoctorRuntimeModel(params: { }): ProviderRuntimeModel { const provider = params.provider || DEFAULT_PROVIDER; const id = params.modelId || DEFAULT_MODEL; - const api = - provider === "openai-codex" - ? "openai-codex-responses" - : provider === "openai" - ? "openai-responses" - : undefined; + const api = params.entry?.api ?? (provider === "openai" ? "openai-responses" : undefined); + const entryBaseUrl = (params.entry as { baseUrl?: string } | undefined)?.baseUrl; const baseUrl = - provider === "openai-codex" + entryBaseUrl ?? + (api === "openai-chatgpt-responses" ? "https://chatgpt.com/backend-api" : provider === "openai" ? "https://api.openai.com/v1" - : undefined; + : undefined); return { ...params.entry, provider, diff --git a/src/flows/model-picker.ts b/src/flows/model-picker.ts index 1aa307b72514..8a49bdfb14f0 100644 --- a/src/flows/model-picker.ts +++ b/src/flows/model-picker.ts @@ -254,7 +254,7 @@ function resolveModelRouteHint(provider: string): string | undefined { if (normalized === "openai") { return "Codex runtime route"; } - if (normalized === "openai-codex") { + if (normalized === "openai") { return "legacy Codex OAuth route"; } return undefined; diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 62c509a21585..d14feea6b11d 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -611,7 +611,7 @@ function shouldSkipEmptyResponseForLiveModel(params: { params.provider === "google-antigravity" || params.provider === "minimax" || params.provider === "minimax-portal" || - params.provider === "openai-codex" || + params.provider === "openai" || params.provider === "zai" ); } @@ -1418,7 +1418,7 @@ describe("shouldSkipEmptyResponseForLiveModel", () => { { provider: "minimax", allowNotFoundSkip: true, expected: true }, { provider: "minimax-portal", allowNotFoundSkip: true, expected: true }, { provider: "zai", allowNotFoundSkip: true, expected: true }, - { provider: "openai-codex", allowNotFoundSkip: true, expected: true }, + { provider: "openai", allowNotFoundSkip: true, expected: true }, { provider: "xai", allowNotFoundSkip: true, expected: false }, ])( "returns $expected for $provider (allowNotFoundSkip=$allowNotFoundSkip)", @@ -1780,19 +1780,17 @@ describe("sanitizeAuthProfileStoreForLiveGateway", () => { }, codexProfile: { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "access", refresh: "refresh", expires: 1, }, }, order: { - openai: ["openaiProfile"], - "openai-codex": ["codexProfile"], + openai: ["codexProfile", "openaiProfile"], }, lastGood: { - openai: "openaiProfile", - "openai-codex": "codexProfile", + openai: "codexProfile", }, usageStats: { openaiProfile: { lastUsed: 1 }, @@ -3017,7 +3015,7 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) { if ( (model.provider === "openai" && model.api === "openai-responses") || - (model.provider === "openai-codex" && model.api === "openai-codex-responses") + (model.provider === "openai" && model.api === "openai-chatgpt-responses") ) { logProgress(`${progressLabel}: tool-only regression`); const runId2 = randomUUID(); @@ -3201,38 +3199,32 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) { break; } // OpenAI Codex refresh tokens can become single-use; skip instead of failing all live tests. - if (model.provider === "openai-codex" && isRefreshTokenReused(message)) { + if (model.provider === "openai" && isRefreshTokenReused(message)) { skippedCount += 1; logProgress(`${progressLabel}: skip (codex refresh token reused)`); break; } - if (model.provider === "openai-codex" && isAccountIdExtractionError(message)) { + if (model.provider === "openai" && isAccountIdExtractionError(message)) { skippedCount += 1; logProgress(`${progressLabel}: skip (codex account id extraction)`); break; } - if (model.provider === "openai-codex" && isChatGPTUsageLimitErrorMessage(message)) { + if (model.provider === "openai" && isChatGPTUsageLimitErrorMessage(message)) { skippedCount += 1; logProgress(`${progressLabel}: skip (chatgpt usage limit)`); break; } - if (model.provider === "openai-codex" && isInstructionsRequiredError(message)) { + if (model.provider === "openai" && isInstructionsRequiredError(message)) { skippedCount += 1; logProgress(`${progressLabel}: skip (instructions required)`); break; } - if ( - (model.provider === "openai" || model.provider === "openai-codex") && - isOpenAIReasoningSequenceError(message) - ) { + if (model.provider === "openai" && isOpenAIReasoningSequenceError(message)) { skippedCount += 1; logProgress(`${progressLabel}: skip (openai reasoning sequence error)`); break; } - if ( - (model.provider === "openai" || model.provider === "openai-codex") && - isToolNonceRefusal(message) - ) { + if (model.provider === "openai" && isToolNonceRefusal(message)) { skippedCount += 1; logProgress(`${progressLabel}: skip (tool probe refusal)`); break; diff --git a/src/gateway/server-methods/models-auth-status.test.ts b/src/gateway/server-methods/models-auth-status.test.ts index 3295c968073d..036915e1f933 100644 --- a/src/gateway/server-methods/models-auth-status.test.ts +++ b/src/gateway/server-methods/models-auth-status.test.ts @@ -169,14 +169,14 @@ function firstBuildAuthHealthSummaryCall() { function createOpenAiCodexOauthHealthSummary(): AuthHealthSummary { const profile = { - profileId: "openai-codex:default", - provider: "openai-codex", + profileId: "openai:default", + provider: "openai", type: "oauth", status: "ok", expiresAt: 1_000_000, remainingMs: 60_000, source: "store", - label: "openai-codex:default", + label: "openai:default", } satisfies AuthHealthSummary["profiles"][number]; return { now: 0, @@ -184,7 +184,7 @@ function createOpenAiCodexOauthHealthSummary(): AuthHealthSummary { profiles: [profile], providers: [ { - provider: "openai-codex", + provider: "openai", status: "ok", expiresAt: 1_000_000, remainingMs: 60_000, @@ -230,7 +230,7 @@ describe("models.authStatus", () => { expect(error).toBeUndefined(); const result = payload as ModelAuthStatusResult; expect(result.providers).toHaveLength(1); - expect(result.providers[0].provider).toBe("openai-codex"); + expect(result.providers[0].provider).toBe("openai"); expect(result.providers[0].status).toBe("ok"); expect(result.providers[0].expiry?.at).toBe(1_000_000); expect(result.providers[0].profiles[0].type).toBe("oauth"); @@ -371,14 +371,14 @@ describe("models.authStatus", () => { warnAfterMs: 0, profiles: [ { - profileId: "openai-codex:default", - provider: "openai-codex", + profileId: "openai:default", + provider: "openai", type: "oauth", status: "ok", expiresAt: 1, remainingMs: 1, source: "store", - label: "openai-codex:default", + label: "openai:default", // Simulate a future profile shape that includes an access token — // the handler must NOT forward this, since it field-maps explicitly. access: "sk-SECRET-TOKEN", @@ -387,20 +387,20 @@ describe("models.authStatus", () => { ], providers: [ { - provider: "openai-codex", + provider: "openai", status: "ok", expiresAt: 1, remainingMs: 1, profiles: [ { - profileId: "openai-codex:default", - provider: "openai-codex", + profileId: "openai:default", + provider: "openai", type: "oauth", status: "ok", expiresAt: 1, remainingMs: 1, source: "store", - label: "openai-codex:default", + label: "openai:default", access: "sk-SECRET-TOKEN", refresh: "rt-SECRET-REFRESH", } as never, @@ -425,7 +425,7 @@ describe("models.authStatus", () => { mocks.getRuntimeConfig.mockReturnValue({ models: { providers: { - "openai-codex": { auth: "oauth", apiKey: "sk-xxxxx" }, + openai: { auth: "oauth", apiKey: "sk-xxxxx" }, }, }, }); @@ -443,7 +443,7 @@ describe("models.authStatus", () => { mocks.getRuntimeConfig.mockReturnValue({ models: { providers: { - "openai-codex": { + openai: { auth: "oauth", apiKey: { source: "env", @@ -456,7 +456,7 @@ describe("models.authStatus", () => { }); await handler(createOptions()); const call = firstBuildAuthHealthSummaryCall(); - expect(call?.[0]?.providers).toEqual(["openai-codex"]); + expect(call?.[0]?.providers).toEqual(["openai"]); }); it("env SecretRef pointing at a set env var is treated as env-backed", async () => { @@ -464,7 +464,7 @@ describe("models.authStatus", () => { mocks.getRuntimeConfig.mockReturnValue({ models: { providers: { - "openai-codex": { + openai: { auth: "oauth", apiKey: { source: "env", @@ -492,12 +492,12 @@ describe("models.authStatus", () => { mocks.getRuntimeConfig.mockReturnValue({ models: { providers: { - "openai-codex": { auth: "oauth", apiKey: "sk-xxxxx" }, + openai: { auth: "oauth", apiKey: "sk-xxxxx" }, }, }, auth: { profiles: { - "openai-codex:default": { provider: "openai-codex", mode: "oauth" }, + "openai:default": { provider: "openai", mode: "oauth" }, }, }, }); @@ -780,7 +780,7 @@ describe("aggregateOAuthStatus", () => { function oauth(status: "ok" | "expiring" | "expired" | "missing", expiresAt?: number) { return { profileId: `p-${status}`, - provider: "openai-codex", + provider: "openai", type: "oauth" as const, status, expiresAt, @@ -793,7 +793,7 @@ describe("aggregateOAuthStatus", () => { function token(status: "ok" | "expired") { return { profileId: `t-${status}`, - provider: "openai-codex", + provider: "openai", type: "token" as const, status, expiresAt: status === "expired" ? NOW - 1 : undefined, @@ -806,7 +806,7 @@ describe("aggregateOAuthStatus", () => { it("ignores token profiles — healthy OAuth + expired token stays ok", () => { const result = aggregateOAuthStatus( { - provider: "openai-codex", + provider: "openai", status: "expired", profiles: [oauth("ok", expiring + 10_000_000), token("expired")], }, @@ -820,7 +820,7 @@ describe("aggregateOAuthStatus", () => { const stale = oauth("expired", NOW - 1); const result = aggregateOAuthStatus( { - provider: "openai-codex", + provider: "openai", status: "ok", effectiveProfiles: [healthy], profiles: [stale, healthy], @@ -855,7 +855,7 @@ describe("aggregateOAuthStatus", () => { it("expired + missing both map to 'expired'", () => { const expiredResult = aggregateOAuthStatus( { - provider: "openai-codex", + provider: "openai", status: "expired", profiles: [oauth("expired", NOW - 1)], }, @@ -865,7 +865,7 @@ describe("aggregateOAuthStatus", () => { const missingResult = aggregateOAuthStatus( { - provider: "openai-codex", + provider: "openai", status: "missing", profiles: [oauth("missing")], }, @@ -878,7 +878,7 @@ describe("aggregateOAuthStatus", () => { // expiring + ok → expiring (expired-marker absent) const res1 = aggregateOAuthStatus( { - provider: "openai-codex", + provider: "openai", status: "expiring", profiles: [oauth("expiring", expiring), oauth("ok", expiring + 10_000_000)], }, @@ -889,7 +889,7 @@ describe("aggregateOAuthStatus", () => { // expired beats expiring const res2 = aggregateOAuthStatus( { - provider: "openai-codex", + provider: "openai", status: "expired", profiles: [oauth("expired", NOW - 1), oauth("expiring", expiring)], }, @@ -903,7 +903,7 @@ describe("aggregateOAuthStatus", () => { const later = NOW + 99_999; const result = aggregateOAuthStatus( { - provider: "openai-codex", + provider: "openai", status: "ok", profiles: [oauth("ok", later), oauth("ok", earlier)], }, diff --git a/src/gateway/server-methods/models.test.ts b/src/gateway/server-methods/models.test.ts index cd17c865ed52..1d79f8003209 100644 --- a/src/gateway/server-methods/models.test.ts +++ b/src/gateway/server-methods/models.test.ts @@ -169,8 +169,8 @@ describe("models.list", () => { it("loads the full catalog for provider-scoped configured view and filters only providers", async () => { const catalog = [ { id: "claude-test", name: "Claude Test", provider: "anthropic" }, - { id: "gpt-5.4-codex", name: "GPT-5.4 Codex", provider: "openai-codex" }, - { id: "gpt-codex-test", name: "GPT Codex Test", provider: "openai-codex" }, + { id: "gpt-5.4-codex", name: "GPT-5.4 Codex", provider: "openai" }, + { id: "gpt-codex-test", name: "GPT Codex Test", provider: "openai" }, { id: "llama-local", name: "Llama Local", provider: "vllm" }, { id: "qwen-local", name: "Qwen Local", provider: "vllm" }, ]; @@ -178,14 +178,14 @@ describe("models.list", () => { agents: { defaults: { models: { - "openai-codex/*": {}, + "openai/*": {}, "vllm/*": {}, }, }, }, models: { providers: { - "openai-codex": { apiKey: "test-key" }, + openai: { apiKey: "test-key" }, vllm: { apiKey: "test-key" }, }, }, @@ -217,8 +217,8 @@ describe("models.list", () => { true, { models: [ - { id: "gpt-5.4-codex", name: "GPT-5.4 Codex", provider: "openai-codex" }, - { id: "gpt-codex-test", name: "GPT Codex Test", provider: "openai-codex" }, + { id: "gpt-5.4-codex", name: "GPT-5.4 Codex", provider: "openai" }, + { id: "gpt-codex-test", name: "GPT Codex Test", provider: "openai" }, { id: "llama-local", name: "Llama Local", provider: "vllm" }, { id: "qwen-local", name: "Qwen Local", provider: "vllm" }, ], diff --git a/src/gateway/server-methods/server-methods.test.ts b/src/gateway/server-methods/server-methods.test.ts index 1d6197e04199..6a67aaacc71d 100644 --- a/src/gateway/server-methods/server-methods.test.ts +++ b/src/gateway/server-methods/server-methods.test.ts @@ -787,8 +787,8 @@ describe("sanitizeChatHistoryMessages", () => { openclawReasoningReplay: { v: 1, source: "openai-responses", - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", model: "gpt-5.5", }, }, diff --git a/src/gateway/server-startup-config.recovery.test.ts b/src/gateway/server-startup-config.recovery.test.ts index 2f39f1be0641..a59618b03545 100644 --- a/src/gateway/server-startup-config.recovery.test.ts +++ b/src/gateway/server-startup-config.recovery.test.ts @@ -732,12 +732,12 @@ describe("gateway startup config validation", () => { { path: "models.providers.openrouter.api", message: - 'Invalid option: expected one of "openai-completions"|"openai-responses"|"openai-codex-responses"|"anthropic-messages"|"google-generative-ai"|"github-copilot"|"bedrock-converse-stream"|"ollama"|"azure-openai-responses"', + 'Invalid option: expected one of "openai-completions"|"openai-responses"|"openai-chatgpt-responses"|"anthropic-messages"|"google-generative-ai"|"github-copilot"|"bedrock-converse-stream"|"ollama"|"azure-openai-responses"', }, { path: "models.providers.openrouter.models.0.api", message: - 'Invalid option: expected one of "openai-completions"|"openai-responses"|"openai-codex-responses"|"anthropic-messages"|"google-generative-ai"|"github-copilot"|"bedrock-converse-stream"|"ollama"|"azure-openai-responses"', + 'Invalid option: expected one of "openai-completions"|"openai-responses"|"openai-chatgpt-responses"|"anthropic-messages"|"google-generative-ai"|"github-copilot"|"bedrock-converse-stream"|"ollama"|"azure-openai-responses"', }, ], legacyIssues: [], diff --git a/src/gateway/server-startup-log.test.ts b/src/gateway/server-startup-log.test.ts index 8a596bac0e0d..f5b2a0502b1b 100644 --- a/src/gateway/server-startup-log.test.ts +++ b/src/gateway/server-startup-log.test.ts @@ -57,9 +57,9 @@ describe("gateway startup log", () => { cfg: { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", models: { - "openai-codex/gpt-5.5": { + "openai/gpt-5.5": { params: { fastMode: true, thinking: "medium", @@ -78,9 +78,9 @@ describe("gateway startup log", () => { }); const firstInfoCall = info.mock.calls[0]; - expect(firstInfoCall?.[0]).toBe("agent model: openai-codex/gpt-5.5 (thinking=medium, fast=on)"); + expect(firstInfoCall?.[0]).toBe("agent model: openai/gpt-5.5 (thinking=medium, fast=on)"); expect(stripAnsi(String(firstInfoCall?.[1]?.consoleMessage))).toBe( - "agent model: openai-codex/gpt-5.5 (thinking=medium, fast=on)", + "agent model: openai/gpt-5.5 (thinking=medium, fast=on)", ); }); @@ -90,12 +90,12 @@ describe("gateway startup log", () => { cfg: { agents: { defaults: { - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", }, list: [{ id: "main", default: true, fastModeDefault: true }], }, }, - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", }), ).toBe("thinking=medium, fast=on"); @@ -108,12 +108,12 @@ describe("gateway startup log", () => { agents: { defaults: { models: { - "openai-codex/gpt-5.5": { params: { thinking: "off", fastMode: true } }, + "openai/gpt-5.5": { params: { thinking: "off", fastMode: true } }, }, }, }, }, - provider: "openai-codex", + provider: "openai", model: "gpt-5.5", }), ).toBe("thinking=off, fast=on"); diff --git a/src/gateway/server-startup.test.ts b/src/gateway/server-startup.test.ts index 1bc57923df14..82d9a1068827 100644 --- a/src/gateway/server-startup.test.ts +++ b/src/gateway/server-startup.test.ts @@ -38,7 +38,7 @@ function expectModelsJsonPrewarmCall(cfg: OpenClawConfig) { expect(agentDir).toBe("/tmp/agent"); expect(options).toEqual({ workspaceDir: "/tmp/workspace", - providerDiscoveryProviderIds: ["openai-codex"], + providerDiscoveryProviderIds: ["openai"], providerDiscoveryTimeoutMs: 5000, providerDiscoveryEntriesOnly: true, }); @@ -61,7 +61,7 @@ describe("gateway startup primary model warmup", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.4", + primary: "openai/gpt-5.4", }, }, }, diff --git a/src/gateway/server.sessions.list-changed.test.ts b/src/gateway/server.sessions.list-changed.test.ts index 0c2f00b0011c..92cbd1ddb6fe 100644 --- a/src/gateway/server.sessions.list-changed.test.ts +++ b/src/gateway/server.sessions.list-changed.test.ts @@ -407,7 +407,7 @@ test("sessions.changed mutation events include live usage metadata", async () => id: "msg-usage-zero", message: { role: "assistant", - provider: "openai-codex", + provider: "openai", model: "gpt-5.3-codex-spark", usage: { input: 5_107, @@ -425,7 +425,7 @@ test("sessions.changed mutation events include live usage metadata", async () => await writeSessionStore({ entries: { main: sessionStoreEntry("sess-main", { - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.3-codex-spark", contextTokens: 123_456, totalTokens: 0, @@ -464,7 +464,7 @@ test("sessions.changed mutation events include live usage metadata", async () => totalTokensFresh: true, contextTokens: 123_456, estimatedCostUsd: 0, - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.3-codex-spark", }); }); diff --git a/src/gateway/session-utils.search.test.ts b/src/gateway/session-utils.search.test.ts index 927db8c68f8f..0c3d44c0ef68 100644 --- a/src/gateway/session-utils.search.test.ts +++ b/src/gateway/session-utils.search.test.ts @@ -414,7 +414,7 @@ describe("listSessionsFromStore search", () => { agents: { list: [{ id: "main", default: true }] }, models: { providers: { - "openai-codex": { + openai: { models: [ { id: "gpt-5.3-codex-spark", @@ -434,7 +434,7 @@ describe("listSessionsFromStore search", () => { "agent:main:main": { sessionId: "sess-main", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.3-codex-spark", inputTokens: 5_107, outputTokens: 1_827, @@ -452,7 +452,7 @@ describe("listSessionsFromStore search", () => { withTranscriptStoreFixture({ prefix: "openclaw-session-utils-zero-cost-", transcriptId: "sess-main", - provider: "openai-codex", + provider: "openai", model: "gpt-5.3-codex-spark", input: 5_107, output: 1_827, @@ -466,7 +466,7 @@ describe("listSessionsFromStore search", () => { entry: { sessionId: "sess-main", updatedAt: now, - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.3-codex-spark", totalTokens: 0, totalTokensFresh: false, diff --git a/src/gateway/session-utils.test.ts b/src/gateway/session-utils.test.ts index eca848075971..ae7e84768fd7 100644 --- a/src/gateway/session-utils.test.ts +++ b/src/gateway/session-utils.test.ts @@ -242,7 +242,7 @@ describe("gateway session utils", () => { pluginId: "test", source: "test", provider: { - id: "openai-codex", + id: "openai", label: "OpenAI Codex", auth: [], resolveThinkingProfile: ({ modelId }) => ({ @@ -262,12 +262,10 @@ describe("gateway session utils", () => { }); setActivePluginRegistry(registry); - const defaults = getSessionDefaults( - createModelDefaultsConfig({ primary: "openai-codex/gpt-5.5" }), - ); + const defaults = getSessionDefaults(createModelDefaultsConfig({ primary: "openai/gpt-5.5" })); expectFields(defaults, { - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", thinkingDefault: "adaptive", }); @@ -402,7 +400,7 @@ describe("gateway session utils", () => { pluginId: "test", source: "test", provider: { - id: "openai-codex", + id: "openai", label: "OpenAI Codex", auth: [], resolveThinkingProfile, @@ -410,13 +408,13 @@ describe("gateway session utils", () => { }); setActivePluginRegistry(registry); - const cfg = createModelDefaultsConfig({ primary: "openai-codex/gpt-5.5" }); + const cfg = createModelDefaultsConfig({ primary: "openai/gpt-5.5" }); const store = Object.fromEntries( Array.from({ length: 5 }, (_value, index) => [ `session-${index}`, { sessionId: `session-${index}`, - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", updatedAt: Date.now() - index, } satisfies SessionEntry, @@ -1374,7 +1372,7 @@ describe("gateway session utils", () => { workspace: "/tmp/default-workspace", model: { primary: "openai/gpt-5.4", - fallbacks: ["openai-codex/gpt-5.4"], + fallbacks: ["openai/gpt-5.4"], }, }, list: [{ id: "main", default: true }], @@ -1388,7 +1386,7 @@ describe("gateway session utils", () => { }); expect(result.agents[0]?.model).toEqual({ primary: "openai/gpt-5.4", - fallbacks: ["openai-codex/gpt-5.4"], + fallbacks: ["openai/gpt-5.4"], }); expect(result.agents[0]?.agentRuntime).toEqual({ id: "codex", @@ -1433,7 +1431,7 @@ describe("gateway session utils", () => { defaults: { model: { primary: "openai/gpt-5.4", - fallbacks: ["openai-codex/gpt-5.4"], + fallbacks: ["openai/gpt-5.4"], }, }, list: [ @@ -1464,7 +1462,7 @@ describe("resolveSessionModelRef", () => { const resolved = resolveSessionModelRef(cfg, { sessionId: "s1", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.4", modelOverride: "claude-opus-4-6", providerOverride: "anthropic", @@ -1499,10 +1497,10 @@ describe("resolveSessionModelRef", () => { const resolved = resolveSessionModelRef(cfg, { sessionId: "s2", updatedAt: Date.now(), - modelOverride: "openai-codex/gpt-5.4", + modelOverride: "openai/gpt-5.4", }); - expect(resolved).toEqual({ provider: "openai-codex", model: "gpt-5.4" }); + expect(resolved).toEqual({ provider: "openai", model: "gpt-5.4" }); }); test("keeps nested model ids under the stored provider override", () => { @@ -1548,11 +1546,11 @@ describe("resolveSessionModelRef", () => { const resolved = resolveSessionModelRef(cfg, { sessionId: "s-qualified-override", updatedAt: Date.now(), - providerOverride: "openai-codex", - modelOverride: "openai-codex/gpt-5.4", + providerOverride: "openai", + modelOverride: "openai/gpt-5.4", }); - expect(resolved).toEqual({ provider: "openai-codex", model: "gpt-5.4" }); + expect(resolved).toEqual({ provider: "openai", model: "gpt-5.4" }); }); test("falls back to resolved provider for unprefixed legacy runtime model", () => { @@ -1768,7 +1766,7 @@ describe("listSessionsFromStore selected model display", () => { updatedAt: Date.now(), providerOverride: "anthropic", modelOverride: "claude-opus-4-6", - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.4", } as SessionEntry, }, @@ -1898,14 +1896,14 @@ describe("listSessionsFromStore selected model display", () => { "agent:main:main": { sessionId: "sess-main", updatedAt: Date.now(), - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", } as SessionEntry, }, opts: {}, }); - expect(result.sessions[0]?.modelProvider).toBe("openai-codex"); + expect(result.sessions[0]?.modelProvider).toBe("openai"); expect(result.sessions[0]?.model).toBe("gpt-5.5"); }); diff --git a/src/gateway/sessions-patch.test.ts b/src/gateway/sessions-patch.test.ts index aed7d8b7c51c..a1f28b05db40 100644 --- a/src/gateway/sessions-patch.test.ts +++ b/src/gateway/sessions-patch.test.ts @@ -622,7 +622,7 @@ describe("gateway sessions patch", () => { }, { id: "work", - model: { primary: "openai-codex/gpt-5.5" }, + model: { primary: "openai/gpt-5.5" }, }, ], }, @@ -903,7 +903,7 @@ describe("gateway sessions patch", () => { cfg: createAllowlistedAnthropicModelCfg(), patch: { key: MAIN_SESSION_KEY, - model: "anthropic/claude-sonnet-4-6@openai-codex:user@example.com", + model: "anthropic/claude-sonnet-4-6@openai:user@example.com", }, loadGatewayModelCatalog: async () => [ { provider: "anthropic", id: "claude-sonnet-4-6", name: "claude-sonnet-4-6" }, @@ -912,7 +912,7 @@ describe("gateway sessions patch", () => { ); expect(entry.providerOverride).toBe("anthropic"); expect(entry.modelOverride).toBe("claude-sonnet-4-6"); - expect(entry.authProfileOverride).toBe("openai-codex:user@example.com"); + expect(entry.authProfileOverride).toBe("openai:user@example.com"); expect(entry.authProfileOverrideSource).toBe("user"); }); diff --git a/src/hooks/llm-slug-generator.test.ts b/src/hooks/llm-slug-generator.test.ts index ef8d318e12b0..4c6254b28fff 100644 --- a/src/hooks/llm-slug-generator.test.ts +++ b/src/hooks/llm-slug-generator.test.ts @@ -81,7 +81,7 @@ describe("generateSlugViaLLM", () => { }, models: { providers: { - "openai-codex": { + openai: { baseUrl: "https://chatgpt.com/backend-api/codex", models: [ { @@ -102,7 +102,7 @@ describe("generateSlugViaLLM", () => { expect(runEmbeddedAgentMock).toHaveBeenCalledOnce(); const options = requireFirstRunOptions(); - expect(options.provider).toBe("openai-codex"); + expect(options.provider).toBe("openai"); expect(options.model).toBe("gpt-5.5"); }); }); diff --git a/src/infra/errors.test.ts b/src/infra/errors.test.ts index e2b6e856c9a8..24ad7af0c0ce 100644 --- a/src/infra/errors.test.ts +++ b/src/infra/errors.test.ts @@ -90,7 +90,7 @@ describe("error helpers", () => { it("dedupes repeated cause messages while preserving deeper distinct causes", () => { const rootCause = new Error("provider auth lookup failed"); - const inner = new Error('No API key found for provider "openai-codex".', { cause: rootCause }); + const inner = new Error('No API key found for provider "openai".', { cause: rootCause }); const wrapper = new Error(inner.message, { cause: inner }); expect(formatErrorMessage(wrapper)).toBe(`${inner.message} | ${rootCause.message}`); }); diff --git a/src/infra/heartbeat-runner.response-prefix-template.test.ts b/src/infra/heartbeat-runner.response-prefix-template.test.ts index ecde0e248a4c..5b482d854d9d 100644 --- a/src/infra/heartbeat-runner.response-prefix-template.test.ts +++ b/src/infra/heartbeat-runner.response-prefix-template.test.ts @@ -74,7 +74,7 @@ describe("runHeartbeatOnce responsePrefix templates", () => { replySpy.mockImplementation(async (_ctx, opts) => { opts?.onModelSelected?.({ - provider: "openai-codex", + provider: "openai", model: "gpt-5.4-20260401", thinkLevel: "high", }); @@ -103,7 +103,7 @@ describe("runHeartbeatOnce responsePrefix templates", () => { expect(sendTelegram).toHaveBeenCalledTimes(1); const [target, message, options] = requireFirstMockCall(sendTelegram, "telegram send"); expect(target).toBe(TELEGRAM_GROUP); - expect(message).toBe("[openai-codex/gpt-5.4|think:high] Heartbeat alert"); + expect(message).toBe("[openai/gpt-5.4|think:high] Heartbeat alert"); expect(typeof options).toBe("object"); }); diff --git a/src/infra/provider-usage.auth.normalizes-keys.test.ts b/src/infra/provider-usage.auth.normalizes-keys.test.ts index fc02d488cba0..97bc0fca6457 100644 --- a/src/infra/provider-usage.auth.normalizes-keys.test.ts +++ b/src/infra/provider-usage.auth.normalizes-keys.test.ts @@ -260,6 +260,7 @@ describe("resolveProviderAuths key normalization", () => { MINIMAX_API_KEY: undefined, MINIMAX_CODE_PLAN_KEY: undefined, MINIMAX_CODING_API_KEY: undefined, + OPENAI_API_KEY: undefined, XIAOMI_API_KEY: undefined, } satisfies Record; @@ -600,6 +601,50 @@ describe("resolveProviderAuths key normalization", () => { }); }); + it("does not use OpenAI api keys for ChatGPT usage auth", async () => { + const config = { + models: { + providers: { + openai: { + baseUrl: "https://api.openai.com/v1", + models: [createTestModelDefinition()], + apiKey: "cfg-openai-key", // pragma: allowlist secret + }, + }, + }, + } satisfies OpenClawConfig; + await expectResolvedAuthsFromSuiteHome({ + providers: ["openai"], + env: { + OPENAI_API_KEY: "env-openai-key", + }, + setup: async (home) => { + await writeConfig(home, config); + await writeAuthProfiles(home, { + "openai:default": { type: "api_key", provider: "openai", key: "profile-openai-key" }, + }); + }, + config, + expected: [], + }); + }); + + it("uses OpenAI oauth-compatible profiles for ChatGPT usage auth", async () => { + await expectResolvedAuthsFromSuiteHome({ + providers: ["openai"], + setup: async (home) => { + await writeAuthProfiles(home, { + "openai:default": { + type: "token", + provider: "openai", + token: "chatgpt-token", + }, + }); + }, + expected: [{ provider: "openai", token: "chatgpt-token" }], + }); + }); + it("discovers oauth provider from config but skips mismatched profile providers", async () => { await withSuiteHome(async (home) => { const config = { diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index 7aae11a430bc..380fd6a1e2e8 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -23,6 +23,7 @@ import { resolveProviderUsageAuthWithPlugin } from "../plugins/provider-runtime. import { resolveProviderAuthEnvVarCandidates } from "../secrets/provider-env-vars.js"; import { normalizeUniqueStringEntries } from "../shared/string-normalization.js"; import { normalizeSecretInput } from "../utils/normalize-secret-input.js"; +import { isOAuthOnlyUsageProvider } from "./provider-usage.shared.js"; import type { UsageProviderId } from "./provider-usage.types.js"; export type ProviderAuth = { @@ -268,11 +269,13 @@ async function resolveProviderUsageAuthViaPlugin(params: { env: params.state.env, provider: params.provider, resolveApiKeyFromConfigAndStore: (options) => - resolveProviderApiKeyFromConfigAndStore({ - state: params.state, - providerIds: options?.providerIds ?? [params.provider], - envDirect: options?.envDirect, - }), + isOAuthOnlyUsageProvider(params.provider) + ? undefined + : resolveProviderApiKeyFromConfigAndStore({ + state: params.state, + providerIds: options?.providerIds ?? [params.provider], + envDirect: options?.envDirect, + }), resolveOAuthToken: async (options) => { const auth = await resolveOAuthToken({ state: params.state, @@ -308,6 +311,9 @@ async function resolveProviderUsageAuthFallback(params: { if (oauthToken) { return oauthToken; } + if (isOAuthOnlyUsageProvider(params.provider)) { + return null; + } const apiKey = resolveProviderApiKeyFromConfigAndStore({ state: params.state, @@ -326,10 +332,12 @@ async function resolveProviderUsageAuthFallback(params: { function hasAuthProfileCredentialSource(params: { state: UsageAuthState; providerIds: string[]; + usageProvider: UsageProviderId; }): boolean { const store = ensureAuthProfileStoreWithoutExternalProfiles(params.state.agentDir, { allowKeychainPrompt: false, }); + const allowApiKey = !isOAuthOnlyUsageProvider(params.usageProvider); for (const provider of params.providerIds) { const order = resolveAuthProfileOrder({ cfg: params.state.cfg, @@ -339,7 +347,11 @@ function hasAuthProfileCredentialSource(params: { if ( dedupeProfileIds(order).some((profileId) => { const cred = store.profiles[profileId]; - return cred?.type === "api_key" || cred?.type === "oauth" || cred?.type === "token"; + return ( + cred?.type === "oauth" || + cred?.type === "token" || + (allowApiKey && cred?.type === "api_key") + ); }) ) { return true; @@ -400,22 +412,24 @@ export async function resolveProviderAuths(params: { provider, }); const hasDirectCredentialSource = - Boolean( + !isOAuthOnlyUsageProvider(provider) && + (Boolean( resolveProviderApiKeyFromConfig({ state: directCredentialState, providerIds: credentialProviderIds, }), ) || - hasProviderAuthEnvCredentialSource({ - state: directCredentialState, - providerIds: credentialProviderIds, - }); + hasProviderAuthEnvCredentialSource({ + state: directCredentialState, + providerIds: credentialProviderIds, + })); const allowAuthProfileStore = hasDirectCredentialSource || (hasAuthProfileStoreSource && hasAuthProfileCredentialSource({ state: authProfileSourceState, providerIds: credentialProviderIds, + usageProvider: provider, })); const state: UsageAuthState = { ...stateBase, diff --git a/src/infra/provider-usage.fetch.codex.test.ts b/src/infra/provider-usage.fetch.codex.test.ts index cd3d9dc5df42..eb055c70032d 100644 --- a/src/infra/provider-usage.fetch.codex.test.ts +++ b/src/infra/provider-usage.fetch.codex.test.ts @@ -58,7 +58,7 @@ describe("fetchCodexUsage", () => { const result = await fetchCodexUsage("token", "acct-1", 5000, mockFetch); - expect(result.provider).toBe("openai-codex"); + expect(result.provider).toBe("openai"); expect(result.plan).toBe("Plus ($12.50)"); expect(result.windows).toEqual([ { label: "3h", usedPercent: 35.5, resetAt: 1_700_000_000_000 }, diff --git a/src/infra/provider-usage.fetch.codex.ts b/src/infra/provider-usage.fetch.codex.ts index 63702ef262b9..f7cebd3341b3 100644 --- a/src/infra/provider-usage.fetch.codex.ts +++ b/src/infra/provider-usage.fetch.codex.ts @@ -59,16 +59,20 @@ export async function fetchCodexUsage( timeoutMs: number, fetchFn: typeof fetch, ): Promise { + const version = process.env.OPENCLAW_VERSION?.trim(); const defaultHeaders: Record = { Authorization: `Bearer ${token}`, Accept: "application/json", + originator: "openclaw", + ...(version ? { version } : {}), + "User-Agent": `openclaw/${version || "dev"}`, }; if (accountId) { defaultHeaders["ChatGPT-Account-Id"] = accountId; } const headers = resolveProviderRequestHeaders({ - provider: "openai-codex", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api/wham/usage", capability: "other", transport: "http", @@ -84,13 +88,13 @@ export async function fetchCodexUsage( if (!res.ok) { return buildUsageHttpErrorSnapshot({ - provider: "openai-codex", + provider: "openai", status: res.status, tokenExpiredStatuses: [401, 403], }); } - const parsed = await readUsageJson("openai-codex", res); + const parsed = await readUsageJson("openai", res); if (!parsed.ok) { return parsed.snapshot; } @@ -132,8 +136,8 @@ export async function fetchCodexUsage( } return { - provider: "openai-codex", - displayName: PROVIDER_LABELS["openai-codex"], + provider: "openai", + displayName: PROVIDER_LABELS.openai, windows, plan, }; diff --git a/src/infra/provider-usage.fetch.gemini.test.ts b/src/infra/provider-usage.fetch.gemini.test.ts index 5d65e1d313c4..864dff08c662 100644 --- a/src/infra/provider-usage.fetch.gemini.test.ts +++ b/src/infra/provider-usage.fetch.gemini.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { createProviderUsageFetch, makeResponse } from "../test-utils/provider-usage-fetch.js"; import { fetchGeminiUsage } from "./provider-usage.fetch.gemini.js"; -const usageProvider = "openai-codex" as const; +const usageProvider = "openai" as const; describe("fetchGeminiUsage", () => { it("returns HTTP errors for failed requests", async () => { @@ -58,7 +58,7 @@ describe("fetchGeminiUsage", () => { expect(result).toEqual({ provider: usageProvider, - displayName: "Codex", + displayName: "OpenAI", windows: [], }); }); diff --git a/src/infra/provider-usage.fetch.shared.test.ts b/src/infra/provider-usage.fetch.shared.test.ts index c74624fac46c..ca5eaf3b0d0a 100644 --- a/src/infra/provider-usage.fetch.shared.test.ts +++ b/src/infra/provider-usage.fetch.shared.test.ts @@ -108,13 +108,13 @@ describe("provider usage fetch shared helpers", () => { it("maps configured status codes to token expired", () => { const snapshot = buildUsageHttpErrorSnapshot({ - provider: "openai-codex", + provider: "openai", status: 401, tokenExpiredStatuses: [401, 403], }); expect(snapshot.error).toBe("Token expired"); - expect(snapshot.provider).toBe("openai-codex"); + expect(snapshot.provider).toBe("openai"); expect(snapshot.windows).toHaveLength(0); }); diff --git a/src/infra/provider-usage.format.test.ts b/src/infra/provider-usage.format.test.ts index 6659bfd1b55d..fd8a6fae9f26 100644 --- a/src/infra/provider-usage.format.test.ts +++ b/src/infra/provider-usage.format.test.ts @@ -127,7 +127,7 @@ describe("provider-usage.format", () => { updatedAt: now, providers: [ { - provider: "openai-codex", + provider: "openai", displayName: "Codex", windows: [], error: "Token expired", diff --git a/src/infra/provider-usage.load.test.ts b/src/infra/provider-usage.load.test.ts index f397043c046a..1f1fb89ae385 100644 --- a/src/infra/provider-usage.load.test.ts +++ b/src/infra/provider-usage.load.test.ts @@ -39,7 +39,7 @@ describe("provider-usage.load", () => { displayName: "Gemini CLI", windows: [{ label: "Pro", usedPercent: 40 }], }; - case "openai-codex": + case "openai": return { provider, displayName: "Codex", @@ -71,7 +71,7 @@ describe("provider-usage.load", () => { [ { provider: "github-copilot", token: "copilot-token" }, { provider: googleGeminiCliProvider, token: "gemini-token" }, - { provider: "openai-codex", token: "codex-token", accountId: "acc-1" }, + { provider: "openai", token: "codex-token", accountId: "acc-1" }, { provider: "xiaomi", token: "xiaomi-token" }, { provider: "xiaomi-token-plan", token: "xiaomi-token-plan-token" }, ], @@ -81,7 +81,7 @@ describe("provider-usage.load", () => { expect(summary.providers.map((provider) => provider.provider)).toEqual([ "github-copilot", googleGeminiCliProvider, - "openai-codex", + "openai", "xiaomi", "xiaomi-token-plan", ]); @@ -93,7 +93,7 @@ describe("provider-usage.load", () => { ?.windows[0]?.label, ).toBe("Pro"); expect( - summary.providers.find((provider) => provider.provider === "openai-codex")?.windows[0]?.label, + summary.providers.find((provider) => provider.provider === "openai")?.windows[0]?.label, ).toBe("3h"); expect(summary.providers.find((provider) => provider.provider === "xiaomi")?.windows).toEqual( [], @@ -166,7 +166,7 @@ describe("provider-usage.load", () => { loadProviderUsageSummary, [ { provider: "anthropic", token: "token-a" }, - { provider: "openai-codex", token: "token-codex" }, + { provider: "openai", token: "token-codex" }, ], mockFetch, ); @@ -179,7 +179,7 @@ describe("provider-usage.load", () => { error: "fetch failed", }, { - provider: "openai-codex", + provider: "openai", displayName: "Codex", windows: [{ label: "3h", usedPercent: 12 }], }, diff --git a/src/infra/provider-usage.shared.test.ts b/src/infra/provider-usage.shared.test.ts index 4b6279cc54d5..f0b75867415e 100644 --- a/src/infra/provider-usage.shared.test.ts +++ b/src/infra/provider-usage.shared.test.ts @@ -23,8 +23,8 @@ describe("provider-usage.shared", () => { }); it("maps canonical OpenAI subscription profiles to Codex usage windows", () => { - expect(resolveUsageProviderId("openai", { credentialType: "oauth" })).toBe("openai-codex"); - expect(resolveUsageProviderId("openai", { credentialType: "token" })).toBe("openai-codex"); + expect(resolveUsageProviderId("openai", { credentialType: "oauth" })).toBe("openai"); + expect(resolveUsageProviderId("openai", { credentialType: "token" })).toBe("openai"); expect(resolveUsageProviderId("openai", { credentialType: "api_key" })).toBeUndefined(); }); diff --git a/src/infra/provider-usage.shared.ts b/src/infra/provider-usage.shared.ts index 0b8ce8633920..c4c04f80049b 100644 --- a/src/infra/provider-usage.shared.ts +++ b/src/infra/provider-usage.shared.ts @@ -8,7 +8,7 @@ export const PROVIDER_LABELS: Record = { "github-copilot": "Copilot", "google-gemini-cli": "Gemini", minimax: "MiniMax", - "openai-codex": "Codex", + openai: "OpenAI", xiaomi: "Xiaomi", "xiaomi-token-plan": "Xiaomi Token Plan", zai: "z.ai", @@ -19,12 +19,16 @@ export const usageProviders: UsageProviderId[] = [ "github-copilot", "google-gemini-cli", "minimax", - "openai-codex", + "openai", "xiaomi", "xiaomi-token-plan", "zai", ]; +export function isOAuthOnlyUsageProvider(provider: UsageProviderId): boolean { + return provider === "openai"; +} + export function resolveUsageProviderId( provider?: string | null, options?: { credentialType?: string | null }, @@ -37,7 +41,10 @@ export function resolveUsageProviderId( normalized === "openai" && (options?.credentialType === "oauth" || options?.credentialType === "token") ) { - return "openai-codex"; + return "openai"; + } + if (normalized === "openai") { + return undefined; } if ( normalized === "minimax-portal" || diff --git a/src/infra/provider-usage.test.ts b/src/infra/provider-usage.test.ts index 143f996c952e..a849ed08b9ff 100644 --- a/src/infra/provider-usage.test.ts +++ b/src/infra/provider-usage.test.ts @@ -50,7 +50,7 @@ describe("provider usage formatting", () => { updatedAt: 0, providers: [ { - provider: "openai-codex", + provider: "openai", displayName: "Codex", windows: [], error: "Token expired", diff --git a/src/infra/provider-usage.types.ts b/src/infra/provider-usage.types.ts index 6b279061a587..7bd1923b2875 100644 --- a/src/infra/provider-usage.types.ts +++ b/src/infra/provider-usage.types.ts @@ -22,7 +22,7 @@ export type UsageProviderId = | "github-copilot" | "google-gemini-cli" | "minimax" - | "openai-codex" + | "openai" | "xiaomi" | "xiaomi-token-plan" | "zai"; diff --git a/src/llm/providers/azure-openai-responses.ts b/src/llm/providers/azure-openai-responses.ts index e3cc05793d86..58f1a89bf77e 100644 --- a/src/llm/providers/azure-openai-responses.ts +++ b/src/llm/providers/azure-openai-responses.ts @@ -23,7 +23,7 @@ import { buildBaseOptions } from "./simple-options.js"; const DEFAULT_AZURE_API_VERSION = "v1"; const AZURE_TOOL_CALL_PROVIDERS = new Set([ "openai", - "openai-codex", + "openai", "opencode", "azure-openai-responses", ]); diff --git a/src/llm/providers/openai-codex-responses.test.ts b/src/llm/providers/openai-chatgpt-responses.test.ts similarity index 98% rename from src/llm/providers/openai-codex-responses.test.ts rename to src/llm/providers/openai-chatgpt-responses.test.ts index 2102d578cf4c..fc9243cc5ac5 100644 --- a/src/llm/providers/openai-codex-responses.test.ts +++ b/src/llm/providers/openai-chatgpt-responses.test.ts @@ -5,7 +5,7 @@ import { extractOpenAICodexAccountId, resetOpenAICodexWebSocketDebugStats, streamOpenAICodexResponses, -} from "./openai-codex-responses.js"; +} from "./openai-chatgpt-responses.js"; function createJwt(payload: Record): string { const header = Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" })).toString("base64url"); @@ -84,15 +84,15 @@ describe("streamOpenAICodexResponses transport", () => { const model = { id: "gpt-5.5", name: "GPT-5.5", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.test/backend-api", reasoning: true, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128_000, maxTokens: 16_000, - } satisfies Model<"openai-codex-responses">; + } satisfies Model<"openai-chatgpt-responses">; const context = { messages: [{ role: "user", content: "hi", timestamp: 1 }], diff --git a/src/llm/providers/openai-codex-responses.ts b/src/llm/providers/openai-chatgpt-responses.ts similarity index 98% rename from src/llm/providers/openai-codex-responses.ts rename to src/llm/providers/openai-chatgpt-responses.ts index d1bfda08da53..ab2bccc94903 100644 --- a/src/llm/providers/openai-codex-responses.ts +++ b/src/llm/providers/openai-chatgpt-responses.ts @@ -41,7 +41,7 @@ import { } from "../utils/diagnostics.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; import { headersToRecord } from "../utils/headers.js"; -import { resolveOpenAICodexAccountId } from "../utils/oauth/openai-codex-jwt.js"; +import { resolveOpenAICodexAccountId } from "../utils/oauth/openai-chatgpt-jwt.js"; import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js"; import { convertResponsesMessages, @@ -59,7 +59,7 @@ const MAX_RETRIES = 3; const BASE_DELAY_MS = 1000; const RETRY_AFTER_HTTP_DATE_RE = /^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), \d{2}-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{2} \d{2}:\d{2}:\d{2} GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [ \d]\d \d{2}:\d{2}:\d{2} \d{4})$/; -const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]); +const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "opencode"]); const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009; const CODEX_RESPONSE_STATUSES = new Set([ @@ -187,10 +187,10 @@ function formatRequestTimeoutError(timeoutMs: number, cause: unknown): Error { // ============================================================================ export const streamOpenAICodexResponses: StreamFunction< - "openai-codex-responses", + "openai-chatgpt-responses", OpenAICodexResponsesOptions > = ( - model: Model<"openai-codex-responses">, + model: Model<"openai-chatgpt-responses">, context: Context, options?: OpenAICodexResponsesOptions, ) => { @@ -202,7 +202,7 @@ export const streamOpenAICodexResponses: StreamFunction< const output: AssistantMessage = { role: "assistant", content: [], - api: "openai-codex-responses" as Api, + api: "openai-chatgpt-responses" as Api, provider: model.provider, model: model.id, usage: { @@ -441,9 +441,9 @@ export const streamOpenAICodexResponses: StreamFunction< }; export const streamSimpleOpenAICodexResponses: StreamFunction< - "openai-codex-responses", + "openai-chatgpt-responses", SimpleStreamOptions -> = (model: Model<"openai-codex-responses">, context: Context, options?: SimpleStreamOptions) => { +> = (model: Model<"openai-chatgpt-responses">, context: Context, options?: SimpleStreamOptions) => { const apiKey = options?.apiKey || getEnvApiKey(model.provider); if (!apiKey) { throw new Error(`No API key for provider: ${model.provider}`); @@ -471,7 +471,7 @@ export const streamSimpleOpenAICodexResponses: StreamFunction< // ============================================================================ function buildRequestBody( - model: Model<"openai-codex-responses">, + model: Model<"openai-chatgpt-responses">, context: Context, options?: OpenAICodexResponsesOptions, ): RequestBody { @@ -524,7 +524,7 @@ function buildRequestBody( } function getServiceTierCostMultiplier( - model: Pick, "id">, + model: Pick, "id">, serviceTier: ResponseCreateParamsStreaming["service_tier"] | undefined, ): number { switch (serviceTier) { @@ -540,7 +540,7 @@ function getServiceTierCostMultiplier( function applyServiceTierPricing( usage: Usage, serviceTier: ResponseCreateParamsStreaming["service_tier"] | undefined, - model: Pick, "id">, + model: Pick, "id">, ) { const multiplier = getServiceTierCostMultiplier(model, serviceTier); if (multiplier === 1) { @@ -599,7 +599,7 @@ async function processStream( response: Response, output: AssistantMessage, stream: AssistantMessageEventStream, - model: Model<"openai-codex-responses">, + model: Model<"openai-chatgpt-responses">, options?: OpenAICodexResponsesOptions, ): Promise { await processResponsesStream(mapCodexEvents(parseSSE(response)), output, stream, model, { @@ -1415,7 +1415,7 @@ async function processWebSocketStream( headers: Headers, output: AssistantMessage, stream: AssistantMessageEventStream, - model: Model<"openai-codex-responses">, + model: Model<"openai-chatgpt-responses">, onStart: () => void, options?: OpenAICodexResponsesOptions, ): Promise { diff --git a/src/llm/providers/openai-completions.ts b/src/llm/providers/openai-completions.ts index b568fc6612c7..22a2e2ddbafd 100644 --- a/src/llm/providers/openai-completions.ts +++ b/src/llm/providers/openai-completions.ts @@ -828,7 +828,7 @@ export function convertMessages( const normalizeToolCallId = (id: string): string => { // Handle pipe-separated IDs from OpenAI Responses API // Format: {call_id}|{id} where {id} can be 400+ chars with special chars (+, /, =) - // These come from providers like github-copilot, openai-codex, opencode + // These come from providers like github-copilot, openai, opencode // Extract just the call_id part and normalize it if (id.includes("|")) { const [callId] = id.split("|"); diff --git a/src/llm/providers/openai-responses.ts b/src/llm/providers/openai-responses.ts index fca0e22069f7..61082850b5ef 100644 --- a/src/llm/providers/openai-responses.ts +++ b/src/llm/providers/openai-responses.ts @@ -24,7 +24,7 @@ import { } from "./openai-responses-shared.js"; import { buildBaseOptions } from "./simple-options.js"; -const OPENAI_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]); +const OPENAI_TOOL_CALL_PROVIDERS = new Set(["openai", "opencode"]); /** * Resolve cache retention preference. diff --git a/src/llm/providers/register-builtins.ts b/src/llm/providers/register-builtins.ts index b8406c45df6a..dfcced3b5a30 100644 --- a/src/llm/providers/register-builtins.ts +++ b/src/llm/providers/register-builtins.ts @@ -15,7 +15,7 @@ import type { AzureOpenAIResponsesOptions } from "./azure-openai-responses.js"; import type { GoogleVertexOptions } from "./google-vertex.js"; import type { GoogleOptions } from "./google.js"; import type { MistralOptions } from "./mistral.js"; -import type { OpenAICodexResponsesOptions } from "./openai-codex-responses.js"; +import type { OpenAICodexResponsesOptions } from "./openai-chatgpt-responses.js"; import type { OpenAICompletionsOptions } from "./openai-completions.js"; import type { OpenAIResponsesOptions } from "./openai-responses.js"; @@ -62,8 +62,11 @@ interface MistralProviderModule { } interface OpenAICodexResponsesProviderModule { - streamOpenAICodexResponses: StreamFunction<"openai-codex-responses", OpenAICodexResponsesOptions>; - streamSimpleOpenAICodexResponses: StreamFunction<"openai-codex-responses", SimpleStreamOptions>; + streamOpenAICodexResponses: StreamFunction< + "openai-chatgpt-responses", + OpenAICodexResponsesOptions + >; + streamSimpleOpenAICodexResponses: StreamFunction<"openai-chatgpt-responses", SimpleStreamOptions>; } interface OpenAICompletionsProviderModule { @@ -97,7 +100,11 @@ let mistralProviderModulePromise: | undefined; let openAICodexResponsesProviderModulePromise: | Promise< - LazyProviderModule<"openai-codex-responses", OpenAICodexResponsesOptions, SimpleStreamOptions> + LazyProviderModule< + "openai-chatgpt-responses", + OpenAICodexResponsesOptions, + SimpleStreamOptions + > > | undefined; let openAICompletionsProviderModulePromise: @@ -261,9 +268,9 @@ function loadMistralProviderModule(): Promise< } function loadOpenAICodexResponsesProviderModule(): Promise< - LazyProviderModule<"openai-codex-responses", OpenAICodexResponsesOptions, SimpleStreamOptions> + LazyProviderModule<"openai-chatgpt-responses", OpenAICodexResponsesOptions, SimpleStreamOptions> > { - openAICodexResponsesProviderModulePromise ||= import("./openai-codex-responses.js").then( + openAICodexResponsesProviderModulePromise ||= import("./openai-chatgpt-responses.js").then( (module) => { const provider = module as OpenAICodexResponsesProviderModule; return { @@ -374,7 +381,7 @@ export function registerBuiltInApiProviders(): void { registerApiProvider( { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", stream: streamOpenAICodexResponses, streamSimple: streamSimpleOpenAICodexResponses, }, diff --git a/src/llm/providers/stream-wrappers/openai.test.ts b/src/llm/providers/stream-wrappers/openai.test.ts index ad8a5e968764..44183b35a2d2 100644 --- a/src/llm/providers/stream-wrappers/openai.test.ts +++ b/src/llm/providers/stream-wrappers/openai.test.ts @@ -25,10 +25,10 @@ function createPayloadCapture(opts?: { initialReasoning?: unknown }) { } const codexModel = { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "gpt-5.1-codex", -} as Model<"openai-codex-responses">; +} as Model<"openai-chatgpt-responses">; const openaiModel = { api: "openai-responses", @@ -130,10 +130,10 @@ describe("createCodexNativeWebSearchWrapper", () => { void wrapped( { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "gateway", id: "gpt-5.5", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, { messages: [], tools: [ @@ -177,10 +177,10 @@ describe("createCodexNativeWebSearchWrapper", () => { void wrapped( { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "gateway", id: "gpt-5.5", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, { messages: [] }, {}, ); @@ -212,10 +212,10 @@ describe("createCodexNativeWebSearchWrapper", () => { void wrapped( { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "gateway", id: "gpt-5.5", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, { messages: [], tools: [ @@ -492,7 +492,7 @@ describe("createOpenAIThinkingLevelWrapper", () => { id: "gpt-5.5", }, { - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", id: "gpt-5.5", }, @@ -524,7 +524,7 @@ describe("createOpenAIAttributionHeadersWrapper", () => { { ...codexModel, baseUrl: "https://chatgpt.com/backend-api", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, { messages: [] }, { headers: { @@ -565,7 +565,7 @@ describe("createOpenAIAttributionHeadersWrapper", () => { { ...codexModel, baseUrl: "https://chatgpt.com/backend-api", - } as Model<"openai-codex-responses">, + } as Model<"openai-chatgpt-responses">, { messages: [] }, { apiKey: "oauth-bearer-token", diff --git a/src/llm/providers/stream-wrappers/openai.ts b/src/llm/providers/stream-wrappers/openai.ts index 0f98a1951b22..7262565f05bd 100644 --- a/src/llm/providers/stream-wrappers/openai.ts +++ b/src/llm/providers/stream-wrappers/openai.ts @@ -76,11 +76,9 @@ function shouldApplyOpenAIAttributionHeaders(model: { api?: unknown; provider?: unknown; baseUrl?: unknown; -}): "openai" | "openai-codex" | undefined { +}): "openai" | undefined { const attributionProvider = resolveOpenAIRequestCapabilities(model).attributionProvider; - return attributionProvider === "openai" || attributionProvider === "openai-codex" - ? attributionProvider - : undefined; + return attributionProvider === "openai" ? attributionProvider : undefined; } function shouldUseCodexNativeTransport(model: { @@ -90,10 +88,10 @@ function shouldUseCodexNativeTransport(model: { compat?: unknown; }): boolean { const api = readStringValue(model.api); - if (api !== "openai-codex-responses") { + if (api !== "openai-chatgpt-responses") { return false; } - return resolveOpenAIRequestCapabilities(model).endpointClass === "openai-codex"; + return resolveOpenAIRequestCapabilities(model).endpointClass === "openai"; } function shouldApplyOpenAIServiceTier(model: { @@ -530,9 +528,9 @@ export function createOpenAIFastModeWrapper(baseStreamFn: StreamFn | undefined): return (model, context, options) => { if ( (model.api !== "openai-responses" && - model.api !== "openai-codex-responses" && + model.api !== "openai-chatgpt-responses" && model.api !== "azure-openai-responses") || - (model.provider !== "openai" && model.provider !== "openai-codex") + model.provider !== "openai" ) { return underlying(model, context, options); } @@ -577,12 +575,12 @@ export function createOpenAITextVerbosityWrapper( ): StreamFn { const underlying = baseStreamFn ?? streamSimple; return (model, context, options) => { - if (model.api !== "openai-responses" && model.api !== "openai-codex-responses") { + if (model.api !== "openai-responses" && model.api !== "openai-chatgpt-responses") { return underlying(model, context, options); } const resolvedVerbosity = resolveOpenAITextVerbosityForModel(model, verbosity); const shouldOverrideExistingVerbosity = - model.api === "openai-codex-responses" || resolvedVerbosity !== verbosity; + model.api === "openai-chatgpt-responses" || resolvedVerbosity !== verbosity; const originalOnPayload = options?.onPayload; return underlying(model, context, { ...options, diff --git a/src/llm/utils/oauth/index.ts b/src/llm/utils/oauth/index.ts index 6ec08ea56b25..d8786807163d 100644 --- a/src/llm/utils/oauth/index.ts +++ b/src/llm/utils/oauth/index.ts @@ -22,7 +22,7 @@ export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken, -} from "./openai-codex.js"; +} from "./openai-chatgpt.js"; export * from "./types.js"; @@ -32,7 +32,7 @@ export * from "./types.js"; import { anthropicOAuthProvider } from "./anthropic.js"; import { githubCopilotOAuthProvider } from "./github-copilot.js"; -import { openaiCodexOAuthProvider } from "./openai-codex.js"; +import { openaiCodexOAuthProvider } from "./openai-chatgpt.js"; import type { OAuthCredentials, OAuthProviderId, diff --git a/src/llm/utils/oauth/openai-codex-jwt.ts b/src/llm/utils/oauth/openai-chatgpt-jwt.ts similarity index 100% rename from src/llm/utils/oauth/openai-codex-jwt.ts rename to src/llm/utils/oauth/openai-chatgpt-jwt.ts diff --git a/src/llm/utils/oauth/openai-codex.test.ts b/src/llm/utils/oauth/openai-chatgpt.test.ts similarity index 97% rename from src/llm/utils/oauth/openai-codex.test.ts rename to src/llm/utils/oauth/openai-chatgpt.test.ts index 5eeecd141edf..0dedd358bd41 100644 --- a/src/llm/utils/oauth/openai-codex.test.ts +++ b/src/llm/utils/oauth/openai-chatgpt.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; type LoginOpenAICodexOAuth = - typeof import("../../../plugins/provider-openai-codex-oauth.js").loginOpenAICodexOAuth; + typeof import("../../../plugins/provider-openai-chatgpt-oauth.js").loginOpenAICodexOAuth; const mocks = vi.hoisted(() => ({ loginOpenAICodexOAuth: vi.fn(), @@ -10,7 +10,7 @@ const mocks = vi.hoisted(() => ({ refreshProviderOAuthCredentialWithPlugin: vi.fn(), })); -vi.mock("../../../plugins/provider-openai-codex-oauth.js", () => ({ +vi.mock("../../../plugins/provider-openai-chatgpt-oauth.js", () => ({ loginOpenAICodexOAuth: mocks.loginOpenAICodexOAuth, })); @@ -23,12 +23,12 @@ vi.mock("../../../plugin-sdk/facade-runtime.js", () => ({ mocks.loadActivatedBundledPluginPublicSurfaceModuleSync, })); -import { loginOpenAICodex, refreshOpenAICodexToken } from "./openai-codex.js"; +import { loginOpenAICodex, refreshOpenAICodexToken } from "./openai-chatgpt.js"; function createCredential() { return { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: 1_700_000_000_000, diff --git a/src/llm/utils/oauth/openai-codex.ts b/src/llm/utils/oauth/openai-chatgpt.ts similarity index 97% rename from src/llm/utils/oauth/openai-codex.ts rename to src/llm/utils/oauth/openai-chatgpt.ts index 4eb581a3f144..59752c897cb2 100644 --- a/src/llm/utils/oauth/openai-codex.ts +++ b/src/llm/utils/oauth/openai-chatgpt.ts @@ -78,7 +78,8 @@ async function refreshViaProviderRuntime(refreshToken: string): Promise { throwIfOAuthLoginAborted(callbacks.signal); - const { loginOpenAICodexOAuth } = await import("../../../plugins/provider-openai-codex-oauth.js"); + const { loginOpenAICodexOAuth } = + await import("../../../plugins/provider-openai-chatgpt-oauth.js"); const manualCodeInput = callbacks.onManualCodeInput; const onManualCodeInput = manualCodeInput ? async () => await withOAuthLoginAbort(manualCodeInput(), callbacks.signal) diff --git a/src/media-generation/runtime-shared.test.ts b/src/media-generation/runtime-shared.test.ts index 7e478015d041..f8869ba4da88 100644 --- a/src/media-generation/runtime-shared.test.ts +++ b/src/media-generation/runtime-shared.test.ts @@ -105,7 +105,7 @@ describe("media-generation runtime shared candidates", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.5", + primary: "openai/gpt-5.5", }, }, }, @@ -120,7 +120,7 @@ describe("media-generation runtime shared candidates", () => { }, { id: "openai", - aliases: ["openai-codex"], + aliases: ["openai"], defaultModel: "gpt-image-2", isConfigured: () => true, }, diff --git a/src/media-understanding/defaults.test.ts b/src/media-understanding/defaults.test.ts index 8b201971281d..7f6f3ed003fa 100644 --- a/src/media-understanding/defaults.test.ts +++ b/src/media-understanding/defaults.test.ts @@ -11,7 +11,7 @@ const mediaMetadataPlugins = vi.hoisted(() => [ "mistral", "moonshot", "openai", - "openai-codex", + "openai", "opencode", "opencode-go", "openrouter", @@ -58,11 +58,6 @@ const mediaMetadataPlugins = vi.hoisted(() => [ autoPriority: { video: 30 }, }, openai: { - capabilities: ["image", "audio"], - defaultModels: { image: "gpt-5.4-mini", audio: "gpt-4o-transcribe" }, - autoPriority: { image: 10, audio: 10 }, - }, - "openai-codex": { capabilities: ["image", "audio"], defaultModels: { image: "gpt-5.5", audio: "gpt-4o-transcribe" }, autoPriority: { image: 20, audio: 20 }, @@ -118,7 +113,7 @@ describe("resolveDefaultMediaModel", () => { expect(resolveDefaultMediaModel({ providerId: "mistral", capability: "audio" })).toBe( "voxtral-mini-latest", ); - expect(resolveDefaultMediaModel({ providerId: "openai-codex", capability: "audio" })).toBe( + expect(resolveDefaultMediaModel({ providerId: "openai", capability: "audio" })).toBe( "gpt-4o-transcribe", ); expect(resolveDefaultMediaModel({ providerId: "openrouter", capability: "audio" })).toBe( @@ -130,9 +125,7 @@ describe("resolveDefaultMediaModel", () => { expect(resolveDefaultMediaModel({ providerId: "minimax-portal", capability: "image" })).toBe( "MiniMax-VL-01", ); - expect(resolveDefaultMediaModel({ providerId: "openai-codex", capability: "image" })).toBe( - "gpt-5.5", - ); + expect(resolveDefaultMediaModel({ providerId: "openai", capability: "image" })).toBe("gpt-5.5"); expect(resolveDefaultMediaModel({ providerId: "moonshot", capability: "image" })).toBe( "kimi-k2.6", ); @@ -176,7 +169,6 @@ describe("resolveAutoMediaKeyProviders", () => { it("keeps the bundled audio fallback order", () => { expect(resolveAutoMediaKeyProviders({ capability: "audio" })).toEqual([ "openai", - "openai-codex", "xai", "openrouter", "google", @@ -186,9 +178,8 @@ describe("resolveAutoMediaKeyProviders", () => { it("keeps the bundled image fallback order", () => { expect(resolveAutoMediaKeyProviders({ capability: "image" })).toEqual([ - "openai", "anthropic", - "openai-codex", + "openai", "google", "minimax", "minimax-portal", diff --git a/src/media-understanding/image.test.ts b/src/media-understanding/image.test.ts index a844c83b5baf..8bc0f4e743f7 100644 --- a/src/media-understanding/image.test.ts +++ b/src/media-understanding/image.test.ts @@ -765,7 +765,7 @@ describe("describeImageWithModel", () => { it("passes image prompt as system instructions for codex image requests", async () => { discoverModelsMock.mockReturnValue({ find: vi.fn(() => ({ - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", input: ["text", "image"], baseUrl: "https://chatgpt.com/backend-api", @@ -773,8 +773,8 @@ describe("describeImageWithModel", () => { }); completeMock.mockResolvedValue({ role: "assistant", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", model: "gpt-5.4", stopReason: "stop", timestamp: Date.now(), @@ -784,7 +784,7 @@ describe("describeImageWithModel", () => { const result = await describeImageWithModel({ cfg: {}, agentDir: "/tmp/openclaw-agent", - provider: "openai-codex", + provider: "openai", model: "gpt-5.4", buffer: Buffer.from("png-bytes"), fileName: "image.png", @@ -801,7 +801,7 @@ describe("describeImageWithModel", () => { const firstCall = requireFirstMockCall(completeMock, "image completion"); const [completionModel, context, options] = firstCall; expect(completionModel).toEqual({ - provider: "openai-codex", + provider: "openai", id: "gpt-5.4", input: ["text", "image"], baseUrl: "https://chatgpt.com/backend-api", diff --git a/src/media-understanding/image.ts b/src/media-understanding/image.ts index 745c38b49bdd..ec602b52cc76 100644 --- a/src/media-understanding/image.ts +++ b/src/media-understanding/image.ts @@ -48,7 +48,7 @@ function isNativeResponsesReasoningPayload(model: Model): boolean { if ( model.api !== "openai-responses" && model.api !== "azure-openai-responses" && - model.api !== "openai-codex-responses" + model.api !== "openai-chatgpt-responses" ) { return false; } diff --git a/src/media-understanding/runner.auto-audio.test.ts b/src/media-understanding/runner.auto-audio.test.ts index 4e4c6ca9aad6..f6add3444b32 100644 --- a/src/media-understanding/runner.auto-audio.test.ts +++ b/src/media-understanding/runner.auto-audio.test.ts @@ -152,8 +152,8 @@ describe("runCapability auto audio entries", () => { await withAudioFixture("openclaw-auto-audio-codex", async ({ ctx, media, cache }) => { const providerRegistry = createProviderRegistry({ - "openai-codex": { - id: "openai-codex", + openai: { + id: "openai", capabilities: ["image", "audio"], defaultModels: { image: "gpt-5.5", audio: "gpt-4o-transcribe" }, transcribeAudio: async (req) => { @@ -165,7 +165,7 @@ describe("runCapability auto audio entries", () => { const cfg = { models: { providers: { - "openai-codex": { + openai: { apiKey: "codex-test-key", // pragma: allowlist secret models: [], }, @@ -180,7 +180,7 @@ describe("runCapability auto audio entries", () => { attachments: cache, media, providerRegistry, - activeModel: { provider: "openai-codex", model: "gpt-5.5" }, + activeModel: { provider: "openai", model: "gpt-5.5" }, }); }); @@ -190,7 +190,7 @@ describe("runCapability auto audio entries", () => { expect(requireCapabilityOutput(runResult, 0)).toEqual({ kind: "audio.transcription", attachmentIndex: 0, - provider: "openai-codex", + provider: "openai", model: "gpt-4o-transcribe", text: "codex audio", }); diff --git a/src/model-catalog/manifest-planner.test.ts b/src/model-catalog/manifest-planner.test.ts index e531335f721c..97c5b7ef1e3b 100644 --- a/src/model-catalog/manifest-planner.test.ts +++ b/src/model-catalog/manifest-planner.test.ts @@ -350,7 +350,7 @@ describe("manifest model catalog suppression planner", () => { plugins: [ { id: "openai", - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], modelCatalog: { aliases: { "azure-openai-responses": { diff --git a/src/plugin-sdk/provider-auth-login.runtime.ts b/src/plugin-sdk/provider-auth-login.runtime.ts index c9f142038c30..57902dd01fcc 100644 --- a/src/plugin-sdk/provider-auth-login.runtime.ts +++ b/src/plugin-sdk/provider-auth-login.runtime.ts @@ -1,6 +1,6 @@ /** @deprecated Provider-owned login helpers; use provider auth hooks instead. */ export { loginChutes } from "../commands/chutes-oauth.js"; /** @deprecated Provider-owned login helpers; use provider auth hooks instead. */ -export { loginOpenAICodexOAuth } from "../plugins/provider-openai-codex-oauth.js"; +export { loginOpenAICodexOAuth } from "../plugins/provider-openai-chatgpt-oauth.js"; /** @deprecated Provider-owned login helpers; use provider auth hooks instead. */ export { githubCopilotLoginCommand } from "./github-copilot-login.js"; diff --git a/src/plugin-sdk/provider-auth.test.ts b/src/plugin-sdk/provider-auth.test.ts index 75a54780f487..682c7afd0280 100644 --- a/src/plugin-sdk/provider-auth.test.ts +++ b/src/plugin-sdk/provider-auth.test.ts @@ -21,9 +21,9 @@ describe("provider auth profile helpers", () => { const fallbackStore: AuthProfileStore = { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "api_key", - provider: "openai-codex", + provider: "openai", key: "fallback-key", }, }, @@ -71,16 +71,16 @@ describe("provider auth profile helpers", () => { const { listUsableProviderAuthProfileIds, resolveProviderAuthProfileApiKey } = await import("./provider-auth.js"); - expect(listUsableProviderAuthProfileIds({ provider: "openai-codex" }).profileIds).toEqual([ - "openai-codex:default", + expect(listUsableProviderAuthProfileIds({ provider: "openai" }).profileIds).toEqual([ + "openai:default", ]); - await expect(resolveProviderAuthProfileApiKey({ provider: "openai-codex" })).resolves.toBe( + await expect(resolveProviderAuthProfileApiKey({ provider: "openai" })).resolves.toBe( "fallback-key", ); expect(resolveApiKeyForProfile).toHaveBeenCalledWith( expect.objectContaining({ agentDir: "/tmp/openclaw-agent", - profileId: "openai-codex:default", + profileId: "openai:default", store: fallbackStore, }), ); @@ -96,16 +96,16 @@ describe("provider auth profile helpers", () => { const externalStore: AuthProfileStore = { version: 1, profiles: { - "openai-codex:default": { + "openai:default": { type: "oauth", - provider: "openai-codex", + provider: "openai", access: "oauth-access", refresh: "oauth-refresh", expires: Date.now() + 60_000, }, }, }; - const externalCli = { mode: "scoped", providerIds: ["openai-codex"] }; + const externalCli = { mode: "scoped", providerIds: ["openai"] }; const loadAuthProfileStoreForSecretsRuntime = vi.fn( (_agentDir?: string, options?: { externalCli?: unknown }) => options?.externalCli ? externalStore : primaryStore, @@ -142,10 +142,10 @@ describe("provider auth profile helpers", () => { const { isProviderAuthProfileConfigured } = await import("./provider-auth.js"); - expect(isProviderAuthProfileConfigured({ provider: "openai-codex" })).toBe(false); + expect(isProviderAuthProfileConfigured({ provider: "openai" })).toBe(false); expect( isProviderAuthProfileConfigured({ - provider: "openai-codex", + provider: "openai", includeExternalCliAuth: true, }), ).toBe(true); diff --git a/src/plugin-sdk/provider-auth.ts b/src/plugin-sdk/provider-auth.ts index 1edf36dd8d9f..655726630793 100644 --- a/src/plugin-sdk/provider-auth.ts +++ b/src/plugin-sdk/provider-auth.ts @@ -108,7 +108,7 @@ export { resolveOpenAICodexAuthIdentity, resolveOpenAICodexImportProfileName, type OpenAICodexAuthIdentity, -} from "./provider-openai-codex-auth.js"; +} from "./provider-openai-chatgpt-auth.js"; export { generateHexPkceVerifierChallenge, generatePkceVerifierChallenge, @@ -405,7 +405,6 @@ function resolveUsableProviderAuthProfiles(params: { const fallbackStore = loadAuthProfileStoreWithoutExternalProfiles(agentDir, { allowKeychainPrompt: params.allowKeychainPrompt ?? false, - resolveLegacyOAuthSidecars: true, }); return { agentDir, diff --git a/src/plugin-sdk/provider-openai-codex-auth.test.ts b/src/plugin-sdk/provider-openai-chatgpt-auth.test.ts similarity index 100% rename from src/plugin-sdk/provider-openai-codex-auth.test.ts rename to src/plugin-sdk/provider-openai-chatgpt-auth.test.ts diff --git a/src/plugin-sdk/provider-openai-codex-auth.ts b/src/plugin-sdk/provider-openai-chatgpt-auth.ts similarity index 100% rename from src/plugin-sdk/provider-openai-codex-auth.ts rename to src/plugin-sdk/provider-openai-chatgpt-auth.ts diff --git a/src/plugin-sdk/provider-tools.test.ts b/src/plugin-sdk/provider-tools.test.ts index 0290bb7c4b7c..da605b14f4ef 100644 --- a/src/plugin-sdk/provider-tools.test.ts +++ b/src/plugin-sdk/provider-tools.test.ts @@ -63,10 +63,10 @@ describe("buildProviderToolCompatFamilyHooks", () => { const normalized = hooks.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/codex", id: "gpt-5.4", } as never, @@ -435,12 +435,12 @@ describe("buildProviderToolCompatFamilyHooks", () => { const hooks = buildProviderToolCompatFamilyHooks("openai"); const diagnostics = hooks.inspectToolSchemas({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", model: { - provider: "openai-codex", - api: "openai-codex-responses", + provider: "openai", + api: "openai-chatgpt-responses", baseUrl: "https://chatgpt.com/backend-api", id: "gpt-5.4", } as never, diff --git a/src/plugin-sdk/provider-tools.ts b/src/plugin-sdk/provider-tools.ts index 826355f4c37e..a7edf8f8f3eb 100644 --- a/src/plugin-sdk/provider-tools.ts +++ b/src/plugin-sdk/provider-tools.ts @@ -124,13 +124,13 @@ function shouldApplyOpenAIToolCompat(ctx: ProviderNormalizeToolSchemasContext): return !baseUrl || isOpenAIResponsesBaseUrl(baseUrl); } return ( - api === "openai-codex-responses" && + api === "openai-chatgpt-responses" && (!baseUrl || isOpenAIResponsesBaseUrl(baseUrl) || isOpenAICodexBaseUrl(baseUrl)) ); } - if (provider === "openai-codex") { + if (provider === "openai") { return ( - api === "openai-codex-responses" && + api === "openai-chatgpt-responses" && (!baseUrl || isOpenAIResponsesBaseUrl(baseUrl) || isOpenAICodexBaseUrl(baseUrl)) ); } diff --git a/src/plugin-sdk/schema-normalization-runtime-contract.test.ts b/src/plugin-sdk/schema-normalization-runtime-contract.test.ts index a0d4d135f939..474509016909 100644 --- a/src/plugin-sdk/schema-normalization-runtime-contract.test.ts +++ b/src/plugin-sdk/schema-normalization-runtime-contract.test.ts @@ -26,9 +26,9 @@ describe("OpenAI-family schema normalization runtime contract", () => { it("normalizes parameter-free schemas for native OpenAI Codex Responses tools", () => { const normalized = hooks.normalizeToolSchemas({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", model: createNativeOpenAICodexResponsesModel() as never, tools: [createParameterFreeTool()] as never, }); @@ -52,9 +52,9 @@ describe("OpenAI-family schema normalization runtime contract", () => { it("keeps permissive schemas observable for transport strict:false downgrade", () => { const tool = createPermissiveTool(); const normalized = hooks.normalizeToolSchemas({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", model: createNativeOpenAICodexResponsesModel() as never, tools: [tool] as never, }); @@ -62,9 +62,9 @@ describe("OpenAI-family schema normalization runtime contract", () => { expect(normalized[0]?.parameters).toEqual(tool.parameters); expect( hooks.inspectToolSchemas({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", - modelApi: "openai-codex-responses", + modelApi: "openai-chatgpt-responses", model: createNativeOpenAICodexResponsesModel() as never, tools: [tool] as never, }), diff --git a/src/plugin-sdk/test-helpers/agents/auth-profile-runtime-contract.ts b/src/plugin-sdk/test-helpers/agents/auth-profile-runtime-contract.ts index d3e75a08bf3e..404356860a65 100644 --- a/src/plugin-sdk/test-helpers/agents/auth-profile-runtime-contract.ts +++ b/src/plugin-sdk/test-helpers/agents/auth-profile-runtime-contract.ts @@ -10,12 +10,12 @@ export const AUTH_PROFILE_RUNTIME_CONTRACT = { runId: "run-auth-contract", workspacePrompt: "continue with the bound Codex profile", openAiProvider: "openai", - openAiCodexProvider: "openai-codex", + openAiCodexProvider: "openai", codexCliProvider: "codex-cli", codexHarnessProvider: "codex", claudeCliProvider: "claude-cli", openAiProfileId: "openai:work", - openAiCodexProfileId: "openai-codex:work", + openAiCodexProfileId: "openai:work", anthropicProfileId: "anthropic:work", } as const; diff --git a/src/plugin-sdk/test-helpers/agents/outcome-fallback-runtime-contract.ts b/src/plugin-sdk/test-helpers/agents/outcome-fallback-runtime-contract.ts index e958226f3815..1cab10b43c64 100644 --- a/src/plugin-sdk/test-helpers/agents/outcome-fallback-runtime-contract.ts +++ b/src/plugin-sdk/test-helpers/agents/outcome-fallback-runtime-contract.ts @@ -1,7 +1,7 @@ import type { EmbeddedAgentRunResult } from "../../../agents/embedded-agent-runner/types.js"; export const OUTCOME_FALLBACK_RUNTIME_CONTRACT = { - primaryProvider: "openai-codex", + primaryProvider: "openai", primaryModel: "gpt-5.4", fallbackProvider: "anthropic", fallbackModel: "claude-haiku-3-5", diff --git a/src/plugin-sdk/test-helpers/agents/prompt-overlay-runtime-contract.ts b/src/plugin-sdk/test-helpers/agents/prompt-overlay-runtime-contract.ts index 4bf4564ba710..d86e016ba496 100644 --- a/src/plugin-sdk/test-helpers/agents/prompt-overlay-runtime-contract.ts +++ b/src/plugin-sdk/test-helpers/agents/prompt-overlay-runtime-contract.ts @@ -5,7 +5,7 @@ export const GPT5_CONTRACT_MODEL_ID = "gpt-5.4"; export const GPT5_PREFIXED_CONTRACT_MODEL_ID = "openai/gpt-5.4"; export const NON_GPT5_CONTRACT_MODEL_ID = "gpt-4.1"; export const OPENAI_CONTRACT_PROVIDER_ID = "openai"; -export const OPENAI_CODEX_CONTRACT_PROVIDER_ID = "openai-codex"; +export const OPENAI_CODEX_CONTRACT_PROVIDER_ID = "openai"; export const CODEX_CONTRACT_PROVIDER_ID = "codex"; export const NON_OPENAI_CONTRACT_PROVIDER_ID = "openrouter"; diff --git a/src/plugin-sdk/test-helpers/agents/schema-normalization-runtime-contract.ts b/src/plugin-sdk/test-helpers/agents/schema-normalization-runtime-contract.ts index ed3385aea59d..9b14186600db 100644 --- a/src/plugin-sdk/test-helpers/agents/schema-normalization-runtime-contract.ts +++ b/src/plugin-sdk/test-helpers/agents/schema-normalization-runtime-contract.ts @@ -56,8 +56,8 @@ export function createNativeOpenAICodexResponsesModel() { return { id: "gpt-5.4", name: "GPT-5.4", - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", reasoning: true, input: ["text"], diff --git a/src/plugin-sdk/test-helpers/provider-auth-contract.ts b/src/plugin-sdk/test-helpers/provider-auth-contract.ts index 6b1311d181e0..c110173d8e15 100644 --- a/src/plugin-sdk/test-helpers/provider-auth-contract.ts +++ b/src/plugin-sdk/test-helpers/provider-auth-contract.ts @@ -152,7 +152,7 @@ export function describeOpenAICodexProviderAuthContract( async function expectStableFallbackProfile(params: { access: string; profileId: string }) { const { default: openAIPlugin } = await load(); - const provider = requireProvider(await registerProviders(openAIPlugin), "openai-codex"); + const provider = requireProvider(await registerProviders(openAIPlugin), "openai"); loginOpenAICodexOAuthMock.mockResolvedValueOnce({ refresh: "refresh-token", access: params.access, @@ -171,7 +171,7 @@ export function describeOpenAICodexProviderAuthContract( async function getProvider() { const { default: openAIPlugin } = await load(); - return requireProvider(await registerProviders(openAIPlugin), "openai-codex"); + return requireProvider(await registerProviders(openAIPlugin), "openai"); } it("keeps OAuth auth results provider-owned", async () => { diff --git a/src/plugin-sdk/test-helpers/provider-runtime-contract.ts b/src/plugin-sdk/test-helpers/provider-runtime-contract.ts index 40c61499c4fa..69bfd050b0ba 100644 --- a/src/plugin-sdk/test-helpers/provider-runtime-contract.ts +++ b/src/plugin-sdk/test-helpers/provider-runtime-contract.ts @@ -7,7 +7,7 @@ import { createProviderUsageFetch, makeResponse } from "../test-env.js"; const CONTRACT_SETUP_TIMEOUT_MS = 300_000; const OPENAI_CODEX_PROVIDER_RUNTIME_MODULE_ID = - "../../../extensions/openai/openai-codex-provider.runtime.js"; + "../../../extensions/openai/openai-chatgpt-provider.runtime.js"; const refreshOpenAICodexTokenMock = vi.fn(); function installProviderRuntimeContractMocks() { @@ -263,7 +263,7 @@ export function describeGithubCopilotProviderRuntimeContract( id === "gpt-5.2-codex" ? createModel({ id, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "github-copilot", baseUrl: "https://api.copilot.example", }) @@ -416,7 +416,7 @@ export function describeGoogleProviderRuntimeContract(load: ProviderRuntimeContr export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContractPluginLoader) { describe("openai provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => { const requireProviderContractProvider = installRuntimeHooks([ - { providerIds: ["openai", "openai-codex"], pluginId: "openai", name: "OpenAI", load }, + { providerIds: ["openai", "openai"], pluginId: "openai", name: "OpenAI", load }, ]); it("owns openai gpt-5.4 forward-compat resolution", () => { @@ -534,10 +534,10 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr }); it("owns refresh fallback for accountId extraction failures", async () => { - const provider = requireProviderContractProvider("openai-codex"); + const provider = requireProviderContractProvider("openai"); const credential = { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "cached-access-token", refresh: "refresh-token", expires: Date.now() - 60_000, @@ -551,17 +551,17 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr }); it("owns forward-compat codex models", () => { - const provider = requireProviderContractProvider("openai-codex"); + const provider = requireProviderContractProvider("openai"); const model = provider.resolveDynamicModel?.({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", modelRegistry: { find: (_provider: string, id: string) => id === "gpt-5.2-codex" ? createModel({ id, - api: "openai-codex-responses", - provider: "openai-codex", + api: "openai-chatgpt-responses", + provider: "openai", baseUrl: "https://chatgpt.com/backend-api", }) : null, @@ -571,23 +571,23 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr expectFields(model, { id: "gpt-5.4", provider: "openai", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", contextWindow: 1_050_000, maxTokens: 128_000, }); }); it("keeps OpenClaw cost metadata but applies Codex context metadata for gpt-5.5 models", () => { - const provider = requireProviderContractProvider("openai-codex"); + const provider = requireProviderContractProvider("openai"); const model = provider.resolveDynamicModel?.({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.5", modelRegistry: { find: (_provider: string, id: string) => id === "gpt-5.5" ? createModel({ id, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", baseUrl: "https://chatgpt.com/backend-api", input: ["text", "image"], @@ -602,7 +602,7 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr expectFields(model, { id: "gpt-5.5", provider: "openai", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", contextWindow: 400_000, contextTokens: 272_000, maxTokens: 128_000, @@ -610,16 +610,16 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr }); it("claims codex mini models through the Codex OAuth route", () => { - const provider = requireProviderContractProvider("openai-codex"); + const provider = requireProviderContractProvider("openai"); const model = provider.resolveDynamicModel?.({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4-mini", modelRegistry: { find: (_provider: string, id: string) => id === "gpt-5.4" ? createModel({ id, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", provider: "openai", baseUrl: "https://chatgpt.com/backend-api", cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, @@ -633,7 +633,7 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr expectFields(model, { id: "gpt-5.4-mini", provider: "openai", - api: "openai-codex-responses", + api: "openai-chatgpt-responses", contextWindow: 400_000, contextTokens: 272_000, maxTokens: 128_000, @@ -642,10 +642,10 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr }); it("owns codex transport defaults", () => { - const provider = requireProviderContractProvider("openai-codex"); + const provider = requireProviderContractProvider("openai"); expect( provider.prepareExtraParams?.({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", extraParams: { temperature: 0.2 }, }), @@ -656,7 +656,7 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr }); it("owns usage snapshot fetching", async () => { - const provider = requireProviderContractProvider("openai-codex"); + const provider = requireProviderContractProvider("openai"); const mockFetch = createProviderUsageFetch(async (url) => { if (url.includes("chatgpt.com/backend-api/wham/usage")) { return makeResponse(200, { @@ -677,14 +677,14 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr provider.fetchUsageSnapshot?.({ config: {} as never, env: {} as NodeJS.ProcessEnv, - provider: "openai-codex", + provider: "openai", token: "codex-token", accountId: "acc-1", timeoutMs: 5_000, fetchFn: mockFetch as unknown as typeof fetch, }), ).resolves.toEqual({ - provider: "openai-codex", + provider: "openai", displayName: "Codex", windows: [{ label: "3h", usedPercent: 12, resetAt: 1_705_000_000 }], plan: "Plus", diff --git a/src/plugin-sdk/test-helpers/public-artifacts.ts b/src/plugin-sdk/test-helpers/public-artifacts.ts index 5fe5708b0b78..36db6816f87e 100644 --- a/src/plugin-sdk/test-helpers/public-artifacts.ts +++ b/src/plugin-sdk/test-helpers/public-artifacts.ts @@ -19,7 +19,7 @@ const EXTRA_GUARDED_EXTENSION_PUBLIC_SURFACE_BASENAMES = assertUniqueValues( "index.js", "login-qr-api.js", "onboard.js", - "openai-codex-catalog.js", + "openai-chatgpt-catalog.js", "provider-catalog.js", "session-key-api.js", "setup-api.js", diff --git a/src/plugins/activation-planner.test.ts b/src/plugins/activation-planner.test.ts index 3d5255834c89..6ba42ed48964 100644 --- a/src/plugins/activation-planner.test.ts +++ b/src/plugins/activation-planner.test.ts @@ -59,7 +59,7 @@ describe("activation planner", () => { onAgentHarnesses: ["codex"], }, setup: { - providers: [{ id: "openai-codex" }], + providers: [{ id: "openai" }], }, channels: [], cliBackends: [], @@ -158,7 +158,7 @@ describe("activation planner", () => { resolveManifestActivationPluginIds({ trigger: { kind: "provider", - provider: "openai-codex", + provider: "openai", }, }), ).toEqual(["openai"]); @@ -295,7 +295,7 @@ describe("activation planner", () => { resolveManifestActivationPlan({ trigger: { kind: "provider", - provider: "openai-codex", + provider: "openai", }, }).entries, ).toEqual([ diff --git a/src/plugins/channel-plugin-ids.test.ts b/src/plugins/channel-plugin-ids.test.ts index ffba681b21bd..d796f076254f 100644 --- a/src/plugins/channel-plugin-ids.test.ts +++ b/src/plugins/channel-plugin-ids.test.ts @@ -163,7 +163,7 @@ function createManifestRegistryFixture(): PluginManifestRegistry { channels: [], origin: "bundled", enabledByDefault: true, - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], cliBackends: [], contracts: { speechProviders: ["openai"], diff --git a/src/plugins/commands.test.ts b/src/plugins/commands.test.ts index b95879fdbc34..09b9e69a83e9 100644 --- a/src/plugins/commands.test.ts +++ b/src/plugins/commands.test.ts @@ -1291,7 +1291,7 @@ describe("registerPluginCommand", () => { senderId: "U123", isAuthorizedSender: true, sessionKey: "agent:main:telegram:direct:runtimecheck", - authProfileId: "openai-codex:claude@example.com", + authProfileId: "openai:claude@example.com", commandBody: "/runtimecheck", config: {} as never, }); @@ -1378,7 +1378,7 @@ describe("registerPluginCommand", () => { isAuthorizedSender: true, agentId: "codex", sessionKey: "plugin-binding:openclaw-codex-app-server:dm", - authProfileId: "openai-codex:owner@example.com", + authProfileId: "openai:owner@example.com", commandBody: "/runtimecheck", config: {} as never, }); @@ -1386,7 +1386,7 @@ describe("registerPluginCommand", () => { expect(completionMocks.prepareSimpleCompletionModelForAgent).toHaveBeenCalledWith( expect.objectContaining({ agentId: "codex", - preferredProfile: "openai-codex:owner@example.com", + preferredProfile: "openai:owner@example.com", }), ); }); diff --git a/src/plugins/config-state.test.ts b/src/plugins/config-state.test.ts index a3b4733749d7..d871719815cc 100644 --- a/src/plugins/config-state.test.ts +++ b/src/plugins/config-state.test.ts @@ -166,10 +166,10 @@ describe("normalizePluginsConfig", () => { it("normalizes legacy plugin ids to their merged bundled plugin id", () => { const result = normalizePluginsConfig({ - allow: ["openai-codex", "google-gemini-cli", "minimax-portal-auth"], - deny: ["openai-codex", "google-gemini-cli", "minimax-portal-auth"], + allow: ["openai", "google-gemini-cli", "minimax-portal-auth"], + deny: ["openai", "google-gemini-cli", "minimax-portal-auth"], entries: { - "openai-codex": { + openai: { enabled: true, }, "google-gemini-cli": { diff --git a/src/plugins/config-state.ts b/src/plugins/config-state.ts index ae0edb12a63d..829098b078f2 100644 --- a/src/plugins/config-state.ts +++ b/src/plugins/config-state.ts @@ -34,7 +34,6 @@ export type PluginActivationConfigSource = { export type NormalizedPluginsConfig = SharedNormalizedPluginsConfig; const BUILT_IN_PLUGIN_ALIAS_FALLBACKS: ReadonlyArray = [ - ["openai-codex", "openai"], ["google-gemini-cli", "google"], ["minimax-portal", "minimax"], ["minimax-portal-auth", "minimax"], diff --git a/src/plugins/contracts/registry.retry.test.ts b/src/plugins/contracts/registry.retry.test.ts index 6c1c7b1be388..d6cd3dd000f0 100644 --- a/src/plugins/contracts/registry.retry.test.ts +++ b/src/plugins/contracts/registry.retry.test.ts @@ -228,7 +228,7 @@ describe("plugin contract registry scoped retries", () => { { pluginId: "openai", provider: { - id: "openai-codex", + id: "openai", label: "OpenAI Codex", docsPath: "/providers/openai", auth: [ @@ -254,7 +254,7 @@ describe("plugin contract registry scoped retries", () => { expect( resolveProviderContractProvidersForPluginIds(["openai"]).map((provider) => provider.id), - ).toEqual(["openai", "openai-codex"]); + ).toEqual(["openai"]); expect(resolveBundledExplicitProviderContractsFromPublicArtifacts).toHaveBeenCalledTimes(1); expect(loadBundledCapabilityRuntimeRegistry).not.toHaveBeenCalled(); }); diff --git a/src/plugins/doctor-contract-registry.load-paths.test.ts b/src/plugins/doctor-contract-registry.load-paths.test.ts index f9917ec40b08..5223fb7f1674 100644 --- a/src/plugins/doctor-contract-registry.load-paths.test.ts +++ b/src/plugins/doctor-contract-registry.load-paths.test.ts @@ -72,8 +72,8 @@ module.exports = { const entry = isRecord(entries[pluginId]) ? { ...entries[pluginId] } : {}; const llm = isRecord(entry.llm) ? { ...entry.llm } : {}; const allowedModels = Array.isArray(llm.allowedModels) ? [...llm.allowedModels] : []; - if (!allowedModels.includes("openai-codex/gpt-5.4-mini")) { - allowedModels.push("openai-codex/gpt-5.4-mini"); + if (!allowedModels.includes("openai/gpt-5.4-mini")) { + allowedModels.push("openai/gpt-5.4-mini"); } root.plugins = plugins; plugins.entries = entries; @@ -209,7 +209,7 @@ describe("doctor contract registry load-path plugins", () => { expect(result.changes).toEqual(["configured load-path doctor contract LLM policy"]); expect(llm).toEqual({ allowModelOverride: true, - allowedModels: ["openai-codex/gpt-5.4-mini"], + allowedModels: ["openai/gpt-5.4-mini"], }); }); diff --git a/src/plugins/doctor-contract-registry.test.ts b/src/plugins/doctor-contract-registry.test.ts index b93f984afb58..8d8cb7849266 100644 --- a/src/plugins/doctor-contract-registry.test.ts +++ b/src/plugins/doctor-contract-registry.test.ts @@ -280,7 +280,7 @@ describe("doctor-contract-registry module loader", () => { entries: { "load-path-doctor": { config: { - summaryModel: "openai-codex/gpt-5.4-mini", + summaryModel: "openai/gpt-5.4-mini", }, }, }, diff --git a/src/plugins/document-extractors.runtime.test.ts b/src/plugins/document-extractors.runtime.test.ts index 2db7140deb94..2a472a8d5da2 100644 --- a/src/plugins/document-extractors.runtime.test.ts +++ b/src/plugins/document-extractors.runtime.test.ts @@ -21,7 +21,7 @@ const mocks = vi.hoisted(() => ({ enabledByDefault: true, channels: [], cliBackends: [], - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], legacyPluginIds: [], contracts: {}, }, diff --git a/src/plugins/gateway-startup-plugin-ids.ts b/src/plugins/gateway-startup-plugin-ids.ts index f3f23a19813b..3a1d9405145b 100644 --- a/src/plugins/gateway-startup-plugin-ids.ts +++ b/src/plugins/gateway-startup-plugin-ids.ts @@ -59,7 +59,7 @@ const CORE_BUILT_IN_MODEL_APIS = new Set([ "google-generative-ai", "google-vertex", "mistral-conversations", - "openai-codex-responses", + "openai-chatgpt-responses", "openai-completions", "openai-responses", ]); diff --git a/src/plugins/hook-types.ts b/src/plugins/hook-types.ts index fed859a696bd..b1914b30901e 100644 --- a/src/plugins/hook-types.ts +++ b/src/plugins/hook-types.ts @@ -319,7 +319,7 @@ export type PluginHookLlmOutputEvent = { * Fully resolved provider/model ref used for the call. * * This intentionally keeps the provider prefix so operator tooling can - * distinguish e.g. openai-codex/gpt-5.4 from codex/gpt-5.4 even when display + * distinguish e.g. openai/gpt-5.4 from codex/gpt-5.4 even when display * names collapse to just the model id. */ resolvedRef?: string; diff --git a/src/plugins/installed-plugin-index.test.ts b/src/plugins/installed-plugin-index.test.ts index 119a1cf71b3d..fc961aea1e6e 100644 --- a/src/plugins/installed-plugin-index.test.ts +++ b/src/plugins/installed-plugin-index.test.ts @@ -676,7 +676,7 @@ describe("installed plugin index", () => { const config = { plugins: { entries: { - "openai-codex": { + openai: { enabled: false, }, }, diff --git a/src/plugins/manifest-registry.test.ts b/src/plugins/manifest-registry.test.ts index 77a0882e03e9..f7c343e28acf 100644 --- a/src/plugins/manifest-registry.test.ts +++ b/src/plugins/manifest-registry.test.ts @@ -702,7 +702,7 @@ describe("loadPluginManifestRegistry", () => { id: "openai", enabledByDefault: true, enabledByDefaultOnPlatforms: ["darwin", "not-a-platform"], - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], providerAuthEnvVars: { openai: ["OPENAI_API_KEY"], }, @@ -757,7 +757,7 @@ describe("loadPluginManifestRegistry", () => { syntheticAuthRefs: ["openai-cli"], nonSecretAuthMarkers: ["openai-cli"], providerAuthAliases: { - "openai-codex": "openai", + openai: "openai", }, providerAuthChoices: [ { @@ -822,7 +822,7 @@ describe("loadPluginManifestRegistry", () => { expect(registry.plugins[0]?.syntheticAuthRefs).toEqual(["openai-cli"]); expect(registry.plugins[0]?.nonSecretAuthMarkers).toEqual(["openai-cli"]); expect(registry.plugins[0]?.providerAuthAliases).toEqual({ - "openai-codex": "openai", + openai: "openai", }); expect(registry.plugins[0]?.enabledByDefault).toBe(true); expect(registry.plugins[0]?.enabledByDefaultOnPlatforms).toEqual(["darwin"]); @@ -1759,11 +1759,11 @@ describe("loadPluginManifestRegistry", () => { }, imageGenerationProviderMetadata: { openai: { - aliases: ["openai-codex"], + aliases: ["openai"], authProviders: ["openai"], authSignals: [ { - provider: "openai-codex", + provider: "openai", providerBaseUrl: { provider: "openai", defaultBaseUrl: "https://api.openai.com/v1", @@ -1817,7 +1817,7 @@ describe("loadPluginManifestRegistry", () => { optional: true, authSignals: [ { - provider: "openai-codex", + provider: "openai", }, ], configSignals: [ @@ -1840,11 +1840,11 @@ describe("loadPluginManifestRegistry", () => { expect(registry.plugins[0]?.imageGenerationProviderMetadata).toEqual({ openai: { - aliases: ["openai-codex"], + aliases: ["openai"], authProviders: ["openai"], authSignals: [ { - provider: "openai-codex", + provider: "openai", providerBaseUrl: { provider: "openai", defaultBaseUrl: "https://api.openai.com/v1", @@ -1892,7 +1892,7 @@ describe("loadPluginManifestRegistry", () => { optional: true, authSignals: [ { - provider: "openai-codex", + provider: "openai", }, ], configSignals: [ @@ -2143,9 +2143,9 @@ describe("loadPluginManifestRegistry", () => { const dir = makeTempDir(); writeManifest(dir, { id: "openai", - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], speechProviders: ["openai"], - mediaUnderstandingProviders: ["openai", "openai-codex"], + mediaUnderstandingProviders: ["openai", "openai"], imageGenerationProviders: ["openai"], configSchema: { type: "object" }, }); diff --git a/src/plugins/manifest.json5-tolerance.test.ts b/src/plugins/manifest.json5-tolerance.test.ts index 767bfed7d3e8..f857647d7040 100644 --- a/src/plugins/manifest.json5-tolerance.test.ts +++ b/src/plugins/manifest.json5-tolerance.test.ts @@ -153,7 +153,7 @@ describe("loadPluginManifest JSON5 tolerance", () => { id: "openai", activation: { onStartup: false, - onProviders: ["openai", "", "openai-codex"], + onProviders: ["openai", "", "openai"], onCommands: ["models", ""], onChannels: ["web", ""], onRoutes: ["gateway-webhook", ""], @@ -177,7 +177,7 @@ describe("loadPluginManifest JSON5 tolerance", () => { if (result.ok) { expect(result.manifest.activation).toEqual({ onStartup: false, - onProviders: ["openai", "openai-codex"], + onProviders: ["openai", "openai"], onCommands: ["models"], onChannels: ["web"], onRoutes: ["gateway-webhook"], diff --git a/src/plugins/plugin-lookup-table.test.ts b/src/plugins/plugin-lookup-table.test.ts index 80f071bbfeac..2849ebe9c72a 100644 --- a/src/plugins/plugin-lookup-table.test.ts +++ b/src/plugins/plugin-lookup-table.test.ts @@ -187,7 +187,7 @@ describe("loadPluginLookUpTable", () => { origin: "bundled", providers: ["openai"], providerAuthAliases: { - "openai-codex": "openai", + openai: "openai", }, modelCatalog: { aliases: { @@ -247,11 +247,11 @@ describe("loadPluginLookUpTable", () => { expect(table.metrics[metricName]).toBeGreaterThanOrEqual(0); } expect(table.byPluginId.get("telegram")?.id).toBe("telegram"); - expect(table.normalizePluginId("openai-codex")).toBe("openai"); + expect(table.normalizePluginId("openai")).toBe("openai"); expect(table.owners.channels.get("telegram")).toEqual(["telegram"]); expect(table.owners.channelConfigs.get("telegram")).toEqual(["telegram"]); expect(table.owners.providers.get("openai")).toEqual(["openai"]); - expect(table.owners.providers.get("openai-codex")).toEqual(["openai"]); + expect(table.owners.providers.get("openai")).toEqual(["openai"]); expect(table.owners.modelCatalogProviders.get("openai")).toEqual(["openai"]); expect(table.owners.modelCatalogProviders.get("azure-openai-responses")).toEqual(["openai"]); expect(table.owners.cliBackends.get("codex-cli")).toBeUndefined(); diff --git a/src/plugins/plugin-registry.test.ts b/src/plugins/plugin-registry.test.ts index 13a3ef0589d6..a110411e34c0 100644 --- a/src/plugins/plugin-registry.test.ts +++ b/src/plugins/plugin-registry.test.ts @@ -380,7 +380,7 @@ describe("plugin registry facade", () => { JSON.stringify({ id: "openai", configSchema: { type: "object" }, - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], channels: ["openai-chat"], }), "utf8", diff --git a/src/plugins/provider-openai-codex-oauth-tls.ts b/src/plugins/provider-openai-chatgpt-oauth-tls.ts similarity index 96% rename from src/plugins/provider-openai-codex-oauth-tls.ts rename to src/plugins/provider-openai-chatgpt-oauth-tls.ts index a51da75d267a..321b979a75c9 100644 --- a/src/plugins/provider-openai-codex-oauth-tls.ts +++ b/src/plugins/provider-openai-chatgpt-oauth-tls.ts @@ -23,6 +23,8 @@ const TLS_CERT_ERROR_PATTERNS = [ const OPENAI_AUTH_PROBE_URL = "https://auth.openai.com/oauth/authorize?response_type=code&client_id=openclaw-preflight&redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback&scope=openid+profile+email"; +const OPENAI_PROVIDER_ID = "openai"; +const LEGACY_OPENAI_PROVIDER_ID = ["openai", "codex"].join("-"); type PreflightFailureKind = "tls-cert" | "network"; @@ -84,7 +86,7 @@ function hasOpenAICodexOAuthProfile(cfg: OpenClawConfig): boolean { } return Object.values(profiles).some( (profile) => - (profile.provider === "openai" || profile.provider === "openai-codex") && + (profile.provider === OPENAI_PROVIDER_ID || profile.provider === LEGACY_OPENAI_PROVIDER_ID) && profile.mode === "oauth", ); } diff --git a/src/plugins/provider-openai-codex-oauth.test.ts b/src/plugins/provider-openai-chatgpt-oauth.test.ts similarity index 96% rename from src/plugins/provider-openai-codex-oauth.test.ts rename to src/plugins/provider-openai-chatgpt-oauth.test.ts index dc1d3cdb1242..0d9113bf4090 100644 --- a/src/plugins/provider-openai-codex-oauth.test.ts +++ b/src/plugins/provider-openai-chatgpt-oauth.test.ts @@ -18,7 +18,7 @@ vi.mock("../plugin-sdk/facade-runtime.js", () => ({ providerRuntimeMocks.loadActivatedBundledPluginPublicSurfaceModuleSync, })); -import { loginOpenAICodexOAuth } from "./provider-openai-codex-oauth.js"; +import { loginOpenAICodexOAuth } from "./provider-openai-chatgpt-oauth.js"; function createPrompter(): WizardPrompter { const spin = { update: vi.fn(), stop: vi.fn() }; @@ -47,7 +47,7 @@ function createRuntime(): RuntimeEnv { function createCredential() { return { type: "oauth" as const, - provider: "openai-codex", + provider: "openai", access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, @@ -81,7 +81,7 @@ describe("loginOpenAICodexOAuth", () => { const controller = new AbortController(); const onManualCodeInput = vi.fn(async () => "manual-code"); providerRuntimeMocks.runOAuth.mockResolvedValueOnce({ - profiles: [{ profileId: "openai-codex:user@example.com", credential }], + profiles: [{ profileId: "openai:user@example.com", credential }], }); const result = await loginOpenAICodexOAuth({ diff --git a/src/plugins/provider-openai-codex-oauth.ts b/src/plugins/provider-openai-chatgpt-oauth.ts similarity index 100% rename from src/plugins/provider-openai-codex-oauth.ts rename to src/plugins/provider-openai-chatgpt-oauth.ts diff --git a/src/plugins/provider-public-artifacts.test.ts b/src/plugins/provider-public-artifacts.test.ts index 53356bac4fe8..c0bb2b3d92a2 100644 --- a/src/plugins/provider-public-artifacts.test.ts +++ b/src/plugins/provider-public-artifacts.test.ts @@ -53,7 +53,7 @@ describe("provider public artifacts", () => { JSON.stringify({ id: "openai", configSchema: { type: "object" }, - providers: ["openai", "openai-codex"], + providers: ["openai", "openai"], }), ); fs.writeFileSync( @@ -90,7 +90,7 @@ describe("provider public artifacts", () => { typeof import("./provider-public-artifacts.js") >(import.meta.url, "./provider-public-artifacts.js?scope=provider-alias"); - const surface = resolvePolicySurface("openai-codex"); + const surface = resolvePolicySurface("openai"); expect(surface?.resolveThinkingProfile).toBeTypeOf("function"); expect(loadBundledPluginPublicArtifactModuleSync).toHaveBeenCalledWith({ @@ -100,7 +100,7 @@ describe("provider public artifacts", () => { expect( surface ?.resolveThinkingProfile?.({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.5", }) ?.levels.map((level) => level.id), @@ -108,7 +108,7 @@ describe("provider public artifacts", () => { expect( surface ?.resolveThinkingProfile?.({ - provider: "openai-codex", + provider: "openai", modelId: "gpt-4.1", }) ?.levels.map((level) => level.id), @@ -148,7 +148,7 @@ describe("provider public artifacts", () => { typeof import("./provider-public-artifacts.js") >(import.meta.url, "./provider-public-artifacts.js?scope=provider-auth-alias"); - const surface = resolvePolicySurface("openai-codex", { + const surface = resolvePolicySurface("openai", { manifestRegistry: { plugins: [ { @@ -159,7 +159,7 @@ describe("provider public artifacts", () => { origin: "bundled", manifestPath: "/tmp/openai/openclaw.plugin.json", providers: ["openai"], - providerAuthAliases: { "openai-codex": "openai" }, + providerAuthAliases: { openai: "openai" }, rootDir: "/tmp/openai", skills: [], source: "/tmp/openai/index.js", @@ -168,10 +168,8 @@ describe("provider public artifacts", () => { }, }); - expect( - surface?.resolveThinkingProfile?.({ provider: "openai-codex", modelId: "gpt-5.5" }), - ).toEqual({ - levels: [{ id: "openai-codex" }], + expect(surface?.resolveThinkingProfile?.({ provider: "openai", modelId: "gpt-5.5" })).toEqual({ + levels: [{ id: "openai" }], }); expect(loadBundledPluginPublicArtifactModuleSync).toHaveBeenCalledWith({ dirName: "openai", diff --git a/src/plugins/provider-replay-helpers.ts b/src/plugins/provider-replay-helpers.ts index 46765d63e7a6..ec0819924304 100644 --- a/src/plugins/provider-replay-helpers.ts +++ b/src/plugins/provider-replay-helpers.ts @@ -22,7 +22,7 @@ export function buildOpenAICompatibleReplayPolicy( if ( modelApi !== "openai-completions" && modelApi !== "openai-responses" && - modelApi !== "openai-codex-responses" && + modelApi !== "openai-chatgpt-responses" && modelApi !== "azure-openai-responses" ) { return undefined; @@ -32,7 +32,7 @@ export function buildOpenAICompatibleReplayPolicy( const dropReasoningFromHistory = options.dropReasoningFromHistory ?? true; const isResponsesFamily = modelApi === "openai-responses" || - modelApi === "openai-codex-responses" || + modelApi === "openai-chatgpt-responses" || modelApi === "azure-openai-responses"; return { diff --git a/src/plugins/provider-runtime.test-support.ts b/src/plugins/provider-runtime.test-support.ts index 6e2ec2f8acc0..93c57de678d5 100644 --- a/src/plugins/provider-runtime.test-support.ts +++ b/src/plugins/provider-runtime.test-support.ts @@ -9,7 +9,7 @@ const openaiCodexCatalogEntries = [ { provider: "openai", id: "gpt-5.2-pro", name: "GPT-5.2 Pro" }, { provider: "openai", id: "gpt-5-mini", name: "GPT-5 mini" }, { provider: "openai", id: "gpt-5-nano", name: "GPT-5 nano" }, - { provider: "openai-codex", id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, + { provider: "openai", id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, ]; export const expectedAugmentedOpenaiCodexCatalogEntries = [ @@ -46,7 +46,7 @@ export function expectCodexMissingAuthHint( context: { env: process.env, provider: "openai", - listProfileIds: (providerId) => (providerId === "openai-codex" ? ["p1"] : []), + listProfileIds: (providerId) => (providerId === "openai" ? ["p1"] : []), }, }), ).toContain(expectedModel); diff --git a/src/plugins/provider-runtime.test.ts b/src/plugins/provider-runtime.test.ts index 6e7afb17cffc..69264db50511 100644 --- a/src/plugins/provider-runtime.test.ts +++ b/src/plugins/provider-runtime.test.ts @@ -1253,7 +1253,7 @@ describe("provider-runtime", () => { it("keeps OpenAI plugin personality fallback for OpenAI-family GPT-5 providers", () => { const contribution = resolveProviderSystemPromptContribution({ - provider: "openai-codex", + provider: "openai", config: { plugins: { entries: { @@ -1262,7 +1262,7 @@ describe("provider-runtime", () => { }, }, context: { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", promptMode: "full", } as never, @@ -1823,7 +1823,7 @@ describe("provider-runtime", () => { shouldDeferSyntheticProfileAuth, normalizeResolvedModel: ({ model }) => ({ ...model, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", }), formatApiKey: (cred) => cred.type === "oauth" ? JSON.stringify({ token: cred.access }) : "", @@ -2119,7 +2119,7 @@ describe("provider-runtime", () => { }), ).toEqual({ ...MODEL, - api: "openai-codex-responses", + api: "openai-chatgpt-responses", }); expect( @@ -2411,7 +2411,7 @@ describe("provider-runtime", () => { { provider: "openai", id: "gpt-5.4-pro", name: "GPT-5.2 Pro" }, { provider: "openai", id: "gpt-5.4-mini", name: "GPT-5 mini" }, { provider: "openai", id: "gpt-5.4-nano", name: "GPT-5 nano" }, - { provider: "openai-codex", id: "gpt-5.4", name: "GPT-5.4" }, + { provider: "openai", id: "gpt-5.4", name: "GPT-5.4" }, ], }, }), diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index 627290d5c53d..5b9bfe098df4 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -97,7 +97,7 @@ function setOwningProviderManifestPlugins() { }), createManifestProviderPlugin({ id: "openai", - providerIds: ["openai", "openai-codex"], + providerIds: ["openai", "openai"], modelSupport: { modelPrefixes: ["gpt-", "o1", "o3", "o4"], }, @@ -121,7 +121,7 @@ function setOwningProviderManifestPluginsWithWorkspace() { }), createManifestProviderPlugin({ id: "openai", - providerIds: ["openai", "openai-codex"], + providerIds: ["openai", "openai"], modelSupport: { modelPrefixes: ["gpt-", "o1", "o3", "o4"], }, @@ -611,12 +611,12 @@ describe("resolvePluginProviders", () => { const plugins = [ createManifestProviderPlugin({ id: "openai", - providerIds: ["openai", "openai-codex"], + providerIds: ["openai", "openai"], }), ]; getCurrentPluginMetadataSnapshotMock.mockReturnValue(createMetadataSnapshotFixture(plugins)); - expectOwningPluginIds("openai-codex", ["openai"]); + expectOwningPluginIds("openai", ["openai"]); expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled(); expect(getCurrentPluginMetadataSnapshotMock).toHaveBeenCalledWith({ @@ -703,14 +703,14 @@ describe("resolvePluginProviders", () => { id: "openai", providerIds: ["openai"], providerAuthAliases: { - "openai-codex": "openai", + openai: "openai", }, }), ]); expectOwningPluginIds("openai", ["openai"]); - expectOwningPluginIds("openai-codex", ["openai"]); - expectModelOwningPluginIds("openai-codex/gpt-5.5", ["openai"]); + expectOwningPluginIds("openai", ["openai"]); + expectModelOwningPluginIds("openai/gpt-5.5", ["openai"]); }); it("reflects provider ownership manifest changes on the next lookup", () => { @@ -1216,7 +1216,7 @@ describe("resolvePluginProviders", () => { resolvePluginProviders({ config: {}, - providerRefs: ["openai-codex"], + providerRefs: ["openai"], activate: true, }); @@ -1634,7 +1634,7 @@ describe("resolvePluginProviders", () => { expectedPluginIds: ["minimax"], }, { - provider: "openai-codex", + provider: "openai", expectedPluginIds: ["openai"], }, { @@ -1778,7 +1778,7 @@ describe("resolvePluginProviders", () => { setManifestPlugins([ createManifestProviderPlugin({ id: "openai", - providerIds: ["openai", "openai-codex"], + providerIds: ["openai", "openai"], modelSupport: { modelPrefixes: ["gpt-", "o1", "o3", "o4"], }, diff --git a/src/plugins/runtime/runtime-llm.runtime.test.ts b/src/plugins/runtime/runtime-llm.runtime.test.ts index e890484fe19d..499d2690cc63 100644 --- a/src/plugins/runtime/runtime-llm.runtime.test.ts +++ b/src/plugins/runtime/runtime-llm.runtime.test.ts @@ -178,7 +178,7 @@ describe("runtime.llm.complete", () => { const runtimeContext = resolveContextEngineCapabilities({ config: cfg, sessionKey: "agent:ada:session:abc", - authProfileId: "openai-codex:claude@martian.engineering", + authProfileId: "openai:claude@martian.engineering", purpose: "context-engine.compaction", }); @@ -189,7 +189,7 @@ describe("runtime.llm.complete", () => { expectSingleCallFirstArg(hoisted.prepareSimpleCompletionModelForAgent, { cfg, agentId: "ada", - preferredProfile: "openai-codex:claude@martian.engineering", + preferredProfile: "openai:claude@martian.engineering", allowBundledStaticCatalogFallback: true, allowMissingApiKeyModes: ["aws-sdk"], skipAgentDiscovery: true, @@ -256,7 +256,7 @@ describe("runtime.llm.complete", () => { await expect( runtimeContext.llm!.complete({ - model: "openai-codex/gpt-5.4-mini", + model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "summarize" }], }), ).rejects.toThrow("cannot override the target model"); @@ -272,7 +272,7 @@ describe("runtime.llm.complete", () => { "lossless-claw": { llm: { allowModelOverride: true, - allowedModels: ["openai-codex/gpt-5.4-mini", "minimax/MiniMax-M2.7"], + allowedModels: ["openai/gpt-5.4-mini", "minimax/MiniMax-M2.7"], }, }, }, @@ -285,13 +285,13 @@ describe("runtime.llm.complete", () => { const result = await runtimeContext.llm!.complete({ agentId: "main", - model: "openai-codex/gpt-5.4-mini", + model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "summarize" }], }); expectSingleCallFirstArg(hoisted.prepareSimpleCompletionModelForAgent, { agentId: "main", - modelRef: "openai-codex/gpt-5.4-mini", + modelRef: "openai/gpt-5.4-mini", }); expectFields(requireRecord(result.audit, "audit"), { caller: { kind: "context-engine", id: "context-engine.compaction" }, @@ -308,7 +308,7 @@ describe("runtime.llm.complete", () => { "lossless-claw": { llm: { allowModelOverride: true, - allowedModels: ["openai-codex/gpt-5.4-mini"], + allowedModels: ["openai/gpt-5.4-mini"], }, }, }, @@ -321,11 +321,11 @@ describe("runtime.llm.complete", () => { await expect( runtimeContext.llm!.complete({ - model: "openai-codex/gpt-5.5", + model: "openai/gpt-5.5", messages: [{ role: "user", content: "summarize" }], }), ).rejects.toThrow( - 'model override "openai-codex/gpt-5.5" is not allowlisted for plugin "lossless-claw"', + 'model override "openai/gpt-5.5" is not allowlisted for plugin "lossless-claw"', ); expect(hoisted.prepareSimpleCompletionModelForAgent).not.toHaveBeenCalled(); }); @@ -425,7 +425,7 @@ describe("runtime.llm.complete", () => { "lossless-claw": { llm: { allowModelOverride: true, - allowedModels: ["openai-codex/gpt-5.4-mini"], + allowedModels: ["openai/gpt-5.4-mini"], }, }, }, @@ -438,7 +438,7 @@ describe("runtime.llm.complete", () => { const result = await withPluginRuntimePluginIdScope("spoofed-plugin", () => runtimeContext.llm!.complete({ - model: "openai-codex/gpt-5.4-mini", + model: "openai/gpt-5.4-mini", messages: [{ role: "user", content: "summarize" }], caller: { kind: "plugin", id: "spoofed-plugin" }, } as Parameters["complete"]>[0] & { @@ -451,7 +451,7 @@ describe("runtime.llm.complete", () => { id: "context-engine.compaction", }); expectSingleCallFirstArg(hoisted.prepareSimpleCompletionModelForAgent, { - modelRef: "openai-codex/gpt-5.4-mini", + modelRef: "openai/gpt-5.4-mini", }); }); @@ -510,7 +510,7 @@ describe("runtime.llm.complete", () => { }); await llm.complete({ - authProfileId: "openai-codex:work", + authProfileId: "openai:work", messages: [{ role: "user", content: "draft" }], } as Parameters[0] & { authProfileId: string }); diff --git a/src/plugins/setup-registry.test.ts b/src/plugins/setup-registry.test.ts index 4e093bb506f0..4cd8558570d1 100644 --- a/src/plugins/setup-registry.test.ts +++ b/src/plugins/setup-registry.test.ts @@ -462,7 +462,7 @@ describe("setup-registry module loader", () => { { id: "openai", rootDir: pluginRoot, - providerAuthAliases: { "openai-codex": "openai" }, + providerAuthAliases: { openai: "openai" }, setup: { providers: [{ id: "openai" }], requiresRuntime: true, @@ -484,7 +484,7 @@ describe("setup-registry module loader", () => { }) { api.registerProvider({ id: "openai", - aliases: ["openai-codex"], + aliases: ["openai"], label: "OpenAI", auth: [], }); @@ -493,9 +493,7 @@ describe("setup-registry module loader", () => { }); }); - const provider = requireRecord( - resolvePluginSetupProvider({ provider: "openai-codex", env: {} }), - ); + const provider = requireRecord(resolvePluginSetupProvider({ provider: "openai", env: {} })); expect(provider.id).toBe("openai"); expect(provider.label).toBe("OpenAI"); }); diff --git a/src/scripts/test-projects.test.ts b/src/scripts/test-projects.test.ts index ba4ba32247f7..39a528068517 100644 --- a/src/scripts/test-projects.test.ts +++ b/src/scripts/test-projects.test.ts @@ -929,11 +929,11 @@ describe("test-projects args", () => { }); it("routes direct OpenAI provider extension file targets to the OpenAI provider config", () => { - expect(buildVitestRunPlans(["extensions/openai/openai-codex-provider.test.ts"])).toEqual([ + expect(buildVitestRunPlans(["extensions/openai/openai-chatgpt-provider.test.ts"])).toEqual([ { config: "test/vitest/vitest.extension-provider-openai.config.ts", forwardedArgs: [], - includePatterns: ["extensions/openai/openai-codex-provider.test.ts"], + includePatterns: ["extensions/openai/openai-chatgpt-provider.test.ts"], watchMode: false, }, ]); diff --git a/src/secrets/apply.test.ts b/src/secrets/apply.test.ts index 1d10220765e6..4d15c6a35536 100644 --- a/src/secrets/apply.test.ts +++ b/src/secrets/apply.test.ts @@ -540,7 +540,7 @@ describe("secrets apply", () => { it("preserves unrelated oauth profiles while applying auth-profile key ref targets", async () => { const codexOAuthRef = { id: "codex-sidecar-ref", - provider: "openai-codex", + provider: "openai", }; await writeJsonFile(fixture.authStorePath, { version: 1, @@ -550,9 +550,9 @@ describe("secrets apply", () => { provider: "openai", key: "sk-openai-static", // pragma: allowlist secret }, - "openai-codex:sidecar": { + "openai:sidecar": { type: "oauth", - provider: "openai-codex", + provider: "openai", oauthRef: codexOAuthRef, email: "codex@example.invalid", }, @@ -562,13 +562,11 @@ describe("secrets apply", () => { }, }, order: { - openai: ["openai:static"], - "openai-codex": ["openai-codex:sidecar"], + openai: ["openai:sidecar", "openai:static"], "claude-cli": ["anthropic:claude-cli"], }, lastGood: { - openai: "openai:static", - "openai-codex": "openai-codex:sidecar", + openai: "openai:sidecar", "claude-cli": "anthropic:claude-cli", }, }); @@ -609,14 +607,14 @@ describe("secrets apply", () => { }; expect(Object.keys(nextAuthStore.profiles).toSorted()).toEqual([ "anthropic:claude-cli", - "openai-codex:sidecar", + "openai:sidecar", "openai:static", ]); expect(nextAuthStore.profiles["openai:static"].key).toBeUndefined(); expect(nextAuthStore.profiles["openai:static"].keyRef).toEqual(OPENAI_API_KEY_ENV_REF); - expect(nextAuthStore.profiles["openai-codex:sidecar"]).toMatchObject({ + expect(nextAuthStore.profiles["openai:sidecar"]).toMatchObject({ type: "oauth", - provider: "openai-codex", + provider: "openai", oauthRef: codexOAuthRef, email: "codex@example.invalid", }); @@ -624,7 +622,7 @@ describe("secrets apply", () => { provider: "claude-cli", mode: "oauth", }); - expect(nextAuthStore.order?.["openai-codex"]).toEqual(["openai-codex:sidecar"]); + expect(nextAuthStore.order?.["openai"]).toEqual(["openai:sidecar", "openai:static"]); expect(nextAuthStore.lastGood?.["claude-cli"]).toBe("anthropic:claude-cli"); }); diff --git a/src/secrets/provider-env-vars.dynamic.test.ts b/src/secrets/provider-env-vars.dynamic.test.ts index 812e4071b020..d414a487eac3 100644 --- a/src/secrets/provider-env-vars.dynamic.test.ts +++ b/src/secrets/provider-env-vars.dynamic.test.ts @@ -134,8 +134,8 @@ describe("provider env vars dynamic manifest metadata", () => { expect(listKnownSecretEnvVarNames()).toContain("FIREWORKS_ALT_API_KEY"); }); - it("lets openai-codex bootstrap from Codex app-server API-key env", () => { - expect(resolveProviderAuthEnvVarCandidates()["openai-codex"]).toEqual([ + it("lets openai bootstrap from Codex app-server API-key env", () => { + expect(resolveProviderAuthEnvVarCandidates()["openai"]).toEqual([ "CODEX_API_KEY", "OPENAI_API_KEY", ]); diff --git a/src/secrets/provider-env-vars.test.ts b/src/secrets/provider-env-vars.test.ts index 146721e3f604..22913c56a738 100644 --- a/src/secrets/provider-env-vars.test.ts +++ b/src/secrets/provider-env-vars.test.ts @@ -49,7 +49,7 @@ describe("provider env vars", () => { it("ignores prototype-chain keys when resolving provider env vars", () => { expect(getProviderEnvVars("__proto__")).toStrictEqual([]); expect(getProviderEnvVars("constructor")).toStrictEqual([]); - expect(getProviderEnvVars("openai")).toEqual(["OPENAI_API_KEY"]); + expect(getProviderEnvVars("openai")).toEqual(["CODEX_API_KEY", "OPENAI_API_KEY"]); expect(getProviderEnvVars("anthropic")).toEqual(["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]); expect(getProviderEnvVars("fal")).toEqual(["FAL_KEY", "FAL_API_KEY"]); }); diff --git a/src/secrets/provider-env-vars.ts b/src/secrets/provider-env-vars.ts index 207a57dc4970..adec1800394b 100644 --- a/src/secrets/provider-env-vars.ts +++ b/src/secrets/provider-env-vars.ts @@ -19,8 +19,7 @@ import { uniqueStrings } from "../shared/string-normalization.js"; const CORE_PROVIDER_AUTH_ENV_VAR_CANDIDATES = { anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"], - openai: ["OPENAI_API_KEY"], - "openai-codex": ["CODEX_API_KEY", "OPENAI_API_KEY"], + openai: ["CODEX_API_KEY", "OPENAI_API_KEY"], voyage: ["VOYAGE_API_KEY"], cerebras: ["CEREBRAS_API_KEY"], "anthropic-openai": ["ANTHROPIC_API_KEY"], diff --git a/src/secrets/provider-integrations.test.ts b/src/secrets/provider-integrations.test.ts index a5df4e43d85d..3f9d0d0e7621 100644 --- a/src/secrets/provider-integrations.test.ts +++ b/src/secrets/provider-integrations.test.ts @@ -411,7 +411,7 @@ describe("secret provider integration presets", () => { const config = { plugins: { entries: { - "openai-codex": { + openai: { enabled: false, }, }, diff --git a/src/secrets/runtime.fast-path.test.ts b/src/secrets/runtime.fast-path.test.ts index bfa6b050ad7e..ad5968b6ccad 100644 --- a/src/secrets/runtime.fast-path.test.ts +++ b/src/secrets/runtime.fast-path.test.ts @@ -212,7 +212,7 @@ describe("secrets runtime fast path", () => { writeFileSync( credentialsPath, `${JSON.stringify({ - "openai-codex": { + openai: { access: "access-token", refresh: "refresh-token", expires: Date.now() + 60_000, diff --git a/src/security/audit-model-hygiene.test.ts b/src/security/audit-model-hygiene.test.ts index fc67db3d7687..3f83ddb7a1b1 100644 --- a/src/security/audit-model-hygiene.test.ts +++ b/src/security/audit-model-hygiene.test.ts @@ -62,8 +62,8 @@ describe("security audit model hygiene findings", () => { fallbacks: ["gpt-prev", "gpt-mini"], }, models: { - "openai-codex/gpt-5.5": { alias: "gpt" }, - "openai-codex/gpt-5.4": { alias: "gpt-prev" }, + "openai/gpt-5.5": { alias: "gpt" }, + "openai/gpt-5.4": { alias: "gpt-prev" }, "openai/gpt-5-mini": { alias: "gpt-mini" }, }, }, diff --git a/src/status/status-message.ts b/src/status/status-message.ts index 21776951d111..d8463ac94606 100644 --- a/src/status/status-message.ts +++ b/src/status/status-message.ts @@ -147,7 +147,7 @@ function resolveConfiguredTextVerbosity(params: { }): "low" | "medium" | "high" | undefined { const provider = params.provider?.trim(); const model = params.model?.trim(); - if (!provider || !model || (provider !== "openai" && provider !== "openai-codex")) { + if (!provider || !model || provider !== "openai") { return undefined; } return resolveOpenAITextVerbosity( diff --git a/src/status/status-text.ts b/src/status/status-text.ts index 3eeaa0aa058a..30a1fc0387db 100644 --- a/src/status/status-text.ts +++ b/src/status/status-text.ts @@ -15,7 +15,7 @@ import { shouldPreferActiveRuntimeAliasAuthLabel, } from "../agents/model-runtime-aliases.js"; import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; -import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../agents/openai-codex-routing.js"; +import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../agents/openai-routing.js"; import { resolveInternalSessionKey, resolveMainSessionAlias, @@ -49,7 +49,7 @@ const USAGE_OAUTH_ONLY_PROVIDERS = new Set([ "anthropic", "github-copilot", "google-gemini-cli", - "openai-codex", + "openai", ]); let statusMessageRuntimePromise: Promise | null = @@ -115,6 +115,23 @@ function shouldLoadUsageSummary(params: { return Boolean(auth?.startsWith("oauth") || auth?.startsWith("token")); } +function resolveUsageCredentialType(authLabel?: string): "oauth" | "token" | "api_key" | undefined { + const auth = normalizeOptionalLowercaseString(authLabel); + if (!auth) { + return undefined; + } + if (auth.startsWith("oauth")) { + return "oauth"; + } + if (auth.startsWith("token")) { + return "token"; + } + if (auth.startsWith("api-key") || auth.startsWith("api key")) { + return "api_key"; + } + return undefined; +} + function formatSessionTaskLine(sessionKey: string): string | undefined { const snapshot = buildTaskStatusSnapshot(listTasksForSessionKeyForStatus(sessionKey)); const task = snapshot.focus; @@ -165,7 +182,7 @@ function resolveStatusRuntimeProvider(params: { const harness = normalizeOptionalLowercaseString(params.effectiveHarness); const provider = normalizeOptionalLowercaseString(params.provider); if (harness === "codex" && (provider === "openai" || provider === "codex")) { - return "openai-codex"; + return "openai"; } if (harness === "claude-cli" && provider === "anthropic") { return "claude-cli"; @@ -293,8 +310,10 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise { runAuthFlow, }); - await handleCommand("/auth openai-codex"); + await handleCommand("/auth openai"); - expect(runAuthFlow).toHaveBeenCalledWith({ provider: "openai-codex" }); + expect(runAuthFlow).toHaveBeenCalledWith({ provider: "openai" }); expect(refreshSessionInfo).toHaveBeenCalledTimes(1); expect(addSystem).toHaveBeenCalledWith( - "opening auth flow for openai-codex; TUI will resume when it exits", + "opening auth flow for openai; TUI will resume when it exits", ); - expect(addSystem).toHaveBeenCalledWith("auth flow finished for openai-codex"); + expect(addSystem).toHaveBeenCalledWith("auth flow finished for openai"); expect(setActivityStatus).toHaveBeenLastCalledWith("idle"); }); @@ -846,7 +846,7 @@ describe("tui command handlers", () => { runAuthFlow, }); - await handleCommand("/auth openai-codex"); + await handleCommand("/auth openai"); expect(runAuthFlow).not.toHaveBeenCalled(); expect(addSystem).toHaveBeenCalledWith("abort the current run before /auth"); diff --git a/src/tui/tui-event-handlers.test.ts b/src/tui/tui-event-handlers.test.ts index 2f568c5af478..0afed8d3d423 100644 --- a/src/tui/tui-event-handlers.test.ts +++ b/src/tui/tui-event-handlers.test.ts @@ -334,7 +334,7 @@ describe("tui-event-handlers: handleAgentEvent", () => { data: { phase: "fallback_step", fallbackStepFinalOutcome: "next_fallback", - fallbackStepFromModel: "openai-codex/gpt-5.5", + fallbackStepFromModel: "openai/gpt-5.5", fallbackStepToModel: "openrouter/meta-llama/llama-3.1-70b", }, }); @@ -1173,7 +1173,7 @@ describe("tui-event-handlers: handleAgentEvent", () => { localMode: true, state: { activeChatRunId: null, - sessionInfo: { modelProvider: "openai-codex" }, + sessionInfo: { modelProvider: "openai" }, }, }); @@ -1186,7 +1186,7 @@ describe("tui-event-handlers: handleAgentEvent", () => { }); expect(chatLog.addSystem).toHaveBeenCalledWith( - "auth or provider access failed for openai-codex. Run /auth openai-codex to refresh credentials; if you already re-authed, switch models/providers because this account may still be blocked for inference.", + "auth or provider access failed for openai. Run /auth openai to refresh credentials; if you already re-authed, switch models/providers because this account may still be blocked for inference.", ); }); diff --git a/src/tui/tui.ts b/src/tui/tui.ts index 74eb5ed098b9..5bd567ffbf40 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -78,7 +78,7 @@ const OPENCLAW_DIST_ENTRY_MJS_PATH = fileURLToPath( new URL("../../dist/entry.mjs", import.meta.url), ); -const OPENAI_CODEX_PROVIDER = "openai-codex"; +const OPENAI_CODEX_PROVIDER = "openai"; type RunTuiOptions = TuiOptions & { backend?: TuiBackend; diff --git a/src/wizard/setup.test.ts b/src/wizard/setup.test.ts index fde610b1dcd8..5a36c6e991c6 100644 --- a/src/wizard/setup.test.ts +++ b/src/wizard/setup.test.ts @@ -944,7 +944,7 @@ describe("runSetupWizard", () => { agents: { defaults: { model: { - primary: "openai-codex/gpt-5.5", + primary: "openai/gpt-5.5", }, }, }, @@ -958,7 +958,7 @@ describe("runSetupWizard", () => { { acceptRisk: true, flow: "quickstart", - authChoice: "openai-codex-api-key", + authChoice: "openai-chatgpt-api-key", openaiApiKey: "sk-flag-value", installDaemon: false, skipChannels: true, @@ -973,7 +973,7 @@ describe("runSetupWizard", () => { ); expect(applyAuthChoice).toHaveBeenCalledTimes(1); - const call = getMockCallArg(applyAuthChoice, 0, 0, "openai-codex auth choice"); + const call = getMockCallArg(applyAuthChoice, 0, 0, "openai auth choice"); const opts = (call as { opts?: Record }).opts ?? {}; expect(opts.openaiApiKey).toBe("sk-flag-value"); }); @@ -1238,13 +1238,13 @@ describe("runSetupWizard", () => { resolvePluginProvidersRuntime.mockClear(); resolveManifestProviderAuthChoice.mockReturnValue({ pluginId: "openai", - providerId: "openai-codex", + providerId: "openai", methodId: "oauth", - choiceId: "openai-codex", + choiceId: "openai", choiceLabel: "ChatGPT/Codex Browser Login", }); resolvePluginSetupProvider.mockReturnValue({ - id: "openai-codex", + id: "openai", label: "OpenAI Codex", auth: [ { @@ -1260,7 +1260,7 @@ describe("runSetupWizard", () => { }, ], }); - promptAuthChoiceGrouped.mockResolvedValueOnce("openai-codex"); + promptAuthChoiceGrouped.mockResolvedValueOnce("openai"); const prompter = buildWizardPrompter({}); const runtime = createRuntime(); @@ -1281,7 +1281,7 @@ describe("runSetupWizard", () => { expectRecordFields( getMockCallArg(resolvePluginSetupProvider, 0, 0, "plugin setup provider"), { - provider: "openai-codex", + provider: "openai", pluginIds: ["openai"], }, "plugin setup provider params", diff --git a/test/helpers/agents/transport-params-runtime-contract.ts b/test/helpers/agents/transport-params-runtime-contract.ts index 89457a574201..b5657cc1718a 100644 --- a/test/helpers/agents/transport-params-runtime-contract.ts +++ b/test/helpers/agents/transport-params-runtime-contract.ts @@ -9,7 +9,7 @@ export const OPENAI_GPT5_TRANSPORT_DEFAULT_CASES = [ modelId: "gpt-5.4", }, { - provider: "openai-codex", + provider: "openai", modelId: "gpt-5.4", }, ] as const; @@ -22,7 +22,7 @@ export const NON_OPENAI_GPT5_TRANSPORT_CASE = { export const GPT_PARALLEL_TOOL_CALLS_PAYLOAD_APIS = [ "openai-completions", "openai-responses", - "openai-codex-responses", + "openai-chatgpt-responses", "azure-openai-responses", ] as const; diff --git a/test/helpers/auto-reply/trigger-handling-test-harness.ts b/test/helpers/auto-reply/trigger-handling-test-harness.ts index 5fc755931018..ed7858ed2cf0 100644 --- a/test/helpers/auto-reply/trigger-handling-test-harness.ts +++ b/test/helpers/auto-reply/trigger-handling-test-harness.ts @@ -113,7 +113,7 @@ const DEFAULT_MODEL_CATALOG = [ }, { provider: "openai", id: "gpt-5.5-mini", name: "GPT-5.5 mini" }, { provider: "openai", id: "gpt-5.5", name: "GPT-5.5" }, - { provider: "openai-codex", id: "gpt-5.5", name: "GPT-5.5 (Codex)" }, + { provider: "openai", id: "gpt-5.5", name: "GPT-5.5 (Codex)" }, { provider: "minimax", id: "MiniMax-M2.7", name: "MiniMax M2.7" }, ]; diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 465e540d4803..9bab6a69e8ff 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -760,7 +760,7 @@ describe("package artifact reuse", () => { ); expect(scheduler).toContain("function liveDockerHarnessScriptCommand"); expect(scheduler).toContain('liveDockerHarnessScriptCommand("test-live-build-docker.sh")'); - expect(liveDockerAuth).toContain("codex-cli | openai | openai-codex)"); + expect(liveDockerAuth).toContain("codex-cli | openai)"); expect(liveDockerAuth).toContain("openclaw_live_init_docker_run_args()"); expect(liveDockerAuth).toContain( 'timeout_value="${2:-${OPENCLAW_LIVE_DOCKER_RUN_TIMEOUT:-2700s}}"', diff --git a/test/vitest-scoped-config.test.ts b/test/vitest-scoped-config.test.ts index 8bad8bd37a1e..991a516591f9 100644 --- a/test/vitest-scoped-config.test.ts +++ b/test/vitest-scoped-config.test.ts @@ -708,7 +708,7 @@ describe("scoped vitest configs", () => { const extensionExcludes = defaultExtensionsConfig.test?.exclude ?? []; expect( extensionExcludes.some((pattern) => - path.matchesGlob("openai/openai-codex-provider.test.ts", pattern), + path.matchesGlob("openai/openai-chatgpt-provider.test.ts", pattern), ), ).toBe(true); }); diff --git a/ui/src/ui/chat-model-ref.test.ts b/ui/src/ui/chat-model-ref.test.ts index 51ab690263e3..2449b9a1f064 100644 --- a/ui/src/ui/chat-model-ref.test.ts +++ b/ui/src/ui/chat-model-ref.test.ts @@ -31,9 +31,7 @@ describe("chat-model-ref helpers", () => { }); it("preserves already-qualified model refs without prepending provider", () => { - expect(resolveServerChatModelValue("ollama/qwen3:30b", "openai-codex")).toBe( - "ollama/qwen3:30b", - ); + expect(resolveServerChatModelValue("ollama/qwen3:30b", "openai")).toBe("ollama/qwen3:30b"); }); it("prefixes provider-native catalog ids that already contain slashes", () => { diff --git a/ui/src/ui/chat/slash-command-executor.node.test.ts b/ui/src/ui/chat/slash-command-executor.node.test.ts index 1dd81aab6e69..33e12f814e0e 100644 --- a/ui/src/ui/chat/slash-command-executor.node.test.ts +++ b/ui/src/ui/chat/slash-command-executor.node.test.ts @@ -508,7 +508,7 @@ describe("executeSlashCommand directives", () => { if (method === "sessions.list") { return { defaults: { - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", thinkingLevels: [ { id: "off", label: "off" }, @@ -537,7 +537,7 @@ describe("executeSlashCommand directives", () => { } if (method === "models.list") { return { - models: [{ id: "gpt-5.5", provider: "openai-codex", reasoning: true }], + models: [{ id: "gpt-5.5", provider: "openai", reasoning: true }], }; } if (method === "sessions.patch") { diff --git a/ui/src/ui/controllers/chat.test.ts b/ui/src/ui/controllers/chat.test.ts index 09ac5355b1ef..49d824fb3871 100644 --- a/ui/src/ui/controllers/chat.test.ts +++ b/ui/src/ui/controllers/chat.test.ts @@ -812,7 +812,7 @@ describe("handleChatEvent", () => { runId: "run-1", sessionKey: "main", state: "error", - errorMessage: 'No API key found for provider "openai-codex".', + errorMessage: 'No API key found for provider "openai".', }; expect(handleChatEvent(state, payload)).toBe("error"); @@ -821,9 +821,9 @@ describe("handleChatEvent", () => { expectTextChatMessage( state.chatMessages[1], "assistant", - 'Error: No API key found for provider "openai-codex".', + 'Error: No API key found for provider "openai".', ); - expect(state.lastError).toBe('No API key found for provider "openai-codex".'); + expect(state.lastError).toBe('No API key found for provider "openai".'); }); it("prefers server-provided assistant error messages", () => { diff --git a/ui/src/ui/views/agents-utils.test.ts b/ui/src/ui/views/agents-utils.test.ts index 845ccdfa326f..35f04c12765e 100644 --- a/ui/src/ui/views/agents-utils.test.ts +++ b/ui/src/ui/views/agents-utils.test.ts @@ -202,7 +202,7 @@ describe("buildAgentContext", () => { workspace: "/tmp/agent-workspace", model: { primary: "openai/gpt-5.5", - fallbacks: ["openai-codex/gpt-5.2-codex"], + fallbacks: ["openai/gpt-5.2-codex"], }, agentRuntime: { id: "claude-cli", fallback: "none", source: "agent" }, }, @@ -227,7 +227,7 @@ describe("buildAgentContext", () => { workspace: "/tmp/default-workspace", model: { primary: "openai/gpt-5.5", - fallbacks: ["openai-codex/gpt-5.2-codex"], + fallbacks: ["openai/gpt-5.2-codex"], }, }, list: [{ id: "main" }], diff --git a/ui/src/ui/views/chat.test.ts b/ui/src/ui/views/chat.test.ts index 6e49e1a784f6..2eb14bc5ea73 100644 --- a/ui/src/ui/views/chat.test.ts +++ b/ui/src/ui/views/chat.test.ts @@ -2245,7 +2245,7 @@ describe("chat session controls", () => { ts: Date.now(), providers: [ { - provider: "openai-codex", + provider: "openai", displayName: "Codex", status: "ok", profiles: [{ profileId: "codex", type: "oauth", status: "ok" }], @@ -2510,7 +2510,7 @@ describe("chat session controls", () => { const { state } = createChatHeaderState({ omitSessionFromList: true }); state.sessionsResult = createSessionsListResult({ defaultsModel: "gpt-5.5", - defaultsProvider: "openai-codex", + defaultsProvider: "openai", defaultsThinkingLevels: [ { id: "off", label: "off" }, { id: "adaptive", label: "adaptive" }, @@ -2601,12 +2601,12 @@ describe("chat session controls", () => { it("always renders full thinking labels", () => { const { state } = createChatHeaderState({ model: "gpt-5.5", - modelProvider: "openai-codex", + modelProvider: "openai", thinkingDefault: "high", }); state.sessionsResult = createSessionsListResult({ defaultsModel: "gpt-5.5", - defaultsProvider: "openai-codex", + defaultsProvider: "openai", defaultsThinkingDefault: "high", defaultsThinkingLevels: [ { id: "off", label: "off" }, diff --git a/ui/src/ui/views/overview.render.test.ts b/ui/src/ui/views/overview.render.test.ts index aa3d3b895dcc..a886f8fc7476 100644 --- a/ui/src/ui/views/overview.render.test.ts +++ b/ui/src/ui/views/overview.render.test.ts @@ -192,7 +192,7 @@ describe("overview view rendering", () => { ts: Date.now(), providers: [ { - provider: "openai-codex", + provider: "openai", displayName: "Codex", status: "ok", profiles: [{ profileId: "codex", type: "oauth", status: "ok" }], diff --git a/ui/src/ui/views/sessions.test.ts b/ui/src/ui/views/sessions.test.ts index bdf52a1c0b13..d66dd7f9070c 100644 --- a/ui/src/ui/views/sessions.test.ts +++ b/ui/src/ui/views/sessions.test.ts @@ -371,7 +371,7 @@ describe("sessions view", () => { updatedAt: Date.now(), }, { - modelProvider: "openai-codex", + modelProvider: "openai", model: "gpt-5.5", thinkingDefault: "high", thinkingLevels: [