# 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
```

---

## 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]
