Commit Graph
100 Commits
Author SHA1 Message Date
docooler 26b5e87457 fix(apicompat): emit content_part events and full output in Responses stream
The Anthropic→Responses streaming converter omits two things the OpenAI
Responses wire format requires, which breaks clients that reconstruct the
response from the event stream (rather than just reading deltas).

1. response.content_part.added is never emitted.

   A message item is opened with content: [], and the OpenAI SDK's
   accumulating stream helper (client.responses.stream) only appends a
   content part when it sees content_part.added. Without it, the next
   output_text.delta indexes output.content[content_index] and raises:

     File "openai/lib/streaming/responses/_responses.py", line 352,
       in accumulate_event
         content = output.content[event.content_index]
     IndexError: list index out of range

   Raw iteration (responses.create(stream=True)) does not accumulate and is
   unaffected, which is why this went unnoticed.

2. response.completed carries Output: []ResponsesOutput{}.

   get_final_response() and tracing integrations parse the terminal event's
   response directly, so callers see an empty output_text even though the
   deltas streamed correctly. This one is invisible when only watching the
   stream render.

Also carries the full text on output_text.done / content_part.done (deltas
carry increments only, done events carry the whole part) and fills in
content/arguments/summary on output_item.done, which had the same
empty-payload issue.

Reproduced against a live Anthropic-platform group with the openai Python
SDK 2.46.0; Arize Phoenix's playground hits the same path. Verified before
(IndexError) and after (full text via get_final_response()).

Adds regression tests covering event ordering, done-event payloads, and the
terminal event's output for both text and tool calls.
2026-07-17 03:35:26 +00:00
Wesley LiddickandGitHub 4de53b61b8 Merge pull request #4339 from wp-a/codex/read-tool-stream-finalization
fix(apicompat): sanitize complete Read tool argument streams
2026-07-15 15:16:24 +08:00
Wesley LiddickandGitHub dba6cefb6b Merge pull request #4340 from wp-a/codex/anthropic-incomplete-finalization
fix(apicompat): preserve incomplete stream finalization
2026-07-15 15:16:12 +08:00
王鹏 9b4a7d5d47 fix(apicompat): sanitize complete Read tool argument streams 2026-07-15 14:31:26 +08:00
王鹏 d6d9f6ea4f fix(apicompat): preserve incomplete stream finalization 2026-07-15 14:31:26 +08:00
王鹏 c3a425837c fix(apicompat): scope additional tools parsing 2026-07-15 14:31:26 +08:00
Wesley LiddickandGitHub 3e30862ddd Merge pull request #4295 from caigee-cmd/perf/direct-anthropic-chatcompletions-bridge
perf(apicompat): force-chat 路径直转 Anthropic↔ChatCompletions,跳过 Responses 中间层
2026-07-15 13:46:56 +08:00
shaw 7b51271a84 fix(apicompat): default stop_reason for empty-choices responses
Adversarial re-verification found one more divergence from the
double-conversion chain: an upstream 200 with empty choices (or a nil
response) left stop_reason as an empty string, while the old chain
reports end_turn. Derive the fallback from the content blocks — the
guard never fires when choices exist, since every finish_reason maps to
a non-empty stop_reason.

Also strengthen the equivalence tests: compare tool_use Content[].Input
and tool_call Function.Arguments across bridges, and add an
empty-choices stop_reason parity case.
2026-07-15 11:44:47 +08:00
shaw 430fd8f20a fix(apicompat): align direct bridge edge semantics with double conversion
Adversarial review of the direct bridge found divergences from the
double-conversion chain it replaces; all are now aligned and covered by
tests that fail against the previous implementation:

- flush argument fragments buffered before a deferred tool announcement,
  and announce name-less tools at finalize, so no tool arguments are lost
  when upstreams stream arguments before the name
- fold text-only user array content into a single string; parts form only
  when an image requires it (strict chat upstreams reject array content)
- drop tool_choice pointing at undeclared tools and unknown choice types
- treat cache_write_tokens/cache_creation_tokens as alternate spellings
  (prefer write), not additive
- generate a response id when the upstream omits one
- derive stop_reason from blocks for content_filter/unknown finish reasons
- emit input_json_delta "{}" when a tool block closes without argument
  deltas
2026-07-15 11:29:22 +08:00
蔡及 22e97898bb fix: gofmt on parallel tool regression test 2026-07-15 00:59:28 +08:00
蔡及 1eb79df0d3 fix: golangci-lint issues (unused helpers, empty branch, errcheck, gofmt) 2026-07-15 00:58:42 +08:00
蔡及 07732a5a35 test: add regression tests for parallel tool_use ghost delta (#4193)
Three tests covering the CC→Responses→Anthropic finalize path:
- TestStreamingParallelToolUseNoGhostDelta: full end-to-end with two
  parallel tool_calls, asserts every content_block_delta targets a
  started block
- TestStreamingParallelToolUseSecondToolPackedArgsDone: focused test
  where tool 1 streams deltas and tool 2 has packed .done args
- TestStreamingThreeParallelToolsAllPackedDone: three tools all with
  packed .done, the most extreme case

All three fail on unfixed code (ghost deltas on wrong indices) and
pass after the fix.
2026-07-15 00:47:04 +08:00
蔡及 3504b8cc88 perf: direct Anthropic↔ChatCompletions bridge for force-chat accounts
Skip the Responses API intermediate representation on the /v1/messages
force-chat path. Previously every streaming token ran through two state
machines (CC→Responses→Anthropic); now it runs through one (CC→Anthropic).

New file backend/internal/pkg/apicompat/chatcompletions_anthropic_bridge.go:
- AnthropicToChatCompletionsRequest: request-side direct conversion
- ChatCompletionsResponseToAnthropic: non-stream response direct conversion
- ChatCompletionsChunkToAnthropicEvents + Finalize: single streaming state machine

openai_gateway_messages_chat_fallback.go rewired to use the direct bridge.
Existing 7 ForceChatCompletions end-to-end tests pass unchanged; 22 new
unit tests added including equivalence tests vs the double-conversion path.
2026-07-15 00:44:13 +08:00
蔡及 4f3b5110e2 fix: parallel tool_use ghost content_block_delta (index error)
resToAnthHandleFuncArgsDone used state.ContentBlockIndex directly
instead of looking up from OutputIndexToBlockIdx like
resToAnthHandleFuncArgsDelta does. When multiple tool_calls arrive
in parallel and arguments come as a packed .done (no prior delta),
the second+ tool would emit content_block_delta on an index that
was never content_block_start'ed, causing Claude Code to report
"Content block not found".

Fix: resolve block index from OutputIndexToBlockIdx, and skip the
delta if the block is already closed or the index doesn't match
the current open block.

Closes #4193
2026-07-15 00:44:09 +08:00
shaw fa1641f05f fix: keep namespace verbatim on WSv2 forwards and check test type assertions
WSv2 egress relays upstream events verbatim without the HTTP-path
namespace restore, so flattening requests that take the WSv2 branch
would surface flattened tool names the client cannot match. Resolve
the WS transport decision before flattening and skip flattening only
when the request will actually go WSv2 (passthrough accounts return
via HTTP before the WSv2 branch and still flatten).

Also check all type assertions in responses_namespace_test.go to
satisfy golangci-lint errcheck.
2026-07-14 14:48:22 +08:00
shaw 252ef8b73a Merge origin/main into codex/fix-native-responses-namespace
Resolve conflict in openai_gateway_passthrough.go streaming path: keep
main's normalizeCompletedImageGenerationStatus normalization ahead of
this branch's namespace restore block, mirroring the established order
in openai_gateway_response_handling.go.
2026-07-14 14:45:04 +08:00
Wesley LiddickandGitHub d774948e09 Merge pull request #4082 from zuelu/agent/fix-codex-additional-tools-chat-bridge
fix(apicompat): 支持 Codex additional_tools 到 Chat Completions 桥接
2026-07-13 14:42:16 +08:00
xingzhi 317de9c04b fix: support namespace tools in native responses 2026-07-13 14:38:05 +08:00
Wesley LiddickandGitHub fbc3f42a22 Merge pull request #4138 from fengshao1227/fix/read-tool-args-streaming
fix(apicompat): Read 工具参数实时流式发送,不再依赖 .done 事件
2026-07-13 14:14:08 +08:00
li b6427d4ec0 fix(apicompat): 流式 Anthropic max_tokens 映射为 incomplete + content_filter 映射为 finish_reason 2026-07-13 12:08:32 +08:00
li a5d40c9845 fix(apicompat): Read 工具参数实时流式发送,不再依赖 .done 事件
Fixes #4114
2026-07-13 11:47:18 +08:00
zuelu ff5c216189 fix(apicompat): bridge Codex additional tools 2026-07-12 11:33:09 +08:00
jjaw 5015b7a1c1 修复 tool_search 参数对象反序列化 2026-07-12 04:53:34 +08:00
Wesley LiddickandGitHub e316ebf528 Merge pull request #3989 from xlplbo/fix/codex-mcp-tools-bridge
fix(apicompat): 补齐 Codex 0.14x 工具链经 chat 回退桥的转换(custom/tool_search/namespace)
2026-07-10 22:09:25 +08:00
xlplboandClaude Fable 5 90e9d03dec fix(apicompat): 强制选择 tool_search 的 tool_choice 降级为指向代理的 function 选择
tool_search 工具未被丢弃而是降级为同名 function 代理,此前 tool_choice 过滤
把它连同其他服务端工具类型一起静默丢弃,强制工具搜索被退化为自动选择,
违反调用方明确指定的工具选择语义。改为映射到指向代理的 function 选择;
未声明 type=tool_search 时无可指向的代理,维持丢弃。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 21:14:33 +08:00
xlplboandClaude Fable 5 e2b68d1f90 fix(apicompat): tool_choice 仅转发实际存在于转换结果中的工具
此前只检查转换后是否还剩任意工具,不校验 tool_choice 指向的工具是否幸存:
强制选择被丢弃的服务端工具(如 web_search)或指向不存在名字时,选择项被
原样转发,chat 上游因 tool_choice 指向未声明工具而 400。改为具名选择项仅
在目标存在于转换后工具集时转发,服务端工具类选择项随工具本身丢弃;
"auto" 等字符串形式保持原样转发。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 21:06:45 +08:00
xlplboandClaude Fable 5 a2cdaa6419 fix(apicompat): 内置 tool_search 与同名工具撞名时显式拒绝请求
tool_search 服务端工具降级后的代理 function 不能改名(codex 的模型侧按
tool_search 这个名字调用),与客户端声明的同名 function/custom 工具无法
区分:去程产生重复声明,回程会把普通工具的调用劫持还原成 tool_search_call。
与 namespace 摊平撞名的处理一致,在请求转换阶段显式报错(网关 400);
重复声明 type=tool_search 去重后只产出一个代理,不拒绝。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 21:06:26 +08:00
li 83f169e4fa fix(apicompat): 补齐 Responses→Anthropic 流式路径的 cache_creation_input_tokens 2026-07-10 20:49:44 +08:00
xlplbo eb4d005031 Merge remote-tracking branch 'upstream/main' 2026-07-10 20:46:38 +08:00
li 0d28f7f90d fix(apicompat): Responses↔Anthropic 转换补齐 cache_creation_input_tokens
Fixes #3935
2026-07-10 20:40:45 +08:00
xlplboandClaude Fable 5 f1082bb78f fix(apicompat): namespace 摊平名撞名时显式拒绝请求
摊平名与顶层 function/custom 工具撞名、或跨 namespace 摊平出同名时,
chat 上游无法按 namespace 区分调用归属:此前重复声明照发上游、回程
固定命中其中一条映射,调用可能被还原到错误工具。这类请求在原生
Responses 上游合法,歧义由摊平转换制造且不可消除,改为在请求转换
阶段直接报错(网关返回 400 invalid_request_error 并点名冲突双方);
同一 (namespace, 子工具) 的重复声明去重后不拒绝。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 17:38:48 +08:00
xlplboandClaude Fable 5 7942338328 fix(apicompat): 回程还原 namespace 子工具调用,修复 Codex MCP 工具 unsupported call
Codex 0.14x 将 MCP 工具声明为 namespace 工具,chat 桥去程摊平为
"<namespace>__<name>" function 工具后,回程仅原样回传平铺名的
function_call 项;codex 按 namespace+name 路由查不到该名字,所有
MCP 工具调用被判为 unsupported call。

- NamespaceToolNames 构建摊平名 →(namespace, 子工具名)反查表
  (摊平名超长带截断哈希,无法按字符串切分还原)
- 非流式/流式回程命中映射时还原为裸子工具名 + namespace 字段,
  ResponsesOutput 新增 Namespace 字段并同步 wire 层白名单
- 回退桥入口将映射与 CustomTools/ToolSearchDeclared 一并穿入

已在测试机经 codex exec + MCP server 端到端验证。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
2026-07-10 17:38:17 +08:00
superman2003 de28eba3c8 fix(openai): harden GPT-5.6 billing and usage 2026-07-10 15:13:11 +08:00
Wesley LiddickandGitHub 8b96acde97 Merge pull request #3898 from Arron196/feat/gpt-5.6-cache-billing-stats
feat(openai): 支持 GPT-5.6 系列缓存写入计费与统计
2026-07-10 13:57:29 +08:00
xlplboandClaude Fable 5 18e26c127c Merge remote-tracking branch 'upstream/main'
解决 backend/internal/service/openai_gateway_responses_chat_fallback.go 冲突:
将本地 tool_search/custom 工具改动(customTools/toolSearch 穿参与流状态标记)
移植到上游重构后的 CC 公共管线;同步适配上游新增调用点
openai_gateway_messages_chat_fallback.go 的 ChatCompletionsResponseToResponses 签名。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F8zMzeFAumSddraEw1FUeG
2026-07-10 09:27:48 +08:00
benjamin 4a2b10c94e feat(openai): support GPT-5.6 cache write billing 2026-07-10 09:08:58 +08:00
CHOS1N ad8afc8a2e Add parallel_tool_calls compatibility mapping
Preserve Chat Completions parallel_tool_calls when converting requests to the Responses API, and map Responses parallel_tool_calls back when falling back to Chat Completions upstreams.

Cover both true and explicit false values so clients can disable parallel tool calls without the field being dropped by omitempty.
2026-07-09 22:52:35 +08:00
li 1785509873 fix(apicompat): ResponsesToAnthropicRequest 补全 instructions 字段并映射 developer role
Fixes #3850
2026-07-09 15:34:10 +08:00
xlplbo 27e29f0562 feat(apicompat): 添加 tool_search 支持,增强工具调用处理逻辑 2026-07-07 19:19:35 +08:00
xlplbo 75fb3c41c2 fix(apicompat): responses→chat 桥支持 custom 工具,修复 Codex exec 丢失
Codex 0.14x 的核心执行工具 exec 是 type=custom(freeform)工具,
responsesToolsToChatTools 丢弃所有非 function 工具,导致模型工具列表中
没有 exec、无法执行任何命令;SSE 序列化层 responsesItemWire 也缺少
custom_tool_call 分支,即使产出该类型 item,call_id/name/input 也会被
白名单剥离。

- custom 工具降级为单一 input:string 参数的 function 工具
- 回程(非流式+流式)按名字还原 custom_tool_call 项/事件
- 历史 custom_tool_call / custom_tool_call_output 项转换(此前被跳过)
- responsesItemWire 补 custom_tool_call 分支
- tools 全部被丢弃时不再转发 tool_choice(修复上游 400
  "'tool_choice' is only allowed when 'tools' are specified")

feat(apicompat): chat 桥补齐 tool_search/namespace/字符串工具声明

对齐参考实现(cc-switch transform_codex_chat.rs)的工具类型覆盖:
- tool_search → 固定 function 代理工具(query 必填/limit 可选),
  历史项 tool_search_call/tool_search_output 一并转换
- namespace → 子 function 工具摊平为 <namespace>__<name>,
  超 64 字符截断加 sha256 短哈希;历史项带 namespace 字段时同规则摊平
- tools: ["name"] 字符串简写按 custom 工具解析

fix(apicompat): 工具调用宣告延迟到类型可判定,兼容 name 迟到的上游

output_item.added 的 custom/function 判定依赖工具名。名字晚于首个分片
到达时,此前会按 function_call 宣告造成 added/done 项类型不一致。
改为存在 custom 工具且名字未到时延迟宣告,宣告时补发已累积的参数增量;
请求无 custom 工具时行为不变。

PR 标题与正文(可直接粘贴)

标题:fix(apicompat): responses→chat 桥丢弃 custom 工具导致 Codex 无法执行命令

正文:

## 问题

OpenAI APIKey 账号接入仅支持 /v1/chat/completions 的第三方上游
(DeepSeek/GLM/Kimi 等)时,Codex 0.14x 客户端文本对话正常,但无法执行
任何命令:exec 调用全部 "aborted",spawn_agent 派出的子智能体永远卡在
running,多轮后模型自述"环境无法执行文件系统命令"放弃。

## 根因

Codex 0.14x 的核心执行工具 exec 是 Responses API 的 custom(freeform)
工具,而非普通 function:

    {"type": "custom", "name": "exec", "format": {"type": "grammar", ...}}

两处缺陷叠加:

1. `responsesToolsToChatTools` 丢弃所有非 function 工具
   (chatcompletions_responses_bridge.go),模型工具列表中从未出现 exec;
2. SSE 序列化 `responsesItemWire` 按 item 类型白名单重组字段,无
   custom_tool_call 分支,call_id/name/input 会被剥离
   (responses_stream_event_wire.go)。

次生缺陷:工具全部被丢弃时 tool_choice 仍被转发,部分上游报 400
"'tool_choice' is only allowed when 'tools' are specified"。

## 改动

(三个提交的要点清单,见各提交说明)

设计与 cc-switch 的 Codex chat 转换层(transform_codex_chat.rs /
streaming_codex_chat.rs)对齐:custom 工具降级为 {"input": string}
function 工具;custom 调用的参数增量不可流式还原为自由文本,缓冲至
收尾一次性下发 custom_tool_call_input.delta/done。

## 测试

- 新增 17 个单元测试:请求工具转换、tool_choice 兜底、历史项转换、
  非流式/流式回程还原、wire 序列化字段完整性、name 迟到宣告、命名摊平
- `go test ./internal/pkg/apicompat/...` 与 service 相关测试全绿
- 真机验证:Codex CLI 0.142.5 → 本桥 → GLM/DeepSeek 系 chat-only 上游,
  exec 实际执行命令并多轮续作正常

## 影响面

仅影响 "APIKey 账号 + 判定不支持 Responses" 的 chat 直转路径:

- OAuth 账号与透传路径不经过本桥(透传为字节级原文转发)
- CC→Responses 反方向仅构造 ResponsesRequest,新增字段带 omitempty
  且不赋值,序列化输出不变
- 探测逻辑未动,路由判定不变
- 共享类型改动均为增量容错(新字段 omitempty;UnmarshalJSON 只新增
  字符串工具的解析成功路径)
2026-07-07 16:23:44 +08:00
CHOS1N e2326a7998 Format response format types 2026-07-06 12:14:30 +08:00
CHOS1N 5fcbe7e307 Add response_format compatibility mapping 2026-07-05 13:58:08 +08:00
wucm667 40c8252734 fix(apicompat): 规范化 custom 工具 schema 2026-06-26 14:08:03 +08:00
visa2andClaude Opus 4.8 29122e3051 fix(apicompat): avoid doubling tool_call arguments from single-chunk upstreams
When converting a Chat Completions stream into Responses events, the first
tool_call delta chunk was copied wholesale into stream state (including
function.arguments), then the same chunk's arguments were accumulated again by
the shared `+=` block. For OpenAI this is harmless because its first tool_call
chunk carries empty arguments, but upstreams that pack id+name+arguments into a
single chunk (e.g. GLM/Zhipu) end up with doubled arguments such as
{"cmd":"ls"}{"cmd":"ls"}. Codex then fails to parse the tool call with
"trailing characters", breaking every tool invocation.

Reset the copied arguments so the shared accumulator counts them exactly once,
keeping the emitted delta and the final done/arguments consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 00:58:38 +08:00
wucm667 edfd5e3736 fix(apicompat): default tool strict to false 2026-06-12 15:01:58 +08:00
shaw 7f6fdcd639 fix(apicompat): remove duplicated stream lifecycle test declarations
PR #3016's merge appended a verbatim second copy of four
TestStream_Reasoning* functions into
chatcompletions_responses_stream_lifecycle_test.go, causing
'redeclared in this block' build failures that broke both the
test and golangci-lint CI jobs.

Remove the duplicate block; each test now appears once.
2026-06-06 15:20:16 +08:00
Wesley LiddickandGitHub 51fd9c6cf4 Merge pull request #3016 from Fool0ntheHill/codex/deepseek-cc-responses-bridge
fix(apicompat): surface reasoning-only chat streams
2026-06-06 15:08:58 +08:00
Wesley LiddickandGitHub 75af992819 Merge pull request #3026 from Zbl1007/openmeta/2956-deepseek
Surface DeepSeek reasoning-only responses
2026-06-06 09:35:29 +08:00
eyre 5e6effd79c feat: 模型感知 Codex prompt / client_metadata / anthropic SSE 补全
- 模型感知 instructions:刷新 instructions.txt 至最新 GPT-5-Codex prompt,新增
  GPT-5.1 / GPT-5.2 真实 Codex 编码 agent prompt;新增 openai.CodexBaseInstructionsForModel
  按模型选用(codex 系→GPT-5-Codex,gpt-5.2→GPT-5.2,gpt-5.1/gpt-5→GPT-5.1),
  defaultCodexSynthInstructions 改为按 model 选择
- client_metadata:OAuth /responses 请求用账号真实 openai_device_id 注入
  client_metadata["x-codex-installation-id"];加法式、幂等、不覆盖既有项,
  无 device_id(非 OAuth 账号)则不写入、不伪造
- anthropic 转换补全 SSE 事件 reasoning_text.delta 与 custom_tool_call_input.delta
  (含 custom_tool_call item 注册为 tool_use),与 chat completions 路径对齐
- 新增单元测试覆盖以上行为(apicompat / openai / service)

承接上一提交,仍为加法式改进,不改动既有可工作的请求头。
2026-06-06 00:59:27 +08:00
eyre baf078fdb7 feat: 提升 Codex 行为模拟保真度(仅加法式改进)
- ForceCodexCLI 兜底 User-Agent 补全为完整结构 {originator}/{ver} ({OS} {ver}; {arch}) {terminal},
  与真实 codex_cli_rs UA 对齐,避免被上游指纹识别为非官方客户端
- 合成路径默认 instructions 改用内嵌的真实 Codex base prompt(openai.DefaultInstructions,
  "You are Codex, based on GPT-5..."),替换通用占位符;transform 与 hotpath 两处统一走
  defaultCodexSynthInstructions()
- /responses→chat 转换补全 SSE 事件 reasoning_text.delta 与 custom_tool_call_input.delta
  (流式 ResponsesEventToChatChunks 与缓冲 BufferedResponseAccumulator 两条路径),
  并将 custom_tool_call item 一并注册以正确映射工具索引
- OAuth /responses 请求带 reasoning 时补齐 include:["reasoning.encrypted_content"],
  幂等且加法式,不改动已有 include

保持旧稳定版兼容:不改动现有可工作的请求头(session_id/conversation_id/originator/
OpenAI-Beta 维持原样)。client_metadata(installation-id) 属最新版特征,会与旧稳定版
头指纹混搭冲突,本次未加。
2026-06-06 00:44:49 +08:00