Skip to main content

docs/adr/0002-codex-oauth-responses-provider.md

# 2. Codex (ChatGPT subscription) OAuth + OpenAI Responses API as the primary provider

Date: 2026-05-29
Status: Accepted

## Context

A core goal is letting users run Pixir on their existing **ChatGPT Plus/Pro
subscription** rather than pay-per-token API keys — the "Sign in with ChatGPT
(Codex)" experience Pi ships. Pi implements this as an OAuth (PKCE) flow against
`auth.openai.com`, then calls the **OpenAI Responses API** with the resulting access
token plus a `chatgpt-account-id` header.

Two facts shaped the decision:
1. `req_llm` (the Elixir multi-provider library Kimojo uses) is API-key +
   chat-completions oriented and does not support this OAuth/subscription path. Even
   Pi keeps OAuth providers as hand-built built-ins.
2. The **Responses API dialect serves both** a subscription OAuth token *and* a plain
   `OPENAI_API_KEY` — same item format, different endpoint/auth/headers.

## Decision

v0.1's single Provider is the **OpenAI Responses API**, reached via two
**Credentials**: a Codex **Subscription** OAuth token (primary) or an `OPENAI_API_KEY`
(fallback). The OAuth flow is implemented natively (PKCE), starting with the
**device-code** method (no local callback server; headless-friendly); the browser +
`127.0.0.1:1455` callback flow is a fast-follow. `req_llm` and all other providers
(Anthropic, local models) are deferred.

## Consequences

- **Subscription-first UX** — users leverage ChatGPT Plus/Pro; no API key required.
- **Responses is the canonical dialect.** Tool calling uses Responses
  `function_call` / `function_call_output` items and reasoning items — *not*
  chat-completions function-calling (a divergence from the Kimojo reference).
- **Auth is the largest single component of v0.1** (PKCE, token storage + refresh,
  the Responses client). Consciously accepted as the differentiator.
- **Device-code first** avoids the localhost-callback failures documented for remote/
  headless Codex sign-in, at the cost of a copy-code step.
- **Tokens** are stored locally and auto-refreshed; `chatgpt_account_id` is extracted
  from the JWT and sent as a header.
- **Deferring `req_llm`** means multi-provider breadth comes later; re-adding it is
  additive (a second dialect behind the same Provider seam).