package openai import ( "strings" "github.com/maximhq/bifrost/core/providers/utils" "github.com/maximhq/bifrost/core/schemas" ) // ToBifrostChatRequest converts an OpenAI chat request to Bifrost format func (req *OpenAIChatRequest) ToBifrostChatRequest(ctx *schemas.BifrostContext) *schemas.BifrostChatRequest { provider, model := schemas.ParseModelString(req.Model, utils.CheckAndSetDefaultProvider(ctx, schemas.OpenAI)) return &schemas.BifrostChatRequest{ Provider: provider, Model: model, Input: ConvertOpenAIMessagesToBifrostMessages(req.Messages), Params: &req.ChatParameters, Fallbacks: schemas.ParseFallbacks(req.Fallbacks), } } // ToOpenAIChatRequest converts a Bifrost chat completion request to OpenAI format func ToOpenAIChatRequest(ctx *schemas.BifrostContext, bifrostReq *schemas.BifrostChatRequest) *OpenAIChatRequest { if bifrostReq == nil || bifrostReq.Input == nil { return nil } openaiReq := &OpenAIChatRequest{ Model: bifrostReq.Model, Messages: ConvertBifrostMessagesToOpenAIMessages(bifrostReq.Input), } if bifrostReq.Params != nil { openaiReq.ChatParameters = *bifrostReq.Params if openaiReq.ChatParameters.MaxCompletionTokens != nil && *openaiReq.ChatParameters.MaxCompletionTokens < MinMaxCompletionTokens { openaiReq.ChatParameters.MaxCompletionTokens = schemas.Ptr(MinMaxCompletionTokens) } // Drop user field if it exceeds OpenAI's 64 character limit openaiReq.ChatParameters.User = SanitizeUserField(openaiReq.ChatParameters.User) openaiReq.ExtraParams = bifrostReq.Params.ExtraParams // Normalize tool parameters for deterministic JSON serialization (improves prompt caching) if len(openaiReq.ChatParameters.Tools) > 0 { normalizedTools := make([]schemas.ChatTool, len(openaiReq.ChatParameters.Tools)) for i, tool := range openaiReq.ChatParameters.Tools { normalizedTools[i] = tool if tool.Function != nil && tool.Function.Parameters != nil { funcCopy := *tool.Function funcCopy.Parameters = tool.Function.Parameters.Normalized() normalizedTools[i].Function = &funcCopy } } openaiReq.ChatParameters.Tools = normalizedTools } } switch bifrostReq.Provider { case schemas.OpenAI, schemas.Azure: return openaiReq case schemas.XAI: openaiReq.filterOpenAISpecificParameters() openaiReq.applyXAICompatibility(bifrostReq.Model) return openaiReq case schemas.Gemini: openaiReq.filterOpenAISpecificParameters() // Removing extra parameters that are not supported by Gemini openaiReq.ServiceTier = nil return openaiReq case schemas.Mistral: openaiReq.filterOpenAISpecificParameters() openaiReq.applyMistralCompatibility() return openaiReq case schemas.Vertex: openaiReq.filterOpenAISpecificParameters() // Apply Mistral-specific transformations for Vertex Mistral models if schemas.IsMistralModel(bifrostReq.Model) { openaiReq.applyMistralCompatibility() } return openaiReq case schemas.Fireworks: // Fireworks uses prompt_cache_isolation_key for cache isolation on chat/completions. // Preserve it before the generic filter strips prompt_cache_key. if openaiReq.ChatParameters.PromptCacheKey != nil && openaiReq.PromptCacheIsolationKey == nil { openaiReq.PromptCacheIsolationKey = openaiReq.ChatParameters.PromptCacheKey } // Fireworks supports predicted outputs; save before the filter strips them. prediction := openaiReq.ChatParameters.Prediction openaiReq.filterOpenAISpecificParameters() openaiReq.ChatParameters.Prediction = prediction return openaiReq default: // Check if provider is a custom provider if isCustomProvider, ok := ctx.Value(schemas.BifrostContextKeyIsCustomProvider).(bool); ok && isCustomProvider { return openaiReq } openaiReq.filterOpenAISpecificParameters() return openaiReq } } // Filter OpenAI Specific Parameters func (req *OpenAIChatRequest) filterOpenAISpecificParameters() { // Handle reasoning parameter: OpenAI uses effort-based reasoning // Priority: effort (native) > max_tokens (estimated) if req.ChatParameters.Reasoning != nil { reasoningCopy := *req.ChatParameters.Reasoning req.ChatParameters.Reasoning = &reasoningCopy if req.ChatParameters.Reasoning.Effort != nil { // Native field is provided, use it (and clear max_tokens) effort := *req.ChatParameters.Reasoning.Effort // Convert "minimal" to "low"; cap "xhigh"/"max" to "high" — OpenAI tops out at high. switch effort { case "minimal": req.ChatParameters.Reasoning.Effort = schemas.Ptr("low") case "xhigh", "max": req.ChatParameters.Reasoning.Effort = schemas.Ptr("high") } // Clear max_tokens since OpenAI doesn't use it req.ChatParameters.Reasoning.MaxTokens = nil } else if req.ChatParameters.Reasoning.MaxTokens != nil { // Estimate effort from max_tokens maxTokens := *req.ChatParameters.Reasoning.MaxTokens maxCompletionTokens := utils.GetMaxOutputTokensOrDefault(req.Model, DefaultCompletionMaxTokens) if req.ChatParameters.MaxCompletionTokens != nil { maxCompletionTokens = *req.ChatParameters.MaxCompletionTokens } effort := utils.GetReasoningEffortFromBudgetTokens(maxTokens, MinReasoningMaxTokens, maxCompletionTokens) req.ChatParameters.Reasoning.Effort = schemas.Ptr(effort) // Clear max_tokens since OpenAI doesn't use it req.ChatParameters.Reasoning.MaxTokens = nil } } if req.ChatParameters.Prediction != nil { req.ChatParameters.Prediction = nil } if req.ChatParameters.PromptCacheKey != nil { req.ChatParameters.PromptCacheKey = nil } if req.ChatParameters.PromptCacheRetention != nil { req.ChatParameters.PromptCacheRetention = nil } if req.ChatParameters.Verbosity != nil { req.ChatParameters.Verbosity = nil } if req.ChatParameters.Store != nil { req.ChatParameters.Store = nil } if req.ChatParameters.WebSearchOptions != nil { req.ChatParameters.WebSearchOptions = nil } } // applyMistralCompatibility applies Mistral-specific transformations to the request func (req *OpenAIChatRequest) applyMistralCompatibility() { // Mistral uses max_tokens instead of max_completion_tokens if req.MaxCompletionTokens != nil { req.MaxTokens = req.MaxCompletionTokens req.MaxCompletionTokens = nil } // Mistral does not support ToolChoiceStruct, only simple tool choice strings are supported if req.ToolChoice != nil && req.ToolChoice.ChatToolChoiceStruct != nil { req.ToolChoice.ChatToolChoiceStr = schemas.Ptr("any") req.ToolChoice.ChatToolChoiceStruct = nil } } // applyXAICompatibility applies xAI-specific transformations to the request func (req *OpenAIChatRequest) applyXAICompatibility(model string) { // Only apply filters if this is a grok reasoning model if !schemas.IsGrokReasoningModel(model) { return } req.ChatParameters.PresencePenalty = nil // Only non-mini grok-3 models support frequency_penalty and stop // grok-3-mini only supports reasoning_effort in reasoning mode if !strings.Contains(model, "grok-3") || strings.Contains(model, "grok-3-mini") { req.ChatParameters.FrequencyPenalty = nil req.ChatParameters.Stop = nil } // Only grok-3-mini supports reasoning_effort if req.ChatParameters.Reasoning != nil && !strings.Contains(model, "grok-3-mini") { // Clear reasoning_effort for non-grok-3-mini models req.ChatParameters.Reasoning.Effort = nil } }