feat(platform): add ChatGPT/Codex subscription transport preview - #13761
Conversation
Part 1 of 5 of the LLM catalog-as-code stack (successor to the DB-registry stack #13605-#13613, closed in favor of this design: the catalog file is the source of truth; the DB holds only per-install runtime state). - ChatMessage.model + ChatMessage.routingSource (nullable): which LLM served each assistant turn and which routing layer picked it ("ld"|"db"|"env") — the join key product-intelligence needs to segment quality judgments by model (see product-intelligence#47) - LlmModelMigration: retirement records (which AgentNodes were rewritten when a model was retired, revertable). Slugs are plain strings validated against the catalog at write time — no catalog tables exist. Partial unique index prevents concurrent active migrations per source; customCreditCost from the original design dropped (billing lives in the catalog file as of Phase B3) Migration verified via prisma migrate reset over the full local chain. Co-authored-by: Bentlybro <Github@bentlybro.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part 2 of 5 of the catalog-as-code stack. backend/data/llm_registry/ catalog.py IS the model database: 85 models / 8 providers / 17 creators / 4 copilot routing cells, generated from the current MODEL_METADATA, MODEL_COST, TOKEN_COST, and ChatConfig defaults so file == reality on day one. Updates happen by PR (catalog-only diffs may ride hotfix-> master for CD-speed changes); git history is the audit log. - catalog_model.py: validated schema — per-model costs (flat credits + per-1M token rates), visibility (GA/EMPLOYEES/ADMINS/HIDDEN), min_subscription_tier, fallback slugs, routing (surface->mode->tier) - registry.py: load_catalog() builds the in-process read cache at startup; same read interface the copilot resolver (part 3), public endpoint (part 4), and Phase B consumers use. No Redis, no pub/sub, no DB — the file only changes at deploy, so coherence is free - forever-guard tests: parse, unique slugs, referential integrity (providers/creators/fallbacks/routing cells), cost bounds - cost-drift tripwires: catalog costs must equal MODEL_COST/TOKEN_COST in both directions until Phase B3 flips the reader and deletes the dicts — centralizing costs now cannot silently diverge - lifespan: fail-soft load; empty catalog degrades to pre-catalog behavior Co-authored-by: Bentlybro <Github@bentlybro.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part 3 of 5 of the catalog-as-code stack. Copilot model resolution
becomes: LD slug flag (per-user cohorts) -> catalog routing cell
(PR-authored, deploy-propagated) -> ChatConfig env default, with the
catalog as the serve-time gate for the first two layers:
- unknown-to-catalog or disabled (kill switch) slugs are refused loudly
(log + Sentry + route_warnings record) and fall through; HIDDEN
visibility serves when explicitly routed (pre-launch testing state);
an EMPTY catalog gates nothing — all 27 pre-existing router tests
pass unchanged
- ChatMessage stamping: every persisted assistant message carries the
served model + routing layer (ld|db|env) via the R1 columns — the
product-intelligence join key (product-intelligence#47)
- two integration bugs found and fixed at resolve time:
1) LD/env use OpenRouter spellings (anthropic/claude-opus-4.6) while
the catalog registers dashed canonical slugs — the gate now
resolves exact -> vendor-stripped -> dots-to-dashes, otherwise
every legitimate LD Claude override would have been refused
2) bare claude-* cell slugs are vendor-prefixed at resolve time so
one cell value works on both OpenRouter and direct-Anthropic
transports
Co-authored-by: Bentlybro <Github@bentlybro.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part 4 of 5 of the catalog-as-code stack. GET /api/llm/catalog serves the catalog's public facts from the in-process cache: - unauthenticated by design; GA-visibility models only; costs and routing cells explicitly excluded from the payload (cloud-internal config, not catalog facts) — covered by a dedicated test - zero DB reads; CDN-cacheable (Cache-Control public/max-age=300/ swr=3600 on 200s, exact-path CACHEABLE_PATHS entry, explicit no-store on 429s) - per-IP fixed-window rate limit, fail-open (protects read capacity, not money), with the backend's first X-Forwarded-For handling Feeds the Phase B registry-driven model picker; self-hosted installs may read it, though they primarily get the catalog on upgrade. Co-authored-by: Bentlybro <Github@bentlybro.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dev-era router tests assume an empty registry; registry_test.py restores the real catalog into module globals. An autouse snapshot/ clear/restore fixture makes suite order irrelevant (14 tests failed registry-first before this). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part 5 of 5 of the catalog-as-code stack. Retiring a model is two operations: flip enabled:False in a catalog PR (stops serving), and migrate existing graph nodes off it — this CLI does the second, salvaging the battle-tested v2 data layer: python -m backend.data.llm_registry.retire --usage <slug> python -m backend.data.llm_registry.retire <slug> --replacement <slug> [--yes] python -m backend.data.llm_registry.retire --revert <migration-id> python -m backend.data.llm_registry.retire --list - SELECT...FOR UPDATE + JSONB_SET rewrite of AgentNode.constantInput inside a transaction, schema-prefix-safe; both migrate and revert use the provider-stripped node value (the v2 revert-matching fix) - replacement validated against the catalog (must exist and be enabled) - every retire records a revertable LlmModelMigration row; concurrent active migrations per source rejected by the partial unique index - dry-run by default (usage summary, exit 1); --yes executes and reminds the operator about the catalog PR Co-authored-by: Bentlybro <Github@bentlybro.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New runbook (docs/platform/contributing/managing-llm-models.md): catalog field reference incl. enabled-vs-visibility semantics, the two update lanes (dev train vs catalog-only hotfix->master), copilot resolution precedence, the HIDDEN rollout recipe, retirement CLI, and the public catalog endpoint. Updates the stale enum/MODEL_COST procedure in ollama.md (with an honest Phase-B note), the CHAT_*_MODEL framing in .env.default and copilot-local-llm.md, branching docs for the catalog hotfix lane, backend AGENTS common tasks, the rate-card docstring scope, and the API guide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Catalog cells hold cloud slugs, and the local transport passes slugs through verbatim with no ValueError — a cell would override the operator's CHAT_*_MODEL config and 404 against Ollama/vLLM at request time with no fallback. Local deployments now resolve LD -> env only, exactly the pre-catalog behavior. Found during the docs pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ruction Pre-review-gate findings B2(part)/S1: - routing cells now carry the vendor-prefixed DOT-form slugs (anthropic/claude-sonnet-4.6) — the spelling OpenRouter actually serves; bare dashed canonical slugs 404 there. New spelling-convention guard test; the reference guard is now slug-tolerant like the router - catalog construction moved out of import time (get_catalog build-once accessor): a bad literal now degrades fail-soft in load_catalog callers instead of ImportError-crashing every process — the bad- catalog vector IS the fast-edit lane, so this matters Co-authored-by: Bentlybro <Github@bentlybro.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…batim Pre-review-gate findings B1/B2(part): - B1: copilot turns run in CoPilotExecutor, not the rest API process — without loading the catalog there, routing cells, serve-time gating, and HIDDEN-serves-when-routed were silent no-ops in production. Fail-soft loader at executor startup + regression tests. Audited all resolve_model/stream callers: rest_api and the copilot executor are the only host processes (the scheduler only enqueues) - B2: the resolver's vendor-prefix hack is gone — cells now carry transport-ready spellings (see the catalog spelling-convention guard) and are returned verbatim; OpenRouter sends them as-is, the direct- Anthropic normalizer strips/dedots them (tested against the real catalog cells on both transports) Co-authored-by: Bentlybro <Github@bentlybro.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-review-gate S3 + nits: min_subscription_tier now round-trips through the public catalog payload (the Phase B picker needs tier gating and it is not sensitive); fallback_model_slug values referencing models excluded from the payload (non-GA) are nulled so consumers never see dangling refs; dead header line removed. OpenAPI regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pped prettier) Content was semantically intact (246 paths both sides) — the prior commit's single-line export just never ran through prettier because the Node version check failed offline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… modes Pre-review-gate B3+S4: AgentNode.constantInput stores FULL enum values (mixed bare and provider-prefixed — graph.migrate_llm_models compares against LlmModel.value verbatim), so node-value mapping is identity with catalog slugs. The carried v2 helper stripped provider prefixes, which silently no-opped retirement of prefixed models (kimi-*) and wrote out-of-enum values when retiring ONTO one. Regression test seeds a moonshotai/kimi-k2.5 node and round-trips retire+revert. CLI action modes are now mutually exclusive instead of silently ignoring the positional. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…istry SubscriptionTierName Literal alias shared by CatalogModel, RegistryModel, and the public payload build — clears the pyright error the S3 fix introduced (str | None flowing into the Literal-typed field). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-review-gate S2: local deployments SKIP catalog routing cells (the docs said the opposite); stamping claim scoped to the baseline path; routing example uses the transport-ready dot-form cell spellings; example slugs match the real catalog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Populated default cells (seeded from ChatConfig CODE defaults) would have silently shadowed the CHAT_*_MODEL env config of any deployment whose env differs from code defaults — including prod — the moment this deployed, and would flip models during LD outages (fall-through lands on the cell, not the env value). Cells now start empty: env stays authoritative for a (mode, tier) until an operator claims that cell in a catalog PR. Reference/spelling guards still govern any cells added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cells are OUR cloud's deployment config traveling in the shipped catalog file — not defaults for everyone. Self-hosted installs (behave_as != CLOUD) now skip the cell layer even on cloud transports: a cell set for prod must not override an operator's CHAT_*_MODEL on their next upgrade, and they have no LD to escape through. Local transports were already skipped. Both resolve LD -> env, the pre-catalog behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
/reapprove |
There was a problem hiding this comment.
Re-approved at the request of @ntindle (#13761 (comment))
|
🔄 Auto-redeploying: new commits pushed to a PR with an active deployment. Refreshing development environment for PR #13761. |
|
/reapprove |
There was a problem hiding this comment.
Re-approved at the request of @ntindle (#13761 (comment))
|
🔄 Auto-redeploying: new commits pushed to a PR with an active deployment. Refreshing development environment for PR #13761. |
|
/reapprove |
There was a problem hiding this comment.
Re-approved at the request of @ntindle (#13761 (comment))
…est against loop-closed flake Same retry-once pattern as conftest's _create_user_with_loop_retry — the batch's added test files shift ordering enough that this session-loop test becomes the first DB call after a function-loop teardown. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🔄 Auto-redeploying: new commits pushed to a PR with an active deployment. Refreshing development environment for PR #13761. |
|
/reapprove |
There was a problem hiding this comment.
Re-approved at the request of @ntindle (#13761 (comment))
|
🧹 Auto-undeploying: PR closed with active deployment. Cleaning up development environment for PR #13761. |
|
🧹 Preview Environment Cleaned Up All resources for PR #13761 have been removed:
Cleanup completed successfully. |
Issues attributed to commits in this pull requestThis pull request was merged and Sentry observed the following issues:
|
Important
Preview release for user-owned ChatGPT/Codex usage.
LOCALdeployments allow it for every authenticated user.CLOUDdeployments allow it for AutoGPTMAX,BUSINESS, andENTERPRISEusers. There is no Codex feature flag or staff-only allowlist, so deploying this branch to hosted production exposes discovery, sign-in, and execution to every user in those tiers. The connected ChatGPT account's own plan, workspace policy, allowance, and credits still determine what Codex can execute.Why / What / How
This PR adds a user-funded ChatGPT connection as a distinct Codex subscription transport, without requiring an OpenAI API key.
Codex App Serveris selectedExisting platform-funded, self-hosted, and OpenAI API-key routes remain available and keep their existing behavior.
The frontend presents Sign in with ChatGPT under the existing OpenAI integration card and uses OpenAI branding. The stored credential and backend auth provider remain
codex, while the API-key method remainsopenai. Keeping those canonical identities separate prevents a ChatGPT subscription credential from being mistaken for an OpenAI API key.Changes 🏗️
Authentication and credentials
IntegrationCredentialpayload asOAuth2Credentials(provider="codex", refresh_strategy="provider_runtime").auth.jsononly inside an isolated temporaryCODEX_HOME. RabbitMQ messages, Redis login state, chat sessions, and Claude CLI environments contain credential IDs or short-lived capabilities, not ChatGPT access, refresh, or ID tokens.Runtime and execution
openai-codex==0.144.4together with its bundled Codex runtime.127.0.0.1translates Claude text and MCP tool rounds to Codex App ServerdynamicTools. Claude Code retains the MCP tool loop, permission/security hooks, transcripts and resume, compaction, attachments, and sub-sessions. The CLI receives only a random per-turn capability.Routing and model behavior
/api/chat/transportsinventory and validate every requested route against what is currently usable for that user and deployment.If a preferred model or effort is unavailable, routing uses an allowed account-advertised default or available OpenAI model and a supported/default effort.
For Code Generation, the model dropdown applies only to the OpenAI API transport. The subscription transport lets App Server choose its live account-compatible model while still applying the selected supported reasoning effort.
Entitlement and cost behavior
require_entitlement(user_id, entitlement)policy shell.LOCALmode, allowcodex_subscription_transportbefore any subscription database lookup.CLOUDmode, read the authenticated user's authoritative AutoGPTUser.subscriptionTierthroughDatabaseManageron every check in this PR and allowMAX,BUSINESS, andENTERPRISE.subscriptionTier.billing_mode=user_subscriptionand the execution path. Do not report public OpenAI API USD pricing as AutoGPT provider spend.Configuration / rollout
No new secret, Codex rollout flag, or Codex-specific frontend build argument is required.
FRONTEND_BASE_URL, build-timeNEXT_PUBLIC_FRONTEND_BASE_URL, and runtimeBETTER_AUTH_URLto the exact public origin. Otherwise the device-login popup can use the wrong host or lack the preview authentication cookie.CODEX_TEMP_ROOTis optional and otherwise falls back to the operating-system temporary directory.CODEX_TEMP_ROOT=/run/autogpt-codexand mounts a 128 MiB memory-backed temporary filesystem in the REST, graph-executor, and Copilot-executor containers.CODEX_TEMP_ROOT=/dev/shm/autogpt-codex, creates it as0700for the unprivileged backend user, and verifies that REST, graph executor, and Copilot executor inherit it. Docker's/dev/shmkeeps auth homes out of the writable layer; the documented quick-start sizes it to 2 GiB.CODEX_*overrides.Explicit exclusions and known limitations
This PR does not route the following through a user's ChatGPT connection:
ClaudeCodeBlock/ E2B;It also does not add credential pooling, silent fallback to a platform credential, multiple active Codex credentials per user, per-user Kubernetes resources, persistent Codex homes, or persistent volumes.
Operational limits that remain:
CODEX_LOGIN_TIMEOUT_SECONDS, 15 minutes by default.codex_credential_busyerror. Multi-replica owner routing or a dedicated bridge is not included.Validation
At review-fix parent
9cbe549d36dd3197b22871dbb7ae79d85c26c03fagainstdev@5c980c2f743ae2431e30f5511ce304413782c163, the formatted tree passed the following focused validation:tsc --noEmit, and targeted Prettier checks pass.Final head
aa4bcb445c450d2da1c0de2a4c1ea03b9c3b70e5restores the framework-compatible optional AutoPilot credential annotation, regenerates the public provider-discovery OpenAPI contract, safely canonicalizes macOS system temp aliases, and includes the exact generated Code Generation documentation sync emitted by CI. On that final diff, the AutoPilot schema/import assertion passes; the temp-home suite passes 12 tests with 6 expected Windows symlink skips; Ruff, Black, Prettier, Git diff hygiene, and full TypeScripttsc --noEmitpass. The final test-only commit also updates the shutdown mock to patch the manager module's bound transport symbol; that exact regression passes locally. Current-head CI remains the authoritative broad verification below.The prior head
9eb8283db22d6ddf624f6c891c5b1579cdd07516also completed a real-account, keyless local single-container smoke: four Claude Agent SDK turns used the Codex subscription route successfully, persisted 40 chat messages, recorded subscription token usage, and recorded zero platform model cost. The review-fix head changes authentication cleanup, model-routing structure, compatibility edge cases, cost reporting, and selector cleanup, so exact-head external-account smoke remains explicitly unchecked below.aa4bcb445c450d2da1c0de2a4c1ea03b9c3b70e5.The unchecked real-account checks are intentionally not represented as automated coverage; they consume an external account's allowance and should be completed on the exact deployment selected for release.
Checklist 📋
For code changes:
For configuration changes:
Note
High Risk
Touches authentication, credential storage/refresh, chat session routing, and billing/paywall behavior for a new user-funded LLM path. A routing or entitlement bug could leak paid platform usage or expose ChatGPT credentials incorrectly.
Overview
Adds a user-funded ChatGPT/Codex subscription transport so AutoPilot chat, the AutoPilot graph block, and Code Generation can run on a connected ChatGPT plan instead of platform-funded or OpenAI API-key routes.
Auth & credentials. Introduces ChatGPT device-code sign-in under the existing OpenAI integration card, stores one
codexOAuth credential with runtime-owned refresh/checkpointing, and blocks direct API creation of Codex credentials. Discovery, connect, and execution are entitlement-gated (LOCALfor all users;CLOUDfor Max/Business/Enterprise).Routing & billing. Adds
/api/chat/transportsand persists an immutablellm_auth_provider/llm_credential_idon each session. Codex AutoPilot skips the platform paywall and USD usage windows; subscription usage is recorded asuser_subscriptionrather than platform spend. Builder-bound sessions stay platform-only.Runtime. Pins the Codex App Server runtime, isolates auth homes under
CODEX_TEMP_ROOT, and reuses a process-local runtime actor under an exclusive credential lease. AutoPilot still goes through the Claude Agent SDK via a loopback compatibility gateway; Code Generation can invoke App Server directly when that transport is selected.Reviewed by Cursor Bugbot for commit 1441864. Bugbot is set up for automated code reviews on this repo. Configure here.