Files
bifrost/core/providers/openai/images.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

362 lines
12 KiB
Go

package openai
import (
"fmt"
"mime/multipart"
"net/http"
"strconv"
providerUtils "github.com/maximhq/bifrost/core/providers/utils"
"github.com/maximhq/bifrost/core/schemas"
)
// ToOpenAIImageGenerationRequest converts a Bifrost Image Request to OpenAI format
func ToOpenAIImageGenerationRequest(bifrostReq *schemas.BifrostImageGenerationRequest) *OpenAIImageGenerationRequest {
if bifrostReq == nil || bifrostReq.Input == nil || bifrostReq.Input.Prompt == "" {
return nil
}
req := &OpenAIImageGenerationRequest{
Model: bifrostReq.Model,
Prompt: bifrostReq.Input.Prompt,
}
if bifrostReq.Params != nil {
req.ImageGenerationParameters = *bifrostReq.Params
}
switch bifrostReq.Provider {
case schemas.XAI:
filterXAISpecificParameters(req)
case schemas.OpenAI, schemas.Azure:
filterOpenAISpecificParameters(req)
}
if bifrostReq.Params != nil {
req.ExtraParams = bifrostReq.Params.ExtraParams
}
return req
}
func filterXAISpecificParameters(req *OpenAIImageGenerationRequest) {
req.ImageGenerationParameters.Quality = nil
req.ImageGenerationParameters.Style = nil
req.ImageGenerationParameters.Size = nil
req.ImageGenerationParameters.OutputCompression = nil
}
func filterOpenAISpecificParameters(req *OpenAIImageGenerationRequest) {
req.ImageGenerationParameters.Seed = nil
req.NumInferenceSteps = nil
req.NegativePrompt = nil
}
// ToBifrostImageGenerationRequest converts an OpenAI image generation request to Bifrost format
func (request *OpenAIImageGenerationRequest) ToBifrostImageGenerationRequest(ctx *schemas.BifrostContext) *schemas.BifrostImageGenerationRequest {
if request == nil {
return nil
}
provider, model := schemas.ParseModelString(request.Model, providerUtils.CheckAndSetDefaultProvider(ctx, schemas.OpenAI))
return &schemas.BifrostImageGenerationRequest{
Provider: provider,
Model: model,
Input: &schemas.ImageGenerationInput{
Prompt: request.Prompt,
},
Params: &request.ImageGenerationParameters,
Fallbacks: schemas.ParseFallbacks(request.Fallbacks),
}
}
func (request *OpenAIImageEditRequest) ToBifrostImageEditRequest(ctx *schemas.BifrostContext) *schemas.BifrostImageEditRequest {
if request == nil {
return nil
}
provider, model := schemas.ParseModelString(request.Model, providerUtils.CheckAndSetDefaultProvider(ctx, schemas.OpenAI))
return &schemas.BifrostImageEditRequest{
Provider: provider,
Model: model,
Input: request.Input,
Params: &request.ImageEditParameters,
Fallbacks: schemas.ParseFallbacks(request.Fallbacks),
}
}
func (request *OpenAIImageVariationRequest) ToBifrostImageVariationRequest(ctx *schemas.BifrostContext) *schemas.BifrostImageVariationRequest {
if request == nil {
return nil
}
provider, model := schemas.ParseModelString(request.Model, providerUtils.CheckAndSetDefaultProvider(ctx, schemas.OpenAI))
return &schemas.BifrostImageVariationRequest{
Provider: provider,
Model: model,
Input: request.Input,
Params: &request.ImageVariationParameters,
Fallbacks: schemas.ParseFallbacks(request.Fallbacks),
}
}
func ToOpenAIImageEditRequest(bifrostReq *schemas.BifrostImageEditRequest) *OpenAIImageEditRequest {
if bifrostReq == nil || bifrostReq.Input == nil || bifrostReq.Input.Images == nil || bifrostReq.Input.Prompt == "" {
return nil
}
req := &OpenAIImageEditRequest{
Model: bifrostReq.Model,
Input: bifrostReq.Input,
}
if bifrostReq.Params != nil {
req.ImageEditParameters = *bifrostReq.Params
}
if bifrostReq.Params != nil {
req.ExtraParams = bifrostReq.Params.ExtraParams
}
return req
}
func parseImageEditFormDataBodyFromRequest(writer *multipart.Writer, openaiReq *OpenAIImageEditRequest, providerName schemas.ModelProvider) *schemas.BifrostError {
// Add model field (required)
if err := writer.WriteField("model", openaiReq.Model); err != nil {
return providerUtils.NewBifrostOperationError("failed to write model field", err)
}
// Add prompt field (required)
if err := writer.WriteField("prompt", openaiReq.Input.Prompt); err != nil {
return providerUtils.NewBifrostOperationError("failed to write prompt field", err)
}
// Add stream field when requesting streaming
if openaiReq.Stream != nil && *openaiReq.Stream {
if err := writer.WriteField("stream", "true"); err != nil {
return providerUtils.NewBifrostOperationError("failed to write stream field", err)
}
}
// Add optional parameters before file parts so routing metadata arrives first upstream.
if openaiReq.N != nil {
if err := writer.WriteField("n", strconv.Itoa(*openaiReq.N)); err != nil {
return providerUtils.NewBifrostOperationError("failed to write n field", err)
}
}
if openaiReq.Size != nil {
if err := writer.WriteField("size", *openaiReq.Size); err != nil {
return providerUtils.NewBifrostOperationError("failed to write size field", err)
}
}
if openaiReq.ResponseFormat != nil {
if err := writer.WriteField("response_format", *openaiReq.ResponseFormat); err != nil {
return providerUtils.NewBifrostOperationError("failed to write response_format field", err)
}
}
if openaiReq.Quality != nil {
if err := writer.WriteField("quality", *openaiReq.Quality); err != nil {
return providerUtils.NewBifrostOperationError("failed to write quality field", err)
}
}
if openaiReq.Background != nil {
if err := writer.WriteField("background", *openaiReq.Background); err != nil {
return providerUtils.NewBifrostOperationError("failed to write background field", err)
}
}
if openaiReq.InputFidelity != nil {
if err := writer.WriteField("input_fidelity", *openaiReq.InputFidelity); err != nil {
return providerUtils.NewBifrostOperationError("failed to write input_fidelity field", err)
}
}
if openaiReq.PartialImages != nil {
if err := writer.WriteField("partial_images", strconv.Itoa(*openaiReq.PartialImages)); err != nil {
return providerUtils.NewBifrostOperationError("failed to write partial_images field", err)
}
}
if openaiReq.OutputFormat != nil {
if err := writer.WriteField("output_format", *openaiReq.OutputFormat); err != nil {
return providerUtils.NewBifrostOperationError("failed to write output_format field", err)
}
}
if openaiReq.OutputCompression != nil {
if err := writer.WriteField("output_compression", strconv.Itoa(*openaiReq.OutputCompression)); err != nil {
return providerUtils.NewBifrostOperationError("failed to write output_compression field", err)
}
}
if openaiReq.User != nil {
if err := writer.WriteField("user", *openaiReq.User); err != nil {
return providerUtils.NewBifrostOperationError("failed to write user field", err)
}
}
// Add image[] fields (one for each image)
for i, imageInput := range openaiReq.Input.Images {
fieldName := "image[]"
// Detect and validate MIME type
mimeType := http.DetectContentType(imageInput.Image)
// Fallback to PNG if content type is undetectable or generic
if mimeType == "" || mimeType == "application/octet-stream" {
mimeType = "image/png"
}
// Determine filename based on MIME type
var filename string
switch mimeType {
case "image/jpeg":
filename = fmt.Sprintf("image%d.jpg", i)
case "image/webp":
filename = fmt.Sprintf("image%d.webp", i)
default:
filename = fmt.Sprintf("image%d.png", i)
}
// Create form part with proper Content-Type header (not CreateFormFile which defaults to application/octet-stream)
part, err := writer.CreatePart(map[string][]string{
"Content-Disposition": {fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, filename)},
"Content-Type": {mimeType},
})
if err != nil {
return providerUtils.NewBifrostOperationError(fmt.Sprintf("failed to create form part for image %d", i), err)
}
if _, err := part.Write(imageInput.Image); err != nil {
return providerUtils.NewBifrostOperationError(fmt.Sprintf("failed to write image %d data", i), err)
}
}
// Add mask if present
if len(openaiReq.Mask) > 0 {
// Detect MIME type for mask
maskMimeType := http.DetectContentType(openaiReq.Mask)
if maskMimeType != "image/png" && maskMimeType != "image/jpeg" && maskMimeType != "image/webp" {
maskMimeType = "image/png"
}
var maskFilename string
switch maskMimeType {
case "image/jpeg":
maskFilename = "mask.jpg"
case "image/webp":
maskFilename = "mask.webp"
default:
maskFilename = "mask.png"
}
// Create form part with proper Content-Type header
maskPart, err := writer.CreatePart(map[string][]string{
"Content-Disposition": {`form-data; name="mask"; filename="` + maskFilename + `"`},
"Content-Type": {maskMimeType},
})
if err != nil {
return providerUtils.NewBifrostOperationError("failed to create mask form part", err)
}
if _, err := maskPart.Write(openaiReq.Mask); err != nil {
return providerUtils.NewBifrostOperationError("failed to write mask data", err)
}
}
// Close the multipart writer
if err := writer.Close(); err != nil {
return providerUtils.NewBifrostOperationError("failed to close multipart writer", err)
}
return nil
}
func ToOpenAIImageVariationRequest(bifrostReq *schemas.BifrostImageVariationRequest) *OpenAIImageVariationRequest {
if bifrostReq == nil || bifrostReq.Input == nil || bifrostReq.Input.Image.Image == nil || len(bifrostReq.Input.Image.Image) == 0 {
return nil
}
req := &OpenAIImageVariationRequest{
Model: bifrostReq.Model,
Input: bifrostReq.Input,
}
if bifrostReq.Params != nil {
req.ImageVariationParameters = *bifrostReq.Params
}
if bifrostReq.Params != nil {
req.ExtraParams = bifrostReq.Params.ExtraParams
}
return req
}
func parseImageVariationFormDataBodyFromRequest(writer *multipart.Writer, openaiReq *OpenAIImageVariationRequest, providerName schemas.ModelProvider) *schemas.BifrostError {
// Add model field (required)
if err := writer.WriteField("model", openaiReq.Model); err != nil {
return providerUtils.NewBifrostOperationError("failed to write model field", err)
}
// Add image file (required)
if openaiReq.Input == nil || openaiReq.Input.Image.Image == nil || len(openaiReq.Input.Image.Image) == 0 {
return providerUtils.NewBifrostOperationError("image is required", nil)
}
// Add optional parameters before the image part so metadata arrives first upstream.
if openaiReq.N != nil {
if err := writer.WriteField("n", strconv.Itoa(*openaiReq.N)); err != nil {
return providerUtils.NewBifrostOperationError("failed to write n field", err)
}
}
if openaiReq.ResponseFormat != nil {
if err := writer.WriteField("response_format", *openaiReq.ResponseFormat); err != nil {
return providerUtils.NewBifrostOperationError("failed to write response_format field", err)
}
}
if openaiReq.Size != nil {
if err := writer.WriteField("size", *openaiReq.Size); err != nil {
return providerUtils.NewBifrostOperationError("failed to write size field", err)
}
}
if openaiReq.User != nil {
if err := writer.WriteField("user", *openaiReq.User); err != nil {
return providerUtils.NewBifrostOperationError("failed to write user field", err)
}
}
// Detect MIME type
mimeType := http.DetectContentType(openaiReq.Input.Image.Image)
// If still not detected, default to PNG
if mimeType == "application/octet-stream" || mimeType == "" {
mimeType = "image/png"
}
filename := "image"
part, err := writer.CreatePart(map[string][]string{
"Content-Disposition": {fmt.Sprintf(`form-data; name="image"; filename="%s"`, filename)},
"Content-Type": {mimeType},
})
if err != nil {
return providerUtils.NewBifrostOperationError("failed to create image part", err)
}
if _, err := part.Write(openaiReq.Input.Image.Image); err != nil {
return providerUtils.NewBifrostOperationError("failed to write image data", err)
}
// Close the multipart writer
if err := writer.Close(); err != nil {
return providerUtils.NewBifrostOperationError("failed to close multipart writer", err)
}
return nil
}