OpenAI’s Codex CLI ships with a great editor-agent UX: shell tool, apply_patch , plan tracking, the lot. The catch — as of February 2026 it only speaks the OpenAI Responses API . Chat Completion support was dropped ( codex-rs/model-provider-info/src/lib.rs : the WireApi enum has one variant, Responses ). If you wanted to point it at a Chat-Completion-only endpoint — Ollama, LM Studio, your favorite Llama runner — you’re out of luck. But Codex CLI is happy to talk to any server that speaks Responses. It has a model_provider config block exactly for that. So if you can stand up a Responses-shaped HTTP endpoint backed by the model of your choice, Codex becomes a generic front-end and you choose the brain. Here’s the trick I’ve been using: a 50-line C# script that runs as both an OpenAI Chat Completion server and a Responses API server, on top of Microsoft.Extensions.AI ‘s vendor-neutral IChatClient abstraction. I then point it at OpenRouter — one API key, hundreds of models including Claude, Gemini, Llama, GPT, you name it — and tell Codex to talk to my local script instead of OpenAI. End result: OpenAI Codex CLI running on Anthropic’s Claude 3.5 Sonnet (or whichever model I’m feeling like that day). The pieces I’m using Cadenza.Agent , an MSBuild SDK I ship that turns a single .cs file into a runnable agent server. It’s part of a small family of single-file scripting SDKs for .NET 10’s file-based programs — same idea as dotnet run script.cs but with a richer Tier-1 API ( Tool , UseOllama , UseOpenAi , Run , etc.). The Agent variant exposes: POST /v1/chat/completions — for Aider / Continue / Cursor / Copilot BYOK / sgpt POST /v1/responses — for Codex CLI Both are backed by the same IChatClient you configure. Switch the backend and the wire-format stays. For the LLM I’m using OpenRouter , which speaks OpenAI’s Chat Completion wire format with a different base URL — perfect for Microsoft.Extensions.AI.OpenAI ‘s drop-in ChatClient . One env var, any model. For Codex’s configuration I’m using its CODEX_HOME environment variable trick: instead of editing ~/.codex/config.toml , you point Codex at a sample-local directory and it loads a fresh config.toml from there. Means I can ship a self-contained sample that never touches the user’s global config. The script The entire backend, in one file: # !/ 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” ; // Generate a sample-local Codex home directory. 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: declares the served model id to Codex so it stops printing // “Defaulting to fallback metadata”. Fields match codex-rs/protocol/src/ // openai_models.rs ModelInfo schema — every key is required. 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” ); // Wire up OpenRouter as the LLM backend. 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 (); That’s it. No project file, no .csproj , no Program.cs . The #:sdk directive at the top tells the .NET 10 file-based program system to use Cadenza.Agent as the SDK, which pulls in the HTTP server, the Responses wire format, all the package references — and exposes Tool , UseOllama , UseChatClient , Run as bare names you can call directly. Running it Save the script as agent-codex-openrouter.cs and: # Terminal 1 — start the agent server $env:OPENROUTER_API_KEY = “sk-or-v1-…” $env:OPENROUTER_MODEL = “anthropic/claude-3.5-sonnet” # or any OpenRouter slug dotnet run agent-codex-openrouter.cs The first run pulls dependencies — Microsoft.Extensions.AI , the OpenAI SDK, ASP.NET Core. After that it boots in well under a second. The script prints exactly what you need in the second terminal: Codex config generated at: D:\work.cadenza-codex-openrouter
In another terminal, run:
$env:CODEX_HOME = “D:\work.cadenza-codex-openrouter”
$env:CADENZA_API_KEY = “any-non-empty-string”
codex Paste those into another terminal, run codex , and you’re chatting with Claude 3.5 Sonnet (or whichever OpenRouter model you picked) through the Codex UX. Tools like shell and apply_patch are sent by Codex itself in every request; the agent forwards them to the model and streams the model’s function_call outputs back so Codex executes them locally. What’s happening behind the scenes When Codex sends POST /v1/responses , the agent does this: Parse the Responses input . Codex sends a message / function_call / function_call_output array; we flatten it into Microsoft.Extensions.AI ‘s IList
Edit my-codex-backend.cs to use the OpenRouter pattern above
$env:OPENROUTER_API_KEY = “sk-or-v1-…” dotnet run my-codex-backend.cs Or grab the ready-to-run sample from the Cadenza repository — agent-codex-openrouter.cs is the version above. The repo also has agent-codex-backend.cs (Ollama variant) and agent-openrouter.cs (Chat Completion variant for Aider / Continue / Cursor). If this is useful, let me know what backend you wire up. I’m curious whether anyone gets Codex running on a fine-tuned local model with a local fallback for offline coding — that’s the next experiment on my list. Cadenza is MIT-licensed. Source: https://github.com/rkttu/cadenza . The Cadenza.Agent package ships at 1.0.14 as of writing. Cover Image Credit: Lukas from Unsplash