Files
flynn/docs/plans/openai-oauth-summary.md
2026-02-17 16:34:54 -08:00

197 lines
7.9 KiB
Markdown

# OpenAI OAuth Implementation Summary
## Goal
Enable Flynn to use OpenAI models (Codex) via ChatGPT Plus/Pro OAuth tokens instead of API keys.
## Minimal Viable Approach
**Device Flow + Token Refresh + Codex Endpoint Routing**
Based on OpenCode's proven `CodexAuthPlugin` implementation.
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ User: flynn login openai │
└────────────────┬────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 1. Device Flow (src/auth/openai.ts) │
│ - Request device code from auth.openai.com │
│ - Display user_code + URL │
│ - Poll for authorization │
│ - Exchange code for tokens (access, refresh, id) │
│ - Extract account_id from JWT claims │
│ - Store to ~/.config/flynn/auth.json (chmod 600) │
└────────────────┬────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 2. Config Update (config.yaml) │
│ models: │
│ default: │
│ provider: openai │
│ model: gpt-5.2-codex │
│ oauth_enabled: true ← NEW │
└────────────────┬────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 3. Daemon Startup (src/daemon/models.ts) │
│ - createClientFromConfig() detects oauth_enabled │
│ - Passes tokenLoader + tokenSaver callbacks │
│ - OpenAIClient created with OAuth mode │
└────────────────┬────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 4. Chat Request (src/models/openai.ts) │
│ - Custom fetch() interceptor checks token expiry │
│ - Auto-refresh if needed via refreshOpenAIToken() │
│ - Set headers: │
│ * Authorization: Bearer {access_token} │
│ * ChatGPT-Account-Id: {account_id} │
│ * originator: flynn │
│ - Rewrite URL to Codex endpoint for codex/gpt-5.x models │
│ - Execute request │
└─────────────────────────────────────────────────────────────────┘
```
---
## File Changes
| File | Type | Lines | Purpose |
|------|------|-------|---------|
| `src/auth/openai.ts` | **NEW** | ~300 | Device flow + token management |
| `src/auth/index.ts` | MODIFY | +8 | Export openai.ts functions |
| `src/models/openai.ts` | MODIFY | +100 | OAuth support + custom fetch |
| `src/config/schema.ts` | MODIFY | +1 | Add `oauth_enabled` field |
| `src/daemon/models.ts` | MODIFY | +15 | Wire up OAuth callbacks |
| `src/cli/commands/login.ts` | **NEW** | ~80 | `flynn login openai` command |
| `src/cli/index.ts` | MODIFY | +2 | Register login command |
| `src/auth/openai.test.ts` | **NEW** | ~150 | Unit tests |
**Total**: ~650 lines of new code + ~130 lines of modifications.
---
## API Flow
### 1. Login Flow
```
flynn login openai
├─► POST https://auth.openai.com/api/accounts/deviceauth/usercode
│ Body: { client_id: "app_EMoamEEZ73f0CkXaXp7hrann" }
│ Response: { device_auth_id, user_code, interval }
├─► Display: "Visit https://auth.openai.com/codex/device"
│ "Enter code: ABCD-1234"
├─► Poll POST https://auth.openai.com/api/accounts/deviceauth/token
│ Body: { device_auth_id, user_code }
│ Response (authorized): { authorization_code, code_verifier }
├─► POST https://auth.openai.com/oauth/token
│ Body: { grant_type: authorization_code, code, code_verifier, ... }
│ Response: { id_token, access_token, refresh_token, expires_in }
├─► Parse JWT to extract account_id
└─► Save to ~/.config/flynn/auth.json
```
### 2. Request Flow
```
User message via Telegram/TUI
├─► modelRouter.chat(request)
├─► OpenAIClient.chat()
├─► Custom fetch() interceptor:
│ ├─► Load token from auth.json
│ ├─► Check expires_at < Date.now()
│ ├─► If expired: POST refresh_token to /oauth/token
│ ├─► Set Authorization header
│ ├─► Rewrite URL to Codex endpoint
│ └─► fetch(codex_url, { headers })
└─► Response returned to agent
```
---
## Key Endpoints
| Purpose | Endpoint |
|---------|----------|
| Device code | `https://auth.openai.com/api/accounts/deviceauth/usercode` |
| Poll auth | `https://auth.openai.com/api/accounts/deviceauth/token` |
| Token exchange | `https://auth.openai.com/oauth/token` |
| Token refresh | `https://auth.openai.com/oauth/token` |
| Codex API | `https://chatgpt.com/backend-api/codex/responses` |
---
## Configuration
### Before (API Key)
```yaml
models:
default:
provider: openai
model: gpt-4
api_key: sk-proj-...
```
### After (OAuth)
```yaml
models:
default:
provider: openai
model: gpt-5.2-codex # or gpt-5.3-codex, gpt-5.1-codex-max
oauth_enabled: true
```
### Current Limitation
- In Flynn, OpenAI OAuth (Codex backend) currently does not send tool definitions to the provider.
- Tool execution is unavailable in this mode; any textual `tool_use` content is non-executable model output.
---
## Testing Strategy
### Unit Tests
```bash
pnpm test src/auth/openai.test.ts
```
Tests:
- JWT parsing (valid/invalid)
- Account ID extraction (multiple claim locations)
- Token storage/loading
### Manual Integration Test
```bash
# 1. Login
flynn login openai
# 2. Verify token stored
cat ~/.config/flynn/auth.json | jq '.openai'
# 3. Create test config
cat > /tmp/flynn-oauth-test.yaml <<EOF
models:
default:
provider: openai
model: gpt-5.2-codex
oauth_enabled: true
telegram:
bot_token: \${TELEGRAM_BOT_TOKEN}
allowed_chat_ids: [123456789]