diff --git a/relay/channel/task/sora/adaptor.go b/relay/channel/task/sora/adaptor.go index e9029aa20d46..c5b605e42100 100644 --- a/relay/channel/task/sora/adaptor.go +++ b/relay/channel/task/sora/adaptor.go @@ -55,6 +55,12 @@ type responseTask struct { Message string `json:"message"` Code string `json:"code"` } `json:"error,omitempty"` + // xAI grok-imagine-video returns the video URL directly in video.url, + // without a separate /content endpoint like OpenAI Sora. + Video *struct { + URL string `json:"url"` + Duration int `json:"duration,omitempty"` + } `json:"video,omitempty"` } // ============================ @@ -302,10 +308,14 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e taskResult.Status = model.TaskStatusQueued case "processing", "in_progress": taskResult.Status = model.TaskStatusInProgress - case "completed": + case "completed", "done": + // "done" is the terminal success status returned by xAI grok-imagine-video. taskResult.Status = model.TaskStatusSuccess - // Url intentionally left empty — the caller constructs the proxy URL using the public task ID - case "failed", "cancelled": + // xAI returns the video URL directly in video.url (no separate content endpoint). + if resTask.Video != nil && resTask.Video.URL != "" { + taskResult.Url = resTask.Video.URL + } + case "failed", "cancelled", "error": taskResult.Status = model.TaskStatusFailure if resTask.Error != nil { taskResult.Reason = resTask.Error.Message diff --git a/service/task_polling.go b/service/task_polling.go index 179fa7342082..4e0258707fc1 100644 --- a/service/task_polling.go +++ b/service/task_polling.go @@ -502,9 +502,12 @@ func updateVideoSingleTask(ctx context.Context, adaptor TaskPollingAdaptor, ch * // 其他错误认为是任务失败,记录错误信息并更新任务状态 taskResult = relaycommon.FailTaskInfo("upstream returned error") } else { - // unknown error format, log original response - logger.LogError(ctx, fmt.Sprintf("Task %s returned empty status with unrecognized error format, response: %s", taskId, string(responseBody))) - taskResult = relaycommon.FailTaskInfo("upstream returned unrecognized message") + // Upstream may return a body without a status field in early/intermediate + // polling phases (e.g. xAI grok-imagine-video returns {"request_id":"...","id":"..."} + // before the task starts). Such a response is not an error — keep the current + // task status and wait for the next polling round instead of failing the task. + logger.LogInfo(ctx, fmt.Sprintf("Task %s returned empty status, will retry next poll. response: %s", taskId, string(responseBody))) + return nil } } }