Skip to content

feat(claude): add native Codex compaction bridge(为Claude通道引入Codex原生上下文压缩) - #4465

Draft
Johnnybyzhang wants to merge 9 commits into
router-for-me:devfrom
Johnnybyzhang:feat/claude-native-compaction-upstream
Draft

Johnnybyzhang wants to merge 9 commits into
router-for-me:devfrom
Johnnybyzhang:feat/claude-native-compaction-upstream

Conversation

@Johnnybyzhang

Copy link
Copy Markdown
Contributor

摘要

为 Claude /v1/messages 增加一条面向 Codex Responses API 的协议桥接路径,让 Claude App、Claude Code 等客户端继续按 Claude Messages 协议工作,同时复用 CPA 现有的 Codex OAuth、模型路由和上游连接。

这条路径不引入新的服务,也不增加 endpoint 或 target model 配置。CPA 仍然只是路由器:普通请求发往当前 Codex 上游的 /responses;当 Claude 自己发出自动压缩请求时,同一条路由直接改用该上游的 /responses/compact。上游模型沿用请求解码后的模型,不做额外覆盖。

背景

Claude 客户端会根据流式 usage 判断上下文消耗,并自行决定何时压缩、重放和继续会话。Codex Responses 流此前只有结束事件携带完整 usage;长时间运行的主会话或 worker 在执行过程中看不到持续增长的 token 数,容易在客户端尚未触发自动压缩前耗尽上下文。

此外,/responses/compact 返回的压缩状态包含不透明加密数据,后续重放必须继续使用生成该状态的原始 Codex 凭据。普通请求不应因此被固定到某个凭据,只有携带压缩状态的续接请求需要保持 auth affinity。

实现

/v1/messages → Codex Responses

  • 仅在 Claude 客户端模型 ID 解码为 GPT 模型时启用 bridge;原生 Claude 和其他 provider 的现有路径保持不变。
  • 非流式和 SSE 流式请求都通过现有 Codex executor 调用 /responses,再转换为 Claude Messages 响应,并把客户端看到的模型 ID 恢复为原值。
  • 请求继续使用 CPA 原有的 Codex OAuth 选择和上游配置。
  • WebSocket 只在所选 auth 明确启用时使用,否则回退到 HTTP/SSE。

Claude 驱动的自动压缩

  • 只识别 Claude 自动压缩提示中稳定的内部约束组合,普通的“总结对话”请求仍走 /responses,避免误判。
  • 压缩请求保留完整历史,Claude 提供的自定义压缩要求作为最后一条输入随 transcript 一起送往 /responses/compact
  • 上游返回的 message 与 opaque compaction item 会封装进带版本、模型和 auth ID 的 capsule,再作为标准 Claude 文本响应返回。
  • 后续请求会校验 capsule 的边界、大小、版本、模型和 item 类型,移除对 Claude 可见的 marker,并把不透明状态放回 Codex input 前部。
  • 初次压缩仍使用正常 auth 调度;只有重放 capsule 时才固定到原始 auth,确保加密状态可用。
  • 流式 compact 请求在内部缓冲 /responses/compact 的结果,再按 Claude SSE 事件序列输出。

运行中的 token usage

  • message_start 提供本地计算的输入 token 数,供 Claude 从响应开始阶段就建立上下文基线。
  • HTTP 和 WebSocket 路径都会发送累计的 message_delta.usage.output_tokens,覆盖文本、工具参数和 reasoning summary 等增量。
  • 对上游未实时公开的 reasoning token 数,使用保守估算生成 output_tokens_details.thinking_tokens;如果客户端启用 thinking-token-count beta,也会发送符合 Claude 事件形状的 estimated_tokens。(仍在测试中)
  • response.completed 中的上游 usage 始终覆盖本地估算,作为本轮最终准确值;Codex 内部 prompt cache 不会伪装成 Anthropic cache-control usage。
  • bridge 使用 200k 的 Claude 兼容上下文边界。超过边界时返回 context_too_large,由 Claude 客户端执行原生 compact-and-retry。

稳定性修正

  • HTTP 上游已经建立连接后不增加网络超时;取消下游 context 时,空闲流会及时关闭 scanner、response body、ticker 和输出 channel。
  • 公共 CountTokens 保持精确计数;用于 bridge 上下文预算的 256-token 保守 framing allowance 不再污染公开结果。
  • compaction marker 只在独立分隔的完整 capsule 上生效,源码或对话中引用 marker 常量不会被误当成重放状态。

用户侧效果

  • Claude 客户端无需修改,也无需了解 Codex Responses 协议。
  • 主会话和 workflow worker 可以在响应进行过程中看到累计 token usage,而不是等整轮结束。
  • 自动压缩仍由 Claude 决定何时触发;CPA 只负责协议转换、路由 /responses/compact 和安全重放。
  • 普通 Codex OAuth 轮转行为保持不变,压缩状态则能稳定续接到原始凭据。

验证

覆盖范围包括 handler 路由、compact 检测与重放、HTTP/SSE、WebSocket、usage 累计、reasoning token 估算、取消清理、精确 CountTokens 和 HTTP fallback。

go test ./internal/runtime/executor -run 'TestCodex.*(Bridge|Cancel|Count|Websocket)|TestValidateClaudeBridgeContextWindow|TestClaudeThinkingTokenCountRequested|TestApplyClaudeResponsesCompactionReplay' -count=1
go test ./internal/runtime/executor/helps -run 'TestClaude(StreamUsage|CumulativeUsage|ApplyMessageStart|ThinkingToken)' -count=1
go test ./sdk/api/handlers/claude -count=1
go test ./internal/translator/codex/claude -count=1
go test -race ./internal/runtime/executor -run 'TestCodex.*(Bridge|Cancel|Count|Websocket)|TestValidateClaudeBridgeContextWindow|TestClaudeThinkingTokenCountRequested|TestApplyClaudeResponsesCompactionReplay' -count=1
go test -race ./internal/runtime/executor/helps -run 'TestClaude(StreamUsage|CumulativeUsage|ApplyMessageStart|ThinkingToken)' -count=1
go test -race ./sdk/api/handlers/claude -count=1
go test ./...
go build -o test-output ./cmd/server && rm test-output

本地端到端验证使用未修改的 Claude Code 2.1.211 和 CPA localhost 实例,通过程序化工具调用持续生成上下文;测试观察到 tool_calls=6compact_boundaries=1,最终输出 CLAUDE_NATIVE_AUTOCOMPACT_E2E_OK。测试期间只降低了触发阈值,没有修改 Claude 客户端。

范围

本 PR 只修改 Claude/Codex bridge 所需的 handler、executor、translator、usage helper 及对应测试,共 13 个文件;不涉及文档、模型目录或其他 provider 的改动。

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a Claude-to-Codex responses bridge and compaction mechanism, allowing Claude /messages requests to use the Codex Responses API while maintaining Claude-compatible responses. It adds support for token estimation, context window validation, live streaming usage estimation, and compaction capsule encoding/decoding. The review feedback highlights several places in the newly added handlers (responses_bridge.go and compact_bridge.go) where defensive nil checks should be added for the returned response or stream objects to prevent potential nil pointer dereferences.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread sdk/api/handlers/claude/responses_bridge.go
Comment thread sdk/api/handlers/claude/responses_bridge.go
Comment thread sdk/api/handlers/claude/compact_bridge.go
@Johnnybyzhang

Copy link
Copy Markdown
Contributor Author

补充一次真实 Claude App 长会话验证:前两次自动压缩可以正常继续,但第三、第四次压缩虽然成功,随后的普通请求仍会收到 Prompt is too long,从而进入“再次压缩、再次失败”的循环。

根因不是 capsule 损坏或旧压缩窗口被重复追加。/responses/compact 返回的新窗口会正确替换上一份 canonical window;问题在于 CPA 为 Claude bridge 设置的 200k 合成边界仍然应用于已经携带有效 compaction replay 的请求。随着会话增长,固定的工具定义和指令开销加上不可再明显缩小的压缩状态仍可能超过该合成边界,于是 CPA 会在请求到达真实上游之前再次拒绝,重复压缩自然无法解除。

36f8d783 调整了这个行为:

  • 未压缩请求仍保留 200k 合成边界,用于触发 Claude 原生自动压缩;
  • 已包含有效 compaction / compaction_summary replay 的请求不再被该合成边界拦截,改由上游模型的真实上下文限制判定;
  • 新增回归测试,覆盖超过合成边界的 compact replay,以及连续压缩时只使用最新 canonical window、不会累计旧 capsule。

本地 focused tests、race tests、go test ./... 和 server build 均通过;下游测试分支上的初步 Claude App 实测也已确认 green。

@Johnnybyzhang
Johnnybyzhang force-pushed the feat/claude-native-compaction-upstream branch from 36f8d78 to 5bd0715 Compare July 23, 2026 17:30
@neighbads

Copy link
Copy Markdown

测试了,完全不可用,使用 cpa_responses_compaction 插入到普通消息上下文,导致后续处理不正常。

  1. 200k 写死的设计本身不合理,客户端 Claude Code 修改过 CompactWindow 后不能正常使用,小问题
  2. 修复 200k 窗口后,发现 使用 cpa_responses_compaction 插入到普通消息上下文 导致后续
    a. 上游返回 403
    b. 本地会话内容中插入大量的 cpa_responses_compaction 胶囊,污染上下文

原本 claude 本身没有压缩协议,如果要实现 codex 的压缩,最优方案就是 CPA 服务端缓存

@Johnnybyzhang
Johnnybyzhang force-pushed the feat/claude-native-compaction-upstream branch from 5bd0715 to 77314b7 Compare August 4, 2026 03:15
@Johnnybyzhang
Johnnybyzhang force-pushed the feat/claude-native-compaction-upstream branch from 77314b7 to dba5cd2 Compare August 19, 2026 05:04
@Johnnybyzhang
Johnnybyzhang force-pushed the feat/claude-native-compaction-upstream branch 2 times, most recently from dbe0f5d to f309f9b Compare September 12, 2026 04:44
@Johnnybyzhang
Johnnybyzhang force-pushed the feat/claude-native-compaction-upstream branch from f309f9b to a9c897e Compare September 16, 2026 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants