ChatGPT プラン連携(Sign in with ChatGPT)実装計画

配信者が自分の ChatGPT Plus / Pro プランの利用枠で、会話AIを動かせるようにする。 OpenAI 公式の「Sign in with ChatGPT」(以下 SIWC)を使い、Codex CLI のクライアント ID を流用する非公式な方法は使わない。

(2026-11-03 時点の調査とコードに基づく。コードはまだ変更していない)

前提: OpenAI の承認が要る(Phase 0)

決めたこと

項目 内容 理由
会話AIの種類 LLMType.ChatGPTPlan を enum の末尾に足す 送り方(Responses API・stream: true・store: false)と認証(OAuth トークン)が既存の OpenAI 系と違う。既存の LLMType.Response は store: true・previous_response_id・temperature・system ロールを使っており、SIWC の制限に合わない
プランで動かす範囲 会話・独り言・カンペ・練習(LLMController の会話経路)だけ SIWC が動くのは Responses API だけ。文字起こしは非対応と明記されている。Chat Completions・Assistants・モデレーションは記載が無い
補助機能 要約・感情分析・話しかけ判定・住所検出・ゲーム判定・文字起こし・モデレーションは、これまでどおり OpenAI の API キー 上と同じ。Admin で「この機能には OpenAI のキーが要る」と表示して、キーが無いまま補助機能だけ失敗している状態に気付けるようにする
トークンの置き場所 配信 PC の中だけ(SecurityUtils.Encrypt で端末に紐づけて暗号化)。Firestore には置かない ChatGPT のトークンを預かる範囲を最小にする。バックエンドと配信 PC の両方がリフレッシュすると、リフレッシュトークンの更新が衝突する
上限・ログイン切れ 既存のフェイルオーバー(#380 failoverLLM1/2)で、回数の閾値を待たずにすぐ切り替える 無人配信で止まると無音になる
解析用 LLM analysisLLM の選択肢には出さない(Phase 1) 画像の詳細解析はリクエストの形が別で、上限も早く使い切る

データの流れ

  1. 選ぶ(Admin): キャラの会話の設定 #config-llm-provider で「ChatGPT プラン」を選び、モデルを選ぶ
  2. 保存: characterConfigs/{id}.llmModelConfig.currentLLM = "ChatGPTPlan"、新フィールド llmModelConfig.chatGptPlanModelId(例 gpt-5.6-luna)
  3. 読み出し(Unity): CharacterConfigSyncManager → LLMController.ApplyRemoteConfig → currentLLM / chatGptPlanModelId
  4. ログイン(配信 PC): Unity のログイン画面(AuthUIController)の「ChatGPT と連携」→ 既定のブラウザで OpenAI のログイン → 127.0.0.1 で受け取る → コードをトークンに交換 → 付与されたスコープに chatgpt.tokens.use.direct があるか確かめる → 暗号化して persistentDataPath に保存
  5. 送る: ChatGptPlanAuth.GetAccessTokenAsync()(期限が近ければリフレッシュ)→ POST https://api.openai.com/v1/responses(Authorization: Bearer <access token>)→ SSE を最後まで受けてから本文をつなぐ → 既存の応答処理へ
  6. 状態を伝える: ClientPresenceReporter の送信に chatGptPlan: { signedIn, limitedUntil, lastError } を足す → Admin の「配信の準備」の「会話AIの接続」に表示

変更するファイル

Unity(Assets/Scripts/)

ファイル 変更
LLMController.cs enum 末尾に ChatGPTPlan、endpoints に /v1/responses
新規 AI/ChatGptPlan/ChatGptPlanAuth.cs PKCE・state / nonce の照合・127.0.0.1 の受け取り(TwitchOAuthManager の HttpListener を参考にする)・リフレッシュ(同時に1回だけ走らせる)・保存と連携解除・スコープの確認
新規 AI/ChatGptPlan/ChatGptPlanResponsesClient.cs リクエストの組み立て、SSE の解析、エラーの分類(認証切れ / 上限 / モデル不可 / その他)。テストしやすいよう static に分ける
LLMcontroller.ApiKeys.cs ChatGPTPlan はキーファイルもクラウドのキーも読まない
LLMcontroller.RequestTransport.cs SendRequest の「API キーが無ければ例外」を、認証情報があるかの判定(新しい HasCredentialFor(llm))に置き換える。CreateRequest で Bearer にトークンを付ける
LLMcontroller.RequestBody.cs ChatGPTPlan のリクエスト(下の「リクエストの形」)
LLMcontroller.ResponseParsing.cs SSE の response.output_text.delta をつなぐ。response.completed / response.failed を見る
LLMcontroller.Monologue.cs SendMonologueRequest に ChatGPTPlan の分岐を足す(今は OpenAI 以外が endpoints[LLMType.OpenAI] と OpenAI キーへ落ちる作り)
LLMcontroller.Practice.cs 練習の送信に分岐を足す
LLMcontroller.PromptSettings.cs max_output_tokens を送れないので、文字数の上限はプロンプトと受信後の切り詰めで守る
LLMcontroller.ModelAccessors.cs HasApiKeyFor を HasCredentialFor に揃える。ProviderSupportsSituationVision(ChatGPTPlan) はモデルが画像を受け付けるとき true
LLMcontroller.Retry.cs / LLMController.cs 上限・認証切れのエラーは failoverThreshold を待たずに切り替える。上限での切り替え中は、primaryRecoveryCheckSeconds ではなくリセット時刻(分かれば)まで戻さない。フェイルオーバー先の判定もキーではなく HasCredentialFor で見る。2026-11-04 の再監査 R-02 から、使えないと分かった代わり(事前診断か実際の切り替えで 400・401・403・404。30分)は除く(全部そうならキーのある最初)
LLMcontroller.RemoteConfig.cs / Cloud/DataModels/CloudDataModels.cs chatGptPlanModelId の適用と書き出し
Core/PreflightDiagnostics.cs ログイン済みか、リフレッシュできるか、GET /v1/models に選んだモデルがあるか
Cloud/AuthUIController.cs 「ChatGPT と連携 / 連携を解除」ボタンと、連携の状態の表示
Cloud/ClientPresenceReporter.cs 連携の状態を送る(トークンは送らない)
Cloud/ApiEndpointConfig.cs SIWC の client ID を環境(staging / production)ごとに持つ。未設定なら「ChatGPT プランは使えません」

Functions(functions/src/)

ファイル 変更
types/index.ts LLMModelConfig.chatGptPlanModelId
handlers/admin.ts(返事を試す、callLLM 付近) サーバーには ChatGPT のトークンが無いので、ChatGPTPlan は「配信 PC でしか試せません」と返す。テナントの OpenAI キーで代わりに試すかは画面で選ばせる
handlers/broadcastHealth.ts(/client/presence) 連携の状態を受け取って保存する
services/developerInvokeService.ts GPT56_DEVELOPER_MODEL_BY_LLM_TYPE に無い ChatGPTPlan を受けたときの動きを確かめて、明示的に扱う

Admin(docs/admin/)

ファイル 変更
index.html #config-llm-provider に「ChatGPT プラン」とモデル欄。#rc-provider-select(返事を試す)と #api-key-type(キー登録)には足さない
modules/admin-broadcast-multimodal.js AdminLLMContract の会話AIの一覧に足す。解析用の一覧には足さない
modules/admin-key-types.js requiredKeys: ChatGPTPlan は need: 'signin'(配信 PC でログイン)とし、補助機能に OpenAI のキーが要ることを並べて出す
modules/admin-character-purpose.js 会話AI → キー種別の対応に足す
modules/admin-character-config.js 選んだときだけモデル欄と注記を出す(OpenAICompatible の注記と同じ作り)
modules/admin-readiness.js 「会話AIの接続」に、配信 PC の連携状態(clientPresence)を出す
modules/admin-response-check.js ChatGPTPlan を選んだキャラのときの説明

リクエストの形(SIWC の制限に合わせる)

{
  "model": "<chatGptPlanModelId>",
  "instructions": "<effectiveSystemMessage>",
  "input": [{ "role": "user", "content": [
    { "type": "input_text", "text": "<prompt>" },
    { "type": "input_image", "image_url": "data:image/jpeg;base64,..." }
  ]}],
  "reasoning": { "effort": "none" },
  "stream": true,
  "store": false
}

条件の組み合わせと期待する動作

# 会話AI 配信 PC の連携 プランの枠 補助用の OpenAI キー 期待する動作
1 ChatGPTPlan 済 残りあり あり 会話・独り言はプラン、補助機能はキー
2 ChatGPTPlan 済 残りあり なし 会話はプラン。補助機能は今と同じく動かないので、配信の準備で「OpenAI のキーが要る機能」を要対応にする
3 ChatGPTPlan 済 上限に達した — すぐ failoverLLM1 → failoverLLM2。リセット時刻までは戻さない。Admin に「上限のため ○○ で配信中」
4 ChatGPTPlan 未連携 / リフレッシュ失効 — — プリフライトで警告、配信の準備で要対応。会話はフェイルオーバー。アプリの画面に再ログインの案内
5 ChatGPTPlan 済 — — 選んだモデルがプランで使えない → プリフライトで警告し、フェイルオーバー
6 別の AI(ChatGPTPlan はフェイルオーバー先) 未連携 — — フェイルオーバー時にスキップ(今の「キーが無ければスキップ」と同じ)
7 ChatGPTPlan・予約 / 24h(isAutoSwitchBroadcastMode) 済 — — 無人なので、フェイルオーバー先が未設定、またはそのキーが未登録なら、キャラ設定の保存時と配信の準備で警告する
8 ChatGPTPlan — — — Admin の「返事を試す」は、サーバーでは試せないと表示する
9 ChatGPTPlan・chatGptPlanModelId 無し(旧データ・未設定) 済 — — 既定のモデル(gpt-5.6-luna)で動かし、画面には既定であることを出す
10 ChatGPTPlan・古い Unity ビルド — — — 既存の TryParseLLMTypeWithWarning が警告して今の値のまま動く。Admin では clientPresence.appVersion を見て「配信アプリの更新が要る」と出す
11 解析用 LLM — — — 選択肢に出さない
12 同じ ChatGPT アカウントを複数の配信 PC で連携 済 共有 — 枠を共有する。PC ごとの連携状態を Admin に出す

境界・例外:

段階

段階 内容 終わりの条件
Phase 0 OpenAI への申請と、上の「確かめること」 client ID と答えが揃う
Phase 1 Unity: 認証・送信・SSE 解析・フェイルオーバー・プリフライト・ログイン画面(client ID 未設定なら無効) EditMode テストが通る。staging の client ID で配信 PC から会話できる
Phase 2 Admin / Functions: 選択肢・モデル欄・準備ページ・返事を試す・連携状態 Jest とローカル Admin の確認が通る
Phase 3(任意) 補助機能をプランへ寄せる、受信しながら読み上げを始める 別に計画する

Phase 1 の実装状況(2026-11-04・未コミット・Player 未配布)

実装の中身は 03_unity_app.md の同日節。計画から変えた点:

確かめたこと: Unity 6000.2 の DLL で Assembly-CSharp と自前 asmdef を全ソースからコンパイルしてエラー 0(変更前もエラー 0)。ChatGptPlanProtocolTest 69 件を Mono で実行して全件成功。 未確認: Unity 2022.3.15f1 Editor でのコンパイルと EditMode テスト(ChatGptPlanLlmProviderTest は未実行)、実際の OpenAI での連携・会話・上限・取り消し(client ID が無い)、Windows の配信 PC での 127.0.0.1 の受け取り。

確認方法

更新するドキュメント

01_admin_console / 02_backend_functions / 03_unity_app / 04_data_flow / 05_known_issues / 07_unity_complete_inventory / 08_backend_handlers_detail / 09_firestore_and_admin_details / 10_remaining_details / 13_data_schemas / 14_full_broadcast_scenario(docs/20260507最新実装/)

参考

Phase 2 の実装状況(2026-11-04・未コミット・未デプロイ)

実装の中身は 01_admin_console.md・02_backend_functions.md の同日節。計画から変えた点:

確かめたこと: Jest 全体(失敗は負荷で時間切れになる backupVerification・infraAssets だけで、どちらも単独で流すと通る。今回の変更で落ちた admin-copy-terms(「テナント」→「組織」)と admin-stage4-connect-voice(ソースの文字列)は直した)、新規 chatGptPlanAdmin.test.ts 27 件、functions の tsc --noEmit・変更ファイルの ESLint・Admin の ESLint(エラー 0)・?v= の検査、Unity の全ソースのコンパイル(Unity 6000.2 の DLL)。確認用画面(preview-admin-usability.py)をブラウザで開き、配信の準備の「会話AIの接続」(上限・未連携・古い配信アプリ)、会話の設定(本番相当で隠す・保存済みは表示・staging で選べる・注記・モデル欄・代わりの会話AIの注意と保存内容)、AIサービスのキーの「このキャラが使うキー」、返事を試すの説明を確認した。 未確認: staging へのデプロイ、実際の配信アプリからのヘルス報告(Player 未配布)、確認用画面での保存ボタンからの送信(確認用画面の API の作りで止まるため、保存内容は collectLLMConfig の結果で確認した)。

実装後の差分(2026-11-04、再監査 R-02。Player 未配布。Functions・Admin は main に入ると自動で出る): 既定の代わり(Claude・Gemini)は、配信アプリのモデル名が公開モデルに無く返事を作れなかったので、今の公開モデル(claude-haiku-5-5・gemini-3.5-flash-lite)と送信の形に直した。SwitchAwayFromChatGptPlan は使えないと分かった代わりを後回しにし、failoverReady は「キーがあり、使えないと分かっていない代わりがある」にした。事前診断に llm_failover「代わりの会話AI」(pass か warn)を足した。実装の後のレビューを受けて、返事を ChatGPT プランへ送っている途中に上限などになったときの扱いも直した。StateChanged から同期で動く SwitchAwayFromChatGptPlan がその場で切り替えても、その返事の TryFailoverGenerateResponse は失敗したのを ChatGPT プランとして扱い、切り替えた先を最初に試す(以前は切り替えた先を飛ばし、使えないと分かっている代わりにだけ送って返事が無くなることがあった)。全部が失敗したときの失敗の理由の AI 名も、この返事で送った AI(ChatGPT プラン)にした。中身は 03_unity_app.md の「2026-11-04 R-02」と NATIVE_TTS_FIX_A_PLAN_20261104.md 4章。