OAuth Providers
Two OAuth-backed auth lanes let clanky use subscription-backed providers instead of standard API keys for its general-purpose orchestrator and voice paths.
| Provider | Upstream | Auth Target | Token Storage | Transport |
|---|---|---|---|---|
claude-oauth | Anthropic Messages API | claude.ai/oauth/authorize | data/claude-oauth-tokens.json (with optional bootstrap from opencode auth) | Standard Anthropic SDK with OAuth headers |
OpenAI OAuth (openai-oauth) | OpenAI / ChatGPT-authenticated Codex lane | auth.openai.com | data/openai-oauth-tokens.json | Reverse-engineered ChatGPT Codex backend |
Claude OAuth (claude-oauth)
Overview
The claude-oauth provider authenticates against the Anthropic Messages API using OAuth tokens from a Claude Pro/Max subscription, instead of a paid API key. This gives access to Claude models at zero marginal cost (covered by the subscription).
The OAuth provider calls the API directly via @anthropic-ai/sdk using an OAuth bearer token plus the required beta headers/query params.
Authentication Flow
- One-time login: OAuth 2.0 PKCE flow against
claude.ai/oauth/authorize - Token storage: Refresh + access tokens stored in
data/claude-oauth-tokens.json - Per-request: Access token auto-refreshed when expired, sent as
Authorization: Bearer <token>
API Compatibility Layer
The OAuth lane uses the standard Anthropic SDK request path with OAuth-specific auth and beta flags:
Authorization: Bearer <access_token>instead ofx-api-keyanthropic-beta: claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14header?beta=truequery param appended to/v1/messages
Token Lifecycle
[refresh_token] --POST /v1/oauth/token--> [access_token + new refresh_token]
|
v
stored in clanky's local token file
(optionally bootstrapped once from opencode Anthropic auth)
access_token used for API calls
auto-refreshed when expires within the refresh buffer
Setup
Option 1: Environment Variable (quickstart)
Set CLAUDE_OAUTH_REFRESH_TOKEN in your .env. You can obtain a refresh token by:
- Visit the authorize URL (logged at startup when token is set)
- Authorize and copy the code
- Exchange via the token endpoint
Option 2: Manual Token File
Create data/claude-oauth-tokens.json:
{
"refreshToken": "<your-refresh-token>",
"accessToken": "",
"expiresAt": 0
}
The access token will be auto-populated on first use.
If you want that file somewhere else, set CLAUDE_OAUTH_TOKEN_FILE and clanky reads and writes that path instead.
Option 3: Reuse opencode Anthropic auth
If you've already logged into Anthropic via opencode or ocrefresh, clanky can bootstrap its own data/claude-oauth-tokens.json from opencode's file-backed Anthropic auth. When both the local cache and opencode auth exist, clanky prefers the more complete/fresher token set and mirrors that into the local cache before continuing to refresh locally. On Windows this bootstrap understands DPAPI-protected auth-secret.json entries.
Configuration
DEFAULT_PROVIDER=claude-oauth
CLAUDE_OAUTH_REFRESH_TOKEN=your-refresh-token-here
In settings, use provider: "claude-oauth" with standard Anthropic model IDs:
{
"provider": "claude-oauth",
"model": "claude-sonnet-4-6"
}
Architecture
LLMService
└── claude-oauth provider
└── Anthropic SDK (same as `anthropic` provider)
└── OAuth token loader (claudeOAuth.ts)
├── local token file
├── bootstrap or refresh local cache from fresher opencode auth when available
├── token refresh (console.anthropic.com/v1/oauth/token)
├── Bearer auth header
├── beta headers
└── ?beta=true query param
The provider reuses the exact same callAnthropic code path as the regular anthropic provider. The only difference is the Anthropic SDK client is configured with an OAuth bearer token plus the required beta headers/query flags.
Prompt Caching
The shared Anthropic request path now emits SDK-typed prompt-caching breakpoints for both the API-key anthropic lane and the subscription-backed claude-oauth lane.
- Stable system prompt text is sent as a cacheable Anthropic text block, so persona, long-form behavior instructions, and tool definitions can be reused instead of re-prefilled every turn.
- Anthropic tool-loop follow-ups mark the newest
tool_resultblock as cacheable, so immediate continuation calls can reuse large fetched context such as search or article results. - The shared Anthropic request path retries one transient overload/rate-limit/network failure before surfacing an error. Streaming retries only happen before any text delta has been emitted, so live voice output does not duplicate partial speech.
This is prompt-prefix reuse, not hidden server-side conversation memory. The Messages API remains stateless and still only knows the context the app resends on each call.
Structured Output Handling
JSON compliance for reply generation is requested via a text instruction appended to the system prompt ("Return strict JSON only." + the schema). API-level enforcement via output_config.format is not used because REPLY_OUTPUT_SCHEMA exceeds Claude's union type limit (29 type: ["string", "null"] / anyOf parameters).
When the model returns plain prose instead of JSON (e.g. short simple requests like "call me X"), the reply pipeline recovers the raw text as the reply content (structured_output_recovered_as_prose warning) instead of silently dropping it. After a tool loop, prose that looks like process narration (e.g. "I searched the conversation history and found...") is dropped to prevent tool internals from leaking into chat, but genuinely conversational prose is still recovered — the model's intent to communicate is preserved.
Reverse-Engineered From
Based on the opencode-anthropic-auth plugin (npm opencode-anthropic-auth@0.0.13), which implements the same OAuth flow used by the Claude Code CLI. Key constants:
- Client ID:
9d1c250a-e61b-44d9-88ed-5944d1962f5e - OAuth authorize:
https://claude.ai/oauth/authorize - Token endpoint:
https://console.anthropic.com/v1/oauth/token - Redirect URI:
https://console.anthropic.com/oauth/code/callback - Scopes:
org:create_api_key user:profile user:inference
OpenAI OAuth (openai-oauth)
Overview
OpenAI OAuth is the product-level auth lane for ChatGPT-backed OpenAI usage in this repo. The canonical provider key is openai-oauth.
Use it for the general-purpose OpenAI orchestrator, voice admission, and voice generation paths. It is not itself the coding worker. The local OpenAI-side coding worker is codex-cli.
When the openai_oauth preset is used, the expected split is:
- general brain/orchestrator work runs on OpenAI OAuth (
openai-oauth) - interactive browser work defaults to
openai_computer_use - implementation defaults to the local
codex-cliworker claude-codecan still be used as an optional local coding worker
This provider is experimental. The authentication model is grounded in the official OpenAI docs, but the transport described below is reverse-engineered from current ChatGPT behavior and from ../opencode; it is not part of the public OpenAI API contract.
What OpenAI Documents
- Codex supports ChatGPT sign-in and API-key sign-in
- ChatGPT sign-in opens a browser login flow by default, and active sessions refresh tokens automatically
- ChatGPT-authenticated Codex usage follows plan limits and credits instead of standard API-key billing
Authentication Flow
- Current implementation performs a PKCE login against
https://auth.openai.com. - Tokens are stored in
data/openai-oauth-tokens.json - If no local token cache is present, the provider also discovers Codex auth files from
$CHATGPT_LOCAL_HOME/auth.json,$CODEX_HOME/auth.json,~/.chatgpt-local/auth.json, and~/.codex/auth.json - Access tokens are refreshed automatically from the stored refresh token
- Requests are sent with bearer auth plus the ChatGPT account id when available
Dashboard auth indicators treat OPENAI_OAUTH_REFRESH_TOKEN and data/openai-oauth-tokens.json equivalently, so file-backed logins show as authenticated even when no env var is set.
Transport Layer (Reverse-Engineered)
The custom fetch wrapper rewrites OpenAI Responses API requests onto a ChatGPT backend path that OpenAI does not document as a public API surface:
/v1/responses->https://chatgpt.com/backend-api/codex/responsesAuthorization: Bearer <access_token>chatgpt-account-id: <account_id>OpenAI-Beta: responses=experimentaloriginator: clanky- request body normalization for Codex compatibility:
storedefaults tofalsewhen omittedmax_output_tokensis stripped before forwarding (Codex rejects it)- for
gpt-5*model ids,temperatureandtop_pare stripped before forwarding - for
gpt-5*model ids, reasoning effortminimalis normalized tolow
Setup
One-time login:
bun scripts/openai-oauth-login.ts
This starts a local callback server, opens the browser login flow, then writes tokens to data/openai-oauth-tokens.json.
Environment bootstrap:
OPENAI_OAUTH_REFRESH_TOKEN=your-refresh-token
DEFAULT_PROVIDER=openai-oauth
DEFAULT_MODEL_OPENAI_OAUTH=gpt-5.4
Usage
Use the canonical provider id provider: "openai-oauth" in settings with a supported model such as:
{
"provider": "openai-oauth",
"model": "gpt-5.4"
}
For dev-team code-agent tasks:
codex-cliis the local workspace-aware OpenAI coding worker and uses the local Codex CLI login/session on that machine
Pricing
The local pricing table records openai-oauth usage as zero USD for app bookkeeping.
In product terms, treat this as ChatGPT-authenticated usage subject to ChatGPT plan limits and credits, not standard API-key billing. It is not an OpenAI-documented guarantee of unlimited or free usage.
Reverse-Engineered From
Based primarily on the ../opencode provider approach plus current observed ChatGPT behavior:
- OAuth issuer:
https://auth.openai.com - ChatGPT backend:
https://chatgpt.com/backend-api/codex/responses - Account-scoped bearer requests with
ChatGPT-Account-Id
Treat this provider as experimental and isolated from the standard openai API-key path.
