- Add /login and /logout commands for OAuth flow - OAuth tokens stored in ~/.pi/agent/oauth.json with 0600 permissions - Auto-refresh tokens when expired (5min buffer) - Priority: OAuth > ANTHROPIC_OAUTH_TOKEN env > ANTHROPIC_API_KEY env - Fix model selector async loading and re-render - Add bracketed paste support to Input component for long codes - Update README.md with OAuth documentation - Add implementation docs and testing checklist
11 KiB
OAuth Support Plan
Add OAuth2 authentication for Anthropic (Claude Pro/Max) and GitHub Copilot to enable free model access for users with subscriptions.
Overview
Many users have Claude Pro/Max or GitHub Copilot subscriptions but can't use them with pi because it requires API keys. This plan adds OAuth support to allow these users to authenticate with their existing subscriptions.
Current limitations:
- Anthropic: Requires paid API keys (
sk-ant-api03-...) - GitHub Copilot: Not supported at all
After implementation:
- Anthropic: Support OAuth tokens (
sk-ant-oat-...) from Claude Pro/Max subscriptions - GitHub Copilot: Support OAuth tokens from Copilot Individual/Business/Enterprise subscriptions
Phase 1: Anthropic OAuth (Initial Implementation)
We'll start with Anthropic OAuth because:
- The
@mariozechner/pi-aiAnthropic provider already handles OAuth tokens (checks forsk-ant-oatprefix) - No custom headers needed - just return the token
- Simpler flow - only needs refresh token exchange
Authentication Flow
-
Device Code Flow (OAuth2 PKCE)
- Client ID:
9d1c250a-e61b-44d9-88ed-5944d1962f5e - Authorization URL:
https://claude.ai/oauth/authorize - Token URL:
https://console.anthropic.com/v1/oauth/token - Scopes:
org:create_api_key user:profile user:inference
- Client ID:
-
User Experience
$ pi login # Shows selector: "Anthropic (Claude Pro/Max)" # Opens browser to https://claude.ai/oauth/authorize?code=... # User authorizes # Paste authorization code in terminal # Saves tokens to ~/.pi/agent/oauth.json # Success message shown -
Token Storage
- File:
~/.pi/agent/oauth.json - Permissions:
0o600(owner read/write only) - Format:
{ "anthropic": { "type": "oauth", "refresh": "ory_rt_...", "access": "sk-ant-oat-...", "expires": 1734567890000 } }
- File:
-
Token Refresh
- Check expiry before each agent loop (with 5 min buffer)
- Auto-refresh using refresh token if expired
- Save new tokens back to
oauth.json
API Key Resolution Order
Modified getApiKeyForModel() for Anthropic:
- Check
ANTHROPIC_OAUTH_TOKENenv var (manual OAuth token) - Check
~/.pi/agent/oauth.jsonfor OAuth credentials (auto-refresh if needed) - Check
ANTHROPIC_API_KEYenv var (paid API key) - Fail with helpful error message
Implementation Details
New Files
src/oauth/storage.ts
export interface OAuthCredentials {
type: "oauth";
refresh: string;
access: string;
expires: number;
}
export async function loadOAuthCredentials(provider: string): Promise<OAuthCredentials | null>
export async function saveOAuthCredentials(provider: string, creds: OAuthCredentials): Promise<void>
export async function removeOAuthCredentials(provider: string): Promise<void>
export async function listOAuthProviders(): Promise<string[]>
src/oauth/anthropic.ts
export async function loginAnthropic(): Promise<void>
export async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials>
src/oauth/index.ts
export type SupportedOAuthProvider = "anthropic" | "github-copilot";
export async function login(provider: SupportedOAuthProvider): Promise<void>
export async function logout(provider: SupportedOAuthProvider): Promise<void>
export async function refreshToken(provider: SupportedOAuthProvider): Promise<string>
Modified Files
src/model-config.ts
- Update
getApiKeyForModel()to check OAuth credentials - Add async token refresh logic
- Change return type to
Promise<string | undefined>
src/main.ts
- Update
getApiKeycallback to be async - Handle async
getApiKeyForModel()
src/cli.ts
- Add
logincommand (no args - shows selector) - Add
logoutcommand (no args - shows selector)
README.md
- Document
pi loginandpi logoutcommands - Explain OAuth vs API key authentication
- Update API Keys section with OAuth option
CLI Commands
pi login
No arguments. Shows interactive selector to pick provider.
$ pi login
Select provider to login:
> Anthropic (Claude Pro/Max)
GitHub Copilot (coming soon)
Opening browser to authorize...
Paste the authorization code here: abc123def456...
✓ Successfully authenticated with Anthropic
Tokens saved to ~/.pi/agent/oauth.json
Implementation:
- Get list of available OAuth providers (filter out ones without implementation)
- Show
SelectListwith provider names - Call provider-specific login flow
- Save credentials
- Show success message
pi logout
No arguments. Shows interactive selector to pick provider.
$ pi logout
Select provider to logout:
> Anthropic (Claude Pro/Max)
[no other providers logged in]
✓ Successfully logged out of Anthropic
Credentials removed from ~/.pi/agent/oauth.json
Implementation:
- Get list of logged-in providers from
oauth.json - Show
SelectListwith logged-in providers - Confirm logout
- Remove credentials
- Show success message
Dependencies
No new dependencies needed:
- Use built-in
cryptofor PKCE generation (copy from opencode) - Use built-in
fetchfor OAuth calls - Use existing
SelectListfor TUI
Testing
-
Manual Testing
pi login→ select Anthropic → authorize → verify token savedpi→ use Claude models → verify OAuth token used- Wait for token expiry → verify auto-refresh
pi logout→ verify credentials removedpi→ verify falls back to API key
-
Integration Testing
- Test with
ANTHROPIC_OAUTH_TOKENenv var - Test with saved OAuth credentials
- Test with
ANTHROPIC_API_KEYfallback - Test token refresh on expiry
- Test with
Security
- Store tokens in
~/.pi/agent/oauth.jsonwith0o600permissions - Never log tokens (use
[REDACTED]in debug output) - Clear credentials on logout
- Token refresh uses HTTPS only
Phase 2: GitHub Copilot OAuth (Future)
Why Later?
GitHub Copilot requires more work:
- Custom
fetchinterceptor for special headers - Two-step token exchange (OAuth → Copilot API token)
- More complex headers (
User-Agent,Editor-Version, etc.) - Support for Enterprise deployments (different base URLs)
Implementation Approach
Token Exchange Flow
-
GitHub OAuth (standard device code flow)
- Client ID:
Iv1.b507a08c87ecfe98 - Get GitHub OAuth token
- Client ID:
-
Copilot Token Exchange
- Exchange GitHub token for Copilot API token
- Endpoint:
https://api.github.com/copilot_internal/v2/token - Returns short-lived token (expires in ~30 min)
Required Headers
{
"Authorization": `Bearer ${copilotToken}`,
"User-Agent": "GitHubCopilotChat/0.32.4",
"Editor-Version": "vscode/1.105.1",
"Editor-Plugin-Version": "copilot-chat/0.32.4",
"Copilot-Integration-Id": "vscode-chat",
"Openai-Intent": "conversation-edits",
"X-Initiator": "agent" // or "user"
}
Custom Fetch
Need to add customFetch support to ProviderTransport:
// In packages/ai/src/stream.ts or in coding-agent transport wrapper
export interface CustomFetchOptions {
provider: string;
url: string;
init: RequestInit;
}
export type CustomFetch = (opts: CustomFetchOptions) => Promise<Response>;
// Then use it before calling provider APIs
if (customFetch && needsCustomFetch(provider)) {
const response = await customFetch({ provider, url, init });
}
New Files
src/oauth/github-copilot.ts
export async function loginGitHubCopilot(): Promise<void>
export async function refreshCopilotToken(githubToken: string): Promise<OAuthCredentials>
export async function createCopilotFetch(getAuth: () => Promise<OAuthCredentials>): CustomFetch
Storage Format
{
"github-copilot": {
"type": "oauth",
"refresh": "gho_...", // GitHub OAuth token
"access": "copilot_token_...", // Copilot API token
"expires": 1234567890000 // Copilot token expiry (short-lived)
}
}
Challenges
- Token Lifespan: Copilot tokens expire quickly (~30 min), need frequent refresh
- Custom Headers: Must inject special headers for every request
- Enterprise Support: Different base URLs for GitHub Enterprise
- Vision Requests: Special
Copilot-Vision-Request: trueheader needed
Migration Path
Users won't need to change anything:
- Existing API key users continue working
- OAuth is opt-in via
pi login - Can switch between OAuth and API keys by setting env vars
- Can use both (OAuth for Anthropic, API key for OpenAI, etc.)
Documentation Updates
README.md
Add new section after "API Keys":
## OAuth Authentication (Optional)
If you have a Claude Pro/Max subscription, you can use OAuth instead of API keys:
\`\`\`bash
pi login
# Select "Anthropic (Claude Pro/Max)"
# Authorize in browser
# Paste code
\`\`\`
This gives you:
- Free access to Claude models (included in your subscription)
- No need to manage API keys
- Automatic token refresh
To logout:
\`\`\`bash
pi logout
\`\`\`
**Note:** OAuth tokens are stored in `~/.pi/agent/oauth.json` with restricted permissions (0600).
Slash Commands Section
### /login
Login with OAuth to use subscription-based models (Claude Pro/Max, GitHub Copilot):
\`\`\`
/login
\`\`\`
Opens an interactive selector to choose provider.
### /logout
Logout from OAuth providers:
\`\`\`
/logout
\`\`\`
Shows a list of logged-in providers to logout from.
Timeline
Phase 1 (Anthropic OAuth) - Estimated: 1 day
- Write plan
- Implement OAuth storage (
storage.ts) - Implement Anthropic OAuth flow (
anthropic.ts) - Update
getApiKeyForModel() - Add
pi logincommand - Add
pi logoutcommand - Update README.md
- Test with real Claude Pro account
- Commit and publish
Phase 2 (GitHub Copilot OAuth) - Estimated: 2-3 days
- Design custom fetch architecture
- Implement GitHub OAuth flow
- Implement Copilot token exchange
- Add custom headers interceptor
- Support Enterprise deployments
- Test with real Copilot subscription
- Update README.md
- Commit and publish
Success Criteria
Phase 1
- Plan documented
pi loginsuccessfully authenticates with Anthropic- Tokens saved to
oauth.jsonwith correct permissions - Models work with OAuth tokens (detected as
sk-ant-oat-...) - Token auto-refresh works on expiry
pi logoutremoves credentials- Falls back to API keys when OAuth not available
- No breaking changes for existing users
Phase 2
pi loginsuccessfully authenticates with GitHub Copilot- Copilot models available in
/modelselector - Requests include all required headers
- Token refresh works for short-lived tokens
- Enterprise deployments supported
- No breaking changes for existing users