Codex Shim BYOK Models
Skill by ara.so — Codex Skills collection.
Overview
codex-shim is a local Python API shim that intercepts Codex Desktop's model requests and routes them to:
- Any model in your
~/.factory/settings.json(Factory BYOK) - Your ChatGPT subscription GPT-5.5
- Custom OpenAI/Anthropic/generic chat completion endpoints
It exposes a local Responses API endpoint that Codex Desktop points to, bypassing Codex's server-side Statsig model allowlist.
Key capabilities:
- Use any OpenAI, Anthropic, DeepSeek, Gemini, OpenRouter, or Z.ai model
- Keep ChatGPT subscription GPT-5.5 alongside BYOK models
- No modification to
~/.codex/config.toml(uses launch-time overrides) - Translates between Codex Responses API ↔ upstream APIs (OpenAI chat completions, Anthropic messages)
Installation
# Clone repository
git clone https://github.com/0xSero/codex-shim ~/Documents/codex-shim
cd ~/Documents/codex-shim
# Install Python dependencies (requires 3.11+)
python3 -m pip install --user aiohttp pytest
# Symlink commands to PATH
ln -s "$PWD/bin/codex-shim" ~/.local/bin/codex-shim
ln -s "$PWD/bin/codex-app" ~/.local/bin/codex-app
ln -s "$PWD/bin/codex-model" ~/.local/bin/codex-model
Verify installation:
codex-shim --help
Configuration
Factory Settings File
The shim reads ~/.factory/settings.json by default. Structure:
{
"customModels": [
{
"model": "gpt-5.5",
"provider": "openai",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "${OPENAI_API_KEY}",
"displayName": "OpenAI GPT-5.5",
"maxContextLimit": 400000
},
{
"model": "claude-opus-4-7-20251109",
"provider": "anthropic",
"baseUrl": "https://api.anthropic.com/v1",
"apiKey": "${ANTHROPIC_API_KEY}",
"displayName": "Claude Opus 4.7"
},
{
"model": "deepseek-v4-pro",
"provider": "anthropic",
"baseUrl": "https://api.deepseek.com/anthropic",
"apiKey": "${DEEPSEEK_API_KEY}",
"displayName": "DeepSeek V4 Pro",
"noImageSupport": true
}
]
}
Supported providers:
openai→ OpenAI/v1/chat/completionsgeneric-chat-completion-api→ OpenAI-compatible endpointsanthropic→ Anthropic/v1/messages
Custom Config Path
codex-shim --settings /path/to/custom-models.json generate
codex-shim --settings /path/to/custom-models.json start
Environment Variables
Store API keys in environment:
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export DEEPSEEK_API_KEY="..."
Key Commands
Generate Catalog
Reads Factory settings and creates .codex-shim/custom_model_catalog.json:
codex-shim generate
Start/Stop Daemon
# Start shim on 127.0.0.1:8765
codex-shim start
# Check status
codex-shim status
# Stop daemon
codex-shim stop
# Restart
codex-shim restart
List Available Models
# Show all generated slugs and upstream routes
codex-shim list
# Show models currently in Codex Desktop picker
codex-model list
Launch Codex Desktop
# Launch with shim wired in (doesn't modify ~/.codex/config.toml)
codex-app
# Launch and open specific path
codex-app /path/to/project
# Equivalent long form
codex-shim app /path/to/project
Switch Active Model
# List available slugs
codex-model list
# Set default model for next launch
codex-model openai-gpt-5-5
# Relaunch Codex
codex-app
Run Codex CLI Through Shim
codex-shim codex -- chat "explain this code"
codex-shim codex -- edit main.py "add error handling"
Model Routing Architecture
Codex Desktop → /v1/responses → codex-shim (127.0.0.1:8765)
┃
┏━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━┓
┃ ┃ ┃
slug "openai-gpt-5-5" provider provider
┃ "openai" "anthropic"
┃ ┃ ┃
chatgpt.com/backend-api baseUrl/ baseUrl/
/codex/responses chat/ messages
(Bearer token) completions (x-api-key)
The shim:
- Receives Codex Responses API request
- Looks up slug in catalog
- Translates to upstream format (OpenAI chat completions or Anthropic messages)
- Streams upstream response
- Translates back to Responses API format
Code Examples
Python: Implementing Custom Provider Translation
# codex_shim/translator.py example pattern
async def translate_to_openai(responses_request):
"""Convert Codex Responses API → OpenAI chat completions."""
return {
"model": responses_request["model"],
"messages": responses_request["messages"],
"stream": True,
"temperature": responses_request.get("temperature", 1.0),
"max_tokens": responses_request.get("max_tokens"),
}
async def translate_to_anthropic(responses_request):
"""Convert Codex Responses API → Anthropic messages."""
messages = []
system = None
for msg in responses_request["messages"]:
if msg["role"] == "system":
system = msg["content"]
else:
messages.append({
"role": msg["role"],
"content": msg["content"]
})
body = {
"model": responses_request["model"],
"messages": messages,
"stream": True,
"max_tokens": responses_request.get("max_tokens", 4096),
}
if system:
body["system"] = system
return body
Python: Adding Custom Model Programmatically
import json
from pathlib import Path
def add_custom_model(model_config):
"""Add model to Factory settings."""
settings_path = Path.home() / ".factory" / "settings.json"
if settings_path.exists():
with open(settings_path) as f:
settings = json.load(f)
else:
settings = {"customModels": []}
settings["customModels"].append(model_config)
with open(settings_path, "w") as f:
json.dump(settings, f, indent=2)
# Example: Add OpenRouter model
add_custom_model({
"model": "anthropic/claude-3.5-sonnet",
"provider": "openai",
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "${OPENROUTER_API_KEY}",
"displayName": "Claude 3.5 Sonnet (OpenRouter)",
"maxContextLimit": 200000
})
Shell: Automated Setup Script
#!/bin/bash
# setup-codex-shim.sh
set -e
CODEX_SHIM_DIR="$HOME/Documents/codex-shim"
# Clone and install
if [ ! -d "$CODEX_SHIM_DIR" ]; then
git clone https://github.com/0xSero/codex-shim "$CODEX_SHIM_DIR"
fi
cd "$CODEX_SHIM_DIR"
python3 -m pip install --user aiohttp
# Symlink commands
mkdir -p "$HOME/.local/bin"
ln -sf "$CODEX_SHIM_DIR/bin/codex-shim" "$HOME/.local/bin/"
ln -sf "$CODEX_SHIM_DIR/bin/codex-app" "$HOME/.local/bin/"
ln -sf "$CODEX_SHIM_DIR/bin/codex-model" "$HOME/.local/bin/"
# Generate catalog and start
codex-shim generate
codex-shim start
echo "✓ Shim installed and running"
codex-shim status
macOS Picker Patch
Codex Desktop's Statsig config hides models not on a server-side allowlist. Apply this one-time ASAR patch to bypass:
APP=/Applications/Codex.app
# Backup
sudo cp -R "$APP" "$APP.unpatched-$(date +%Y%m%d-%H%M%S)"
# Extract ASAR
cd /tmp && rm -rf codex-asar-patch && mkdir codex-asar-patch && cd codex-asar-patch
npx --yes @electron/asar extract "$APP/Contents/Resources/app.asar" extracted
# Patch picker filter (disables useHiddenModels check)
PATCH_FILE=$(grep -RIl 'useHiddenModels' extracted/webview/assets/model-queries-*.js | head -n1)
sed -i.bak -E 's/let u=c\.useHiddenModels&&o!==`amazonBedrock`,d;/let u=!1,d;/' "$PATCH_FILE"
# Verify exactly one change
diff "$PATCH_FILE.bak" "$PATCH_FILE" && echo "ERROR: No changes made" && exit 1
rm "$PATCH_FILE.bak"
# Repack
npx --yes @electron/asar pack extracted app.asar.new
sudo cp app.asar.new "$APP/Contents/Resources/app.asar"
# Recompute ASAR header hash for Electron integrity check
HEADER_HASH=$(python3 - "$APP/Contents/Resources/app.asar" <<'PY'
import struct, hashlib, sys
with open(sys.argv[1], 'rb') as f:
data_size, header_size, _, json_size = struct.unpack('<4I', f.read(16))
header_json = f.read(json_size)
print(hashlib.sha256(header_json).hexdigest())
PY
)
# Update Info.plist
sudo /usr/libexec/PlistBuddy -c \
"Set :ElectronAsarIntegrity:Resources/app.asar:hash $HEADER_HASH" \
"$APP/Contents/Info.plist"
# Re-sign (ad-hoc)
sudo codesign --force --deep --sign - "$APP"
echo "✓ Patch applied. Launch Codex Desktop."
Rollback:
sudo rm -rf "$APP"
sudo mv "$APP.unpatched-YYYYMMDD-HHMMSS" "$APP"
ChatGPT GPT-5.5 Passthrough
If ~/.codex/auth.json exists with auth_mode: chatgpt, the shim auto-generates a synthetic slug openai-gpt-5-5 that proxies to:
https://chatgpt.com/backend-api/codex/responses
Authorization: Bearer <access_token from auth.json>
This bypasses Factory and uses your ChatGPT subscription quota.
Disable:
# Remove from catalog after generation
jq 'del(.models[] | select(.slug == "openai-gpt-5-5"))' \
.codex-shim/custom_model_catalog.json > tmp.json && mv tmp.json .codex-shim/custom_model_catalog.json
Common Patterns
Multi-Provider Setup
{
"customModels": [
{
"model": "gpt-5.5",
"provider": "openai",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "${OPENAI_API_KEY}",
"displayName": "GPT-5.5"
},
{
"model": "claude-opus-4-7-20251109",
"provider": "anthropic",
"baseUrl": "https://api.anthropic.com/v1",
"apiKey": "${ANTHROPIC_API_KEY}",
"displayName": "Claude Opus 4.7"
},
{
"model": "gemini-2.0-flash-exp",
"provider": "openai",
"baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai",
"apiKey": "${GOOGLE_API_KEY}",
"displayName": "Gemini 2.0 Flash"
}
]
}
OpenRouter Aggregation
{
"model": "meta-llama/llama-3.3-70b-instruct",
"provider": "openai",
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "${OPENROUTER_API_KEY}",
"displayName": "Llama 3.3 70B (OpenRouter)"
}
DeepSeek with Anthropic-Style Thinking
{
"model": "deepseek-v4-pro",
"provider": "anthropic",
"baseUrl": "https://api.deepseek.com/anthropic",
"apiKey": "${DEEPSEEK_API_KEY}",
"displayName": "DeepSeek V4 Pro",
"noImageSupport": true
}
The shim translates extended thinking blocks from thinking items to reasoning.encrypted_content.
Troubleshooting
Models Don't Appear in Picker
Cause: Statsig allowlist still active.
Solution: Apply macOS picker patch (see section above).
"Connection refused" When Launching Codex
Cause: Shim daemon not running.
codex-shim status # Check if running
codex-shim start # Start if stopped
401 Unauthorized Errors
Cause: API key not found or invalid.
Check environment variables:
echo $OPENAI_API_KEY
echo $ANTHROPIC_API_KEY
Verify settings file:
cat ~/.factory/settings.json | jq '.customModels[].apiKey'
Test upstream directly:
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer $OPENAI_API_KEY"
ChatGPT Passthrough Not Working
Verify auth.json exists:
cat ~/.codex/auth.json | jq '.auth_mode'
# Should output: "chatgpt"
Check access token validity:
TOKEN=$(jq -r '.access_token' ~/.codex/auth.json)
curl https://chatgpt.com/backend-api/me \
-H "Authorization: Bearer $TOKEN"
Shim Logs
# Tail daemon logs
tail -f ~/.codex-shim/codex-shim.log
# Check last 50 lines
tail -n 50 ~/.codex-shim/codex-shim.log
Port Already in Use
# Change default port (8765)
codex-shim --port 9876 start
codex-shim --port 9876 app
Extended Thinking Not Appearing
For Anthropic-shaped providers (Claude, DeepSeek), thinking blocks appear as reasoning.encrypted_content items in Codex UI.
Verify provider is set to anthropic:
jq '.customModels[] | select(.model=="deepseek-v4-pro") | .provider' \
~/.factory/settings.json
Should return "anthropic", not "openai".
Testing
Run test suite:
cd ~/Documents/codex-shim
python3 -m pytest tests/ -v
Test specific translation:
# tests/test_translation.py
import pytest
from codex_shim.translator import translate_to_anthropic
@pytest.mark.asyncio
async def test_system_message_extraction():
request = {
"model": "claude-opus-4-7",
"messages": [
{"role": "system", "content": "You are helpful"},
{"role": "user", "content": "Hello"}
]
}
result = await translate_to_anthropic(request)
assert result["system"] == "You are helpful"
assert len(result["messages"]) == 1
MCP Tool Forwarding
Codex Desktop forwards three generic MCP tools to all models (built-in and shim-routed):
list_mcp_resourceslist_mcp_resource_templatesread_mcp_resource
These are available in the function calling schema for every routed model. The model calls list_mcp_resources to discover available resources.
Note: Codex Desktop does not flatten individual MCP server tools. That's a Codex client behavior, not a shim limitation.
Official Repository: https://github.com/0xSero/codex-shim
License: MIT
Requires: Python 3.11+, aiohttp