Skip to content

feat: Sign in with ChatGPT (use a ChatGPT subscription for OpenAI models)#65

Merged
evandempsey merged 9 commits into
mainfrom
feat/sign-in-with-chatgpt
Jun 20, 2026
Merged

feat: Sign in with ChatGPT (use a ChatGPT subscription for OpenAI models)#65
evandempsey merged 9 commits into
mainfrom
feat/sign-in-with-chatgpt

Conversation

@evandempsey

Copy link
Copy Markdown
Member

What & why

Lets users drive OpenAI models with a paid ChatGPT plan (Plus/Pro/Business) instead of an API key. Run /login in the TUI, complete the browser sign-in, and Kolega Code switches to the OpenAI (ChatGPT subscription) provider (e.g. gpt-5-codex). This replicates the OpenAI Codex CLI flow: OAuth 2.0 + PKCE against auth.openai.com, a loopback callback, and inference through the ChatGPT backend's Responses API. Tokens are stored locally at 0600, refreshed automatically (lazily + on 401); /logout removes them.

⚠️ Important caveat (please read)

This reuses the first-party Codex OAuth client_id and the chatgpt.com/backend-api/codex backend — the same approach other third-party agents (Cline, opencode) use. It is not an officially sanctioned integration: OpenAI's supported "Sign in with ChatGPT" (Apps SDK) is identity-only and does not grant model access to third-party tools. Using it is unofficial, against the spirit of OpenAI's ToS, and carries account-ban risk. A Free plan cannot run models this way. All Codex-specific constants live in auth/constants.py so a forced change is a one-file edit.

How it's built

  • kolega_code/auth/ (new): Codex OAuth constants (single source), OAuthTokens + ChatGPTTokenManager (lazy + 401 refresh, injected persistence), JWT claim decode, and the browser-loopback login flow (PKCE, ports 1455/1457, plan gating).
  • llm/providers/chatgpt_oauth.py: ChatGPTOAuthProvider on the Responses API with a refreshing httpx.Auth, full message/tool/image→Responses conversion, and a ResponsesStreamWrapper matching the existing text/thinking/tool_use_start + get_final_message() contract.
  • Wiring: ModelProvider.OPENAI_CHATGPT, MODEL_SPECS (new openai_responses_reasoning effort mode), LLMClient routing + token_manager threading through all construction sites, AgentConfig token field/validation/manager accessor, build_agent_config, registry labels, and /login + /logout commands.
  • Settings/UX: settings tab guides users to /login for the OAuth provider (disabled key field + clear error). README section + .env.example note.
  • Persistence: additive oauth_tokens on CliSettings (no schema bump, matching the web_search/active_theme precedent), chmod 0600.

Testing

  • 1064 unit tests pass; ruff clean on all changed/new files.
  • New: token model + refresh, PKCE + end-to-end loopback login, Responses conversion + streaming + provider + LLMClient routing + AgentConfig validation, plus a gated live test (test_chatgpt_live.py, skipped without credentials).

Manual end-to-end

/login with a Plus/Pro account → confirm oauth_tokens.openai_chatgpt in settings.json at -rw-------/model openai_chatgpt/gpt-5-codex → send a prompt that triggers a tool call and confirm streaming + tool execution.

🤖 Generated with Claude Code

evandempsey and others added 9 commits June 20, 2026 10:00
…enAI models

Lets users drive OpenAI models with a paid ChatGPT plan (Plus/Pro/Business)
instead of an API key, via `/login`. This replicates the OpenAI Codex CLI flow:
OAuth 2.0 + PKCE against auth.openai.com, a loopback callback, and inference
through the ChatGPT backend's Responses API.

- auth/: new package — Codex OAuth constants (single file), OAuthTokens model +
  ChatGPTTokenManager (lazy + 401 refresh, injected persistence), JWT claim
  decode, and the browser-loopback login flow (PKCE, port 1455/1457, plan gating).
- providers/chatgpt_oauth.py: ChatGPTOAuthProvider speaking the Responses API
  with a refreshing httpx.Auth, full message/tool/image conversion, and a
  streaming wrapper matching the existing text/thinking/tool_use_start contract.
- Wiring: ModelProvider.OPENAI_CHATGPT, MODEL_SPECS (openai_responses_reasoning
  effort mode), LLMClient routing + token_manager threading, AgentConfig token
  field/validation/manager, build_agent_config, settings persistence (additive
  oauth_tokens, no schema bump), registry labels, and /login + /logout commands.
- Settings tab guides users to /login for the OAuth provider; README + .env note.
- Tests: token/refresh, PKCE + end-to-end loopback login, Responses conversion +
  streaming + provider + routing + config validation, plus a gated live test.

Note: reuses the first-party Codex OAuth client and ChatGPT backend. This is
unofficial and against the spirit of OpenAI's ToS (account-ban risk); the
sanctioned "Sign in with ChatGPT" is identity-only and does not grant inference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parameterize /login and /logout so they take a provider target, leaving room
for additional OAuth integrations later. `/login chatgpt` runs the ChatGPT
flow; bare `/login` (or `/login help`) prints usage; an unknown target is
rejected. Available targets live in KolegaCodeApp.LOGIN_TARGETS. Updated all
user-facing hints, the settings placeholder, config errors, and the README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes "openai couldn't process the request" from the ChatGPT backend and
refreshes the model list to the current Codex picker.

Request shape (verified against openai/codex codex-rs source):
- Stop sending `max_output_tokens` — Codex never sends it and the backend
  rejects it. This was the primary cause of the 400.
- Always stream (the /responses backend is SSE-only); generate() now drains a
  stream instead of issuing stream:false.
- Send the fields Codex sends: tool_choice="auto", parallel_tool_calls=false,
  prompt_cache_key, and reasoning={effort, summary:"auto"}.
- Headers: drop OpenAI-Beta (HTTP path doesn't use it), add a codex_cli_rs
  User-Agent, use ChatGPT-Account-ID / session-id casing.

Models: replace gpt-5-codex/gpt-5 with the current Codex slugs gpt-5.5 (default),
gpt-5.4, gpt-5.4-mini, and gpt-5.3-codex-spark, with Responses reasoning-effort
options. Context/output limits mirror the API gpt-5.x specs and are
server-enforced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A settings.json whose active_model no longer exists in MODEL_SPECS (e.g. an old
ChatGPT slug like gpt-5-codex after the model rename) made build_agent_config
raise, which set config=None and disabled the chat composer — locking the user
out with no way to type /model.

_active_provider_model now coerces an unknown saved model to the provider's
default (and treats an unknown saved provider as unconfigured), so a stale
selection self-heals on the next launch instead of bricking the session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mpty output)

The agent "said one thing and stopped" on the ChatGPT-subscription provider
because the backend behaves differently from the standard Responses API:

- response.completed arrives with an EMPTY output[] (the answer streams only as
  deltas). get_final_message preferred response.output, so it parsed [] and
  returned an empty assistant message — ending the turn with no content/tool
  calls. Now the final message is built from the accumulated stream deltas, and
  response.output is only a fallback when no deltas were seen. Verified live
  against the real backend (gpt-5.5 now returns text).
- The backend hard-requires a non-empty `instructions` field (400 "Instructions
  are required"); always send one.
- Also capture tool calls from response.output_item.done so they survive when
  the completed output is empty.

Adds regression tests for the empty-output, output_item.done, and instructions
cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The two "Field name 'json' ... shadows an attribute in parent 'BaseModel'"
warnings printed on every startup come from the firecrawl package itself —
firecrawl/v2/types.py defines a model field named `json` that shadows
BaseModel.json, which pydantic warns about at class-definition time. It is a bug
in firecrawl's own code (not ours, and not fixable here), so we suppress exactly
that warning where we import firecrawl. Keeps the eager import (and the
module-level Firecrawl symbol the tests patch) intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New Configuration page presenting the ChatGPT-subscription sign-in: eligibility,
the /login chatgpt flow, the available Codex models, where credentials live,
checking status with doctor, and /logout chatgpt. Adds it to the sidebar, lists
/login and /logout in the slash-commands reference, adds the openai_chatgpt
provider row to Providers & Models, and trims the README section to present the
feature. Docs site builds clean (23 pages).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rovider too

These models were only on the openai_chatgpt provider, but they work with a
standard OPENAI_API_KEY as well. Add them to the `openai` provider so both
providers offer the same line-up (gpt-5.5, gpt-5.4, gpt-5.4-mini,
gpt-5.3-codex-spark). Uses the openai provider's Chat-Completions reasoning-effort
convention; labels are already shared in the registry so the model picker and
Settings UI pick them up automatically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gpt-5.4 and gpt-5.4-mini share the API-key `openai` provider's line-up with
gpt-5.5; all three verified working live against Chat Completions with an
OPENAI_API_KEY. gpt-5.3-codex-spark is intentionally excluded — it 404s on the
standard API ("model does not exist or you do not have access") and is a Codex
model only reachable through the ChatGPT-subscription backend (openai_chatgpt),
where it stays.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@evandempsey evandempsey merged commit f8118ad into main Jun 20, 2026
7 checks passed
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