
OpenAI Codex CLIをClaude・Gemini・Llamaの上で動かす — C# 50行で
OpenAIのCodex CLIは優れたエディタエージェントUXを提供します(shellツール、apply_patch、plan tracking がすべて揃っています)。問題は、2026年2月時点でOpenAI Responses APIだけをサポートしていることです。Chat Completionサポートは削除されており(codex-rs/model-provider-info/src/lib.rsのWireApi enumにはResponsesのみが残っています)、Chat Completionのみをサポートするエンドポイント(Ollama、LM Studio、お気に入りのLlama runner)はそのまま閉ざされてしまいます。本記事は.NET 10のfile-basedプログラムとMicrosoft.Extensions.AIのIChatClient抽象化を活用し、50行のC#一ファイルでResponses互換サーバーを立て、OpenRouterを介してCodex CLIを任意のモデル上で動作させた過程をまとめます。 はじめに Codex CLIはResponsesを話すどんなサーバーとも気持ちよく対話します。model_provider configブロックがまさにこのために存在します。つまり、好きなモデルでバックされたResponses互換HTTPエンドポイントを立てさえすれば、Codexは汎用フロントエンドになり、頭脳はユーザーが選べます。 最近私が気に入っているトリックは次のとおりです。Microsoft.Extensions.AIのベンダー中立なIChatClient抽象化の上で、OpenAI Chat CompletionサーバーとResponses APIサーバーを同時に動かす50行のC#スクリプトを起動します。バックエンドはOpenRouterに向けます(APIキー1つで Claude、Gemini、Llama、GPTなど数百のモデルを使えます)。そしてCodexに対して、OpenAIではなくこのローカルスクリプトと対話するよう設定します。 最終的な結果は、OpenAI Codex CLIがAnthropicのClaude 3.5 Sonnetの上で動作する状態です(あるいはその日に使いたい別のモデルの上でも)。 構成要素 私が自分で公開しているCadenza.AgentというMSBuild SDKを使います。単一の.csファイルを実行可能なエージェントサーバーに変換するSDKで、.NET 10のfile-basedプログラム向け単一ファイルスクリプティングSDKファミリーの一部です(dotnet run script.csと同じ発想ですが、より豊富なTier-1 API(Tool、UseOllama、UseOpenAi、Runなど)を提供します)。Agentバリアントは次を公開します。 POST /v1/chat/completions — Aider / Continue / Cursor / Copilot BYOK / sgpt 向け POST /v1/responses — Codex CLI 向け どちらもユーザーが構成した同じIChatClientでバックされます。バックエンドを変えても wireフォーマットはそのままです。 LLM側はOpenRouterを使います。OpenAIのChat Completion wireフォーマットを別のbase URLでそのまま提供するので、Microsoft.Extensions.AI.OpenAIのChatClientをそのまま挿して使えます。環境変数1つ、任意のモデル。 Codexの設定はCODEX_HOME環境変数のトリックを活用します。~/.codex/config.tomlを編集する代わりに、Codexが指すディレクトリをサンプル専用に別途作っておけば、そこから新しいconfig.tomlを読みます。これでユーザーのグローバル設定に一切触れない、自己完結型のサンプルが作れます。 スクリプト バックエンドのすべて、ファイル1つです。 #!/usr/bin/env dotnet run #:sdk Cadenza.Agent@1.0.14 using System.ClientModel; using OpenAI; var apiKey = Env.Get("OPENROUTER_API_KEY") ?? throw new InvalidOperationException("OPENROUTER_API_KEY env var missing"); var model = Env.Get("OPENROUTER_MODEL") ?? "anthropic/claude-3.5-sonnet"; ServedModelName = "cadenza-codex-openrouter"; // サンプル専用のCodex homeディレクトリを作成。 var codexHome = Path.Combine(Env.Cwd, ".cadenza-codex-openrouter"); MakeDir(codexHome); var catalogPath = Path.Combine(codexHome, "cadenza-catalog.json").Replace('\\', '/'); var configToml = $""" model = "cadenza-codex-openrouter" model_provider = "cadenza" model_catalog_json = "{catalogPath}" [model_providers.cadenza] name = "Cadenza.Agent (OpenRouter-backed)" base_url = "http://localhost:8080/v1" wire_api = "responses" env_key = "CADENZA_API_KEY" stream_idle_timeout_ms = 300000 """; WriteText(Path.Combine(codexHome, "config.toml"), configToml); // Catalog JSON: Codex に提供するモデル id を宣言して "Defaulting to // fallback metadata" 警告を防ぎます。フィールドは codex-rs/protocol/src/ // openai_models.rs の ModelInfo スキーマ基準 — すべてのキーが必須です。 var catalogJson = """ { "models": [{ "slug": "cadenza-codex-openrouter", "display_name": "Cadenza (OpenRouter)", "description": "OpenRouter-backed agent served by Cadenza.Agent", "supported_reasoning_levels": [], "shell_type": "default", "visibility": "list", "supported_in_api": true, "priority": 50, "availability_nux": null, "upgrade": null, "base_instructions": "", "supports_reasoning_summaries": false, "support_verbosity": false, "default_verbosity": null, "apply_patch_tool_type": "freeform", "truncation_policy": { "mode": "tokens", "limit": 8192 }, "supports_parallel_tool_calls": true, "context_window": 200000, "max_context_window": 200000, "auto_compact_token_limit": 180000, "effective_context_window_percent": 95, "experimental_supported_tools": [] }] } """; WriteText(Path.Combine(codexHome, "cadenza-catalog.json"), catalogJson); WriteLine($"Codex config generated at: {codexHome}"); WriteLine("In another terminal, run:"); WriteLine($" $env:CODEX_HOME = \"{codexHome}\""); WriteLine($" $env:CADENZA_API_KEY = \"any-non-empty-string\""); WriteLine($" codex"); // OpenRouter を LLM バックエンドとして接続。 var openAiOptions = new OpenAIClientOptions { Endpoint = new Uri("https://openrouter.ai/api/v1") }; var chatClient = new OpenAI.Chat.ChatClient(model, new ApiKeyCredential(apiKey), openAiOptions) .AsIChatClient(); UseChatClient(chatClient); await Run(); これがすべてです。プロジェクトファイルも、.csprojも、Program.csもありません。一番上の#:sdkディレクティブが.NET 10のfile-basedプログラムシステムに「このスクリプトはCadenza.Agent SDKを使う」と知らせ、SDKがHTTPサーバー、Responses wireフォーマット、すべてのパッケージ参照を引き込みつつ、Tool、UseOllama、UseChatClient、Runを名前空間なしですぐ呼べる名前として公開します。 ...