feat: add OpenAI OAuth, strict model overrides, and Gmail pull mode
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
# 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]
|
||||
Reference in New Issue
Block a user