| @@ -63,11 +63,12 @@ func performVideosRouteRequest(t *testing.T, method string, routePath string, re · 62 unmodified lines |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | 65 | type videoAuthCaptureExecutor struct { |
| 66 | | − mu sync.Mutex |
| 67 | | − requestID string |
| 68 | | − contentURL string |
| 69 | | − authIDs []string |
| 70 | | − models []string |
| 66 | + mu sync.Mutex |
| 67 | + requestID string |
| 68 | + contentURL string |
| 69 | + authIDs []string |
| 70 | + models []string |
| 71 | + payloadModels []string |
| 71 | 72 | } |
| 72 | 73 | |
| 73 | 74 | func (e *videoAuthCaptureExecutor) Identifier() string { return "xai" } |
| @@ -80,6 +81,7 @@ func (e *videoAuthCaptureExecutor) Execute(_ context.Context, auth *coreauth.Aut · 6 unmodified lines |
| 80 | 81 | e.mu.Lock() |
| 81 | 82 | e.authIDs = append(e.authIDs, authID) |
| 82 | 83 | e.models = append(e.models, req.Model) |
| 84 | + e.payloadModels = append(e.payloadModels, strings.TrimSpace(gjson.GetBytes(req.Payload, "model").String())) |
| 83 | 85 | e.mu.Unlock() |
| 84 | 86 | |
| 85 | 87 | requestID := strings.TrimSpace(gjson.GetBytes(req.Payload, "request_id").String()) |
| @@ -126,6 +128,14 @@ func (e *videoAuthCaptureExecutor) Models() []string { · 40 unmodified lines |
| 126 | 128 | return out |
| 127 | 129 | } |
| 128 | 130 | |
| 131 | +func (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 | + |
| 129 | 139 | func resetVideoAuthBindingsForTest(t *testing.T) { |
| 130 | 140 | t.Helper() |
| 131 | 141 | previous := videoAuthBindings |
| @@ -170,6 +180,10 @@ func TestVideosModelValidationAllowsXAIVideoModel(t *testing.T) { · 38 unmodified lines |
| 170 | 180 | "xai/grok-imagine-video", |
| 171 | 181 | "x-ai/grok-imagine-video", |
| 172 | 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 | 187 | "grok-imagine-video-1.5-preview", |
| 174 | 188 | "xai/grok-imagine-video-1.5-preview", |
| 175 | 189 | "x-ai/grok-imagine-video-1.5-preview", |
| @@ -188,6 +202,9 @@ func TestVideosModelValidationAllowsXAIVideoModel(t *testing.T) { · 12 unmodified lines |
| 188 | 202 | if isSupportedVideosModel("codex/grok-imagine-video") { |
| 189 | 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 | 208 | if isSupportedVideosModel("codex/grok-imagine-video-1.5-preview") { |
| 192 | 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 | |
| 243 | | −func TestBuildXAIVideosCreateRequestAllowsPreviewModel(t *testing.T) { |
| 260 | +func 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 | + |
| 279 | +func TestBuildXAIVideosCreateRequestNormalizesVideo15PreviewAlias(t *testing.T) { |
| 244 | 280 | rawJSON := []byte(`{"model":"xai/grok-imagine-video-1.5-preview","prompt":"a cat playing piano","seconds":"8"}`) |
| 245 | 281 | |
| 246 | 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 | 284 | t.Fatalf("buildXAIVideosCreateRequest() error = %v", err) |
| 249 | 285 | } |
| 250 | 286 | |
| 251 | | − if got := gjson.GetBytes(req, "model").String(); got != xaiVideos15PreviewModel { |
| 252 | | − t.Fatalf("model = %q, want %s", got, xaiVideos15PreviewModel) |
| 287 | + if got := gjson.GetBytes(req, "model").String(); got != xaiVideos15Model { |
| 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 { |
| 255 | | − t.Fatalf("meta model = %q, want %s", meta.Model, xaiVideos15PreviewModel) |
| 293 | + if meta.RoutingModel != xaiVideos15PreviewAlias { |
| 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 | |
| 737 | | −func TestXAIVideosNativeRetrieveUsesBoundModel(t *testing.T) { |
| 776 | +func TestXAIVideosNativeRetrieveUsesCanonicalBoundModel(t *testing.T) { |
| 738 | 777 | resetVideoAuthBindingsForTest(t) |
| 739 | | − executor := &videoAuthCaptureExecutor{requestID: "video-xai-preview-bound"} |
| 778 | + executor := &videoAuthCaptureExecutor{requestID: "video-xai-1.5-bound"} |
| 740 | 779 | manager := coreauth.NewManager(nil, &coreauth.RoundRobinSelector{}, nil) |
| 741 | 780 | manager.RegisterExecutor(executor) |
| 742 | 781 | |
| @@ -744,8 +783,8 @@ func TestXAIVideosNativeRetrieveUsesBoundModel(t *testing.T) { · 1 unmodified line |
| 744 | 783 | authID string |
| 745 | 784 | model string |
| 746 | 785 | }{ |
| 747 | | − {authID: "video-xai-preview-default-auth", model: defaultXAIVideosModel}, |
| 748 | | − {authID: "video-xai-preview-auth", model: xaiVideos15PreviewModel}, |
| 786 | + {authID: "video-xai-1.5-default-auth", model: defaultXAIVideosModel}, |
| 787 | + {authID: "video-xai-1.5-auth", model: xaiVideos15Model}, |
| 749 | 788 | } |
| 750 | 789 | for _, entry := range authModels { |
| 751 | 790 | auth := &coreauth.Auth{ |
| @@ -768,7 +807,7 @@ func TestXAIVideosNativeRetrieveUsesBoundModel(t *testing.T) { · 16 unmodified lines |
| 768 | 807 | base := apihandlers.NewBaseAPIHandlers(&sdkconfig.SDKConfig{}, manager) |
| 769 | 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 | 811 | if createResp.Code != http.StatusOK { |
| 773 | 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 | 825 | if len(authIDs) != 2 { |
| 787 | 826 | t.Fatalf("authIDs = %v, want two calls", authIDs) |
| 788 | 827 | } |
| 789 | | − if authIDs[0] != "video-xai-preview-auth" || authIDs[1] != authIDs[0] { |
| 790 | | − t.Fatalf("authIDs = %v, want both calls to use video-xai-preview-auth", authIDs) |
| 828 | + if authIDs[0] != "video-xai-1.5-auth" || authIDs[1] != authIDs[0] { |
| 829 | + t.Fatalf("authIDs = %v, want both calls to use video-xai-1.5-auth", authIDs) |
| 791 | 830 | } |
| 792 | 831 | models := executor.Models() |
| 793 | 832 | if len(models) != 2 { |
| 794 | 833 | t.Fatalf("models = %v, want two calls", models) |
| 795 | 834 | } |
| 796 | | − if models[0] != xaiVideos15PreviewModel || models[1] != xaiVideos15PreviewModel { |
| 797 | | − t.Fatalf("models = %v, want both calls to use %s", models, xaiVideos15PreviewModel) |
| 835 | + if models[0] != xaiVideos15Model || models[1] != xaiVideos15Model { |
| 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 | + |
| 851 | +func 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 | + |
| 873 | +func 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 | + |
| 895 | +func 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 | + |
| 914 | +func 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 | + |
| 920 | +func 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 | + |
| 943 | +func 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 | 958 | binding, ok := videoAuthBindings.getBinding(videoID) |
| 800 | 959 | if !ok { |
| 801 | 960 | t.Fatal("video auth binding was not stored") |
| 802 | 961 | } |
| 803 | | − if binding.authID != "video-xai-preview-auth" || binding.model != xaiVideos15PreviewModel { |
| 804 | | − t.Fatalf("binding = {authID:%q model:%q}, want {authID:%q model:%q}", binding.authID, binding.model, "video-xai-preview-auth", xaiVideos15PreviewModel) |
| 962 | + if binding.authID != authID || binding.model != xaiVideos15PreviewAlias { |
| 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 | |