Skip to content

fix(protocol): sub2api-parity audit — 9 conversion gaps across chat/responses/anthropic - #33

Merged
6Kmfi6HP merged 4 commits into
mainfrom
fix/protocol-parity-sub2api
Sep 28, 2026
Merged

6Kmfi6HP merged 4 commits into
mainfrom
fix/protocol-parity-sub2api

Conversation

@6Kmfi6HP

Copy link
Copy Markdown
Owner

对照审计

以 Wei-Shaw/sub2api backend/internal/pkg/apicompat 为参照(chatcompletions_to_responses / responses_to_chatcompletions / chatcompletions_anthropic_bridge / anthropic_to_responses / responses_to_anthropic / responses_stream_event_wire / responses_namespace / types),辅以官方 Responses 流事件文档,对三条转换链路逐项对照,补齐 9 项差异。历史三个协议转换 issue(#17 工具索引分裂、#19 免费层指纹、#21 responses 指纹形状)已在 main 中修复并验证,本 PR 不碰那些路径,只补审计中新发现的 gaps。

修复清单

  1. chat→responses 请求体:parallel_tool_calls / service_tier 透传上游(顶层键经 preserveChatPassthroughKeys 从原始 body 回填 ExtraBody,类型化结构装不下;extra_body 显式键优先);response_format 映射为 text.format(此前直接丢弃)。
  2. responses→chat 流/聚合/非流式:custom_tool_call 纳入工具槽位,custom_tool_call_input.delta/done 同形累积(done 读 input 键);incomplete 按 reason 细分(content_filter→content_filter);上游 service_tier 回写 chat 顶层;response.done 别名与 completed 同等终结。
  3. chat→anthropic 请求体:thinking 生效即剥离 temperature/top_p(此前无条件透传→上游 400;与 convertClaudeRequest 同口径)。
  4. claude 响应:content_filter 的 stop_reason 由非法枚举 refusal 改为 end_turn;stop 分支加 tool_use block 存在性回退。
  5. usage/finish 闭集合:cache_creation_input_tokens→details 归位;normalizeFinishReason 未知原因回退 stop。

测试

  • 新增 protocol_parity_sub2api_test.go 11 用例(custom 工具同 index、content_filter 双路径、response.done 哨兵、service_tier 双路径、thinking 剥参、cache_creation 归位、未知 finish 闭集合、ExtraBody 回填等)。
  • go test ./... 全绿,go vet / gofmt 干净。
  • 真实请求测试(新二进制,18358 direct + 18359 muse-spark responses-rule):direct chat 工具单 index、via-responses 200、claude/responses 工具、chat 流 [DONE]、responses 流 completed、messages 流 message_stop,9/9 通过;thinking+temperature 同传 200。

🤖 Generated with Claude Code

6Kmfi6HP and others added 4 commits September 28, 2026 15:35
…esponses/anthropic

- chat->responses request: passthrough parallel_tool_calls/service_tier
  (top-level keys backfilled from raw body via preserveChatPassthroughKeys);
  map response_format to text.format (json_schema flatten / json_object type)
- responses->chat stream/aggregate/nonstream: accept custom_tool_call +
  custom_tool_call_input delta/done on shared tool index (done reads input
  key); split incomplete reason (content_filter vs length); echo upstream
  service_tier on chat top level; treat response.done alias like completed
- chat->anthropic request: strip temperature/top_p when thinking enabled
- claude responses: content_filter stop_reason refusal -> end_turn (enum);
  stop branch falls back to tool_use when content holds tool_use blocks
- usage/finish closed sets: cache_creation_input_tokens -> details
 归位; normalizeFinishReason unknown -> stop
- 11 regression tests in protocol_parity_sub2api_test.go; docs/API.md +
  CHANGELOG updated
- live E2E (mimo-v2.6-flash direct + muse-spark responses-rule): 9/9 pass

Co-Authored-By: Claude Code <noreply@anthropic.com>
同一客户端 token 下所有请求曾恒选同一池 key(仅哈希 token 部分),
1811 把 key 实际只有 k36 在服务。现 sticky 复用完整 egress sticky 键
(token + 客户端会话后缀),同一会话仍粘定同 key(缓存亲和),不同
会话按哈希散开;failover 重试混入常量后缀跳离故障 key。

Co-Authored-By: Claude Code <noreply@anthropic.com>
stickySessionBase 对无 token(admin 密码触发池)的请求剥离共享
public fallback 前缀,以 sess:<会话后缀> 散列:不同网关会话散开,
同一会话仍粘定;有 token 路径保持 tok:<token>|cli:<会话> 不变。
回归测试覆盖 admin scope 散开 + 同 scope 粘定。

Co-Authored-By: Claude Code <noreply@anthropic.com>
withSessionFromRequest 实际克隆全部请求头供 sticky 路由使用,
而不仅是 x-opencode-session;改名为 withClientHeadersFromRequest
避免读者误以为客户端会话头被丢弃。无行为变更。

Co-Authored-By: Claude Code <noreply@anthropic.com>
@6Kmfi6HP
6Kmfi6HP merged commit 07b54f3 into main Sep 28, 2026
14 checks passed
@6Kmfi6HP
6Kmfi6HP deleted the fix/protocol-parity-sub2api branch September 28, 2026 15:13
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.

1 participant