package openai import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strconv" "strings" "sync" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/router-for-me/CLIProxyAPI/v7/internal/clienterror" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) const ( videosPath = "/v1/videos" openAIVideosPath = "/openai/v1/videos" xaiVideosGenerationsAPI = "/v1/videos/generations" xaiVideosEditsAPI = "/v1/videos/edits" xaiVideosExtensionsAPI = "/v1/videos/extensions" defaultOpenAIVideosModel = "sora-2" defaultXAIVideosModel = "grok-imagine-video" xaiVideos15Model = "grok-imagine-video-1.5" xaiVideos15PreviewAlias = "grok-imagine-video-1.5-preview" xaiVideosHandlerType = "openai-video" defaultVideosSeconds = "4" defaultVideosSize = "720x1280" defaultVideosResolution = "720p" maxXAIVideoReferences = 7 ) const defaultVideoAuthBindingTTL = 3 * time.Hour var videoAuthBindings = newVideoAuthBindingStore() type xaiVideoCreateMetadata struct { Model string RoutingModel string Prompt string Seconds string Size string CreatedAt int64 } type videoAuthBinding struct { authID string model string expiresAt time.Time } type videoAuthBindingStore struct { mu sync.RWMutex entries map[string]videoAuthBinding } func newVideoAuthBindingStore() *videoAuthBindingStore { return &videoAuthBindingStore{ entries: make(map[string]videoAuthBinding), } } func (s *videoAuthBindingStore) set(videoID string, authID string, ttl time.Duration) { s.setWithModel(videoID, authID, "", ttl) } func (s *videoAuthBindingStore) setWithModel(videoID string, authID string, model string, ttl time.Duration) { if s == nil { return } videoID = strings.TrimSpace(videoID) authID = strings.TrimSpace(authID) if videoID == "" || authID == "" { return } if ttl <= 0 { ttl = defaultVideoAuthBindingTTL } now := time.Now() s.mu.Lock() s.cleanupExpiredLocked(now) s.entries[videoID] = videoAuthBinding{ authID: authID, model: strings.TrimSpace(model), expiresAt: now.Add(ttl), } s.mu.Unlock() } func (s *videoAuthBindingStore) get(videoID string) (string, bool) { binding, ok := s.getBinding(videoID) if !ok { return "", false } return binding.authID, true } func (s *videoAuthBindingStore) getBinding(videoID string) (videoAuthBinding, bool) { if s == nil { return videoAuthBinding{}, false } videoID = strings.TrimSpace(videoID) if videoID == "" { return videoAuthBinding{}, false } now := time.Now() s.mu.RLock() entry, ok := s.entries[videoID] s.mu.RUnlock() if !ok { return videoAuthBinding{}, false } if now.After(entry.expiresAt) { s.mu.Lock() if current, exists := s.entries[videoID]; exists && now.After(current.expiresAt) { delete(s.entries, videoID) } s.mu.Unlock() return videoAuthBinding{}, false } return entry, true } func (s *videoAuthBindingStore) cleanupExpiredLocked(now time.Time) { for videoID, entry := range s.entries { if now.After(entry.expiresAt) { delete(s.entries, videoID) } } } func videosModelBase(model string) string { _, baseModel := imagesModelParts(model) return strings.ToLower(strings.TrimSpace(baseModel)) } func isXAIVideosModel(model string) bool { prefix, baseModel := imagesModelParts(model) baseModel = strings.ToLower(strings.TrimSpace(baseModel)) if baseModel != defaultXAIVideosModel && baseModel != xaiVideos15Model && baseModel != xaiVideos15PreviewAlias { return false } prefix = strings.ToLower(strings.TrimSpace(prefix)) return prefix == "" || prefix == "xai" || prefix == "x-ai" || prefix == "grok" } func isSoraVideosModel(model string) bool { _, baseModel := imagesModelParts(model) baseModel = strings.ToLower(strings.TrimSpace(baseModel)) return baseModel == defaultOpenAIVideosModel || strings.HasPrefix(baseModel, defaultOpenAIVideosModel+"-") } func isSupportedVideosModel(model string) bool { return isXAIVideosModel(model) || isSoraVideosModel(model) } func rejectUnsupportedVideosModel(c *gin.Context, model string) bool { if isSupportedVideosModel(model) { return false } path := strings.TrimSpace(c.Request.URL.Path) if path == "" { path = openAIVideosPath } writeVideosFailedError(c, http.StatusBadRequest, model, "invalid_request_error", fmt.Sprintf("Model %s is not supported on %s. Use %s.", model, path, defaultOpenAIVideosModel)) return true } func rejectUnsupportedNativeVideosModel(c *gin.Context, model string) bool { if isXAIVideosModel(model) { return false } c.JSON(http.StatusBadRequest, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: fmt.Sprintf("Model %s is not supported on %s, %s, or %s. Use %s.", model, xaiVideosGenerationsAPI, xaiVideosEditsAPI, xaiVideosExtensionsAPI, defaultXAIVideosModel), Type: "invalid_request_error", }, }) return true } func canonicalXAIVideosModel(model string) string { if isSoraVideosModel(model) { return defaultXAIVideosModel } switch videosModelBase(model) { case defaultXAIVideosModel: return defaultXAIVideosModel case xaiVideos15Model, xaiVideos15PreviewAlias: return xaiVideos15Model } return defaultXAIVideosModel } func routingXAIVideosModel(model string) string { if isSoraVideosModel(model) { return defaultXAIVideosModel } switch videosModelBase(model) { case defaultXAIVideosModel: return defaultXAIVideosModel case xaiVideos15Model: return xaiVideos15Model case xaiVideos15PreviewAlias: return xaiVideos15PreviewAlias } return defaultXAIVideosModel } func responseVideosModel(model string) string { return canonicalXAIVideosModel(model) } func readVideosCreateRequest(c *gin.Context) ([]byte, error) { contentType := strings.ToLower(strings.TrimSpace(c.ContentType())) switch contentType { case "multipart/form-data", "application/x-www-form-urlencoded": return videosCreateRequestFromForm(c) default: rawJSON, err := handlers.ReadRequestBody(c) if err != nil { return nil, err } if !json.Valid(rawJSON) { return nil, fmt.Errorf("body must be valid JSON") } return rawJSON, nil } } func readXAIVideosNativeRequest(c *gin.Context) ([]byte, error) { rawJSON, err := handlers.ReadRequestBody(c) if err != nil { return nil, err } if !json.Valid(rawJSON) { return nil, fmt.Errorf("body must be valid JSON") } return rawJSON, nil } func videosCreateRequestFromForm(c *gin.Context) ([]byte, error) { rawJSON := []byte(`{}`) for _, field := range []string{"model", "prompt", "seconds", "size", "aspect_ratio", "resolution"} { if value := strings.TrimSpace(c.PostForm(field)); value != "" { rawJSON, _ = sjson.SetBytes(rawJSON, field, value) } } if value := strings.TrimSpace(firstPostForm(c, "input_reference[image_url]", "input_reference.image_url", "image_url")); value != "" { rawJSON, _ = sjson.SetBytes(rawJSON, "input_reference.image_url", value) } if value := strings.TrimSpace(firstPostForm(c, "input_reference[file_id]", "input_reference.file_id", "file_id")); value != "" { rawJSON, _ = sjson.SetBytes(rawJSON, "input_reference.file_id", value) } if refs := strings.TrimSpace(c.PostForm("reference_image_urls")); refs != "" { for _, ref := range strings.Split(refs, ",") { if ref = strings.TrimSpace(ref); ref != "" { rawJSON, _ = sjson.SetBytes(rawJSON, "reference_image_urls.-1", ref) } } } return rawJSON, nil } func firstPostForm(c *gin.Context, keys ...string) string { for _, key := range keys { if value := c.PostForm(key); strings.TrimSpace(value) != "" { return value } } return "" } func (h *OpenAIAPIHandler) videoAuthBindingTTL() time.Duration { if h != nil && h.BaseAPIHandler != nil && h.Cfg != nil { raw := strings.TrimSpace(h.Cfg.VideoResultAuthCacheTTL) if raw != "" { if ttl, err := time.ParseDuration(raw); err == nil && ttl > 0 { return ttl } } } return defaultVideoAuthBindingTTL } func videoIDFromPayload(payload []byte) string { videoID := strings.TrimSpace(gjson.GetBytes(payload, "request_id").String()) if videoID == "" { videoID = strings.TrimSpace(gjson.GetBytes(payload, "id").String()) } return videoID } func (h *OpenAIAPIHandler) bindVideoAuthIDFromPayload(payload []byte, authID string) { h.bindVideoAuthIDAndModelFromPayload(payload, authID, strings.TrimSpace(gjson.GetBytes(payload, "model").String())) } func (h *OpenAIAPIHandler) bindVideoAuthIDAndModelFromPayload(payload []byte, authID string, model string) { videoID := videoIDFromPayload(payload) if videoID == "" { return } videoAuthBindings.setWithModel(videoID, authID, routingXAIVideosModel(model), h.videoAuthBindingTTL()) } func (h *OpenAIAPIHandler) bindVideoAuthID(videoID string, authID string, model string) { videoAuthBindings.setWithModel(videoID, authID, routingXAIVideosModel(model), h.videoAuthBindingTTL()) } func (h *OpenAIAPIHandler) contextWithVideoAuthBinding(ctx context.Context, videoID string) context.Context { if authID, ok := videoAuthBindings.get(videoID); ok { return handlers.WithPinnedAuthID(ctx, authID) } return ctx } func (h *OpenAIAPIHandler) modelWithVideoAuthBinding(videoID string, fallbackModel string) string { if binding, ok := videoAuthBindings.getBinding(videoID); ok { if model := strings.TrimSpace(binding.model); model != "" { return model } } return fallbackModel } func buildXAIVideosCreateRequest(rawJSON []byte, model string) ([]byte, xaiVideoCreateMetadata, error) { prompt := strings.TrimSpace(gjson.GetBytes(rawJSON, "prompt").String()) if prompt == "" { return nil, xaiVideoCreateMetadata{}, fmt.Errorf("prompt is required") } seconds, duration, err := normalizeXAIVideosSeconds(gjson.GetBytes(rawJSON, "seconds").String()) if err != nil { return nil, xaiVideoCreateMetadata{}, err } size, aspectRatio, resolution, err := xaiVideosSizeOptions(gjson.GetBytes(rawJSON, "size").String()) if err != nil { return nil, xaiVideoCreateMetadata{}, err } if value := xaiVideosAspectRatio(gjson.GetBytes(rawJSON, "aspect_ratio").String(), ""); value != "" { aspectRatio = value } if value := xaiVideosResolution(gjson.GetBytes(rawJSON, "resolution").String(), ""); value != "" { resolution = value } imageURL, err := xaiVideosInputImageURL(rawJSON) if err != nil { return nil, xaiVideoCreateMetadata{}, err } referenceImages := collectXAIVideoReferenceImages(rawJSON) if len(referenceImages) > maxXAIVideoReferences { return nil, xaiVideoCreateMetadata{}, fmt.Errorf("reference_images supports at most %d images on xAI", maxXAIVideoReferences) } if imageURL != "" && len(referenceImages) > 0 { return nil, xaiVideoCreateMetadata{}, fmt.Errorf("image and reference_images cannot be combined on xAI") } if len(referenceImages) > 0 && duration > 10 { duration = 10 seconds = "10" } videoModel := canonicalXAIVideosModel(model) req := []byte(`{}`) 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) req, _ = sjson.SetBytes(req, "resolution", resolution) if imageURL != "" { req, _ = sjson.SetBytes(req, "image.url", imageURL) } for _, image := range referenceImages { req, _ = sjson.SetBytes(req, "reference_images.-1.url", image) } meta := xaiVideoCreateMetadata{ Model: responseVideosModel(model), RoutingModel: routingXAIVideosModel(model), Prompt: prompt, Seconds: seconds, Size: size, CreatedAt: time.Now().Unix(), } return req, meta, nil } func normalizeXAIVideosSeconds(raw string) (string, int64, error) { seconds := strings.TrimSpace(raw) if seconds == "" { seconds = defaultVideosSeconds } duration, err := strconv.ParseInt(seconds, 10, 64) if err != nil { return "", 0, fmt.Errorf("seconds must be an integer") } if duration < 1 { duration = 1 } if duration > 15 { duration = 15 } return strconv.FormatInt(duration, 10), duration, nil } func xaiVideosSizeOptions(raw string) (size string, aspectRatio string, resolution string, err error) { size = strings.TrimSpace(raw) if size == "" { size = defaultVideosSize } switch size { case "720x1280", "1024x1792": return size, "9:16", defaultVideosResolution, nil case "1280x720", "1792x1024": return size, "16:9", defaultVideosResolution, nil default: return "", "", "", fmt.Errorf("size must be one of 720x1280, 1280x720, 1024x1792, or 1792x1024") } } func xaiVideosAspectRatio(raw string, fallback string) string { switch strings.ToLower(strings.TrimSpace(raw)) { case "1:1", "square": return "1:1" case "16:9", "landscape": return "16:9" case "9:16", "portrait": return "9:16" case "4:3": return "4:3" case "3:4": return "3:4" case "3:2": return "3:2" case "2:3": return "2:3" default: return fallback } } func xaiVideosResolution(raw string, fallback string) string { switch strings.ToLower(strings.TrimSpace(raw)) { case "480p": return "480p" case "720p": return "720p" default: return fallback } } func xaiVideosInputImageURL(rawJSON []byte) (string, error) { inputRef := gjson.GetBytes(rawJSON, "input_reference") if inputRef.Exists() { imageURL := strings.TrimSpace(inputRef.Get("image_url").String()) fileID := strings.TrimSpace(inputRef.Get("file_id").String()) if imageURL != "" && fileID != "" { return "", fmt.Errorf("input_reference must provide exactly one of image_url or file_id") } if fileID != "" { return "", fmt.Errorf("input_reference.file_id is not supported for xAI video generation; use input_reference.image_url") } if imageURL != "" { return imageURL, nil } } image := gjson.GetBytes(rawJSON, "image") if image.Exists() { if image.Type == gjson.String { return strings.TrimSpace(image.String()), nil } if value := strings.TrimSpace(image.Get("url").String()); value != "" { return value, nil } if value := strings.TrimSpace(image.Get("image_url.url").String()); value != "" { return value, nil } } return strings.TrimSpace(gjson.GetBytes(rawJSON, "image_url").String()), nil } func collectXAIVideoReferenceImages(rawJSON []byte) []string { out := make([]string, 0) appendRef := func(value string) { value = strings.TrimSpace(value) if value != "" { out = append(out, value) } } collectArray := func(result gjson.Result) { if !result.IsArray() { return } result.ForEach(func(_, item gjson.Result) bool { if item.Type == gjson.String { appendRef(item.String()) return true } if value := item.Get("url").String(); value != "" { appendRef(value) return true } if value := item.Get("image_url.url").String(); value != "" { appendRef(value) } return true }) } collectArray(gjson.GetBytes(rawJSON, "reference_images")) collectArray(gjson.GetBytes(rawJSON, "reference_image_urls")) return out } func buildVideosCreateAPIResponseFromXAI(payload []byte, meta xaiVideoCreateMetadata) ([]byte, error) { requestID := strings.TrimSpace(gjson.GetBytes(payload, "request_id").String()) if requestID == "" { requestID = strings.TrimSpace(gjson.GetBytes(payload, "id").String()) } if requestID == "" { return nil, fmt.Errorf("xAI video response did not include request_id") } out := []byte(`{"object":"video","progress":0,"status":"queued"}`) out, _ = sjson.SetBytes(out, "id", requestID) out, _ = sjson.SetBytes(out, "model", meta.Model) out, _ = sjson.SetBytes(out, "prompt", meta.Prompt) out, _ = sjson.SetBytes(out, "seconds", meta.Seconds) out, _ = sjson.SetBytes(out, "size", meta.Size) out, _ = sjson.SetBytes(out, "created_at", meta.CreatedAt) if status := openAIVideoStatus(gjson.GetBytes(payload, "status").String()); status != "" { out, _ = sjson.SetBytes(out, "status", status) } if progress := gjson.GetBytes(payload, "progress"); progress.Exists() { out, _ = sjson.SetRawBytes(out, "progress", []byte(progress.Raw)) } return out, nil } func buildVideosFailedAPIResponse(model string, code string, message string) []byte { model = strings.TrimSpace(model) if model == "" { model = defaultXAIVideosModel } code = strings.TrimSpace(code) if code == "" { code = "invalid_request_error" } message = strings.TrimSpace(message) if message == "" { message = "Video generation failed" } out := []byte(`{"object":"video","status":"failed","progress":0}`) out, _ = sjson.SetBytes(out, "id", "video_"+strings.ReplaceAll(uuid.NewString(), "-", "")) out, _ = sjson.SetBytes(out, "model", model) out, _ = sjson.SetBytes(out, "error.code", code) out, _ = sjson.SetBytes(out, "error.message", message) return out } func writeVideosFailedError(c *gin.Context, status int, model string, code string, message string) { if status <= 0 { status = http.StatusBadRequest } c.Data(status, "application/json", buildVideosFailedAPIResponse(model, code, message)) } func buildVideosRetrieveAPIResponseFromXAI(videoID string, payload []byte, fallbackModel string) ([]byte, error) { out := []byte(`{"object":"video"}`) out, _ = sjson.SetBytes(out, "id", videoID) model := strings.TrimSpace(gjson.GetBytes(payload, "model").String()) if model == "" { model = responseVideosModel(fallbackModel) } out, _ = sjson.SetBytes(out, "model", model) for _, field := range []string{"created_at", "completed_at", "expires_at", "prompt", "remixed_from_video_id", "size"} { if value := gjson.GetBytes(payload, field); value.Exists() { out, _ = sjson.SetRawBytes(out, field, []byte(value.Raw)) } } if status := openAIVideoStatus(gjson.GetBytes(payload, "status").String()); status != "" { out, _ = sjson.SetBytes(out, "status", status) } if progress := gjson.GetBytes(payload, "progress"); progress.Exists() { out, _ = sjson.SetRawBytes(out, "progress", []byte(progress.Raw)) } if seconds := gjson.GetBytes(payload, "seconds"); seconds.Exists() { out, _ = sjson.SetRawBytes(out, "seconds", []byte(seconds.Raw)) } else if duration := gjson.GetBytes(payload, "video.duration"); duration.Exists() { out, _ = sjson.SetBytes(out, "seconds", duration.String()) } if videoURL := strings.TrimSpace(gjson.GetBytes(payload, "video.url").String()); videoURL != "" { out, _ = sjson.SetBytes(out, "video_url", videoURL) } out = setOpenAIVideoErrorFromXAI(out, payload) return out, nil } func setOpenAIVideoErrorFromXAI(out []byte, payload []byte) []byte { if errPayload := gjson.GetBytes(payload, "error"); errPayload.Exists() { out = markOpenAIVideoFailed(out) if errPayload.Type == gjson.JSON && json.Valid([]byte(errPayload.Raw)) { message := strings.TrimSpace(errPayload.Get("message").String()) if message != "" { code := strings.TrimSpace(gjson.GetBytes(payload, "code").String()) if code == "" { code = strings.TrimSpace(errPayload.Get("code").String()) } if code == "" { code = "video_generation_failed" } out, _ = sjson.SetBytes(out, "error.code", code) out, _ = sjson.SetBytes(out, "error.message", message) } return out } message := strings.TrimSpace(errPayload.String()) if message != "" { code := strings.TrimSpace(gjson.GetBytes(payload, "code").String()) if code == "" { code = "video_generation_failed" } out, _ = sjson.SetBytes(out, "error.code", code) out, _ = sjson.SetBytes(out, "error.message", message) } return out } code := strings.TrimSpace(gjson.GetBytes(payload, "code").String()) if code != "" { out = markOpenAIVideoFailed(out) out, _ = sjson.SetBytes(out, "error.code", code) out, _ = sjson.SetBytes(out, "error.message", code) } return out } func markOpenAIVideoFailed(out []byte) []byte { if !gjson.GetBytes(out, "status").Exists() { out, _ = sjson.SetBytes(out, "status", "failed") } if !gjson.GetBytes(out, "progress").Exists() { out, _ = sjson.SetRawBytes(out, "progress", []byte("0")) } return out } func xaiVideoContentURLFromPayload(payload []byte) (string, error) { rawURL := strings.TrimSpace(gjson.GetBytes(payload, "video.url").String()) if rawURL == "" { return "", fmt.Errorf("xAI video response did not include video.url") } parsed, err := url.Parse(rawURL) if err != nil || parsed == nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Host == "" { return "", fmt.Errorf("xAI video response included invalid video.url") } return rawURL, nil } func openAIVideoStatus(status string) string { switch strings.ToLower(strings.TrimSpace(status)) { case "queued", "pending": return "queued" case "in_progress", "processing", "running": return "in_progress" case "completed", "done", "succeeded", "success": return "completed" case "failed", "error", "expired", "cancelled", "canceled": return "failed" default: return "" } } func (h *OpenAIAPIHandler) VideosCreate(c *gin.Context) { rawJSON, err := readVideosCreateRequest(c) if err != nil { writeVideosFailedError(c, http.StatusBadRequest, defaultXAIVideosModel, "invalid_request_error", fmt.Sprintf("Invalid request: %v", err)) return } videoModel := strings.TrimSpace(gjson.GetBytes(rawJSON, "model").String()) if videoModel == "" { videoModel = defaultXAIVideosModel } if rejectUnsupportedVideosModel(c, videoModel) { return } xaiReq, meta, err := buildXAIVideosCreateRequest(rawJSON, videoModel) if err != nil { writeVideosFailedError(c, http.StatusBadRequest, responseVideosModel(videoModel), "invalid_request_error", fmt.Sprintf("Invalid request: %v", err)) return } h.collectXAIVideosCreate(c, xaiReq, meta) } func (h *OpenAIAPIHandler) XAIVideosGenerations(c *gin.Context) { h.handleXAIVideosNativePost(c) } func (h *OpenAIAPIHandler) XAIVideosEdits(c *gin.Context) { h.handleXAIVideosNativePost(c) } func (h *OpenAIAPIHandler) XAIVideosExtensions(c *gin.Context) { h.handleXAIVideosNativePost(c) } func (h *OpenAIAPIHandler) handleXAIVideosNativePost(c *gin.Context) { rawJSON, err := readXAIVideosNativeRequest(c) if err != nil { c.JSON(http.StatusBadRequest, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: fmt.Sprintf("Invalid request: %v", err), Type: "invalid_request_error", }, }) return } videoModel := strings.TrimSpace(gjson.GetBytes(rawJSON, "model").String()) if videoModel == "" { videoModel = defaultXAIVideosModel } if rejectUnsupportedNativeVideosModel(c, videoModel) { return } routingModel := routingXAIVideosModel(videoModel) rawJSON, _ = sjson.SetBytes(rawJSON, "model", canonicalXAIVideosModel(videoModel)) h.collectXAIVideosNative(c, rawJSON, routingModel, true) } func (h *OpenAIAPIHandler) XAIVideosRetrieve(c *gin.Context) { requestID := strings.TrimSpace(c.Param("request_id")) if requestID == "" { requestID = strings.TrimSpace(c.Param("video_id")) } if requestID == "" { c.JSON(http.StatusBadRequest, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: "Invalid request: request_id is required", Type: "invalid_request_error", }, }) return } payload := []byte(`{}`) payload, _ = sjson.SetBytes(payload, "request_id", requestID) h.collectXAIVideosNative(c, payload, defaultXAIVideosModel, false) } func (h *OpenAIAPIHandler) VideosRetrieve(c *gin.Context) { videoID := strings.TrimSpace(c.Param("video_id")) if videoID == "" { c.JSON(http.StatusBadRequest, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: "Invalid request: video_id is required", Type: "invalid_request_error", }, }) return } payload := []byte(`{}`) payload, _ = sjson.SetBytes(payload, "request_id", videoID) c.Header("Content-Type", "application/json") cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background()) selectedAuthID := "" cliCtx = h.contextWithVideoAuthBinding(cliCtx, videoID) executionModel := h.modelWithVideoAuthBinding(videoID, defaultXAIVideosModel) cliCtx = handlers.WithSelectedAuthIDCallback(cliCtx, func(authID string) { selectedAuthID = authID }) stopKeepAlive := h.StartNonStreamingKeepAlive(c, cliCtx) resp, upstreamHeaders, errMsg := h.ExecuteWithAuthManager(cliCtx, xaiVideosHandlerType, executionModel, payload, "") stopKeepAlive() if errMsg != nil { h.WriteErrorResponse(c, errMsg) if errMsg.Error != nil { cliCancel(errMsg.Error) } else { cliCancel(nil) } return } out, err := buildVideosRetrieveAPIResponseFromXAI(videoID, resp, defaultOpenAIVideosModel) if err != nil { errMsg := &interfaces.ErrorMessage{StatusCode: http.StatusBadGateway, Error: err} h.WriteErrorResponse(c, errMsg) cliCancel(err) return } h.bindVideoAuthID(videoID, selectedAuthID, executionModel) handlers.WriteUpstreamHeaders(c.Writer.Header(), upstreamHeaders) _, _ = c.Writer.Write(out) cliCancel(nil) } func (h *OpenAIAPIHandler) VideosContent(c *gin.Context) { videoID := strings.TrimSpace(c.Param("video_id")) if videoID == "" { c.JSON(http.StatusBadRequest, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: "Invalid request: video_id is required", Type: "invalid_request_error", }, }) return } variant := strings.TrimSpace(c.Query("variant")) if variant == "" { variant = "video" } if variant != "video" { c.JSON(http.StatusBadRequest, handlers.ErrorResponse{ Error: handlers.ErrorDetail{ Message: fmt.Sprintf("Invalid request: variant %q is not available for xAI video downloads", variant), Type: "invalid_request_error", }, }) return } payload := []byte(`{}`) payload, _ = sjson.SetBytes(payload, "request_id", videoID) cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background()) selectedAuthID := "" cliCtx = h.contextWithVideoAuthBinding(cliCtx, videoID) executionModel := h.modelWithVideoAuthBinding(videoID, defaultXAIVideosModel) cliCtx = handlers.WithSelectedAuthIDCallback(cliCtx, func(authID string) { selectedAuthID = authID }) stopKeepAlive := h.StartNonStreamingKeepAlive(c, cliCtx) resp, _, errMsg := h.ExecuteWithAuthManager(cliCtx, xaiVideosHandlerType, executionModel, payload, "") stopKeepAlive() if errMsg != nil { h.WriteErrorResponse(c, errMsg) if errMsg.Error != nil { cliCancel(errMsg.Error) } else { cliCancel(nil) } return } h.bindVideoAuthID(videoID, selectedAuthID, executionModel) contentURL, err := xaiVideoContentURLFromPayload(resp) if err != nil { errMsg := &interfaces.ErrorMessage{StatusCode: http.StatusBadGateway, Error: err} h.WriteErrorResponse(c, errMsg) cliCancel(err) return } if errDownload := h.writeVideoContentFromURL(c, contentURL); errDownload != nil { cliCancel(errDownload) return } cliCancel(nil) } func (h *OpenAIAPIHandler) writeVideoContentFromURL(c *gin.Context, contentURL string) error { req, err := http.NewRequestWithContext(c.Request.Context(), http.MethodGet, contentURL, nil) if err != nil { errMsg := &interfaces.ErrorMessage{ StatusCode: clienterror.HTTPStatusFromErrorOr(err, http.StatusBadGateway), Error: err, } h.WriteErrorResponse(c, errMsg) return err } httpClient := h.videoContentHTTPClient(c) resp, err := httpClient.Do(req) if err != nil { errMsg := &interfaces.ErrorMessage{ StatusCode: clienterror.HTTPStatusFromErrorOr(err, http.StatusBadGateway), Error: err, } h.WriteErrorResponse(c, errMsg) return err } defer func() { if errClose := resp.Body.Close(); errClose != nil { log.Errorf("video content body close error: %v", errClose) } }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, _ := io.ReadAll(resp.Body) errDownloadStatus := fmt.Errorf("video content download failed: %s", strings.TrimSpace(string(body))) if strings.TrimSpace(string(body)) == "" { errDownloadStatus = fmt.Errorf("video content download failed: %s", resp.Status) } errMsg := &interfaces.ErrorMessage{StatusCode: resp.StatusCode, Error: errDownloadStatus} h.WriteErrorResponse(c, errMsg) return errDownloadStatus } copyVideoContentHeaders(c.Writer.Header(), resp.Header) if c.Writer.Header().Get("Content-Type") == "" { c.Writer.Header().Set("Content-Type", "application/octet-stream") } c.Status(resp.StatusCode) _, err = io.Copy(c.Writer, resp.Body) return err } func (h *OpenAIAPIHandler) videoContentHTTPClient(c *gin.Context) *http.Client { ctx := context.Background() if c != nil && c.Request != nil { ctx = c.Request.Context() } var cfg *config.Config if h != nil && h.BaseAPIHandler != nil && h.Cfg != nil { cfg = &config.Config{SDKConfig: *h.Cfg} } return helps.NewProxyAwareHTTPClient(ctx, cfg, h.videoContentDownloadAuth(c), 0) } func (h *OpenAIAPIHandler) videoContentDownloadAuth(c *gin.Context) *coreauth.Auth { if h == nil || h.BaseAPIHandler == nil || h.AuthManager == nil || c == nil { return nil } videoID := strings.TrimSpace(c.Param("video_id")) if videoID == "" { return nil } authID, ok := videoAuthBindings.get(videoID) if !ok { return nil } auth, ok := h.AuthManager.GetByID(authID) if !ok { return nil } return auth } func copyVideoContentHeaders(dst http.Header, src http.Header) { for _, key := range []string{"Content-Type", "Content-Length", "Content-Disposition", "Cache-Control", "ETag", "Last-Modified"} { if value := src.Get(key); value != "" { dst.Set(key, value) } } } func (h *OpenAIAPIHandler) collectXAIVideosNative(c *gin.Context, rawJSON []byte, model string, bindCreatedVideoAuth bool) { c.Header("Content-Type", "application/json") cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background()) selectedAuthID := "" videoID := videoIDFromPayload(rawJSON) executionModel := model if !bindCreatedVideoAuth { cliCtx = h.contextWithVideoAuthBinding(cliCtx, videoID) executionModel = h.modelWithVideoAuthBinding(videoID, model) } cliCtx = handlers.WithSelectedAuthIDCallback(cliCtx, func(authID string) { selectedAuthID = authID }) stopKeepAlive := h.StartNonStreamingKeepAlive(c, cliCtx) resp, upstreamHeaders, errMsg := h.ExecuteWithAuthManager(cliCtx, xaiVideosHandlerType, executionModel, rawJSON, "") stopKeepAlive() if errMsg != nil { h.WriteErrorResponse(c, errMsg) if errMsg.Error != nil { cliCancel(errMsg.Error) } else { cliCancel(nil) } return } if bindCreatedVideoAuth { h.bindVideoAuthIDAndModelFromPayload(resp, selectedAuthID, executionModel) } else { h.bindVideoAuthID(videoID, selectedAuthID, executionModel) } handlers.WriteUpstreamHeaders(c.Writer.Header(), upstreamHeaders) _, _ = c.Writer.Write(resp) cliCancel(nil) } func (h *OpenAIAPIHandler) collectXAIVideosCreate(c *gin.Context, xaiReq []byte, meta xaiVideoCreateMetadata) { c.Header("Content-Type", "application/json") cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background()) selectedAuthID := "" cliCtx = handlers.WithSelectedAuthIDCallback(cliCtx, func(authID string) { selectedAuthID = authID }) routingModel := strings.TrimSpace(meta.RoutingModel) if routingModel == "" { routingModel = routingXAIVideosModel(meta.Model) } stopKeepAlive := h.StartNonStreamingKeepAlive(c, cliCtx) resp, upstreamHeaders, errMsg := h.ExecuteWithAuthManager(cliCtx, xaiVideosHandlerType, routingModel, xaiReq, "") stopKeepAlive() if errMsg != nil { h.WriteErrorResponse(c, errMsg) if errMsg.Error != nil { cliCancel(errMsg.Error) } else { cliCancel(nil) } return } out, err := buildVideosCreateAPIResponseFromXAI(resp, meta) if err != nil { errMsg := &interfaces.ErrorMessage{StatusCode: http.StatusBadGateway, Error: err} h.WriteErrorResponse(c, errMsg) cliCancel(err) return } h.bindVideoAuthIDAndModelFromPayload(out, selectedAuthID, routingModel) handlers.WriteUpstreamHeaders(c.Writer.Header(), upstreamHeaders) _, _ = c.Writer.Write(out) cliCancel(nil) }