mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 23:01:30 +00:00
Clean up docs, add session format documentation
This commit is contained in:
parent
011e8d705a
commit
9ed1c4e7ba
4 changed files with 78 additions and 797 deletions
|
|
@ -1,173 +0,0 @@
|
|||
# OAuth Implementation Summary
|
||||
|
||||
## Status: Phase 1 (Anthropic OAuth) - Complete ✓
|
||||
|
||||
Implementation of OAuth2 authentication support for Anthropic (Claude Pro/Max) has been completed according to the plan in `oauth-plan.md`.
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
### New Files Created
|
||||
|
||||
1. **`src/oauth/storage.ts`** - OAuth credentials storage
|
||||
- `loadOAuthCredentials()` - Load credentials for a provider
|
||||
- `saveOAuthCredentials()` - Save credentials for a provider
|
||||
- `removeOAuthCredentials()` - Remove credentials for a provider
|
||||
- `listOAuthProviders()` - List all providers with saved credentials
|
||||
- Stores credentials in `~/.pi/agent/oauth.json` with `0o600` permissions
|
||||
|
||||
2. **`src/oauth/anthropic.ts`** - Anthropic OAuth flow
|
||||
- `loginAnthropic()` - Device code flow implementation with PKCE
|
||||
- `refreshAnthropicToken()` - Refresh expired OAuth tokens
|
||||
- Uses Anthropic's OAuth endpoints with proper client ID and scopes
|
||||
|
||||
3. **`src/oauth/index.ts`** - OAuth provider abstraction
|
||||
- `getOAuthProviders()` - List available OAuth providers
|
||||
- `login()` - Generic login function (routes to provider-specific implementation)
|
||||
- `logout()` - Generic logout function
|
||||
- `refreshToken()` - Refresh token for any provider
|
||||
- `getOAuthToken()` - Get token with automatic refresh if expired
|
||||
|
||||
4. **`src/tui/oauth-selector.ts`** - TUI component for provider selection
|
||||
- Interactive selector for login/logout operations
|
||||
- Shows available providers and their status
|
||||
- Keyboard navigation (arrow keys, Enter, Escape)
|
||||
|
||||
### Modified Files
|
||||
|
||||
1. **`src/model-config.ts`**
|
||||
- Updated `getApiKeyForModel()` to be async and check OAuth credentials
|
||||
- Resolution order for Anthropic:
|
||||
1. `ANTHROPIC_OAUTH_TOKEN` env var
|
||||
2. OAuth storage (auto-refresh if needed)
|
||||
3. `ANTHROPIC_API_KEY` env var
|
||||
- Updated `getAvailableModels()` to be async
|
||||
|
||||
2. **`src/main.ts`**
|
||||
- Updated all calls to `getApiKeyForModel()` and `getAvailableModels()` to await them
|
||||
- Transport's `getApiKey` callback is already async, just needed to await the helper
|
||||
|
||||
3. **`src/tui/tui-renderer.ts`**
|
||||
- Added `/login` and `/logout` slash commands
|
||||
- Implemented `showOAuthSelector()` - shows provider selector and handles auth flow
|
||||
- Implemented `hideOAuthSelector()` - restores editor after auth
|
||||
- Updated `handleInput()` in editor to handle new commands
|
||||
- Added OAuth selector field to class
|
||||
- Updated API key validation to use async `getApiKeyForModel()`
|
||||
|
||||
4. **`src/tui/model-selector.ts`**
|
||||
- Updated `loadModels()` to be async
|
||||
- Changed initialization to await model loading
|
||||
|
||||
5. **`README.md`**
|
||||
- Added "OAuth Authentication (Optional)" section after API Keys
|
||||
- Documented `/login` and `/logout` slash commands
|
||||
- Explained benefits of OAuth (free models, no key management, auto-refresh)
|
||||
|
||||
## How It Works
|
||||
|
||||
### User Flow
|
||||
|
||||
1. User types `/login` in the interactive session
|
||||
2. Provider selector appears (currently only shows Anthropic)
|
||||
3. User selects provider with arrow keys and Enter
|
||||
4. Browser opens to Anthropic's OAuth authorization page
|
||||
5. User authorizes the app and copies the authorization code
|
||||
6. User pastes code in the terminal input
|
||||
7. Tokens are exchanged and saved to `~/.pi/agent/oauth.json`
|
||||
8. User can now use Claude models without API keys
|
||||
|
||||
### Technical Flow
|
||||
|
||||
1. **Login**: Authorization Code Flow with PKCE
|
||||
- Generate PKCE verifier and challenge
|
||||
- Build auth URL with `state=verifier`
|
||||
- User authorizes in browser, gets code in format `code#state`
|
||||
- Exchange code for tokens using JSON API
|
||||
- Save tokens to storage
|
||||
2. **Token Usage**: Check expiry → auto-refresh if needed → return access token
|
||||
3. **API Key Resolution**: OAuth tokens checked before falling back to API keys
|
||||
4. **Logout**: Remove credentials from storage file
|
||||
|
||||
### OAuth Flow Details (from opencode-anthropic-auth)
|
||||
|
||||
Based on SST's opencode implementation:
|
||||
- **Redirect URI**: `https://console.anthropic.com/oauth/code/callback`
|
||||
- **Authorization Code Format**: `code#state` (split on `#`)
|
||||
- **Token Exchange**: Uses JSON body (not form-urlencoded)
|
||||
- **State Parameter**: Uses PKCE verifier as state
|
||||
- **Code Query Param**: Sets `code=true` in auth URL
|
||||
|
||||
### Security
|
||||
|
||||
- Tokens stored in `~/.pi/agent/oauth.json` with `0o600` permissions (owner read/write only)
|
||||
- PKCE used for authorization code flow (prevents authorization code interception)
|
||||
- 5-minute buffer before token expiry to prevent edge cases
|
||||
- Tokens never logged (would need to add `[REDACTED]` in debug output if we add logging)
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. **Happy Path**
|
||||
- `/login` → authorize → verify token saved
|
||||
- Use Claude models → verify OAuth token used
|
||||
- `/logout` → verify credentials removed
|
||||
|
||||
2. **Error Cases**
|
||||
- Invalid authorization code
|
||||
- Network errors during token exchange
|
||||
- Expired refresh token
|
||||
|
||||
3. **Fallback Behavior**
|
||||
- OAuth token expires → auto-refresh
|
||||
- Refresh fails → fall back to API key
|
||||
- No OAuth, no API key → show helpful error
|
||||
|
||||
4. **Integration**
|
||||
- Test with `ANTHROPIC_OAUTH_TOKEN` env var (manual token)
|
||||
- Test with saved OAuth credentials (auto-refresh)
|
||||
- Test with `ANTHROPIC_API_KEY` fallback
|
||||
- Test switching between OAuth and API key models
|
||||
|
||||
## Next Steps (Phase 2 - Future)
|
||||
|
||||
Phase 2 (GitHub Copilot OAuth) is planned but not implemented. See `oauth-plan.md` for details.
|
||||
|
||||
Key differences from Anthropic:
|
||||
- Two-step token exchange (GitHub OAuth → Copilot API token)
|
||||
- Custom headers required for every request
|
||||
- Shorter token lifespan (~30 min)
|
||||
- More complex implementation
|
||||
|
||||
## Success Criteria (Phase 1) ✓
|
||||
|
||||
- [x] Plan documented
|
||||
- [x] `pi login` successfully authenticates with Anthropic
|
||||
- [x] Tokens saved to `oauth.json` with correct permissions
|
||||
- [x] Models work with OAuth tokens (detected as `sk-ant-oat-...`)
|
||||
- [x] Token auto-refresh works on expiry
|
||||
- [x] `pi logout` removes credentials
|
||||
- [x] Falls back to API keys when OAuth not available
|
||||
- [x] No breaking changes for existing users
|
||||
- [x] TypeScript compilation passes
|
||||
- [x] Linting passes
|
||||
- [x] README updated with OAuth documentation
|
||||
|
||||
## Files Summary
|
||||
|
||||
**New Files (4):**
|
||||
- `src/oauth/storage.ts` (2,233 bytes)
|
||||
- `src/oauth/anthropic.ts` (3,225 bytes)
|
||||
- `src/oauth/index.ts` (2,662 bytes)
|
||||
- `src/tui/oauth-selector.ts` (3,386 bytes)
|
||||
|
||||
**Modified Files (5):**
|
||||
- `src/model-config.ts` - Async API key resolution with OAuth
|
||||
- `src/main.ts` - Async updates for model/key lookups
|
||||
- `src/tui/tui-renderer.ts` - Login/logout commands and UI
|
||||
- `src/tui/model-selector.ts` - Async model loading
|
||||
- `README.md` - OAuth documentation
|
||||
|
||||
**Total Changes:**
|
||||
- ~11,506 bytes of new code
|
||||
- Multiple async function updates
|
||||
- Documentation updates
|
||||
- Zero breaking changes
|
||||
|
|
@ -1,394 +0,0 @@
|
|||
# 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:
|
||||
1. The `@mariozechner/pi-ai` Anthropic provider already handles OAuth tokens (checks for `sk-ant-oat` prefix)
|
||||
2. No custom headers needed - just return the token
|
||||
3. Simpler flow - only needs refresh token exchange
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
1. **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`
|
||||
|
||||
2. **User Experience**
|
||||
```bash
|
||||
$ 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
|
||||
```
|
||||
|
||||
3. **Token Storage**
|
||||
- File: `~/.pi/agent/oauth.json`
|
||||
- Permissions: `0o600` (owner read/write only)
|
||||
- Format:
|
||||
```json
|
||||
{
|
||||
"anthropic": {
|
||||
"type": "oauth",
|
||||
"refresh": "ory_rt_...",
|
||||
"access": "sk-ant-oat-...",
|
||||
"expires": 1734567890000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **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:
|
||||
|
||||
1. Check `ANTHROPIC_OAUTH_TOKEN` env var (manual OAuth token)
|
||||
2. Check `~/.pi/agent/oauth.json` for OAuth credentials (auto-refresh if needed)
|
||||
3. Check `ANTHROPIC_API_KEY` env var (paid API key)
|
||||
4. Fail with helpful error message
|
||||
|
||||
### Implementation Details
|
||||
|
||||
#### New Files
|
||||
|
||||
**`src/oauth/storage.ts`**
|
||||
```typescript
|
||||
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`**
|
||||
```typescript
|
||||
export async function loginAnthropic(): Promise<void>
|
||||
export async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials>
|
||||
```
|
||||
|
||||
**`src/oauth/index.ts`**
|
||||
```typescript
|
||||
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 `getApiKey` callback to be async
|
||||
- Handle async `getApiKeyForModel()`
|
||||
|
||||
**`src/cli.ts`**
|
||||
- Add `login` command (no args - shows selector)
|
||||
- Add `logout` command (no args - shows selector)
|
||||
|
||||
**`README.md`**
|
||||
- Document `pi login` and `pi logout` commands
|
||||
- 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.
|
||||
|
||||
```bash
|
||||
$ 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:
|
||||
1. Get list of available OAuth providers (filter out ones without implementation)
|
||||
2. Show `SelectList` with provider names
|
||||
3. Call provider-specific login flow
|
||||
4. Save credentials
|
||||
5. Show success message
|
||||
|
||||
#### `pi logout`
|
||||
|
||||
No arguments. Shows interactive selector to pick provider.
|
||||
|
||||
```bash
|
||||
$ 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:
|
||||
1. Get list of logged-in providers from `oauth.json`
|
||||
2. Show `SelectList` with logged-in providers
|
||||
3. Confirm logout
|
||||
4. Remove credentials
|
||||
5. Show success message
|
||||
|
||||
### Dependencies
|
||||
|
||||
No new dependencies needed:
|
||||
- Use built-in `crypto` for PKCE generation (copy from opencode)
|
||||
- Use built-in `fetch` for OAuth calls
|
||||
- Use existing `SelectList` for TUI
|
||||
|
||||
### Testing
|
||||
|
||||
1. **Manual Testing**
|
||||
- `pi login` → select Anthropic → authorize → verify token saved
|
||||
- `pi` → use Claude models → verify OAuth token used
|
||||
- Wait for token expiry → verify auto-refresh
|
||||
- `pi logout` → verify credentials removed
|
||||
- `pi` → verify falls back to API key
|
||||
|
||||
2. **Integration Testing**
|
||||
- Test with `ANTHROPIC_OAUTH_TOKEN` env var
|
||||
- Test with saved OAuth credentials
|
||||
- Test with `ANTHROPIC_API_KEY` fallback
|
||||
- Test token refresh on expiry
|
||||
|
||||
### Security
|
||||
|
||||
- Store tokens in `~/.pi/agent/oauth.json` with `0o600` permissions
|
||||
- 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:
|
||||
1. Custom `fetch` interceptor for special headers
|
||||
2. Two-step token exchange (OAuth → Copilot API token)
|
||||
3. More complex headers (`User-Agent`, `Editor-Version`, etc.)
|
||||
4. Support for Enterprise deployments (different base URLs)
|
||||
|
||||
### Implementation Approach
|
||||
|
||||
#### Token Exchange Flow
|
||||
|
||||
1. **GitHub OAuth** (standard device code flow)
|
||||
- Client ID: `Iv1.b507a08c87ecfe98`
|
||||
- Get GitHub OAuth token
|
||||
|
||||
2. **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
|
||||
|
||||
```typescript
|
||||
{
|
||||
"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`:
|
||||
|
||||
```typescript
|
||||
// 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`**
|
||||
```typescript
|
||||
export async function loginGitHubCopilot(): Promise<void>
|
||||
export async function refreshCopilotToken(githubToken: string): Promise<OAuthCredentials>
|
||||
export async function createCopilotFetch(getAuth: () => Promise<OAuthCredentials>): CustomFetch
|
||||
```
|
||||
|
||||
#### Storage Format
|
||||
|
||||
```json
|
||||
{
|
||||
"github-copilot": {
|
||||
"type": "oauth",
|
||||
"refresh": "gho_...", // GitHub OAuth token
|
||||
"access": "copilot_token_...", // Copilot API token
|
||||
"expires": 1234567890000 // Copilot token expiry (short-lived)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Challenges
|
||||
|
||||
1. **Token Lifespan**: Copilot tokens expire quickly (~30 min), need frequent refresh
|
||||
2. **Custom Headers**: Must inject special headers for every request
|
||||
3. **Enterprise Support**: Different base URLs for GitHub Enterprise
|
||||
4. **Vision Requests**: Special `Copilot-Vision-Request: true` header needed
|
||||
|
||||
## Migration Path
|
||||
|
||||
Users won't need to change anything:
|
||||
1. Existing API key users continue working
|
||||
2. OAuth is opt-in via `pi login`
|
||||
3. Can switch between OAuth and API keys by setting env vars
|
||||
4. Can use both (OAuth for Anthropic, API key for OpenAI, etc.)
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
### README.md
|
||||
|
||||
Add new section after "API Keys":
|
||||
|
||||
```markdown
|
||||
## 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
|
||||
|
||||
```markdown
|
||||
### /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
|
||||
- [x] Write plan
|
||||
- [ ] Implement OAuth storage (`storage.ts`)
|
||||
- [ ] Implement Anthropic OAuth flow (`anthropic.ts`)
|
||||
- [ ] Update `getApiKeyForModel()`
|
||||
- [ ] Add `pi login` command
|
||||
- [ ] Add `pi logout` command
|
||||
- [ ] 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
|
||||
- [x] Plan documented
|
||||
- [ ] `pi login` successfully authenticates with Anthropic
|
||||
- [ ] Tokens saved to `oauth.json` with correct permissions
|
||||
- [ ] Models work with OAuth tokens (detected as `sk-ant-oat-...`)
|
||||
- [ ] Token auto-refresh works on expiry
|
||||
- [ ] `pi logout` removes credentials
|
||||
- [ ] Falls back to API keys when OAuth not available
|
||||
- [ ] No breaking changes for existing users
|
||||
|
||||
### Phase 2
|
||||
- [ ] `pi login` successfully authenticates with GitHub Copilot
|
||||
- [ ] Copilot models available in `/model` selector
|
||||
- [ ] Requests include all required headers
|
||||
- [ ] Token refresh works for short-lived tokens
|
||||
- [ ] Enterprise deployments supported
|
||||
- [ ] No breaking changes for existing users
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
# OAuth Testing Checklist
|
||||
|
||||
## Manual Testing Guide
|
||||
|
||||
### Prerequisites
|
||||
- You need a Claude Pro or Claude Max subscription
|
||||
- A web browser for OAuth authorization
|
||||
|
||||
### Test 1: Basic Login Flow
|
||||
1. Start pi in interactive mode:
|
||||
```bash
|
||||
pi
|
||||
```
|
||||
|
||||
2. Type `/login` and press Enter
|
||||
|
||||
3. Expected: OAuth provider selector appears showing "Anthropic (Claude Pro/Max)"
|
||||
|
||||
4. Press Enter to select Anthropic
|
||||
|
||||
5. Expected:
|
||||
- Browser opens to https://claude.ai/oauth/authorize?...
|
||||
- Terminal shows "Paste the authorization code below:"
|
||||
|
||||
6. Authorize the app in the browser
|
||||
|
||||
7. Copy the authorization code from the browser
|
||||
|
||||
8. Paste the code in the terminal and press Enter
|
||||
|
||||
9. Expected:
|
||||
- Success message: "✓ Successfully logged in to Anthropic"
|
||||
- Message: "Tokens saved to ~/.pi/agent/oauth.json"
|
||||
|
||||
10. Verify file created:
|
||||
```bash
|
||||
ls -la ~/.pi/agent/oauth.json
|
||||
```
|
||||
Expected: File exists with permissions `-rw-------` (0600)
|
||||
|
||||
11. Verify file contents:
|
||||
```bash
|
||||
cat ~/.pi/agent/oauth.json
|
||||
```
|
||||
Expected: JSON with structure:
|
||||
```json
|
||||
{
|
||||
"anthropic": {
|
||||
"type": "oauth",
|
||||
"refresh": "ory_rt_...",
|
||||
"access": "sk-ant-oat-...",
|
||||
"expires": 1234567890000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test 2: Using OAuth Token
|
||||
1. With OAuth credentials saved (from Test 1), start a new pi session:
|
||||
```bash
|
||||
pi
|
||||
```
|
||||
|
||||
2. Type `/model` and press Enter
|
||||
|
||||
3. Expected: Claude models (e.g., claude-sonnet-4-5) appear in the list
|
||||
|
||||
4. Select a Claude model
|
||||
|
||||
5. Send a simple message:
|
||||
```
|
||||
You: Hello, tell me what 2+2 is
|
||||
```
|
||||
|
||||
6. Expected:
|
||||
- Model responds successfully
|
||||
- No "API key not found" errors
|
||||
- OAuth token is used automatically (check that it works without ANTHROPIC_API_KEY set)
|
||||
|
||||
### Test 3: Logout
|
||||
1. In an interactive pi session, type `/logout`
|
||||
|
||||
2. Expected: OAuth provider selector shows "Anthropic (Claude Pro/Max)"
|
||||
|
||||
3. Press Enter to select Anthropic
|
||||
|
||||
4. Expected:
|
||||
- Success message: "✓ Successfully logged out of Anthropic"
|
||||
- Message: "Credentials removed from ~/.pi/agent/oauth.json"
|
||||
|
||||
5. Verify file is empty or doesn't contain anthropic:
|
||||
```bash
|
||||
cat ~/.pi/agent/oauth.json
|
||||
```
|
||||
Expected: `{}` or file doesn't exist
|
||||
|
||||
### Test 4: Token Auto-Refresh
|
||||
This test requires waiting for token expiry (or manually setting a past expiry time).
|
||||
|
||||
1. Modify `~/.pi/agent/oauth.json` to set an expired time:
|
||||
```json
|
||||
{
|
||||
"anthropic": {
|
||||
"type": "oauth",
|
||||
"refresh": "ory_rt_...",
|
||||
"access": "sk-ant-oat-...",
|
||||
"expires": 1000000000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Start pi and send a message to a Claude model
|
||||
|
||||
3. Expected:
|
||||
- Token is automatically refreshed
|
||||
- New access token and expiry time saved to oauth.json
|
||||
- Request succeeds without user intervention
|
||||
|
||||
### Test 5: Fallback to API Key
|
||||
1. Remove OAuth credentials:
|
||||
```bash
|
||||
rm ~/.pi/agent/oauth.json
|
||||
```
|
||||
|
||||
2. Set ANTHROPIC_API_KEY:
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY=sk-ant-...
|
||||
```
|
||||
|
||||
3. Start pi and send a message to a Claude model
|
||||
|
||||
4. Expected:
|
||||
- Model uses API key successfully
|
||||
- No errors about missing OAuth credentials
|
||||
|
||||
### Test 6: OAuth Takes Priority
|
||||
1. Set both OAuth and API key:
|
||||
- Login with `/login` (saves OAuth credentials)
|
||||
- Also set: `export ANTHROPIC_API_KEY=sk-ant-...`
|
||||
|
||||
2. Start pi and check which is used
|
||||
|
||||
3. Expected: OAuth token is used (verify in logs or by checking if API key would fail)
|
||||
|
||||
### Test 7: Error Handling - Invalid Code
|
||||
1. Start pi and type `/login`
|
||||
|
||||
2. Select Anthropic
|
||||
|
||||
3. Enter an invalid authorization code (e.g., "invalid123")
|
||||
|
||||
4. Expected:
|
||||
- Error message shown
|
||||
- No credentials saved
|
||||
- Can try again
|
||||
|
||||
### Test 8: Error Handling - No Browser
|
||||
1. Start pi in a headless environment or where browser can't open
|
||||
|
||||
2. Type `/login` and select Anthropic
|
||||
|
||||
3. Expected:
|
||||
- URL is shown in terminal
|
||||
- User can manually copy URL to browser
|
||||
- Auth flow continues normally
|
||||
|
||||
### Test 9: Slash Command Autocomplete
|
||||
1. Start pi
|
||||
|
||||
2. Type `/` and press Tab
|
||||
|
||||
3. Expected: Autocomplete shows `/login` and `/logout` among other commands
|
||||
|
||||
4. Type `/log` and press Tab
|
||||
|
||||
5. Expected: Autocomplete completes to `/login` or `/logout`
|
||||
|
||||
### Test 10: No OAuth Available (Logout)
|
||||
1. Ensure no OAuth credentials are saved:
|
||||
```bash
|
||||
rm ~/.pi/agent/oauth.json
|
||||
```
|
||||
|
||||
2. Start pi and type `/logout`
|
||||
|
||||
3. Expected:
|
||||
- Message: "No OAuth providers logged in. Use /login first."
|
||||
- Selector doesn't appear
|
||||
|
||||
## Automated Testing Ideas
|
||||
|
||||
The following tests should be added to the test suite:
|
||||
|
||||
1. **Unit Tests for `oauth/storage.ts`**
|
||||
- `saveOAuthCredentials()` creates file with correct permissions
|
||||
- `loadOAuthCredentials()` returns saved credentials
|
||||
- `removeOAuthCredentials()` removes credentials
|
||||
- `listOAuthProviders()` returns correct list
|
||||
|
||||
2. **Unit Tests for `oauth/anthropic.ts`**
|
||||
- PKCE generation creates valid verifier/challenge
|
||||
- Token refresh makes correct API call
|
||||
- Error handling for failed requests
|
||||
|
||||
3. **Integration Tests for `model-config.ts`**
|
||||
- `getApiKeyForModel()` checks OAuth before API key
|
||||
- Async behavior works correctly
|
||||
- Proper fallback to API keys
|
||||
|
||||
4. **Mock Tests for OAuth Flow**
|
||||
- Mock fetch to test token exchange
|
||||
- Test auto-refresh logic
|
||||
- Test expiry checking
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **Manual Testing Required**: The OAuth flow involves browser interaction, so it's difficult to fully automate
|
||||
2. **Requires Real Credentials**: Testing with a real Claude Pro/Max account is needed
|
||||
3. **Token Expiry**: Default tokens last a long time, so auto-refresh is hard to test naturally
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] All manual tests pass
|
||||
- [ ] OAuth login works end-to-end
|
||||
- [ ] Tokens are saved securely (0600 permissions)
|
||||
- [ ] Token auto-refresh works
|
||||
- [ ] Logout removes credentials
|
||||
- [ ] Fallback to API keys works
|
||||
- [ ] No breaking changes for existing API key users
|
||||
- [ ] Error handling is user-friendly
|
||||
- [ ] Documentation is clear and accurate
|
||||
78
packages/coding-agent/docs/session.md
Normal file
78
packages/coding-agent/docs/session.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Session File Format
|
||||
|
||||
Sessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with a `type` field.
|
||||
|
||||
## File Location
|
||||
|
||||
```
|
||||
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
|
||||
```
|
||||
|
||||
Where `<path>` is the working directory with `/` replaced by `-`.
|
||||
|
||||
## Entry Types
|
||||
|
||||
### SessionHeader
|
||||
|
||||
First line of the file. Defines session metadata.
|
||||
|
||||
```json
|
||||
{"type":"session","id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","provider":"anthropic","modelId":"claude-sonnet-4-5","thinkingLevel":"off"}
|
||||
```
|
||||
|
||||
### SessionMessageEntry
|
||||
|
||||
A message in the conversation. The `message` field contains an `AppMessage` (see [RPC.md](./RPC.md#message-types)).
|
||||
|
||||
```json
|
||||
{"type":"message","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello","timestamp":1733234567890}}
|
||||
{"type":"message","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"api":"anthropic-messages","provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop","timestamp":1733234567891}}
|
||||
{"type":"message","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false,"timestamp":1733234567900}}
|
||||
```
|
||||
|
||||
### ModelChangeEntry
|
||||
|
||||
Emitted when the user switches models mid-session.
|
||||
|
||||
```json
|
||||
{"type":"model_change","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}
|
||||
```
|
||||
|
||||
### ThinkingLevelChangeEntry
|
||||
|
||||
Emitted when the user changes the thinking/reasoning level.
|
||||
|
||||
```json
|
||||
{"type":"thinking_level_change","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}
|
||||
```
|
||||
|
||||
## Type Definitions
|
||||
|
||||
See [`src/session-manager.ts`](../src/session-manager.ts) for entry types and [`packages/agent/src/types.ts`](../../agent/src/types.ts) for `AppMessage`.
|
||||
|
||||
## Parsing Example
|
||||
|
||||
```typescript
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const lines = readFileSync("session.jsonl", "utf8").trim().split("\n");
|
||||
|
||||
for (const line of lines) {
|
||||
const entry = JSON.parse(line);
|
||||
|
||||
switch (entry.type) {
|
||||
case "session":
|
||||
console.log(`Session: ${entry.id}, Model: ${entry.provider}/${entry.modelId}`);
|
||||
break;
|
||||
case "message":
|
||||
console.log(`${entry.message.role}: ${JSON.stringify(entry.message.content)}`);
|
||||
break;
|
||||
case "model_change":
|
||||
console.log(`Switched to: ${entry.provider}/${entry.modelId}`);
|
||||
break;
|
||||
case "thinking_level_change":
|
||||
console.log(`Thinking: ${entry.thinkingLevel}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue