package replicate import ( "fmt" "strings" "time" "github.com/maximhq/bifrost/core/schemas" ) func ToReplicateResponsesRequest(bifrostReq *schemas.BifrostResponsesRequest) (*ReplicatePredictionRequest, error) { if bifrostReq == nil { return nil, fmt.Errorf("bifrost request is nil") } input := &ReplicatePredictionRequestInput{} if strings.HasPrefix(bifrostReq.Model, "openai/") && strings.Contains(bifrostReq.Model, "gpt-5-structured") { // handle responses style request if len(bifrostReq.Input) > 0 { input.InputItemList = bifrostReq.Input } if bifrostReq.Params != nil { if bifrostReq.Params.Instructions != nil { input.Instructions = bifrostReq.Params.Instructions } if bifrostReq.Params.Tools != nil { input.Tools = bifrostReq.Params.Tools } if bifrostReq.Params.MaxOutputTokens != nil { input.MaxOutputTokens = bifrostReq.Params.MaxOutputTokens } if bifrostReq.Params.Text != nil { input.JsonSchema = bifrostReq.Params.Text } if bifrostReq.Params.ExtraParams != nil { input.ExtraParams = bifrostReq.Params.ExtraParams } } } else { // handle chat style request (same logic as chat converter) if len(bifrostReq.Input) > 0 { // if model is from openai family, use messages if strings.HasPrefix(bifrostReq.Model, string(schemas.OpenAI)) { input.Messages = schemas.ToChatMessages(bifrostReq.Input) } else { // convert input to prompt and system prompt var systemPrompt string var conversationParts []string var imageInput []string for _, msg := range bifrostReq.Input { if msg.Content == nil { continue } // Get message content as string var contentStr string if msg.Content.ContentStr != nil { contentStr = *msg.Content.ContentStr } else if msg.Content.ContentBlocks != nil { // Concatenate text blocks only var textParts []string for _, block := range msg.Content.ContentBlocks { if block.Text != nil && *block.Text != "" { textParts = append(textParts, *block.Text) } if block.ResponsesInputMessageContentBlockImage != nil && block.ResponsesInputMessageContentBlockImage.ImageURL != nil && *block.ResponsesInputMessageContentBlockImage.ImageURL != "" { imageInput = append(imageInput, *block.ResponsesInputMessageContentBlockImage.ImageURL) } } contentStr = strings.Join(textParts, "\n") } if contentStr == "" { continue } // Handle different roles if msg.Role != nil { switch *msg.Role { case schemas.ResponsesInputMessageRoleSystem: if systemPrompt == "" { systemPrompt = contentStr } else { systemPrompt += "\n" + contentStr } case schemas.ResponsesInputMessageRoleUser: conversationParts = append(conversationParts, contentStr) case schemas.ResponsesInputMessageRoleAssistant: // For assistant messages, we can include them in the conversation context conversationParts = append(conversationParts, contentStr) } } } // Set system prompt if present and model supports it modelSupportsSystemPrompt := supportsSystemPrompt(bifrostReq.Model) if systemPrompt != "" { if modelSupportsSystemPrompt { // Model supports system_prompt field input.SystemPrompt = &systemPrompt } else { // Model doesn't support system_prompt - prepend to prompt if len(conversationParts) > 0 { // Prepend system prompt to conversation conversationParts = append([]string{systemPrompt}, conversationParts...) } else { // No conversation parts, use system prompt as the prompt conversationParts = []string{systemPrompt} } } } // Build the final prompt from conversation parts if len(conversationParts) > 0 { prompt := strings.Join(conversationParts, "\n\n") input.Prompt = &prompt } if len(imageInput) > 0 { input.ImageInput = imageInput } } } // Map parameters if present if bifrostReq.Params != nil { params := bifrostReq.Params // Temperature if params.Temperature != nil { input.Temperature = params.Temperature } // Top P if params.TopP != nil { input.TopP = params.TopP } // Max tokens - use max_completion_tokens if available if params.MaxOutputTokens != nil { if strings.HasPrefix(bifrostReq.Model, string(schemas.OpenAI)) { input.MaxCompletionTokens = params.MaxOutputTokens } else { input.MaxTokens = params.MaxOutputTokens } } // Reasoning effort if params.Reasoning != nil { if params.Reasoning.Effort != nil { input.ReasoningEffort = params.Reasoning.Effort } } if params.Instructions != nil && *params.Instructions != "" { if supportsSystemPrompt(bifrostReq.Model) { if input.SystemPrompt == nil { input.SystemPrompt = params.Instructions } } else { if input.Prompt != nil && *input.Prompt != "" { prefixed := *params.Instructions + "\n\n" + *input.Prompt input.Prompt = schemas.Ptr(prefixed) } else if input.Prompt == nil { input.Prompt = params.Instructions } } } if params.ExtraParams != nil { input.ExtraParams = params.ExtraParams } } } // Check if model is a version ID and set version field accordingly req := &ReplicatePredictionRequest{ Input: input, } if isVersionID(bifrostReq.Model) { req.Version = &bifrostReq.Model } if bifrostReq.Params != nil && bifrostReq.Params.ExtraParams != nil { if webhook, ok := schemas.SafeExtractStringPointer(bifrostReq.Params.ExtraParams["webhook"]); ok { req.Webhook = webhook } if webhookEventsFilter, ok := schemas.SafeExtractStringSlice(bifrostReq.Params.ExtraParams["webhook_events_filter"]); ok { req.WebhookEventsFilter = webhookEventsFilter } } return req, nil } func (response *ReplicatePredictionResponse) ToBifrostResponsesResponse() *schemas.BifrostResponsesResponse { if response == nil { return nil } // Parse timestamps createdAt := ParseReplicateTimestamp(response.CreatedAt) if createdAt == 0 { createdAt = time.Now().Unix() } var completedAt *int if response.CompletedAt != nil { completed := int(ParseReplicateTimestamp(*response.CompletedAt)) if completed > 0 { completedAt = &completed } } // Initialize Bifrost response bifrostResponse := &schemas.BifrostResponsesResponse{ ID: schemas.Ptr(response.ID), Model: response.Model, CreatedAt: int(createdAt), CompletedAt: completedAt, } // Convert output to ResponsesMessage var outputMessages []schemas.ResponsesMessage if response.Output != nil { var contentStr *string // Handle different output types if response.Output.OutputStr != nil { contentStr = response.Output.OutputStr } else if response.Output.OutputArray != nil { // Join array of strings into a single string joined := strings.Join(response.Output.OutputArray, "") contentStr = &joined } else if response.Output.OutputObject != nil && response.Output.OutputObject.Text != nil { // Use text field from OutputObject contentStr = response.Output.OutputObject.Text } if contentStr != nil && *contentStr != "" { messageType := schemas.ResponsesMessageTypeMessage role := schemas.ResponsesInputMessageRoleAssistant outputMsg := schemas.ResponsesMessage{ Type: &messageType, Role: &role, Content: &schemas.ResponsesMessageContent{ ContentStr: contentStr, }, } outputMessages = append(outputMessages, outputMsg) } } bifrostResponse.Output = outputMessages // Set status based on prediction status var status string switch response.Status { case ReplicatePredictionStatusSucceeded: status = "completed" case ReplicatePredictionStatusFailed: status = "failed" case ReplicatePredictionStatusCanceled: status = "cancelled" case ReplicatePredictionStatusProcessing: status = "in_progress" case ReplicatePredictionStatusStarting: status = "queued" default: status = string(response.Status) } bifrostResponse.Status = &status // Set error if present if response.Error != nil && *response.Error != "" { bifrostResponse.Error = &schemas.ResponsesResponseError{ Code: "provider_error", Message: *response.Error, } } // Extract usage information from logs if response.Logs != nil { inputTokens, outputTokens, totalTokens, found := parseTokenUsageFromLogs(response.Logs, schemas.ResponsesRequest) if found { bifrostResponse.Usage = &schemas.ResponsesResponseUsage{ InputTokens: inputTokens, OutputTokens: outputTokens, TotalTokens: totalTokens, } } } return bifrostResponse }