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
17 changes: 17 additions & 0 deletions model/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ import (

type TaskStatus string

func (t TaskStatus) ToVideoStatus() string {
var status string
switch t {
case TaskStatusQueued, TaskStatusSubmitted:
status = commonRelay.VideoStatusQueued
case TaskStatusInProgress:
status = commonRelay.VideoStatusInProgress
case TaskStatusSuccess:
status = commonRelay.VideoStatusCompleted
case TaskStatusFailure:
status = commonRelay.VideoStatusFailed
default:
status = commonRelay.VideoStatusUnknown // Default fallback
}
return status
}
Comment on lines +14 to +29

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.

⚠️ Potential issue | 🟠 Major

Map NOT_START to a valid video status

InitTask initializes tasks with TaskStatusNotStart, but ToVideoStatus() currently drops that into the default branch, returning "unknown". Downstream OpenAI-compatible clients expect queued/in-progress/etc.; returning unknown for every freshly created task is misleading and breaks status-based flows. Please map TaskStatusNotStart (and optionally any other pre-start aliases) to VideoStatusQueued.

-	switch t {
-	case TaskStatusQueued, TaskStatusSubmitted:
+	switch t {
+	case TaskStatusNotStart, TaskStatusQueued, TaskStatusSubmitted:
📝 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
func (t TaskStatus) ToVideoStatus() string {
var status string
switch t {
case TaskStatusQueued, TaskStatusSubmitted:
status = commonRelay.VideoStatusQueued
case TaskStatusInProgress:
status = commonRelay.VideoStatusInProgress
case TaskStatusSuccess:
status = commonRelay.VideoStatusCompleted
case TaskStatusFailure:
status = commonRelay.VideoStatusFailed
default:
status = commonRelay.VideoStatusUnknown // Default fallback
}
return status
}
func (t TaskStatus) ToVideoStatus() string {
var status string
switch t {
case TaskStatusNotStart, TaskStatusQueued, TaskStatusSubmitted:
status = commonRelay.VideoStatusQueued
case TaskStatusInProgress:
status = commonRelay.VideoStatusInProgress
case TaskStatusSuccess:
status = commonRelay.VideoStatusCompleted
case TaskStatusFailure:
status = commonRelay.VideoStatusFailed
default:
status = commonRelay.VideoStatusUnknown // Default fallback
}
return status
}
🤖 Prompt for AI Agents
In model/task.go around lines 13 to 28, ToVideoStatus currently maps
TaskStatusNotStart into the default "unknown"; update the switch to include a
case for TaskStatusNotStart (and any other pre-start aliases you have, e.g.,
TaskStatusInitialized) that assigns commonRelay.VideoStatusQueued so newly
created tasks report "queued" instead of "unknown". Keep existing cases intact
and return the status as before.


const (
TaskStatusNotStart TaskStatus = "NOT_START"
TaskStatusSubmitted = "SUBMITTED"
Expand Down
31 changes: 30 additions & 1 deletion relay/channel/task/jimeng/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *rela
return
}

c.JSON(http.StatusOK, gin.H{"task_id": jResp.Data.TaskID})
ov := relaycommon.NewOpenAIVideo()
ov.ID = jResp.Data.TaskID
ov.TaskID = jResp.Data.TaskID
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
c.JSON(http.StatusOK, ov)
Comment on lines +161 to +166

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.

⚠️ Potential issue | 🟠 Major

Set creation response status to “queued”

OpenAIVideo.Status must be a valid enum value. Leaving it blank for Jimeng task submissions produces an invalid payload for SDK consumers. Initialize it to VideoStatusQueued when returning the creation acknowledgement.

 	ov := relaycommon.NewOpenAIVideo()
 	ov.ID = jResp.Data.TaskID
 	ov.TaskID = jResp.Data.TaskID
 	ov.CreatedAt = time.Now().Unix()
 	ov.Model = info.OriginModelName
+	ov.Status = relaycommon.VideoStatusQueued
 	c.JSON(http.StatusOK, ov)
📝 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
ov := relaycommon.NewOpenAIVideo()
ov.ID = jResp.Data.TaskID
ov.TaskID = jResp.Data.TaskID
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
c.JSON(http.StatusOK, ov)
ov := relaycommon.NewOpenAIVideo()
ov.ID = jResp.Data.TaskID
ov.TaskID = jResp.Data.TaskID
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
ov.Status = relaycommon.VideoStatusQueued
c.JSON(http.StatusOK, ov)
🤖 Prompt for AI Agents
In relay/channel/task/jimeng/adaptor.go around lines 160–165, the returned
OpenAIVideo is missing a valid Status enum; set ov.Status to the queued enum
value (relaycommon.VideoStatusQueued) before sending the JSON response so the
creation acknowledgement contains a valid status for SDK consumers.

return jResp.Data.TaskID, responseBody, nil
}

Expand Down Expand Up @@ -400,6 +405,30 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e
return &taskResult, nil
}

func (a *TaskAdaptor) ConvertToOpenAIVideo(originTask *model.Task) (*relaycommon.OpenAIVideo, error) {
var jimengResp responseTask
if err := json.Unmarshal(originTask.Data, &jimengResp); err != nil {
return nil, errors.Wrap(err, "unmarshal jimeng task data failed")
}

openAIVideo := relaycommon.NewOpenAIVideo()
openAIVideo.ID = originTask.TaskID
openAIVideo.Status = originTask.Status.ToVideoStatus()
openAIVideo.SetProgressStr(originTask.Progress)
openAIVideo.SetMetadata("url", jimengResp.Data.VideoUrl)

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 | 🟠 Major

Check VideoUrl before setting metadata.

Unlike the vidu and kling adapters (which verify URL presence), this unconditionally sets metadata even when VideoUrl is empty, polluting the metadata map with empty strings.

Apply this diff to align with the other adapters:

-	openAIVideo.SetMetadata("url", jimengResp.Data.VideoUrl)
+	if jimengResp.Data.VideoUrl != "" {
+		openAIVideo.SetMetadata("url", jimengResp.Data.VideoUrl)
+	}
📝 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
openAIVideo.SetMetadata("url", jimengResp.Data.VideoUrl)
if jimengResp.Data.VideoUrl != "" {
openAIVideo.SetMetadata("url", jimengResp.Data.VideoUrl)
}
🤖 Prompt for AI Agents
In relay/channel/task/jimeng/adaptor.go around line 418, the code
unconditionally sets openAIVideo metadata "url" to jimengResp.Data.VideoUrl
which can be empty; update it to check that jimengResp.Data.VideoUrl is
non-empty (e.g., len > 0 or != "") before calling openAIVideo.SetMetadata so the
metadata map is not populated with empty strings, following the same guard used
in the vidu and kling adapters.

openAIVideo.CreatedAt = originTask.CreatedAt
openAIVideo.CompletedAt = originTask.UpdatedAt

if jimengResp.Code != 10000 {
openAIVideo.Error = &relaycommon.OpenAIVideoError{
Message: jimengResp.Message,
Code: fmt.Sprintf("%d", jimengResp.Code),
}
}

return openAIVideo, nil
}

func isNewAPIRelay(apiKey string) bool {
return strings.HasPrefix(apiKey, "sk-")
}
38 changes: 13 additions & 25 deletions relay/channel/task/kling/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import (
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"

"github.com/QuantumNous/new-api/model"

"github.com/bytedance/gopkg/util/logger"
"github.com/samber/lo"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -190,8 +188,12 @@ func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *rela
taskErr = service.TaskErrorWrapperLocal(fmt.Errorf(kResp.Message), "task_failed", http.StatusBadRequest)
return
}
kResp.TaskId = kResp.Data.TaskId
c.JSON(http.StatusOK, kResp)
ov := relaycommon.NewOpenAIVideo()
ov.ID = kResp.Data.TaskId
ov.TaskID = kResp.Data.TaskId
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
c.JSON(http.StatusOK, ov)
return kResp.Data.TaskId, responseBody, nil
Comment on lines +191 to 197

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.

⚠️ Potential issue | 🟠 Major

Populate OpenAIVideo status on creation responses

OpenAIVideo.Status is left empty when we return the creation response, yet the OpenAI schema requires a concrete value (e.g. "queued"). Clients inspecting the enum will see an invalid blank status. Please initialize it—for a freshly submitted task, VideoStatusQueued (and optionally zero progress) is the expected value.

 	ov := relaycommon.NewOpenAIVideo()
 	ov.ID = kResp.Data.TaskId
 	ov.TaskID = kResp.Data.TaskId
 	ov.CreatedAt = time.Now().Unix()
 	ov.Model = info.OriginModelName
+	ov.Status = relaycommon.VideoStatusQueued
 	c.JSON(http.StatusOK, ov)
🤖 Prompt for AI Agents
In relay/channel/task/kling/adaptor.go around lines 190 to 196,
OpenAIVideo.Status is left empty on creation responses which violates the OpenAI
schema; set ov.Status = relaycommon.VideoStatusQueued (and optionally
ov.Progress = 0 or 0.0 if the struct has a Progress field) before c.JSON so the
created task returns a concrete "queued" status and zero progress for new tasks.

}

Expand Down Expand Up @@ -371,31 +373,17 @@ func (a *TaskAdaptor) ConvertToOpenAIVideo(originTask *model.Task) (*relaycommon
return nil, errors.Wrap(err, "unmarshal kling task data failed")
}

convertProgress := func(progress string) int {
progress = strings.TrimSuffix(progress, "%")
p, err := strconv.Atoi(progress)
if err != nil {
logger.Warnf("convert progress failed, progress: %s, err: %v", progress, err)
}
return p
}

openAIVideo := &relaycommon.OpenAIVideo{
ID: klingResp.Data.TaskId,
Object: "video",
//Model: "kling-v1", //todo save model
Status: string(originTask.Status),
CreatedAt: klingResp.Data.CreatedAt,
CompletedAt: klingResp.Data.UpdatedAt,
Metadata: make(map[string]any),
Progress: convertProgress(originTask.Progress),
}
openAIVideo := relaycommon.NewOpenAIVideo()
openAIVideo.ID = originTask.TaskID
openAIVideo.Status = originTask.Status.ToVideoStatus()
openAIVideo.SetProgressStr(originTask.Progress)
openAIVideo.CreatedAt = klingResp.Data.CreatedAt
openAIVideo.CompletedAt = klingResp.Data.UpdatedAt

// 处理视频 URL
if len(klingResp.Data.TaskResult.Videos) > 0 {
video := klingResp.Data.TaskResult.Videos[0]
if video.Url != "" {
openAIVideo.Metadata["url"] = video.Url
openAIVideo.SetMetadata("url", video.Url)
}
if video.Duration != "" {
openAIVideo.Seconds = video.Duration
Expand Down
37 changes: 35 additions & 2 deletions relay/channel/task/vidu/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"time"

"github.com/gin-gonic/gin"

Expand Down Expand Up @@ -135,7 +136,7 @@ func (a *TaskAdaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, req
return channel.DoTaskApiRequest(a, c, info, requestBody)
}

func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, _ *relaycommon.RelayInfo) (taskID string, taskData []byte, taskErr *dto.TaskError) {
func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (taskID string, taskData []byte, taskErr *dto.TaskError) {
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
taskErr = service.TaskErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
Expand All @@ -154,7 +155,12 @@ func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, _ *relayco
return
}

c.JSON(http.StatusOK, vResp)
ov := relaycommon.NewOpenAIVideo()
ov.ID = vResp.TaskId
ov.TaskID = vResp.TaskId
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
c.JSON(http.StatusOK, ov)
return vResp.TaskId, responseBody, nil
Comment on lines +158 to 164

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.

⚠️ Potential issue | 🟠 Major

Return a valid status in the creation response

The OpenAI video schema expects status to be one of the defined enums, but the current response leaves it empty. For a just-created VIDU task, set it to VideoStatusQueued so downstream callers receive a valid value.

 	ov := relaycommon.NewOpenAIVideo()
 	ov.ID = vResp.TaskId
 	ov.TaskID = vResp.TaskId
 	ov.CreatedAt = time.Now().Unix()
 	ov.Model = info.OriginModelName
+	ov.Status = relaycommon.VideoStatusQueued
 	c.JSON(http.StatusOK, ov)
📝 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
ov := relaycommon.NewOpenAIVideo()
ov.ID = vResp.TaskId
ov.TaskID = vResp.TaskId
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
c.JSON(http.StatusOK, ov)
return vResp.TaskId, responseBody, nil
ov := relaycommon.NewOpenAIVideo()
ov.ID = vResp.TaskId
ov.TaskID = vResp.TaskId
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
ov.Status = relaycommon.VideoStatusQueued
c.JSON(http.StatusOK, ov)
return vResp.TaskId, responseBody, nil
🤖 Prompt for AI Agents
In relay/channel/task/vidu/adaptor.go around lines 158 to 164, the created
OpenAI video object is returned without a valid status; set its status to the
queued enum before sending/returning it (e.g., assign ov.Status =
relaycommon.VideoStatusQueued after creating ov) so the creation response
contains a valid VideoStatusQueued value for downstream callers.

}

Expand Down Expand Up @@ -256,3 +262,30 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e

return taskInfo, nil
}

func (a *TaskAdaptor) ConvertToOpenAIVideo(originTask *model.Task) (*relaycommon.OpenAIVideo, error) {
var viduResp taskResultResponse
if err := json.Unmarshal(originTask.Data, &viduResp); err != nil {
return nil, errors.Wrap(err, "unmarshal vidu task data failed")
}

openAIVideo := relaycommon.NewOpenAIVideo()
openAIVideo.ID = originTask.TaskID
openAIVideo.Status = originTask.Status.ToVideoStatus()
openAIVideo.SetProgressStr(originTask.Progress)
openAIVideo.CreatedAt = originTask.CreatedAt
openAIVideo.CompletedAt = originTask.UpdatedAt

if len(viduResp.Creations) > 0 && viduResp.Creations[0].URL != "" {
openAIVideo.SetMetadata("url", viduResp.Creations[0].URL)
}

if viduResp.State == "failed" && viduResp.ErrCode != "" {
openAIVideo.Error = &relaycommon.OpenAIVideoError{
Message: viduResp.ErrCode,
Code: viduResp.ErrCode,
}
}

return openAIVideo, nil
}
21 changes: 0 additions & 21 deletions relay/common/relay_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,24 +551,3 @@ func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOther
}
return jsonDataAfter, nil
}

type OpenAIVideo struct {
ID string `json:"id"`
TaskID string `json:"task_id,omitempty"` //兼容旧接口 待废弃
Object string `json:"object"`
Model string `json:"model"`
Status string `json:"status"`
Progress int `json:"progress"`
CreatedAt int64 `json:"created_at"`
CompletedAt int64 `json:"completed_at,omitempty"`
ExpiresAt int64 `json:"expires_at,omitempty"`
Seconds string `json:"seconds,omitempty"`
Size string `json:"size,omitempty"`
RemixedFromVideoID string `json:"remixed_from_video_id,omitempty"`
Error *OpenAIVideoError `json:"error,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
type OpenAIVideoError struct {
Message string `json:"message"`
Code string `json:"code"`
}
52 changes: 52 additions & 0 deletions relay/common/relay_video.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package common

import (
"strconv"
"strings"
)

const (
VideoStatusUnknown = "unknown"
VideoStatusQueued = "queued"
VideoStatusInProgress = "in_progress"
VideoStatusCompleted = "completed"
VideoStatusFailed = "failed"
)

type OpenAIVideo struct {
ID string `json:"id"`
TaskID string `json:"task_id,omitempty"` //兼容旧接口 待废弃

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.

⚠️ Potential issue | 🟡 Minor

Clarify deprecation timeline for TaskID.

The deprecation comment lacks guidance on when TaskID will be removed or what users should migrate to. Consider adding a timeline or migration path in the comment.

Example:

TaskID string `json:"task_id,omitempty"` // Deprecated: use ID instead. Will be removed in v2.0.
🤖 Prompt for AI Agents
In relay/common/relay_video.go around line 18, the inline comment on TaskID is
ambiguous about deprecation; update the comment to a proper Go-style deprecation
note that states the replacement field (e.g., ID), a removal version or
timeframe (e.g., "Will be removed in v2.0"), and brief migration guidance (e.g.,
"use ID instead"). Replace the current Chinese/ambiguous note with a clear
English deprecation tag such as: "Deprecated: use ID instead. Will be removed in
v2.0." and optionally add a one-line migration hint.

Object string `json:"object"`
Model string `json:"model"`
Status string `json:"status"` // Should use VideoStatus constants: VideoStatusQueued, VideoStatusInProgress, VideoStatusCompleted, VideoStatusFailed
Progress int `json:"progress"`
CreatedAt int64 `json:"created_at"`
CompletedAt int64 `json:"completed_at,omitempty"`
ExpiresAt int64 `json:"expires_at,omitempty"`
Seconds string `json:"seconds,omitempty"`
Size string `json:"size,omitempty"`
RemixedFromVideoID string `json:"remixed_from_video_id,omitempty"`
Error *OpenAIVideoError `json:"error,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}

func (m *OpenAIVideo) SetProgressStr(progress string) {
progress = strings.TrimSuffix(progress, "%")
m.Progress, _ = strconv.Atoi(progress)
}
Comment on lines +33 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.

⚠️ Potential issue | 🟠 Major

Handle parsing errors in SetProgressStr.

The error from strconv.Atoi is silently ignored, which means invalid input like "abc" will result in Progress being set to 0 without any indication of failure. This could mask data quality issues and make debugging difficult.

Consider one of these approaches:

Option 1: Return the error

-func (m *OpenAIVideo) SetProgressStr(progress string) {
+func (m *OpenAIVideo) SetProgressStr(progress string) error {
 	progress = strings.TrimSuffix(progress, "%")
-	m.Progress, _ = strconv.Atoi(progress)
+	var err error
+	m.Progress, err = strconv.Atoi(progress)
+	return err
 }

Option 2: Log the error and set a default

+import "log"
+
 func (m *OpenAIVideo) SetProgressStr(progress string) {
 	progress = strings.TrimSuffix(progress, "%")
-	m.Progress, _ = strconv.Atoi(progress)
+	var err error
+	m.Progress, err = strconv.Atoi(progress)
+	if err != nil {
+		log.Printf("Failed to parse progress '%s': %v", progress, err)
+		m.Progress = 0
+	}
 }
📝 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
func (m *OpenAIVideo) SetProgressStr(progress string) {
progress = strings.TrimSuffix(progress, "%")
m.Progress, _ = strconv.Atoi(progress)
}
func (m *OpenAIVideo) SetProgressStr(progress string) error {
progress = strings.TrimSuffix(progress, "%")
var err error
m.Progress, err = strconv.Atoi(progress)
return err
}
🤖 Prompt for AI Agents
In relay/common/relay_video.go around lines 33 to 36, SetProgressStr silently
ignores strconv.Atoi errors which masks invalid input; either change
SetProgressStr to return an error (update callers) and propagate the
strconv.Atoi error, or keep the void signature but detect the error, log a
warning with the offending input and set m.Progress to a safe default (e.g., 0)
only on parse failure; implement one of these two approaches and ensure callers
or logs reflect the parse failure.

func (m *OpenAIVideo) SetMetadata(k string, v any) {
if m.Metadata == nil {
m.Metadata = make(map[string]any)
}
m.Metadata[k] = v
}
func NewOpenAIVideo() *OpenAIVideo {
return &OpenAIVideo{
Object: "video",
}
}

type OpenAIVideoError struct {
Message string `json:"message"`
Code string `json:"code"`
}