Feat/anthropic oauth subscription - #27
Merged
Merged
Conversation
Reach Claude Pro/Max subscription inference instead of a metered API key. Spec: docs/superpowers/specs/2026-09-05-anthropic-oauth-provider.md. The subscription endpoint only answers requests that look like Claude Code, so this impersonates it: Claude Code's OAuth client id, User-Agent, beta headers, billing-attribution block and identity block. Read §0.1 before extending — the account at risk is the user's own, which is why this is opt-in and never a default, and why the impersonation is quarantined to the OAuth path by an invariant with a test. The seam is a composed fetch rather than the SDK's apiKey: we already wrap fetch for timeouts, and per-request is where token refresh has to live. Refresh is single-flight and rotation-aware. Anthropic rotates refresh tokens, so two concurrent refreshes can persist a dead one and permanently break the login; before refreshing we re-read the store and defer to a fresher token rather than racing it. An "OAuth not allowed for this organization" 403 is detected at the fetch — generateText throws it while streamText emits it as a chunk, but the fetch sees the same raw body on both paths — then latches for the process and falls back to the API key. The latch gates usesAnthropicOAuth() itself, so the fallback request also drops the identity block: a key-authenticated request must never claim to be Claude Code. Cost: a subscription call prices as undefined, never $0, or rollups would report fake savings. The mode is stamped on model.response and folded into ModelSpan, so cost is a property of the CALL — reading live config at fold time repriced every historical API-key session as "subscription" the moment someone logged in, and made a span's price depend on which machine read the log. pricing.ts no longer imports config.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
…iption Phase 1 of the OAuth spec: our own PKCE login, so freecode works on a machine that never ran Claude Code (Phase 0 could only borrow that CLI's credentials). Authorize against the claude.ai surface, NOT the console one — the console endpoint mints tokens that refresh fine and are then refused at inference time, which is a silent failure hours later rather than a login error. state IS the PKCE verifier (jcode's convention: the token endpoint requires a state, and reusing the verifier binds it to the PKCE secret). That makes login inherently single-process, so there is deliberately no --code flag: a fresh process has a fresh verifier and its exchange could never succeed. The callback listener binds an ephemeral 127.0.0.1 port; failing to bind is not an error but a downgrade to the manual redirect URI and a paste prompt, which is also what --no-browser selects. Paste accepts what a user can plausibly have in hand: a bare code, a callback URL, a query string, or code#state. Login prints the §0.1 disclosure and pins authMode to oauth; logout deletes the tokens and un-pins it. Pinning is what makes an explicit login stick on a machine that also has ANTHROPIC_API_KEY set, where the unpinned fallback prefers the key. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
…e switch OAuth spec §8. The subscription endpoint sends a different beta set and two extra system blocks, so an OAuth run is not the same instrument as an API-key run. Without this, one subscription run silently became the bar every later API-key run was measured against. An absent mode on either side normalises to api-key: every baseline written before this landed has no mode, and treating that as a mismatch would discard all of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
OAuth spec §3.5 asked for these shapes to be checked rather than assumed.
They are covered — by the sk-ant- prefix pattern alone: the generic
token[:=]value pattern does NOT match a JSON-quoted key ("access_token": …),
so if Anthropic ever changes the prefix, this test is what fails instead of
tokens quietly reaching the embedding index.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uXPoints at the spec and states the load-bearing bits an agent needs before touching this: the opt-in paths, that login is single-process, where the billing block sits, that cost is stamped on the call, and that tool-name mapping stays unbuilt until a real turn answers §9 Q1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.