Claude Code vs Codex CLI:AI编程助手API调用机制的逆向深度对比

时间:2026-07-08 08:35:42 来源:互联网

针对claude.exe(225MB)与codex.exe(308MB)的二进制逆向剖析,映射出两种迥异的Agent协议设计理念。本文将从API调用机制到上下文管理策略,系统比较两种方案的架构差异与设计权衡。

img_6a4d9b5deb47130.webp

引言

AI编程助手已成为众多开发者日常工作的标配工具。当我们在终端内输入“帮我修复这个bug”并按下回车键,背后究竟发生了怎样的技术流程?每一次工具调用,每一次“让我先看看这个文件”,各自对应着多少次API请求?每次请求是否都必须将完整的系统提示词重新发送一遍?

这些问题表面上是实现层面的细节,但实际上它们直接决定着推理速度、Token成本、上下文质量,以及为何在某些工具中能连续对话两小时,而在另一些工具中仅仅半小时便开始出现记忆衰退。

本文通过对Claude Code(Anthropic)与Codex CLI(OpenAI)两个二进制文件的逆向解析,试图对上述问题给出回答。

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

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

两者的共同之处在于,它们都将自身编译为独立的可执行文件——不是Node.js包装器,不是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}
}

关键特征

  1. 每次请求都携带完整上下文:系统提示词、工具定义、完整对话历史——全部重新发送
  2. 服务端无状态:每个请求之间没有关联,服务端不维护任何会话状态
  3. 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: [...]
}

关键特征

  1. TurnStart 全量,TurnSteer 增量:一个 turn 内只有第一次发送完整上下文
  2. expectedTurnId 作为状态索引:服务端通过它查找内存中的会话状态
  3. 服务端有状态:维护 system prompt + tools + conversation history + KV cache
  4. 验证机制:如果 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 CodeCodex CLI
窗口大小最高 1M tokens272K 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 CodeCodex CLI
认证方式API Key / OAuth / AWS Bedrock / GCP Vertex / FoundryChatGPT OAuth / API Key / AWS Bedrock
沙箱策略readonly / workspace-write / danger-full-access / linux-seccompreadonly / workspace-write / danger-full-access / windows-sandbox
审批机制never / on-request / always + Reviewer Agentnever / on-request / always + Guardian Agent
遥测端点api.anthropic.comab.chatgpt.com/otlp/v1/metrics + Sentry
联邦认证OIDC Federation + WIFJWT 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(无缓存也低)
谁的首字延迟更低?每次都要 prefillTurnSteer 时 KV Cache 直接续,接近零延迟
谁的长对话体验更好?1M 窗口 + auto-compact272K 窗口 + 10K 硬截断
谁的架构更简单?无状态 HTTP,水平扩展友好有状态 WebSocket,运维复杂度高
谁更容易调试?每个请求是独立的、可复现的依赖会话状态,复现需模拟整个序列

后记

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

Anthropic的答案是:“客户端负责每次将完整上下文发送给我,我用缓存帮你节省成本。”OpenAI的答案是:“你初次发送完整上下文,后续只传递增量,我帮你在服务端维护状态。”

两种方案经过各自优化后,总成本与体验的差距并未如表面所见那般悬殊——真正的差别在于生态位的不同。Claude Code凭借1M窗口加Prompt Caching,在“持久对话”场景中占据优势;Codex CLI则依托KV Cache复用,在“快速迭代”场景中更显灵活。

用户实际感知的差异往往根植于上下文管理策略层面,而非协议层的取舍。是采用大窗口保留全量历史,还是用小窗口进行激进截断,这反映了产品设计理念的根本分野——期望打造能长时间协作的编程伴侣,还是聚焦快速完成当前任务的高效工具。