History

Merge pull request #4668 from oscarbrey/fix/grok-imagine-video-1.5-ga

fix(xai): support Grok Imagine Video 1.5 GA

Luis Pater committed 3 months ago

Commit 32f10692 parents f27be00 84232746 files · +263 −60Browse files

Unified · Split

6 files
  1. internal/api/server_test.go +2 −0
  2. internal/client/codex/models/models.go +1 −1
  3. internal/registry/model_definitions.go +24 −10
  4. internal/registry/model_definitions_test.go +14 −4
  5. sdk/api/handlers/openai/openai_videos_handlers.go +42 −24
  6. sdk/api/handlers/openai/openai_videos_handlers_test.go +180 −21
internal/api/server_test.go+2−0
@@ -1648,6 +1648,7 @@ func TestModelsWithClientVersionReturnsCodexCatalog(t *testing.T) { · 1647 unmodified lines
1648 {ID: "gpt-image-2", Object: "model", OwnedBy: "openai", Type: "openai"},1648 {ID: "gpt-image-2", Object: "model", OwnedBy: "openai", Type: "openai"},
1649 {ID: "grok-imagine-image", Object: "model", OwnedBy: "xai", Type: "openai"},1649 {ID: "grok-imagine-image", Object: "model", OwnedBy: "xai", Type: "openai"},
1650 {ID: "grok-imagine-video", Object: "model", OwnedBy: "xai", Type: "openai"},1650 {ID: "grok-imagine-video", Object: "model", OwnedBy: "xai", Type: "openai"},
1651 {ID: "grok-imagine-video-1.5", Object: "model", OwnedBy: "xai", Type: "openai"},
1651 {ID: "grok-imagine-video-1.5-preview", Object: "model", OwnedBy: "xai", Type: "openai"},1652 {ID: "grok-imagine-video-1.5-preview", Object: "model", OwnedBy: "xai", Type: "openai"},
1652 })1653 })
1653 t.Cleanup(func() {1654 t.Cleanup(func() {
@@ -1747,6 +1748,7 @@ func TestModelsWithClientVersionReturnsCodexCatalog(t *testing.T) { · 93 unmodified lines
1747 "gpt-image-2": false,1748 "gpt-image-2": false,
1748 "grok-imagine-image": false,1749 "grok-imagine-image": false,
1749 "grok-imagine-video": false,1750 "grok-imagine-video": false,
1751 "grok-imagine-video-1.5": false,
1750 "grok-imagine-video-1.5-preview": false,1752 "grok-imagine-video-1.5-preview": false,
1751 }1753 }
1752 for _, model := range resp.Models {1754 for _, model := range resp.Models {
internal/client/codex/models/models.go+1−1
@@ -286,7 +286,7 @@ func applyCodexClientModelMetadata(entry map[string]any, id string, model map[st · 285 unmodified lines
286 286
287func applyCodexClientVisibilityOverride(entry map[string]any, id string) {287func applyCodexClientVisibilityOverride(entry map[string]any, id string) {
288 switch strings.TrimSpace(id) {288 switch strings.TrimSpace(id) {
289 case "grok-imagine-image-quality", "gpt-image-1.5", "gpt-image-2", "grok-imagine-image", "grok-imagine-video", "grok-imagine-video-1.5-preview":289 case "grok-imagine-image-quality", "gpt-image-1.5", "gpt-image-2", "grok-imagine-image", "grok-imagine-video", "grok-imagine-video-1.5", "grok-imagine-video-1.5-preview":
290 entry["visibility"] = "hide"290 entry["visibility"] = "hide"
291 }291 }
292}292}
internal/registry/model_definitions.go+24−10
@@ -7,12 +7,13 @@ import ( · 6 unmodified lines
7)7)
8 8
9const (9const (
10 codexBuiltinImage15ModelID = "gpt-image-1.5"10 codexBuiltinImage15ModelID = "gpt-image-1.5"
11 codexBuiltinImageModelID = "gpt-image-2"11 codexBuiltinImageModelID = "gpt-image-2"
12 xaiBuiltinImageModelID = "grok-imagine-image"12 xaiBuiltinImageModelID = "grok-imagine-image"
13 xaiBuiltinImageQualityModelID = "grok-imagine-image-quality"13 xaiBuiltinImageQualityModelID = "grok-imagine-image-quality"
14 xaiBuiltinVideoModelID = "grok-imagine-video"14 xaiBuiltinVideoModelID = "grok-imagine-video"
15 xaiBuiltinVideo15PreviewModelID = "grok-imagine-video-1.5-preview"15 xaiBuiltinVideo15ModelID = "grok-imagine-video-1.5"
16 xaiBuiltinVideo15PreviewID = "grok-imagine-video-1.5-preview"
16)17)
17 18
18// staticModelsJSON mirrors the top-level structure of models.json.19// staticModelsJSON mirrors the top-level structure of models.json.
@@ -120,7 +121,7 @@ func WithCodexBuiltins(models []*ModelInfo) []*ModelInfo { · 101 unmodified lines
120// WithXAIBuiltins injects hard-coded xAI image/video model definitions that should121// WithXAIBuiltins injects hard-coded xAI image/video model definitions that should
121// not depend on remote models.json updates.122// not depend on remote models.json updates.
122func WithXAIBuiltins(models []*ModelInfo) []*ModelInfo {123func WithXAIBuiltins(models []*ModelInfo) []*ModelInfo {
123 return upsertModelInfos(models, xaiBuiltinImageModelInfo(), xaiBuiltinImageQualityModelInfo(), xaiBuiltinVideoModelInfo(), xaiBuiltinVideo15PreviewModelInfo())124 return upsertModelInfos(models, xaiBuiltinImageModelInfo(), xaiBuiltinImageQualityModelInfo(), xaiBuiltinVideoModelInfo(), xaiBuiltinVideo15ModelInfo(), xaiBuiltinVideo15PreviewModelInfo())
124}125}
125 126
126func normalizeAntigravityCapabilityModelID(modelID string) string {127func normalizeAntigravityCapabilityModelID(modelID string) string {
@@ -194,16 +195,29 @@ func xaiBuiltinVideoModelInfo() *ModelInfo { · 67 unmodified lines
194 }195 }
195}196}
196 197
198func xaiBuiltinVideo15ModelInfo() *ModelInfo {
199 return &ModelInfo{
200 ID: xaiBuiltinVideo15ModelID,
201 Object: "model",
202 Created: 1735689600, // 2025-01-01
203 OwnedBy: "xai",
204 Type: "xai",
205 DisplayName: "Grok Imagine Video 1.5",
206 Name: xaiBuiltinVideo15ModelID,
207 Description: "xAI Grok video generation model.",
208 }
209}
210
197func xaiBuiltinVideo15PreviewModelInfo() *ModelInfo {211func xaiBuiltinVideo15PreviewModelInfo() *ModelInfo {
198 return &ModelInfo{212 return &ModelInfo{
199 ID: xaiBuiltinVideo15PreviewModelID,213 ID: xaiBuiltinVideo15PreviewID,
200 Object: "model",214 Object: "model",
201 Created: 1735689600, // 2025-01-01215 Created: 1735689600, // 2025-01-01
202 OwnedBy: "xai",216 OwnedBy: "xai",
203 Type: "xai",217 Type: "xai",
204 DisplayName: "Grok Imagine Video 1.5 Preview",218 DisplayName: "Grok Imagine Video 1.5 Preview",
205 Name: xaiBuiltinVideo15PreviewModelID,219 Name: xaiBuiltinVideo15PreviewID,
206 Description: "xAI Grok preview video generation model.",220 Description: "Compatibility alias for the xAI Grok video generation model.",
207 }221 }
208}222}
209 223
internal/registry/model_definitions_test.go+14−4
@@ -35,19 +35,29 @@ func TestGeminiVertexModelsUseFlashLiteReleaseID(t *testing.T) { · 34 unmodified lines
35 t.Fatalf("Vertex models do not contain %q", releaseID)35 t.Fatalf("Vertex models do not contain %q", releaseID)
36}36}
37 37
38func TestWithXAIBuiltinsIncludesVideoPreviewModel(t *testing.T) {38func TestWithXAIBuiltinsIncludesVideo15GAAndPreviewAlias(t *testing.T) {
39 models := WithXAIBuiltins(nil)39 models := WithXAIBuiltins(nil)
40 foundGA := false
41 foundPreviewAlias := false
40 42
41 for _, model := range models {43 for _, model := range models {
42 if model == nil {44 if model == nil {
43 continue45 continue
44 }46 }
45 if model.ID == xaiBuiltinVideo15PreviewModelID {47 if model.ID == xaiBuiltinVideo15ModelID {
46 return48 foundGA = true
49 }
50 if model.ID == xaiBuiltinVideo15PreviewID {
51 foundPreviewAlias = true
47 }52 }
48 }53 }
49 54
50 t.Fatalf("expected xAI builtin model %s", xaiBuiltinVideo15PreviewModelID)55 if !foundGA {
56 t.Fatalf("expected xAI builtin model %s", xaiBuiltinVideo15ModelID)
57 }
58 if !foundPreviewAlias {
59 t.Fatalf("expected xAI builtin compatibility alias %s", xaiBuiltinVideo15PreviewID)
60 }
51}61}
52 62
53func TestAntigravityWebSearchModelForRequiresRequestedModelCapability(t *testing.T) {63func TestAntigravityWebSearchModelForRequiresRequestedModelCapability(t *testing.T) {
sdk/api/handlers/openai/openai_videos_handlers.go+42−24
@@ -32,7 +32,8 @@ const ( · 31 unmodified lines
32 xaiVideosExtensionsAPI = "/v1/videos/extensions"32 xaiVideosExtensionsAPI = "/v1/videos/extensions"
33 defaultOpenAIVideosModel = "sora-2"33 defaultOpenAIVideosModel = "sora-2"
34 defaultXAIVideosModel = "grok-imagine-video"34 defaultXAIVideosModel = "grok-imagine-video"
35 xaiVideos15PreviewModel = "grok-imagine-video-1.5-preview"35 xaiVideos15Model = "grok-imagine-video-1.5"
36 xaiVideos15PreviewAlias = "grok-imagine-video-1.5-preview"
36 xaiVideosHandlerType = "openai-video"37 xaiVideosHandlerType = "openai-video"
37 defaultVideosSeconds = "4"38 defaultVideosSeconds = "4"
38 defaultVideosSize = "720x1280"39 defaultVideosSize = "720x1280"
@@ -45,12 +46,12 @@ const defaultVideoAuthBindingTTL = 3 * time.Hour · 6 unmodified lines
45var videoAuthBindings = newVideoAuthBindingStore()46var videoAuthBindings = newVideoAuthBindingStore()
46 47
47type xaiVideoCreateMetadata struct {48type xaiVideoCreateMetadata struct {
48 Model string49 Model string
49 UpstreamModel string50 RoutingModel string
50 Prompt string51 Prompt string
51 Seconds string52 Seconds string
52 Size string53 Size string
53 CreatedAt int6454 CreatedAt int64
54}55}
55 56
56type videoAuthBinding struct {57type videoAuthBinding struct {
@@ -147,7 +148,7 @@ func videosModelBase(model string) string { · 90 unmodified lines
147func isXAIVideosModel(model string) bool {148func isXAIVideosModel(model string) bool {
148 prefix, baseModel := imagesModelParts(model)149 prefix, baseModel := imagesModelParts(model)
149 baseModel = strings.ToLower(strings.TrimSpace(baseModel))150 baseModel = strings.ToLower(strings.TrimSpace(baseModel))
150 if baseModel != defaultXAIVideosModel && baseModel != xaiVideos15PreviewModel {151 if baseModel != defaultXAIVideosModel && baseModel != xaiVideos15Model && baseModel != xaiVideos15PreviewAlias {
151 return false152 return false
152 }153 }
153 154
@@ -199,8 +200,23 @@ func canonicalXAIVideosModel(model string) string { · 45 unmodified lines
199 switch videosModelBase(model) {200 switch videosModelBase(model) {
200 case defaultXAIVideosModel:201 case defaultXAIVideosModel:
201 return defaultXAIVideosModel202 return defaultXAIVideosModel
202 case xaiVideos15PreviewModel:203 case xaiVideos15Model, xaiVideos15PreviewAlias:
203 return xaiVideos15PreviewModel204 return xaiVideos15Model
205 }
206 return defaultXAIVideosModel
207}
208
209func routingXAIVideosModel(model string) string {
210 if isSoraVideosModel(model) {
211 return defaultXAIVideosModel
212 }
213 switch videosModelBase(model) {
214 case defaultXAIVideosModel:
215 return defaultXAIVideosModel
216 case xaiVideos15Model:
217 return xaiVideos15Model
218 case xaiVideos15PreviewAlias:
219 return xaiVideos15PreviewAlias
204 }220 }
205 return defaultXAIVideosModel221 return defaultXAIVideosModel
206}222}
@@ -298,11 +314,11 @@ func (h *OpenAIAPIHandler) bindVideoAuthIDAndModelFromPayload(payload []byte, au · 91 unmodified lines
298 if videoID == "" {314 if videoID == "" {
299 return315 return
300 }316 }
301 videoAuthBindings.setWithModel(videoID, authID, canonicalXAIVideosModel(model), h.videoAuthBindingTTL())317 videoAuthBindings.setWithModel(videoID, authID, routingXAIVideosModel(model), h.videoAuthBindingTTL())
302}318}
303 319
304func (h *OpenAIAPIHandler) bindVideoAuthID(videoID string, authID string, model string) {320func (h *OpenAIAPIHandler) bindVideoAuthID(videoID string, authID string, model string) {
305 videoAuthBindings.setWithModel(videoID, authID, canonicalXAIVideosModel(model), h.videoAuthBindingTTL())321 videoAuthBindings.setWithModel(videoID, authID, routingXAIVideosModel(model), h.videoAuthBindingTTL())
306}322}
307 323
308func (h *OpenAIAPIHandler) contextWithVideoAuthBinding(ctx context.Context, videoID string) context.Context {324func (h *OpenAIAPIHandler) contextWithVideoAuthBinding(ctx context.Context, videoID string) context.Context {
@@ -374,12 +390,12 @@ func buildXAIVideosCreateRequest(rawJSON []byte, model string) ([]byte, xaiVideo · 65 unmodified lines
374 }390 }
375 391
376 meta := xaiVideoCreateMetadata{392 meta := xaiVideoCreateMetadata{
377 Model: responseVideosModel(model),393 Model: responseVideosModel(model),
378 UpstreamModel: videoModel,394 RoutingModel: routingXAIVideosModel(model),
379 Prompt: prompt,395 Prompt: prompt,
380 Seconds: seconds,396 Seconds: seconds,
381 Size: size,397 Size: size,
382 CreatedAt: time.Now().Unix(),398 CreatedAt: time.Now().Unix(),
383 }399 }
384 return req, meta, nil400 return req, meta, nil
385}401}
@@ -732,7 +748,9 @@ func (h *OpenAIAPIHandler) handleXAIVideosNativePost(c *gin.Context) { · 346 unmodified lines
732 return748 return
733 }749 }
734 750
735 h.collectXAIVideosNative(c, rawJSON, videoModel, true)751 routingModel := routingXAIVideosModel(videoModel)
752 rawJSON, _ = sjson.SetBytes(rawJSON, "model", canonicalXAIVideosModel(videoModel))
753 h.collectXAIVideosNative(c, rawJSON, routingModel, true)
736}754}
737 755
738func (h *OpenAIAPIHandler) XAIVideosRetrieve(c *gin.Context) {756func (h *OpenAIAPIHandler) XAIVideosRetrieve(c *gin.Context) {
@@ -995,12 +1013,12 @@ func (h *OpenAIAPIHandler) collectXAIVideosCreate(c *gin.Context, xaiReq []byte, · 256 unmodified lines
995 cliCtx = handlers.WithSelectedAuthIDCallback(cliCtx, func(authID string) {1013 cliCtx = handlers.WithSelectedAuthIDCallback(cliCtx, func(authID string) {
996 selectedAuthID = authID1014 selectedAuthID = authID
997 })1015 })
998 upstreamModel := strings.TrimSpace(meta.UpstreamModel)1016 routingModel := strings.TrimSpace(meta.RoutingModel)
999 if upstreamModel == "" {1017 if routingModel == "" {
1000 upstreamModel = meta.Model1018 routingModel = routingXAIVideosModel(meta.Model)
1001 }1019 }
1002 stopKeepAlive := h.StartNonStreamingKeepAlive(c, cliCtx)1020 stopKeepAlive := h.StartNonStreamingKeepAlive(c, cliCtx)
1003 resp, upstreamHeaders, errMsg := h.ExecuteWithAuthManager(cliCtx, xaiVideosHandlerType, upstreamModel, xaiReq, "")1021 resp, upstreamHeaders, errMsg := h.ExecuteWithAuthManager(cliCtx, xaiVideosHandlerType, routingModel, xaiReq, "")
1004 stopKeepAlive()1022 stopKeepAlive()
1005 if errMsg != nil {1023 if errMsg != nil {
1006 h.WriteErrorResponse(c, errMsg)1024 h.WriteErrorResponse(c, errMsg)
@@ -1020,7 +1038,7 @@ func (h *OpenAIAPIHandler) collectXAIVideosCreate(c *gin.Context, xaiReq []byte, · 13 unmodified lines
1020 return1038 return
1021 }1039 }
1022 1040
1023 h.bindVideoAuthIDFromPayload(out, selectedAuthID)1041 h.bindVideoAuthIDAndModelFromPayload(out, selectedAuthID, routingModel)
1024 handlers.WriteUpstreamHeaders(c.Writer.Header(), upstreamHeaders)1042 handlers.WriteUpstreamHeaders(c.Writer.Header(), upstreamHeaders)
1025 _, _ = c.Writer.Write(out)1043 _, _ = c.Writer.Write(out)
1026 cliCancel(nil)1044 cliCancel(nil)
sdk/api/handlers/openai/openai_videos_handlers_test.go+180−21
@@ -63,11 +63,12 @@ func performVideosRouteRequest(t *testing.T, method string, routePath string, re · 62 unmodified lines
63}63}
64 64
65type videoAuthCaptureExecutor struct {65type videoAuthCaptureExecutor struct {
66 mu sync.Mutex66 mu sync.Mutex
67 requestID string67 requestID string
68 contentURL string68 contentURL string
69 authIDs []string69 authIDs []string
70 models []string70 models []string
71 payloadModels []string
71}72}
72 73
73func (e *videoAuthCaptureExecutor) Identifier() string { return "xai" }74func (e *videoAuthCaptureExecutor) Identifier() string { return "xai" }
@@ -80,6 +81,7 @@ func (e *videoAuthCaptureExecutor) Execute(_ context.Context, auth *coreauth.Aut · 6 unmodified lines
80 e.mu.Lock()81 e.mu.Lock()
81 e.authIDs = append(e.authIDs, authID)82 e.authIDs = append(e.authIDs, authID)
82 e.models = append(e.models, req.Model)83 e.models = append(e.models, req.Model)
84 e.payloadModels = append(e.payloadModels, strings.TrimSpace(gjson.GetBytes(req.Payload, "model").String()))
83 e.mu.Unlock()85 e.mu.Unlock()
84 86
85 requestID := strings.TrimSpace(gjson.GetBytes(req.Payload, "request_id").String())87 requestID := strings.TrimSpace(gjson.GetBytes(req.Payload, "request_id").String())
@@ -126,6 +128,14 @@ func (e *videoAuthCaptureExecutor) Models() []string { · 40 unmodified lines
126 return out128 return out
127}129}
128 130
131func (e *videoAuthCaptureExecutor) PayloadModels() []string {
132 e.mu.Lock()
133 defer e.mu.Unlock()
134 out := make([]string, len(e.payloadModels))
135 copy(out, e.payloadModels)
136 return out
137}
138
129func resetVideoAuthBindingsForTest(t *testing.T) {139func resetVideoAuthBindingsForTest(t *testing.T) {
130 t.Helper()140 t.Helper()
131 previous := videoAuthBindings141 previous := videoAuthBindings
@@ -170,6 +180,10 @@ func TestVideosModelValidationAllowsXAIVideoModel(t *testing.T) { · 38 unmodified lines
170 "xai/grok-imagine-video",180 "xai/grok-imagine-video",
171 "x-ai/grok-imagine-video",181 "x-ai/grok-imagine-video",
172 "grok/grok-imagine-video",182 "grok/grok-imagine-video",
183 "grok-imagine-video-1.5",
184 "xai/grok-imagine-video-1.5",
185 "x-ai/grok-imagine-video-1.5",
186 "grok/grok-imagine-video-1.5",
173 "grok-imagine-video-1.5-preview",187 "grok-imagine-video-1.5-preview",
174 "xai/grok-imagine-video-1.5-preview",188 "xai/grok-imagine-video-1.5-preview",
175 "x-ai/grok-imagine-video-1.5-preview",189 "x-ai/grok-imagine-video-1.5-preview",
@@ -188,6 +202,9 @@ func TestVideosModelValidationAllowsXAIVideoModel(t *testing.T) { · 12 unmodified lines
188 if isSupportedVideosModel("codex/grok-imagine-video") {202 if isSupportedVideosModel("codex/grok-imagine-video") {
189 t.Fatal("expected codex/grok-imagine-video to be rejected")203 t.Fatal("expected codex/grok-imagine-video to be rejected")
190 }204 }
205 if isSupportedVideosModel("codex/grok-imagine-video-1.5") {
206 t.Fatal("expected codex/grok-imagine-video-1.5 to be rejected")
207 }
191 if isSupportedVideosModel("codex/grok-imagine-video-1.5-preview") {208 if isSupportedVideosModel("codex/grok-imagine-video-1.5-preview") {
192 t.Fatal("expected codex/grok-imagine-video-1.5-preview to be rejected")209 t.Fatal("expected codex/grok-imagine-video-1.5-preview to be rejected")
193 }210 }
@@ -240,7 +257,26 @@ func TestBuildXAIVideosCreateRequest(t *testing.T) { · 46 unmodified lines
240 }257 }
241}258}
242 259
243func TestBuildXAIVideosCreateRequestAllowsPreviewModel(t *testing.T) {260func TestBuildXAIVideosCreateRequestAllowsVideo15Model(t *testing.T) {
261 rawJSON := []byte(`{"model":"xai/grok-imagine-video-1.5","prompt":"a cat playing piano","seconds":"8"}`)
262
263 req, meta, err := buildXAIVideosCreateRequest(rawJSON, "xai/grok-imagine-video-1.5")
264 if err != nil {
265 t.Fatalf("buildXAIVideosCreateRequest() error = %v", err)
266 }
267
268 if got := gjson.GetBytes(req, "model").String(); got != xaiVideos15Model {
269 t.Fatalf("model = %q, want %s", got, xaiVideos15Model)
270 }
271 if meta.Model != xaiVideos15Model {
272 t.Fatalf("meta model = %q, want %s", meta.Model, xaiVideos15Model)
273 }
274 if meta.RoutingModel != xaiVideos15Model {
275 t.Fatalf("routing model = %q, want %s", meta.RoutingModel, xaiVideos15Model)
276 }
277}
278
279func TestBuildXAIVideosCreateRequestNormalizesVideo15PreviewAlias(t *testing.T) {
244 rawJSON := []byte(`{"model":"xai/grok-imagine-video-1.5-preview","prompt":"a cat playing piano","seconds":"8"}`)280 rawJSON := []byte(`{"model":"xai/grok-imagine-video-1.5-preview","prompt":"a cat playing piano","seconds":"8"}`)
245 281
246 req, meta, err := buildXAIVideosCreateRequest(rawJSON, "xai/grok-imagine-video-1.5-preview")282 req, meta, err := buildXAIVideosCreateRequest(rawJSON, "xai/grok-imagine-video-1.5-preview")
@@ -248,11 +284,14 @@ func TestBuildXAIVideosCreateRequestAllowsPreviewModel(t *testing.T) { · 1 unmodified line
248 t.Fatalf("buildXAIVideosCreateRequest() error = %v", err)284 t.Fatalf("buildXAIVideosCreateRequest() error = %v", err)
249 }285 }
250 286
251 if got := gjson.GetBytes(req, "model").String(); got != xaiVideos15PreviewModel {287 if got := gjson.GetBytes(req, "model").String(); got != xaiVideos15Model {
252 t.Fatalf("model = %q, want %s", got, xaiVideos15PreviewModel)288 t.Fatalf("model = %q, want %s", got, xaiVideos15Model)
289 }
290 if meta.Model != xaiVideos15Model {
291 t.Fatalf("meta model = %q, want %s", meta.Model, xaiVideos15Model)
253 }292 }
254 if meta.Model != xaiVideos15PreviewModel {293 if meta.RoutingModel != xaiVideos15PreviewAlias {
255 t.Fatalf("meta model = %q, want %s", meta.Model, xaiVideos15PreviewModel)294 t.Fatalf("routing model = %q, want %s", meta.RoutingModel, xaiVideos15PreviewAlias)
256 }295 }
257}296}
258 297
@@ -734,9 +773,9 @@ func TestXAIVideosNativeCreateBindsRetrieveToSelectedAuth(t *testing.T) { · 475 unmodified lines
734 }773 }
735}774}
736 775
737func TestXAIVideosNativeRetrieveUsesBoundModel(t *testing.T) {776func TestXAIVideosNativeRetrieveUsesCanonicalBoundModel(t *testing.T) {
738 resetVideoAuthBindingsForTest(t)777 resetVideoAuthBindingsForTest(t)
739 executor := &videoAuthCaptureExecutor{requestID: "video-xai-preview-bound"}778 executor := &videoAuthCaptureExecutor{requestID: "video-xai-1.5-bound"}
740 manager := coreauth.NewManager(nil, &coreauth.RoundRobinSelector{}, nil)779 manager := coreauth.NewManager(nil, &coreauth.RoundRobinSelector{}, nil)
741 manager.RegisterExecutor(executor)780 manager.RegisterExecutor(executor)
742 781
@@ -744,8 +783,8 @@ func TestXAIVideosNativeRetrieveUsesBoundModel(t *testing.T) { · 1 unmodified line
744 authID string783 authID string
745 model string784 model string
746 }{785 }{
747 {authID: "video-xai-preview-default-auth", model: defaultXAIVideosModel},786 {authID: "video-xai-1.5-default-auth", model: defaultXAIVideosModel},
748 {authID: "video-xai-preview-auth", model: xaiVideos15PreviewModel},787 {authID: "video-xai-1.5-auth", model: xaiVideos15Model},
749 }788 }
750 for _, entry := range authModels {789 for _, entry := range authModels {
751 auth := &coreauth.Auth{790 auth := &coreauth.Auth{
@@ -768,7 +807,7 @@ func TestXAIVideosNativeRetrieveUsesBoundModel(t *testing.T) { · 16 unmodified lines
768 base := apihandlers.NewBaseAPIHandlers(&sdkconfig.SDKConfig{}, manager)807 base := apihandlers.NewBaseAPIHandlers(&sdkconfig.SDKConfig{}, manager)
769 handler := NewOpenAIAPIHandler(base)808 handler := NewOpenAIAPIHandler(base)
770 809
771 createResp := performVideosEndpointRequest(t, http.MethodPost, xaiVideosGenerationsAPI, "application/json", strings.NewReader(`{"model":"grok-imagine-video-1.5-preview","prompt":"make a video"}`), handler.XAIVideosGenerations)810 createResp := performVideosEndpointRequest(t, http.MethodPost, xaiVideosGenerationsAPI, "application/json", strings.NewReader(`{"model":"grok-imagine-video-1.5","prompt":"make a video"}`), handler.XAIVideosGenerations)
772 if createResp.Code != http.StatusOK {811 if createResp.Code != http.StatusOK {
773 t.Fatalf("create status = %d, want %d: %s", createResp.Code, http.StatusOK, createResp.Body.String())812 t.Fatalf("create status = %d, want %d: %s", createResp.Code, http.StatusOK, createResp.Body.String())
774 }813 }
@@ -786,22 +825,142 @@ func TestXAIVideosNativeRetrieveUsesBoundModel(t *testing.T) { · 11 unmodified lines
786 if len(authIDs) != 2 {825 if len(authIDs) != 2 {
787 t.Fatalf("authIDs = %v, want two calls", authIDs)826 t.Fatalf("authIDs = %v, want two calls", authIDs)
788 }827 }
789 if authIDs[0] != "video-xai-preview-auth" || authIDs[1] != authIDs[0] {828 if authIDs[0] != "video-xai-1.5-auth" || authIDs[1] != authIDs[0] {
790 t.Fatalf("authIDs = %v, want both calls to use video-xai-preview-auth", authIDs)829 t.Fatalf("authIDs = %v, want both calls to use video-xai-1.5-auth", authIDs)
791 }830 }
792 models := executor.Models()831 models := executor.Models()
793 if len(models) != 2 {832 if len(models) != 2 {
794 t.Fatalf("models = %v, want two calls", models)833 t.Fatalf("models = %v, want two calls", models)
795 }834 }
796 if models[0] != xaiVideos15PreviewModel || models[1] != xaiVideos15PreviewModel {835 if models[0] != xaiVideos15Model || models[1] != xaiVideos15Model {
797 t.Fatalf("models = %v, want both calls to use %s", models, xaiVideos15PreviewModel)836 t.Fatalf("models = %v, want both calls to use %s", models, xaiVideos15Model)
837 }
838 payloadModels := executor.PayloadModels()
839 if len(payloadModels) != 2 || payloadModels[0] != xaiVideos15Model {
840 t.Fatalf("payload models = %v, want create payload model %s", payloadModels, xaiVideos15Model)
841 }
842 binding, ok := videoAuthBindings.getBinding(videoID)
843 if !ok {
844 t.Fatal("video auth binding was not stored")
845 }
846 if binding.authID != "video-xai-1.5-auth" || binding.model != xaiVideos15Model {
847 t.Fatalf("binding = {authID:%q model:%q}, want {authID:%q model:%q}", binding.authID, binding.model, "video-xai-1.5-auth", xaiVideos15Model)
848 }
849}
850
851func TestVideosCreatePreviewAliasUsesPreviewAuthWithGAPayload(t *testing.T) {
852 resetVideoAuthBindingsForTest(t)
853 executor := &videoAuthCaptureExecutor{requestID: "video-openai-preview-alias"}
854 handler := newVideoSingleModelAuthTestHandler(t, executor, "video-openai-preview-auth", xaiVideos15PreviewAlias)
855
856 createResp := performVideosEndpointRequest(t, http.MethodPost, openAIVideosPath, "application/json", strings.NewReader(`{"model":"grok-imagine-video-1.5-preview","prompt":"make a video"}`), handler.VideosCreate)
857 if createResp.Code != http.StatusOK {
858 t.Fatalf("create status = %d, want %d: %s", createResp.Code, http.StatusOK, createResp.Body.String())
859 }
860 videoID := gjson.GetBytes(createResp.Body.Bytes(), "id").String()
861 if got := gjson.GetBytes(createResp.Body.Bytes(), "model").String(); got != xaiVideos15Model {
862 t.Fatalf("response model = %q, want %s", got, xaiVideos15Model)
863 }
864
865 retrieveResp := performVideosRouteRequest(t, http.MethodGet, openAIVideosPath+"/:video_id", openAIVideosPath+"/"+videoID, "", nil, handler.VideosRetrieve)
866 if retrieveResp.Code != http.StatusOK {
867 t.Fatalf("retrieve status = %d, want %d: %s", retrieveResp.Code, http.StatusOK, retrieveResp.Body.String())
868 }
869
870 assertPreviewAliasRouting(t, executor, videoID, "video-openai-preview-auth")
871}
872
873func TestVideosCreatePreviewAliasUsesDefaultXAIModelsWithGAPayload(t *testing.T) {
874 resetVideoAuthBindingsForTest(t)
875 executor := &videoAuthCaptureExecutor{requestID: "video-openai-preview-default-models"}
876 handler := newVideoAuthTestHandler(t, executor, "video-openai-preview-default-auth", registry.GetXAIModels())
877
878 createResp := performVideosEndpointRequest(t, http.MethodPost, openAIVideosPath, "application/json", strings.NewReader(`{"model":"grok-imagine-video-1.5-preview","prompt":"make a video"}`), handler.VideosCreate)
879 if createResp.Code != http.StatusOK {
880 t.Fatalf("create status = %d, want %d: %s", createResp.Code, http.StatusOK, createResp.Body.String())
881 }
882 videoID := gjson.GetBytes(createResp.Body.Bytes(), "id").String()
883 if got := gjson.GetBytes(createResp.Body.Bytes(), "model").String(); got != xaiVideos15Model {
884 t.Fatalf("response model = %q, want %s", got, xaiVideos15Model)
885 }
886
887 retrieveResp := performVideosRouteRequest(t, http.MethodGet, openAIVideosPath+"/:video_id", openAIVideosPath+"/"+videoID, "", nil, handler.VideosRetrieve)
888 if retrieveResp.Code != http.StatusOK {
889 t.Fatalf("retrieve status = %d, want %d: %s", retrieveResp.Code, http.StatusOK, retrieveResp.Body.String())
890 }
891
892 assertPreviewAliasRouting(t, executor, videoID, "video-openai-preview-default-auth")
893}
894
895func TestXAIVideosNativePreviewAliasUsesPreviewAuthWithGAPayload(t *testing.T) {
896 resetVideoAuthBindingsForTest(t)
897 executor := &videoAuthCaptureExecutor{requestID: "video-native-preview-alias"}
898 handler := newVideoSingleModelAuthTestHandler(t, executor, "video-native-preview-auth", xaiVideos15PreviewAlias)
899
900 createResp := performVideosEndpointRequest(t, http.MethodPost, xaiVideosGenerationsAPI, "application/json", strings.NewReader(`{"model":"grok-imagine-video-1.5-preview","prompt":"make a video"}`), handler.XAIVideosGenerations)
901 if createResp.Code != http.StatusOK {
902 t.Fatalf("create status = %d, want %d: %s", createResp.Code, http.StatusOK, createResp.Body.String())
903 }
904 videoID := gjson.GetBytes(createResp.Body.Bytes(), "request_id").String()
905
906 retrieveResp := performVideosRouteRequest(t, http.MethodGet, videosPath+"/:request_id", videosPath+"/"+videoID, "", nil, handler.XAIVideosRetrieve)
907 if retrieveResp.Code != http.StatusOK {
908 t.Fatalf("retrieve status = %d, want %d: %s", retrieveResp.Code, http.StatusOK, retrieveResp.Body.String())
909 }
910
911 assertPreviewAliasRouting(t, executor, videoID, "video-native-preview-auth")
912}
913
914func newVideoSingleModelAuthTestHandler(t *testing.T, executor *videoAuthCaptureExecutor, authID string, model string) *OpenAIAPIHandler {
915 t.Helper()
916
917 return newVideoAuthTestHandler(t, executor, authID, []*registry.ModelInfo{{ID: model}})
918}
919
920func newVideoAuthTestHandler(t *testing.T, executor *videoAuthCaptureExecutor, authID string, models []*registry.ModelInfo) *OpenAIAPIHandler {
921 t.Helper()
922
923 manager := coreauth.NewManager(nil, &coreauth.RoundRobinSelector{}, nil)
924 manager.RegisterExecutor(executor)
925 auth := &coreauth.Auth{
926 ID: authID,
927 Provider: "xai",
928 Status: coreauth.StatusActive,
929 }
930 if _, errRegister := manager.Register(context.Background(), auth); errRegister != nil {
931 t.Fatalf("manager.Register(%s): %v", authID, errRegister)
932 }
933 registry.GetGlobalRegistry().RegisterClient(authID, auth.Provider, models)
934 manager.RefreshSchedulerEntry(authID)
935 t.Cleanup(func() {
936 registry.GetGlobalRegistry().UnregisterClient(authID)
937 })
938
939 base := apihandlers.NewBaseAPIHandlers(&sdkconfig.SDKConfig{}, manager)
940 return NewOpenAIAPIHandler(base)
941}
942
943func assertPreviewAliasRouting(t *testing.T, executor *videoAuthCaptureExecutor, videoID string, authID string) {
944 t.Helper()
945
946 authIDs := executor.AuthIDs()
947 if len(authIDs) != 2 || authIDs[0] != authID || authIDs[1] != authID {
948 t.Fatalf("authIDs = %v, want both calls to use %s", authIDs, authID)
949 }
950 models := executor.Models()
951 if len(models) != 2 || models[0] != xaiVideos15PreviewAlias || models[1] != xaiVideos15PreviewAlias {
952 t.Fatalf("models = %v, want both calls to route with %s", models, xaiVideos15PreviewAlias)
953 }
954 payloadModels := executor.PayloadModels()
955 if len(payloadModels) != 2 || payloadModels[0] != xaiVideos15Model {
956 t.Fatalf("payload models = %v, want create payload model %s", payloadModels, xaiVideos15Model)
798 }957 }
799 binding, ok := videoAuthBindings.getBinding(videoID)958 binding, ok := videoAuthBindings.getBinding(videoID)
800 if !ok {959 if !ok {
801 t.Fatal("video auth binding was not stored")960 t.Fatal("video auth binding was not stored")
802 }961 }
803 if binding.authID != "video-xai-preview-auth" || binding.model != xaiVideos15PreviewModel {962 if binding.authID != authID || binding.model != xaiVideos15PreviewAlias {
804 t.Fatalf("binding = {authID:%q model:%q}, want {authID:%q model:%q}", binding.authID, binding.model, "video-xai-preview-auth", xaiVideos15PreviewModel)963 t.Fatalf("binding = {authID:%q model:%q}, want {authID:%q model:%q}", binding.authID, binding.model, authID, xaiVideos15PreviewAlias)
805 }964 }
806}965}
807 966

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t