feat: add OpenAI video provider failover - #5117
Conversation
|
Looking for one thing? Review this PR in Change Stack to search files, summaries, diffs, and code without losing your place. 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:
WalkthroughThis PR introduces an OpenAI Video channel with multi-provider relay support, image-channel adaptors, and extensive documentation. It adds provider-based task submission/polling, endpoint-type-aware routing, controller model discovery enhancements, and integrates support for multiple upstream platforms (LK888, Runway, XB-Sora, Qilin, BLTCY, Apexer, XGAPI, NewAPI) alongside ListenHub and expanded SiliconFlow image handling. ChangesOpenAI Video channel and provider relay
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 17
🤖 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 `@controller/channel.go`:
- Around line 207-214: The isXBSoraModelsAPI function is over-broadly treating
any URL ending with "/v1" or "/api/v1" as XB-Sora; change it to only detect
XB-Sora via explicit, unique indicators (e.g., substrings "xb-sora", "xbsora",
or known XB hostnames) and remove the generic suffix checks (HasSuffix(baseURL,
"/api/v1") and HasSuffix(baseURL, "/v1")). Update the function isXBSoraModelsAPI
so it normalizes the baseURL as before but returns true only when the baseURL
contains the XB-specific tokens ("xb-sora", "xbsora") or matches a whitelist of
XB-specific domains/hostnames; do not infer XB-Sora from generic "/v1" paths to
avoid misclassifying standard OpenAI-compatible providers.
In `@controller/video_proxy.go`:
- Around line 123-132: The code currently sets skipSSRFValidation = true for
rewritten runway: URLs which bypasses SSRF checks; instead remove or avoid
setting skipSSRFValidation and ensure the rewritten videoURL is validated via
ValidateURLWithFetchSetting (or explicitly call
ValidateURLWithFetchSetting(videoURL, ...) after composing videoURL) so the SSRF
policy still runs; apply the same change to the other runway handling block
around the later lines (the other occurrence uses the same skipSSRFValidation
flag) and keep references to channel.Type, constant.ChannelTypeOpenAIVideo,
videoURL, baseURL, skipSSRFValidation and ValidateURLWithFetchSetting to locate
and update the code.
- Around line 112-116: The OpenAIVideo proxy uses channel.Key but must prefer
the API key captured with the task; update the ChannelTypeOpenAIVideo branch so
when setting X-API-Key (inside the isHongniaoVideoChannel check) you use
task.PrivateData.Key if present/non-empty, falling back to channel.Key
otherwise; locate the case constant.ChannelTypeOpenAIVideo block (videoURL =
task.GetResultURL()) and replace the req.Header.Set("X-API-Key", channel.Key)
call accordingly, referencing task.PrivateData.Key as the primary source.
In `@controller/xb_sora_models_test.go`:
- Line 5: Replace direct uses of encoding/json in
controller/xb_sora_models_test.go with the project's wrapper: remove the
"encoding/json" import and replace any json.Unmarshal/json.Marshal calls with
common.Unmarshal/common.Marshal (or the appropriate wrapper names), e.g., update
test helpers or calls in functions like the test cases that currently call
json.Unmarshal to instead call common.Unmarshal and handle the returned
value/error the same way; ensure you import the common package and run tests to
confirm signatures match.
In `@docs/api-usage.md`:
- Around line 14-23: The file contains committed real-looking bearer tokens
(e.g., the literal string "sk-qZ9riqHVLChWVgVJXEkgYht3kVvVnnXS9xx9hVjzlcG7nKe9"
and the example header "Authorization: Bearer ...") — remove these secrets and
replace them with a placeholder (e.g., "sk-REDACTED" or "<YOUR_API_KEY_HERE>")
in the documentation and any example headers (the shown Authorization header),
then treat the exposed token as compromised: rotate/revoke it immediately and
update any CI/infra that used it; search the repo for the exact token string and
the Authorization example to ensure all occurrences (including the other
referenced spots) are redacted.
In `@docs/deployment.md`:
- Around line 175-176: The docs contain plaintext secrets (e.g., the sshpass
invocation with "-p 'm0HLTSun1xE4'" and exposed admin login credentials) —
remove all hardcoded passwords immediately, rotate/revoke the leaked
credentials, and replace them in the document with non-sensitive placeholders
(e.g., SSH_PASS, ADMIN_USER, ADMIN_PASS) that are retrieved from a secure source
at runtime (environment variables or a secret manager); update the examples to
show how to load secrets (env var or secret manager reference) and add a short
note instructing readers to fetch credentials securely and to confirm credential
rotation and notification to the security team after the leak is remediated.
In `@docs/grok-video-api.md`:
- Line 34: Replace the exposed test API key string
"sk-qZ9riqHVLChWVgVJXEkgYht3kVvVnnXS9xx9hVjzlcG7nKe9" in the documentation table
row labeled "测试 API Key" with a masked placeholder such as `YOUR_API_KEY` (or
`REDACTED`) and remove any real credential text; after changing the doc,
rotate/disable the exposed credential in your secrets manager and update any
environment variables that used it.
In `@docs/openapi/relay.json`:
- Around line 595-603: The schema for the seconds property is too narrow
(currently "seconds" typed as "string"); update the OpenAPI schema for the
seconds field so it accepts both numeric and string shapes used by callers
(e.g., change "type": "string" to a union like "type": ["string","integer"] or
an equivalent oneOf schema, and adjust the example if needed) so generated
clients and validation accept numeric inputs; target the "seconds" property in
docs/openapi/relay.json when making this change.
In `@relay/channel/claude/relay-claude.go`:
- Around line 382-420: The switch on mimeType inside the dto.ContentTypeFile
handling silently drops unsupported MIME types; update the logic in the handler
that builds claudeMediaMessages (the block that calls service.GetBase64Data and
switches on mimeType) to return a descriptive error when mimeType does not match
the handled cases ("application/pdf", "text/*", "image/*") instead of doing
nothing—include the offending mimeType and file.FileName (or other identifier)
in the error to aid debugging and ensure callers get notified of unsupported
file types.
In `@relay/channel/task/openaivideo/apexerapi.go`:
- Around line 35-38: The parsing currently only returns resp.ID and treats
responses with only resp.TaskID as failures; update the submit/parse logic in
apexerapi.go to accept resp.TaskID as a fallback (e.g., if resp.ID == "" use
resp.TaskID), return the non-empty value, and keep the error path that logs the
body when both are empty so valid upstream responses containing only task_id are
handled.
In `@relay/channel/task/openaivideo/newapi.go`:
- Around line 22-31: The response struct newapiQueryResponse is missing the
video URL field so parseQueryResponse never sets TaskInfo.Url; add a Url string
field to newapiQueryResponse with the correct JSON tag matching the API (e.g.,
`Url string `json:"url"` or `json:"result_url"` as the API returns) and update
parseQueryResponse to read that field and assign it to TaskInfo.Url on success
(handle empty/omitted values and preserve existing error/status handling);
repeat the same addition/update for the other similar struct(s) referenced in
the diff (the definitions around lines 58-78) so all success paths populate
TaskInfo.Url.
In `@relay/channel/task/openaivideo/provider.go`:
- Around line 158-163: The isXBSoraBaseURL function is too permissive by
treating any URL ending with "/v1" or "/api/v1" as XB-Sora; narrow the detection
to avoid misrouting by removing the generic HasSuffix checks and instead
validate the actual hostname or path tokens for XB-Sora-specific markers: update
isXBSoraBaseURL to parse the URL (use net/url.Parse), inspect u.Host and u.Path,
and return true only if the host or path contains the explicit identifiers
("xb-sora2", "xbsora2", "xb-sora", "xbsora") or matches a known XB-Sora path
pattern (e.g., path segments like "/xb-sora/v1" or "/api/v1/xb-sora"); ensure
you still call containsAny on the normalized host/path but do not treat a bare
"/v1" or "/api/v1" suffix as sufficient.
In `@relay/channel/task/openaivideo/runway.go`:
- Around line 68-72: The success branch only reads resp.Result.FileURLs[0], so
ti.Url remains empty when upstream returns resp.Result.Files instead; update the
block in runway.go (around the code that sets ti.Url) to fall back to
resp.Result.Files (or the file URL field in those objects) when FileURLs is
empty: check resp.Result.FileURLs first, if empty iterate or check
resp.Result.Files for a non-empty trimmed URL (e.g., Files[0].URL or
equivalent), then set ti.Url = "runway:"+thatURL only when a non-empty value is
found.
In `@relay/channel/task/openaivideo/xb_sora_test.go`:
- Line 5: Remove the direct use of encoding/json in xb_sora_test.go: delete the
"encoding/json" import and replace the call json.Unmarshal(body, &req) with
common.Unmarshal(body, &req); also add/import the package alias common in the
test imports so the call to common.Unmarshal(body, &req) resolves, keeping the
same variables (body, req) and error handling unchanged.
In `@relay/channel/task/openaivideo/xb_sora.go`:
- Line 4: Change xbSoraResponseEnvelope.Data from json.RawMessage to a
wrapper-free type (e.g., any) and remove the direct encoding/json import; in
unwrapXBSoraNestedResponse, when you need to re-process the nested payload,
re-marshal the Data value using common.Marshal and then feed that bytes into
common.Unmarshal for the second-level decode (use the existing
common.Marshal/common.Unmarshal helpers). Update references in
xbSoraResponseEnvelope and unwrapXBSoraNestedResponse accordingly so no direct
encoding/json usage remains.
In `@relay/common/relay_utils.go`:
- Around line 161-167: The code currently only validates req.AspectRatio when
non-empty, allowing frames/components models to proceed without an aspect ratio;
update the isFramesModel branch (where isFramesModel(model) and
hasInputReference are checked) to require req.AspectRatio be present and equal
to either "9:16" or "16:9" — if missing or invalid, return
createTaskError(fmt.Errorf(...), "invalid_aspect_ratio", http.StatusBadRequest,
true); keep the existing TaskActionGenerate assignment logic (action =
constant.TaskActionGenerate) but perform the required-aspect_ratio check before
continuing.
In `@relay/relay_task.go`:
- Around line 440-446: adaptor.FetchTask is currently called with
channelModel.Key which can break for tasks created with rotated or alternate
keys; change the key argument to prefer task.PrivateData.Key (or
task.PrivateData.Get("Key")/the appropriate field) and fall back to
channelModel.Key when PrivateData.Key is empty/nil, so the call to
adaptor.FetchTask(..., key, ...) uses the task-stored key; update the call site
where FetchTask is invoked (referencing adaptor.FetchTask, channelModel.Key, and
task.PrivateData.Key) to compute key := task.PrivateData.Key; if empty use
channelModel.Key, then pass that key into FetchTask alongside
task.GetUpstreamTaskID(), task.Action, channelModel.Other,
task.Properties.OriginModelName, task.Properties.UpstreamModelName, and proxy.
🪄 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: 43ac26be-93da-4e07-b82c-eea2aab97914
📒 Files selected for processing (51)
.gitignorecommon/endpoint_type.gocommon/model.goconstant/channel.goconstant/context_key.gocontroller/channel.gocontroller/channel_upstream_update.gocontroller/task_video.gocontroller/video_proxy.gocontroller/xb_sora_models_test.godocs/api-usage.mddocs/deployment.mddocs/grok-video-api.mddocs/lk888-video-api.mddocs/openapi/relay.jsondocs/sora-video-api.mddto/openai_image.godto/openai_video.gomiddleware/distributor.gomodel/channel_cache.gorelay/channel/claude/relay-claude.gorelay/channel/gemini/constant.gorelay/channel/openai/constant.gorelay/channel/task/openaivideo/adaptor.gorelay/channel/task/openaivideo/apexerapi.gorelay/channel/task/openaivideo/bltcy.gorelay/channel/task/openaivideo/constants.gorelay/channel/task/openaivideo/lk888.gorelay/channel/task/openaivideo/newapi.gorelay/channel/task/openaivideo/provider.gorelay/channel/task/openaivideo/qilin.gorelay/channel/task/openaivideo/qilin_test.gorelay/channel/task/openaivideo/runway.gorelay/channel/task/openaivideo/xb_sora.gorelay/channel/task/openaivideo/xb_sora_test.gorelay/channel/task/openaivideo/xgapi.gorelay/common/relay_info.gorelay/common/relay_utils.gorelay/helper/stream_scanner.gorelay/relay_adaptor.gorelay/relay_task.goservice/task_polling.gosetting/ratio_setting/model_ratio.goweb/classic/src/constants/channel.constants.jsweb/classic/src/helpers/render.jsxweb/classic/src/index.jsxweb/classic/vite.config.jsweb/default/package.jsonweb/default/src/features/channels/constants.tsweb/default/src/features/channels/lib/channel-type-config.tsweb/default/src/features/channels/lib/channel-utils.ts
💤 Files with no reviewable changes (2)
- model/channel_cache.go
- web/classic/src/index.jsx
| func isXBSoraModelsAPI(baseURL string) bool { | ||
| baseURL = strings.ToLower(strings.TrimRight(strings.TrimSpace(baseURL), "/")) | ||
| return strings.Contains(baseURL, "xb-sora2") || | ||
| strings.Contains(baseURL, "xbsora2") || | ||
| strings.Contains(baseURL, "xb-sora") || | ||
| strings.Contains(baseURL, "xbsora") || | ||
| strings.HasSuffix(baseURL, "/api/v1") || | ||
| strings.HasSuffix(baseURL, "/v1") |
There was a problem hiding this comment.
Over-broad XB-Sora detection can break non-XB video providers.
Classifying any base URL ending with /v1 or /api/v1 as XB-Sora will force X-API-Key + XB-specific parsing for standard OpenAI-compatible providers that still require Bearer auth.
💡 Proposed fix
func isXBSoraModelsAPI(baseURL string) bool {
baseURL = strings.ToLower(strings.TrimRight(strings.TrimSpace(baseURL), "/"))
return strings.Contains(baseURL, "xb-sora2") ||
strings.Contains(baseURL, "xbsora2") ||
strings.Contains(baseURL, "xb-sora") ||
- strings.Contains(baseURL, "xbsora") ||
- strings.HasSuffix(baseURL, "/api/v1") ||
- strings.HasSuffix(baseURL, "/v1")
+ strings.Contains(baseURL, "xbsora")
}🤖 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 `@controller/channel.go` around lines 207 - 214, The isXBSoraModelsAPI function
is over-broadly treating any URL ending with "/v1" or "/api/v1" as XB-Sora;
change it to only detect XB-Sora via explicit, unique indicators (e.g.,
substrings "xb-sora", "xbsora", or known XB hostnames) and remove the generic
suffix checks (HasSuffix(baseURL, "/api/v1") and HasSuffix(baseURL, "/v1")).
Update the function isXBSoraModelsAPI so it normalizes the baseURL as before but
returns true only when the baseURL contains the XB-specific tokens ("xb-sora",
"xbsora") or matches a whitelist of XB-specific domains/hostnames; do not infer
XB-Sora from generic "/v1" paths to avoid misclassifying standard
OpenAI-compatible providers.
| case constant.ChannelTypeOpenAIVideo: | ||
| videoURL = task.GetResultURL() | ||
| if isHongniaoVideoChannel(baseURL, channel.Other) { | ||
| req.Header.Set("X-API-Key", channel.Key) | ||
| } |
There was a problem hiding this comment.
Use task-scoped API key for OpenAIVideo proxy fetches.
This currently uses channel.Key, which can diverge from the key used at submission time and break follow-up fetches for existing tasks.
Suggested patch
case constant.ChannelTypeOpenAIVideo:
videoURL = task.GetResultURL()
if isHongniaoVideoChannel(baseURL, channel.Other) {
- req.Header.Set("X-API-Key", channel.Key)
+ apiKey := task.PrivateData.Key
+ if apiKey == "" {
+ apiKey = channel.Key
+ }
+ req.Header.Set("X-API-Key", apiKey)
}Based on learnings: "In async video task flows ... follow-up requests (polling, content download) reuse the authentication context captured at task submission time ... Prefer task.PrivateData.Key when available over channel.Key."
🤖 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 `@controller/video_proxy.go` around lines 112 - 116, The OpenAIVideo proxy uses
channel.Key but must prefer the API key captured with the task; update the
ChannelTypeOpenAIVideo branch so when setting X-API-Key (inside the
isHongniaoVideoChannel check) you use task.PrivateData.Key if present/non-empty,
falling back to channel.Key otherwise; locate the case
constant.ChannelTypeOpenAIVideo block (videoURL = task.GetResultURL()) and
replace the req.Header.Set("X-API-Key", channel.Key) call accordingly,
referencing task.PrivateData.Key as the primary source.
| skipSSRFValidation := false | ||
| if channel.Type == constant.ChannelTypeOpenAIVideo && strings.HasPrefix(videoURL, "runway:") { | ||
| filePath := strings.TrimSpace(strings.TrimPrefix(videoURL, "runway:")) | ||
| if !strings.HasPrefix(filePath, "/files/") { | ||
| videoProxyError(c, http.StatusBadGateway, "server_error", "Invalid Runway file URL") | ||
| return | ||
| } | ||
| videoURL = strings.TrimRight(baseURL, "/") + filePath | ||
| skipSSRFValidation = true | ||
| } |
There was a problem hiding this comment.
Do not bypass SSRF validation for rewritten runway: URLs.
The rewritten URL is still externally fetched; skipping ValidateURLWithFetchSetting creates a policy bypass path.
Suggested patch
- skipSSRFValidation := false
if channel.Type == constant.ChannelTypeOpenAIVideo && strings.HasPrefix(videoURL, "runway:") {
filePath := strings.TrimSpace(strings.TrimPrefix(videoURL, "runway:"))
if !strings.HasPrefix(filePath, "/files/") {
videoProxyError(c, http.StatusBadGateway, "server_error", "Invalid Runway file URL")
return
}
videoURL = strings.TrimRight(baseURL, "/") + filePath
- skipSSRFValidation = true
}
@@
- if !skipSSRFValidation {
- if err := common.ValidateURLWithFetchSetting(videoURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainFilterMode, fetchSetting.IpFilterMode, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts, fetchSetting.ApplyIPFilterForDomain); err != nil {
- logger.LogError(c.Request.Context(), fmt.Sprintf("Video URL blocked for task %s: %v", taskID, err))
- videoProxyError(c, http.StatusForbidden, "server_error", fmt.Sprintf("request blocked: %v", err))
- return
- }
+ if err := common.ValidateURLWithFetchSetting(videoURL, fetchSetting.EnableSSRFProtection, fetchSetting.AllowPrivateIp, fetchSetting.DomainFilterMode, fetchSetting.IpFilterMode, fetchSetting.DomainList, fetchSetting.IpList, fetchSetting.AllowedPorts, fetchSetting.ApplyIPFilterForDomain); err != nil {
+ logger.LogError(c.Request.Context(), fmt.Sprintf("Video URL blocked for task %s: %v", taskID, err))
+ videoProxyError(c, http.StatusForbidden, "server_error", fmt.Sprintf("request blocked: %v", err))
+ return
}Also applies to: 148-153
🤖 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 `@controller/video_proxy.go` around lines 123 - 132, The code currently sets
skipSSRFValidation = true for rewritten runway: URLs which bypasses SSRF checks;
instead remove or avoid setting skipSSRFValidation and ensure the rewritten
videoURL is validated via ValidateURLWithFetchSetting (or explicitly call
ValidateURLWithFetchSetting(videoURL, ...) after composing videoURL) so the SSRF
policy still runs; apply the same change to the other runway handling block
around the later lines (the other occurrence uses the same skipSSRFValidation
flag) and keep references to channel.Type, constant.ChannelTypeOpenAIVideo,
videoURL, baseURL, skipSSRFValidation and ValidateURLWithFetchSetting to locate
and update the code.
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use common.Unmarshal instead of encoding/json in this test file.
This test directly imports/calls encoding/json; switch to the project JSON wrapper for consistency with repository rules.
Suggested patch
import (
"bytes"
- "encoding/json"
"net/http"
"net/http/httptest"
"testing"
+ "github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/model"
"github.com/gin-gonic/gin"
)
@@
- if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
+ if err := common.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("unmarshal response: %v", err)
}As per coding guidelines: "**/*.go: All JSON marshal/unmarshal operations MUST use wrapper functions from common/json.go... Do NOT directly import or call encoding/json in business code."
Also applies to: 100-102
🤖 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 `@controller/xb_sora_models_test.go` at line 5, Replace direct uses of
encoding/json in controller/xb_sora_models_test.go with the project's wrapper:
remove the "encoding/json" import and replace any json.Unmarshal/json.Marshal
calls with common.Unmarshal/common.Marshal (or the appropriate wrapper names),
e.g., update test helpers or calls in functions like the test cases that
currently call json.Unmarshal to instead call common.Unmarshal and handle the
returned value/error the same way; ensure you import the common package and run
tests to confirm signatures match.
| if ti.Status == model.TaskStatusSuccess && resp.Result != nil { | ||
| if len(resp.Result.FileURLs) > 0 && strings.TrimSpace(resp.Result.FileURLs[0]) != "" { | ||
| ti.Url = "runway:" + strings.TrimSpace(resp.Result.FileURLs[0]) | ||
| } | ||
| } |
There was a problem hiding this comment.
Add fallback to result.files when fileUrls is empty.
On Line 68-72, success handling only reads resp.Result.FileURLs[0]. If upstream returns only files, TaskInfo.Url stays empty even on success.
Proposed fix
if ti.Status == model.TaskStatusSuccess && resp.Result != nil {
- if len(resp.Result.FileURLs) > 0 && strings.TrimSpace(resp.Result.FileURLs[0]) != "" {
- ti.Url = "runway:" + strings.TrimSpace(resp.Result.FileURLs[0])
- }
+ if len(resp.Result.FileURLs) > 0 && strings.TrimSpace(resp.Result.FileURLs[0]) != "" {
+ ti.Url = "runway:" + strings.TrimSpace(resp.Result.FileURLs[0])
+ } else if len(resp.Result.Files) > 0 && strings.TrimSpace(resp.Result.Files[0]) != "" {
+ ti.Url = "runway:" + strings.TrimSpace(resp.Result.Files[0])
+ }
}🤖 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/task/openaivideo/runway.go` around lines 68 - 72, The success
branch only reads resp.Result.FileURLs[0], so ti.Url remains empty when upstream
returns resp.Result.Files instead; update the block in runway.go (around the
code that sets ti.Url) to fall back to resp.Result.Files (or the file URL field
in those objects) when FileURLs is empty: check resp.Result.FileURLs first, if
empty iterate or check resp.Result.Files for a non-empty trimmed URL (e.g.,
Files[0].URL or equivalent), then set ti.Url = "runway:"+thatURL only when a
non-empty value is found.
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify there are no direct encoding/json marshal/unmarshal calls in openaivideo tests.
rg -n --type go '^\s*"encoding/json"|json\.(Marshal|Unmarshal|NewDecoder|NewEncoder)\s*\(' relay/channel/task/openaivideoRepository: QuantumNous/new-api
Length of output: 295
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show imports and the json.Unmarshal usage region
sed -n '1,120p' relay/channel/task/openaivideo/xb_sora_test.go
echo "----"
sed -n '200,270p' relay/channel/task/openaivideo/xb_sora_test.go
echo "==== common/json.go ===="
# Locate the wrapper definitions
COMMON_JSON_FILE="$(fd -a -t f 'json.go' common model relay | head -n 20 | rg 'common/json.go$' || true)"
if [ -z "${COMMON_JSON_FILE}" ]; then
# fallback: try common/json.go specifically
if [ -f common/json.go ]; then COMMON_JSON_FILE=common/json.go; fi
fi
if [ -z "${COMMON_JSON_FILE}" ]; then
# last resort: find any json.go under common
COMMON_JSON_FILE="$(fd -a -t f 'json.go' | rg '^common/' | head -n 1 || true)"
fi
echo "Using: ${COMMON_JSON_FILE}"
sed -n '1,220p' "${COMMON_JSON_FILE}"Repository: QuantumNous/new-api
Length of output: 8081
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --type go 'encoding/json|json\.(Marshal|Unmarshal|NewDecoder|NewEncoder)\s*\(' relay/channel/task/openaivideo/xb_sora_test.goRepository: QuantumNous/new-api
Length of output: 140
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check existing usage of common.Unmarshal/common import patterns in the file
rg -n 'github\.com/QuantumNous/new-api/common|common\.Unmarshal|common\.Marshal|common\.DecodeJson' relay/channel/task/openaivideo/xb_sora_test.go || true
# Confirm common import path usage elsewhere (quick sample)
rg -n --type go 'github\.com/QuantumNous/new-api/common' relay/channel/task/openaivideo | head -n 20Repository: QuantumNous/new-api
Length of output: 823
Replace direct encoding/json usage with common.Unmarshal in xb_sora_test.go
- Remove the
encoding/jsonimport and changejson.Unmarshal(body, &req)(line ~241) tocommon.Unmarshal(body, &req).
Proposed patch
import (
"bytes"
- "encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
+ "github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/service"
"github.com/gin-gonic/gin"
)
@@
var req map[string]any
- if err := json.Unmarshal(body, &req); err != nil {
+ if err := common.Unmarshal(body, &req); err != nil {
t.Fatalf("submit body is not json: %v", err)
}🤖 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/task/openaivideo/xb_sora_test.go` at line 5, Remove the direct
use of encoding/json in xb_sora_test.go: delete the "encoding/json" import and
replace the call json.Unmarshal(body, &req) with common.Unmarshal(body, &req);
also add/import the package alias common in the test imports so the call to
common.Unmarshal(body, &req) resolves, keeping the same variables (body, req)
and error handling unchanged.
| package openaivideo | ||
|
|
||
| import ( | ||
| "encoding/json" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="relay/channel/task/openaivideo/xb_sora.go"
echo "== Imports and json usage in $FILE =="
sed -n '1,120p' "$FILE" | cat -n
echo
echo "== Search for encoding/json usage =="
rg -n "encoding/json|json\.Marshal|json\.Unmarshal|json\.NewDecoder|json\.NewEncoder|json\.RawMessage" "$FILE" || true
echo
echo "== Check wrapper availability in common/json.go =="
if [ -f "common/json.go" ]; then
sed -n '1,200p' common/json.go | cat -n
rg -n "func (Marshal|Unmarshal|UnmarshalJsonStr|DecodeJson|GetJsonType)\\b" common/json.go || true
else
echo "common/json.go not found at repo root"
fiRepository: QuantumNous/new-api
Length of output: 6608
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="relay/channel/task/openaivideo/xb_sora.go"
echo "== RawMessage occurrences =="
rg -n "\bRawMessage\b" "$FILE" || true
echo
echo "== json. selector occurrences =="
rg -n "\bjson\." "$FILE" || true
echo
echo "== Show envelope handling / unwrap function area =="
rg -n "type xbSoraResponseEnvelope|unwrapXBSoraNestedResponse|Data:" "$FILE" -n --context=5 || trueRepository: QuantumNous/new-api
Length of output: 1615
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find RawMessage aliases in common/ =="
rg -n "type\s+.*RawMessage|RawMessage\s*=" common || true
rg -n "JsonRawMessageToString|GetJsonType" common/json.go || true
echo
echo "== Inspect unwrapXBSoraNestedResponse implementation (relevant section) =="
sed -n '430,520p' relay/channel/task/openaivideo/xb_sora.go | cat -nRepository: QuantumNous/new-api
Length of output: 2629
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find any common RawMessage alias/type =="
rg -n "package common" -S common || true
rg -n "type\s+\w*RawMessage\s*=" -S . || true
rg -n "type\s+RawMessage\s*=" -S . || true
echo
echo "== Find other business code imports of encoding/json for RawMessage (context) =="
rg -n "\"encoding/json\"" -S relay | head -n 50 || true
rg -n "json\.RawMessage" -S relay | head -n 50 || trueRepository: QuantumNous/new-api
Length of output: 7922
Remove direct encoding/json import from relay/channel/task/openaivideo/xb_sora.go.
xb_sora.go imports encoding/json at line 4 to use json.RawMessage for xbSoraResponseEnvelope.Data, which violates the “no direct encoding/json import in business code” rule. Refactor Data to a wrapper-free type (e.g., any) and in unwrapXBSoraNestedResponse re-marshal via common.Marshal before the second common.Unmarshal.
🤖 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/task/openaivideo/xb_sora.go` at line 4, Change
xbSoraResponseEnvelope.Data from json.RawMessage to a wrapper-free type (e.g.,
any) and remove the direct encoding/json import; in unwrapXBSoraNestedResponse,
when you need to re-process the nested payload, re-marshal the Data value using
common.Marshal and then feed that bytes into common.Unmarshal for the
second-level decode (use the existing common.Marshal/common.Unmarshal helpers).
Update references in xbSoraResponseEnvelope and unwrapXBSoraNestedResponse
accordingly so no direct encoding/json usage remains.
| if isFramesModel(model) { | ||
| if hasInputReference { | ||
| action = constant.TaskActionGenerate | ||
| } | ||
| if req.AspectRatio != "" && !lo.Contains([]string{"9:16", "16:9"}, req.AspectRatio) { | ||
| return createTaskError(fmt.Errorf("aspect_ratio must be 9:16 or 16:9"), "invalid_aspect_ratio", http.StatusBadRequest, true) | ||
| } |
There was a problem hiding this comment.
Require aspect_ratio for frames/components models.
Line 165 only validates when aspect_ratio is non-empty, so frames requests without this field bypass validation and may fail later upstream. Make it required and constrained to 9:16 or 16:9.
Suggested patch
if isFramesModel(model) {
if hasInputReference {
action = constant.TaskActionGenerate
}
- if req.AspectRatio != "" && !lo.Contains([]string{"9:16", "16:9"}, req.AspectRatio) {
+ if req.AspectRatio == "" || !lo.Contains([]string{"9:16", "16:9"}, req.AspectRatio) {
return createTaskError(fmt.Errorf("aspect_ratio must be 9:16 or 16:9"), "invalid_aspect_ratio", http.StatusBadRequest, true)
}
}📝 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.
| if isFramesModel(model) { | |
| if hasInputReference { | |
| action = constant.TaskActionGenerate | |
| } | |
| if req.AspectRatio != "" && !lo.Contains([]string{"9:16", "16:9"}, req.AspectRatio) { | |
| return createTaskError(fmt.Errorf("aspect_ratio must be 9:16 or 16:9"), "invalid_aspect_ratio", http.StatusBadRequest, true) | |
| } | |
| if isFramesModel(model) { | |
| if hasInputReference { | |
| action = constant.TaskActionGenerate | |
| } | |
| if req.AspectRatio == "" || !lo.Contains([]string{"9:16", "16:9"}, req.AspectRatio) { | |
| return createTaskError(fmt.Errorf("aspect_ratio must be 9:16 or 16:9"), "invalid_aspect_ratio", http.StatusBadRequest, true) | |
| } | |
| } |
🤖 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/common/relay_utils.go` around lines 161 - 167, The code currently only
validates req.AspectRatio when non-empty, allowing frames/components models to
proceed without an aspect ratio; update the isFramesModel branch (where
isFramesModel(model) and hasInputReference are checked) to require
req.AspectRatio be present and equal to either "9:16" or "16:9" — if missing or
invalid, return createTaskError(fmt.Errorf(...), "invalid_aspect_ratio",
http.StatusBadRequest, true); keep the existing TaskActionGenerate assignment
logic (action = constant.TaskActionGenerate) but perform the
required-aspect_ratio check before continuing.
| resp, err := adaptor.FetchTask(baseURL, channelModel.Key, map[string]any{ | ||
| "task_id": task.GetUpstreamTaskID(), | ||
| "action": task.Action, | ||
| "task_id": task.GetUpstreamTaskID(), | ||
| "action": task.Action, | ||
| "channel_other": channelModel.Other, | ||
| "origin_model_name": task.Properties.OriginModelName, | ||
| "upstream_model_name": task.Properties.UpstreamModelName, | ||
| }, proxy) |
There was a problem hiding this comment.
Prefer task-stored key when realtime-fetching upstream task status.
Using channelModel.Key here can fail for tasks created with a rotated/alternate key; prefer task.PrivateData.Key with fallback.
Suggested patch
- resp, err := adaptor.FetchTask(baseURL, channelModel.Key, map[string]any{
+ key := task.PrivateData.Key
+ if key == "" {
+ key = channelModel.Key
+ }
+ resp, err := adaptor.FetchTask(baseURL, key, map[string]any{
"task_id": task.GetUpstreamTaskID(),
"action": task.Action,
"channel_other": channelModel.Other,
"origin_model_name": task.Properties.OriginModelName,
"upstream_model_name": task.Properties.UpstreamModelName,
}, proxy)🤖 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/relay_task.go` around lines 440 - 446, adaptor.FetchTask is currently
called with channelModel.Key which can break for tasks created with rotated or
alternate keys; change the key argument to prefer task.PrivateData.Key (or
task.PrivateData.Get("Key")/the appropriate field) and fall back to
channelModel.Key when PrivateData.Key is empty/nil, so the call to
adaptor.FetchTask(..., key, ...) uses the task-stored key; update the call site
where FetchTask is invoked (referencing adaptor.FetchTask, channelModel.Key, and
task.PrivateData.Key) to compute key := task.PrivateData.Key; if empty use
channelModel.Key, then pass that key into FetchTask alongside
task.GetUpstreamTaskID(), task.Action, channelModel.Other,
task.Properties.OriginModelName, task.Properties.UpstreamModelName, and proxy.
Includes: - Channel list query refactoring (buildChannelListQuery) - gjson-based fast model name extraction in distributor - UpstreamRequestBodySize and hasRemovableDisabledField optimization - Claude fcIdx offset fix (concurrent tool call collision) - Multi-key auto-disabled channel cache eviction fix - Frontend dependency upgrades (React 19.2.6, TS 6.0, shadcn 4.8) - Channel editor UX rebuild - Waffo Pancake gateway integration - Security: compliance confirmation for paid features - Performance: println→LogDebug, avoid eager formatting
api-usage.md: - 更新测试 API Key 为无限额度 Key - 验证日期 2026-05-24 → 2026-05-28 - 推荐模型从 4 个扩展到 5 个: +ss-sora-2, +veo3.1-4k - grok-video-3 因 LK888 上游故障从推荐降级为尝试 - 新增全能视频2.0 不可用记录 - 更新所有 task_id、下载大小、Python/cURL 示例 API Key - 路由实务重新排序体现当前可用性优先级 - grok-imagine-1.0-video 标注仅支持 5 种特定尺寸 - Runway 系列标记为未部署,Kling 3.0/O3 待就绪 deployment.md: - 新增 2026-05-28 部署验证记录 (upstream merge/docker/全模型回归) - Runway 适配器状态更新: 当前未配置 sora-video-api.md: xb-sora2 + ss-sora-2 回归验证 grok-video-api.md: grok-imagine-1.0-video 回归验证 + 尺寸约束 lk888-video-api.md: grok-video-3 上游故障状态
There was a problem hiding this comment.
♻️ Duplicate comments (1)
docs/api-usage.md (1)
14-23:⚠️ Potential issue | 🔴 CriticalDuplicate: Exposed API key in documentation.
This was flagged in a previous review. The test API key should be replaced with a placeholder to prevent unauthorized usage and follow security best practices.
Also applies to: 34-35, 43-44, 52-53, 63-64, 72-73, 718-719
🤖 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 `@docs/api-usage.md` around lines 14 - 23, The doc exposes a real test API key string (EW93ybOP6Zr1axAPYNEu8VpehQzdTkZBTATszAGYEDiwpCmJ) and literal Authorization header examples; replace every occurrence of that exact key and the inline header example ("Authorization: Bearer ...") with a clear placeholder like <YOUR_API_KEY> or {TEST_API_KEY} in the table cell (测试 API Key) and in all code blocks and examples (e.g., the "Authorization: Bearer ..." snippet) to avoid leaking credentials; search the file for the exact key string to update all instances.
🤖 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.
Duplicate comments:
In `@docs/api-usage.md`:
- Around line 14-23: The doc exposes a real test API key string
(EW93ybOP6Zr1axAPYNEu8VpehQzdTkZBTATszAGYEDiwpCmJ) and literal Authorization
header examples; replace every occurrence of that exact key and the inline
header example ("Authorization: Bearer ...") with a clear placeholder like
<YOUR_API_KEY> or {TEST_API_KEY} in the table cell (测试 API Key) and in all code
blocks and examples (e.g., the "Authorization: Bearer ..." snippet) to avoid
leaking credentials; search the file for the exact key string to update all
instances.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6a52772f-e7e5-4167-8ee7-9c3a57a026a8
📒 Files selected for processing (29)
common/api_type.gocommon/endpoint_type.gocommon/model.goconstant/api_type.goconstant/channel.gocontroller/channel.gocontroller/task_video.godocs/api-usage.mddocs/deployment.mddocs/grok-video-api.mddocs/lk888-video-api.mddocs/sora-video-api.mdmiddleware/distributor.gomodel/channel_cache.gorelay/channel/claude/relay-claude.gorelay/channel/listenhub/adaptor.gorelay/channel/listenhub/constants.gorelay/channel/listenhub/dto.gorelay/channel/listenhub/image.gorelay/channel/openai/constant.gorelay/common/relay_info.gorelay/helper/stream_scanner.gorelay/relay_adaptor.goservice/task_polling.goweb/classic/src/constants/channel.constants.jsweb/default/package.jsonweb/default/src/features/channels/constants.tsweb/default/src/features/channels/lib/channel-type-config.tsweb/default/src/features/channels/lib/channel-utils.ts
✅ Files skipped from review due to trivial changes (7)
- relay/channel/openai/constant.go
- relay/channel/listenhub/constants.go
- model/channel_cache.go
- docs/sora-video-api.md
- docs/lk888-video-api.md
- docs/grok-video-api.md
- docs/deployment.md
🚧 Files skipped from review as they are similar to previous changes (14)
- common/model.go
- controller/task_video.go
- common/endpoint_type.go
- web/default/package.json
- web/classic/src/constants/channel.constants.js
- service/task_polling.go
- web/default/src/features/channels/lib/channel-type-config.ts
- web/default/src/features/channels/constants.ts
- constant/channel.go
- relay/common/relay_info.go
- relay/helper/stream_scanner.go
- controller/channel.go
- middleware/distributor.go
- relay/channel/claude/relay-claude.go
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/deployment.md (1)
449-459:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winClarify ListenHub status to avoid operational contradiction.
This section says the channel is manually disabled pending
type=59deployment, but other updated docs describe unified-entry verification as already completed. Please add explicit “historical snapshot as-of date” wording or update to current status to prevent wrong rollout decisions.🤖 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 `@docs/deployment.md` around lines 449 - 459, Update the ListenHub status paragraph to remove ambiguity: either add an explicit "historical snapshot as-of" sentence (e.g., "Status as of 2026-06-01: ...") before the sentence mentioning manual disablement of channel `listenhub-images` (ID 12, `priority=120`, `type=59`) so readers know the date of that observation, or replace the sentence with a current-status sentence that clearly states whether `type=59` support has been merged and the channel is now enabled; ensure you reference the channel name `listenhub-images`, the `type=59` deployment dependency, and the Base URL `https://api.marswave.ai/openapi` so the operational state is unambiguous.
🤖 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/siliconflow/dto.go`:
- Line 29: The OutputFormat field is currently a plain string which loses
absent-vs-explicit-empty semantics when re-marshaled; change OutputFormat to a
pointer type (*string) with the existing `json:"output_format,omitempty"` tag in
the DTO struct that declares OutputFormat, and update any code that reads or
forwards this field to handle nil (absent) vs non-nil (explicit) values
appropriately so upstream requests preserve presence semantics.
In `@relay/channel/siliconflow/relay-siliconflow.go`:
- Around line 59-65: The code returns a provider error using resp.StatusCode
which can be 200, causing an error payload with a success HTTP status; update
the error-return in the block that checks siliconflowResp.Images and
siliconflowResp.Message to pass a non-200 status to types.WithOpenAIError (for
example http.StatusBadGateway or map siliconflowResp.Code to a valid HTTP error
code) instead of resp.StatusCode, and ensure you fall back to a safe non-200
value if siliconflowResp.Code is missing/invalid; adjust the call that
constructs the types.OpenAIError (same block referencing siliconflowResp, resp,
and types.WithOpenAIError) accordingly.
- Around line 77-79: The response handling currently calls
info.PriceData.AddOtherRatio("n", float64(len(imageResponse.Data))) which
duplicates image-count billing already added by ImageHelper; remove that call
(or the whole if block using imageResponse.Data) so AddOtherRatio("n") is not
applied here. Locate the usage of imageResponse and the call to
info.PriceData.AddOtherRatio in relay-siliconflow.go and delete or disable that
AddOtherRatio invocation to prevent double-charging.
---
Outside diff comments:
In `@docs/deployment.md`:
- Around line 449-459: Update the ListenHub status paragraph to remove
ambiguity: either add an explicit "historical snapshot as-of" sentence (e.g.,
"Status as of 2026-06-01: ...") before the sentence mentioning manual
disablement of channel `listenhub-images` (ID 12, `priority=120`, `type=59`) so
readers know the date of that observation, or replace the sentence with a
current-status sentence that clearly states whether `type=59` support has been
merged and the channel is now enabled; ensure you reference the channel name
`listenhub-images`, the `type=59` deployment dependency, and the Base URL
`https://api.marswave.ai/openapi` so the operational state is unambiguous.
🪄 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: a8b5c934-58fd-41e3-b421-8a3197aea987
📒 Files selected for processing (8)
.gitignorecommon/model.godocs/api-usage.mddocs/deployment.mdrelay/channel/siliconflow/adaptor.gorelay/channel/siliconflow/constant.gorelay/channel/siliconflow/dto.gorelay/channel/siliconflow/relay-siliconflow.go
✅ Files skipped from review due to trivial changes (1)
- .gitignore
| NumInferenceSteps *uint `json:"num_inference_steps,omitempty"` | ||
| GuidanceScale *float64 `json:"guidance_scale,omitempty"` | ||
| Cfg *float64 `json:"cfg,omitempty"` | ||
| OutputFormat string `json:"output_format,omitempty"` |
There was a problem hiding this comment.
Use pointer type for optional output_format to preserve presence semantics.
OutputFormat is optional request input but is modeled as plain string, which loses absent-vs-explicit-empty distinction during remarshal.
Suggested fix
- OutputFormat string `json:"output_format,omitempty"`
+ OutputFormat *string `json:"output_format,omitempty"`As per coding guidelines, “Optional scalar fields in request structs parsed from client JSON and re-marshaled to upstream providers MUST use pointer types with omitempty tags.”
📝 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.
| OutputFormat string `json:"output_format,omitempty"` | |
| OutputFormat *string `json:"output_format,omitempty"` |
🤖 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/siliconflow/dto.go` at line 29, The OutputFormat field is
currently a plain string which loses absent-vs-explicit-empty semantics when
re-marshaled; change OutputFormat to a pointer type (*string) with the existing
`json:"output_format,omitempty"` tag in the DTO struct that declares
OutputFormat, and update any code that reads or forwards this field to handle
nil (absent) vs non-nil (explicit) values appropriately so upstream requests
preserve presence semantics.
Source: Coding guidelines
| if len(siliconflowResp.Images) == 0 && siliconflowResp.Message != "" { | ||
| return nil, types.WithOpenAIError(types.OpenAIError{ | ||
| Message: siliconflowResp.Message, | ||
| Type: "siliconflow_error", | ||
| Code: fmt.Sprintf("%v", siliconflowResp.Code), | ||
| }, resp.StatusCode) | ||
| } |
There was a problem hiding this comment.
Don’t propagate provider body-errors with HTTP 200 status.
When images is empty and message is present, this is treated as an error, but using resp.StatusCode can keep it at 200. That returns an error payload with success status.
Suggested fix
if len(siliconflowResp.Images) == 0 && siliconflowResp.Message != "" {
+ status := resp.StatusCode
+ if status == http.StatusOK {
+ status = http.StatusBadGateway
+ }
return nil, types.WithOpenAIError(types.OpenAIError{
Message: siliconflowResp.Message,
Type: "siliconflow_error",
Code: fmt.Sprintf("%v", siliconflowResp.Code),
- }, resp.StatusCode)
+ }, status)
}📝 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.
| if len(siliconflowResp.Images) == 0 && siliconflowResp.Message != "" { | |
| return nil, types.WithOpenAIError(types.OpenAIError{ | |
| Message: siliconflowResp.Message, | |
| Type: "siliconflow_error", | |
| Code: fmt.Sprintf("%v", siliconflowResp.Code), | |
| }, resp.StatusCode) | |
| } | |
| if len(siliconflowResp.Images) == 0 && siliconflowResp.Message != "" { | |
| status := resp.StatusCode | |
| if status == http.StatusOK { | |
| status = http.StatusBadGateway | |
| } | |
| return nil, types.WithOpenAIError(types.OpenAIError{ | |
| Message: siliconflowResp.Message, | |
| Type: "siliconflow_error", | |
| Code: fmt.Sprintf("%v", siliconflowResp.Code), | |
| }, status) | |
| } |
🤖 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/siliconflow/relay-siliconflow.go` around lines 59 - 65, The
code returns a provider error using resp.StatusCode which can be 200, causing an
error payload with a success HTTP status; update the error-return in the block
that checks siliconflowResp.Images and siliconflowResp.Message to pass a non-200
status to types.WithOpenAIError (for example http.StatusBadGateway or map
siliconflowResp.Code to a valid HTTP error code) instead of resp.StatusCode, and
ensure you fall back to a safe non-200 value if siliconflowResp.Code is
missing/invalid; adjust the call that constructs the types.OpenAIError (same
block referencing siliconflowResp, resp, and types.WithOpenAIError) accordingly.
| if len(imageResponse.Data) > 0 { | ||
| info.PriceData.AddOtherRatio("n", float64(len(imageResponse.Data))) | ||
| } |
There was a problem hiding this comment.
Remove OtherRatio("n") here to avoid image-count double billing.
This path is standard image relay; adding n again at response handling risks charging n² on priced requests.
Suggested fix
- if len(imageResponse.Data) > 0 {
- info.PriceData.AddOtherRatio("n", float64(len(imageResponse.Data)))
- }Based on learnings, in this repository’s standard image relay path the image count is already embedded by ImageHelper, and adding OtherRatios["n"] causes double-billing.
📝 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.
| if len(imageResponse.Data) > 0 { | |
| info.PriceData.AddOtherRatio("n", float64(len(imageResponse.Data))) | |
| } |
🤖 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/siliconflow/relay-siliconflow.go` around lines 77 - 79, The
response handling currently calls info.PriceData.AddOtherRatio("n",
float64(len(imageResponse.Data))) which duplicates image-count billing already
added by ImageHelper; remove that call (or the whole if block using
imageResponse.Data) so AddOtherRatio("n") is not applied here. Locate the usage
of imageResponse and the call to info.PriceData.AddOtherRatio in
relay-siliconflow.go and delete or disable that AddOtherRatio invocation to
prevent double-charging.
Source: Learnings
…failover # Conflicts: # .gitignore # middleware/distributor.go # web/classic/src/helpers/render.jsx # web/classic/vite.config.js
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
relay/channel/claude/relay-claude.go (1)
455-455:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winReplace direct
encoding/jsoncalls withcommon/json.gowrappers.Three instances violate the coding guideline requiring use of
common.Marshal()andcommon.Unmarshal()for application-level JSON operations. As per coding guidelines, all JSON marshal/unmarshal operations in business code must use the wrapper functions fromcommon/json.go.🔧 Proposed fixes
Line 455: Replace
json.Unmarshalwithcommon.UnmarshalinputObj := make(map[string]any) - if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &inputObj); err != nil { + if err := common.Unmarshal([]byte(toolCall.Function.Arguments), &inputObj); err != nil { common.SysLog("tool call function arguments is not a map[string]any: " + fmt.Sprintf("%v", toolCall.Function.Arguments))Line 586: Replace
json.Marshalwithcommon.Marshalcase "tool_use": - args, _ := json.Marshal(message.Input) + args, _ := common.Marshal(message.Input) tools = append(tools, dto.ToolCallResponse{Line 965: Replace
json.Marshalwithcommon.MarshalopenaiResponse := ResponseClaude2OpenAI(&claudeResponse) openaiResponse.Usage = buildOpenAIStyleUsageFromClaudeUsage(claudeInfo.Usage) - responseData, err = json.Marshal(openaiResponse) + responseData, err = common.Marshal(openaiResponse) if err != nil {Also applies to: 586-586, 965-965
🤖 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/claude/relay-claude.go` at line 455, Replace direct calls to encoding/json with the project wrappers: change json.Unmarshal([]byte(toolCall.Function.Arguments), &inputObj) to use common.Unmarshal and change the two json.Marshal usages at the other sites to common.Marshal; ensure you import the common package if not present and keep the same error handling/variable names (e.g., the Unmarshal call involving toolCall.Function.Arguments and inputObj, and the two Marshal calls at the other locations) so the functions still return the same bytes/error values.Source: Coding guidelines
🤖 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.
Outside diff comments:
In `@relay/channel/claude/relay-claude.go`:
- Line 455: Replace direct calls to encoding/json with the project wrappers:
change json.Unmarshal([]byte(toolCall.Function.Arguments), &inputObj) to use
common.Unmarshal and change the two json.Marshal usages at the other sites to
common.Marshal; ensure you import the common package if not present and keep the
same error handling/variable names (e.g., the Unmarshal call involving
toolCall.Function.Arguments and inputObj, and the two Marshal calls at the other
locations) so the functions still return the same bytes/error values.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ed80abc4-5881-44f2-b9a3-16de3ec8070e
📒 Files selected for processing (8)
.gitignoredocs/deployment.mdmiddleware/distributor.gorelay/channel/claude/relay-claude.gorelay/helper/stream_scanner.gosetting/ratio_setting/model_ratio.goweb/classic/src/index.jsxweb/default/package.json
✅ Files skipped from review due to trivial changes (2)
- .gitignore
- docs/deployment.md
🚧 Files skipped from review as they are similar to previous changes (5)
- web/default/package.json
- web/classic/src/index.jsx
- setting/ratio_setting/model_ratio.go
- relay/helper/stream_scanner.go
- middleware/distributor.go
…failover # Conflicts: # relay/helper/stream_scanner.go
- channels hit with upstream insufficient-credit errors enter a short cooldown (QUOTA_ERROR_COOLDOWN_SECONDS, default 600s) and are skipped by channel selection until expiry; all-cooled fallback keeps service up - quota error keywords moved to operation_setting as the configurable UpstreamQuotaErrorKeywords option so new downstream platforms can be covered without a release - video task relay now fails over on upstream quota errors (400/402/403 with quota keywords) instead of returning the error to the caller - enabling a channel clears its quota cooldown
- new manxiaobaiProvider for api.manxiaobai.online: standard OpenAI Video protocol (POST /v1/videos JSON, string seconds, landscape and portrait size normalization), registered in all provider dispatch points via 'manxiaobai' hint or base URL match - register grok-imagine-video / grok-imagine-video-1.5-preview video models and gpt-image-2-1k/2k/4k image tiers with per-call prices (upstream cost noted in comments)
upstream OpenAI-style error bodies set errorCode to the upstream code (e.g. insufficient_user_quota) instead of bad_response_status_code, so the strict code check prevented quota cooldown from arming; match keywords against message plus error code and exclude skip-retry local errors instead
filter cooled channels within each bucket instead of removing them from the candidate list, so a cooldown armed mid-request no longer shifts bucket indexes and skips the next-best channel on retry; an empty bucket falls through to lower-priority buckets, and the all-cooled case still releases the target bucket as a passive probe
upstream /v1/videos JSON parsing drops the model field (Field required from its grok backend); the documented multipart form path works, so convert JSON bodies to form data like other form-based providers
standard OpenAI Video upstreams like manxiaobai return no direct link
in poll responses; fall back to GET {base}/v1/videos/{id}/content with
the channel bearer key instead of failing with an empty video URL
when a task succeeds without an upstream direct link, ResultURL holds this service's own proxy address; treat it like an empty url and fetch from the upstream auth content endpoint instead of fetching ourselves
- api-usage: bump last-verified to 2026-06-11, update gpt-image-2 and gemini dash-name routing chains with manxiaobai fallback, add new model prices and upstream costs, record grok-imagine-video task verification and download stats - deployment: mark manxiaobai section activated, complete local change inventory with cooldown/quota/provider files, note 1.5-preview reference-image pre-upload as pending work - failover review: close P1-4 reference-image single point, count manxiaobai as the 8th video provider
逐个实测全部下游中转站的余额接口(详见 docs/channel-balance-query.md), 实现按 base_url/Other 路由的余额 provider 注册表(先于 channel.Type switch), 三档语义: - balance(真实余额):lk888 /v1/skills/balance、listenhub /openapi/v1/user/subscription、siliconflow - spend_only(仅累计消费):new-api 套壳站无限额度 key 只能查 /v1/dashboard/billing/usage; 配 setting.balance_query.mode=newapi_console(账密)可登录拿真实钱包余额 - console_only:hongniao 等仅 web 控制台可查 新增聚合接口 GET /api/channel/balance_overview(AdminAuth):按 (base_url,key) 去重, 一次查出所有下游余额;?cached=true 只读存储、?include_disabled=true 含禁用渠道。 单渠道查询响应增加 kind/unit/used/remaining/provider;结果落渠道 OtherInfo。 前端:余额列按三档区分展示(剩余/⚠仅消费/仅控制台)+ 单位; EditChannel 增加“下游余额查询”配置表单(模式/账密/累计充值)。 附带修复 relay_retry_test.go 既有的未使用变量编译错误。
51fdfc5 to
2b6f1df
Compare
Summary
Verification
Summary by CodeRabbit
New Features
Documentation