Files
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

3350 lines
112 KiB
Go

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
}