《Windows + Clash 环境下 Codex CLI 接入 DeepSeek V4 Pro 完整踩坑记录》

Windows + Clash 环境下 Codex CLI 接入 DeepSeek V4 Pro 完整踩坑实录

适用版本 :Codex CLI v0.120+(Rust 重写版)· DeepSeek V4 Pro · Windows 10/11 · Clash for Windows / Clash Verge

最后更新 :2026-06

作者背景:在 Windows 主力机 + Clash 翻墙的真实环境下把这套链路跑通,踩了所有能踩的坑,记录在此。


为什么需要这篇文章

Codex CLI 和 DeepSeek 各自都很香,但两者不能直连------这不是配置问题,是协议层根本不兼容:

objectivec 复制代码
Codex CLI  →  OpenAI Responses API(私有协议)
DeepSeek   →  Chat Completions API(标准 OpenAI 格式)

中间必须有一个翻译层(bridge/proxy)。

网上现有教程大多是 Mac/Linux 视角,跳过了 Windows 特有的代理冲突,并且对 SSE 流式中断、OpenRouter 兼容性这些深层问题几乎没有解释。这篇文章是我在 Windows + Clash 环境下把完整链路跑通的第一手记录。


整体架构

bash 复制代码
Codex CLI
    │  Responses API(POST /v1/responses)
    ▼
codex-bridge(本地 Node.js,端口 4000)
    │  Chat Completions API(翻译后)
    ▼
DeepSeek API(api.deepseek.com)
    │  走系统代理 / 直连(取决于你的 Clash 配置)
    ▼
DeepSeek V4 Pro 模型

关键认知:Codex 的工具调用、沙盒、上下文管理全在客户端,bridge 只负责协议翻译,不会影响 Codex 的核心能力。


坑 #1:OpenRouter 不是银弹------SSE 格式不兼容

现象

社区里最常见的建议是"用 OpenRouter BYOK 就行"。理论上 OpenRouter 支持 Responses API,于是很多人第一步就去配 OpenRouter。结果:

  • 短任务偶尔能跑
  • 稍长的任务流式响应中途断掉(stream disconnected
  • Codex 报错后重试,陷入死循环

根本原因

OpenRouter 的 Responses API 实现与 Codex 期望的 SSE 事件格式存在细节差异

字段 Codex 期望 OpenRouter 实际返回
event: response.output_item.done 每个 output item 结束时必须发送 部分情况下省略或延迟
data: [DONE] 时机 stream 完全结束后 有时提前发送
heartbeat keep-alive 长任务期间需要空行保活 不保证

Codex CLI 对 SSE 格式的解析是严格的,任何一个事件缺失或顺序错乱,它就认为流已结束。

解决方案

不要把 OpenRouter 作为 bridge 方案。用本地 bridge 直连 DeepSeek 官方 API,自己控制 SSE 格式。


坑 #2:SSE heartbeat 与 upstream timeout 的隐性矛盾

现象

本地 bridge 配好后,短对话正常,但让 Codex 执行稍复杂的任务(比如分析一个完整代码库)时:

javascript 复制代码
Error: stream disconnected after 60s

或者更诡异------任务静默终止,没有任何报错,Codex 直接回到提示符。

根本原因

这是三层 timeout 叠加的问题:

复制代码
DeepSeek V4 Pro 思考耗时  →  可能 30-120 秒
Node.js 默认请求超时     →  通常 60 秒
Codex SSE 心跳期望       →  如果 30 秒内没有任何 SSE 事件,认为连接断开

DeepSeek V4 Pro 在深度思考时会有较长的静默期(模型在 reasoning,但还没输出),这段时间 bridge 如果不主动发送 keep-alive 事件,Codex 会单方面断开连接。

解决方案

在 bridge 代码中加入 SSE heartbeat 逻辑,每 15 秒发送一个注释行:

javascript 复制代码
// 在 bridge 的 SSE 响应处理函数中
const heartbeatInterval = setInterval(() => {
  res.write(': keep-alive\n\n');  // SSE 注释行,Codex 会忽略内容但保持连接
}, 15000);

// 请求完成后清理
upstream.on('end', () => {
  clearInterval(heartbeatInterval);
  res.end();
});

同时需要延长 upstream 请求的 timeout:

arduino 复制代码
// Node.js 发出请求时
const options = {
  timeout: 300000,  // 5 分钟,给 DeepSeek 足够的思考时间
  // ...
};

如果你用的是 wujfeng712/codex-bridge,这个问题已经在其实现中处理了。推荐直接用这个版本而不是自己从头写


坑 #3:Clash 劫持回环流量------最隐蔽的坑

现象

bridge 启动正常,curl 直接测试 localhost:4000 也能通,但 Codex 通过 bridge 请求 DeepSeek 时报:

复制代码
401 Unauthorized

或者:

arduino 复制代码
Error: connect ETIMEDOUT api.deepseek.com

换一种表现:bridge 收到了 Codex 的请求,转发出去,但 DeepSeek 那边验证失败------明明 API key 是对的。

根本原因

Clash 的 TUN 模式会劫持所有网络流量,包括本机回环(127.0.0.1)

完整的问题链路是:

less 复制代码
Codex → 127.0.0.1:4000(本地 bridge)
              ↓ 被 Clash TUN 接管
       Clash 把这个请求当作"需要代理的流量"
              ↓
       发送到你的境外节点
              ↓
       节点转发到 api.deepseek.com
              ↓ 但 Authorization header 在途中被修改或丢失(部分节点会处理 header)
       DeepSeek 收到无效 key → 401

即使 Clash 没有修改 header,回环流量经过代理节点再回来,API key 的来源 IP 变成了节点 IP,而 DeepSeek 的速率限制可能把多用户共享节点 IP 的请求当作异常处理。

解决方案

在启动 Codex(或 bridge)之前,设置 NO_PROXY 环境变量,告诉系统代理不要处理本机回环流量:

PowerShell(推荐,每次启动 bridge 前执行):

ini 复制代码
$env:NO_PROXY = "127.0.0.1,localhost,::1"
$env:no_proxy = "127.0.0.1,localhost,::1"  # 部分工具读小写版本
node proxy.mjs

一行命令版(推荐写成启动脚本):

ruby 复制代码
$env:NO_PROXY="127.0.0.1,localhost,::1"; $env:no_proxy="127.0.0.1,localhost,::1"; node proxy.mjs

更彻底的方案:在 Clash 配置文件中加入旁路规则:

markdown 复制代码
# Clash config.yaml
bypass:
  - localhost
  - 127.*
  - 10.*
  - 172.16.*
  - 192.168.*
  - ::1

⚠️ 验证方法:bridge 启动后,先用 curl 测试:

css 复制代码
curl -H "Content-Type: application/json" http://127.0.0.1:4000/v1/responses -d '{"model":"deepseek-v4-pro","input":[{"type":"message","role":"user","content":"hi"}],"max_output_tokens":10}'

如果返回正常响应,说明 bridge → DeepSeek 链路通了,再接 Codex。


坑 #4:config.toml 字段位置敏感,错了不报错

现象

按照文档配好 ~/.codex/config.toml,启动 Codex 后:

  • 模型选择界面看起来正常
  • 发送第一条消息后,Codex 实际请求的还是 api.openai.com(用了默认配置)
  • 或者报 Model metadata not found,但任务继续进行,最终结果不对

根本原因

Codex config.toml 对 [model_providers.xxx] 块的字段顺序和缩进有隐性要求wire_api = "responses" 必须在 base_url 之前声明,否则静默使用默认值。

另一个常见问题:provider 名称和 model 字段的引用必须大小写完全一致

正确的 config.toml 模板

ini 复制代码
# ~/.codex/config.toml
# 注意:先备份原文件!
# cp ~/.codex/config.toml ~/.codex/config.toml.bak

model = "deepseek-v4-pro"
model_provider = "deepseek_bridge"

[model_providers.deepseek_bridge]
name = "DeepSeek via Bridge"
wire_api = "responses"           # ← 必须在 base_url 之前
base_url = "http://127.0.0.1:4000/v1"
env_key = "DEEPSEEK_API_KEY"

model_context_window = 128000    # DeepSeek V4 Pro 上下文窗口
model_max_output_tokens = 8192

.env 或启动前设置:

ini 复制代码
$env:DEEPSEEK_API_KEY = "sk-你的DeepSeek密钥"

验证配置是否生效

启动 Codex 后,在第一条消息里让它自报家门:

复制代码
你现在使用的是哪个模型?返回 model 字段的完整值。

如果返回 deepseek-v4-pro,配置生效。如果返回 gpt-* 或空,说明 config 没有被正确加载,检查文件路径和字段顺序。


坑 #5:reasoning_content 在多轮 tool call 中丢失(进阶)

现象

使用 DeepSeek V4 Pro 的思考模式(深度推理)时,第一轮 tool call 输出质量很好,但从第二轮开始,模型的回答开始"变蠢"------逻辑跳跃,前后矛盾,像是忘记了自己在想什么。

根本原因

DeepSeek V4 Pro 在思考模式下,每次响应包含两部分:

css 复制代码
reasoning_content  →  模型的内部推理过程(用户不可见)
content            →  最终输出

Codex 是多轮对话框架,每一轮 tool call 都需要把上下文传回模型。标准 Chat Completions API 里没有 reasoning_content 的续传机制------如果 bridge 不做特殊处理,第二轮请求里的上下文就丢失了推理链,模型等于从头开始想。

解决方案

bridge 需要在服务端缓存 reasoning_content,并在下一轮请求时作为 prefixcache 形式回注。

wujfeng712/codex-bridge 实现了一个 LRU bounded store ,用 previous_response_id 作为 key 缓存推理内容,在多轮 tool call 时自动重播。这是目前中文生态里极少有人解释清楚的机制。

如果你自己实现 bridge,需要在 POST /v1/responses 处理函数里:

csharp 复制代码
// 伪代码,说明逻辑
const cache = new LRUCache({ max: 50 });

async function handleResponses(req, res) {
  const { previous_response_id, ...rest } = req.body;
  
  // 如果有上轮 ID,取出缓存的 reasoning_content
  const cachedReasoning = previous_response_id 
    ? cache.get(previous_response_id) 
    : null;
  
  // 构造发给 DeepSeek 的请求,把 reasoning 回注
  const upstreamPayload = buildChatCompletions({
    ...rest,
    prefix_reasoning: cachedReasoning,  // 实际字段名取决于 DeepSeek API 版本
  });
  
  // 转发,收到响应后缓存本轮的 reasoning_content
  const response = await callDeepSeek(upstreamPayload);
  cache.set(response.id, response.reasoning_content);
  
  // 翻译回 Responses API 格式返回给 Codex
  return translateToResponsesAPI(response);
}

完整启动流程(可做成 .ps1 脚本)

把下面内容保存为 start-codex.ps1,每次启动时运行:

php 复制代码
# start-codex.ps1
# Codex CLI + DeepSeek V4 Pro 一键启动脚本(Windows + Clash 环境)

# 1. 设置 NO_PROXY,避免 Clash 劫持回环流量
$env:NO_PROXY = "127.0.0.1,localhost,::1"
$env:no_proxy = "127.0.0.1,localhost,::1"

# 2. 设置 DeepSeek API Key(建议改为从系统环境变量读取,不要硬编码)
# $env:DEEPSEEK_API_KEY = "sk-你的密钥"  # 取消注释并填入,或提前在系统变量里设置

# 3. 启动 bridge(后台运行)
$bridgePath = "$env:USERPROFILE\Desktop\codex-bridge"  # 改为你的实际路径
Start-Process -FilePath "node" -ArgumentList "proxy.mjs" -WorkingDirectory $bridgePath -WindowStyle Minimized

# 4. 等待 bridge 启动
Start-Sleep -Seconds 2

# 5. 验证 bridge 可用
try {
    $testBody = '{"model":"deepseek-v4-pro","input":[{"type":"message","role":"user","content":"ping"}],"max_output_tokens":5}'
    $response = Invoke-RestMethod -Uri "http://127.0.0.1:4000/v1/responses" -Method Post -Body $testBody -ContentType "application/json" -TimeoutSec 10
    Write-Host "✅ Bridge 启动成功,DeepSeek 连接正常" -ForegroundColor Green
} catch {
    Write-Host "❌ Bridge 或 DeepSeek 连接异常,请检查" -ForegroundColor Red
    Write-Host $_.Exception.Message
    exit 1
}

# 6. 启动 Codex
codex

快速排错对照表

现象 最可能的原因 检查方向
stream disconnected 且任务未完成 SSE heartbeat 缺失 / upstream timeout bridge 有无 keep-alive 逻辑;DeepSeek 请求超时设置
401 Unauthorized,key 明明是对的 Clash 劫持回环流量,header 被处理 检查 NO_PROXY;临时关闭 Clash TUN 模式测试
404 Not Found/v1/responses 没有 bridge,直接打 DeepSeek base_url 是否指向本地 bridge 而非 DeepSeek 官方
Codex 实际用的还是 OpenAI 模型 config.toml 没生效 wire_api 字段位置;provider 名称大小写
第一轮推理好,后续变差 reasoning_content 多轮丢失 bridge 是否支持 previous_response_id 缓存
Model metadata not found 警告 DeepSeek 不返回 OpenAI 格式的 model 元数据 这是正常警告,不影响使用,可忽略
PowerShell 变量设置后无效 大小写问题,部分工具读 no_proxy(小写) 同时设置 NO_PROXYno_proxy

推荐使用的 Bridge 实现

不建议自己从零写 bridge,直接用已经验证的版本:

wujfeng712/codex-bridge

Zero-dependency,单文件 proxy.mjs,不需要 npm install,处理了:

  • Responses API ↔ Chat Completions 双向翻译
  • SSE streaming bridge
  • reasoning_content 多轮缓存
  • 多 provider 路由(DeepSeek / MiMo / OpenAI)
  • per-provider reasoning effort 映射

启动方式:

bash 复制代码
# 克隆
git clone https://github.com/wujfeng712-ui/codex-bridge.git
cd codex-bridge
cp env.example .env
# 编辑 .env,填入 DEEPSEEK_API_KEY

# 启动(加上 NO_PROXY)
$env:NO_PROXY="127.0.0.1,localhost,::1"; $env:no_proxy="127.0.0.1,localhost,::1"; node proxy.mjs

Node.js 版本要求 20+--env-file flag 在 Node 18/19 上不可用)


成本参考

这套方案把 Codex CLI 的后端从 GPT-5 换成 DeepSeek V4 Pro:

项目 费用
Codex CLI 本体 免费(Apache 2.0 开源)
DeepSeek V4 Pro API 约 $0.27/M input tokens(2026 年 4 月降价后)
codex-bridge 免费开源
vs. GPT-5 直接使用 降低约 30-50 倍成本

已知局限

  1. Codex 内置 web_search 工具不可用:这个工具在 OpenAI 服务端,bridge 无法模拟
  2. 图片输入受限:DeepSeek V4 Pro 的视觉能力与 GPT-5 有差异
  3. 协议可能变化:Codex CLI 正从 TypeScript 迁移到 Rust,未来版本的 Responses API 格式可能调整,bridge 需要跟进更新

相关资源


如果这篇文章帮你省了时间,欢迎 Star 或转发。有新的坑欢迎在评论区补充。

相关推荐
winfredzhang7 小时前
用 Python + wxPython 做一个个人健康饮食管理工具:从记录三餐到综合生活建议
python·wxpython·deepseek·生活习惯管理
AI导出鸭PC端8 小时前
智谱清言怎样生成word文档——AI导出鸭助您一键转文档
人工智能·ai·word·豆包·deepseek·ai导出鸭
沉默王二8 小时前
不用 GPT-Image2,DeepSeek V4/GLM-5.1 + draw.io 就很顶!
gpt·ai编程·deepseek
DS随心转插件9 小时前
DeepSeek 代码手机端导出与 AI 辅助方案实测
android·人工智能·chatgpt·智能手机·deepseek·ai导出鸭
xiezhr10 小时前
折腾了一下午,终于让Codex用上了DeepSeek
人工智能·openai·deepseek
花花少年11 小时前
基于Docker快速部署OneAPI以及统一调用国产大模型
docker·oneapi·deepseek
DS随心转APP1 天前
AI导出鸭:AI 文档排版与一键导出实战指南
人工智能·ai·chatgpt·deepseek·ai导出鸭
AC赳赳老秦1 天前
用 OpenClaw 整理团队技术分享:自动提取 PPT 内容、生成文字稿、同步到知识库
开发语言·python·自动化·powerpoint·wpf·deepseek·openclaw
leikooo1 天前
LangChain4j 调用 DeepSeek 工具时报 400?用 pi 抓包定位,同包覆盖修复 reasoning_content
langchain·deepseek