一次对 claude.exe (225MB) 和 codex.exe (308MB) 的二进制逆向分析,揭示了两种截然不同的 Agent 协议设计哲学。


引言

AI 编程助手已经成为许多开发者的日常工具。当我们在终端里输入"帮我修复这个 bug",敲下回车——背后到底发生了什么?每一次工具调用、每一个 "让我先看看这个文件",到底对应多少次 API 请求?每次请求都要把完整的系统提示词重新发送一遍吗?

这些问题看似是"实现细节",但实际上它们直接决定了推理速度、token 成本、上下文质量,以及为什么你在一个工具里能连续对话两小时而在另一个里半小时就开始"失忆"。

本文通过对 Claude Code(Anthropic)和 Codex CLI(OpenAI)两个二进制文件的逆向分析,试图回答这些问题。


一、物理形态:两个二进制文件

维度 Claude Code Codex CLI
文件 claude.exe codex.exe
路径 npm/@anthropic-ai/claude-code/bin/ npm/@openai/codex/.../bin/
大小 225 MB 308 MB
类型 PE32+ Console x86-64 PE32+ Console x86-64
节数 12 10
语言 TypeScript → Bun 编译 Rust → MSVC 编译
JS 引擎 Bun (JavaScriptCore) V8 (嵌入)
配置语言 JSON/TOML Starlark (Bazel 方言)
API 后端 api.anthropic.com chatgpt.com/backend-api/codex
API 协议 Anthropic Messages API (HTTP) OpenAI Responses API (WebSocket)

两者的共同点是都把自己编译成了独立可执行文件——不是 Node.js wrapper,不是 Python 脚本,而是包含了完整运行时环境的原生二进制。这意味着你不需要安装 Node、Python 或任何运行时依赖,下载即用。

但它们的共同点到此为止。在 API 调用机制上,两者做出了截然不同的选择。


二、核心差异:有状态 vs 无状态

2.1 Claude Code:无状态 HTTP,每次全量发送

Claude Code 的每次 API 调用都是一个独立的 HTTP POST 请求:

POST https://api.anthropic.com/v1/messages
Authorization: x-api-key sk-ant-...
anthropic-version: 2023-06-01
Content-Type: application/json
​
{
  "model": "claude-fable-5",
  "system": "You are Claude... [~8000 tokens 的系统提示词]",
  "messages": [
    {"role": "user", "content": "帮我修复这个 bug"},
    {"role": "assistant", "content": [
      {"type": "tool_use", "name": "Grep", ...}
    ]},
    {"role": "user", "content": [
      {"type": "tool_result", ...}
    ]},
    // ... 完整历史 ...
  ],
  "tools": [/* ~50+ 工具定义,每个包含完整 JSON Schema */],
  "max_tokens": 32000,
  "thinking": {"type": "enabled", "budget_tokens": 16000}
}

关键特征

  • 每次请求都携带完整上下文:系统提示词、工具定义、完整对话历史——全部重新发送

  • 服务端无状态:每个请求之间没有关联,服务端不维护任何会话状态

  • Prompt Caching 补偿:Anthropic 的服务端缓存机制将重复的系统提示词成本降低约 90%

2.2 Codex CLI:有状态 WebSocket,TurnStart/TurnSteer 分离

通过对 codex.exe 二进制的逆向分析,我们发现它实现了一个精巧的 有状态协议,核心是两个消息类型:

TurnStartParams(20 个字段)——创建会话
TurnStart 请求 = {
    clientUserMessageId: string,       // 客户端消息 ID
    input: UserInput,                  // 用户输入
    responsesapiClientMetadata: {...}, // 客户端元数据 (版本/OS/终端类型)
    additionalContext: [...],          // Skills, Memory, Git status...
    environments: {...},               // 环境变量
    runtimeWorkspaceRoots: [...],      // 工作区根目录
    approvalPolicy: string,            // on-request | never | always
    approvalsReviewer: string,         // 审核者设置
    sandboxPolicy: string,             // readonly | workspace-write | ...
    serviceTier: string,               // Free | Plus | Pro | Team | Enterprise
    effort: string,                    // low | medium | high | xhigh
    outputSchema: {...},               // 输出格式约束
    collaborationMode: string,         // primary | review | ...
    multiAgentMode: string,            // disabled | proactive | ultra
    
    // 系统提示词和工具定义也在此发送!
    system_prompt: "...",
    tools: [...]
}
TurnSteerParams(6 个字段)——增量追加
TurnSteer 请求 = {
    expectedTurnId: string,   //  服务端状态索引
    // 只发送工具调用的结果,不重复系统提示词和工具定义!
    tool_results: [...]
}

关键特征

  • TurnStart 全量,TurnSteer 增量:一个 turn 内只有第一次发送完整上下文

  • expectedTurnId 作为状态索引:服务端通过它查找内存中的会话状态

  • 服务端有状态:维护 system prompt + tools + conversation history + KV cache

  • 验证机制:如果 expectedTurnId 不匹配当前活跃 turn → 返回 400 "no active turn to steer"


三、协议对比:一次用户交互的全流程

假设用户输入"帮我修复 login 模块的空指针异常",模型需要 5 步完成(搜索 → 读取 → 分析 → 编辑 → 总结)。

Claude Code 的流程

用户消息 #1 (Turn 开始)
│
├─ API 请求 #1: POST /v1/messages
│  ├─ system: [8,000 tokens]
│  ├─ tools: [2,000 tokens]
│  ├─ user_msg: "帮我修复..."
│  └─ 总输入: ~11,000 tokens
│
├─ API 请求 #2: POST /v1/messages
│  ├─ system: [8,000 tokens] ← 再次发送
│  ├─ tools: [2,000 tokens]  ← 再次发送
│  ├─ 完整历史 + tool_result
│  └─ 总输入: ~15,000 tokens
│
├─ API 请求 #3: POST /v1/messages
│  ├─ system: [8,000 tokens] ← 再次发送
│  ├─ tools: [2,000 tokens]  ← 再次发送
│  ├─ 完整历史 + tool_result
│  └─ 总输入: ~18,000 tokens
│
├─ API 请求 #4: ... (继续累积)
│
└─ API 请求 #5: ...
   └─ 总输入: ~25,000 tokens
​
   累计 input tokens: 
   原始 ≈ 11K + 15K + 18K + 21K + 25K = 90,000 tokens
   考虑 Prompt Caching (system+tools 缓存后降价 90%):
   实际 ≈ 11K + 6K + 9K + 12K + 16K = 54,000 tokens

Codex CLI 的流程

用户消息 #1 (Turn 开始)
│
├─ TurnStart (WebSocket msg #1)
│  ├─ system_prompt: [3,000 tokens]
│  ├─ tools: [5,000 tokens]
│  ├─ context: [2,000 tokens]
│  └─ 总输入: ~10,000 tokens (截断限制)
│
├─ TurnSteer (WebSocket msg #2)
│  ├─ 只发增量: tool_result + next_prompt
│  ├─ 不重复 system_prompt 
│  ├─ 不重复 tools 
│  └─ 总增量: ~2,000 tokens
│
├─ TurnSteer (WebSocket msg #3)
│  └─ 增量: ~3,000 tokens
│
├─ TurnSteer (WebSocket msg #4)
│  └─ 增量: ~3,000 tokens
│
└─ TurnSteer (WebSocket msg #5)
   └─ 增量: ~2,000 tokens
​
 累计 input tokens: ~10K + 2K + 3K + 3K + 2K = 20,000 tokens

可视化对比

同一个任务(5 步完成),input tokens 消耗:
​
Claude Code (无缓存):  ████████████████████████████████████████  90,000
Claude Code (有缓存):  ████████████████████████                  54,000
Codex CLI:             ████████                                 20,000
                       ├────────────────────────────────────────┤
                       0                                    90,000 tokens

四、技术细节:Codex 的 TurnSteer 是如何工作的?

4.1 协议定义(从二进制逆向提取)

codex.exe.rdata 段中,找到了嵌入的 TypeScript 类型定义文件:

// TurnSteerParams.ts (嵌入在 codex.exe 中的源文件)
/**
 * Required active turn id precondition.
 * The request fails when it does not match the currently active turn.
 */
expectedTurnId: string

以及错误处理逻辑:

"expectedTurnId must not be empty"
"no active turn to steer"

4.2 服务端状态维护

OpenAI 的服务端维护一个 Turn Session Store,结构大致如下:

TurnSession {
    turn_id: "turn_abc123",
    thread_id: "thread_xyz",
    
    // ⬇ 以下内容在 TurnSteer 时不再传输
    
    system_prompt: {                    // 完整的系统指令
        base_instructions: "...",
        personality: "friendly",
        skills: [...],
        memory_entries: [...]
    },
    
    tool_registry: {                    // 完整的工具注册表
        "fs/readFile": { schema: {...} },
        "fs/writeFile": { schema: {...} },
        "command/exec": { schema: {...} },
        // ... 50+ 工具
    },
    
    conversation: [                     // 累积的完整对话
        {role: "user", content: "..."},
        {role: "assistant", tool_calls: [...]},
        {role: "tool", content: "..."},
        // ...
    ],
    
    settings: {                         // Turn 级设置快照
        approval_policy: "on-request",
        sandbox_policy: "workspace-write",
        service_tier: "plus",
        effort: "medium"
    },
    
    model_state: {                      // GPT-5.5 推理状态
        kv_cache: [...],                // 已计算的 KV Cache
        position: 15432                 // 当前 token 位置
    }
}

4.3 为什么 TurnSteer 能这么快?

关键是 KV Cache 复用。当 GPT-5.5 处理 TurnStart 时,它对 system prompt + tools + user message 进行了 prefill,计算结果保存在 KV cache 中。当 TurnSteer 到达时:

  1. 服务端通过 expectedTurnId 找到对应的 session

  2. 将 tool_result 追加到 conversation_history

  3. 直接从已有的 KV cache 继续解码,不需要重新 prefill

这意味着 TurnSteer 的 Time-To-First-Token (TTFT) 接近零——模型不需要重新处理系统提示词和工具定义,直接产出下一个 token。

对比 Claude Code,每次 HTTP POST 都要完整地 prefill system prompt + tools + history,即使有 Prompt Caching 降低了计费成本,prefill 的延迟是无法跳过的


五、设计哲学:两种世界观

5.1 为什么 Anthropic 选择无状态 HTTP?

Claude Code 的设计哲学:
  "可靠性 > 效率"
  "简单 > 精巧"

优点:
├─ 容错性极强: 任何一个请求失败,重试就是完整的
├─ 服务端简单: 不需要维护 WebSocket 会话状态
├─ 无状态扩展: 请求可以路由到任意服务器
├─ 跨 turn 复用: Prompt Caching 可以横跨多个请求
├─ 调试友好: 每个请求都是可独立复现的
└─ 1M 上下文窗口: 有空间容纳重复发送的开销

代价:
├─ 每次请求都要发送完整 payload(网络开销)
├─ 每次请求都要 prefill(延迟)
└─ 依赖服务端缓存来降低成本(不是所有场景都生效)

5.2 为什么 OpenAI 选择有状态 WebSocket?

Codex CLI 的设计哲学:
  "效率 > 简单"
  "精巧 > 通用"

优点:
├─ Token 成本极低: turn 内不重复发送上下文
├─ TTFT 极低: KV Cache 持续复用
├─ 实时性好: WebSocket 天然支持 streaming + 通知
├─ 分层设计: TurnStart/TurnSteer/TurnInterrupt 各司其职
└─ 协议级支持: turn 是一等概念,不是通过无状态模拟

代价:
├─ 服务端复杂度高: 需要维护大量并发 Turn Session
├─ 容错性差: 断连 = 整个 turn 丢失
├─ 不可跨 turn 复用: 每个用户消息都是新 TurnStart
├─ 硬截断风险: 10K tokens 的截断上限
└─ 调试困难: 无法独立复现单个 TurnSteer

5.3 上下文管理策略对比

策略 Claude Code Codex CLI
窗口大小 最高 1M tokens 272K tokens
截断方式 Auto-compact 自动摘要 10K tokens 硬截断
长对话管理 渐进压缩 → 摘要 激进截断 → 丢失
跨 turn 记忆 Memory 文件系统 Memory 系统 (类似)
子 Agent 独立上下文窗口 共享 session

这解释了为什么很多用户感觉 Claude Code 在长对话中更"持久"——不是因为它不丢上下文,而是因为 1M 的窗口太大,在达到极限之前 Auto-compact 已经帮你摘要了。而 Codex 的 10K 硬截断意味着,如果你的对话 + system prompt + tools 超过了这个阈值,早期内容会被直接丢弃。


六、模型配置对比(从二进制中提取)

Claude Code 的模型列表(环境变量定义)

ANTHROPIC_DEFAULT_FABLE_MODEL     → claude-fable-5
ANTHROPIC_DEFAULT_OPUS_MODEL      → claude-opus-4-8
ANTHROPIC_DEFAULT_SONNET_MODEL    → claude-sonnet-4-6
ANTHROPIC_DEFAULT_HAIKU_MODEL     → claude-haiku-4-5
ANTHROPIC_SMALL_FAST_MODEL        → (备选快速模型)

Opus 4.8 配置:1M context window, 支持 extended thinking Fable 5 配置:Mythos-class tier, 共享底层模型

Codex CLI 的模型列表(嵌入 JSON)

{
  "slug": "gpt-5.5",
  "display_name": "GPT-5.5",
  "description": "Frontier model for complex coding, research, and real-world work.",
  "context_window": 272000,
  "max_context_window": 272000,
  "auto_compact_token_limit": null,
  "truncation_policy": { "mode": "tokens", "limit": 10000 },
  "default_reasoning_level": "medium",
  "supports_parallel_tool_calls": true
}

GPT-5.4:同样 272K 窗口,但 max_context_window: 1,000,000(预留扩展) GPT-5.4-Mini:小型快速模型 GPT-5.3-Codex:编程优化模型(前代) GPT-5.2:长运行 Agent 优化


七、安全与认证对比

维度 Claude Code Codex CLI
认证方式 API Key / OAuth / AWS Bedrock / GCP Vertex / Foundry ChatGPT OAuth / API Key / AWS Bedrock
沙箱策略 readonly / workspace-write / danger-full-access / linux-seccomp readonly / workspace-write / danger-full-access / windows-sandbox
审批机制 never / on-request / always + Reviewer Agent never / on-request / always + Guardian Agent
遥测端点 api.anthropic.com ab.chatgpt.com/otlp/v1/metrics + Sentry
联邦认证 OIDC Federation + WIF JWT Agent Identity

两者在安全模型上高度相似——都有沙箱分层、审批门控、代码审查 Agent,这是这个品类的基本安全基线。差异主要在于实现细节:Claude Code 支持更多企业认证方式(Vertex AI、Foundry),Codex 的 Windows 沙箱集成更深。


八、总结

问题 Claude Code 的答案 Codex CLI 的答案
每次 API 调用都发系统提示词吗? 是。但 Prompt Caching 将成本降到 10% 不。TurnStart 发一次,TurnSteer 不发
一个用户消息调用多少次 API? N 次(每个 tool round-trip 一次 HTTP POST) 1 次 TurnStart + (N-1) 次 TurnSteer
谁能用更少的 token 完成同样任务? ~54,000(有缓存时) ~20,000(无缓存也低)
谁的首字延迟更低? 每次都要 prefill TurnSteer 时 KV Cache 直接续,接近零延迟
谁的长对话体验更好? 1M 窗口 + auto-compact 272K 窗口 + 10K 硬截断
谁的架构更简单? 无状态 HTTP,水平扩展友好 有状态 WebSocket,运维复杂度高
谁更容易调试? 每个请求是独立的、可复现的 依赖会话状态,复现需要模拟整个序列

后记

差异在于它们在同一个根本问题上做出了相反的选择:上下文状态应该由客户端管理还是服务端管理?

Anthropic 的回答是:"客户端负责每次把完整上下文发给我,我用缓存帮你省钱。" OpenAI 的回答是:"你第一次发完整上下文,后面只发增量,我帮你在服务端记着。"

两种方案经过各自优化后的总成本和体验差距并没有看起来那么大——真正的差异在于生态位。Claude Code 靠 1M 窗口 + Prompt Caching 在"持久对话"这个场景里占优,Codex CLI 靠 KV Cache 复用在"快速迭代"的场景里更敏捷。

最终用户感受到的差异,往往不是协议层的选择导致的,而是上下文管理策略——你是愿意用大窗口容纳全量历史,还是用小窗口做激进截断。在这个意义上,协议设计反映的其实是产品理念:你要的是一个能陪你聊一下午的编程搭档,还是一个快速高效完成当前任务的工具。


分析日期:2026-07-03 分析目标:claude.exe (225MB, Bun编译) 和 codex.exe (308MB, Rust编译)

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐