Skip to content

LLM クライアントと Responses API

源码版本rust-v0.145.0

codex は全てのモデル呼び出しを Responses API(/v1/responses)に集約する。このレイヤーは core/src/client.rs で実装され、責務は prompt + モデル設定 + auth を受け取り、リクエストを組み立て、WebSocket または HTTP で通信し、SSE ストリームを ResponseEvent にパースし、token usage、reasoning summary、tool call delta を上位ループに渡すことだ。同じ ModelClient がリモート compaction リクエスト(compact_conversation_history)や auth prewarm、独立の responses-api-proxy デバッグプロキシも兼ねる。

責務

  1. セッションレベルのクライアント:ModelClient::new は auth manager、provider info、thread id、HTTP client factory など 13 個の引数を取り、これらの turn をまたいで安定な状態を Arc<ModelClientState> にまとめる。codex-rs/core/src/client.rs:413-460
  2. Turn レベルの session:ModelClientSessionnew_session で生成され、一つの WebsocketSession キャッシュと turn_state: Arc<OnceLock<String>> を持ち、同じ turn 内の複数リクエストで sticky-routing token を再利用する。codex-rs/core/src/client.rs:480-486
  3. ストリーミング入口:ModelClientSession::streamWireApi でディスパッチする。Responses API は WebSocket(利用可能なら)→ 失敗時 HTTP にフォールバックし、全てのパスは ResponseStream を返す。codex-rs/core/src/client.rs:1792-1843
  4. リモート圧縮:同じ ModelClientcompact_conversation_history を置き、/responses/compact エンドポイント経由で server 側に履歴を折り畳ませ、新しい ResponseItem リストを返す。codex-rs/core/src/client.rs:538-547

設計動機

Responses API は従来の Chat Completions と異なり、reasoning summary、server-side compaction、streaming tool call delta などの能力をネイティブにサポートする。codex は全てのモデル呼び出しを /v1/responses 一本(WireApi::Responses は現在唯一のバリアント)に集め、統一された ResponseEvent ストリーム処理を実現した。WebSocket はオプションの転送方式で、HTTP SSE に比べて同一接続上で複数の差分リクエストを送れる(turn 内の多段推論でコンテキストを再利用)。x-codex-turn-state sticky-routing token で同じ turn が同じバックエンドインスタンスにルーティングされる。ただし WS は複雑度が高いため、force_http_fallback は WS を永久に無効化してキャッシュをクリアし、エラー後は session 全体を HTTP に戻す。

ModelClientModelClientSession の二層分割:前者は session-scoped(Codex セッション全体で一つ)、後者は turn-scoped(各 turn で new_session() を一回呼ぶ)で、turn をまたいでは再利用できない——そうしないと turn_state token が turn をまたいでルーティングを汚染する。current_client_setup は auth 解析のロックを単一点に集め、prewarm(バックグラウンドの事前接続)と実際の turn が一貫した auth/provider 状態を見ることを保証する。compact_conversation_history が同じ client を再利用するのは細かいポイント:/responses ではなく /responses/compact に向かうが、transport ヘッダ、auth ヘッダ、telemetry は全て同じ builder を使い、compaction リクエストと通常の turn がモニタリング上で区別がつかないようにする。

主要ファイル

codex-rs/core/src/client.rs:253-272ModelClientModelClientSession の構造体定義。後者のドキュメントは各 turn で new_session を一回呼ぶことを明示する。codex-rs/core/src/client.rs:509-528force_http_fallback。WS を永久無効化 + キャッシュクリア + telemetry 送信。codex-rs/core/src/client.rs:944-962current_client_setup。auth + provider の解析を集約。codex-rs/codex-api/src/common.rs:74-119ResponseEvent 列挙型。Created / OutputItemDone / Completed / ReasoningSummaryDelta など全ての SSE イベント型。codex-rs/responses-api-proxy/src/lib.rs:73-108run_main。デバッグ用 tiny_http server で upstream_url に転送し、任意で dump する。

ModelClient::new のパラメータは #[allow(clippy::too_many_arguments)] が必要なほど多いが、どれも session-scoped で必須だ。create_model_provider(provider_info, auth_manager) が provider info を具体的な provider(OpenAI / Bedrock / Ollama / LMStudio / Anthropic-style external)に変換し、以降の全 auth 解析はこの provider 抽象を経由する。

rust
// core/src/client.rs:413-460 — ModelClient::new 装配 session-scoped 状态
pub fn new(
    auth_manager: Option<Arc<AuthManager>>,
    agent_identity_policy: AgentIdentityAuthPolicy,
    thread_id: ThreadId,
    provider_info: ModelProviderInfo,
    session_source: SessionSource,
    originator: String,
    model_verbosity: Option<VerbosityConfig>,
    enable_request_compression: bool,
    include_timing_metrics: bool,
    beta_features_header: Option<String>,
    item_ids_enabled: bool,
    concurrent_reasoning_summaries_enabled: bool,
    attestation_provider: Option<Arc<dyn AttestationProvider>>,
    http_client_factory: HttpClientFactory,
) -> Self {
    let model_provider = create_model_provider(provider_info, auth_manager);
    // ...
    Self { state: Arc::new(ModelClientState { /* ... */ }), /* ... */ }
}

stream が turn 内の核心入口だ。まず wire_api が Responses か(現在唯一の選択肢)を見て、次に responses_websocket_enabled() で WS か HTTP かを決める。WS パスが WebsocketStreamOutcome::FallbackToHttp を返したら try_switch_fallback_transport で永久に HTTP に切り替え、stream_responses_api に戻る。これで WS 失敗で turn が止まらず、再利用の利益を失うだけで済む。

rust
// core/src/client.rs:1792-1843 — ModelClientSession::stream 分派
pub async fn stream(
    &mut self,
    prompt: &Prompt,
    model_info: &ModelInfo,
    session_telemetry: &SessionTelemetry,
    effort: Option<ReasoningEffortConfig>,
    summary: ReasoningSummaryConfig,
    service_tier: Option<String>,
    responses_metadata: &CodexResponsesMetadata,
    inference_trace: &InferenceTraceContext,
) -> Result<ResponseStream> {
    let wire_api = self.client.state.provider.info().wire_api;
    match wire_api {
        WireApi::Responses => {
            if self.client.responses_websocket_enabled() {
                match self.stream_responses_websocket(/* ... */).await? {
                    WebsocketStreamOutcome::Stream(stream) => return Ok(stream),
                    WebsocketStreamOutcome::FallbackToHttp => {
                        self.try_switch_fallback_transport(session_telemetry, model_info);
                    }
                }
            }
            self.stream_responses_api(/* ... */).await
        }
    }
}

current_client_setup は prewarm と turn が共有する入口だ。provider から auth()api_provider() を取り、ProviderAuthScope で agent identity scope を解決する——agent_identity_policy が ChatGPT auth の agent identity への自動昇格を許可するかを決め、session_source が scope の範囲を決める。CurrentClientSetup を返し、後続の build_api_transport はこの構造を read-only で使う。

rust
// core/src/client.rs:944-962 — 集中 auth + provider 解析
async fn current_client_setup(&self) -> Result<CurrentClientSetup> {
    let auth = self.state.provider.auth().await;
    let api_provider = self.state.provider.api_provider().await?;
    let resolved_auth = self
        .state
        .provider
        .api_auth_for_scope(ProviderAuthScope {
            agent_identity_policy: self.agent_identity_policy,
            session_source: self.state.session_source.clone(),
            agent_identity_session_fallback: self.state.agent_identity_session_fallback.clone(),
        })
        .await?;
    Ok(CurrentClientSetup {
        auth,
        api_provider,
        api_auth: resolved_auth.auth,
        agent_identity_telemetry: resolved_auth.agent_identity_telemetry,
    })
}

データフロー

境界と失敗

  • WS フォールバックは永久:force_http_fallbackAtomicBool::swap で WS を永久に無効化し、store_cached_websocket_session(WebsocketSession::default()) で構築済み接続をクリアする。一度 turn 内でフォールバックが発火すると、session の残り全 turn は HTTP を通り、WS を再試しない。codex-rs/core/src/client.rs:509-528
  • turn session は再利用しない:ModelClientSession のドキュメントは各 turn で new_session を一回呼ぶことを明示する。turn をまたいで再利用すると前の turn の x-codex-turn-state sticky-routing token が次の turn に流れ、サーバ側のルーティングが乱れる。codex-rs/core/src/client.rs:260-272
  • prompt 空なら compact をスキップ:compact_conversation_historyprompt.input.is_empty() の時に空の Vec を返し、無駄なネットワーク呼び出しをしない。codex-rs/core/src/client.rs:548-550
  • auth prewarm と turn の一貫性:prewarm_authcurrent_client_setup を通り、バックグラウンドの事前接続と実際の turn が全く同じ auth/provider 状態を見ることを保証する。prewarm で接続を建てた後に新しい token で上書きされるのを防ぐ。codex-rs/core/src/client.rs:979-981

まとめ

ModelClient は全てのモデル呼び出しを Responses API に集約し、new_session で turn-scoped のハンドルを切り出す。stream は WebSocket と HTTP の二択で、失敗時は永久に HTTP に切る。compact_conversation_history は同じ transport と auth ヘッダで /responses/compact に向かう。responses-api-proxy は独立のデバッグプロキシを提供する。このレイヤーはループのスケジューリングには関与せず、prompt を流し込み ResponseEvent を流し返すだけだ。ループを本当に駆動するのは Agent メインループ。auth 状態は ChatGPT ログインと認証 でディスクに落ちた auth.json から来る。