Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion middleware/distributor.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ func getModelRequest(c *gin.Context) (*ModelRequest, bool, error) {
relayMode = relayconstant.RelayModeVideoFetchByID
shouldSelectChannel = false
}
c.Set("relay_mode", relayMode)
if _, ok := c.Get("relay_mode"); !ok {
c.Set("relay_mode", relayMode)
}
} else if strings.HasPrefix(c.Request.URL.Path, "/v1beta/models/") || strings.HasPrefix(c.Request.URL.Path, "/v1/models/") {
// Gemini API 路径处理: /v1beta/models/gemini-2.0-flash:generateContent
relayMode := relayconstant.RelayModeGemini
Expand Down
66 changes: 66 additions & 0 deletions middleware/jimeng_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package middleware

import (
"bytes"
"encoding/json"
"github.com/gin-gonic/gin"
"io"
"net/http"
"one-api/common"
"one-api/constant"
relayconstant "one-api/relay/constant"
)

func JimengRequestConvert() func(c *gin.Context) {
return func(c *gin.Context) {
action := c.Query("Action")
if action == "" {
abortWithOpenAiMessage(c, http.StatusBadRequest, "Action query parameter is required")
return
}

Comment on lines +16 to +21

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Validate Action against allowed values (and optionally Version)

Restrict Action to the official set to fail fast on unsupported requests. Optionally validate Version if the API requires strict versioning.

  action := c.Query("Action")
  if action == "" {
    abortWithOpenAiMessage(c, http.StatusBadRequest, "Action query parameter is required")
    return
  }
+ allowed := map[string]bool{
+   "CVSync2AsyncSubmitTask": true,
+   "CVSync2AsyncGetResult":  true,
+ }
+ if !allowed[action] {
+   abortWithOpenAiMessage(c, http.StatusBadRequest, "unsupported Action")
+   return
+ }
+ // Optionally enforce version compatibility
+ // if v := c.Query("Version"); v != "2022-08-31" {
+ //   abortWithOpenAiMessage(c, http.StatusBadRequest, "unsupported Version")
+ //   return
+ // }
📝 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.

Suggested change
action := c.Query("Action")
if action == "" {
abortWithOpenAiMessage(c, http.StatusBadRequest, "Action query parameter is required")
return
}
action := c.Query("Action")
if action == "" {
abortWithOpenAiMessage(c, http.StatusBadRequest, "Action query parameter is required")
return
}
allowed := map[string]bool{
"CVSync2AsyncSubmitTask": true,
"CVSync2AsyncGetResult": true,
}
if !allowed[action] {
abortWithOpenAiMessage(c, http.StatusBadRequest, "unsupported Action")
return
}
// Optionally enforce version compatibility
// if v := c.Query("Version"); v != "2022-08-31" {
// abortWithOpenAiMessage(c, http.StatusBadRequest, "unsupported Version")
// return
// }
🤖 Prompt for AI Agents
In middleware/jimeng_adapter.go around lines 16 to 21, the Action query
parameter is checked only for presence but not validated against allowed values.
Update the code to define a set or list of allowed Action values and verify that
the provided Action matches one of these. If it does not, respond with an
appropriate error and abort. Optionally, add similar validation for the Version
parameter if strict versioning is required by the API.

// Handle Jimeng official API request
var originalReq map[string]interface{}
if err := common.UnmarshalBodyReusable(c, &originalReq); err != nil {
abortWithOpenAiMessage(c, http.StatusBadRequest, "Invalid request body")
return
}
model, _ := originalReq["req_key"].(string)
prompt, _ := originalReq["prompt"].(string)

unifiedReq := map[string]interface{}{
"model": model,
"prompt": prompt,
"metadata": originalReq,
}

Comment on lines +28 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm mapping of req_key to model

You populate model from req_key. Verify req_key indeed corresponds to the routing model slug used by Distribute/channel selection; otherwise model-based channel selection may fail. If req_key is not a model slug, introduce a mapping or set a stable Jimeng model name.

Would you like me to add a mapping layer or set a default Jimeng model slug?


🏁 Script executed:

#!/bin/bash
# Inspect whether a Jimeng platform/model slug exists and how model is used for channel selection.

rg -n --line-number $'TaskPlatformJimeng|Jimeng' || true
rg -n --line-number $'RelayModeVideoSubmit|RelayModeVideoFetchByID' relay || true
rg -n --line-number $'CoverTaskActionToModelName' one-api | sed -n '1,200p'
rg -n --line-number $'CacheGetRandomSatisfiedChannel|GetModelPrice|FormatMatchingModelName' one-api || true

Length of output: 2939


Ensure req_key Maps to a Valid Jimeng Model Slug
We currently populate the model field directly from originalReq["req_key"] (middleware/jimeng_adapter.go:28–36), but no lookup or transformation is applied. If your internal req_key values don’t exactly match Jimeng’s model slugs, channel selection will break.

Please update one of the following:

  • Introduce a mapping table from your req_key values → Jimeng model names.
  • Provide a sensible default Jimeng model slug when req_key is absent or unrecognized.

[middleware/jimeng_adapter.go:28–36]

🤖 Prompt for AI Agents
In middleware/jimeng_adapter.go around lines 28 to 36, the code assigns the
model field directly from originalReq["req_key"] without validating or mapping
it to a valid Jimeng model slug. To fix this, introduce a mapping from internal
req_key values to Jimeng model slugs and use it to set the model field.
Additionally, provide a sensible default Jimeng model slug if req_key is missing
or unrecognized to ensure channel selection works correctly.

jsonData, err := json.Marshal(unifiedReq)
if err != nil {
abortWithOpenAiMessage(c, http.StatusInternalServerError, "Failed to marshal request body")
return
}

// Update request body
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))
c.Set(common.KeyRequestBody, jsonData)

if image, ok := originalReq["image"]; !ok || image == "" {
c.Set("action", constant.TaskActionTextGenerate)
}
Comment on lines +47 to +49

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Avoid setting a text-generation action for a video task

Setting action to TaskActionTextGenerate may misclassify video submissions and skew action→model resolution. Prefer a video-specific action (e.g., text2video/image2video) if available, or let the adaptor set it during validation.

Please confirm available video action constants and usages:


🏁 Script executed:

#!/bin/bash
rg -n --line-number $'TaskAction.*Video|TaskActionTextGenerate' one-api || true
rg -n --line-number $'CoverTaskActionToModelName\\(.*\\)' one-api || true

Length of output: 247


Here’s a broader search across the entire repo to locate any video‐related action constants and the mapping function:


🏁 Script executed:

#!/bin/bash
# 1. Find where TaskActionTextGenerate is defined or used
rg -n 'TaskActionTextGenerate'

# 2. Look for any TaskAction constants that include “Video”
rg -n 'TaskAction.*Video'

# 3. Locate the CoverTaskActionToModelName function (definition and calls)
rg -n 'CoverTaskActionToModelName'

Length of output: 831


Let’s pull the exact list of TaskAction constants and see how CoverTaskActionToModelName handles them:


🏁 Script executed:

#!/bin/bash
# 1. List all TaskAction constants
rg -n 'TaskAction[A-Za-z0-9_]*' constant/task.go

# 2. Show the body of CoverTaskActionToModelName for mapping logic
rg -n -A20 'func CoverTaskActionToModelName' service/task.go

Length of output: 368


Introduce or handle a video-specific action constant in the adapter

Currently, constant/task.go only defines:

  • TaskActionGenerate = "generate"
  • TaskActionTextGenerate = "textGenerate"

No video-specific action exists, so setting

c.Set("action", constant.TaskActionTextGenerate)

for a payload containing "video" yields "vidu_textgenerate" in

CoverTaskActionToModelName(platform, action)

and will misroute video tasks to text models.

Please update the code as follows:

• In constant/task.go, add a video action constant, for example:

// TaskActionTextToVideo triggers text→video generation
TaskActionTextToVideo = "textToVideo"

• In middleware/jimeng_adapter.go (around lines 47–49), detect video first and set the new constant:

-   if image, ok := originalReq["image"]; !ok || image == "" {
-       c.Set("action", constant.TaskActionTextGenerate)
-   }
+   if _, hasVideo := originalReq["video"]; hasVideo {
+       c.Set("action", constant.TaskActionTextToVideo)
+   } else if image, ok := originalReq["image"]; !ok || image == "" {
+       c.Set("action", constant.TaskActionTextGenerate)
+   }

This ensures CoverTaskActionToModelName produces vidu_textToVideo (or similar) and routes to the correct video model.

🤖 Prompt for AI Agents
In middleware/jimeng_adapter.go around lines 47 to 49, the code sets the action
to TaskActionTextGenerate even when the payload contains a video, causing video
tasks to be misrouted to text models. To fix this, first add a new constant
TaskActionTextToVideo in constant/task.go to represent video-specific actions.
Then update the adapter code to detect if the payload contains a video and set
the action to this new constant instead of TaskActionTextGenerate. This will
ensure CoverTaskActionToModelName returns the correct model name for video
tasks.


c.Request.URL.Path = "/v1/video/generations"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Set platform context to ensure correct adaptor selection

Make the platform explicit so RelayTaskSubmit picks the Jimeng adaptor without relying on heuristics.

   c.Request.URL.Path = "/v1/video/generations"
+  // Ensure adaptor selection picks Jimeng implementation
+  // Note: confirm the constant name in your codebase.
+  // c.Set("platform", string(constant.TaskPlatformJimeng))

Verify the platform constant exists:


🏁 Script executed:

#!/bin/bash
rg -n --line-number $'TaskPlatformJimeng' one-api || true

Length of output: 103


The previous search targeted a non-existent directory. Let’s search the entire repository for the Jimeng constant:


🏁 Script executed:

#!/bin/bash
# 1. Look specifically for TaskPlatformJimeng
rg -n "TaskPlatformJimeng" || echo ">> No exact match for TaskPlatformJimeng"

# 2. List all TaskPlatform constants to find the correct name
rg -n "TaskPlatform" -A 2

Length of output: 4887


Define and apply TaskPlatformJimeng for explicit adaptor selection

Our search indicates TaskPlatformJimeng isn’t defined, so setting it will fail. Please:

  • In constant/task.go, add the new constant:
    type TaskPlatform string
    
    const (
        TaskPlatformSuno         TaskPlatform = "suno"
        TaskPlatformMidjourney                = "mj"
    +   TaskPlatformJimeng       TaskPlatform = "jimeng"
    )
  • In middleware/jimeng_adapter.go, set the platform before rerouting:
        c.Request.URL.Path = "/v1/video/generations"
    +   // Explicitly select the Jimeng adaptor
    +   c.Set("platform", string(constant.TaskPlatformJimeng))

Confirm that "jimeng" matches the intended platform identifier.

🤖 Prompt for AI Agents
In middleware/jimeng_adapter.go at line 51, you set the platform without
defining TaskPlatformJimeng. First, define TaskPlatformJimeng as a constant
string "jimeng" in constant/task.go. Then, in middleware/jimeng_adapter.go
before changing the URL path, assign c.Request.Context or relevant request field
to TaskPlatformJimeng to explicitly mark the platform. This ensures the platform
is correctly identified before rerouting.


if action == "CVSync2AsyncGetResult" {
taskId, ok := originalReq["task_id"].(string)
if !ok || taskId == "" {
abortWithOpenAiMessage(c, http.StatusBadRequest, "task_id is required for CVSync2AsyncGetResult")
return
}
c.Request.URL.Path = "/v1/video/generations/" + taskId
c.Request.Method = http.MethodGet
c.Set("task_id", taskId)
c.Set("relay_mode", relayconstant.RelayModeVideoFetchByID)
}
c.Next()
}
}
3 changes: 3 additions & 0 deletions relay/relay_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ func sunoFetchByIDRespBodyBuilder(c *gin.Context) (respBody []byte, taskResp *dt

func videoFetchByIDRespBodyBuilder(c *gin.Context) (respBody []byte, taskResp *dto.TaskError) {
taskId := c.Param("task_id")
if taskId == "" {
taskId = c.GetString("task_id")
}
userId := c.GetInt("id")

originTask, exist, err := model.GetByTaskId(userId, taskId)
Expand Down
8 changes: 8 additions & 0 deletions router/video-router.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@ func SetVideoRouter(router *gin.Engine) {
klingV1Router.GET("/videos/text2video/:task_id", controller.RelayTask)
klingV1Router.GET("/videos/image2video/:task_id", controller.RelayTask)
}

// Jimeng official API routes - direct mapping to official API format
jimengOfficialGroup := router.Group("jimeng")
jimengOfficialGroup.Use(middleware.JimengRequestConvert(), middleware.TokenAuth(), middleware.Distribute())
{
// Maps to: /?Action=CVSync2AsyncSubmitTask&Version=2022-08-31 and /?Action=CVSync2AsyncGetResult&Version=2022-08-31
jimengOfficialGroup.POST("/", controller.RelayTask)
}
}