package integrations import ( "context" "encoding/base64" "encoding/json" "errors" "io" "mime/multipart" "net/url" "strconv" "strings" "github.com/bytedance/sonic" "github.com/google/uuid" bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/providers/openai" "github.com/maximhq/bifrost/core/schemas" "github.com/maximhq/bifrost/transports/bifrost-http/lib" "github.com/valyala/fasthttp" ) // setAzureModelName sets the model name for Azure requests with proper prefix handling // When deploymentID is present, it always takes precedence over the request body model // to avoid deployment/model mismatches. func setAzureModelName(currentModel, deploymentID string) string { if deploymentID != "" { return "azure/" + deploymentID } else if currentModel != "" && !strings.HasPrefix(currentModel, "azure/") { return "azure/" + currentModel } return currentModel } // OpenAIRouter holds route registrations for OpenAI endpoints. // It supports standard chat completions, speech synthesis, audio transcription, and streaming capabilities with OpenAI-specific formatting. type OpenAIRouter struct { *GenericRouter } // azureEndpointStarters is the fixed set of path segments that can follow a // deployment-id in Azure OpenAI URLs. var azureEndpointStarters = map[string]bool{ "chat": true, "audio": true, "images": true, "completions": true, "embeddings": true, "responses": true, "models": true, } // isAzureSDKRequest reports whether the request originates from the Azure OpenAI // SDK by inspecting the User-Agent header directly from the HTTP request. // This avoids any ordering dependency on AzureEndpointPreHook. func isAzureSDKRequest(ctx *fasthttp.RequestCtx) bool { return strings.Contains(string(ctx.UserAgent()), "AzureOpenAI") } func hydrateOpenAIRequestFromLargePayloadMetadata(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) { if bifrostCtx == nil { return } isLargePayload, _ := bifrostCtx.Value(schemas.BifrostContextKeyLargePayloadMode).(bool) if !isLargePayload { return } metadata := resolveLargePayloadMetadata(bifrostCtx) if metadata == nil { return } streamRequested := false hasStream := metadata.StreamRequested != nil if hasStream { streamRequested = *metadata.StreamRequested } switch r := req.(type) { case *openai.OpenAITextCompletionRequest: if r.Model == "" { r.Model = metadata.Model } if hasStream && r.Stream == nil { r.Stream = schemas.Ptr(streamRequested) } case *openai.OpenAIChatRequest: if r.Model == "" { r.Model = metadata.Model } if hasStream && r.Stream == nil { r.Stream = schemas.Ptr(streamRequested) } case *openai.OpenAIResponsesRequest: if r.Model == "" { r.Model = metadata.Model } if hasStream && r.Stream == nil { r.Stream = schemas.Ptr(streamRequested) } case *openai.OpenAIEmbeddingRequest: if r.Model == "" { r.Model = metadata.Model } case *openai.OpenAISpeechRequest: if r.Model == "" { r.Model = metadata.Model } if hasStream && streamRequested && r.StreamFormat == nil { r.StreamFormat = schemas.Ptr("sse") } case *openai.OpenAITranscriptionRequest: if r.Model == "" { r.Model = metadata.Model } if hasStream && r.Stream == nil { r.Stream = schemas.Ptr(streamRequested) } case *openai.OpenAIImageGenerationRequest: if r.Model == "" { r.Model = metadata.Model } if hasStream && r.Stream == nil { r.Stream = schemas.Ptr(streamRequested) } case *openai.OpenAIImageEditRequest: if r.Model == "" { r.Model = metadata.Model } if hasStream && r.Stream == nil { r.Stream = schemas.Ptr(streamRequested) } case *openai.OpenAIImageVariationRequest: if r.Model == "" { r.Model = metadata.Model } } } // openAILargePayloadPreHook populates model + stream from LargePayloadMetadata // when body parsing is skipped under large payload mode. func openAILargePayloadPreHook(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { hydrateOpenAIRequestFromLargePayloadMetadata(ctx, bifrostCtx, req) schemas.ExtractAndSetUserAgentFromHeaders(extractHeadersFromRequest(ctx), bifrostCtx) return nil } func AzureEndpointPreHook(handlerStore lib.HandlerStore) func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { hydrateOpenAIRequestFromLargePayloadMetadata(ctx, bifrostCtx, req) schemas.ExtractAndSetUserAgentFromHeaders(extractHeadersFromRequest(ctx), bifrostCtx) azureKey := ctx.Request.Header.Peek("authorization") deploymentEndpoint := ctx.Request.Header.Peek("x-bf-azure-endpoint") apiVersion := string(ctx.QueryArgs().Peek("api-version")) // ----------------------------- // Parse deploymentPath wildcard // ----------------------------- deploymentPathRaw := ctx.UserValue("deploymentPath") if deploymentPathRaw == nil { return nil } deploymentPath, ok := deploymentPathRaw.(string) if !ok { return nil } // Guard against empty input before splitting if deploymentPath == "" { return nil } // Example: // gpt-4o/chat/completions // openai/gpt-4o/chat/completions // huggingface/fal-ai/flux/dev/images/generations parts := strings.Split(deploymentPath, "/") if len(parts) < 1 { return nil } // Find endpoint start endpointIndex := -1 for i, p := range parts { if azureEndpointStarters[p] { endpointIndex = i break } } if endpointIndex == -1 { return errors.New("invalid azure deployment path") } deploymentParts := parts[:endpointIndex] if len(deploymentParts) == 0 { return errors.New("missing deployment id") } var deploymentProviderStr string var deploymentIDStr string // provider/deployment-id OR deployment-id if len(deploymentParts) >= 2 { deploymentProviderStr = deploymentParts[0] deploymentIDStr = strings.Join(deploymentParts[1:], "/") } else { deploymentIDStr = deploymentParts[0] } // ----------------------------- // Set Model // ----------------------------- setModel := func(currentModel string) string { if deploymentProviderStr != "" { return deploymentProviderStr + "/" + deploymentIDStr } return setAzureModelName(currentModel, deploymentIDStr) } switch r := req.(type) { case *openai.OpenAITextCompletionRequest: r.Model = setModel(r.Model) case *openai.OpenAIChatRequest: r.Model = setModel(r.Model) case *openai.OpenAIResponsesRequest: r.Model = setModel(r.Model) case *openai.OpenAISpeechRequest: r.Model = setModel(r.Model) case *openai.OpenAITranscriptionRequest: r.Model = setModel(r.Model) case *openai.OpenAIEmbeddingRequest: r.Model = setModel(r.Model) case *openai.OpenAIImageGenerationRequest: r.Model = setModel(r.Model) case *openai.OpenAIImageEditRequest: r.Model = setModel(r.Model) case *openai.OpenAIImageVariationRequest: r.Model = setModel(r.Model) case *schemas.BifrostListModelsRequest: if deploymentProviderStr != "" { r.Provider = schemas.ModelProvider(deploymentProviderStr) } else { r.Provider = schemas.Azure } } // ----------------------------- // Direct Azure Keys // ----------------------------- if deploymentEndpoint == nil || azureKey == nil || !handlerStore.ShouldAllowDirectKeys() { return nil } // Non-Azure providers skip direct Azure keys if deploymentProviderStr != "" && deploymentProviderStr != string(schemas.Azure) { return nil } azureKeyStr := string(azureKey) deploymentEndpointStr := string(deploymentEndpoint) apiVersionStr := apiVersion key := schemas.Key{ ID: uuid.New().String(), Models: schemas.WhiteList{"*"}, AzureKeyConfig: &schemas.AzureKeyConfig{}, } if deploymentEndpointStr != "" && deploymentIDStr != "" && azureKeyStr != "" { key.Value = *schemas.NewEnvVar(strings.TrimPrefix(azureKeyStr, "Bearer ")) key.AzureKeyConfig.Endpoint = *schemas.NewEnvVar(deploymentEndpointStr) } if apiVersionStr != "" { key.AzureKeyConfig.APIVersion = schemas.NewEnvVar(apiVersionStr) } ctx.SetUserValue(schemas.BifrostContextKeyDirectKey, key) return nil } } // CreateOpenAIRouteConfigs creates route configurations for OpenAI endpoints. func CreateOpenAIRouteConfigs(pathPrefix string, handlerStore lib.HandlerStore) []RouteConfig { var routes []RouteConfig routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + "/openai/deployments/{deploymentPath:*}", Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { deploymentPathVal, ok := ctx.UserValue("deploymentPath").(string) if !ok { return schemas.UnknownRequest } path := deploymentPathVal switch { case strings.HasSuffix(path, "/chat/completions"): return schemas.ChatCompletionRequest case strings.HasSuffix(path, "/completions"): return schemas.TextCompletionRequest case strings.HasSuffix(path, "/embeddings"): return schemas.EmbeddingRequest case strings.HasSuffix(path, "/audio/speech"): return schemas.SpeechRequest case strings.HasSuffix(path, "/audio/transcriptions"): return schemas.TranscriptionRequest case strings.HasSuffix(path, "/images/generations"): return schemas.ImageGenerationRequest case strings.HasSuffix(path, "/images/edits"): return schemas.ImageEditRequest case strings.HasSuffix(path, "/images/variations"): return schemas.ImageVariationRequest } return schemas.UnknownRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { if requestType, ok := ctx.Value(schemas.BifrostContextKeyHTTPRequestType).(schemas.RequestType); ok { switch requestType { case schemas.ChatCompletionRequest: return &openai.OpenAIChatRequest{} case schemas.TextCompletionRequest: return &openai.OpenAITextCompletionRequest{} case schemas.EmbeddingRequest: return &openai.OpenAIEmbeddingRequest{} case schemas.SpeechRequest: return &openai.OpenAISpeechRequest{} case schemas.TranscriptionRequest: return &openai.OpenAITranscriptionRequest{} case schemas.ImageGenerationRequest: return &openai.OpenAIImageGenerationRequest{} case schemas.ImageEditRequest: return &openai.OpenAIImageEditRequest{} case schemas.ImageVariationRequest: return &openai.OpenAIImageVariationRequest{} default: return &openai.OpenAIChatRequest{} } } return &openai.OpenAIChatRequest{} }, // Dynamic RequestParser: dispatch to the correct multipart parser for // transcription/image-edit/image-variation, fall through to default JSON // parsing (nil return) for everything else. RequestParser: func(ctx *fasthttp.RequestCtx, req interface{}) error { switch req.(type) { case *openai.OpenAITranscriptionRequest: return parseTranscriptionMultipartRequest(ctx, req) case *openai.OpenAIImageEditRequest: return parseOpenAIImageEditMultipartRequest(ctx, req) case *openai.OpenAIImageVariationRequest: return parseOpenAIImageVariationMultipartRequest(ctx, req) default: // JSON-based request — parse manually here since returning nil // would mean "no error, parsing done" but body wasn't parsed. rawBody := ctx.Request.Body() if len(rawBody) > 0 { return sonic.Unmarshal(rawBody, req) } return nil } }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if openaiReq, ok := req.(*openai.OpenAIChatRequest); ok { return &schemas.BifrostRequest{ ChatRequest: openaiReq.ToBifrostChatRequest(ctx), }, nil } else if openaiReq, ok := req.(*openai.OpenAITextCompletionRequest); ok { return &schemas.BifrostRequest{ TextCompletionRequest: openaiReq.ToBifrostTextCompletionRequest(ctx), }, nil } else if openaiReq, ok := req.(*openai.OpenAIEmbeddingRequest); ok { return &schemas.BifrostRequest{ EmbeddingRequest: openaiReq.ToBifrostEmbeddingRequest(ctx), }, nil } else if openaiReq, ok := req.(*openai.OpenAISpeechRequest); ok { return &schemas.BifrostRequest{ SpeechRequest: openaiReq.ToBifrostSpeechRequest(ctx), }, nil } else if openaiReq, ok := req.(*openai.OpenAITranscriptionRequest); ok { return &schemas.BifrostRequest{ TranscriptionRequest: openaiReq.ToBifrostTranscriptionRequest(ctx), }, nil } else if openaiReq, ok := req.(*openai.OpenAIImageGenerationRequest); ok { return &schemas.BifrostRequest{ ImageGenerationRequest: openaiReq.ToBifrostImageGenerationRequest(ctx), }, nil } else if openaiReq, ok := req.(*openai.OpenAIImageEditRequest); ok { return &schemas.BifrostRequest{ ImageEditRequest: openaiReq.ToBifrostImageEditRequest(ctx), }, nil } else if openaiReq, ok := req.(*openai.OpenAIImageVariationRequest); ok { return &schemas.BifrostRequest{ ImageVariationRequest: openaiReq.ToBifrostImageVariationRequest(ctx), }, nil } return nil, errors.New("invalid request type") }, ChatResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostChatResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, TextResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostTextCompletionResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, EmbeddingResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostEmbeddingResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, SpeechResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostSpeechResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, TranscriptionResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostTranscriptionResponse) (interface{}, error) { if schemas.IsPlainTextTranscriptionFormat(resp.ResponseFormat) { return []byte(resp.Text), nil } if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, ImageGenerationResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostImageGenerationResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, StreamConfig: &StreamConfig{ ChatStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostChatResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, TextStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostTextCompletionResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, SpeechStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostSpeechStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, TranscriptionStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostTranscriptionStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, ImageGenerationStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostImageGenerationStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: AzureEndpointPreHook(handlerStore), }) // Chat completions endpoint for _, path := range []string{ "/v1/chat/completions", "/chat/completions", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ChatCompletionRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIChatRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if openaiReq, ok := req.(*openai.OpenAIChatRequest); ok { br := &schemas.BifrostRequest{ ChatRequest: openaiReq.ToBifrostChatRequest(ctx), } return br, nil } return nil, errors.New("invalid request type") }, ChatResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostChatResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } // Here we will combine content blocks into a single text block as required by openai SDK if len(resp.Choices) == 0 { return resp, nil } choice := resp.Choices[0] allText := true message := choice.ChatNonStreamResponseChoice.Message if message == nil || message.Content == nil || message.Content.ContentBlocks == nil { return resp, nil } for _, block := range message.Content.ContentBlocks { if block.Type != schemas.ChatContentBlockTypeText { allText = false break } } if !allText || len(message.Content.ContentBlocks) == 0 { return resp, nil } var contentStr *string contentBlocks := message.Content.ContentBlocks var reasoningDetails []schemas.ChatReasoningDetails if message.ChatAssistantMessage != nil && message.ChatAssistantMessage.ReasoningDetails != nil { reasoningDetails = message.ChatAssistantMessage.ReasoningDetails } needsCombine := len(contentBlocks) > 1 if !needsCombine { contentStr = contentBlocks[0].Text } else { var parts []string // Then text blocks top to bottom for _, block := range contentBlocks { if block.Text != nil { parts = append(parts, *block.Text) } } joined := strings.Join(parts, "\n\n") contentStr = &joined } if message.ChatAssistantMessage != nil { message.ReasoningDetails = reasoningDetails } message.Content.ContentStr = contentStr message.Content.ContentBlocks = nil return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, StreamConfig: &StreamConfig{ ChatStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostChatResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, }) } // Text completions endpoint for _, path := range []string{ "/v1/completions", "/completions", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.TextCompletionRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAITextCompletionRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if openaiReq, ok := req.(*openai.OpenAITextCompletionRequest); ok { return &schemas.BifrostRequest{ TextCompletionRequest: openaiReq.ToBifrostTextCompletionRequest(ctx), }, nil } return nil, errors.New("invalid request type") }, TextResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostTextCompletionResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, StreamConfig: &StreamConfig{ TextStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostTextCompletionResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, }) } // Responses endpoint for _, path := range []string{ "/v1/responses", "/responses", "/openai/responses", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ResponsesRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIResponsesRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if openaiReq, ok := req.(*openai.OpenAIResponsesRequest); ok { return &schemas.BifrostRequest{ ResponsesRequest: openaiReq.ToBifrostResponsesRequest(ctx), }, nil } return nil, errors.New("invalid request type") }, ResponsesResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostResponsesResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp.WithDefaults(), nil }, AsyncResponsesResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.AsyncJobResponse, responsesResponseConverter ResponsesResponseConverter) (interface{}, map[string]string, error) { bifrostResponse := &schemas.BifrostResponsesResponse{ ID: &resp.ID, Status: bifrost.Ptr(string(resp.Status)), } if resp.Status == schemas.AsyncJobStatusCompleted { responsesResp, ok := resp.Result.(*schemas.BifrostResponsesResponse) if !ok { return nil, nil, errors.New("invalid responses response type") } bifrostResponse = responsesResp } response, err := responsesResponseConverter(ctx, bifrostResponse) if err != nil { return nil, nil, err } return response, nil, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, StreamConfig: &StreamConfig{ ResponsesStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostResponsesStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return string(resp.Type), resp.ExtraFields.RawResponse, nil } } converted := resp.WithDefaults() if converted == nil { return "", nil, nil } return string(resp.Type), converted, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, PreCallback: func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { hydrateOpenAIRequestFromLargePayloadMetadata(ctx, bifrostCtx, req) schemas.ExtractAndSetUserAgentFromHeaders(extractHeadersFromRequest(ctx), bifrostCtx) if isAzureSDKRequest(ctx) { bifrostCtx.SetValue(schemas.BifrostContextKeyIsAzureUserAgent, true) } return nil }, }) } // Input tokens endpoint (for counting tokens in a request) for _, path := range []string{ "/v1/responses/input_tokens", "/responses/input_tokens", "/openai/responses/input_tokens", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.CountTokensRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIResponsesRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if openaiReq, ok := req.(*openai.OpenAIResponsesRequest); ok { return &schemas.BifrostRequest{ CountTokensRequest: openaiReq.ToBifrostResponsesRequest(ctx), }, nil } return nil, errors.New("invalid request type for input tokens") }, CountTokensResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostCountTokensResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }) } // Embeddings endpoint for _, path := range []string{ "/v1/embeddings", "/embeddings", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.EmbeddingRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIEmbeddingRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if embeddingReq, ok := req.(*openai.OpenAIEmbeddingRequest); ok { return &schemas.BifrostRequest{ EmbeddingRequest: embeddingReq.ToBifrostEmbeddingRequest(ctx), }, nil } return nil, errors.New("invalid embedding request type") }, EmbeddingResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostEmbeddingResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }) } // Speech synthesis endpoint for _, path := range []string{ "/v1/audio/speech", "/audio/speech", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.SpeechRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAISpeechRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if speechReq, ok := req.(*openai.OpenAISpeechRequest); ok { return &schemas.BifrostRequest{ SpeechRequest: speechReq.ToBifrostSpeechRequest(ctx), }, nil } return nil, errors.New("invalid speech request type") }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, StreamConfig: &StreamConfig{ SpeechStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostSpeechStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, }) } // Audio transcription endpoint for _, path := range []string{ "/v1/audio/transcriptions", "/audio/transcriptions", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.TranscriptionRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAITranscriptionRequest{} }, RequestParser: parseTranscriptionMultipartRequest, // Handle multipart form parsing RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if transcriptionReq, ok := req.(*openai.OpenAITranscriptionRequest); ok { return &schemas.BifrostRequest{ TranscriptionRequest: transcriptionReq.ToBifrostTranscriptionRequest(ctx), }, nil } return nil, errors.New("invalid transcription request type") }, TranscriptionResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostTranscriptionResponse) (interface{}, error) { if schemas.IsPlainTextTranscriptionFormat(resp.ResponseFormat) { return []byte(resp.Text), nil } if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, StreamConfig: &StreamConfig{ TranscriptionStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostTranscriptionStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return "", resp.ExtraFields.RawResponse, nil } } return "", resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, }) } // Image Generation endpoint for _, path := range []string{ "/v1/images/generations", "/images/generations", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ImageGenerationRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIImageGenerationRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if imageGenReq, ok := req.(*openai.OpenAIImageGenerationRequest); ok { return &schemas.BifrostRequest{ ImageGenerationRequest: imageGenReq.ToBifrostImageGenerationRequest(ctx), }, nil } return nil, errors.New("invalid image generation request type") }, ImageGenerationResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostImageGenerationResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, StreamConfig: &StreamConfig{ ImageGenerationStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostImageGenerationStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return string(resp.Type), resp.ExtraFields.RawResponse, nil } } return string(resp.Type), resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, }) } for _, path := range []string{ "/v1/images/edits", "/images/edits", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ImageEditRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIImageEditRequest{} }, RequestParser: parseOpenAIImageEditMultipartRequest, // Handle multipart form parsing RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if imageEditReq, ok := req.(*openai.OpenAIImageEditRequest); ok { return &schemas.BifrostRequest{ ImageEditRequest: imageEditReq.ToBifrostImageEditRequest(ctx), }, nil } return nil, errors.New("invalid image edit request type") }, ImageGenerationResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostImageGenerationResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, StreamConfig: &StreamConfig{ ImageGenerationStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostImageGenerationStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return string(resp.Type), resp.ExtraFields.RawResponse, nil } } return string(resp.Type), resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, }) } for _, path := range []string{ "/v1/images/variations", "/images/variations", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", PreCallback: openAILargePayloadPreHook, GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ImageVariationRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIImageVariationRequest{} }, RequestParser: parseOpenAIImageVariationMultipartRequest, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if imageVariationReq, ok := req.(*openai.OpenAIImageVariationRequest); ok { return &schemas.BifrostRequest{ ImageVariationRequest: imageVariationReq.ToBifrostImageVariationRequest(ctx), }, nil } return nil, errors.New("invalid image variation request type") }, ImageGenerationResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostImageGenerationResponse) (interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return resp.ExtraFields.RawResponse, nil } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, StreamConfig: &StreamConfig{ ImageGenerationStreamResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostImageGenerationStreamResponse) (string, interface{}, error) { if resp.ExtraFields.Provider == schemas.OpenAI { if resp.ExtraFields.RawResponse != nil { return string(resp.Type), resp.ExtraFields.RawResponse, nil } } return string(resp.Type), resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }, }) } // generate video endpoint for _, path := range []string{ "/v1/videos", "/videos", "/openai/videos", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.VideoGenerationRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIVideoGenerationRequest{} }, RequestParser: parseOpenAIVideoGenerationMultipartRequest, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if videoGenerationReq, ok := req.(*openai.OpenAIVideoGenerationRequest); ok { return &schemas.BifrostRequest{ VideoGenerationRequest: videoGenerationReq.ToBifrostVideoGenerationRequest(ctx), }, nil } return nil, errors.New("invalid video generation request type") }, VideoGenerationResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostVideoGenerationResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { if isAzureSDKRequest(ctx) { bifrostCtx.SetValue(schemas.BifrostContextKeyIsAzureUserAgent, true) } return nil }, }) } // retrieve video endpoint for _, path := range []string{ "/v1/videos/{video_id}", "/videos/{video_id}", "/openai/videos/{video_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.VideoRetrieveRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostVideoRetrieveRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if videoRetrieveReq, ok := req.(*schemas.BifrostVideoRetrieveRequest); ok { return &schemas.BifrostRequest{ VideoRetrieveRequest: videoRetrieveReq, }, nil } return nil, errors.New("invalid video retrieve request type") }, VideoGenerationResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostVideoGenerationResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractVideoIDFromPath(handlerStore), }) } // download video endpoint for _, path := range []string{ "/v1/videos/{video_id}/content", "/videos/{video_id}/content", "/openai/videos/{video_id}/content", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.VideoDownloadRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostVideoDownloadRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if videoDownloadReq, ok := req.(*schemas.BifrostVideoDownloadRequest); ok { return &schemas.BifrostRequest{ VideoDownloadRequest: videoDownloadReq, }, nil } return nil, errors.New("invalid video retrieve request type") }, VideoDownloadResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostVideoDownloadResponse) (interface{}, error) { return resp.Content, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractVideoIDFromPath(handlerStore), }) } // delete video endpoint for _, path := range []string{ "/v1/videos/{video_id}", "/videos/{video_id}", "/openai/videos/{video_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "DELETE", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.VideoDeleteRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostVideoDeleteRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if videoDeleteReq, ok := req.(*schemas.BifrostVideoDeleteRequest); ok { return &schemas.BifrostRequest{ VideoDeleteRequest: videoDeleteReq, }, nil } return nil, errors.New("invalid video delete request type") }, VideoDeleteResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostVideoDeleteResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractVideoIDFromPath(handlerStore), }) } // remix video endpoint for _, path := range []string{ "/v1/videos/{video_id}/remix", "/videos/{video_id}/remix", "/openai/videos/{video_id}/remix", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.VideoRemixRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &openai.OpenAIVideoRemixRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if videoRemixReq, ok := req.(*openai.OpenAIVideoRemixRequest); ok { return &schemas.BifrostRequest{ VideoRemixRequest: openai.ToBifrostVideoRemixRequest(videoRemixReq), }, nil } return nil, errors.New("invalid video remix request type") }, VideoGenerationResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostVideoGenerationResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractVideoIDFromPath(handlerStore), }) } // list videos endpoint for _, path := range []string{ "/v1/videos", "/videos", "/openai/videos", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.VideoListRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostVideoListRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if videoListReq, ok := req.(*schemas.BifrostVideoListRequest); ok { return &schemas.BifrostRequest{ VideoListRequest: videoListReq, }, nil } return nil, errors.New("invalid video list request type") }, VideoListResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostVideoListResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }) } return routes } // CreateOpenAIListModelsRouteConfigs creates route configurations for OpenAI list models endpoint. func CreateOpenAIListModelsRouteConfigs(pathPrefix string, handlerStore lib.HandlerStore) []RouteConfig { var routes []RouteConfig // Models endpoint for _, path := range []string{ "/v1/models", "/models", "/openai/models", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ListModelsRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostListModelsRequest{} }, RequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*schemas.BifrostRequest, error) { if listModelsReq, ok := req.(*schemas.BifrostListModelsRequest); ok { return &schemas.BifrostRequest{ ListModelsRequest: listModelsReq, }, nil } return nil, errors.New("invalid request type") }, ListModelsResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostListModelsResponse) (interface{}, error) { return openai.ToOpenAIListModelsResponse(resp), nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, }) } return routes } // CreateOpenAIBatchRouteConfigs creates route configurations for OpenAI Batch API endpoints. func CreateOpenAIBatchRouteConfigs(pathPrefix string, handlerStore lib.HandlerStore) []RouteConfig { var routes []RouteConfig // Create batch endpoint - POST /v1/batches for _, path := range []string{ "/v1/batches", "/batches", "/openai/batches", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.BatchCreateRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostBatchCreateRequest{} }, BatchRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*BatchRequest, error) { if openaiReq, ok := req.(*schemas.BifrostBatchCreateRequest); ok { switch openaiReq.Provider { case schemas.Gemini: if openaiReq.InputFileID != "" { openaiReq.InputFileID = strings.Replace(openaiReq.InputFileID, "files-", "files/", 1) } case schemas.Bedrock: if openaiReq.InputFileID != "" { // Base64 decode the input field id if it's base64 encoded if decodedFileID, err := base64.StdEncoding.DecodeString(openaiReq.InputFileID); err == nil { openaiReq.InputFileID = string(decodedFileID) } } } return &BatchRequest{ Type: schemas.BatchCreateRequest, CreateRequest: openaiReq, }, nil } return nil, errors.New("invalid batch create request type") }, BatchCreateResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostBatchCreateResponse) (interface{}, error) { switch resp.ExtraFields.Provider { case schemas.Gemini: resp.ID = strings.Replace(resp.ID, "batches/", "batches-", 1) resp.InputFileID = strings.Replace(resp.InputFileID, "files/", "files-", 1) case schemas.Bedrock: resp.ID = base64.StdEncoding.EncodeToString([]byte(resp.ID)) resp.InputFileID = base64.StdEncoding.EncodeToString([]byte(resp.InputFileID)) } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { // Provider is parsed from JSON body (extra_body), default to OpenAI if not set if createReq, ok := req.(*schemas.BifrostBatchCreateRequest); ok { if createReq.Provider == "" { if isAzureSDKRequest(ctx) { createReq.Provider = schemas.Azure } else { createReq.Provider = schemas.OpenAI } } // For Bedrock, extract extra params from raw body // ExtraParams has json:"-" tag so it's not auto-populated if createReq.Provider == schemas.Bedrock { var extraFields map[string]interface{} if err := json.Unmarshal(ctx.Request.Body(), &extraFields); err == nil { if createReq.ExtraParams == nil { createReq.ExtraParams = make(map[string]interface{}) } // Extract role_arn (required for Bedrock) if roleArn, ok := extraFields["role_arn"].(string); ok { createReq.ExtraParams["role_arn"] = roleArn } // Extract output_s3_uri (required for Bedrock) if outputS3Uri, ok := extraFields["output_s3_uri"].(string); ok { createReq.ExtraParams["output_s3_uri"] = outputS3Uri } // Extract job_name (optional, stored in Metadata) if jobName, ok := extraFields["job_name"].(string); ok { if createReq.Metadata == nil { createReq.Metadata = make(map[string]string) } createReq.Metadata["job_name"] = jobName } } } // For Anthropic, extract inline requests from raw body // Anthropic uses inline requests instead of file-based batching if createReq.Provider == schemas.Anthropic { var extraFields map[string]interface{} if err := json.Unmarshal(ctx.Request.Body(), &extraFields); err == nil { // Extract requests array for inline batching if requestsRaw, ok := extraFields["requests"].([]interface{}); ok { createReq.Requests = make([]schemas.BatchRequestItem, len(requestsRaw)) for i, r := range requestsRaw { if reqMap, ok := r.(map[string]interface{}); ok { item := schemas.BatchRequestItem{} if customID, ok := reqMap["custom_id"].(string); ok { item.CustomID = customID } if params, ok := reqMap["params"].(map[string]interface{}); ok { item.Params = params } createReq.Requests[i] = item } } } } } } return nil }, }) } // List batches endpoint - GET /v1/batches for _, path := range []string{ "/v1/batches", "/batches", "/openai/batches", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.BatchListRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostBatchListRequest{} }, BatchRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*BatchRequest, error) { if listReq, ok := req.(*schemas.BifrostBatchListRequest); ok { if listReq.Provider == "" { listReq.Provider = schemas.OpenAI } return &BatchRequest{ Type: schemas.BatchListRequest, ListRequest: listReq, }, nil } return nil, errors.New("invalid batch list request type") }, BatchListResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostBatchListResponse) (interface{}, error) { switch resp.ExtraFields.Provider { case schemas.Gemini: for i, batch := range resp.Data { resp.Data[i].ID = strings.Replace(batch.ID, "batches/", "batches-", 1) resp.Data[i].InputFileID = strings.Replace(batch.InputFileID, "files/", "files-", 1) } case schemas.Bedrock: for i, batch := range resp.Data { resp.Data[i].ID = base64.StdEncoding.EncodeToString([]byte(batch.ID)) resp.Data[i].InputFileID = base64.StdEncoding.EncodeToString([]byte(batch.InputFileID)) } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractBatchListQueryParams(handlerStore), }) } // Retrieve batch endpoint - GET /v1/batches/{batch_id} for _, path := range []string{ "/v1/batches/{batch_id}", "/batches/{batch_id}", "/openai/batches/{batch_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.BatchRetrieveRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostBatchRetrieveRequest{} }, BatchRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*BatchRequest, error) { if retrieveReq, ok := req.(*schemas.BifrostBatchRetrieveRequest); ok { if retrieveReq.Provider == "" { retrieveReq.Provider = schemas.OpenAI } switch retrieveReq.Provider { case schemas.Gemini: retrieveReq.BatchID = strings.Replace(retrieveReq.BatchID, "batches-", "batches/", 1) case schemas.Bedrock: // Base64 decode the batch ID (ARN) for Bedrock if decodedBatchID, err := base64.StdEncoding.DecodeString(retrieveReq.BatchID); err == nil { retrieveReq.BatchID = string(decodedBatchID) } } return &BatchRequest{ Type: schemas.BatchRetrieveRequest, RetrieveRequest: retrieveReq, }, nil } return nil, errors.New("invalid batch retrieve request type") }, BatchRetrieveResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostBatchRetrieveResponse) (interface{}, error) { switch resp.ExtraFields.Provider { case schemas.Gemini: resp.ID = strings.Replace(resp.ID, "batches/", "batches-", 1) resp.InputFileID = strings.Replace(resp.InputFileID, "files/", "files-", 1) case schemas.Bedrock: resp.ID = base64.StdEncoding.EncodeToString([]byte(resp.ID)) resp.InputFileID = base64.StdEncoding.EncodeToString([]byte(resp.InputFileID)) } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractBatchIDFromPath(handlerStore), }) } // Cancel batch endpoint - POST /v1/batches/{batch_id}/cancel for _, path := range []string{ "/v1/batches/{batch_id}/cancel", "/batches/{batch_id}/cancel", "/openai/batches/{batch_id}/cancel", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.BatchCancelRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostBatchCancelRequest{} }, BatchRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*BatchRequest, error) { if cancelReq, ok := req.(*schemas.BifrostBatchCancelRequest); ok { if cancelReq.Provider == "" { cancelReq.Provider = schemas.OpenAI } switch cancelReq.Provider { case schemas.Gemini: cancelReq.BatchID = strings.Replace(cancelReq.BatchID, "batches-", "batches/", 1) case schemas.Bedrock: // Base64 decode the batch ID (ARN) for Bedrock if decodedBatchID, err := base64.StdEncoding.DecodeString(cancelReq.BatchID); err == nil { cancelReq.BatchID = string(decodedBatchID) } } return &BatchRequest{ Type: schemas.BatchCancelRequest, CancelRequest: cancelReq, }, nil } return nil, errors.New("invalid batch cancel request type") }, BatchCancelResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostBatchCancelResponse) (interface{}, error) { switch resp.ExtraFields.Provider { case schemas.Gemini: resp.ID = strings.Replace(resp.ID, "batches/", "batches-", 1) case schemas.Bedrock: resp.ID = base64.StdEncoding.EncodeToString([]byte(resp.ID)) } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractBatchIDFromPath(handlerStore), }) } return routes } // CreateOpenAIFileRouteConfigs creates route configurations for OpenAI Files API endpoints. func CreateOpenAIFileRouteConfigs(pathPrefix string, handlerStore lib.HandlerStore) []RouteConfig { var routes []RouteConfig // Upload file endpoint - POST /v1/files for _, path := range []string{ "/v1/files", "/files", "/openai/files", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.FileUploadRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostFileUploadRequest{} }, RequestParser: parseOpenAIFileUploadMultipartRequest, FileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*FileRequest, error) { if uploadReq, ok := req.(*schemas.BifrostFileUploadRequest); ok { return &FileRequest{ Type: schemas.FileUploadRequest, UploadRequest: uploadReq, }, nil } return nil, errors.New("invalid file upload request type") }, FileUploadResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostFileUploadResponse) (interface{}, error) { if resp.ExtraFields.RawResponse != nil && resp.ExtraFields.Provider == schemas.OpenAI { return resp.ExtraFields.RawResponse, nil } switch resp.ExtraFields.Provider { case schemas.Gemini: resp.ID = strings.Replace(resp.ID, "files/", "files-", 1) case schemas.Bedrock: resp.ID = base64.StdEncoding.EncodeToString([]byte(resp.ID)) default: return resp, nil } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { // Default to OpenAI if provider not set from extra_body if bifrostReq, ok := req.(*schemas.BifrostFileUploadRequest); ok { if bifrostReq.Provider == "" { if isAzureSDKRequest(ctx) { bifrostReq.Provider = schemas.Azure } else { bifrostReq.Provider = schemas.OpenAI } } } return nil }, }) } // List files endpoint - GET /v1/files for _, path := range []string{ "/v1/files", "/files", "/openai/files", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.FileListRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostFileListRequest{} }, FileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*FileRequest, error) { if listReq, ok := req.(*schemas.BifrostFileListRequest); ok { if listReq.Provider == "" { listReq.Provider = schemas.OpenAI } return &FileRequest{ Type: schemas.FileListRequest, ListRequest: listReq, }, nil } return nil, errors.New("invalid file list request type") }, FileListResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostFileListResponse) (interface{}, error) { if resp.ExtraFields.RawResponse != nil && resp.ExtraFields.Provider == schemas.OpenAI { return resp.ExtraFields.RawResponse, nil } switch resp.ExtraFields.Provider { case schemas.Gemini: for i, file := range resp.Data { resp.Data[i].ID = strings.Replace(file.ID, "files/", "files-", 1) } case schemas.Bedrock: for i, file := range resp.Data { resp.Data[i].ID = base64.StdEncoding.EncodeToString([]byte(file.ID)) } } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractFileListQueryParams(handlerStore), }) } // Retrieve file endpoint - GET /v1/files/{file_id} for _, path := range []string{ "/v1/files/{file_id}", "/files/{file_id}", "/openai/files/{file_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.FileRetrieveRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostFileRetrieveRequest{} }, FileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*FileRequest, error) { if retrieveReq, ok := req.(*schemas.BifrostFileRetrieveRequest); ok { if retrieveReq.Provider == "" { retrieveReq.Provider = schemas.OpenAI } if retrieveReq.Provider == schemas.Gemini { retrieveReq.FileID = strings.Replace(retrieveReq.FileID, "files-", "files/", 1) } return &FileRequest{ Type: schemas.FileRetrieveRequest, RetrieveRequest: retrieveReq, }, nil } return nil, errors.New("invalid file content request type") }, FileRetrieveResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostFileRetrieveResponse) (interface{}, error) { // Raw response is invalid even for OpenAI switch resp.ExtraFields.Provider { case schemas.Gemini: resp.ID = strings.Replace(resp.ID, "files/", "files-", 1) case schemas.Bedrock: resp.ID = base64.StdEncoding.EncodeToString([]byte(resp.ID)) default: return resp, nil } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractFileIDFromPath(handlerStore), }) } // Delete file endpoint - DELETE /v1/files/{file_id} for _, path := range []string{ "/v1/files/{file_id}", "/files/{file_id}", "/openai/files/{file_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "DELETE", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.FileDeleteRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostFileDeleteRequest{} }, FileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*FileRequest, error) { if deleteReq, ok := req.(*schemas.BifrostFileDeleteRequest); ok { if deleteReq.Provider == "" { deleteReq.Provider = schemas.OpenAI } if deleteReq.Provider == schemas.Gemini { deleteReq.FileID = strings.Replace(deleteReq.FileID, "files-", "files/", 1) } return &FileRequest{ Type: schemas.FileDeleteRequest, DeleteRequest: deleteReq, }, nil } return nil, errors.New("invalid file delete request type") }, FileDeleteResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostFileDeleteResponse) (interface{}, error) { if resp.ExtraFields.RawResponse != nil && resp.ExtraFields.Provider == schemas.OpenAI { return resp.ExtraFields.RawResponse, nil } switch resp.ExtraFields.Provider { case schemas.Gemini: resp.ID = strings.Replace(resp.ID, "files/", "files-", 1) case schemas.Bedrock: resp.ID = base64.StdEncoding.EncodeToString([]byte(resp.ID)) default: return resp, nil } return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractFileIDFromPath(handlerStore), }) } // Get file content endpoint - GET /v1/files/{file_id}/content for _, path := range []string{ "/v1/files/{file_id}/content", "/files/{file_id}/content", "/openai/files/{file_id}/content", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.FileContentRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostFileContentRequest{} }, FileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*FileRequest, error) { if contentReq, ok := req.(*schemas.BifrostFileContentRequest); ok { if contentReq.Provider == "" { contentReq.Provider = schemas.OpenAI } switch contentReq.Provider { case schemas.Gemini: contentReq.FileID = strings.Replace(contentReq.FileID, "files-", "files/", 1) } return &FileRequest{ Type: schemas.FileContentRequest, ContentRequest: contentReq, }, nil } return nil, errors.New("invalid file content request type") }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractFileIDFromPath(handlerStore), }) } return routes } // extractBatchListQueryParams extracts query parameters for batch list requests func extractBatchListQueryParams(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { if listReq, ok := req.(*schemas.BifrostBatchListRequest); ok { // Extract provider from extra_query if provider := string(ctx.QueryArgs().Peek("provider")); provider != "" { listReq.Provider = schemas.ModelProvider(provider) } if listReq.Provider == "" { if isAzureSDKRequest(ctx) { listReq.Provider = schemas.Azure } else { listReq.Provider = schemas.OpenAI } } // Extract limit from query parameters if limitStr := string(ctx.QueryArgs().Peek("limit")); limitStr != "" { if limit, err := strconv.Atoi(limitStr); err == nil { listReq.Limit = limit } else { // We are keeping default as 30 listReq.Limit = 30 } } // Extract after cursor if after := string(ctx.QueryArgs().Peek("after")); after != "" { listReq.After = &after } } return nil } } // extractBatchIDFromPath extracts batch_id from path parameters and provider from query params func extractBatchIDFromPath(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { batchID := ctx.UserValue("batch_id") if batchID == nil { return errors.New("batch_id is required") } batchIDStr, ok := batchID.(string) if !ok || batchIDStr == "" { return errors.New("batch_id must be a non-empty string") } // Extract provider from extra_query (for GET requests) provider := schemas.ModelProvider(string(ctx.QueryArgs().Peek("provider"))) if provider == "" { if isAzureSDKRequest(ctx) { provider = schemas.Azure } else { provider = schemas.OpenAI } } switch r := req.(type) { case *schemas.BifrostBatchRetrieveRequest: r.BatchID = batchIDStr r.Provider = provider case *schemas.BifrostBatchCancelRequest: r.BatchID = batchIDStr // For POST cancel, provider comes from body, only set if empty if r.Provider == "" { r.Provider = provider } case *schemas.BifrostBatchResultsRequest: r.BatchID = batchIDStr r.Provider = provider } return nil } } // extractVideoIDFromPath extracts video_id from path parameters in provider:id format. func extractVideoIDFromPath(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { videoID := ctx.UserValue("video_id") if videoID == nil { return errors.New("video_id is required") } videoIDStr, ok := videoID.(string) if !ok || videoIDStr == "" { return errors.New("video_id must be a non-empty string") } decodedVideoID, err := url.PathUnescape(videoIDStr) if err != nil { return errors.New("invalid video_id encoding") } providerName, rawVideoID, err := ParseProviderScopedVideoID(decodedVideoID) if err != nil { return err } // extract variant from query parameters variant := string(ctx.QueryArgs().Peek("variant")) if variant == "" { variant = "video" } switch r := req.(type) { case *schemas.BifrostVideoReferenceRequest: r.Provider = providerName r.ID = rawVideoID case *schemas.BifrostVideoDownloadRequest: r.Provider = providerName r.ID = rawVideoID r.Variant = schemas.Ptr(schemas.VideoDownloadVariant(variant)) case *openai.OpenAIVideoRemixRequest: r.Provider = providerName r.ID = rawVideoID } return nil } } // extractFileListQueryParams extracts query parameters for file list requests func extractFileListQueryParams(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { if listReq, ok := req.(*schemas.BifrostFileListRequest); ok { // Extract provider from extra_query if provider := string(ctx.QueryArgs().Peek("provider")); provider != "" { listReq.Provider = schemas.ModelProvider(provider) } if listReq.Provider == "" { if isAzureSDKRequest(ctx) { listReq.Provider = schemas.Azure } else { listReq.Provider = schemas.OpenAI } } // We extract S3 storage config from extra_query for Bedrock provider only. if listReq.Provider == schemas.Bedrock { // Extract S3 storage config from extra_query (bracket notation: storage_config[s3][bucket]) if s3Bucket := string(ctx.QueryArgs().Peek("storage_config[s3][bucket]")); s3Bucket != "" { if listReq.StorageConfig == nil { listReq.StorageConfig = &schemas.FileStorageConfig{} } if listReq.StorageConfig.S3 == nil { listReq.StorageConfig.S3 = &schemas.S3StorageConfig{} } listReq.StorageConfig.S3.Bucket = s3Bucket } if s3Region := string(ctx.QueryArgs().Peek("storage_config[s3][region]")); s3Region != "" { if listReq.StorageConfig == nil { listReq.StorageConfig = &schemas.FileStorageConfig{} } if listReq.StorageConfig.S3 == nil { listReq.StorageConfig.S3 = &schemas.S3StorageConfig{} } listReq.StorageConfig.S3.Region = s3Region } if s3Prefix := string(ctx.QueryArgs().Peek("storage_config[s3][prefix]")); s3Prefix != "" { if listReq.StorageConfig == nil { listReq.StorageConfig = &schemas.FileStorageConfig{} } if listReq.StorageConfig.S3 == nil { listReq.StorageConfig.S3 = &schemas.S3StorageConfig{} } listReq.StorageConfig.S3.Prefix = s3Prefix } } // Extract purpose filter if purpose := string(ctx.QueryArgs().Peek("purpose")); purpose != "" { listReq.Purpose = schemas.FilePurpose(purpose) } // Extract limit if limitStr := string(ctx.QueryArgs().Peek("limit")); limitStr != "" { if limit, err := strconv.Atoi(limitStr); err == nil { listReq.Limit = limit } } // Extract after cursor if after := string(ctx.QueryArgs().Peek("after")); after != "" { listReq.After = &after } // Extract order if order := string(ctx.QueryArgs().Peek("order")); order != "" { listReq.Order = &order } } return nil } } // extractFileIDFromPath extracts file_id from path parameters and provider/S3 config from query params func extractFileIDFromPath(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { fileID := ctx.UserValue("file_id") if fileID == nil { return errors.New("file_id is required") } fileIDStr, ok := fileID.(string) if !ok || fileIDStr == "" { return errors.New("file_id must be a non-empty string") } // Extract provider from extra_query provider := schemas.ModelProvider(string(ctx.QueryArgs().Peek("provider"))) if provider == "" { if isAzureSDKRequest(ctx) { provider = schemas.Azure } else { provider = schemas.OpenAI } } var storageConfig *schemas.FileStorageConfig if provider == schemas.Bedrock { // Check fileIDStr is base64 encoded if decodedFileID, err := base64.StdEncoding.DecodeString(fileIDStr); err == nil { fileIDStr = string(decodedFileID) } // First checking if fileIDStr starting with s3:// if strings.HasPrefix(fileIDStr, "s3://") { bucket, key := parseS3URI(fileIDStr) storageConfig = &schemas.FileStorageConfig{ S3: &schemas.S3StorageConfig{ Bucket: bucket, Prefix: key, }, } } else { // Extract S3 storage config from extra_query (bracket notation: storage_config[s3][bucket]) s3Bucket := string(ctx.QueryArgs().Peek("storage_config[s3][bucket]")) s3Region := string(ctx.QueryArgs().Peek("storage_config[s3][region]")) s3Prefix := string(ctx.QueryArgs().Peek("storage_config[s3][prefix]")) if s3Bucket != "" || s3Region != "" || s3Prefix != "" { storageConfig = &schemas.FileStorageConfig{ S3: &schemas.S3StorageConfig{ Bucket: s3Bucket, Region: s3Region, Prefix: s3Prefix, }, } } } } switch r := req.(type) { case *schemas.BifrostFileRetrieveRequest: r.FileID = fileIDStr r.Provider = provider if storageConfig != nil { r.StorageConfig = storageConfig } case *schemas.BifrostFileDeleteRequest: r.FileID = fileIDStr r.Provider = provider if storageConfig != nil { r.StorageConfig = storageConfig } case *schemas.BifrostFileContentRequest: r.FileID = fileIDStr r.Provider = provider if storageConfig != nil { r.StorageConfig = storageConfig } } return nil } } // parseOpenAIFileUploadMultipartRequest parses multipart/form-data for file upload requests func parseOpenAIFileUploadMultipartRequest(ctx *fasthttp.RequestCtx, req interface{}) error { uploadReq, ok := req.(*schemas.BifrostFileUploadRequest) if !ok { return errors.New("invalid request type for file upload") } // Parse multipart form form, err := ctx.MultipartForm() if err != nil { return err } // Extract purpose (required) purposeValues := form.Value["purpose"] if len(purposeValues) == 0 || purposeValues[0] == "" { return errors.New("purpose field is required") } uploadReq.Purpose = schemas.FilePurpose(purposeValues[0]) // Extract file (required) fileHeaders := form.File["file"] if len(fileHeaders) == 0 { return errors.New("file field is required") } fileHeader := fileHeaders[0] file, err := fileHeader.Open() if err != nil { return err } defer file.Close() // Read file data fileData, err := io.ReadAll(file) if err != nil { return err } uploadReq.File = fileData uploadReq.Filename = fileHeader.Filename // Extract provider from extra_body (form field) if providerValues := form.Value["provider"]; len(providerValues) > 0 && providerValues[0] != "" { uploadReq.Provider = schemas.ModelProvider(providerValues[0]) } // Extract S3 storage config from extra_body (form fields) // OpenAI client sends nested objects as bracket notation: storage_config[s3][bucket] if uploadReq.Provider == schemas.Bedrock { if s3BucketValues := form.Value["storage_config[s3][bucket]"]; len(s3BucketValues) > 0 && s3BucketValues[0] != "" { if uploadReq.StorageConfig == nil { uploadReq.StorageConfig = &schemas.FileStorageConfig{} } if uploadReq.StorageConfig.S3 == nil { uploadReq.StorageConfig.S3 = &schemas.S3StorageConfig{} } uploadReq.StorageConfig.S3.Bucket = s3BucketValues[0] } if s3RegionValues := form.Value["storage_config[s3][region]"]; len(s3RegionValues) > 0 && s3RegionValues[0] != "" { if uploadReq.StorageConfig == nil { uploadReq.StorageConfig = &schemas.FileStorageConfig{} } if uploadReq.StorageConfig.S3 == nil { uploadReq.StorageConfig.S3 = &schemas.S3StorageConfig{} } uploadReq.StorageConfig.S3.Region = s3RegionValues[0] } if s3PrefixValues := form.Value["storage_config[s3][prefix]"]; len(s3PrefixValues) > 0 && s3PrefixValues[0] != "" { if uploadReq.StorageConfig == nil { uploadReq.StorageConfig = &schemas.FileStorageConfig{} } if uploadReq.StorageConfig.S3 == nil { uploadReq.StorageConfig.S3 = &schemas.S3StorageConfig{} } uploadReq.StorageConfig.S3.Prefix = s3PrefixValues[0] } } return nil } // CreateOpenAIContainerRouteConfigs creates route configurations for OpenAI Containers API endpoints. func CreateOpenAIContainerRouteConfigs(pathPrefix string, handlerStore lib.HandlerStore) []RouteConfig { var routes []RouteConfig // Create container endpoint - POST /v1/containers for _, path := range []string{ "/v1/containers", "/containers", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerCreateRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerCreateRequest{} }, ContainerRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerRequest, error) { enableRawRequestResponseForContainer(ctx) if createReq, ok := req.(*schemas.BifrostContainerCreateRequest); ok { return &ContainerRequest{ Type: schemas.ContainerCreateRequest, CreateRequest: createReq, }, nil } return nil, errors.New("invalid container create request type") }, ContainerCreateResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerCreateResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { if createReq, ok := req.(*schemas.BifrostContainerCreateRequest); ok { if createReq.Provider == "" { createReq.Provider = schemas.OpenAI } } return nil }, }) } // List containers endpoint - GET /v1/containers for _, path := range []string{ "/v1/containers", "/containers", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerListRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerListRequest{} }, ContainerRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerRequest, error) { enableRawRequestResponseForContainer(ctx) if listReq, ok := req.(*schemas.BifrostContainerListRequest); ok { if listReq.Provider == "" { listReq.Provider = schemas.OpenAI } return &ContainerRequest{ Type: schemas.ContainerListRequest, ListRequest: listReq, }, nil } return nil, errors.New("invalid container list request type") }, ContainerListResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerListResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractContainerListQueryParams(handlerStore), }) } // Retrieve container endpoint - GET /v1/containers/{container_id} for _, path := range []string{ "/v1/containers/{container_id}", "/containers/{container_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerRetrieveRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerRetrieveRequest{} }, ContainerRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerRequest, error) { enableRawRequestResponseForContainer(ctx) if retrieveReq, ok := req.(*schemas.BifrostContainerRetrieveRequest); ok { if retrieveReq.Provider == "" { retrieveReq.Provider = schemas.OpenAI } return &ContainerRequest{ Type: schemas.ContainerRetrieveRequest, RetrieveRequest: retrieveReq, }, nil } return nil, errors.New("invalid container retrieve request type") }, ContainerRetrieveResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerRetrieveResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractContainerIDFromPath(handlerStore), }) } // Delete container endpoint - DELETE /v1/containers/{container_id} for _, path := range []string{ "/v1/containers/{container_id}", "/containers/{container_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "DELETE", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerDeleteRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerDeleteRequest{} }, ContainerRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerRequest, error) { enableRawRequestResponseForContainer(ctx) if deleteReq, ok := req.(*schemas.BifrostContainerDeleteRequest); ok { if deleteReq.Provider == "" { deleteReq.Provider = schemas.OpenAI } return &ContainerRequest{ Type: schemas.ContainerDeleteRequest, DeleteRequest: deleteReq, }, nil } return nil, errors.New("invalid container delete request type") }, ContainerDeleteResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerDeleteResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractContainerIDFromPath(handlerStore), }) } return routes } // extractContainerListQueryParams extracts query parameters for container list requests func extractContainerListQueryParams(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { if listReq, ok := req.(*schemas.BifrostContainerListRequest); ok { // Extract provider from query if provider := string(ctx.QueryArgs().Peek("provider")); provider != "" { listReq.Provider = schemas.ModelProvider(provider) } if listReq.Provider == "" { listReq.Provider = schemas.OpenAI } // Extract limit if limitStr := string(ctx.QueryArgs().Peek("limit")); limitStr != "" { if limit, err := strconv.Atoi(limitStr); err == nil { listReq.Limit = limit } } // Extract after cursor if after := string(ctx.QueryArgs().Peek("after")); after != "" { listReq.After = &after } // Extract order if order := string(ctx.QueryArgs().Peek("order")); order != "" { listReq.Order = &order } } return nil } } // extractContainerIDFromPath extracts container_id from path parameters and provider from query params func extractContainerIDFromPath(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { containerID := ctx.UserValue("container_id") if containerID == nil { return errors.New("container_id is required") } containerIDStr, ok := containerID.(string) if !ok || containerIDStr == "" { return errors.New("container_id must be a non-empty string") } // Extract provider from query provider := schemas.ModelProvider(string(ctx.QueryArgs().Peek("provider"))) if provider == "" { provider = schemas.OpenAI } switch r := req.(type) { case *schemas.BifrostContainerRetrieveRequest: r.ContainerID = containerIDStr r.Provider = provider case *schemas.BifrostContainerDeleteRequest: r.ContainerID = containerIDStr r.Provider = provider } return nil } } // ============================================================================= // CONTAINER FILES API ROUTES // ============================================================================= // CreateOpenAIContainerFileRouteConfigs creates route configurations for OpenAI Container Files API endpoints. func CreateOpenAIContainerFileRouteConfigs(pathPrefix string, handlerStore lib.HandlerStore) []RouteConfig { var routes []RouteConfig // Create container file endpoint - POST /v1/containers/{container_id}/files for _, path := range []string{ "/v1/containers/{container_id}/files", "/containers/{container_id}/files", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "POST", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerFileCreateRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerFileCreateRequest{} }, RequestParser: parseContainerFileCreateMultipartRequest, ContainerFileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerFileRequest, error) { enableRawRequestResponseForContainer(ctx) if createReq, ok := req.(*schemas.BifrostContainerFileCreateRequest); ok { return &ContainerFileRequest{ Type: schemas.ContainerFileCreateRequest, CreateRequest: createReq, }, nil } return nil, errors.New("invalid container file create request type") }, ContainerFileCreateResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerFileCreateResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractContainerFileCreateParams(handlerStore), }) } // List container files endpoint - GET /v1/containers/{container_id}/files for _, path := range []string{ "/v1/containers/{container_id}/files", "/containers/{container_id}/files", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerFileListRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerFileListRequest{} }, ContainerFileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerFileRequest, error) { enableRawRequestResponseForContainer(ctx) if listReq, ok := req.(*schemas.BifrostContainerFileListRequest); ok { return &ContainerFileRequest{ Type: schemas.ContainerFileListRequest, ListRequest: listReq, }, nil } return nil, errors.New("invalid container file list request type") }, ContainerFileListResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerFileListResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractContainerFileListQueryParams(handlerStore), }) } // Retrieve container file endpoint - GET /v1/containers/{container_id}/files/{file_id} for _, path := range []string{ "/v1/containers/{container_id}/files/{file_id}", "/containers/{container_id}/files/{file_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerFileRetrieveRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerFileRetrieveRequest{} }, ContainerFileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerFileRequest, error) { enableRawRequestResponseForContainer(ctx) if retrieveReq, ok := req.(*schemas.BifrostContainerFileRetrieveRequest); ok { return &ContainerFileRequest{ Type: schemas.ContainerFileRetrieveRequest, RetrieveRequest: retrieveReq, }, nil } return nil, errors.New("invalid container file retrieve request type") }, ContainerFileRetrieveResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerFileRetrieveResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractContainerAndFileIDFromPath(handlerStore), }) } // Retrieve container file content endpoint - GET /v1/containers/{container_id}/files/{file_id}/content for _, path := range []string{ "/v1/containers/{container_id}/files/{file_id}/content", "/containers/{container_id}/files/{file_id}/content", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "GET", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerFileContentRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerFileContentRequest{} }, ContainerFileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerFileRequest, error) { enableRawRequestResponseForContainer(ctx) if contentReq, ok := req.(*schemas.BifrostContainerFileContentRequest); ok { return &ContainerFileRequest{ Type: schemas.ContainerFileContentRequest, ContentRequest: contentReq, }, nil } return nil, errors.New("invalid container file content request type") }, ContainerFileContentResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerFileContentResponse) (interface{}, error) { return resp.Content, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractContainerAndFileIDFromPath(handlerStore), }) } // Delete container file endpoint - DELETE /v1/containers/{container_id}/files/{file_id} for _, path := range []string{ "/v1/containers/{container_id}/files/{file_id}", "/containers/{container_id}/files/{file_id}", } { routes = append(routes, RouteConfig{ Type: RouteConfigTypeOpenAI, Path: pathPrefix + path, Method: "DELETE", GetHTTPRequestType: func(ctx *fasthttp.RequestCtx) schemas.RequestType { return schemas.ContainerFileDeleteRequest }, GetRequestTypeInstance: func(ctx context.Context) interface{} { return &schemas.BifrostContainerFileDeleteRequest{} }, ContainerFileRequestConverter: func(ctx *schemas.BifrostContext, req interface{}) (*ContainerFileRequest, error) { enableRawRequestResponseForContainer(ctx) if deleteReq, ok := req.(*schemas.BifrostContainerFileDeleteRequest); ok { return &ContainerFileRequest{ Type: schemas.ContainerFileDeleteRequest, DeleteRequest: deleteReq, }, nil } return nil, errors.New("invalid container file delete request type") }, ContainerFileDeleteResponseConverter: func(ctx *schemas.BifrostContext, resp *schemas.BifrostContainerFileDeleteResponse) (interface{}, error) { return resp, nil }, ErrorConverter: func(ctx *schemas.BifrostContext, err *schemas.BifrostError) interface{} { return err }, PreCallback: extractContainerAndFileIDFromPath(handlerStore), }) } return routes } // extractContainerFileCreateParams extracts container_id from path and provider from query for file create func extractContainerFileCreateParams(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { containerID := ctx.UserValue("container_id") if containerID == nil { return errors.New("container_id is required") } containerIDStr, ok := containerID.(string) if !ok || containerIDStr == "" { return errors.New("container_id must be a non-empty string") } provider := schemas.ModelProvider(string(ctx.QueryArgs().Peek("provider"))) if provider == "" { provider = schemas.OpenAI } if createReq, ok := req.(*schemas.BifrostContainerFileCreateRequest); ok { createReq.ContainerID = containerIDStr if createReq.Provider == "" { createReq.Provider = provider } } return nil } } // extractContainerFileListQueryParams extracts query parameters for container file list requests func extractContainerFileListQueryParams(_ lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { containerID := ctx.UserValue("container_id") if containerID == nil { return errors.New("container_id is required") } containerIDStr, ok := containerID.(string) if !ok || containerIDStr == "" { return errors.New("container_id must be a non-empty string") } if listReq, ok := req.(*schemas.BifrostContainerFileListRequest); ok { listReq.ContainerID = containerIDStr // Extract provider from query if provider := string(ctx.QueryArgs().Peek("provider")); provider != "" { listReq.Provider = schemas.ModelProvider(provider) } if listReq.Provider == "" { listReq.Provider = schemas.OpenAI } // Extract limit if limitStr := string(ctx.QueryArgs().Peek("limit")); limitStr != "" { if limit, err := strconv.Atoi(limitStr); err == nil { listReq.Limit = limit } } // Extract after cursor if after := string(ctx.QueryArgs().Peek("after")); after != "" { listReq.After = &after } // Extract order if order := string(ctx.QueryArgs().Peek("order")); order != "" { listReq.Order = &order } } return nil } } // extractContainerAndFileIDFromPath extracts container_id and file_id from path parameters and provider from query params func extractContainerAndFileIDFromPath(handlerStore lib.HandlerStore) PreRequestCallback { return func(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext, req interface{}) error { containerID := ctx.UserValue("container_id") if containerID == nil { return errors.New("container_id is required") } containerIDStr, ok := containerID.(string) if !ok || containerIDStr == "" { return errors.New("container_id must be a non-empty string") } fileID := ctx.UserValue("file_id") if fileID == nil { return errors.New("file_id is required") } fileIDStr, ok := fileID.(string) if !ok || fileIDStr == "" { return errors.New("file_id must be a non-empty string") } // Extract provider from query provider := schemas.ModelProvider(string(ctx.QueryArgs().Peek("provider"))) if provider == "" { provider = schemas.OpenAI } switch r := req.(type) { case *schemas.BifrostContainerFileRetrieveRequest: r.ContainerID = containerIDStr r.FileID = fileIDStr r.Provider = provider case *schemas.BifrostContainerFileContentRequest: r.ContainerID = containerIDStr r.FileID = fileIDStr r.Provider = provider case *schemas.BifrostContainerFileDeleteRequest: r.ContainerID = containerIDStr r.FileID = fileIDStr r.Provider = provider } return nil } } // OpenAIWSResponsesPaths returns WebSocket GET paths for the Responses API. // Mirrors the HTTP POST paths from CreateOpenAIRouteConfigs for /v1/responses and /responses. // No /deployments/ paths — model is specified in event body, not URL. func OpenAIWSResponsesPaths(pathPrefix string) []string { basePaths := []string{ "/v1/responses", "/responses", "/openai/responses", } paths := make([]string, 0, len(basePaths)) for _, p := range basePaths { paths = append(paths, pathPrefix+p) } return paths } // OpenAIRealtimePaths returns WebSocket GET paths for the Realtime API. // Azure GA uses /openai/v1/realtime?model=..., preview uses /openai/realtime?deployment=... // No /deployments/ paths — model is always in query params. func OpenAIRealtimePaths(pathPrefix string) []string { basePaths := []string{ "/v1/realtime", "/realtime", "/openai/realtime", } paths := make([]string, 0, len(basePaths)) for _, p := range basePaths { paths = append(paths, pathPrefix+p) } return paths } // OpenAIRealtimeWebRTCCallsPaths returns HTTP POST paths for the GA /realtime/calls // WebRTC SDP exchange endpoint (multipart sdp + session format). func OpenAIRealtimeWebRTCCallsPaths(pathPrefix string) []string { basePaths := []string{ "/v1/realtime/calls", "/realtime/calls", "/openai/realtime/calls", } paths := make([]string, 0, len(basePaths)) for _, p := range basePaths { paths = append(paths, pathPrefix+p) } return paths } // OpenAIRealtimeClientSecretPaths returns HTTP POST paths for OpenAI-compatible // realtime client secret creation aliases. func OpenAIRealtimeClientSecretPaths(pathPrefix string) []string { basePaths := []string{ "/v1/realtime/client_secrets", "/v1/realtime/sessions", } paths := make([]string, 0, len(basePaths)) for _, p := range basePaths { paths = append(paths, pathPrefix+p) } return paths } // NewOpenAIRouter creates a new OpenAIRouter with the given bifrost client. func NewOpenAIRouter(client *bifrost.Bifrost, handlerStore lib.HandlerStore, logger schemas.Logger) *OpenAIRouter { routes := CreateOpenAIRouteConfigs("/openai", handlerStore) routes = append(routes, CreateOpenAIListModelsRouteConfigs("/openai", handlerStore)...) routes = append(routes, CreateOpenAIBatchRouteConfigs("/openai", handlerStore)...) routes = append(routes, CreateOpenAIFileRouteConfigs("/openai", handlerStore)...) routes = append(routes, CreateOpenAIContainerRouteConfigs("/openai", handlerStore)...) routes = append(routes, CreateOpenAIContainerFileRouteConfigs("/openai", handlerStore)...) return &OpenAIRouter{ GenericRouter: NewGenericRouter(client, handlerStore, routes, nil, logger), } } // parseTranscriptionMultipartRequest is a RequestParser that handles multipart/form-data for transcription requests func parseTranscriptionMultipartRequest(ctx *fasthttp.RequestCtx, req interface{}) error { transcriptionReq, ok := req.(*openai.OpenAITranscriptionRequest) if !ok { return errors.New("invalid request type for transcription") } // Parse multipart form form, err := ctx.MultipartForm() if err != nil { return err } // Extract model (required) modelValues := form.Value["model"] if len(modelValues) == 0 || modelValues[0] == "" { return errors.New("model field is required") } transcriptionReq.Model = modelValues[0] // Extract file (required) fileHeaders := form.File["file"] if len(fileHeaders) == 0 { return errors.New("file field is required") } fileHeader := fileHeaders[0] file, err := fileHeader.Open() if err != nil { return err } defer file.Close() // Read file data fileData, err := io.ReadAll(file) if err != nil { return err } transcriptionReq.File = fileData // Extract optional parameters if languageValues := form.Value["language"]; len(languageValues) > 0 && languageValues[0] != "" { language := languageValues[0] transcriptionReq.TranscriptionParameters.Language = &language } if promptValues := form.Value["prompt"]; len(promptValues) > 0 && promptValues[0] != "" { prompt := promptValues[0] transcriptionReq.TranscriptionParameters.Prompt = &prompt } if responseFormatValues := form.Value["response_format"]; len(responseFormatValues) > 0 && responseFormatValues[0] != "" { responseFormat := responseFormatValues[0] transcriptionReq.TranscriptionParameters.ResponseFormat = &responseFormat } if streamValues := form.Value["stream"]; len(streamValues) > 0 && streamValues[0] != "" { stream, err := strconv.ParseBool(streamValues[0]) if err != nil { return errors.New("invalid stream value") } transcriptionReq.Stream = &stream } return nil } // parseOpenAIImageEditMultipartRequest is a RequestParser that handles multipart/form-data for image edit requests func parseOpenAIImageEditMultipartRequest(ctx *fasthttp.RequestCtx, req interface{}) error { imageEditReq, ok := req.(*openai.OpenAIImageEditRequest) if !ok { return errors.New("invalid request type for image edit") } // Parse multipart form form, err := ctx.MultipartForm() if err != nil { return err } // Extract model (required) modelValues := form.Value["model"] if len(modelValues) == 0 || modelValues[0] == "" { return errors.New("model field is required") } imageEditReq.Model = modelValues[0] // Extract prompt (required) promptValues := form.Value["prompt"] if len(promptValues) == 0 || promptValues[0] == "" { return errors.New("prompt field is required") } prompt := promptValues[0] // Extract images (required) - handle both "image[]" and "image" var imageFiles []*multipart.FileHeader if imageFilesArray := form.File["image[]"]; len(imageFilesArray) > 0 { imageFiles = imageFilesArray } else if imageFilesSingle := form.File["image"]; len(imageFilesSingle) > 0 { imageFiles = imageFilesSingle } if len(imageFiles) == 0 { return errors.New("at least one image is required") } // Read all image files images := make([]schemas.ImageInput, 0, len(imageFiles)) for _, fileHeader := range imageFiles { file, err := fileHeader.Open() if err != nil { return err } defer file.Close() // Read file data fileData, err := io.ReadAll(file) if err != nil { return err } images = append(images, schemas.ImageInput{ Image: fileData, }) } // Create image edit input imageEditReq.Input = &schemas.ImageEditInput{ Images: images, Prompt: prompt, } // Extract optional parameters if nValues := form.Value["n"]; len(nValues) > 0 && nValues[0] != "" { n, err := strconv.Atoi(nValues[0]) if err != nil { return errors.New("invalid n value") } imageEditReq.N = &n } if sizeValues := form.Value["size"]; len(sizeValues) > 0 && sizeValues[0] != "" { size := sizeValues[0] imageEditReq.Size = &size } if qualityValues := form.Value["quality"]; len(qualityValues) > 0 && qualityValues[0] != "" { quality := qualityValues[0] imageEditReq.Quality = &quality } if responseFormatValues := form.Value["response_format"]; len(responseFormatValues) > 0 && responseFormatValues[0] != "" { responseFormat := responseFormatValues[0] imageEditReq.ResponseFormat = &responseFormat } if backgroundValues := form.Value["background"]; len(backgroundValues) > 0 && backgroundValues[0] != "" { background := backgroundValues[0] imageEditReq.Background = &background } if inputFidelityValues := form.Value["input_fidelity"]; len(inputFidelityValues) > 0 && inputFidelityValues[0] != "" { inputFidelity := inputFidelityValues[0] imageEditReq.InputFidelity = &inputFidelity } if partialImagesValues := form.Value["partial_images"]; len(partialImagesValues) > 0 && partialImagesValues[0] != "" { partialImages, err := strconv.Atoi(partialImagesValues[0]) if err != nil { return errors.New("invalid partial_images value") } imageEditReq.PartialImages = &partialImages } if outputFormatValues := form.Value["output_format"]; len(outputFormatValues) > 0 && outputFormatValues[0] != "" { outputFormat := outputFormatValues[0] imageEditReq.OutputFormat = &outputFormat } if numInferenceStepsValues := form.Value["num_inference_steps"]; len(numInferenceStepsValues) > 0 && numInferenceStepsValues[0] != "" { numInferenceSteps, err := strconv.Atoi(numInferenceStepsValues[0]) if err != nil { return errors.New("invalid num_inference_steps value") } imageEditReq.NumInferenceSteps = &numInferenceSteps } if seedValues := form.Value["seed"]; len(seedValues) > 0 && seedValues[0] != "" { seed, err := strconv.Atoi(seedValues[0]) if err != nil { return errors.New("invalid seed value") } imageEditReq.Seed = &seed } if outputCompressionValues := form.Value["output_compression"]; len(outputCompressionValues) > 0 && outputCompressionValues[0] != "" { outputCompression, err := strconv.Atoi(outputCompressionValues[0]) if err != nil { return errors.New("invalid output_compression value") } imageEditReq.OutputCompression = &outputCompression } if negativePromptValues := form.Value["negative_prompt"]; len(negativePromptValues) > 0 && negativePromptValues[0] != "" { negativePrompt := negativePromptValues[0] imageEditReq.NegativePrompt = &negativePrompt } if userValues := form.Value["user"]; len(userValues) > 0 && userValues[0] != "" { user := userValues[0] imageEditReq.User = &user } // Extract type (required for Bedrock, optional for others) if typeValues := form.Value["type"]; len(typeValues) > 0 && typeValues[0] != "" { editType := typeValues[0] imageEditReq.Type = &editType } // Extract mask if present if maskFiles := form.File["mask"]; len(maskFiles) > 0 { maskFile := maskFiles[0] file, err := maskFile.Open() if err != nil { return err } defer file.Close() maskData, err := io.ReadAll(file) if err != nil { return err } imageEditReq.Mask = maskData } // Extract stream parameter if streamValues := form.Value["stream"]; len(streamValues) > 0 && streamValues[0] != "" { stream, err := strconv.ParseBool(streamValues[0]) if err != nil { return errors.New("invalid stream value") } imageEditReq.Stream = &stream } // Extract fallbacks if fallbackValues := form.Value["fallbacks"]; len(fallbackValues) > 0 { imageEditReq.Fallbacks = fallbackValues } return nil } // parseOpenAIImageVariationMultipartRequest parses multipart/form-data for image variation requests func parseOpenAIImageVariationMultipartRequest(ctx *fasthttp.RequestCtx, req interface{}) error { imageVariationReq, ok := req.(*openai.OpenAIImageVariationRequest) if !ok { return errors.New("invalid request type for image variation") } // Parse multipart form form, err := ctx.MultipartForm() if err != nil { return err } // Extract model (required) modelValues := form.Value["model"] if len(modelValues) == 0 || modelValues[0] == "" { return errors.New("model field is required") } imageVariationReq.Model = modelValues[0] // Extract image (required) - handle both "image[]" and "image" var imageFiles []*multipart.FileHeader if imageFilesArray := form.File["image[]"]; len(imageFilesArray) > 0 { imageFiles = imageFilesArray } else if imageFilesSingle := form.File["image"]; len(imageFilesSingle) > 0 { imageFiles = imageFilesSingle } if len(imageFiles) == 0 { return errors.New("at least one image is required") } // Read first image file (image variation only uses the first image) fileHeader := imageFiles[0] file, err := fileHeader.Open() if err != nil { return err } defer file.Close() // Read file data fileData, err := io.ReadAll(file) if err != nil { return err } // Create image variation input imageVariationReq.Input = &schemas.ImageVariationInput{ Image: schemas.ImageInput{ Image: fileData, }, } // Extract optional parameters if nValues := form.Value["n"]; len(nValues) > 0 && nValues[0] != "" { n, err := strconv.Atoi(nValues[0]) if err != nil { return errors.New("invalid n value") } imageVariationReq.N = &n } if sizeValues := form.Value["size"]; len(sizeValues) > 0 && sizeValues[0] != "" { size := sizeValues[0] imageVariationReq.Size = &size } if responseFormatValues := form.Value["response_format"]; len(responseFormatValues) > 0 && responseFormatValues[0] != "" { responseFormat := responseFormatValues[0] imageVariationReq.ResponseFormat = &responseFormat } if userValues := form.Value["user"]; len(userValues) > 0 && userValues[0] != "" { user := userValues[0] imageVariationReq.User = &user } // Extract fallbacks if fallbackValues := form.Value["fallbacks"]; len(fallbackValues) > 0 { imageVariationReq.Fallbacks = fallbackValues } return nil } func parseOpenAIVideoGenerationMultipartRequest(ctx *fasthttp.RequestCtx, req interface{}) error { videoGenerationReq, ok := req.(*openai.OpenAIVideoGenerationRequest) if !ok { return errors.New("invalid request type for video generation") } contentType := string(ctx.Request.Header.ContentType()) if !strings.HasPrefix(contentType, "multipart/form-data") { // For JSON requests (no input_reference file), parse request body directly. rawBody := ctx.Request.Body() if len(rawBody) == 0 { return errors.New("request body is required for video generation") } if err := json.Unmarshal(rawBody, videoGenerationReq); err != nil { return err } if videoGenerationReq.Model == "" { return errors.New("model field is required") } if videoGenerationReq.Prompt == "" { return errors.New("prompt field is required") } return nil } // Parse multipart form form, err := ctx.MultipartForm() if err != nil { return err } // Extract model (required) modelValues := form.Value["model"] if len(modelValues) == 0 || modelValues[0] == "" { return errors.New("model field is required") } videoGenerationReq.Model = modelValues[0] // Extract prompt (required) promptValues := form.Value["prompt"] if len(promptValues) == 0 || promptValues[0] == "" { return errors.New("prompt field is required") } videoGenerationReq.Prompt = promptValues[0] // Extract optional input_reference file (image that guides generation) if inputRefFiles := form.File["input_reference"]; len(inputRefFiles) > 0 { fileHeader := inputRefFiles[0] file, err := fileHeader.Open() if err != nil { return err } defer file.Close() // Read file data fileData, err := io.ReadAll(file) if err != nil { return err } videoGenerationReq.InputReference = fileData } // Extract optional parameters if secondsValues := form.Value["seconds"]; len(secondsValues) > 0 && secondsValues[0] != "" { seconds := secondsValues[0] videoGenerationReq.Seconds = &seconds } if sizeValues := form.Value["size"]; len(sizeValues) > 0 && sizeValues[0] != "" { size := sizeValues[0] videoGenerationReq.Size = size } if negativePromptValues := form.Value["negative_prompt"]; len(negativePromptValues) > 0 && negativePromptValues[0] != "" { negativePrompt := negativePromptValues[0] videoGenerationReq.NegativePrompt = &negativePrompt } if seedValues := form.Value["seed"]; len(seedValues) > 0 && seedValues[0] != "" { seed, err := strconv.Atoi(seedValues[0]) if err != nil { return errors.New("invalid seed value") } videoGenerationReq.Seed = &seed } if videoURIValues := form.Value["video_uri"]; len(videoURIValues) > 0 && videoURIValues[0] != "" { videoURI := videoURIValues[0] videoGenerationReq.VideoURI = &videoURI } // Extract fallbacks if fallbackValues := form.Value["fallbacks"]; len(fallbackValues) > 0 { videoGenerationReq.Fallbacks = fallbackValues } return nil } // enableRawRequestResponseForContainer sets per-request overrides to always capture and // send back raw request/response for container operations. Container operations don't have // model-specific content, so raw data is useful for debugging and should be enabled by default. func enableRawRequestResponseForContainer(bifrostCtx *schemas.BifrostContext) { bifrostCtx.SetValue(schemas.BifrostContextKeySendBackRawRequest, true) bifrostCtx.SetValue(schemas.BifrostContextKeySendBackRawResponse, true) bifrostCtx.SetValue(schemas.BifrostContextKeyStoreRawRequestResponse, true) } // parseContainerFileCreateMultipartRequest is a RequestParser that handles multipart/form-data for container file create requests func parseContainerFileCreateMultipartRequest(ctx *fasthttp.RequestCtx, req interface{}) error { createReq, ok := req.(*schemas.BifrostContainerFileCreateRequest) if !ok { return errors.New("invalid request type for container file create") } contentType := string(ctx.Request.Header.ContentType()) if !strings.HasPrefix(contentType, "multipart/form-data") { return nil // Let JSON parsing handle it } // Parse multipart form form, err := ctx.MultipartForm() if err != nil { return err } // Extract file (optional for multipart - could be file_id instead) if fileHeaders := form.File["file"]; len(fileHeaders) > 0 { fileHeader := fileHeaders[0] file, err := fileHeader.Open() if err != nil { return err } defer file.Close() fileData, err := io.ReadAll(file) if err != nil { return err } createReq.File = fileData } // Extract optional file_id if fileIDValues := form.Value["file_id"]; len(fileIDValues) > 0 && fileIDValues[0] != "" { fileID := fileIDValues[0] createReq.FileID = &fileID } // Extract optional file_path if filePathValues := form.Value["file_path"]; len(filePathValues) > 0 && filePathValues[0] != "" { filePath := filePathValues[0] createReq.Path = &filePath } // Extract optional provider if providerValues := form.Value["provider"]; len(providerValues) > 0 { if providerValue := strings.TrimSpace(providerValues[0]); providerValue != "" { createReq.Provider = schemas.ModelProvider(providerValue) } } return nil }