Skip to content

新增MiniMax海螺视频模型支持 - #2225

Merged
creamlike1024 merged 3 commits into
QuantumNous:mainfrom
feitianbubu:pr/add-hailuo-video
Nov 14, 2025
Merged

新增MiniMax海螺视频模型支持#2225
creamlike1024 merged 3 commits into
QuantumNous:mainfrom
feitianbubu:pr/add-hailuo-video

Conversation

@feitianbubu

@feitianbubu feitianbubu commented Nov 14, 2025

Copy link
Copy Markdown
Member

官方文档: https://platform.minimaxi.com/docs/api-reference/video-generation-intro
支持模型: MiniMax-Hailuo-2.3, MiniMax-Hailuo-02(首尾帧生视频), S2V-01(参考生视频)
请求示例:

  1. 图生视频:
    请求:
curl http://localhost:3000/v1/videos \
  --request POST \
  --header 'Content-Type: multipart/form-data' \
  --form 'prompt=转个圈' \
  --form 'model=MiniMax-Hailuo-02' \
  --form 'first_frame_image=https://cdn.hailuoai.com/prod/2024-09-18-16/user/multi_chat_file/9c0b5c14-ee88-4a5b-b503-4f626f018639.jpeg'
image
  1. 首尾帧生视频:
    请求:
curl http://localhost:3000/v1/videos \
  --request POST \
  --header 'Content-Type: multipart/form-data' \
  --form 'prompt=转个圈' \
  --form 'model=MiniMax-Hailuo-02' \
  --form 'first_frame_image=https://cdn.hailuoai.com/prod/2024-09-18-16/user/multi_chat_file/9c0b5c14-ee88-4a5b-b503-4f626f018639.jpeg' \
  --form 'last_frame_image=https://filecdn.minimax.chat/public/97b7cd08-764e-4b8b-a7bf-87a0bd898575.jpeg'
image image

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for Hailuo video generation channel with full integration for video task submission, progress tracking, and result retrieval.
    • Enhanced API key handling for improved channel connectivity.
  • Improvements

    • Added video data conversion capabilities for standardized output formatting.
    • Improved method signatures for better null-pointer safety.

@coderabbitai

coderabbitai Bot commented Nov 14, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This PR introduces integration for the hailuo video generation channel by adding a complete TaskAdaptor implementation with request/response handling, propagating API keys into task initialization, and implementing Task-to-OpenAI video format conversion alongside supporting data models and constants.

Changes

Cohort / File(s) Change Summary
API Key Propagation
controller/task_video.go, relay/common/relay_info.go
Populates RelayInfo.ApiKey before adaptor initialization; updates receiver types for GetPrompt() and HasImage() methods from value to pointer and adds UnmarshalMetadata() helper
Task Model Conversion
model/task.go
Adds ToOpenAIVideo() method to convert Task fields into OpenAI video format with status conversion and metadata mapping
Hailuo Channel Adaptor
relay/channel/task/hailuo/adaptor.go
New TaskAdaptor implementation with full lifecycle: request URL/header/body construction, HTTP execution, response parsing, task polling, status translation, OpenAI video conversion, and video URL retrieval
Hailuo Configuration & Models
relay/channel/task/hailuo/constants.go, relay/channel/task/hailuo/models.go
Defines channel name, API endpoints, status codes, task states, resolutions, and default configurations; introduces request/response payloads, task status info, and model configuration lookup with per-model capabilities
Adaptor Registration
relay/relay_adaptor.go
Extends GetTaskAdaptor() dispatch to map ChannelTypeMiniMax to hailuo TaskAdaptor

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller as controller/task_video.go
    participant Adaptor as hailuo/adaptor.go
    participant HailuoAPI as Hailuo API
    participant DB as Task DB

    Client->>Controller: Video generation request
    Controller->>Controller: Populate RelayInfo.ApiKey from channel
    Controller->>Adaptor: Init(RelayInfo)
    Adaptor->>Adaptor: Store apiKey, baseURL
    
    Controller->>Adaptor: ValidateRequestAndSetAction()
    Adaptor-->>Controller: ✓ or TaskError
    
    Controller->>Adaptor: BuildRequestURL()
    Adaptor-->>Controller: API endpoint URL
    
    Controller->>Adaptor: BuildRequestHeader()
    Adaptor-->>Controller: Headers (auth, content-type)
    
    Controller->>Adaptor: BuildRequestBody()
    Adaptor->>Adaptor: convertToRequestPayload()
    Adaptor-->>Controller: Request JSON payload
    
    Controller->>Adaptor: DoRequest()
    Adaptor->>HailuoAPI: POST /v1/video_generation
    HailuoAPI-->>Adaptor: Response + TaskID
    
    Controller->>Adaptor: DoResponse()
    Adaptor->>Adaptor: ParseTaskResult()
    Adaptor-->>Controller: TaskID, TaskData
    
    Controller->>DB: Store task
    
    Note over Controller,Adaptor: Later: Task status polling
    Controller->>Adaptor: FetchTask()
    Adaptor->>HailuoAPI: GET /v1/query/video_generation
    HailuoAPI-->>Adaptor: Status, FileID
    
    Adaptor->>Adaptor: ParseTaskResult() + buildVideoURL()
    Adaptor-->>Controller: TaskInfo with status/URL
    
    Controller->>Adaptor: ConvertToOpenAIVideo()
    Adaptor-->>Controller: OpenAI video response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • hailuo/adaptor.go: Dense implementation with ~700 lines of request construction, payload conversion, response parsing, status mapping, and URL building logic—each method requires validation of correctness against hailuo API spec
  • Payload conversion and resolution parsing: convertToRequestPayload() and parseResolutionFromSize() contain conditional logic for defaults and mappings that need verification
  • Status and error translation: ParseTaskResult() and ConvertToOpenAIVideo() map hailuo-specific codes/states to internal formats—requires correctness check
  • API key propagation flow: Cross-file changes to RelayInfo and controller initialization need cohesion verification
  • Method receiver changes: Updates to GetPrompt() and HasImage() may affect existing call sites and nil-safety patterns throughout the codebase

Possibly related PRs

  • 新增Sora视频渠道 #1988 — Modifies controller/task_video.go similarly to initialize task adaptors with channel context and introduces new channel-specific task adaptors in parallel
  • fix veo3 adapter #1794 — Introduces RelayInfo-driven stateful task adaptors that propagate ApiKey into adaptor initialization, establishing the architectural pattern this PR extends
  • Gemini渠道支持veo视频生成 #2075 — Adds new channel TaskAdaptor implementations and extends GetTaskAdaptor dispatch logic, following the same adaptor registration pattern

Suggested reviewers

  • creamlike1024
  • seefs001

Poem

🐰 A hailuo hop through videos galore,
With adaptors crafted and APIs to adore,
Keys propagate swift, payloads transform,
Task states dance through status and form—
The relay runs true, from request to reply! 🎬✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and clearly describes the main change: adding support for MiniMax Hailuo video models, which matches the substantial code additions across multiple files for hailuo integration.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
model/task.go (1)

432-442: Populate TaskID in OpenAIVideo for backward compatibility

ToOpenAIVideo currently only sets ID, leaving TaskID empty even though it exists for legacy clients. To align with existing create responses (e.g. hailuo DoResponse) and ease migration, consider also setting TaskID:

func (t *Task) ToOpenAIVideo() *dto.OpenAIVideo {
	openAIVideo := dto.NewOpenAIVideo()
	openAIVideo.ID = t.TaskID
+	openAIVideo.TaskID = t.TaskID
	openAIVideo.Status = t.Status.ToVideoStatus()
	openAIVideo.Model = t.Properties.OriginModelName
	openAIVideo.SetProgressStr(t.Progress)
	openAIVideo.CreatedAt = t.CreatedAt
	openAIVideo.CompletedAt = t.UpdatedAt
	openAIVideo.SetMetadata("url", t.FailReason)
	return openAIVideo
}

Optionally, you could later gate CompletedAt/metadata["url"] to success states only, but that’s not blocking.

relay/channel/task/hailuo/models.go (1)

1-170: Model and config definitions look good; consider hoisting configs map

The type definitions closely follow the API schema and the GetModelConfig fallback behavior is sensible.

If GetModelConfig is called frequently, you might want to move the configs map to a package‑level var to avoid reallocating it on every call:

-var ModelConfig map[string]ModelConfig // example
-
-func GetModelConfig(model string) ModelConfig {
-    configs := map[string]ModelConfig{
-        // ...
-    }
+var modelConfigs = map[string]ModelConfig{
+    // ...
+}
+
+func GetModelConfig(model string) ModelConfig {
-    if config, exists := configs[model]; exists {
+    if config, exists := modelConfigs[model]; exists {
         return config
     }
    // ...
}

This is an optimization only; current implementation is functionally correct.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1a52f1 and d8dc802.

📒 Files selected for processing (7)
  • controller/task_video.go (1 hunks)
  • model/task.go (1 hunks)
  • relay/channel/task/hailuo/adaptor.go (1 hunks)
  • relay/channel/task/hailuo/constants.go (1 hunks)
  • relay/channel/task/hailuo/models.go (1 hunks)
  • relay/common/relay_info.go (2 hunks)
  • relay/relay_adaptor.go (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-26T09:59:00.337Z
Learnt from: Sh1n3zZ
Repo: QuantumNous/new-api PR: 1659
File: relay/relay_task.go:285-305
Timestamp: 2025-08-26T09:59:00.337Z
Learning: In controller/task_video.go, the redactVideoResponseBody function sanitizes video task responses by removing bytesBase64Encoded fields and truncating base64 strings to 256 characters to prevent large binary data from being stored in task.Data.

Applied to files:

  • relay/channel/task/hailuo/adaptor.go
📚 Learning: 2025-08-26T09:59:00.337Z
Learnt from: Sh1n3zZ
Repo: QuantumNous/new-api PR: 1659
File: relay/relay_task.go:285-305
Timestamp: 2025-08-26T09:59:00.337Z
Learning: In controller/task_video.go, data: URLs (containing base64 encoded video data) are prevented from being stored in task.FailReason by checking if the URL starts with "data:" before assignment. This same pattern should be applied consistently across the codebase.

Applied to files:

  • relay/channel/task/hailuo/adaptor.go
🧬 Code graph analysis (5)
model/task.go (1)
dto/openai_video.go (2)
  • OpenAIVideo (16-31)
  • NewOpenAIVideo (43-47)
relay/channel/task/hailuo/models.go (1)
relay/channel/task/hailuo/constants.go (5)
  • DefaultResolution (51-51)
  • Resolution768P (45-45)
  • Resolution1080P (46-46)
  • Resolution512P (43-43)
  • Resolution720P (44-44)
relay/common/relay_info.go (2)
relay/common/relay_utils.go (1)
  • HasImage (21-23)
common/json.go (2)
  • Marshal (21-23)
  • Unmarshal (9-11)
relay/relay_adaptor.go (3)
constant/channel.go (1)
  • ChannelTypeMiniMax (35-35)
relay/channel/task/hailuo/adaptor.go (1)
  • TaskAdaptor (26-30)
relay/channel/adapter.go (1)
  • TaskAdaptor (34-53)
relay/channel/task/hailuo/adaptor.go (9)
relay/common/relay_info.go (2)
  • RelayInfo (76-123)
  • TaskSubmitReq (488-499)
relay/common/relay_utils.go (1)
  • ValidateBasicTaskRequest (204-228)
common/json.go (2)
  • Marshal (21-23)
  • Unmarshal (9-11)
relay/channel/api_request.go (1)
  • DoTaskApiRequest (301-323)
service/error.go (1)
  • TaskErrorWrapper (140-157)
relay/channel/task/hailuo/models.go (7)
  • VideoResponse (22-25)
  • BaseResp (27-30)
  • VideoRequest (8-20)
  • GetModelConfig (82-170)
  • ModelConfig (59-66)
  • QueryTaskResponse (36-43)
  • RetrieveFileResponse (68-71)
dto/openai_video.go (2)
  • NewOpenAIVideo (43-47)
  • OpenAIVideoError (49-52)
service/http_client.go (1)
  • GetHttpClient (49-51)
model/task.go (6)
  • TaskStatusFailure (37-37)
  • TaskStatusInProgress (36-36)
  • TaskStatusSuccess (38-38)
  • Task (42-63)
  • Task (297-301)
  • Task (303-307)
🔇 Additional comments (4)
controller/task_video.go (1)

51-56: API key propagation into RelayInfo looks correct

Setting info.ApiKey = cacheGetChannel.Key after initializing ChannelMeta correctly exposes the channel key to task adaptors via promoted fields and is required for hailuo’s Init. No further changes needed here.

relay/common/relay_info.go (1)

501-507: TaskSubmitReq helpers are fine; callsites must pass proper pointers

The pointer receivers on GetPrompt/HasImage and the new UnmarshalMetadata implementation are reasonable. UnmarshalMetadata correctly treats nil metadata as a no-op and reuses encoding/json for map→struct conversion.

Callers should pass a pointer to the target struct (e.g. req.UnmarshalMetadata(videoReq)) rather than a pointer-to-pointer to avoid unexpected behavior; the hailuo adaptor currently needs that adjustment (see hailuo/adaptor.go).

Also applies to: 540-553

relay/relay_adaptor.go (1)

35-35: MiniMax → hailuo TaskAdaptor wiring looks correct

Importing the hailuo package and mapping ChannelTypeMiniMax to &hailuo.TaskAdaptor{} in GetTaskAdaptor is consistent with the existing pattern and correctly routes MiniMax video tasks to the new channel.

Also applies to: 157-158

relay/channel/task/hailuo/constants.go (1)

1-52: Channel constants and model list are coherent

The channel name, model list, endpoints, status codes, task states, and resolution/default constants are well-structured and align with how the adaptor and models consume them. No issues here.

Comment on lines +80 to +111
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)
return
}
_ = resp.Body.Close()

var hResp VideoResponse
if err := json.Unmarshal(responseBody, &hResp); err != nil {
taskErr = service.TaskErrorWrapper(errors.Wrapf(err, "body: %s", responseBody), "unmarshal_response_body_failed", http.StatusInternalServerError)
return
}

if hResp.BaseResp.StatusCode != StatusSuccess {
taskErr = service.TaskErrorWrapper(
fmt.Errorf("hailuo api error: %s", hResp.BaseResp.StatusMsg),
strconv.Itoa(hResp.BaseResp.StatusCode),
http.StatusBadRequest,
)
return
}

ov := dto.NewOpenAIVideo()
ov.ID = hResp.TaskID
ov.TaskID = hResp.TaskID
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName

c.JSON(http.StatusOK, ov)
return hResp.TaskID, responseBody, nil
}

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 initial video status/progress in DoResponse

The create handler returns an OpenAIVideo without Status or progress populated, which is inconsistent with the OpenAI‑style contract (clients typically expect "queued" right after creation). You already have a VideoStatusQueued constant via dto.

Consider initializing these fields:

ov := dto.NewOpenAIVideo()
ov.ID = hResp.TaskID
ov.TaskID = hResp.TaskID
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
+ov.Status = dto.VideoStatusQueued
+ov.SetProgressStr("0%")

This makes the creation response more useful without affecting downstream polling.

📝 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 (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)
return
}
_ = resp.Body.Close()
var hResp VideoResponse
if err := json.Unmarshal(responseBody, &hResp); err != nil {
taskErr = service.TaskErrorWrapper(errors.Wrapf(err, "body: %s", responseBody), "unmarshal_response_body_failed", http.StatusInternalServerError)
return
}
if hResp.BaseResp.StatusCode != StatusSuccess {
taskErr = service.TaskErrorWrapper(
fmt.Errorf("hailuo api error: %s", hResp.BaseResp.StatusMsg),
strconv.Itoa(hResp.BaseResp.StatusCode),
http.StatusBadRequest,
)
return
}
ov := dto.NewOpenAIVideo()
ov.ID = hResp.TaskID
ov.TaskID = hResp.TaskID
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
c.JSON(http.StatusOK, ov)
return hResp.TaskID, responseBody, nil
}
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)
return
}
_ = resp.Body.Close()
var hResp VideoResponse
if err := json.Unmarshal(responseBody, &hResp); err != nil {
taskErr = service.TaskErrorWrapper(errors.Wrapf(err, "body: %s", responseBody), "unmarshal_response_body_failed", http.StatusInternalServerError)
return
}
if hResp.BaseResp.StatusCode != StatusSuccess {
taskErr = service.TaskErrorWrapper(
fmt.Errorf("hailuo api error: %s", hResp.BaseResp.StatusMsg),
strconv.Itoa(hResp.BaseResp.StatusCode),
http.StatusBadRequest,
)
return
}
ov := dto.NewOpenAIVideo()
ov.ID = hResp.TaskID
ov.TaskID = hResp.TaskID
ov.CreatedAt = time.Now().Unix()
ov.Model = info.OriginModelName
ov.Status = dto.VideoStatusQueued
ov.SetProgressStr("0%")
c.JSON(http.StatusOK, ov)
return hResp.TaskID, responseBody, nil
}
🤖 Prompt for AI Agents
In relay/channel/task/hailuo/adaptor.go around lines 80 to 111, the created
OpenAIVideo is missing initial status/progress; set ov.Status =
dto.VideoStatusQueued and set ov.Progress = 0 (and optionally ov.UpdatedAt =
ov.CreatedAt if that field exists) before sending the JSON response so the
creation response follows the OpenAI‑style contract and signals the video is
queued.

Comment on lines +140 to +162
func (a *TaskAdaptor) convertToRequestPayload(req *relaycommon.TaskSubmitReq) (*VideoRequest, error) {
modelConfig := GetModelConfig(req.Model)
duration := DefaultDuration
if req.Duration > 0 {
duration = req.Duration
}
resolution := modelConfig.DefaultResolution
if req.Size != "" {
resolution = a.parseResolutionFromSize(req.Size, modelConfig)
}

videoRequest := &VideoRequest{
Model: req.Model,
Prompt: req.Prompt,
Duration: &duration,
Resolution: resolution,
}
if err := req.UnmarshalMetadata(&videoRequest); err != nil {
return nil, errors.Wrap(err, "unmarshal metadata to video request failed")
}

return videoRequest, nil
}

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 | 🔴 Critical

Fix metadata unmarshal target in convertToRequestPayload

convertToRequestPayload currently calls:

if err := req.UnmarshalMetadata(&videoRequest); err != nil {
    // ...
}

Here videoRequest is already a *VideoRequest, so &videoRequest is a **VideoRequest. This will cause encoding/json to treat the target as a pointer-to-pointer, which can overwrite the pointer itself and/or lose your pre‑filled defaults (model, duration, resolution) instead of overlaying metadata onto them.

You want to unmarshal into the struct that videoRequest points to:

func (a *TaskAdaptor) convertToRequestPayload(req *relaycommon.TaskSubmitReq) (*VideoRequest, error) {
    modelConfig := GetModelConfig(req.Model)
    duration := DefaultDuration
    if req.Duration > 0 {
        duration = req.Duration
    }
    resolution := modelConfig.DefaultResolution
    if req.Size != "" {
        resolution = a.parseResolutionFromSize(req.Size, modelConfig)
    }

    videoRequest := &VideoRequest{
        Model:      req.Model,
        Prompt:     req.Prompt,
        Duration:   &duration,
        Resolution: resolution,
    }
-   if err := req.UnmarshalMetadata(&videoRequest); err != nil {
+   if err := req.UnmarshalMetadata(videoRequest); err != nil {
        return nil, errors.Wrap(err, "unmarshal metadata to video request failed")
    }

    return videoRequest, nil
}

This preserves the defaults while allowing metadata to override fields where present.

🤖 Prompt for AI Agents
In relay/channel/task/hailuo/adaptor.go around lines 140 to 162, the call to
req.UnmarshalMetadata currently passes &videoRequest (a **VideoRequest) which
causes JSON unmarshal to target a pointer-to-pointer and can overwrite the
pointer (losing prefilled defaults); change the call to pass videoRequest (the
*VideoRequest) so UnmarshalMetadata decodes into the struct pointed to and
overlays metadata onto the existing defaults, keeping the existing error
wrapping logic unchanged.

Comment on lines +179 to +219
func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, error) {
resTask := QueryTaskResponse{}
if err := json.Unmarshal(respBody, &resTask); err != nil {
return nil, errors.Wrap(err, "unmarshal task result failed")
}

taskResult := relaycommon.TaskInfo{}

if resTask.BaseResp.StatusCode == StatusSuccess {
taskResult.Code = 0
} else {
taskResult.Code = resTask.BaseResp.StatusCode
taskResult.Reason = resTask.BaseResp.StatusMsg
taskResult.Status = model.TaskStatusFailure
taskResult.Progress = "100%"
}

switch resTask.Status {
case TaskStatusPreparing, TaskStatusQueueing, TaskStatusProcessing:
taskResult.Status = model.TaskStatusInProgress
taskResult.Progress = "30%"
if resTask.Status == TaskStatusProcessing {
taskResult.Progress = "50%"
}
case TaskStatusSuccess:
taskResult.Status = model.TaskStatusSuccess
taskResult.Progress = "100%"
taskResult.Url = a.buildVideoURL(resTask.TaskID, resTask.FileID)
case TaskStatusFailed:
taskResult.Status = model.TaskStatusFailure
taskResult.Progress = "100%"
if taskResult.Reason == "" {
taskResult.Reason = "task failed"
}
default:
taskResult.Status = model.TaskStatusInProgress
taskResult.Progress = "30%"
}

return &taskResult, nil
}

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

Tighten error handling in ParseTaskResult and note URL retrieval behavior

  1. BaseResp error handling can be overridden

When resTask.BaseResp.StatusCode != StatusSuccess, you set failure status and Progress = "100%", but then unconditionally run the switch resTask.Status, which can downgrade a failed task back to IN_PROGRESS:

if resTask.BaseResp.StatusCode == StatusSuccess {
    taskResult.Code = 0
} else {
    taskResult.Code = resTask.BaseResp.StatusCode
    taskResult.Reason = resTask.BaseResp.StatusMsg
    taskResult.Status = model.TaskStatusFailure
    taskResult.Progress = "100%"
}

Then the switch may overwrite Status/Progress. To avoid stuck tasks on upstream errors, return early on non‑success:

if resTask.BaseResp.StatusCode == StatusSuccess {
    taskResult.Code = 0
} else {
    taskResult.Code = resTask.BaseResp.StatusCode
    taskResult.Reason = resTask.BaseResp.StatusMsg
    taskResult.Status = model.TaskStatusFailure
    taskResult.Progress = "100%"
+   return &taskResult, nil
}
  1. Video URL retrieval behavior

buildVideoURL does a secondary call to /v1/files/retrieve and returns DownloadURL, which is then stored in taskResult.Url. This is consistent with the existing pattern where the controller copies non‑data: URLs into task.FailReason and redacts raw binary/base64 from task data, so you’re staying aligned with prior redaction guidance. Based on learnings.

Given this, the main change needed here is the early return on non‑success StatusCode.

Also applies to: 243-279

🤖 Prompt for AI Agents
In relay/channel/task/hailuo/adaptor.go around lines 179 to 219 (and similarly
apply to 243-279), the ParseTaskResult function sets failure fields when
resTask.BaseResp.StatusCode != StatusSuccess but then continues to a switch on
resTask.Status that can overwrite Status/Progress; change the control flow to
return early on non-success BaseResp by constructing and returning the TaskInfo
immediately after setting Code, Reason, Status=model.TaskStatusFailure and
Progress="100%" so the downstream switch cannot downgrade the error state;
ensure the early return preserves Code and Reason and does not attempt to
build/assign the video URL when the base response indicates failure.

@creamlike1024
creamlike1024 merged commit e7bef09 into QuantumNous:main Nov 14, 2025
1 check passed
ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants