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

7.9 KiB

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)

models:
  default:
    provider: openai
    model: gpt-4
    api_key: sk-proj-...

After (OAuth)

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

pnpm test src/auth/openai.test.ts

Tests:

  • JWT parsing (valid/invalid)
  • Account ID extraction (multiple claim locations)
  • Token storage/loading

Manual Integration Test

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