Commit Graph
176 Commits
Author SHA1 Message Date
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 Liddick 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 Liddick 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 Liddick 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
Zbl1007 9b99f6c1f3 fix(apicompat): surface DeepSeek reasoning-only replies 2026-06-04 11:34:54 +08:00
Fool0ntheHill 55655b8654 fix(apicompat): surface reasoning-only chat streams 2026-06-03 20:52:11 +08:00
visa2andClaude Opus 4.8 60867022b6 fix(apicompat): repair tool_use/tool_result pairing on the Responses→Anthropic path
When an OpenAI Chat Completions client targets an Anthropic-platform group,
ForwardAsChatCompletions converts the request CC → Responses → Anthropic
(ChatCompletionsToResponses → ResponsesToAnthropicRequest) before forwarding it
upstream. The Responses→Anthropic converter emits each function_call as its own
assistant message and each function_call_output as its own user message and
relies solely on mergeConsecutiveMessages to alternate roles. That is not enough
to satisfy Anthropic's tool-pairing invariants, so a trimmed or partial tool
history produces an upstream 400, e.g.:

    tool_use_id found in tool_result blocks: call_00_...
    Each tool_result block must have a corresponding tool_use block in the
    previous message.

The failures this leaves unrepaired:

  - orphan tool_result — a client that does sliding-window context management
    keeps a recent tool result but drops the assistant tool_calls message that
    announced it, so the tool_result has no matching tool_use;
  - unanswered/dangling tool_use — a parallel call whose sibling result never
    came back, or a call left dangling, which Anthropic also rejects.

Add normalizeAnthropicToolPairing, run between two merge passes: the first merge
groups parallel calls and their results; the pairing pass indexes every
tool_result by its tool_use id, keeps only answered tool_use blocks (dropping
unanswered/dangling calls, and the assistant message entirely when nothing else
remains) and re-emits the matching tool_result blocks as the immediately
following user message; standalone/orphan tool_results are dropped from their
original position; the second merge restores alternation. This mirrors
normalizeChatMessages on the Responses→Chat path.

Tested two ways: responses_to_anthropic_tool_pairing_test.go covers the repair
on direct Responses input (developer message between call and output, parallel
both-answered kept grouped, parallel one-unanswered dropped, orphan tool_result,
dangling call, single-call baseline); responses_to_anthropic_cc_chain_test.go
drives the real ChatCompletionsToResponses → ResponsesToAnthropicRequest chain
and reproduces the production 400 (orphan and unanswered-parallel) — both fail
without the repair and pass with it. The full apicompat suite stays green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:26:05 +08:00
visa2andClaude Opus 4.8 003b2786da test(apicompat): check type assertions in responses stream wire tests
errcheck (check-type-assertions) flagged unchecked single-value type
assertions; switch to the comma-ok form so golangci-lint passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 12:03:15 +08:00
visa2andClaude Opus 4.8 f10bca8155 refactor(apicompat): redesign the Codex Responses ↔ Chat Completions bridge
Codex CLI speaks the OpenAI Responses protocol (streaming, store:false), while
many upstreams (e.g. DeepSeek in thinking mode) only expose Chat Completions.
The bridge that translates between the two had grown field by field and leaned
on Go's serialization defaults, which both the Responses client (Codex) and the
Chat upstream reject in ways the official OpenAI endpoints tolerate.

Problems this fixes (all observed running Codex CLI against a DeepSeek upstream):
  - Streaming reasoning was never shown in the Codex TUI (the answer appeared
    with no visible thinking): reasoning deltas were emitted before the reasoning
    item was opened, so the strict client discarded them.
  - A tool-using turn could wedge the session into a "no response" state: the
    function_call stream was never closed (no function_call_arguments.done /
    output_item.done), so Codex never saw the tool call complete.
  - Parallel tool calls were rejected upstream (400/502): each function_call
    became its own assistant message, producing consecutive assistant messages
    with mismatched tool replies.
  - A tool turn was rejected with "reasoning_content in the thinking mode must be
    passed back": the reasoning that produced the tool call was dropped instead
    of being returned on the assistant message.
  - Items with no Chat equivalent (web_search_call, ...) and Codex's
    command-approval notice landed between an assistant tool_calls message and
    its tool reply, triggering "An assistant message with 'tool_calls' must be
    followed by tool messages responding to each 'tool_call_id'".
  - Interrupt/reconnect left an unanswered or dangling tool_call in the history,
    triggering the same 400.

The shared root cause is reliance on serialization defaults — omitempty dropping
protocol-required zero values, and unrecognized item types falling through a
generic path — rather than deliberately reproducing the target protocol. The
bridge is reworked into two explicit layers.

Request direction (Responses input -> Chat messages): a parse -> build ->
normalize pipeline.
  - reasoning_content is carried back on the assistant message that produced a
    tool call (DeepSeek thinking mode requires it to continue the same thought)
  - consecutive function_call items (parallel tool calls) are merged into a
    single assistant message's tool_calls array
  - item types with no Chat equivalent are skipped instead of leaking through a
    generic path
  - normalizeChatMessages is the single invariant gate: it guarantees every
    assistant tool_calls message is immediately followed by one tool reply per
    tool_call_id — reordering any intervening message (such as a command-approval
    notice) to after the replies, dropping unanswered tool_calls and orphan tool
    replies, and preserving bare passthrough tool messages.

Response direction (Chat SSE -> Responses SSE): ResponsesStreamEvent.MarshalJSON
constructs each streamed event explicitly so protocol-required fields are always
present (output_index/content_index/summary_index at 0, message content:[],
reasoning summary:[], function_call call_id/name/arguments, output_text part
text/annotations/logprobs). This is a single source of truth that removes any
post-hoc JSON patching. Reasoning is emitted as its own output item, opened
before its deltas, and tool calls are fully closed
(function_call_arguments.done + output_item.done with complete arguments).

Tests cover request-direction message invariants against golden Codex request
shapes (parallel calls, unknown items, intervening messages, partial/dangling
calls), per-event wire completeness, and streaming lifecycle ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 16:14:58 +08:00
Wesley Liddick 1d46be02ae Merge pull request #2830 from stabey/fix/anthropic-to-responses-cache-tokens
fix(apicompat): Anthropic 转 Responses 时按 OpenAI 语义汇总 input_tokens
2026-05-29 11:00:19 +08:00
JIA-ss 20f5340784 fix(apicompat): Responses→Chat 转换补齐 completion_tokens_details 透传
OpenAI Responses API 在 gpt-5.x 等 reasoning 模型上会返回
output_tokens_details.reasoning_tokens, 但 ResponsesToChatCompletions
只映射了 input_tokens_details.cached_tokens, 导致客户端拿到的
chat.completion.usage 中 completion_tokens 出现无法解释的波动
(短 prompt 也可能 30+ token), 且缺失 reasoning_tokens 细分字段,
难以与 OpenAI 原生 Chat Completions 响应对账。

按 OpenAI 官方 CompletionUsage schema (openai/openai-go SDK
completion.go) 补齐所有 token-details 字段, 全部 omitempty:

  prompt_tokens_details:
    - cached_tokens   (原已支持)
    - audio_tokens    (新增)
  completion_tokens_details:
    - reasoning_tokens             (新增)
    - audio_tokens                 (新增)
    - accepted_prediction_tokens   (新增)
    - rejected_prediction_tokens   (新增)

实现细节:
- 抽出 promptDetailsFromResponses / completionDetailsFromResponses
  两个 helper, 全零字段返回 nil
- 非流路径 ResponsesToChatCompletions 复用已存在的
  chatUsageFromResponsesUsage helper, 消除两条路径间的重复
- 非 reasoning / 非 audio 上游 (Anthropic, Gemini, gpt-4o) 不填这些
  字段, helper 返回 nil → CompletionTokensDetails 不输出, 对现有响应
  字节级兼容

新增单测:
- TestResponsesToChatCompletions_ReasoningTokens
- TestResponsesToChatCompletions_AllTokenDetailsPassThrough
- TestResponsesToChatCompletions_NoReasoningTokensWhenZero
- TestResponsesEventToChatChunks_CompletedWithReasoningTokens
2026-05-28 00:38:25 +08:00
stabeyandClaude Opus 4.7 89dffdd2e1 fix(apicompat): emit OpenAI-semantic input_tokens when converting Anthropic to Responses
Anthropic Messages reports input_tokens excluding cache_read/cache_creation, but
OpenAI Responses input_tokens is the total including cached tokens. The reverse
converter passed Anthropic's input_tokens straight through, so client-facing
prompt_tokens/input_tokens were short by the cached count and cache_creation
was dropped entirely.

Fix the non-stream path and the streaming state machine to add cache_read +
cache_creation back into input_tokens, and track CacheCreationInputTokens on
the streaming state. Six downstream paths benefit (Anthropic->Responses,
Anthropic->ChatCompletions, Gemini->ChatCompletions, each sync + stream).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 22:36:52 +08:00
shaw f7ac5e5931 fix(openai): preserve chat responses usage billing 2026-05-26 21:33:28 +08:00
wucm667 c4d7edba08 fix(apicompat): map developer role to system 2026-05-21 16:37:05 +08:00
wucm667 cae93ae137 fix(openai): /v1/responses respect force chat completions 2026-05-20 14:17:26 +08:00
wucm667 276b5c7755 fix(apicompat): strip temperature/top_p for reasoning models in Responses conversion
gpt-5.x models served via the OpenAI Responses API reject requests that
include temperature or top_p with:
  {"detail":"Unsupported parameter: temperature"}

This caused ClaudeCode agent/subagent tool requests to fail with a 400
error when an OpenAI group had the Messages-format support enabled.

Root cause: AnthropicToResponses and ChatCompletionsToResponses were
unconditionally forwarding temperature and top_p from the incoming
request to the ResponsesRequest, even though all gpt-5.x reasoning
models reject these sampling parameters.

Fix:
- Add isReasoningModel(model string) bool helper that returns true for
  any model whose name starts with "gpt-5".
- Skip temperature and top_p when converting to ResponsesRequest for
  reasoning models. Non-reasoning models (e.g. gpt-4o) are unaffected.
- ResponsesRequest.Temperature and TopP are already *float64 with
  omitempty, so nil values are safely omitted from the JSON body.

Tests:
- TestAnthropicToResponses_TemperatureStrippedForReasoningModel
- TestAnthropicToResponses_TemperatureStrippedForAllGpt5Variants
- TestChatCompletionsToResponses_TemperatureStrippedForReasoningModel
- TestChatCompletionsToResponses_TemperaturePreservedForNonReasoningModel

Fixes #2487
2026-05-19 20:03:16 +08:00
Wesley Liddick e65fb8b086 Merge pull request #2543 from L494264Tt/fix/deepseek-reasoning-content
fix: preserve DeepSeek reasoning_content in chat compatibility paths
2026-05-19 17:34:58 +08:00
L494264Tt fe3283a1d5 fix: satisfy errcheck for reasoning content conversion 2026-05-19 17:17:39 +08:00
Wesley Liddick 6c8b6843fd Merge pull request #2546 from nanobanana123/fix/anthropic-empty-thinking-sse
fix(apicompat): preserve empty streaming thinking blocks
2026-05-19 17:03:54 +08:00
L494264Tt 6082d02d22 Merge origin/main into fix/deepseek-reasoning-content 2026-05-19 17:00:57 +08:00
Wesley Liddick 23e95b77b7 Merge pull request #2528 from wucm667/fix/openai-responses-null-content
fix(openai): 修复 chat-completions 转 responses 时 content 为 null 导致上游 400
2026-05-19 14:43:55 +08:00
e9a25e7b92 fix(apicompat): preserve empty streaming thinking blocks
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-05-18 15:10:35 +08:00
L494264Tt 1d47fd6300 This preserves DeepSeek reasoning_content across chat compatibility paths.
DeepSeek thinking-mode tool-call conversations may require the assistant
  reasoning_content from previous turns to be sent back in later requests. Without
  preserving it, those conversations can fail or lose reasoning context.

  Changes:
  - Preserve assistant reasoning_content when converting Chat Completions messages
    to Responses input by wrapping it as a thinking block.
  - Add regression coverage for non-streaming DeepSeek responses.
  - Add regression coverage for streaming DeepSeek deltas.
  - Add regression coverage that request-side messages[].reasoning_content is
    passed through with tool calls.

  Tests:
  go test -tags=unit ./internal/pkg/apicompat ./internal/service -run
  'TestChatCompletionsToResponses_AssistantReasoningContentPreserved|
  TestChatCompletionsToResponses_AssistantThinkingTagPreserved|
  TestForwardAsRawChatCompletions_PreservesDeepSeekReasoningContent|
  TestForwardAsRawChatCompletions_ForcesStreamUsageUpstreamAndPassesUsageDownstream
2026-05-18 11:22:23 +08:00
wucm667 df82a3bc69 fix(openai): avoid null content when converting chat-completions to responses
When a chat-completions message has no usable content parts (empty array,
empty text part, or filtered-out image part), marshalChatInputContent
marshalled a nil slice to JSON null. The upstream Responses API rejects a
null content field with HTTP 400. Fall back to an empty string instead.

Fixes #2515
2026-05-17 11:20:05 +08:00
name 0393bd7c82 Fix OpenAI compat usage parsing 2026-05-16 03:03:43 +08:00
lyen1688 0584305e5a feat: improve OpenAI messages compatibility for Claude Code 2026-05-05 19:36:33 +08:00
shaw 72d5ee4cd1 fix: drain OpenAI compat streams for usage 2026-05-03 17:11:27 +08:00
ivanvolt 04b2866f65 fix: use Responses-compatible function tool_choice format 2026-04-28 16:26:09 +08:00