feat(task):task增加xAI渠道grok-imagine-video模型支持 - #4217
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
WalkthroughAdds a new xAI TaskAdaptor (request/response lifecycle, billing, polling), helpers for video attributes and image inputs, tests for billing estimation, and registers ChannelTypeXai in InitTask and GetTaskAdaptor. ChangesxAI TaskAdaptor
Sequence DiagramsequenceDiagram
participant Client
participant TaskAdaptor as xAI TaskAdaptor
participant HTTPClient
participant XaiAPI as xAI API
Client->>TaskAdaptor: ValidateRequestAndSetAction()
TaskAdaptor-->>Client: validation result
Client->>TaskAdaptor: BuildRequestURL/BuildRequestHeader/BuildRequestBody
TaskAdaptor-->>Client: URL, headers, JSON body
Client->>TaskAdaptor: DoRequest()
TaskAdaptor->>HTTPClient: POST /v1/videos/generations (Bearer + JSON)
HTTPClient->>XaiAPI: submit video generation
XaiAPI-->>HTTPClient: {request_id,...}
HTTPClient-->>TaskAdaptor: response
TaskAdaptor-->>Client: taskID + OpenAIVideo response
loop Polling
Client->>TaskAdaptor: FetchTask()
TaskAdaptor->>HTTPClient: GET /v1/videos/{task_id} (Bearer)
HTTPClient->>XaiAPI: check status
XaiAPI-->>HTTPClient: {status, video_url, ...}
HTTPClient-->>TaskAdaptor: response
TaskAdaptor->>Client: ParseTaskResult -> TaskInfo (status/URL/error)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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.
移植上游 PR QuantumNous#4217,并修复其三处缺陷。 1. nil 解引用崩溃(生产路径) EstimateBilling 以 nil info 调用 ExtractMultipartImageURL,而该函数 末尾无条件写 info.Action。用 multipart 上传 input_reference 调用 xAI 视频生成即触发 nil 解引用。改为仅在 info 非 nil 时设置 Action, 并补 c/c.Request 的 nil 防御(gin 的 MultipartForm 会解引用 c.Request,该函数其余错误路径均返回空串,唯独这里会崩)。 2. 时长缺少上界(计费安全) ResolveDurationSeconds 的 metadata/duration/seconds 各路径均无上界, 用户可用超大 duration 直接放大计费乘数。按 gemini/aiai/ali/doubao 的既有做法钳到 MaxTaskDurationSeconds;float64 分支先比较再转换, 避免超范围转 int 的未定义行为。 3. 自带测试从未通过 原测试断言 ratios["seconds"],但 EstimateBilling 返回的 key 是 total_units,取到零值必然失败;且会走进上述 nil 解引用而 panic。 按 testify 规范重写为表驱动,并补时长钳制的回归覆盖。 同时清理开发期遗留的第二人称口语注释。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
移植 QuantumNous#4217 时留下的两处计费与实际不符,均由 review 查出。 1. 时长钳制被绕过 我此前只钳了 ResolveDurationSeconds,但 BuildRequestBody 随后又用 meta.Duration 覆盖了结果,且未再钳。二者读的是同一个 metadata.duration 字段,所以 {"metadata":{"duration":100000}} 会按 3600 秒计费、却把 100000 发给上游,差额由平台承担。该覆盖分支本就多余——ResolveDurationSeconds 已按优先级取值并钳制,且覆盖面更广(还认字符串形式),直接删掉。 2. 按 N 张图收费但只发 1 张 上游 VideoGenerationRequest 只接受一张输入图,BuildRequestBody 也只在 multipart 与 Images[0] 之间二选一,多传的图片既不发给上游也不产生成本, 却按 len(req.Images) 计费,且该数量无上界(违反 AGENTS.md 对用户可控 计费乘数必须有界的要求)。改为按实际发送张数计费,这同时让该乘数天然有界。 对应地修正测试:原用例断言"两张图收两份",把多收费钉成了正确行为;改为 断言多余图片不计费。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
New Features
Tests