codex-app-transfer-proxy

v2026.09.25

Local Rust proxy that translates OpenAI Codex CLI Responses API into Chat Completions for Kimi, DeepSeek, GLM, and other OpenAI-compatible providers

GitHub
Install command
npx skhub add reason-machines/codex-app-transfer-proxy
Markdown
SKILL.md

Codex App Transfer Proxy

Skill by ara.so — Codex Skills collection.

Codex App Transfer is a local desktop gateway written in Rust that proxies OpenAI Codex CLI/Desktop requests. It translates the Responses API protocol (/responses) into Chat Completions format, enabling Codex to work with third-party providers like DeepSeek, Kimi, Xiaomi MiMo, Zhipu GLM, Alibaba Bailian, MiniMax, Gemini, Claude, and Grok.

The tool runs as a Tauri 2.x desktop app with a local HTTP server (default 127.0.0.1:18080) that intercepts Codex requests, performs protocol translation (including multi-turn context, previous_response_id history replay, tool calls, and apply_patch diffs), and forwards to upstream providers.

Installation

From Releases (Recommended)

Download the latest binary for your platform from GitHub Releases:

  • Windows: Codex-App-Transfer-v<version>-Windows-x64-Setup.exe (NSIS installer)
  • macOS (Apple Silicon): Codex-App-Transfer-v<version>-macOS-arm64.dmg
  • macOS (Intel): Codex-App-Transfer-v<version>-macOS-x64.dmg
  • Linux (Debian/Ubuntu): Codex-App-Transfer-v<version>-Linux-x86_64.deb
  • Linux (AppImage): Codex-App-Transfer-v<version>-Linux-x86_64.AppImage

Build from Source

git clone https://github.com/Cmochance/codex-app-transfer.git
cd codex-app-transfer
cargo tauri build --bundles app,dmg  # macOS
cargo tauri build --bundles nsis     # Windows
cargo tauri build --bundles deb,appimage  # Linux

Development Mode

cargo tauri dev  # Hot-reload desktop app
cargo test --workspace --lib  # Run unit tests

Configuration

Adding a Provider

  1. Launch Codex App Transfer desktop app
  2. Click + in the dashboard top-right corner
  3. Select a preset (DeepSeek, Kimi, MiMo, etc.) or choose Custom
  4. Fill in:
    • API Base URL: e.g., https://api.deepseek.com/v1
    • API Key: $DEEPSEEK_API_KEY (stored encrypted)
    • API Format: chat_completions (or gemini_native, anthropic_messages, etc.)
  5. Click Fetch Models to populate available models
  6. Map OpenAI model slots to real models:
    • gpt-5.5 → deepseek-v4-pro
    • gpt-5.4 → deepseek-v4-mini
    • gpt-5.3-codex → kimi-k2.6
  7. Click Apply at the bottom of the page
  8. Click ↻ Restart Codex (top-right) to reload Codex Desktop/CLI

Provider Configuration Examples

DeepSeek V4

[[providers]]
name = "DeepSeek V4"
api_base = "https://api.deepseek.com/v1"
api_key = "${DEEPSEEK_API_KEY}"
api_format = "chat_completions"
enabled = true
default_model = "deepseek-v4-pro"

[providers.models]
"gpt-5.5" = "deepseek-v4-pro"
"gpt-5.4" = "deepseek-v4-mini"

Kimi For Coding

[[providers]]
name = "Kimi Code"
api_base = "https://api.moonshot.cn/v1"
api_key = "${KIMI_API_KEY}"
api_format = "chat_completions"
enabled = true
default_model = "kimi-k2.6"

[providers.models]
"gpt-5.5" = "kimi-k2.6"
"gpt-5.4" = "kimi-k1.5"

Gemini Native (Google AI Studio)

[[providers]]
name = "Gemini"
api_base = "https://generativelanguage.googleapis.com"
api_key = "${GOOGLE_AI_STUDIO_KEY}"
api_format = "gemini_native"
enabled = true
default_model = "gemini-3-pro"

[providers.models]
"gpt-5.5" = "gemini-3-pro"
"gpt-5.4" = "gemini-2.0-flash"

Anthropic Messages (Claude)

[[providers]]
name = "Claude"
api_base = "https://api.anthropic.com"
api_key = "${ANTHROPIC_API_KEY}"
api_format = "anthropic_messages"
enabled = true
default_model = "claude-3-7-sonnet-20250219"

[providers.models]
"gpt-5.5" = "claude-3-7-sonnet-20250219"

Configuration File Location

  • macOS/Linux: ~/.codex-app-transfer/config.toml
  • Windows: %USERPROFILE%\.codex-app-transfer\config.toml

Config is auto-saved when you click Apply in the UI. Manual edits require app restart.

Session Persistence

Multi-turn history is stored in two layers:

  • L1 (Memory): LRU cache for active sessions
  • L2 (SQLite): ~/.codex-app-transfer/sessions.db (30-day TTL)

Sessions survive app restarts. To clear:

rm ~/.codex-app-transfer/sessions.db

Using with Codex CLI/Desktop

Starting the Proxy

  1. Launch Codex App Transfer
  2. Enable at least one provider (toggle switch on provider card)
  3. Click Apply
  4. Proxy server starts on http://127.0.0.1:18080

Codex App Transfer auto-patches ~/.codex/config.toml and ~/.codex/auth.json to route through the local proxy. Original config is snapshotted before modification.

Verifying Connection

In Codex Desktop, the model picker should show:

DeepSeek V4 / deepseek-v4-pro
Kimi Code / kimi-k2.6

If you see OpenAI model names only (gpt-5.5, gpt-5.4), the proxy is not active — click ↻ Restart Codex.

Example Codex Interaction

# In Codex CLI
codex --model gpt-5.5 "Explain this function"
# Routed to deepseek-v4-pro via local proxy

Codex Desktop chat will show tool calls (e.g., read_file, apply_patch) properly round-tripped through the proxy's Responses ↔ Chat Completions adapter.

Protocol Translation Details

Responses API → Chat Completions

Codex sends:

POST /responses HTTP/1.1
Content-Type: application/json

{
  "model": "gpt-5.5",
  "messages": [...],
  "stream": true,
  "thinking": {"type": "enabled", "budget": "high"}
}

Proxy translates to:

POST https://api.deepseek.com/v1/chat/completions
Content-Type: application/json
Authorization: Bearer ${DEEPSEEK_API_KEY}

{
  "model": "deepseek-v4-pro",
  "messages": [...],
  "stream": true,
  "reasoning_effort": "high"
}

Upstream response is translated back to Responses format with:

  • response_id rewritten from chatcmpl-... → resp_<base64>
  • Tool calls bridged: function_call ↔ custom_tool_call
  • Thinking content injected as reasoning_content for Codex UI
  • previous_response_id maintained across turns for history replay

Tool Call Handling (apply_patch)

Codex's apply_patch tool (file edit diff UI) works on chat-completions providers via bidirectional adapter:

Codex → Upstream (Chat)

{
  "type": "custom_tool_call",
  "name": "apply_patch",
  "arguments": "{\"file_path\":\"main.rs\",\"patch\":\"...\"}"
}

becomes:

{
  "type": "function",
  "function": {
    "name": "apply_patch",
    "arguments": "{\"file_path\":\"main.rs\",\"patch\":\"...\"}"
  }
}

Upstream → Codex (Responses)

{
  "tool_calls": [{
    "type": "function",
    "function": {"name": "apply_patch", "arguments": "..."}
  }]
}

becomes:

{
  "custom_tool_calls": [{
    "type": "custom_tool_call",
    "name": "apply_patch",
    "arguments": "..."
  }]
}

Patch format follows V4A standard:

*** Begin Patch
@@ main.rs 10-15
- old line 1
- old line 2
+ new line 1
+ new line 2
*** End Patch

Thinking Mode (reasoning_effort)

Codex thinking budget (low / medium / high / xhigh) maps to provider-specific fields:

Providerxhigh/maxOther levels
DeepSeek V4reasoning_effort: "max""high"
Kimi/GLM/MiMo(omit field)(omit field)
Custom Chatclamp to "high"pass through

Reasoning content (e.g., DeepSeek's <think> blocks) is extracted and injected as Codex reasoning_content field.

Codex Desktop Theme Injection (Optional)

Codex App Transfer can inject custom CSS + background images into Codex Desktop (Electron) via Chrome DevTools Protocol:

  1. Navigate to Theme tab
  2. Enable Theme Injection toggle
  3. Select a preset (Changli, Azur Lane, Nailin, Zani, Carton) or Add Custom
  4. For custom: upload JPG/PNG → crop in 1:1 modal → Apply
  5. Theme auto-applies on next Codex Desktop launch

Custom Background Upload

// Internal: Theme::Custom stores base64-encoded image + crop rect
#[derive(Serialize, Deserialize)]
pub struct CustomTheme {
    pub image_data: String,  // base64 JPEG/PNG
    pub crop_rect: CropRect, // {x, y, width, height}
}

Injected CSS example:

body::before {
  content: '';
  position: fixed;
  inset: 0;
  background: url(data:image/jpeg;base64,...) center/cover no-repeat;
  opacity: 0.3;
  z-index: -1;
}
.chat-panel {
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(10px);
}

Turn off Theme Injection toggle to restore native Codex UI.

Codex Document Management

Sidebar → Codex tab provides editors for Codex's AI-readable index files:

Agents (AGENTS.md)

  • Reads/writes AGENTS.md in any directory (auto-detects nearest .git/ for project-root vs. subdir classification)
  • UI: textarea editor + file picker
  • Save triggers Codex reload if running
# Agents

## Project Agent
You are a Rust backend engineer working on codex-app-transfer...

## Frontend Agent
You specialize in Tauri + Vue.js...

Memories

  • Main index: ~/.codex/memories/MEMORY.md
  • Summary: ~/.codex/memories/memory_summary.md
  • UI: two-tab editor for both files
# Memory

## Coding Preferences
- Always use `anyhow::Result` for error handling
- Prefer `tracing` over `log`

## Project Context
- DeepSeek V4 requires `reasoning_effort: "max"` for xhigh budget

Skills

  • Scans ~/.codex/skills/<skill-name>/SKILL.md
  • UI: list of skills with raw markdown editor per skill
  • Open Folder button launches system file manager for multi-file skills (scripts, examples, templates)
---
name: rust-async-patterns
description: Common async/await patterns in Tokio
triggers:
  - write async rust code
  - use tokio runtime
---

# Rust Async Patterns

Use `tokio::spawn` for concurrent tasks:
...

MCP (Model Context Protocol)

  • Edits ~/.codex/config.toml [mcp_servers.*] sections (preserves comments via toml_edit)
  • Plugins subtab: lists installed plugins from ~/.codex/plugins/cache/ with enable/disable toggle and uninstall button
[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"]

All document edits are atomic writes with independent version history (SHA-256 hash-based paths, no cross-file collision).

API Reference (Internal Rust Modules)

src-tauri/src/proxy/protocol_adapter.rs

Key Function: adapt_chat_to_responses

pub async fn adapt_chat_to_responses(
    chat_stream: impl Stream<Item = Result<ChatCompletionChunk>>,
    original_request: &ResponsesRequest,
    provider_config: &ProviderConfig,
) -> Result<impl Stream<Item = Result<ResponsesStreamChunk>>> {
    // Translates chat SSE chunks to Responses format
    // - Rewrites response_id
    // - Bridges tool_calls
    // - Injects reasoning_content from <think> blocks
}

Key Function: adapt_responses_to_chat

pub fn adapt_responses_to_chat(
    req: &ResponsesRequest,
    provider: &ProviderConfig,
) -> Result<ChatCompletionRequest> {
    // Maps Responses messages to chat messages
    // - Flattens custom_tool_calls → function calls
    // - Injects system prompts for apply_patch rules (language-aware)
    // - Maps thinking.budget → reasoning_effort
}

src-tauri/src/session/manager.rs

Session Lookup

pub async fn get_or_create_session(
    &self,
    response_id: Option<&str>,
    request: &ResponsesRequest,
) -> Result<Arc<Session>> {
    // L1: check memory cache
    // L2: query sqlite sessions.db
    // Create new if not found
}

History Replay

pub async fn replay_history(
    &self,
    previous_response_id: &str,
) -> Result<Vec<Message>> {
    // Fetches full conversation history for previous_response_id
    // Used in autocompact + multi-turn tool loops
}

src-tauri/src/config/mod.rs

Load/Save Config

pub fn load_config() -> Result<AppConfig> {
    let path = get_config_path()?; // ~/.codex-app-transfer/config.toml
    let content = fs::read_to_string(&path)?;
    toml::from_str(&content)
}

pub fn save_config(config: &AppConfig) -> Result<()> {
    let path = get_config_path()?;
    let toml_str = toml::to_string_pretty(config)?;
    fs::write(&path, toml_str)?;
    Ok(())
}

Provider Schema

#[derive(Serialize, Deserialize, Clone)]
pub struct ProviderConfig {
    pub name: String,
    pub api_base: String,
    pub api_key: String,
    pub api_format: ApiFormat, // chat_completions | gemini_native | anthropic_messages | responses_passthrough
    pub enabled: bool,
    pub default_model: String,
    pub models: HashMap<String, String>, // "gpt-5.5" -> "deepseek-v4-pro"
    pub request_options: Option<serde_json::Value>, // Provider-specific overrides
}

Common Patterns

Adding a New Provider Preset

Edit src-tauri/src/config/presets.rs:

pub fn get_preset(name: &str) -> Option<ProviderConfig> {
    match name {
        "deepseek" => Some(ProviderConfig {
            name: "DeepSeek V4".into(),
            api_base: "https://api.deepseek.com/v1".into(),
            api_format: ApiFormat::ChatCompletions,
            models: HashMap::from([
                ("gpt-5.5".into(), "deepseek-v4-pro".into()),
                ("gpt-5.4".into(), "deepseek-v4-mini".into()),
            ]),
            ..Default::default()
        }),
        "my-provider" => Some(ProviderConfig {
            name: "My Provider".into(),
            api_base: "https://api.example.com/v1".into(),
            api_format: ApiFormat::ChatCompletions,
            models: HashMap::from([
                ("gpt-5.5".into(), "my-model-large".into()),
            ]),
            ..Default::default()
        }),
        _ => None,
    }
}

Rebuild UI to expose preset in dropdown.

Injecting Custom System Prompts

Prompts follow UI language setting (zh-CN or en). Edit src-tauri/src/proxy/protocol_adapter.rs:

fn build_apply_patch_system_prompt(lang: &str) -> String {
    match lang {
        "zh-CN" => "你必须使用 apply_patch 工具...",
        _ => "You must use the apply_patch tool...",
    }
}

Prompts are injected only for non-OpenAI providers when apply_patch tool is available.

Custom Tool Repair (Grok Web Example)

Grok Web flattens nested tool_calls. Add repair in src-tauri/src/proxy/grok.rs:

fn repair_tool_calls(chunk: &mut ChatCompletionChunk) {
    if let Some(choice) = chunk.choices.first_mut() {
        if let Some(tools) = &choice.delta.tool_calls {
            // Grok sends [{function: {name, arguments}}, ...] at top level
            // Wrap into proper tool_calls structure
            choice.delta.tool_calls = Some(tools.iter().map(|t| ToolCall {
                id: format!("call_{}", uuid::Uuid::new_v4()),
                r#type: "function".into(),
                function: t.function.clone(),
            }).collect());
        }
    }
}

Called in adapt_chat_to_responses before standard bridging.

Troubleshooting

Codex still uses OpenAI models after enabling provider

Cause: Codex config not reloaded.

Fix: Click ↻ Restart Codex button in Codex App Transfer UI (top-right corner).

Provider shows "API key invalid" in logs

Cause: API key env var not resolved or incorrect.

Fix:

# Verify env var is set
echo $DEEPSEEK_API_KEY

# Restart Codex App Transfer (env vars loaded on startup)

Or paste literal key in UI (not recommended for security).

apply_patch diffs not rendering in Codex

Cause: Provider not bridging function_call ↔ custom_tool_call correctly.

Fix: Check logs for tool_call_repair warnings. Ensure provider config has api_format: "chat_completions" (not responses_passthrough).

Debug:

# Enable trace logging
export RUST_LOG=codex_app_transfer=trace
# Check logs tab in UI for tool call JSON

DeepSeek thinking mode not working

Cause: reasoning_effort not sent or wrong value.

Fix: For xhigh budget, ensure adapter maps to "max":

// In protocol_adapter.rs
let reasoning_effort = match thinking_budget {
    "xhigh" => "max",
    _ => "high",
};

Verify in logs:

[proxy] Sending to DeepSeek: {"reasoning_effort":"max",...}

Session history not persisting

Cause: SQLite DB locked or corrupt.

Fix:

# Stop Codex App Transfer
# Remove DB (will recreate on next start)
rm ~/.codex-app-transfer/sessions.db

Codex Desktop theme not applying

Cause: CDP connection failed or Codex not running.

Fix:

  1. Ensure Codex Desktop is running
  2. Check Theme tab shows "Connected" status
  3. Toggle Theme Injection off then on
  4. Restart Codex Desktop

Logs:

[theme] CDP connect failed: ConnectionRefused

Means Codex Desktop debug port (9222) not accessible — check firewall.

Multiple instances conflict

Symptom: Config changes not saving, "file lock" errors.

Cause: Two Codex App Transfer processes running.

Fix: Quit both instances (right-click system tray → Exit), relaunch once. App uses single-instance lock (second launch auto-focuses existing window).

Provider fetch models returns 401

Cause: API base URL wrong or key expired.

Fix:

# Test API manually
curl https://api.deepseek.com/v1/models \
  -H "Authorization: Bearer $DEEPSEEK_API_KEY"

# If 401, regenerate key from provider dashboard

Update key in UI, click Apply.


For more details: See GitHub Issues or Changelog.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

NOASSERTION

Source path

skills/codex-app-transfer-proxy

Default branch

main

Latest commit

901a4e3

Tree SHA

faa2fa4