yoi/tickets/llm-auth-codex-oauth.md

83 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Codex OAuth 認証の流用
**Status: Reviewed / Approved**(詳細は `llm-auth-codex-oauth.review.md`
## 背景
決定済み方針(`docs/plan/llm_providers.md`で、ChatGPT サブスクリプションの OAuth トークンを流用して OpenAI Responses API を叩く経路を第一級サポートとする。OpenAI は Codex CLI を Apache-2.0 で公開し、ChatGPT OAuth の第三者ツール利用を service terms で名指し禁止していない(互換経路)。
Codex CLI の実装github.com/openai/codex、`codex-rs/login/` 配下)から以下が確定:
- トークンは `~/.codex/auth.json``{ auth_mode, tokens: { id_token (JWT 文字列), access_token (JWT), refresh_token, account_id }, last_refresh }` 形式で保存。`OPENAI_API_KEY` フィールドも同居する場合あり
- リクエストは `https://chatgpt.com/backend-api/v1/responses` に投げるResponses API wire
- 認証ヘッダは `Authorization: Bearer <access_token>` + `ChatGPT-Account-Id: <account_id>`FedRAMP 組織なら `X-OpenAI-Fedramp: true`
- リフレッシュは `https://auth.openai.com/oauth/token``{ client_id: "app_EMoamEEZ73f0CkXaXp7hrann", grant_type: "refresh_token", refresh_token }` を JSON POST。response は `{ id_token?, access_token?, refresh_token? }`、401 body の `error.code``refresh_token_expired|reused|invalidated` で永続失敗を分類
- proactive refresh の判定: access_token JWT の `exp` claim が `now` 以下、fallback で `last_refresh < now - 8 days`(バッファなし)
- Codex CLI 自身はファイルロックを取らず、(a) プロセス内 `AsyncMutex` (b) refresh 前の guarded reload (account_id 比較で他プロセスの先行更新を検知) (c) 書込前の再 load + diff merge で並行動作を吸収
- Codex CLI の credentials store はデフォルト `File` (auth.json)。`Keyring` / `Auto` モードは opt-in
## 要件
1. **`AuthRef::CodexOAuth` の追加**: `manifest::AuthRef` に ChatGPT OAuth バリアントを追加型定義済み、provider 側で実装する)
2. **トークン読み取り**: `~/.codex/auth.json` を読み、以下を取り出す
- `tokens.access_token` / `tokens.refresh_token` / `tokens.account_id`
- `last_refresh`fallback 期限判定用)
- `tokens.id_token` の JWT payload を base64url decode し `https://api.openai.com/auth` claim から `chatgpt_account_is_fedramp` (bool) と `chatgpt_plan_type` を取得(署名検証なし)
3. **ヘッダ注入**: `HttpTransport``AuthRef::CodexOAuth` を解決するとき以下を組み立てる
- `Authorization: Bearer <access_token>`
- `ChatGPT-Account-Id: <account_id>`
- FedRAMP 組織なら `X-OpenAI-Fedramp: true`
実装方針: llm-worker 側で `ResolvedAuth::Custom(Arc<dyn AuthProvider>)` バリアントと `AuthProvider` traitasync でリクエスト毎にヘッダを返す)を新設し、`HttpTransport` の `AuthRequirement::Custom` 経路から呼ぶ。Codex 固有ロジックauth.json 読取・refresh`crates/provider/src/codex_oauth/` に置く。
4. **base_url の自動適用**: `AuthRef::CodexOAuth` 指定時、`ModelConfig.base_url` 未指定なら `https://chatgpt.com/backend-api` を既定とする。明示指定API key 経路と共用したい場合の `https://api.openai.com` 等)は尊重。`scheme` 側の既定(`https://api.openai.com`)は変えず、`build_client` で auth に応じて差し替える。
5. **トークンリフレッシュ**:
- 送信前に proactive チェック: access_token JWT の `exp` claim が `now` 以下、fallback で `last_refresh + 8 days < now`。バッファは持たせないCodex 準拠。401 駆動の retry は将来拡張
- refresh は `https://auth.openai.com/oauth/token` に上記 body を POST、response の `access_token` / `id_token` / `refresh_token` を auth.json に書き戻し `last_refresh = now` を更新
- 並行動作の排他: プロセス内 `tokio::sync::Mutex` を取った上で、refresh 直前に再 load し account_id が一致しないなら自分はスキップして読み直した値を採用guarded reload。書込時も再 load + diff mergeCodex CLI に揃える、ファイルロックは使わない)
- 失敗時: `RefreshTokenError::Permanent`401 + `refresh_token_expired|reused|invalidated`)は `ClientError::Auth` 相当で「`codex login` を再実行してください」のメッセージ、`Transient` (network 等) はそのまま伝播
6. **scheme/openai_responses との組合せで動作**: `ModelConfig { scheme: OpenAIResponses, base_url: (既定), model_id: "gpt-5-codex" 等, auth: CodexOAuth }` で ChatGPT 枠を使って Codex 相当の動作ができる
7. **完了時の動作**: ChatGPT アカウント保持者が `codex login`File モード)済みの環境で insomnia を起動すると、追加設定なしで Codex と同じモデル(`gpt-5-codex` 等)が利用可能
## 設計判断
### auth.json の書き戻しと競合制御
ファイルロックは取らない。Codex CLI 自身も取っていない(単純な truncate write + `mode 0o600`)。代わりに guarded reload + 書込前 merge で吸収する。`fs2` 依存を増やさない方が筋が良い。
### refresh token の失効時の挙動
`Permanent` 分類のエラーをそのまま `ClientError::Auth { message }` で返し、CLI/TUI 上で「`codex login` を再実行」のメッセージを表示する。insomnia 側で再ログインフローは持たない。
### ChatGPT OAuth 専用モデル (gpt-5-codex 等)
`scheme/openai_responses/capability.rs` の静的テーブルに該当モデル ID を追加するのみ。API key 経路で使われたら OpenAI 側が 401 で弾くので動的フォールバックは不要。
### Keyring モード対応
Scope 外。Codex CLI の `cli_auth_credentials_store = "keyring"` で保存しているユーザーには `~/.codex/config.toml``"file"` に切り替えて `codex login` をやり直すよう案内するauth.json 不在エラー時にメッセージで誘導)。
### セキュリティと権限
`~/.codex/auth.json` のパーミッション600を尊重。書き込み時に `OpenOptions``mode(0o600)` を再設定するCodex CLI と同じ)。
## Scope 外
- Claude Pro/Max OAuth 経路(方針上非採用)
- `claude -p` CLI fork 経路
- `codex login` 自体の実装Codex CLI に任せ、insomnia は auth.json を読むのみ)
- Codex CLI の Keyring credentials store 対応
- 401 駆動の動的 refresh + retryproactive のみで実装)
- ChatGPT backend の rate limit 観測Retry-After 処理は HttpTransport 共通の責務)
## 依存
- `manifest::AuthRef::CodexOAuth`(型のみ定義済み)
- `llm_worker::llm_client::transport::HttpTransport` / `auth::AuthRequirement::Custom`(経路の枠は用意済み)
- `scheme/openai_responses``/v1/responses` wire format、実装済み