The OAuth authorization-code flows omp signs in with (/login anthropic,
/login openai-codex), built as omp's declarative engine builds them from
pi-catalog's rules/auth/anthropic.kdl and openai-codex.kdl:
- Anthropic (Claude Pro/Max):
https://claude.ai/oauth/authorizewithcode=true, PKCE S256, redirecthttp://localhost:54545/callback; the token endpointhttps://api.anthropic.com/v1/oauth/tokentakes a JSON body (the exchange also sendsstate; a refresh sends Claude Code'santhropic-betaandUser-Agentheaders). Access tokens live ~8 h and are stored 5 min short; each refresh rotates the refresh token, and the grant dies ~30 days after the interactive login. - OpenAI Codex (ChatGPT Plus/Pro):
https://auth.openai.com/oauth/authorize, PKCE S256, redirect exactlyhttp://localhost:1455/auth/callback(the only one OpenAI allows);https://auth.openai.com/oauth/tokentakes a form body. The ChatGPT account id comes from the access/id token'shttps://api.openai.com/authclaim.
Credentials are pi's auth.json entry: %{"type" => "oauth", "access", "refresh", "expires" (ms since the epoch), "accountId", "email"} (the
last two only when known).
HTTP: opts[:respond] (tests) answers a request instead of the network,
fn %Req.Request{} -> %Req.Response{} end, seeing it fully encoded.
Summary
Functions
PKCE S256: base64url(sha256(verifier)), unpadded.
The provider's registered localhost redirect URI (the one start/2 normally gets).
Exchanges an authorization code for credentials. A code#state (what
Anthropic's code page shows) is split, the pasted state winning, as omp
does. opts: :respond, and :retry_until (a monotonic ms deadline:
transport errors are retried every 2 s until then, for a phone whose
network Android cuts while the browser is in front).
Trades creds' refresh token for new credentials. The refresh token
rotates when the response carries one; identity the response lacks is
kept from creds.
A new PKCE verifier and state, and the authorize URL for them: the standard parameters in omp's order, then the provider's extras.
Types
Functions
PKCE S256: base64url(sha256(verifier)), unpadded.
The provider's registered localhost redirect URI (the one start/2 normally gets).
@spec exchange(provider(), String.t(), flow(), String.t(), keyword()) :: {:ok, creds()} | {:error, String.t()}
Exchanges an authorization code for credentials. A code#state (what
Anthropic's code page shows) is split, the pasted state winning, as omp
does. opts: :respond, and :retry_until (a monotonic ms deadline:
transport errors are retried every 2 s until then, for a phone whose
network Android cuts while the browser is in front).
Trades creds' refresh token for new credentials. The refresh token
rotates when the response carries one; identity the response lacks is
kept from creds.
@spec start(provider(), String.t()) :: %{ url: String.t(), verifier: String.t(), state: String.t() }
A new PKCE verifier and state, and the authorize URL for them: the standard parameters in omp's order, then the provider's extras.