feat(task):task增加xAI渠道grok-imagine-video模型支持#4217
feat(task):task增加xAI渠道grok-imagine-video模型支持#4217faqwang wants to merge 1 commit intoQuantumNous:mainfrom
Conversation
WalkthroughThis PR introduces support for xAI video generation by implementing a new Changes
Sequence DiagramsequenceDiagram
participant Client
participant TaskAdaptor as xAI TaskAdaptor
participant XaiAPI as xAI API
participant HTTPClient
Client->>TaskAdaptor: ValidateRequestAndSetAction()
activate TaskAdaptor
TaskAdaptor-->>Client: validation result
deactivate TaskAdaptor
Client->>TaskAdaptor: BuildRequestURL()
TaskAdaptor-->>Client: /v1/videos/generations
Client->>TaskAdaptor: BuildRequestHeader()
TaskAdaptor-->>Client: Bearer auth + Content-Type
Client->>TaskAdaptor: BuildRequestBody()
TaskAdaptor-->>Client: JSON with video params
Client->>TaskAdaptor: DoRequest()
activate TaskAdaptor
TaskAdaptor->>HTTPClient: POST request
HTTPClient->>XaiAPI: Submit video generation
XaiAPI-->>HTTPClient: {request_id, ...}
HTTPClient-->>TaskAdaptor: response
deactivate TaskAdaptor
TaskAdaptor-->>Client: response
Client->>TaskAdaptor: DoResponse()
activate TaskAdaptor
TaskAdaptor->>TaskAdaptor: Parse request_id
TaskAdaptor-->>Client: taskID + OpenAIVideo response
deactivate TaskAdaptor
loop Poll for result
Client->>TaskAdaptor: FetchTask()
activate TaskAdaptor
TaskAdaptor->>HTTPClient: GET /v1/videos/{task_id}
HTTPClient->>XaiAPI: Check status
XaiAPI-->>HTTPClient: {status, video_url, ...}
HTTPClient-->>TaskAdaptor: response
TaskAdaptor-->>Client: response
deactivate TaskAdaptor
Client->>TaskAdaptor: ParseTaskResult()
TaskAdaptor-->>Client: TaskInfo with status/URL/error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@model/task.go`:
- Around line 176-179: When handling ChannelTypeXai in the task creation branch
that currently only sets privateData.Key = relayInfo.ChannelMeta.ApiKey, also
persist the submit-time header overrides (the effective auth context) into the
task's private data so async follow-ups can reuse them; specifically, copy
relayInfo.ChannelMeta's client header / header-override field(s) (e.g.,
client_header, header_overrides) into a new or existing privateData field (e.g.,
privateData.HeaderOverrides or privateData.ClientHeaders) so
relay/channel/task/xai/adaptor.go can reconstruct requests from the full auth
context rather than just channel.Key.
In `@relay/channel/task/xai/adaptor.go`:
- Around line 79-90: The code currently counts all entries in req.Images plus a
multipart-upload image and multiplies by 0.02 units each, but BuildRequestBody
only ever forwards a single image and 0.02 units is 10x too small (0.02 USD per
image with a $0.1 base unit equals 0.2 units). Change the logic to treat images
as at-most-one-billable input: compute a boolean like hasImage :=
len(req.Images) > 0 || ExtractMultipartImageURL(c, nil) != "" and set imageCount
= 1 if hasImage else 0, then compute imageBillingUnits := float64(imageCount) *
0.2 (0.2 units per image). Update references to imageCount/imageBillingUnits and
keep ExtractMultipartImageURL and BuildRequestBody behavior unchanged.
In `@relay/channel/task/xai/helpers.go`:
- Around line 97-113: The SizeToResolution function currently maps any size
under 1920 to "720p", causing 480p inputs to be billed at a higher tier; update
SizeToResolution to parse width/height as it does now, compute maxDim from parts
(using strconv.Atoi results), then return "480p" when maxDim <= 480, "720p" when
maxDim > 480 and < 1920, and "1080p" when maxDim >= 1920 (retain the existing
fallback behavior on parse errors).
In `@relay/channel/task/xai/image.go`:
- Around line 49-50: Guard against a nil info before mutating its Action: in the
function where info.Action is set (referencing info and
constant.TaskActionGenerate in image.go and the callsite EstimateBilling() ->
ExtractMultipartImageURL in adaptor.go), add a conditional check (if info !=
nil) around the line that assigns info.Action = constant.TaskActionGenerate so
multipart input_reference flows that pass a nil info won't panic; keep the
subsequent return buildDataURL(mimeType, fileBytes) unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0adf2e99-f473-42b0-ab5f-f16e8c914863
📒 Files selected for processing (7)
model/task.gorelay/channel/task/xai/adaptor.gorelay/channel/task/xai/adaptor_test.gorelay/channel/task/xai/dto.gorelay/channel/task/xai/helpers.gorelay/channel/task/xai/image.gorelay/relay_adaptor.go
| if relayInfo.ChannelMeta.ChannelType == constant.ChannelTypeGemini || | ||
| relayInfo.ChannelMeta.ChannelType == constant.ChannelTypeVertexAi { | ||
| relayInfo.ChannelMeta.ChannelType == constant.ChannelTypeVertexAi || | ||
| relayInfo.ChannelMeta.ChannelType == constant.ChannelTypeXai { | ||
| privateData.Key = relayInfo.ChannelMeta.ApiKey |
There was a problem hiding this comment.
Persist xAI's resolved header overrides, not just the task key.
Adding privateData.Key for ChannelTypeXai is necessary, but it still leaves async follow-up requests short of the full auth context. relay/channel/task/xai/adaptor.go rebuilds polling requests from the bearer key alone, so any submit-time {client_header:*} / header-override auth will be lost after task creation.
Based on learnings, async video task flows should reuse the effective authentication context (API key + header overrides) captured at task submit time rather than re-deriving it from channel.Key at each call site.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@model/task.go` around lines 176 - 179, When handling ChannelTypeXai in the
task creation branch that currently only sets privateData.Key =
relayInfo.ChannelMeta.ApiKey, also persist the submit-time header overrides (the
effective auth context) into the task's private data so async follow-ups can
reuse them; specifically, copy relayInfo.ChannelMeta's client header /
header-override field(s) (e.g., client_header, header_overrides) into a new or
existing privateData field (e.g., privateData.HeaderOverrides or
privateData.ClientHeaders) so relay/channel/task/xai/adaptor.go can reconstruct
requests from the full auth context rather than just channel.Key.
| // 4. 统计图片数量并计算图片计费单位 | ||
| // 统计普通请求里的图片 | ||
| imageCount := len(req.Images) | ||
|
|
||
| // 如果有文件上传形式的图片,也算一张 | ||
| // (参考你代码里的 ExtractMultipartImageURL 逻辑) | ||
| if imageURL := ExtractMultipartImageURL(c, nil); imageURL != "" { | ||
| imageCount++ | ||
| } | ||
|
|
||
| // 每张图 $0.002,基准 $0.1,所以每张图合 0.02 个单位 | ||
| imageBillingUnits := float64(imageCount) * 0.02 |
There was a problem hiding this comment.
Align image billing with the single-image request shape and stated price.
This path bills len(req.Images) plus a multipart upload, but BuildRequestBody() only ever forwards one image (input_reference or req.Images[0]). It also uses 0.02 base units per image, which is $0.002 when the base unit is $0.1; the PR objective says $0.02 per image. As written, multi-image requests are billed for inputs that are dropped, and each billed image is still underpriced by 10x.
💸 Proposed fix
- // 统计普通请求里的图片
- imageCount := len(req.Images)
+ imageCount := 0
+ if len(req.Images) > 0 {
+ imageCount = 1
+ }
- // 如果有文件上传形式的图片,也算一张
- // (参考你代码里的 ExtractMultipartImageURL 逻辑)
+ // multipart input takes precedence over req.Images[0]
if imageURL := ExtractMultipartImageURL(c, nil); imageURL != "" {
- imageCount++
+ imageCount = 1
}
- // 每张图 $0.002,基准 $0.1,所以每张图合 0.02 个单位
- imageBillingUnits := float64(imageCount) * 0.02
+ // 每张图 $0.02,基准 $0.1,所以每张图合 0.2 个单位
+ imageBillingUnits := float64(imageCount) * 0.2📝 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.
| // 4. 统计图片数量并计算图片计费单位 | |
| // 统计普通请求里的图片 | |
| imageCount := len(req.Images) | |
| // 如果有文件上传形式的图片,也算一张 | |
| // (参考你代码里的 ExtractMultipartImageURL 逻辑) | |
| if imageURL := ExtractMultipartImageURL(c, nil); imageURL != "" { | |
| imageCount++ | |
| } | |
| // 每张图 $0.002,基准 $0.1,所以每张图合 0.02 个单位 | |
| imageBillingUnits := float64(imageCount) * 0.02 | |
| // 4. 统计图片数量并计算图片计费单位 | |
| imageCount := 0 | |
| if len(req.Images) > 0 { | |
| imageCount = 1 | |
| } | |
| // multipart input takes precedence over req.Images[0] | |
| if imageURL := ExtractMultipartImageURL(c, nil); imageURL != "" { | |
| imageCount = 1 | |
| } | |
| // 每张图 $0.02,基准 $0.1,所以每张图合 0.2 个单位 | |
| imageBillingUnits := float64(imageCount) * 0.2 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@relay/channel/task/xai/adaptor.go` around lines 79 - 90, The code currently
counts all entries in req.Images plus a multipart-upload image and multiplies by
0.02 units each, but BuildRequestBody only ever forwards a single image and 0.02
units is 10x too small (0.02 USD per image with a $0.1 base unit equals 0.2
units). Change the logic to treat images as at-most-one-billable input: compute
a boolean like hasImage := len(req.Images) > 0 || ExtractMultipartImageURL(c,
nil) != "" and set imageCount = 1 if hasImage else 0, then compute
imageBillingUnits := float64(imageCount) * 0.2 (0.2 units per image). Update
references to imageCount/imageBillingUnits and keep ExtractMultipartImageURL and
BuildRequestBody behavior unchanged.
| // SizeToResolution converts a "WxH" size string to a resolution label used by xAI. | ||
| func SizeToResolution(size string) string { | ||
| parts := strings.SplitN(strings.ToLower(strings.TrimSpace(size)), "x", 2) | ||
| if len(parts) != 2 { | ||
| return "720p" | ||
| } | ||
| w, _ := strconv.Atoi(parts[0]) | ||
| h, _ := strconv.Atoi(parts[1]) | ||
| maxDim := w | ||
| if h > maxDim { | ||
| maxDim = h | ||
| } | ||
| if maxDim >= 1920 { | ||
| return "1080p" | ||
| } | ||
| return "720p" | ||
| } |
There was a problem hiding this comment.
Handle a real 480p tier instead of collapsing all smaller sizes into 720p.
SizeToResolution("640x480") / ("854x480") currently returns 720p, so this adaptor can never hit the lower-resolution pricing path when the client provides a 480p-sized input. That will request and bill a higher tier than intended.
💡 Proposed direction
func SizeToResolution(size string) string {
parts := strings.SplitN(strings.ToLower(strings.TrimSpace(size)), "x", 2)
if len(parts) != 2 {
return "720p"
}
w, _ := strconv.Atoi(parts[0])
h, _ := strconv.Atoi(parts[1])
+ if w <= 0 || h <= 0 {
+ return "720p"
+ }
maxDim := w
if h > maxDim {
maxDim = h
}
if maxDim >= 1920 {
return "1080p"
}
- return "720p"
+ if maxDim >= 1280 {
+ return "720p"
+ }
+ return "480p"
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@relay/channel/task/xai/helpers.go` around lines 97 - 113, The
SizeToResolution function currently maps any size under 1920 to "720p", causing
480p inputs to be billed at a higher tier; update SizeToResolution to parse
width/height as it does now, compute maxDim from parts (using strconv.Atoi
results), then return "480p" when maxDim <= 480, "720p" when maxDim > 480 and <
1920, and "1080p" when maxDim >= 1920 (retain the existing fallback behavior on
parse errors).
| info.Action = constant.TaskActionGenerate | ||
| return buildDataURL(mimeType, fileBytes) |
There was a problem hiding this comment.
Guard info before mutating Action.
EstimateBilling() calls ExtractMultipartImageURL(c, nil) in relay/channel/task/xai/adaptor.go, so a multipart input_reference request will panic here before submission. Make the action update conditional on info != nil.
🐛 Proposed fix
- info.Action = constant.TaskActionGenerate
+ if info != nil {
+ info.Action = constant.TaskActionGenerate
+ }📝 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.
| info.Action = constant.TaskActionGenerate | |
| return buildDataURL(mimeType, fileBytes) | |
| if info != nil { | |
| info.Action = constant.TaskActionGenerate | |
| } | |
| return buildDataURL(mimeType, fileBytes) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@relay/channel/task/xai/image.go` around lines 49 - 50, Guard against a nil
info before mutating its Action: in the function where info.Action is set
(referencing info and constant.TaskActionGenerate in image.go and the callsite
EstimateBilling() -> ExtractMultipartImageURL in adaptor.go), add a conditional
check (if info != nil) around the line that assigns info.Action =
constant.TaskActionGenerate so multipart input_reference flows that pass a nil
info won't panic; keep the subsequent return buildDataURL(mimeType, fileBytes)
unchanged.
Important
📝 变更描述 / Description
支持通过/v1/video/generations调用grok-imagine-video视频生成任务,支持image.url入参,待完善引用图、视频传入模式。
计费规则硬编码在代码里,推荐模型计费基准设置grok-imagine-video按次计费,0.1/次即官方价格,官方计费规则:480p/720p计费标准分别为0.05/0.07每秒,图片0.02每张(图片价格硬编码到代码里)
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

创建任务结果:
任务完成结果:

Summary by CodeRabbit