feat: 新增 Responses → ChatCompletions 协议降级兼容配置 - #5209
Conversation
新增协议转换引擎,将 /v1/responses 请求转为 ChatCompletions 协议: - ResponsesRequestToChatCompletionsRequest 请求体转换 - ChatCompletionsResponseToResponsesResponse 非流式响应转换 - OaiChatToResponsesStreamHandler 流式 SSE 转换 - responsesViaChatCompletions 编排函数 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 新增 ResponsesToChatCompletionsPolicy 策略结构体 - 新增 ShouldResponsesUseChatCompletionsGlobal 策略评估 - 在 ResponsesHelper 中插入策略检查和降级分发 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- FunctionRequest 新增 Strict 字段用于工具参数透传 - 前端默认主题从 classic 改为 default Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
在系统设置的 Global Model Configuration 中添加协议降级策略配置区块, 支持通过 JSON 配置 enabled、all_channels、channel_ids、model_patterns 等参数 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
更新所有 6 个语言文件,添加 Responses→ChatCompletions 兼容配置 的描述文字和 model_patterns 说明的翻译 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
在 classic 主题的模型设置中添加协议降级策略配置区块, 更新所有 8 个语言文件的翻译 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
为协议转换的核心函数添加了详细的中文注释,包括: - ResponsesRequestToChatCompletionsRequest 请求转换 - ChatCompletionsResponseToResponsesResponse 非流式响应转换 - OaiChatToResponsesStreamHandler 流式 SSE 转换 - responsesViaChatCompletions 编排函数 - toolCallState 流式工具调用追踪 - convertResponsesInputToMessages input→messages 转换 - convertResponsesToolsToChatTools 工具格式转换 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds routing that can downgrade OpenAI Responses requests to chat completions and convert requests/responses bidirectionally, with streaming translation, policy gating, relay orchestration, UI settings, and localization. ChangesResponses→ChatCompletions Downgrade
Sequence DiagramsequenceDiagram
participant Client as Responses API Client
participant Relay as Relay (ResponsesHelper)
participant Converter as ResponsesToChatCompletionsRequest
participant Upstream as OpenAI Chat Completions
participant StreamHandler as OaiChatToResponsesStreamHandler / Converter
Client->>Relay: /v1/responses request
Relay->>Converter: convert to Chat Completions request
Converter->>Upstream: send chat/completions (stream or non-stream)
Upstream->>StreamHandler: stream chunks or final response
StreamHandler->>Client: emit Responses-format events / final response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/default/src/features/system-settings/models/global-settings-card.tsx (1)
98-115:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRestrict policy fields to JSON objects.
jsonStringonly checks that the input parses, so the new policy field will accept values like[],"foo", or1even though the form treats policies as object configs ({}fallback in Line 147 and object examples in the new section). That makes it easy to save an invalid policy and push the failure downstream instead of catching it in the form.Suggested fix
+import { validateJsonString } from './utils' + -const jsonString = z.string().refine((value) => { - const trimmed = value.trim() - if (!trimmed) return true - try { - JSON.parse(trimmed) - return true - } catch { - return false - } -}, 'Invalid JSON format') +const jsonString = z.string().superRefine((value, ctx) => { + const result = validateJsonString(value, { + predicate: (parsed) => + parsed !== null && + typeof parsed === 'object' && + !Array.isArray(parsed), + predicateMessage: 'Policy JSON must be a JSON object', + }) + + if (!result.valid) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: result.message ?? 'Policy JSON must be a JSON object', + }) + } +})🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/default/src/features/system-settings/models/global-settings-card.tsx` around lines 98 - 115, The current jsonString refine only validates that the value parses as JSON, allowing arrays, strings, numbers, etc., so fields like thinking_model_blacklist, chat_completions_to_responses_policy, and responses_to_chat_completions_policy can be non-object values; update jsonString (or create a new jsonObjectString) used in the z.object schema to parse the trimmed value and then ensure the parsed result is a plain object (typeof === 'object', not null, and not Array.isArray(parsed)), returning false otherwise so the schema only accepts JSON objects consistent with the form's expected {} defaults and examples.
🧹 Nitpick comments (1)
web/default/src/i18n/locales/fr.json (1)
679-679: ⚡ Quick winUse hierarchical i18n keys for these new entries.
Line 679 and Lines 1363-1364 introduce sentence-style keys. Please switch these newly added keys to namespaced hierarchical keys (for example,
settings.responsesToChatCompletions.titleandsettings.responsesToChatCompletions.modelPatternsHelp.*) to keep key naming consistent and maintainable.As per coding guidelines: “Use hierarchical and semantically clear translation key names such as
dashboard.overview.titleand maintain naming consistency”.Also applies to: 1363-1364
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/default/src/i18n/locales/fr.json` at line 679, Replace the sentence-style translation keys with hierarchical, namespaced keys: change "Responses -> ChatCompletions Compatibility" to a namespaced key such as settings.responsesToChatCompletions.title and convert the related keys at lines 1363-1364 (e.g., model patterns help entries) to something like settings.responsesToChatCompletions.modelPatternsHelp.*; update the JSON object keys accordingly and ensure any code that references the old strings (key lookups) is updated to use the new keys (search for the exact string "Responses -> ChatCompletions Compatibility" and the two sentence-style keys at 1363-1364 to locate usages).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@relay/channel/openai/chat_to_responses_stream.go`:
- Line 4: Remove the direct encoding/json usage and make tool-call finalization
deterministic: replace the import of "encoding/json" and the call argsJSON, _ :=
json.Marshal(buf.args) with common.Marshal(buf.args) (referencing argsJSON and
buf.args), and in finalizeAllToolCalls ensure you sort the tcBuf slice by each
buf.itemIdx before iterating so emission of
response.function_call_arguments.done and response.output_item.done occurs in
deterministic order consistent with output_index; update the
finalizeAllToolCalls loop to iterate the sorted list instead of ranging the
unsorted map iteration.
- Around line 424-439: finalizeAllToolCalls currently iterates the map tcBuf
which yields nondeterministic order; change finalizeAllToolCalls to collect the
keys (buf.itemIdx or map keys), sort them (ascending by itemIdx/output_index),
then iterate the sorted list and for each index retrieve tcBuf[index], set
outputItems[index].Status = "completed" and call sendResponsesEvent for
"response.function_call_arguments.done" and "response.output_item.done" in that
deterministic order, and finally reset tcBuf = make(map[int]*toolCallState).
In `@relay/responses_handler.go`:
- Around line 81-85: The branch in responses_handler.go that decides between
PostAudioConsumeQuota and PostTextConsumeQuota should stop relying solely on
info.OriginModelName and instead mirror the usage-based audio detection used in
the compatibility handler: check the request/response audio token details (e.g.,
info.AudioTokens > 0) and the audio/text ratio availability (e.g.,
info.AudioTextRatio != nil or >0) before calling
service.PostAudioConsumeQuota(c, info, usage, ""); otherwise call
service.PostTextConsumeQuota(c, info, usage, nil). Replace the current
strings.HasPrefix(info.OriginModelName, "gpt-4o-audio") conditional with that
combined audio-token + ratio check so aliased or future audio-capable models are
correctly billed.
- Around line 74-87: The downgrade branch wrongly applies to compact responses;
update the conditional that routes to responsesViaChatCompletions (the block
calling service.ShouldResponsesUseChatCompletionsGlobal and
responsesViaChatCompletions) to skip when the request is compact by adding an
explicit check excluding RelayModeResponsesCompact (e.g. ensure info.RelayMode
!= RelayModeResponsesCompact or equivalent) so compact `/v1/responses/compact`
callers are not downgraded and still follow the compact payload/billing path
handled later.
In `@relay/responses_via_chat_completions.go`:
- Around line 99-100: The nil-response branch currently calls
types.NewOpenAIError(nil, ...) which causes a panic because NewOpenAIError
dereferences the error; update the resp == nil branch to create and pass a
concrete error (e.g. errors.New("empty response from DoRequest" or similar)
instead of nil when calling types.NewOpenAIError, ensuring the function (where
the check lives) returns types.NewOpenAIError(err, types.ErrorCodeBadResponse,
http.StatusInternalServerError) with that concrete err; reference the resp ==
nil check and the types.NewOpenAIError call to locate the change.
In `@service/openaicompat/chat_to_responses_response.go`:
- Around line 4-10: The converter in chat_to_responses_response.go is using
encoding/json directly and ignores json.Unmarshal errors for msg.ToolCalls;
replace direct json usage with common.Unmarshal/common.Marshal and ensure any
unmarshal/marshal error (especially when decoding msg.ToolCalls) is returned
from the converter so OaiChatToResponsesHandler can surface a bad-response
error; specifically, swap json.Unmarshal/json.Marshal calls to
common.Unmarshal/common.Marshal for msg.ToolCalls and any payload serialization,
check their returned error values, and propagate those errors from the converter
instead of discarding them.
In `@service/openaicompat/responses_to_chat_request.go`:
- Around line 150-153: The code currently drops non-array "input" values by
returning nil,nil when jsonType != "array"; instead, modify the handling in the
function that converts Responses to a Chat request (the block checking jsonType
and variable jsonType) to return a clear error for unsupported input shapes
(HTTP 400 / bad request) rather than silently returning nil. Change the branch
that now does "if jsonType != \"array\" { return nil, nil }" to construct and
return an appropriate error describing the unsupported input type so callers
fail fast with a 400-like validation error.
In `@web/classic/src/pages/Setting/Model/SettingGlobalModel.jsx`:
- Around line 328-330: The helper text passed to extraText for the model pattern
field (the t('model_patterns ...') string in SettingGlobalModel.jsx) uses mixed
examples (deepseek/glm) that conflict with the placeholders elsewhere in the
ChatCompletions→Responses section (which use gpt-*); update the example regexes
in that helper string to be direction-consistent with the placeholders (e.g.,
change ["^deepseek-.*$", "^glm-.*$"] to match the gpt-* style like ["^gpt-.*$",
"^gpt-4.*$"] or vice versa) so admins see consistent examples; edit the t
key/string accordingly in SettingGlobalModel.jsx where extraText is set.
- Around line 411-413: The Tag component in SettingGlobalModel.jsx currently
contains hardcoded Chinese text "测试版"; replace this with a translatable string
by calling the app's localization helper (e.g., use intl.formatMessage({ id:
'setting.beta', defaultMessage: 'Beta' }) or the project's t('setting.beta')
helper) instead of the literal characters, update/create the corresponding
locale key ("setting.beta") in the locale files, and ensure the Tag markup (the
<Tag ...> ... </Tag> instance) uses the translated value so the label renders
correctly for all locales.
---
Outside diff comments:
In `@web/default/src/features/system-settings/models/global-settings-card.tsx`:
- Around line 98-115: The current jsonString refine only validates that the
value parses as JSON, allowing arrays, strings, numbers, etc., so fields like
thinking_model_blacklist, chat_completions_to_responses_policy, and
responses_to_chat_completions_policy can be non-object values; update jsonString
(or create a new jsonObjectString) used in the z.object schema to parse the
trimmed value and then ensure the parsed result is a plain object (typeof ===
'object', not null, and not Array.isArray(parsed)), returning false otherwise so
the schema only accepts JSON objects consistent with the form's expected {}
defaults and examples.
---
Nitpick comments:
In `@web/default/src/i18n/locales/fr.json`:
- Line 679: Replace the sentence-style translation keys with hierarchical,
namespaced keys: change "Responses -> ChatCompletions Compatibility" to a
namespaced key such as settings.responsesToChatCompletions.title and convert the
related keys at lines 1363-1364 (e.g., model patterns help entries) to something
like settings.responsesToChatCompletions.modelPatternsHelp.*; update the JSON
object keys accordingly and ensure any code that references the old strings (key
lookups) is updated to use the new keys (search for the exact string "Responses
-> ChatCompletions Compatibility" and the two sentence-style keys at 1363-1364
to locate usages).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6a161b35-37b1-436d-a667-d44b49490efa
📒 Files selected for processing (31)
common/constants.godto/openai_request.gorelay/channel/openai/chat_to_responses_stream.gorelay/responses_handler.gorelay/responses_via_chat_completions.goservice/openai_chat_responses_compat.goservice/openai_chat_responses_mode.goservice/openaicompat/chat_to_responses_response.goservice/openaicompat/policy.goservice/openaicompat/responses_to_chat_request.gosetting/model_setting/global.goweb/classic/src/i18n/locales/en.jsonweb/classic/src/i18n/locales/fr.jsonweb/classic/src/i18n/locales/ja.jsonweb/classic/src/i18n/locales/ru.jsonweb/classic/src/i18n/locales/vi.jsonweb/classic/src/i18n/locales/zh-CN.jsonweb/classic/src/i18n/locales/zh-TW.jsonweb/classic/src/i18n/locales/zh.jsonweb/classic/src/pages/Setting/Model/SettingGlobalModel.jsxweb/default/src/features/models/components/drawers/model-mutate-drawer.tsxweb/default/src/features/system-settings/models/global-settings-card.tsxweb/default/src/features/system-settings/models/index.tsxweb/default/src/features/system-settings/models/section-registry.tsxweb/default/src/features/system-settings/types.tsweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/fr.jsonweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.jsonweb/default/src/i18n/locales/zh.json
| package openai | ||
|
|
||
| import ( | ||
| "encoding/json" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the target file
FILE="relay/channel/openai/chat_to_responses_stream.go"
test -f "$FILE" && echo "FOUND: $FILE" || { echo "MISSING: $FILE"; exit 1; }
echo "---- imports (first ~80 lines) ----"
sed -n '1,120p' "$FILE" | nl -ba
echo "---- encoding/json usage ----"
rg -n --no-heading 'encoding/json|json\.(Marshal|Unmarshal|MarshalIndent|NewEncoder|NewDecoder|Valid)' "$FILE" || true
echo "---- toolCallState definition and iteration block ----"
rg -n --no-heading 'type toolCallState|toolCallState|tcBuf' "$FILE"
# Print the suspected finalization loop area (around reported lines)
# Use a window around 380-480 to cover both Marshal and iteration.
echo "---- window 380-480 ----"
nl -ba "$FILE" | sed -n '380,480p'Repository: QuantumNous/new-api
Length of output: 216
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="relay/channel/openai/chat_to_responses_stream.go"
test -f "$FILE" && echo "FOUND: $FILE" || { echo "MISSING: $FILE"; exit 1; }
echo "---- imports (first ~120 lines) ----"
sed -n '1,120p' "$FILE" | cat -n
echo "---- encoding/json usage ----"
rg -n --no-heading 'encoding/json|json\.(Marshal|Unmarshal|MarshalIndent|NewEncoder|NewDecoder|Valid)' "$FILE" || true
echo "---- toolCallState definition and iteration markers ----"
rg -n --no-heading 'type toolCallState|toolCallState|tcBuf' "$FILE" || true
echo "---- window 380-480 ----"
sed -n '380,480p' "$FILE" | cat -nRepository: QuantumNous/new-api
Length of output: 9275
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="relay/channel/openai/chat_to_responses_stream.go"
echo "---- other encoding/json usage ----"
rg -n --no-heading 'encoding/json|json\.(Marshal|Unmarshal|MarshalIndent|NewEncoder|NewDecoder|Valid)' "$FILE" || true
echo "---- range loops over tcBuf (map iteration) ----"
rg -n --no-heading 'for\s+_,\s+\w+\s*:=\s*range\s+tcBuf\s*' "$FILE" || true
echo "---- any range loops over maps other than slices ----"
rg -n --no-heading 'for\s+.*:=\s*range\s+\w+' "$FILE" | head -n 80Repository: QuantumNous/new-api
Length of output: 409
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="relay/channel/openai/chat_to_responses_stream.go"
echo "---- tcBuf creation & tcIndex/itemIdx assignment (120-390) ----"
sed -n '120,390p' "$FILE" | cat -n
echo "---- delta.ToolCalls loop (520-590) ----"
sed -n '500,590p' "$FILE" | cat -nRepository: QuantumNous/new-api
Length of output: 10808
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="common/json.go"
test -f "$FILE" && echo "FOUND: $FILE" || { echo "MISSING: $FILE"; exit 1; }
echo "---- common/json.go (first 220 lines) ----"
sed -n '1,220p' "$FILE" | cat -n
echo "---- find Marshal implementation ----"
rg -n --no-heading 'func\s+Marshal\s*\(|func\s+Unmarshal\s*\(|func\s+MarshalJsonStr\s*\(|func\s+DecodeJson\s*\(|func\s+GetJsonType\s*\(' "$FILE" || trueRepository: QuantumNous/new-api
Length of output: 1981
Fix JSON wrapper rule and make tool-call finalization event order deterministic
- Remove the direct
encoding/jsonimport and replaceargsJSON, _ := json.Marshal(buf.args)withcommon.Marshal(buf.args)(lines 4, 412-413). - In
finalizeAllToolCalls,for _, buf := range tcBufiterates a map, soresponse.function_call_arguments.done/response.output_item.donecan be emitted in nondeterministic order vsoutput_index(around line 425); sort tool calls bybuf.itemIdxbefore emitting.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@relay/channel/openai/chat_to_responses_stream.go` at line 4, Remove the
direct encoding/json usage and make tool-call finalization deterministic:
replace the import of "encoding/json" and the call argsJSON, _ :=
json.Marshal(buf.args) with common.Marshal(buf.args) (referencing argsJSON and
buf.args), and in finalizeAllToolCalls ensure you sort the tcBuf slice by each
buf.itemIdx before iterating so emission of
response.function_call_arguments.done and response.output_item.done occurs in
deterministic order consistent with output_index; update the
finalizeAllToolCalls loop to iterate the sorted list instead of ranging the
unsorted map iteration.
| finalizeAllToolCalls := func() { | ||
| for _, buf := range tcBuf { | ||
| item := &outputItems[buf.itemIdx] | ||
| item.Status = "completed" | ||
|
|
||
| sendResponsesEvent("response.function_call_arguments.done", map[string]any{ | ||
| "output_index": buf.itemIdx, | ||
| "call_id": buf.callID, | ||
| "arguments": buf.args, | ||
| }) | ||
| sendResponsesEvent("response.output_item.done", map[string]any{ | ||
| "output_index": buf.itemIdx, | ||
| "item": *item, | ||
| }) | ||
| } | ||
| tcBuf = make(map[int]*toolCallState) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the target file
FILE="relay/channel/openai/chat_to_responses_stream.go"
test -f "$FILE" || { echo "Missing $FILE"; exit 1; }
# Show the relevant section with line numbers (424-460 approximate)
nl -ba "$FILE" | sed -n '380,470p'
# Extract tcBuf declaration and nearby context
rg -n "tcBuf" "$FILE"
echo "---- tcBuf definition sites ----"
# Try to show the lines around the first definition
first_def=$(rg -n "tcBuf\\s*[:=]" "$FILE" | head -n1 | cut -d: -f1 || true)
if [ -n "${first_def:-}" ]; then
start=$((first_def-30)); end=$((first_def+30));
nl -ba "$FILE" | sed -n "${start},${end}p"
fi
# Determine whether finalizeAllToolCalls is called and from where
rg -n "finalizeAllToolCalls" "$FILE"
rg -n "response\\.function_call_arguments\\.done|response\\.output_item\\.done" "$FILE"
# Search repository for ordering expectations (tests/log assertions) around these event names
rg -n "response\\.function_call_arguments\\.done|response\\.output_item\\.done" --glob='**/*.{go,ts,js,py,md}' . | head -n 50Repository: QuantumNous/new-api
Length of output: 106
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="relay/channel/openai/chat_to_responses_stream.go"
test -f "$FILE" || { echo "Missing $FILE"; exit 1; }
# Relevant code window (approx)
cat -n "$FILE" | sed -n '380,470p'
# Find tcBuf definition/usage in the file
echo "---- tcBuf related lines ----"
rg -n "tcBuf" "$FILE"
echo "---- finalizeAllToolCalls related lines ----"
rg -n "finalizeAllToolCalls" "$FILE"
echo "---- emitted event names in the file ----"
rg -n "response\\.function_call_arguments\\.done|response\\.output_item\\.done" "$FILE"
echo "---- repo-wide ordering expectations around emitted events ----"
rg -n "response\\.function_call_arguments\\.done|response\\.output_item\\.done" --glob='**/*.{go,ts,js,py,md}' . | head -n 80Repository: QuantumNous/new-api
Length of output: 4880
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect downstream handling for the done events
FILE="relay/channel/openai/chat_via_responses.go"
test -f "$FILE" || { echo "Missing $FILE"; exit 1; }
echo "---- Inspect chat_via_responses.go around event handling ----"
rg -n "response\\.output_item\\.added|response\\.output_item\\.done|response\\.function_call_arguments\\.done" "$FILE"
echo "---- Context around those cases ----"
# Show a broader window around the switch/case
line=$(rg -n "case \"response\\.output_item\\.added\"|case \"response\\.function_call_arguments\\.done\"" "$FILE" | head -n 1 | cut -d: -f1)
start=$((line-40)); end=$((line+80))
cat -n "$FILE" | sed -n "${start},${end}p"
# Also inspect dto/openai_response.go notes about ordering (if any)
FILE2="dto/openai_response.go"
test -f "$FILE2" && {
echo "---- Inspect dto/openai_response.go around ResponsesOutputTypeItemDone / comments ----"
rg -n "ResponsesOutputTypeItemDone|function_call_arguments.done" "$FILE2"
sed -n '360,430p' "$FILE2" | cat -n
}
# Inspect sendResponsesEvent implementation to see whether it buffers/serializes ordering
FILE3="relay/channel/openai/chat_to_responses_stream.go"
echo "---- Inspect sendResponsesEvent definition/behavior in same file ----"
rg -n "func sendResponsesEvent|sendResponsesEvent\\(" "$FILE3" | head -n 20
defline=$(rg -n "func sendResponsesEvent" "$FILE3" | head -n 1 | cut -d: -f1 || true)
if [ -n "${defline:-}" ]; then
start=$((defline-30)); end=$((defline+80))
cat -n "$FILE3" | sed -n "${start},${end}p"
fiRepository: QuantumNous/new-api
Length of output: 8951
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Repo-wide search for consumers of these event names
rg -n "response\\.function_call_arguments\\.done" --glob='**/*.go' .
rg -n "response\\.output_item\\.done" --glob='**/*.go' .
# Look for any ordering/sorting logic around output_index or done events
rg -n "output_index" --glob='**/*.go' relay dto . | head -n 200
rg -n "Sort\\(|sort\\.Ints|order|ordering|determin" --glob='**/*.go' relay dto . | head -n 200
# Specifically inspect sendResponsesEvent implementation and whether it serializes writes in-order
FILE="relay/channel/openai/chat_to_responses_stream.go"
rg -n "func sendResponsesEvent" "$FILE"
line=$(rg -n "func sendResponsesEvent" "$FILE" | head -n1 | cut -d: -f1)
start=$((line-30)); end=$((line+120))
cat -n "$FILE" | sed -n "${start},${end}p"Repository: QuantumNous/new-api
Length of output: 21994
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="relay/channel/openai/chat_to_responses_stream.go"
test -f "$FILE" || { echo "Missing $FILE"; exit 1; }
echo "---- Imports in chat_to_responses_stream.go ----"
sed -n '1,120p' "$FILE" | cat -n
echo "---- sendResponsesEvent implementation (if in this file) ----"
rg -n "func sendResponsesEvent" "$FILE"
line=$(rg -n "func sendResponsesEvent" "$FILE" | head -n1 | cut -d: -f1 || true)
if [ -n "${line:-}" ]; then
start=$((line-20)); end=$((line+120))
sed -n "${start},${end}p" "$FILE" | cat -n
fi
echo "---- check whether tcBuf is keyed by output index or something deterministic ----"
sed -n '100,180p' "$FILE" | cat -nRepository: QuantumNous/new-api
Length of output: 5316
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Find where sendResponsesEvent is defined
rg -n "func sendResponsesEvent\\b" --glob='**/*.go' .
# 2) Inspect ordering behavior around output_item.done / function_call_arguments.done
FILE="relay/channel/openai/chat_via_responses.go"
test -f "$FILE" || { echo "Missing $FILE"; exit 1; }
# Show the whole switch cases area
rg -n "case \"response\\.output_item\\.added\"|case \"response\\.output_item\\.done\"|case \"response\\.function_call_arguments\\.done\"" "$FILE"
# show around those lines
line1=$(rg -n "case \"response\\.output_item\\.added\", \"response\\.output_item\\.done\"" "$FILE" | head -n1 | cut -d: -f1)
line2=$(rg -n "case \"response\\.function_call_arguments\\.done\"" "$FILE" | head -n1 | cut -d: -f1)
start1=$((line1-40)); end1=$((line1+120))
start2=$((line2-30)); end2=$((line2+80))
cat -n "$FILE" | sed -n "${start1},${end1}p"
echo "----"
cat -n "$FILE" | sed -n "${start2},${end2}p"
# 3) Look for any consumer that depends on event ordering rather than output_index
rg -n "output_item\\.done\"|function_call_arguments\\.done\"" --glob='**/*.go' relay dto .Repository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- sendResponsesEvent definition(s) ----"
rg -n "sendResponsesEvent\\b" --glob='**/*.go' relay/channel/openai/chat_to_responses_stream.go
rg -n "func sendResponsesEvent\\b" --glob='**/*.go' . || true
echo "---- Inspect chat_via_responses.go around cases ----"
FILE="relay/channel/openai/chat_via_responses.go"
test -f "$FILE" || { echo "Missing $FILE"; exit 1; }
# Print a focused window around the case labels
cat -n "$FILE" | sed -n '360,520p'
echo "---- Repo-wide any ordering/buffering logic based on these event names ----"
rg -n "response\\.output_item\\.done|response\\.function_call_arguments\\.done" --glob='**/*.go' . || trueRepository: QuantumNous/new-api
Length of output: 8479
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect sendResponsesEvent closure body
FILE="relay/channel/openai/chat_to_responses_stream.go"
echo "---- sendResponsesEvent closure (lines ~120-210) ----"
sed -n '120,210p' "$FILE" | cat -n
# Repo-wide search for any consumers/tests that may assert ordering
echo "---- occurrences of event names outside the translator (all files) ----"
rg -n "response\\.output_item\\.done|response\\.function_call_arguments\\.done" . | head -n 200
echo "---- check for ordering/sorting expectations near these event names (go tests/other) ----"
rg -n "output_item\\.done|function_call_arguments\\.done" --glob='**/*.{go,ts,js,py,md,json,yml,yaml}' . | head -n 200
echo "---- check for test assertions involving event sequences (look for arrays/snapshots/order keywords near event names) ----"
rg -n "response\\.output_item\\.done.*(require|assert|Equal|diff|snapshot|order|sequence)" --glob='**/*.{go,ts,js,py}' . || trueRepository: QuantumNous/new-api
Length of output: 5739
Finalize tool call events deterministically in finalizeAllToolCalls
finalizeAllToolCalls iterates tcBuf (map[int]*toolCallState), so emission order for response.function_call_arguments.done and response.output_item.done is nondeterministic when multiple tool calls are active; sort the tool calls before sending (e.g., by itemIdx/output_index) to keep a stable event sequence.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@relay/channel/openai/chat_to_responses_stream.go` around lines 424 - 439,
finalizeAllToolCalls currently iterates the map tcBuf which yields
nondeterministic order; change finalizeAllToolCalls to collect the keys
(buf.itemIdx or map keys), sort them (ascending by itemIdx/output_index), then
iterate the sorted list and for each index retrieve tcBuf[index], set
outputItems[index].Status = "completed" and call sendResponsesEvent for
"response.function_call_arguments.done" and "response.output_item.done" in that
deterministic order, and finally reset tcBuf = make(map[int]*toolCallState).
| passThroughGlobal := model_setting.GetGlobalSettings().PassThroughRequestEnabled | ||
| if !passThroughGlobal && !info.ChannelSetting.PassThroughBodyEnabled && | ||
| service.ShouldResponsesUseChatCompletionsGlobal(info.ChannelId, info.ChannelType, info.OriginModelName) { | ||
| usage, newApiErr := responsesViaChatCompletions(c, info, request) | ||
| if newApiErr != nil { | ||
| return newApiErr | ||
| } | ||
| if strings.HasPrefix(info.OriginModelName, "gpt-4o-audio") { | ||
| service.PostAudioConsumeQuota(c, info, usage, "") | ||
| } else { | ||
| service.PostTextConsumeQuota(c, info, usage, nil) | ||
| } | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Exclude RelayModeResponsesCompact from this downgrade branch.
This condition also matches /v1/responses/compact, but responsesViaChatCompletions always writes standard Responses output via OaiChatToResponsesHandler / OaiChatToResponsesStreamHandler. That bypasses the compact-specific response and billing path at Lines 159-173, so compact callers can receive the wrong payload shape.
Suggested fix
- if !passThroughGlobal && !info.ChannelSetting.PassThroughBodyEnabled &&
+ if info.RelayMode != relayconstant.RelayModeResponsesCompact &&
+ !passThroughGlobal && !info.ChannelSetting.PassThroughBodyEnabled &&
service.ShouldResponsesUseChatCompletionsGlobal(info.ChannelId, info.ChannelType, info.OriginModelName) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| passThroughGlobal := model_setting.GetGlobalSettings().PassThroughRequestEnabled | |
| if !passThroughGlobal && !info.ChannelSetting.PassThroughBodyEnabled && | |
| service.ShouldResponsesUseChatCompletionsGlobal(info.ChannelId, info.ChannelType, info.OriginModelName) { | |
| usage, newApiErr := responsesViaChatCompletions(c, info, request) | |
| if newApiErr != nil { | |
| return newApiErr | |
| } | |
| if strings.HasPrefix(info.OriginModelName, "gpt-4o-audio") { | |
| service.PostAudioConsumeQuota(c, info, usage, "") | |
| } else { | |
| service.PostTextConsumeQuota(c, info, usage, nil) | |
| } | |
| return nil | |
| } | |
| passThroughGlobal := model_setting.GetGlobalSettings().PassThroughRequestEnabled | |
| if info.RelayMode != relayconstant.RelayModeResponsesCompact && | |
| !passThroughGlobal && !info.ChannelSetting.PassThroughBodyEnabled && | |
| service.ShouldResponsesUseChatCompletionsGlobal(info.ChannelId, info.ChannelType, info.OriginModelName) { | |
| usage, newApiErr := responsesViaChatCompletions(c, info, request) | |
| if newApiErr != nil { | |
| return newApiErr | |
| } | |
| if strings.HasPrefix(info.OriginModelName, "gpt-4o-audio") { | |
| service.PostAudioConsumeQuota(c, info, usage, "") | |
| } else { | |
| service.PostTextConsumeQuota(c, info, usage, nil) | |
| } | |
| return nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@relay/responses_handler.go` around lines 74 - 87, The downgrade branch
wrongly applies to compact responses; update the conditional that routes to
responsesViaChatCompletions (the block calling
service.ShouldResponsesUseChatCompletionsGlobal and responsesViaChatCompletions)
to skip when the request is compact by adding an explicit check excluding
RelayModeResponsesCompact (e.g. ensure info.RelayMode !=
RelayModeResponsesCompact or equivalent) so compact `/v1/responses/compact`
callers are not downgraded and still follow the compact payload/billing path
handled later.
| if strings.HasPrefix(info.OriginModelName, "gpt-4o-audio") { | ||
| service.PostAudioConsumeQuota(c, info, usage, "") | ||
| } else { | ||
| service.PostTextConsumeQuota(c, info, usage, nil) | ||
| } |
There was a problem hiding this comment.
Use the same usage-based audio billing check here as the compatibility handler.
This new path classifies audio solely from the model-name prefix. relay/compatible_handler.go:80-93 already uses audio token details plus ratio availability, which is safer for aliased/model-mapped names and future audio-capable models. Otherwise this branch can settle audio traffic as text.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@relay/responses_handler.go` around lines 81 - 85, The branch in
responses_handler.go that decides between PostAudioConsumeQuota and
PostTextConsumeQuota should stop relying solely on info.OriginModelName and
instead mirror the usage-based audio detection used in the compatibility
handler: check the request/response audio token details (e.g., info.AudioTokens
> 0) and the audio/text ratio availability (e.g., info.AudioTextRatio != nil or
>0) before calling service.PostAudioConsumeQuota(c, info, usage, ""); otherwise
call service.PostTextConsumeQuota(c, info, usage, nil). Replace the current
strings.HasPrefix(info.OriginModelName, "gpt-4o-audio") conditional with that
combined audio-token + ratio check so aliased or future audio-capable models are
correctly billed.
| if resp == nil { | ||
| return nil, types.NewOpenAIError(nil, types.ErrorCodeBadResponse, http.StatusInternalServerError) |
There was a problem hiding this comment.
Don't pass nil into NewOpenAIError.
If DoRequest returns nil, this branch panics while building the error message because types.NewOpenAIError dereferences err.Error(). Return a concrete error instead.
Suggested fix
+import "fmt"
+
if resp == nil {
- return nil, types.NewOpenAIError(nil, types.ErrorCodeBadResponse, http.StatusInternalServerError)
+ return nil, types.NewOpenAIError(fmt.Errorf("upstream returned nil response"), types.ErrorCodeBadResponse, http.StatusInternalServerError)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@relay/responses_via_chat_completions.go` around lines 99 - 100, The
nil-response branch currently calls types.NewOpenAIError(nil, ...) which causes
a panic because NewOpenAIError dereferences the error; update the resp == nil
branch to create and pass a concrete error (e.g. errors.New("empty response from
DoRequest" or similar) instead of nil when calling types.NewOpenAIError,
ensuring the function (where the check lives) returns types.NewOpenAIError(err,
types.ErrorCodeBadResponse, http.StatusInternalServerError) with that concrete
err; reference the resp == nil check and the types.NewOpenAIError call to locate
the change.
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/QuantumNous/new-api/common" | ||
| "github.com/QuantumNous/new-api/dto" | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Inspect direct encoding/json imports and calls in the touched converter files:"
rg -n -C2 'encoding/json|json\.(Marshal|Unmarshal|NewDecoder|NewEncoder)\s*\(' \
service/openaicompat/chat_to_responses_response.go \
service/openaicompat/responses_to_chat_request.goRepository: QuantumNous/new-api
Length of output: 1608
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Find converter function and how its errors are handled:"
rg -n "chat_to_responses_response\.go|ChatToResponses|converter|toolCalls|OaiChatToResponsesHandler" \
service/openaicompat/chat_to_responses_response.go \
service/openaicompat | head -n 50
echo "Locate where chat_to_responses_response converter is called (handler/stream):"
rg -n "New.*ChatToResponses|OaiChatToResponsesHandler|chat_to_responses_response" \
service | head -n 50
echo "Inspect relay path mentioned in the review comment:"
rg -n "chat_to_responses_stream\.go" -S service/relay || true
rg -n "converter.*Responses|tool_calls|toolCalls|failed to decode tool calls" \
service/relay || trueRepository: QuantumNous/new-api
Length of output: 2216
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Find converter function and how its errors are handled:"
rg -n "ToolCalls|json\.Unmarshal|json\.Marshal|common\.Unmarshal|common\.Marshal|OaiChatToResponsesHandler" \
service/openaicompat/chat_to_responses_response.go \
service/openaicompat | head -n 80
echo "Locate where chat_to_responses_response converter is called (handler/stream):"
rg -n "chat_to_responses_response|OaiChatToResponsesHandler" service | head -n 80
echo "Inspect relay path mentioned in the review comment:"
rg -n "chat_to_responses_stream\.go" -S service/relay || true
rg -n "tool_calls|toolCalls|converter|ChatToResponses" \
service/relay/channel/openai/chat_to_responses_stream.go || trueRepository: QuantumNous/new-api
Length of output: 5067
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Find converter function and how its errors are handled:"
rg -n "ToolCalls|json\.Unmarshal|json\.Marshal|common\.Unmarshal|common\.Marshal|OaiChatToResponsesHandler" \
service/openaicompat/chat_to_responses_response.go \
service/openaicompat | head -n 80
echo "Locate where chat_to_responses_response converter is called (handler/stream):"
rg -n "chat_to_responses_response|OaiChatToResponsesHandler" service | head -n 80
echo "Inspect relay path mentioned in the review comment:"
rg -n "chat_to_responses_stream\.go" -S service/relay || true
rg -n "tool_calls|toolCalls|converter|ChatToResponses" \
service/relay/channel/openai/chat_to_responses_stream.go || trueRepository: QuantumNous/new-api
Length of output: 5067
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== converter function(s) in chat_to_responses_response.go =="
rg -n "func " service/openaicompat/chat_to_responses_response.go
echo
echo "== find references to chat_to_responses_response converter =="
rg -n "chat_to_responses_response" service | head -n 50
echo
echo "== locate call to converter in openai stream =="
rg -n "toResponsesResponse|ResponsesResponse|chat.*responses.*response|tool_calls" service/relay/channel/openai/chat_to_responses_stream.go || true
echo
echo "== show openai stream error handling section =="
# Print a focused window around likely conversion/error code
sed -n '1,220p' service/relay/channel/openai/chat_to_responses_stream.go | nl -ba | sed -n '1,220p' >/tmp/stream_head.txt
head -n 220 /tmp/stream_head.txt
echo
echo "== show the relevant converter implementation =="
wc -l service/openaicompat/chat_to_responses_response.go
sed -n '1,220p' service/openaicompat/chat_to_responses_response.go | nl -ba | sed -n '1,220p'Repository: QuantumNous/new-api
Length of output: 312
🏁 Script executed:
#!/bin/bash
set -u
echo "== find call sites for ChatCompletionsResponseToResponsesResponse =="
rg -n "ChatCompletionsResponseToResponsesResponse\s*\(" service || true
echo
echo "== inspect openai stream file for conversion usage =="
rg -n "ChatCompletionsResponseToResponsesResponse\s*\(|toResponsesResponse|ResponsesResponse" service/relay/channel/openai/chat_to_responses_stream.go || true
echo
echo "== show converter call + error handling area in chat_to_responses_stream.go (first 250 lines with line numbers) =="
nl -ba service/relay/channel/openai/chat_to_responses_stream.go | sed -n '1,250p'
echo
echo "== show rest of stream file around any conversion/errors (250-520) =="
nl -ba service/relay/channel/openai/chat_to_responses_stream.go | sed -n '250,520'
echo
echo "== locate OaiChatToResponsesHandler and inspect its error propagation (search and print relevant windows) =="
rg -n "OaiChatToResponsesHandler" service || true
rg -n "ChatCompletionsResponseToResponsesResponse\s*\(" service/relay/channel/openai || trueRepository: QuantumNous/new-api
Length of output: 1257
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate chat_to_responses_stream.go =="
fd -a "chat_to_responses_stream.go" service . || true
fd -a "*chat_to_responses*stream*.go" service . || true
echo
echo "== locate OaiChatToResponsesHandler =="
rg -n "OaiChatToResponsesHandler" service . || true
echo
echo "== locate ChatCompletionsResponseToResponsesResponse usage (any file) =="
rg -n "ChatCompletionsResponseToResponsesResponse\s*\(" service . || true
echo
echo "== locate openai responses compat/handler file(s) =="
rg -n "responses_compat|toResponsesResponse|OpenAIResponses" service . | head -n 50 || trueRepository: QuantumNous/new-api
Length of output: 9574
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== converter implementation (service/openaicompat/chat_to_responses_response.go) =="
wc -l service/openaicompat/chat_to_responses_response.go
sed -n '1,220p' service/openaicompat/chat_to_responses_response.go | cat -n
echo
echo "== openai stream handler (relay/channel/openai/chat_to_responses_stream.go) =="
wc -l relay/channel/openai/chat_to_responses_stream.go
sed -n '1,220p' relay/channel/openai/chat_to_responses_stream.go | cat -n
echo
echo "== openai stream handler later section around conversion usage (lines 220-320 if exists) =="
sed -n '220,360p' relay/channel/openai/chat_to_responses_stream.go | cat -nRepository: QuantumNous/new-api
Length of output: 18250
Use common JSON wrappers and propagate tool_calls decode failures
In service/openaicompat/chat_to_responses_response.go, this converter imports encoding/json and calls json.Unmarshal/json.Marshal directly (lines 4-10, 71-73, 118-125), discarding the json.Unmarshal error for msg.ToolCalls. Malformed upstream tool_calls can therefore yield a successful but incomplete Responses payload with missing function_call output items, while OaiChatToResponsesHandler only returns an error when the converter returns a non-nil err (relay/channel/openai/chat_to_responses_stream.go around 44-47). Switch to common.Unmarshal/common.Marshal and return the failure so the handler can surface a bad-response error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@service/openaicompat/chat_to_responses_response.go` around lines 4 - 10, The
converter in chat_to_responses_response.go is using encoding/json directly and
ignores json.Unmarshal errors for msg.ToolCalls; replace direct json usage with
common.Unmarshal/common.Marshal and ensure any unmarshal/marshal error
(especially when decoding msg.ToolCalls) is returned from the converter so
OaiChatToResponsesHandler can surface a bad-response error; specifically, swap
json.Unmarshal/json.Marshal calls to common.Unmarshal/common.Marshal for
msg.ToolCalls and any payload serialization, check their returned error values,
and propagate those errors from the converter instead of discarding them.
| // Array of items | ||
| if jsonType != "array" { | ||
| return nil, nil | ||
| } |
There was a problem hiding this comment.
Reject unsupported input shapes instead of dropping them.
This branch silently strips any non-string/non-array input, so an invalid or currently unsupported Responses payload can be downgraded into an empty Chat Completions request instead of failing fast with a clear 400. Returning an error here keeps the contract failure local and avoids confusing upstream errors.
Suggested fix
// Array of items
if jsonType != "array" {
- return nil, nil
+ return nil, fmt.Errorf("unsupported input type: %s", jsonType)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Array of items | |
| if jsonType != "array" { | |
| return nil, nil | |
| } | |
| // Array of items | |
| if jsonType != "array" { | |
| return nil, fmt.Errorf("unsupported input type: %s", jsonType) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@service/openaicompat/responses_to_chat_request.go` around lines 150 - 153,
The code currently drops non-array "input" values by returning nil,nil when
jsonType != "array"; instead, modify the handling in the function that converts
Responses to a Chat request (the block checking jsonType and variable jsonType)
to return a clear error for unsupported input shapes (HTTP 400 / bad request)
rather than silently returning nil. Change the branch that now does "if jsonType
!= \"array\" { return nil, nil }" to construct and return an appropriate error
describing the unsupported input type so callers fail fast with a 400-like
validation error.
| extraText={t( | ||
| 'model_patterns 支持正则匹配模型名称,例如 ["^deepseek-.*$", "^glm-.*$"],留空表示匹配所有模型', | ||
| )} |
There was a problem hiding this comment.
Use direction-consistent model pattern examples in helper text.
Line 329 shows deepseek/glm examples under ChatCompletions→Responses, while the placeholders in this same section use gpt-*. This is confusing for admins copying examples.
Suggested fix
- extraText={t(
- 'model_patterns 支持正则匹配模型名称,例如 ["^deepseek-.*$", "^glm-.*$"],留空表示匹配所有模型',
- )}
+ extraText={t(
+ 'model_patterns 支持正则匹配模型名称,例如 ["^gpt-4o.*$", "^gpt-5.*$"],留空表示匹配所有模型',
+ )}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| extraText={t( | |
| 'model_patterns 支持正则匹配模型名称,例如 ["^deepseek-.*$", "^glm-.*$"],留空表示匹配所有模型', | |
| )} | |
| extraText={t( | |
| 'model_patterns 支持正则匹配模型名称,例如 ["^gpt-4o.*$", "^gpt-5.*$"],留空表示匹配所有模型', | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/classic/src/pages/Setting/Model/SettingGlobalModel.jsx` around lines 328
- 330, The helper text passed to extraText for the model pattern field (the
t('model_patterns ...') string in SettingGlobalModel.jsx) uses mixed examples
(deepseek/glm) that conflict with the placeholders elsewhere in the
ChatCompletions→Responses section (which use gpt-*); update the example regexes
in that helper string to be direction-consistent with the placeholders (e.g.,
change ["^deepseek-.*$", "^glm-.*$"] to match the gpt-* style like ["^gpt-.*$",
"^gpt-4.*$"] or vice versa) so admins see consistent examples; edit the t
key/string accordingly in SettingGlobalModel.jsx where extraText is set.
| <Tag color='orange' size='small'> | ||
| 测试版 | ||
| </Tag> |
There was a problem hiding this comment.
Avoid hardcoded 测试版; keep the new section fully translatable.
Line 412 uses hardcoded Chinese text in a newly added UI block. This breaks locale consistency in non-Chinese classic UI.
Suggested fix
- <Tag color='orange' size='small'>
- 测试版
- </Tag>
+ <Tag color='orange' size='small'>{t('测试版')}</Tag>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Tag color='orange' size='small'> | |
| 测试版 | |
| </Tag> | |
| <Tag color='orange' size='small'>{t('测试版')}</Tag> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/classic/src/pages/Setting/Model/SettingGlobalModel.jsx` around lines 411
- 413, The Tag component in SettingGlobalModel.jsx currently contains hardcoded
Chinese text "测试版"; replace this with a translatable string by calling the app's
localization helper (e.g., use intl.formatMessage({ id: 'setting.beta',
defaultMessage: 'Beta' }) or the project's t('setting.beta') helper) instead of
the literal characters, update/create the corresponding locale key
("setting.beta") in the locale files, and ensure the Tag markup (the <Tag ...>
... </Tag> instance) uses the translated value so the label renders correctly
for all locales.
将 docker-compose.yml 中 PostgreSQL 的数据卷从 Docker 命名卷 (pg_data) 改为宿主机目录挂载 (./postgresql),方便数据备份和管理 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docker-compose.yml`:
- Around line 92-93: Update the commented MySQL section so the instructions
explicitly require uncommenting both the top-level "volumes:" header and the
"mysql_data:" entry (referencing the commented "volumes:" and "mysql_data:"
symbols) to avoid producing invalid YAML; modify the quick-start comment text
that surrounds the commented block to state "Uncomment both 'volumes:' and
'mysql_data:'" and ensure the sample shows both lines uncommented together.
- Line 72: The docker-compose bind-mount entry (-
./postgresql:/var/lib/postgresql/data) requires users to pre-create the
./postgresql directory with permissions/ownership compatible with the Postgres
container user; update the README (or docs) to add a short setup note
instructing users to create ./postgresql and chown it to the Postgres UID/GID
used by the image (e.g., 999:999 or the value of PG_UID/PG_GID) or adjust
permissions accordingly before running docker-compose, and add postgresql/ to
the repository .gitignore so the data directory is not committed; reference the
docker-compose.yml bind-mount line to locate the change, update README.md (or
docs/setup.md) to include the steps, and add the postgresql/ pattern to
.gitignore.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| #volumes: | ||
| # mysql_data: |
There was a problem hiding this comment.
Clarify MySQL enablement comments to include volumes: header.
Line 92 leaves volumes: commented. The quick-start notes should explicitly tell users to uncomment both volumes: and mysql_data:; otherwise they can end up with invalid YAML while following the switch-to-MySQL steps.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docker-compose.yml` around lines 92 - 93, Update the commented MySQL section
so the instructions explicitly require uncommenting both the top-level
"volumes:" header and the "mysql_data:" entry (referencing the commented
"volumes:" and "mysql_data:" symbols) to avoid producing invalid YAML; modify
the quick-start comment text that surrounds the commented block to state
"Uncomment both 'volumes:' and 'mysql_data:'" and ensure the sample shows both
lines uncommented together.
将 PostgreSQL 数据卷从本地目录挂载改回命名卷 pg_data, 恢复 volumes 定义中的 pg_data 条目。`.dockerignore` 已保持原始状态。
默认使用 goproxy.cn 加速 Go 依赖下载,可通过 --build-arg GOPROXY=xxx 自定义覆盖 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
注释掉远程镜像 calciumion/new-api:latest, 改为 build: . 使用本地代码构建,便于开发测试 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
使用 RUN --mount=type=cache,target=/go/pkg/mod 缓存 Go 模块,避免每次构建都重新下载所有依赖 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
使用 --mount=type=cache 缓存 Go 模块和编译产物, GOCACHE 设为独立路径 /cache/go-build,不依赖用户目录 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
将 Go 编译临时目录(GOTMPDIR)也指向 cache mount, 确保所有编译临时文件都不写入容器可写层 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
使用 --mount=type=cache 缓存 Go 模块与编译产物, 设置 GOCACHE/GOTMPDIR 将临时文件定向到缓存挂载点 而非容器可写层,避免根分区磁盘空间不足 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
补充一个我这边用真实 Codex CLI -> 本地 new-api -> DeepSeek 渠道联调时遇到的兼容细节: 当前这里已经支持把 Responses 的 function tool 顶层结构转换成 Chat Completions 的 我这边真实遇到的上游错误是:
本地做法是:
修完后重新跑真实 如果这个 PR 的目标是覆盖真实 Codex 流量,这里建议补一个空 |
当Codex CLI通过Responses API传入type为function但name为空的工具项时, convertResponsesToolsToChatTools会将其透传给上游Chat Completions接口, 导致DeepSeek等上游返回400错误(function.name最小长度为1)。 在转换后对name为空或仅空白字符的function tool直接continue跳过, 确保无效工具不会转发给不兼容的上游。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
好的,已经修复好并提交了 |
|
为何还不合并呀 |
There was a problem hiding this comment.
请不要在 Dockerfile 中引入无意义的部分 (配置镜像/配置缓存) 或者说请不要提交本地测试用的 Dockerfile
|
这个配置放在渠道管理里是不是更好? |
|
{ 不生效呀?? |
|
什么时候会合并这个功能呀? |
再挂一层litellm 😢 |
|
什么时候会合并这个功能呀? |
Based on PR QuantumNous/new-api#5209. Adapted for upstream refactoring (openaicompat -> relayconvert). - developer role auto-mapped to system - non-function tools filtered out - tool parameter schema patched - DeepSeek thinking mode adapted - Streaming and non-streaming response conversion Excludes Dockerfile/docker-compose.yml (fork-specific build config).
|
是的,这个到底什么时候合并到master? |
📝 变更描述
背景
当前项目已支持 ChatCompletions → Responses 的协议兼容配置(将
/v1/chat/completions转为 Responses 协议发给支持 Responses API 的上游),但缺少反向场景:当/v1/responses请求命中了不支持 Responses 协议的渠道(如 DeepSeek、智谱 GLM 等)时,会直接返回错误。
实现方案
在系统设置中新增 Responses → ChatCompletions 兼容配置,与现有的 ChatCompletions → Responses 配置完全对称。用户可通过 JSON 策略配置哪些渠道/模型启用协议降级。
当启用的请求到达时,在
ResponsesHelper中拦截,执行以下转换流程:input、instructions、tools等字段)转为 Chat Completions 格式(messages数组)/v1/chat/completions关键设计要点
pendingCalls+respondedIDs机制,通过call_id匹配 function_call 和 function_call_output,确保复杂的多轮工具调用对话正确转换reasoning_summary_part.added/done、content_part.added/done、function_call_arguments.done等),避免客户端等待
developer角色自动映射为system;非 function 类型工具自动过滤;工具参数 schema 自动修补reasoning_content兜底值,满足 DeepSeek 思考模式的校验要求channel_ids(渠道 ID)、channel_types(渠道类型)、model_patterns(模型正则)三种过滤条件影响范围
变更类型选 ✨ 新功能。
Summary by CodeRabbit
New Features
Chores