| @@ -63,11 +63,12 @@ func performVideosRouteRequest(t *testing.T, method string, routePath string, re · 62 unmodified lines |
| 63 | } | 63 | } |
| 64 | | 64 | |
| 65 | type videoAuthCaptureExecutor struct { | 65 | type videoAuthCaptureExecutor struct { |
| 66 | mu sync.Mutex | 66 | mu sync.Mutex |
| 67 | requestID string | 67 | requestID string |
| 68 | contentURL string | 68 | contentURL string |
| 69 | authIDs []string | 69 | authIDs []string |
| 70 | models []string | 70 | models []string |
| | 71 | payloadModels []string |
| 71 | } | 72 | } |
| 72 | | 73 | |
| 73 | func (e *videoAuthCaptureExecutor) Identifier() string { return "xai" } | 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 | 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 out | 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 | func resetVideoAuthBindingsForTest(t *testing.T) { | 139 | func resetVideoAuthBindingsForTest(t *testing.T) { |
| 130 | t.Helper() | 140 | t.Helper() |
| 131 | previous := videoAuthBindings | 141 | 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 | |
| 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 | 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 | |
| 737 | func TestXAIVideosNativeRetrieveUsesBoundModel(t *testing.T) { | 776 | func 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 string | 783 | authID string |
| 745 | model string | 784 | 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 | |
| | 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 | 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 | |