feat(models): add support for grok-imagine-video-1.5-preview model

- Introduced `grok-imagine-video-1.5-preview` as a new XAI video model.
- Updated handlers, registry, and validation logic to include support for the new model.
- Enhanced test coverage to validate integration and functionality of the preview model.
This commit is contained in:
Luis Pater
2026-06-01 02:56:15 +08:00
parent bbcdaab79d
commit ac1360f479
5 changed files with 64 additions and 15 deletions
+6 -4
View File
@@ -269,6 +269,7 @@ func TestModelsWithClientVersionReturnsCodexCatalog(t *testing.T) {
{ID: "gpt-image-2", Object: "model", OwnedBy: "openai", Type: "openai"},
{ID: "grok-imagine-image", Object: "model", OwnedBy: "xai", Type: "openai"},
{ID: "grok-imagine-video", Object: "model", OwnedBy: "xai", Type: "openai"},
{ID: "grok-imagine-video-1.5-preview", Object: "model", OwnedBy: "xai", Type: "openai"},
})
t.Cleanup(func() {
modelRegistry.UnregisterClient(clientID)
@@ -355,10 +356,11 @@ func TestModelsWithClientVersionReturnsCodexCatalog(t *testing.T) {
}
hiddenModels := map[string]bool{
"grok-imagine-image-quality": false,
"gpt-image-2": false,
"grok-imagine-image": false,
"grok-imagine-video": false,
"grok-imagine-image-quality": false,
"gpt-image-2": false,
"grok-imagine-image": false,
"grok-imagine-video": false,
"grok-imagine-video-1.5-preview": false,
}
for _, model := range resp.Models {
slug, _ := model["slug"].(string)
+19 -5
View File
@@ -7,10 +7,11 @@ import (
)
const (
codexBuiltinImageModelID = "gpt-image-2"
xaiBuiltinImageModelID = "grok-imagine-image"
xaiBuiltinImageQualityModelID = "grok-imagine-image-quality"
xaiBuiltinVideoModelID = "grok-imagine-video"
codexBuiltinImageModelID = "gpt-image-2"
xaiBuiltinImageModelID = "grok-imagine-image"
xaiBuiltinImageQualityModelID = "grok-imagine-image-quality"
xaiBuiltinVideoModelID = "grok-imagine-video"
xaiBuiltinVideo15PreviewModelID = "grok-imagine-video-1.5-preview"
)
// staticModelsJSON mirrors the top-level structure of models.json.
@@ -99,7 +100,7 @@ func WithCodexBuiltins(models []*ModelInfo) []*ModelInfo {
// WithXAIBuiltins injects hard-coded xAI image/video model definitions that should
// not depend on remote models.json updates.
func WithXAIBuiltins(models []*ModelInfo) []*ModelInfo {
return upsertModelInfos(models, xaiBuiltinImageModelInfo(), xaiBuiltinImageQualityModelInfo(), xaiBuiltinVideoModelInfo())
return upsertModelInfos(models, xaiBuiltinImageModelInfo(), xaiBuiltinImageQualityModelInfo(), xaiBuiltinVideoModelInfo(), xaiBuiltinVideo15PreviewModelInfo())
}
func codexBuiltinImageModelInfo() *ModelInfo {
@@ -153,6 +154,19 @@ func xaiBuiltinVideoModelInfo() *ModelInfo {
}
}
func xaiBuiltinVideo15PreviewModelInfo() *ModelInfo {
return &ModelInfo{
ID: xaiBuiltinVideo15PreviewModelID,
Object: "model",
Created: 1735689600, // 2025-01-01
OwnedBy: "xai",
Type: "xai",
DisplayName: "Grok Imagine Video 1.5 Preview",
Name: xaiBuiltinVideo15PreviewModelID,
Description: "xAI Grok preview video generation model.",
}
}
func upsertModelInfos(models []*ModelInfo, extras ...*ModelInfo) []*ModelInfo {
if len(extras) == 0 {
return models
@@ -151,7 +151,7 @@ func applyCodexClientModelMetadata(entry map[string]any, id string, model map[st
func applyCodexClientVisibilityOverride(entry map[string]any, id string) {
switch strings.TrimSpace(id) {
case "grok-imagine-image-quality", "gpt-image-2", "grok-imagine-image", "grok-imagine-video":
case "grok-imagine-image-quality", "gpt-image-2", "grok-imagine-image", "grok-imagine-video", "grok-imagine-video-1.5-preview":
entry["visibility"] = "hide"
}
}
@@ -22,6 +22,7 @@ const (
xaiVideosEditsAPI = "/v1/videos/edits"
xaiVideosExtensionsAPI = "/v1/videos/extensions"
defaultXAIVideosModel = "grok-imagine-video"
xaiVideos15PreviewModel = "grok-imagine-video-1.5-preview"
xaiVideosHandlerType = "openai-video"
defaultVideosSeconds = "4"
defaultVideosSize = "720x1280"
@@ -45,7 +46,7 @@ func videosModelBase(model string) string {
func isXAIVideosModel(model string) bool {
prefix, baseModel := imagesModelParts(model)
baseModel = strings.ToLower(strings.TrimSpace(baseModel))
if baseModel != defaultXAIVideosModel {
if baseModel != defaultXAIVideosModel && baseModel != xaiVideos15PreviewModel {
return false
}
@@ -86,8 +87,11 @@ func rejectUnsupportedNativeVideosModel(c *gin.Context, model string) bool {
}
func canonicalXAIVideosModel(model string) string {
if videosModelBase(model) == defaultXAIVideosModel {
switch videosModelBase(model) {
case defaultXAIVideosModel:
return defaultXAIVideosModel
case xaiVideos15PreviewModel:
return xaiVideos15PreviewModel
}
return defaultXAIVideosModel
}
@@ -190,8 +194,9 @@ func buildXAIVideosCreateRequest(rawJSON []byte, model string) ([]byte, xaiVideo
seconds = "10"
}
videoModel := canonicalXAIVideosModel(model)
req := []byte(`{}`)
req, _ = sjson.SetBytes(req, "model", canonicalXAIVideosModel(model))
req, _ = sjson.SetBytes(req, "model", videoModel)
req, _ = sjson.SetBytes(req, "prompt", prompt)
req, _ = sjson.SetRawBytes(req, "duration", []byte(strconv.FormatInt(duration, 10)))
req, _ = sjson.SetBytes(req, "aspect_ratio", aspectRatio)
@@ -204,7 +209,7 @@ func buildXAIVideosCreateRequest(rawJSON []byte, model string) ([]byte, xaiVideo
}
meta := xaiVideoCreateMetadata{
Model: defaultXAIVideosModel,
Model: videoModel,
Prompt: prompt,
Seconds: seconds,
Size: size,
@@ -33,7 +33,16 @@ func performVideosEndpointRequest(t *testing.T, method string, endpointPath stri
}
func TestVideosModelValidationAllowsXAIVideoModel(t *testing.T) {
for _, model := range []string{"grok-imagine-video", "xai/grok-imagine-video", "x-ai/grok-imagine-video", "grok/grok-imagine-video"} {
for _, model := range []string{
"grok-imagine-video",
"xai/grok-imagine-video",
"x-ai/grok-imagine-video",
"grok/grok-imagine-video",
"grok-imagine-video-1.5-preview",
"xai/grok-imagine-video-1.5-preview",
"x-ai/grok-imagine-video-1.5-preview",
"grok/grok-imagine-video-1.5-preview",
} {
if !isSupportedVideosModel(model) {
t.Fatalf("expected %s to be supported", model)
}
@@ -44,6 +53,9 @@ func TestVideosModelValidationAllowsXAIVideoModel(t *testing.T) {
if isSupportedVideosModel("codex/grok-imagine-video") {
t.Fatal("expected codex/grok-imagine-video to be rejected")
}
if isSupportedVideosModel("codex/grok-imagine-video-1.5-preview") {
t.Fatal("expected codex/grok-imagine-video-1.5-preview to be rejected")
}
}
func TestBuildXAIVideosCreateRequest(t *testing.T) {
@@ -77,6 +89,22 @@ func TestBuildXAIVideosCreateRequest(t *testing.T) {
}
}
func TestBuildXAIVideosCreateRequestAllowsPreviewModel(t *testing.T) {
rawJSON := []byte(`{"model":"xai/grok-imagine-video-1.5-preview","prompt":"a cat playing piano","seconds":"8"}`)
req, meta, err := buildXAIVideosCreateRequest(rawJSON, "xai/grok-imagine-video-1.5-preview")
if err != nil {
t.Fatalf("buildXAIVideosCreateRequest() error = %v", err)
}
if got := gjson.GetBytes(req, "model").String(); got != xaiVideos15PreviewModel {
t.Fatalf("model = %q, want %s", got, xaiVideos15PreviewModel)
}
if meta.Model != xaiVideos15PreviewModel {
t.Fatalf("meta model = %q, want %s", meta.Model, xaiVideos15PreviewModel)
}
}
func TestBuildXAIVideosCreateRequestAllowsCustomSeconds(t *testing.T) {
rawJSON := []byte(`{"model":"grok-imagine-video","prompt":"a cat playing piano","seconds":"6"}`)