-
Notifications
You must be signed in to change notification settings - Fork 11.2k
Feature/range price #4459
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
Feature/range price #4459
Changes from all commits
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 | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,9 +3,14 @@ package controller | |||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||
| "errors" | ||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| "github.com/QuantumNous/new-api/middleware" | ||||||||||||||||||||||||||||||||||||||
| "github.com/QuantumNous/new-api/model" | ||||||||||||||||||||||||||||||||||||||
| "github.com/QuantumNous/new-api/relay" | ||||||||||||||||||||||||||||||||||||||
| relaycommon "github.com/QuantumNous/new-api/relay/common" | ||||||||||||||||||||||||||||||||||||||
| "github.com/QuantumNous/new-api/types" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
@@ -54,3 +59,101 @@ func Playground(c *gin.Context) { | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Relay(c, types.RelayFormatOpenAI) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // PlaygroundVideoProxy proxies an authenticated video content request to the upstream. | ||||||||||||||||||||||||||||||||||||||
| // Route: GET /pg/video/:channelId/:videoId/content | ||||||||||||||||||||||||||||||||||||||
| // The backend fetches the video binary with the channel's Bearer token and streams it back. | ||||||||||||||||||||||||||||||||||||||
| func PlaygroundVideoProxy(c *gin.Context) { | ||||||||||||||||||||||||||||||||||||||
| channelIdStr := c.Param("channelId") | ||||||||||||||||||||||||||||||||||||||
| videoId := c.Param("videoId") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if channelIdStr == "" || videoId == "" { | ||||||||||||||||||||||||||||||||||||||
| c.JSON(http.StatusBadRequest, gin.H{"error": "missing channelId or videoId"}) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| channelId, err := strconv.Atoi(channelIdStr) | ||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||
| c.JSON(http.StatusBadRequest, gin.H{"error": "invalid channelId"}) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| channel, err := model.GetChannelById(channelId, true) | ||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||
| c.JSON(http.StatusNotFound, gin.H{"error": "channel not found"}) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+81
to
+85
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. Distinguish "not found" from transient DB errors.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| baseURL := "" | ||||||||||||||||||||||||||||||||||||||
| if channel.BaseURL != nil { | ||||||||||||||||||||||||||||||||||||||
| baseURL = strings.TrimRight(*channel.BaseURL, "/") | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if baseURL == "" { | ||||||||||||||||||||||||||||||||||||||
| c.JSON(http.StatusBadRequest, gin.H{"error": "channel has no base URL"}) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| keys := channel.GetKeys() | ||||||||||||||||||||||||||||||||||||||
| if len(keys) == 0 { | ||||||||||||||||||||||||||||||||||||||
| c.JSON(http.StatusInternalServerError, gin.H{"error": "channel has no API key"}) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| apiKey := keys[0] | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| upstreamURL := fmt.Sprintf("%s/videos/%s/content", baseURL, videoId) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| req, err := http.NewRequestWithContext(c.Request.Context(), http.MethodGet, upstreamURL, nil) | ||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||
| c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create upstream request"}) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| req.Header.Set("Authorization", "Bearer "+apiKey) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| client := &http.Client{} | ||||||||||||||||||||||||||||||||||||||
| resp, err := client.Do(req) | ||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||
| c.JSON(http.StatusBadGateway, gin.H{"error": "upstream request failed: " + err.Error()}) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+112
to
+117
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. Add timeout / safer transport to upstream HTTP client.
Consider sharing a package-level 🛠️ Suggested change- client := &http.Client{}
- resp, err := client.Do(req)
+ // videoProxyClient is a package-level client; consider declaring at file scope.
+ client := &http.Client{
+ Transport: &http.Transport{
+ ResponseHeaderTimeout: 30 * time.Second,
+ IdleConnTimeout: 90 * time.Second,
+ },
+ }
+ resp, err := client.Do(req)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| defer resp.Body.Close() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if resp.StatusCode != http.StatusOK { | ||||||||||||||||||||||||||||||||||||||
| body, _ := io.ReadAll(resp.Body) | ||||||||||||||||||||||||||||||||||||||
| c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), body) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| contentType := resp.Header.Get("Content-Type") | ||||||||||||||||||||||||||||||||||||||
| if contentType == "" { | ||||||||||||||||||||||||||||||||||||||
| contentType = "video/mp4" | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| c.Header("Content-Type", contentType) | ||||||||||||||||||||||||||||||||||||||
| if cl := resp.Header.Get("Content-Length"); cl != "" { | ||||||||||||||||||||||||||||||||||||||
| c.Header("Content-Length", cl) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| c.Header("Cache-Control", "no-store") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| c.Status(http.StatusOK) | ||||||||||||||||||||||||||||||||||||||
| io.Copy(c.Writer, resp.Body) //nolint:errcheck | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
+138
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Confirm router wiring and auth middleware (or absence thereof) on /pg routes.
rg -nP -C5 '"/pg"|/pg/video|/pg/audio|PlaygroundVideoProxy|PlaygroundAudioProxy' --type=goRepository: QuantumNous/new-api Length of output: 5729 🏁 Script executed: #!/bin/bash
# 1. Find where videoId is generated or comes from (especially for Sora)
rg -nP 'videoID|videoId|video_id' controller/playground.go relay/channel/openai/ --type=go -A 2 -B 2 | head -60
# 2. Check for rate limiting implementation
rg -nP 'rate|limit|RateLimit|Limit' router/relay-router.go middleware/ --type=go 2>/dev/null | head -30
# 3. Verify videoId format from OpenAI Sora task/status handling
rg -nP 'type.*Task|TaskResponse|Status' relay/channel/openai/ --type=go | grep -i video | head -20Repository: QuantumNous/new-api Length of output: 5712 🏁 Script executed: #!/bin/bash
# Check OpenAI Sora task structure to understand videoId format
fd -t f 'sora|task' relay/channel/openai/ --type=go | xargs rg -l 'video.*id|Video' --type=go | head -5Repository: QuantumNous/new-api Length of output: 365 🏁 Script executed: #!/bin/bash
# Look for any rate limiting or abuse protection middleware
rg -nP 'Limiter|RateLimit|abuse|throttle|quota' --type=go | grep -i 'middleware\|controller\|handler' | head -20Repository: QuantumNous/new-api Length of output: 2126 🏁 Script executed: #!/bin/bash
# Check main router setup and global middleware
rg -nP 'func.*[Rr]outer|engine.*Use|main.go|router.go' router/ --type=go -B 2 -A 5 | head -100Repository: QuantumNous/new-api Length of output: 3378 🏁 Script executed: #!/bin/bash
# Look for how the main Gin engine is initialized and what global middleware is applied
fd -t f 'main.go' . | xargs cat | head -150Repository: QuantumNous/new-api Length of output: 3916 🏁 Script executed: #!/bin/bash
# Check for any wrapper around pgVideoRouter that might apply middleware globally
rg -nP 'pgVideoRouter|playgroundRouter|Group.*"/pg"' router/relay-router.go -B 5 -A 10Repository: QuantumNous/new-api Length of output: 1197 🌐 Web query:
💡 Result: OpenAI Sora video IDs follow the format "video_" followed by a 64-character hexadecimal string. Example: "video_68d7512d07848190b3e45da0ecbebcde004da08e1e0678d5". This appears to be a custom opaque identifier rather than a standard UUID (which is typically 36 characters with hyphens in canonical form). The hex string provides high entropy (approximately 251 bits, since 64 hex digits = 256 bits minus any potential prefix/checksum), suitable for uniquely identifying billions of videos with negligible collision risk. No official documentation specifies the exact generation method (e.g., SHA-256 hash, truncated UUID, or random hex), but official API examples and references consistently use this prefixed hex format. Citations:
🌐 Web query:
💡 Result: The OpenAI Sora API (Videos API at /v1/videos) returns a video task (job) response containing a unique ID for the video generation job. This ID is used to poll status via GET /videos/{video_id} and download the video via GET /videos/{video_id}/content once completed. The format is a string prefixed with "video_", followed by a 64-character hexadecimal string (32 bytes in hex). Example from official documentation: "video_68d7512d07848190b3e45da0ecbebcde004da08e1e0678d5" Other examples: "video_123", "video_abc123". The full response object is a JSON with fields like: { "id": "video_...", "object": "video", "created_at": 1758941485, "status": "queued", "model": "sora-2-pro", "progress": 0, "seconds": "8", "size": "1280x720" } Citations:
Add rate limiting to The route group Current threat model assumptions are partially undermined:
Recommended fix:
The router comment (line 71) "IDs are unguessable" should be corrected—only 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // PlaygroundAudioProxy serves a cached TTS audio file by its UUID. | ||||||||||||||||||||||||||||||||||||||
| // Route: GET /pg/audio/:audioId | ||||||||||||||||||||||||||||||||||||||
| func PlaygroundAudioProxy(c *gin.Context) { | ||||||||||||||||||||||||||||||||||||||
| audioId := c.Param("audioId") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Strip any file extension from the ID (e.g. "uuid.mp3" → "uuid") | ||||||||||||||||||||||||||||||||||||||
| if idx := strings.LastIndex(audioId, "."); idx != -1 { | ||||||||||||||||||||||||||||||||||||||
| audioId = audioId[:idx] | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| data, contentType, ok := relay.GetCachedAudio(audioId) | ||||||||||||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||||||||||||
| c.JSON(http.StatusNotFound, gin.H{"error": "audio not found or expired"}) | ||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| c.Header("Cache-Control", "no-store") | ||||||||||||||||||||||||||||||||||||||
| c.Data(http.StatusOK, contentType, data) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/QuantumNous/new-api/common" | ||
| "github.com/QuantumNous/new-api/logger" | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| // responseBodyWriter wraps gin.ResponseWriter to capture the response body. | ||
| type responseBodyWriter struct { | ||
| gin.ResponseWriter | ||
| buf *bytes.Buffer | ||
| } | ||
|
|
||
| func (w *responseBodyWriter) Write(b []byte) (int, error) { | ||
| w.buf.Write(b) | ||
| return w.ResponseWriter.Write(b) | ||
| } | ||
|
|
||
| func (w *responseBodyWriter) WriteString(s string) (int, error) { | ||
| w.buf.WriteString(s) | ||
| return w.ResponseWriter.WriteString(s) | ||
| } | ||
|
Comment on lines
+14
to
+27
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. Don't buffer whole responses in this middleware.
Also applies to: 67-87 🤖 Prompt for AI Agents |
||
|
|
||
| // RequestLogger logs the inbound request (URL, headers, body) and the | ||
| // outbound response body when DEBUG=true. | ||
| func RequestLogger() gin.HandlerFunc { | ||
| return func(c *gin.Context) { | ||
| if !common.DebugEnabled { | ||
| c.Next() | ||
| return | ||
| } | ||
|
|
||
| // --- request --- | ||
| var sb strings.Builder | ||
| sb.WriteString("\n========== Inbound Request ==========\n") | ||
| sb.WriteString(fmt.Sprintf("%s %s\n", c.Request.Method, c.Request.URL.String())) | ||
|
|
||
| sb.WriteString("--- Headers ---\n") | ||
| for key, values := range c.Request.Header { | ||
| for _, v := range values { | ||
| if strings.EqualFold(key, "Authorization") || strings.EqualFold(key, "x-api-key") { | ||
| if len(v) > 12 { | ||
| v = v[:12] + "***" | ||
| } | ||
| } | ||
| sb.WriteString(fmt.Sprintf("%s: %s\n", key, v)) | ||
| } | ||
| } | ||
|
|
||
| sb.WriteString("--- Body ---\n") | ||
| bodyStorage, err := common.GetBodyStorage(c) | ||
| if err == nil { | ||
| bodyBytes, err := bodyStorage.Bytes() | ||
| if err == nil { | ||
| sb.Write(bodyBytes) | ||
| sb.WriteString("\n") | ||
| } | ||
| } | ||
| sb.WriteString("=====================================") | ||
| logger.LogDebug(c.Request.Context(), sb.String()) | ||
|
|
||
| // --- wrap response writer to capture output --- | ||
| rbw := &responseBodyWriter{ResponseWriter: c.Writer, buf: &bytes.Buffer{}} | ||
| c.Writer = rbw | ||
|
|
||
| c.Next() | ||
|
|
||
| // --- response --- | ||
| var sb2 strings.Builder | ||
| sb2.WriteString("\n========== Inbound Response ==========\n") | ||
| sb2.WriteString(fmt.Sprintf("Status: %d\n", rbw.Status())) | ||
| sb2.WriteString("--- Body ---\n") | ||
| const maxRespBytes = 4 << 10 // 4 KB | ||
| respBytes := rbw.buf.Bytes() | ||
| if len(respBytes) > maxRespBytes { | ||
| sb2.Write(respBytes[:maxRespBytes]) | ||
| sb2.WriteString(fmt.Sprintf("\n... (truncated, total %d bytes)", len(respBytes))) | ||
| } else { | ||
| sb2.Write(respBytes) | ||
| } | ||
| sb2.WriteString("\n======================================") | ||
| logger.LogDebug(c.Request.Context(), sb2.String()) | ||
|
Comment on lines
+38
to
+87
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. Avoid logging raw request and response payloads. These logs will capture prompts, completions, user content, and any secrets passed in JSON bodies. Even behind 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -202,7 +202,7 @@ func InitDB() (err error) { | |||||
| //_, _ = sqlDB.Exec("ALTER TABLE channels MODIFY model_mapping TEXT;") // TODO: delete this line when most users have upgraded | ||||||
| } | ||||||
| common.SysLog("database migration started") | ||||||
| err = migrateDB() | ||||||
| // err = migrateDB() | ||||||
|
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. Critical: AutoMigrate disabled — fresh installs and schema upgrades will break. Commenting out
This looks like leftover local-debug code that shouldn't be merged. Please restore the migration calls (or, if the intent was to switch to the parallel 🔧 Proposed fix@@ InitDB
common.SysLog("database migration started")
- // err = migrateDB()
+ err = migrateDB()
return err@@ InitLogDB
common.SysLog("database migration started")
- // err = migrateLOGDB()
+ err = migrateLOGDB()
return err📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| return err | ||||||
| } else { | ||||||
| common.FatalLog(err) | ||||||
|
|
@@ -239,7 +239,7 @@ func InitLogDB() (err error) { | |||||
| return nil | ||||||
| } | ||||||
| common.SysLog("database migration started") | ||||||
| err = migrateLOGDB() | ||||||
| // err = migrateLOGDB() | ||||||
| return err | ||||||
| } else { | ||||||
| common.FatalLog(err) | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -292,10 +292,25 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody | |
| if err != nil { | ||
| return nil, fmt.Errorf("get request url failed: %w", err) | ||
| } | ||
|
|
||
| var bodyBytes []byte | ||
| if requestBody != nil { | ||
| bodyBytes, err = io.ReadAll(requestBody) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("read request body failed: %w", err) | ||
| } | ||
| } | ||
|
|
||
| if common2.DebugEnabled { | ||
| println("fullRequestURL:", fullRequestURL) | ||
| var sb strings.Builder | ||
| sb.WriteString("\n========== Upstream Request ==========\n") | ||
| sb.WriteString(fmt.Sprintf("URL: %s %s\n", c.Request.Method, fullRequestURL)) | ||
| sb.WriteString(fmt.Sprintf("Body: %s\n", string(bodyBytes))) | ||
| sb.WriteString("======================================") | ||
| logger.LogDebug(c.Request.Context(), sb.String()) | ||
| } | ||
| req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody) | ||
|
|
||
| req, err := http.NewRequest(c.Request.Method, fullRequestURL, strings.NewReader(string(bodyBytes))) | ||
|
Comment on lines
+296
to
+313
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. Don't buffer every relay body just to support debug logging. Line 296 now reads the full payload even when Possible fix+import "bytes"
+
- var bodyBytes []byte
- if requestBody != nil {
- bodyBytes, err = io.ReadAll(requestBody)
- if err != nil {
- return nil, fmt.Errorf("read request body failed: %w", err)
- }
- }
+ var bodyBytes []byte
+ requestReader := requestBody
+ if common2.DebugEnabled && requestBody != nil {
+ bodyBytes, err = io.ReadAll(requestBody)
+ if err != nil {
+ return nil, fmt.Errorf("read request body failed: %w", err)
+ }
+ requestReader = bytes.NewReader(bodyBytes)
+ }
...
- req, err := http.NewRequest(c.Request.Method, fullRequestURL, strings.NewReader(string(bodyBytes)))
+ req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestReader)🤖 Prompt for AI Agents |
||
| if err != nil { | ||
| return nil, fmt.Errorf("new request failed: %w", err) | ||
| } | ||
|
|
@@ -311,6 +326,25 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody | |
| return nil, err | ||
| } | ||
| applyHeaderOverrideToRequest(req, headerOverride) | ||
|
|
||
| if common2.DebugEnabled { | ||
| var sb strings.Builder | ||
| sb.WriteString("\n========== Upstream Request Headers ==========\n") | ||
| for key, values := range req.Header { | ||
| for _, v := range values { | ||
| if strings.EqualFold(key, "Authorization") { | ||
| // mask the key, only show prefix | ||
| if len(v) > 12 { | ||
| v = v[:12] + "***" | ||
| } | ||
| } | ||
| sb.WriteString(fmt.Sprintf("%s: %s\n", key, v)) | ||
| } | ||
| } | ||
| sb.WriteString("==============================================") | ||
| logger.LogDebug(c.Request.Context(), sb.String()) | ||
| } | ||
|
|
||
| resp, err := doRequest(c, req, info) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("do request failed: %w", err) | ||
|
|
||
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.
Inconsistent case-sensitivity in model classification.
gpt-imageanddall-eare matched against the rawmodelargument, while every other token is matched againstlower. This means"DALL-E-3","DALL-E", or"GPT-Image-1"will not be classified as image-generation models and will be misrouted to/v1/chat/completionsduring channel tests. Uselowerconsistently:🐛 Proposed fix
func isImageOrVideoGenerationModel(model string) bool { lower := strings.ToLower(model) - return strings.Contains(model, "gpt-image") || - strings.Contains(model, "dall-e") || + return strings.Contains(lower, "gpt-image") || + strings.Contains(lower, "dall-e") || strings.Contains(lower, "stable-diffusion") || strings.Contains(lower, "flux") || strings.HasPrefix(lower, "veo-") || strings.HasPrefix(lower, "sora-") || strings.Contains(lower, "imagen") || strings.Contains(lower, "seedream") }🤖 Prompt for AI Agents