feat: OpenAI ChatGPT-subscription support (Sign in with ChatGPT)#698
Merged
Conversation
Use OpenAI models via a ChatGPT Plus/Pro plan instead of metered API billing — the mechanism OpenCode's openai plugin implements and the Codex CLI's official OAuth app provides. - src/auth/openai_subscription.py: authorization-code+PKCE against auth.openai.com with a localhost:1455 callback, device-code flow for headless boxes, import-from-Codex-CLI, form-urlencoded token exchange/ refresh (lock + rotation-safe), JWT account-id extraction, 0600 atomic credential store at ~/.clawcodex/openai-oauth.json. - src/providers/openai_responses.py: Responses-API wire format — Anthropic-format messages → input items (function_call/ function_call_output/reasoning replay with ids stripped, the Codex convention), flat function tools, SSE parsing, usage mapping with billing_mode=subscription (/bin/zsh cost via the #697 hook). - OpenAIProvider: subscription mode (active when no API key + stored OAuth) streams from chatgpt.com/backend-api/codex/responses with ESC-abort worker/queue parity, 401→refresh-once retry, encrypted reasoning replayed across turns via raw_content_blocks passthrough. - Foreign-provider strips: Chat-Completions converter, Anthropic and Minimax providers drop the passthrough blocks (mid-session /model switches); Gemini drops unknown blocks by construction. - provider_has_credentials(): shared gate for startup validation and BOTH agent-server gates — also fixes #697's Claude subscription being rejected by the TUI session-init/set_provider api-key checks. - Model configs for gpt-5.5/5.4/5.4-mini/5.3-codex-spark (272K/128K windows) + legacy gpt rows; subscription model list in login/picker. - CLI: clawcodex login openai→subscription (browser/device-code/import), clawcodex logout openai, config status line. Validated live against the ChatGPT backend: single-turn with custom instructions+tools, multi-turn replay (with and without reasoning items), headless -p runs incl. Bash tool loop, and the agent-server gate in both directions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Abort-path test: ESC mid-stream raises AbortError and the guard's close-on-abort listener closes the httpx response (critic MAJOR). - Honor /effort on the subscription path: the agent-server's _EffortProvider injects extra_body.reasoning_effort — map it to reasoning.effort with xhigh/max clamped to high (general gpt-5.x models advertise low/medium/high). - Construct the provider from load_credentials() presence only — get_valid_credentials() could do a blocking 30s token refresh at startup / every model switch; the request path already refreshes. - Warn at login when an exported OPENAI_API_KEY shadows the subscription. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ClawCodex can now use OpenAI models via a ChatGPT Plus/Pro subscription ("Sign in with ChatGPT") instead of metered API billing. This ports the mechanism OpenCode's
openaiplugin implements (reference_projects/opencode/packages/opencode/src/plugin/openai/codex.ts) — the same official OAuth app the Codex CLI uses — and mirrors the in-repo Claude-subscription pattern from #697.clawcodex login→openai→subscription→ pick a method:~/.codex/auth.json(offered only when present)With a subscription connected and no
OPENAI_API_KEY, requests go tohttps://chatgpt.com/backend-api/codex/responses(the Responses API) and draw on the plan's allowance — the cost display shows $0 (billing_mode: subscription, the #697 hook). A configured API key always wins. Subscription models:gpt-5.5,gpt-5.4,gpt-5.4-mini,gpt-5.3-codex-spark.How it works
src/auth/openai_subscription.py— authorization-code + PKCE (S256) againstauth.openai.com, localhost callback with CSRF state check, device-code flow, form-urlencoded token exchange/refresh (double-checked locking; rotation-safe when the response omitsrefresh_token), ChatGPT account-id extraction from JWT claims, 0600 atomic credential store at~/.clawcodex/openai-oauth.json.src/providers/openai_responses.py— pure wire-format helpers: Anthropic-format history → Responsesinputitems (function_call/function_call_output/reasoningreplay with server ids stripped — the Codexstore:falseconvention), flat function tools, tolerant SSE parsing, usage mapping.OpenAIProvider— subscription mode overrideschat/chat_stream/chat_stream_responsewith an httpx SSE stream against the Codex backend: same ESC-abort worker/queue architecture as the base class, proactive refresh + reactive 401→refresh-once retry,reasoning: {effort: medium (env-overridable), summary: auto},text.verbosity: low(non-codex gpt-5),prompt_cache_key= per-session UUID, nomax_output_tokens/sampler params (Codex-CLI parity, same as OpenCode).ChatResponse.raw_content_blocksinto assistant history asopenai_responses_itempassthrough blocks (the advisor-block mechanism) and are replayed verbatim next turn, preserving reasoning↔tool-call interleaving. Foreign providers strip them (Chat-Completions converter, Anthropic + Minimax overrides; Gemini drops unknown blocks by construction), so mid-session/modelswitches stay clean.provider_has_credentials()— one shared credentials gate for startup validation and both agent-server gates (session init +set_provider). This also fixes feat: add Claude subscription OAuth support #697: the agent-server's inline "no API key" checks rejected Claude-subscription sessions in the TUI.Validation
Live against the real ChatGPT backend (existing Codex CLI tokens on this machine):
instructionsaccepted, flat tools work, reasoning item returnsencrypted_contentclawcodex -pmath answer; Bash tool loop (head -1 README.mdechoed correctly); Write+Bash coding task (fib.py) in 13.4s end-to-endTest suite:
tests/test_openai_subscription.py(24 tests: credential store perms/rotation, PKCE URL, account-id claim precedence, Codex-CLI import, converter mappings incl. orphan tool_results + passthrough replay + multimodal splits, flat tools, foreign-provider strips, streaming provider with mocked SSE, 401-refresh retry, failure events, api-key-wins, shared gate incl. anthropic parity). Full suite: 8497 passed, 3 skipped (two tests that pinned "gpt has no model config" updated for the new rows).Critic review: design REVISE→fixes applied (system-kwarg fold, Minimax strip) → implementation reviewed.
🤖 Generated with Claude Code