llm-auth-codex-oauth完了

This commit is contained in:
Keisuke Hirata 2026-04-20 23:14:45 +09:00
parent 6c6eb0dcb6
commit 1aa992d07e
2 changed files with 0 additions and 115 deletions

View File

@ -1,82 +0,0 @@
# 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、実装済み

View File

@ -1,33 +0,0 @@
# Codex OAuth 認証の流用 — レビュー
判定: **Approved**(ブロッカーなし)
## 指摘事項
### 1. `ensure_fresh` の double-load が冗長(軽微)
`crates/provider/src/codex_oauth/mod.rs` 104 行目と 114 行目で、mutex 保持下に `auth_json::load()` を 2 回呼んでいる。1 回目で stale 判定、2 回目を guarded reload として別プロセス差分検知に使う意図だが、mutex 取得直後に連続 load しているので間隔がほぼゼロで実効的な差分検知になっていない。`persist_refreshed` 内の merge に任せ、`pre_refresh` を使わず `snap` をそのまま refresh 材料にするほうが素直。
判断: 他プロセスとの競合検知を「保険として二段階で絞る」意図なら害はない。このままでも OK。
### 2. `pub use PermanentReason``pub(crate)` に絞る(軽微)
`crates/provider/src/codex_oauth/mod.rs` 36 行目。クレート外利用者がいないので `pub(crate) use` で十分。
判断: 気が向いたら直す。
### 3. AGENTS.md の git 操作指示の追記
本チケット実装と独立した変更。commit 時は別コミットに切り出すか、本コミットで「ついで」と明記する。
判断: commit 作成時の注意事項。実装自体への指摘ではない。
### 4. `Arc::new(CodexAuthProvider)` の二重 Arc軽微
`crates/provider/src/lib.rs``ResolvedAuth::Custom(Arc::new(provider))` としており、`CodexAuthProvider` 自体も内部に `Arc<Mutex<State>>` を持つため Arc が二段。`CodexAuthProvider: Clone` にして単段で扱える余地はある。
判断: MVP として OK。
## 結論
4 件とも軽微な改善候補で、いずれも機能・正しさ・設計方針に影響しない。チケット要件はすべて満たされており、テストも通っている。完了として問題なし。