Repository navigation
feat: add OpenAI video provider failover #5117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
3ffc068
e90a2c1
42c4d46
9a3a371
a3ad449
224d358
ae376ab
d7c2d7b
8783484
17aba3d
61e1ddb
bdea10a
93ab7a7
6135dd1
e5500a5
98cb125
4731254
48f8be8
f24287c
0399d58
dbee0f0
e46bd6e
5e06210
51d62fc
b38e056
83b8540
c3eea86
3b8fb58
2c558a2
b4a01f4
909e359
fbed8de
e454011
b3c2ef9
c10ec53
6ccdc9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,3 +35,4 @@ data/ | |
| .test | ||
| token_estimator_test.go | ||
| skills-lock.json | ||
| generated-videos/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,12 +109,27 @@ func VideoProxy(c *gin.Context) { | |
| case constant.ChannelTypeOpenAI, constant.ChannelTypeSora: | ||
| videoURL = fmt.Sprintf("%s/v1/videos/%s/content", baseURL, task.GetUpstreamTaskID()) | ||
| req.Header.Set("Authorization", "Bearer "+channel.Key) | ||
| case constant.ChannelTypeOpenAIVideo: | ||
| videoURL = task.GetResultURL() | ||
| if isHongniaoVideoChannel(baseURL, channel.Other) { | ||
| req.Header.Set("X-API-Key", channel.Key) | ||
| } | ||
|
Comment on lines
+113
to
+123
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use task-scoped API key for OpenAIVideo proxy fetches. This currently uses 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 |
||
| default: | ||
| // Video URL is stored in PrivateData.ResultURL (fallback to FailReason for old data) | ||
| videoURL = task.GetResultURL() | ||
| } | ||
|
|
||
| videoURL = strings.TrimSpace(videoURL) | ||
| 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 | ||
| } | ||
|
Comment on lines
+130
to
+139
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not bypass SSRF validation for rewritten The rewritten URL is still externally fetched; skipping 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 |
||
| if videoURL == "" { | ||
| logger.LogError(c.Request.Context(), fmt.Sprintf("Video URL is empty for task %s", taskID)) | ||
| videoProxyError(c, http.StatusBadGateway, "server_error", "Failed to fetch video content") | ||
|
|
@@ -130,10 +145,12 @@ func VideoProxy(c *gin.Context) { | |
| } | ||
|
|
||
| fetchSetting := system_setting.GetFetchSetting() | ||
| 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 !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 | ||
| } | ||
| } | ||
|
|
||
| req.URL, err = url.Parse(videoURL) | ||
|
|
@@ -171,6 +188,15 @@ func VideoProxy(c *gin.Context) { | |
| } | ||
| } | ||
|
|
||
| func isHongniaoVideoChannel(baseURL, channelOther string) bool { | ||
| baseURL = strings.ToLower(strings.TrimSpace(baseURL)) | ||
| channelOther = strings.ToLower(strings.TrimSpace(channelOther)) | ||
| return strings.Contains(baseURL, "open.hongniaoai.com") || | ||
| strings.Contains(channelOther, "hongniao") || | ||
| strings.Contains(channelOther, "xb-sora") || | ||
| strings.Contains(channelOther, "xbsora") | ||
| } | ||
|
|
||
| func writeVideoDataURL(c *gin.Context, dataURL string) error { | ||
| parts := strings.SplitN(dataURL, ",", 2) | ||
| if len(parts) != 2 { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Over-broad XB-Sora detection can break non-XB video providers.
Classifying any base URL ending with
/v1or/api/v1as XB-Sora will forceX-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