Skip to content

feat: OpenAI ChatGPT-subscription support (Sign in with ChatGPT)#698

Merged
ericleepi314 merged 2 commits into
mainfrom
feat/openai-subscription
Jul 12, 2026
Merged

feat: OpenAI ChatGPT-subscription support (Sign in with ChatGPT)#698
ericleepi314 merged 2 commits into
mainfrom
feat/openai-subscription

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

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 openai plugin 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 loginopenaisubscription → pick a method:

  • browser — PKCE authorization at auth.openai.com with a localhost:1455 redirect
  • device-code — for headless/SSH machines (enter a short code at auth.openai.com/codex/device)
  • import-codex-cli — copies an existing Codex CLI ChatGPT login from ~/.codex/auth.json (offered only when present)

With a subscription connected and no OPENAI_API_KEY, requests go to https://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) against auth.openai.com, localhost callback with CSRF state check, device-code flow, form-urlencoded token exchange/refresh (double-checked locking; rotation-safe when the response omits refresh_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 → Responses input items (function_call / function_call_output / reasoning replay with server ids stripped — the Codex store:false convention), flat function tools, tolerant SSE parsing, usage mapping.
  • OpenAIProvider — subscription mode overrides chat / chat_stream / chat_stream_response with 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, no max_output_tokens/sampler params (Codex-CLI parity, same as OpenCode).
  • Encrypted reasoning replay — response items ride ChatResponse.raw_content_blocks into assistant history as openai_responses_item passthrough 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 /model switches 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.
  • Model configs — gpt-5.5/5.4/5.4-mini (272K) and codex-spark (128K) context windows (plus exact rows for legacy gpt models); previously every gpt model fell back to the 200K default, firing auto-compact ~70K tokens early.

Validation

Live against the real ChatGPT backend (existing Codex CLI tokens on this machine):

  • single-turn probe: custom instructions accepted, flat tools work, reasoning item returns encrypted_content
  • multi-turn replay with ids stripped (with AND without reasoning items) → 200
  • provider e2e: 2-turn tool exchange with history built exactly as query.py builds it → correct final answer
  • full-stack headless: clawcodex -p math answer; Bash tool loop (head -1 README.md echoed correctly); Write+Bash coding task (fib.py) in 13.4s end-to-end
  • agent-server gate e2e: subscription session initializes cleanly; negative control (no creds) still produces the API-key error frame

Test 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

ericleepi314 and others added 2 commits July 12, 2026 11:17
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>
@ericleepi314 ericleepi314 merged commit 9517d97 into main Jul 12, 2026
2 checks passed
@ericleepi314 ericleepi314 deleted the feat/openai-subscription branch July 12, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant