From 1aa992d07e46ceedefd0dc922998beb00e416386 Mon Sep 17 00:00:00 2001 From: Hare Date: Mon, 20 Apr 2026 23:14:45 +0900 Subject: [PATCH] =?UTF-8?q?llm-auth-codex-oauth=E5=AE=8C=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tickets/llm-auth-codex-oauth.md | 82 -------------------------- tickets/llm-auth-codex-oauth.review.md | 33 ----------- 2 files changed, 115 deletions(-) delete mode 100644 tickets/llm-auth-codex-oauth.md delete mode 100644 tickets/llm-auth-codex-oauth.review.md diff --git a/tickets/llm-auth-codex-oauth.md b/tickets/llm-auth-codex-oauth.md deleted file mode 100644 index 4d5da248..00000000 --- a/tickets/llm-auth-codex-oauth.md +++ /dev/null @@ -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 ` + `ChatGPT-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 ` - - `ChatGPT-Account-Id: ` - - FedRAMP 組織なら `X-OpenAI-Fedramp: true` - - 実装方針: llm-worker 側で `ResolvedAuth::Custom(Arc)` バリアントと `AuthProvider` trait(async でリクエスト毎にヘッダを返す)を新設し、`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 merge(Codex 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 + retry(proactive のみで実装) -- 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、実装済み) diff --git a/tickets/llm-auth-codex-oauth.review.md b/tickets/llm-auth-codex-oauth.review.md deleted file mode 100644 index 0872ccde..00000000 --- a/tickets/llm-auth-codex-oauth.review.md +++ /dev/null @@ -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>` を持つため Arc が二段。`CodexAuthProvider: Clone` にして単段で扱える余地はある。 - -判断: MVP として OK。 - -## 結論 - -4 件とも軽微な改善候補で、いずれも機能・正しさ・設計方針に影響しない。チケット要件はすべて満たされており、テストも通っている。完了として問題なし。