feat: support qiniu ai provider#4251
Conversation
Introduce Qiniu (OpenAI-compatible) channel type, enable upstream /v1/models fetching in UI, and add a unit test for model listing. Made-with: Cursor
Replace hardcoded OpenAI/Azure channel type check with the SupportStreamOptions flag so all channels registered in streamSupportedChannels (including Qiniu) correctly preserve StreamOptions in upstream requests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughThis pull request introduces support for a new Qiniu channel type (ID: 58). Changes include backend constant definitions mapping Qiniu to the OpenAI API type, stream options support configuration, upstream model fetching capability, and frontend UI enhancements enabling Qiniu as an OpenAI-compatible channel option. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ast-grep (0.42.1)web/src/components/table/channels/modals/EditChannelModal.jsx[] 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: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@controller/channel_upstream_update_qiniu_test.go`:
- Around line 16-25: The handler goroutine inside srv :=
httptest.NewServer(http.HandlerFunc(...) ) uses t.Fatalf which must not be
called from spawned goroutines; instead return HTTP error responses (e.g.,
http.Error with 400/500) when checks on r.Method, r.URL.Path or
r.Header.Get("Authorization") fail, and capture the actual values (method, path,
auth header) into variables or send them on a channel so the main test goroutine
can perform the assertions after the request completes; replace each t.Fatalf in
the handler with an appropriate http.Error call and move the t.Fatalf/assert
checks into the main test goroutine using the captured values or channel result.
🪄 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: 94c1c68d-cf50-4119-bb6c-4b8e6916e4e6
📒 Files selected for processing (8)
common/api_type.goconstant/channel.gocontroller/channel_upstream_update_qiniu_test.gorelay/channel/openai/adaptor.gorelay/common/relay_info.goweb/src/components/table/channels/modals/EditChannelModal.jsxweb/src/constants/channel.constants.jsweb/src/helpers/render.jsx
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| if r.Method != http.MethodGet { | ||
| t.Fatalf("unexpected method: %s", r.Method) | ||
| } | ||
| if r.URL.Path != "/v1/models" { | ||
| t.Fatalf("unexpected path: %s", r.URL.Path) | ||
| } | ||
| if got := r.Header.Get("Authorization"); got != "Bearer test-key" { | ||
| t.Fatalf("unexpected Authorization header: %q", got) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Go testing package docs: can t.Fatalf (or t.FailNow) be called from a goroutine created by the test, such as an httptest server handler goroutine?
💡 Result:
No. In Go tests, t.Fatalf/t.FailNow (and t.Fatal/t.FailNow/t.SkipNow variants) must be called only from the goroutine running the test function; calling them from other goroutines created by the test (for example, an httptest server handler goroutine) is explicitly disallowed and can lead to race conditions and incorrect behavior. What the docs say: FailNow “stops its execution by calling runtime.Goexit” in the current goroutine, and “FailNow must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test. Calling FailNow does not stop those other goroutines.” The general “test ends when … calls any of … T.FailNow, T.Fatal, T.Fatalf …” plus “must be called only from the goroutine running the Test function” language likewise applies. Safe patterns: - From goroutine(s) spawned during the test, use t.Errorf/t.Fail (non-fatal) or otherwise report via channels/WaitGroups and have the main test goroutine call Fatalf/FailNow. - For httptest handlers, avoid calling t.Fatal/Fatalf directly in the handler; instead signal failure back to the test goroutine (e.g., send an error on a channel), then fail in the test goroutine. If you need, I can show a small channel-based example for httptest handlers.
Citations:
- 1: https://pkg.go.dev/testing@go1.25.6
- 2: https://go.dev/src/testing/testing.go?s=16299%3A16316
- 3: staticcheck: SA2002 Called testing.T.FailNow - expand to include httptest.NewServer dominikh/go-tools#157
- 4: testing: complain loudly during concurrent use of T.FatalX and T.SkipX golang/go#15758
Replace t.Fatalf calls in the handler goroutine with HTTP error responses.
Calling t.Fatalf from within the httptest handler goroutine violates Go testing semantics. Per the testing package documentation, t.Fatalf and t.FailNow must only be called from the test goroutine itself; calling them from spawned goroutines (including handlers) can cause race conditions and unreliable test failures. Return an HTTP error status from the handler and perform assertions in the main test goroutine instead.
Suggested adjustment
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
- t.Fatalf("unexpected method: %s", r.Method)
+ http.Error(w, "unexpected method", http.StatusBadRequest)
+ return
}
if r.URL.Path != "/v1/models" {
- t.Fatalf("unexpected path: %s", r.URL.Path)
+ http.Error(w, "unexpected path", http.StatusBadRequest)
+ return
}
if got := r.Header.Get("Authorization"); got != "Bearer test-key" {
- t.Fatalf("unexpected Authorization header: %q", got)
+ http.Error(w, "unexpected Authorization header", http.StatusUnauthorized)
+ return
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@controller/channel_upstream_update_qiniu_test.go` around lines 16 - 25, The
handler goroutine inside srv := httptest.NewServer(http.HandlerFunc(...) ) uses
t.Fatalf which must not be called from spawned goroutines; instead return HTTP
error responses (e.g., http.Error with 400/500) when checks on r.Method,
r.URL.Path or r.Header.Get("Authorization") fail, and capture the actual values
(method, path, auth header) into variables or send them on a channel so the main
test goroutine can perform the assertions after the request completes; replace
each t.Fatalf in the handler with an appropriate http.Error call and move the
t.Fatalf/assert checks into the main test goroutine using the captured values or
channel result.
📝 变更描述 / Description
本 PR 为 Qiniu(七牛) 增加了一个独立的渠道类型(
type=58),并将其按 OpenAI 兼容供应商接入现有转发链路:后端将ChannelTypeQiniu映射到APITypeOpenAI,因此请求转发与鉴权逻辑复用 OpenAI adaptor(Authorization: Bearer <key>),无需额外的请求体/响应体转换。同时,补齐了“从上游拉取模型列表”的能力:前端将
type=58加入可拉取模型列表的白名单集合,使渠道编辑弹窗可以调用现有的/v1/models拉取流程;后端沿用统一的/v1/models解析逻辑,返回data[].id作为模型列表。新增单元测试使用httptest模拟上游/v1/models,验证请求路径与 Bearer 鉴权头正确,并能正确解析模型 ID。🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
本地测试通过(只跑本次新增覆盖项):
go test ./controller -run Qiniu ok github.com/QuantumNous/new-api/controller 0.456sSummary by CodeRabbit