diff --git a/transports/bifrost-http/integrations/anthropic/router.go b/transports/bifrost-http/integrations/anthropic/router.go index 2e7f1be9f6..81d2275997 100644 --- a/transports/bifrost-http/integrations/anthropic/router.go +++ b/transports/bifrost-http/integrations/anthropic/router.go @@ -1,6 +1,8 @@ package anthropic import ( + "errors" + bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" "github.com/maximhq/bifrost/transports/bifrost-http/integrations" @@ -21,14 +23,14 @@ func NewAnthropicRouter(client *bifrost.Bifrost) *AnthropicRouter { GetRequestTypeInstance: func() interface{} { return &AnthropicMessageRequest{} }, - RequestConverter: func(req interface{}) *schemas.BifrostRequest { + RequestConverter: func(req interface{}) (*schemas.BifrostRequest, error) { if anthropicReq, ok := req.(*AnthropicMessageRequest); ok { - return anthropicReq.ConvertToBifrostRequest() + return anthropicReq.ConvertToBifrostRequest(), nil } - return nil + return nil, errors.New("invalid request type") }, - ResponseFunc: func(resp *schemas.BifrostResponse) interface{} { - return DeriveAnthropicFromBifrostResponse(resp) + ResponseConverter: func(resp *schemas.BifrostResponse) (interface{}, error) { + return DeriveAnthropicFromBifrostResponse(resp), nil }, }, } diff --git a/transports/bifrost-http/integrations/anthropic/types.go b/transports/bifrost-http/integrations/anthropic/types.go index ee867962bc..693232671e 100644 --- a/transports/bifrost-http/integrations/anthropic/types.go +++ b/transports/bifrost-http/integrations/anthropic/types.go @@ -138,11 +138,7 @@ func (r *AnthropicMessageRequest) ConvertToBifrostRequest() *schemas.BifrostRequ case "image": if content.Source != nil { bifrostMsg.UserMessage = &schemas.UserMessage{ - ImageContent: &schemas.ImageContent{ - Type: bifrost.Ptr(content.Source.Type), - URL: content.Source.Data, - MediaType: &content.Source.MediaType, - }, + ImageContent: convertAnthropicImageSource(content.Source), } } case "tool_use": @@ -171,11 +167,7 @@ func (r *AnthropicMessageRequest) ConvertToBifrostRequest() *schemas.BifrostRequ } } if content.Source != nil { - bifrostMsg.ToolMessage.ImageContent = &schemas.ImageContent{ - Type: bifrost.Ptr(content.Source.Type), - URL: content.Source.Data, - MediaType: &content.Source.MediaType, - } + bifrostMsg.ToolMessage.ImageContent = convertAnthropicImageSource(content.Source) } bifrostMsg.Role = schemas.ModelChatMessageRoleTool } @@ -416,3 +408,28 @@ func DeriveAnthropicFromBifrostResponse(bifrostResp *schemas.BifrostResponse) *A anthropicResp.Content = content return anthropicResp } + +// convertAnthropicImageSource converts an Anthropic image source to Bifrost ImageContent format +func convertAnthropicImageSource(source *AnthropicImageSource) *schemas.ImageContent { + if source == nil { + return nil + } + + // Convert Anthropic source type to Bifrost ImageContentType + var contentType schemas.ImageContentType + switch source.Type { + case "base64": + contentType = schemas.ImageContentTypeBase64 + case "url": + contentType = schemas.ImageContentTypeURL + default: + // Default to base64 if unknown type, as this is more common in Anthropic + contentType = schemas.ImageContentTypeBase64 + } + + return &schemas.ImageContent{ + Type: contentType, + URL: source.Data, + MediaType: &source.MediaType, + } +} diff --git a/transports/bifrost-http/integrations/genai/router.go b/transports/bifrost-http/integrations/genai/router.go index 4a6668056f..8f0b470a9b 100644 --- a/transports/bifrost-http/integrations/genai/router.go +++ b/transports/bifrost-http/integrations/genai/router.go @@ -1,6 +1,7 @@ package genai import ( + "errors" "fmt" "strings" @@ -24,14 +25,14 @@ func NewGenAIRouter(client *bifrost.Bifrost) *GenAIRouter { GetRequestTypeInstance: func() interface{} { return &GeminiChatRequest{} }, - RequestConverter: func(req interface{}) *schemas.BifrostRequest { + RequestConverter: func(req interface{}) (*schemas.BifrostRequest, error) { if geminiReq, ok := req.(*GeminiChatRequest); ok { - return geminiReq.ConvertToBifrostRequest() + return geminiReq.ConvertToBifrostRequest(), nil } - return nil + return nil, errors.New("invalid request type") }, - ResponseFunc: func(resp *schemas.BifrostResponse) interface{} { - return DeriveGenAIFromBifrostResponse(resp) + ResponseConverter: func(resp *schemas.BifrostResponse) (interface{}, error) { + return DeriveGenAIFromBifrostResponse(resp), nil }, PreCallback: extractAndSetModelFromURL, }, diff --git a/transports/bifrost-http/integrations/genai/types.go b/transports/bifrost-http/integrations/genai/types.go index 5e5de34fef..de7a250103 100644 --- a/transports/bifrost-http/integrations/genai/types.go +++ b/transports/bifrost-http/integrations/genai/types.go @@ -171,9 +171,8 @@ func (r *GeminiChatRequest) convertContentToBifrostMessages(content genai_sdk.Co case part.InlineData != nil: // Handle inline images/media - imageType := "base64" imageContent := schemas.ImageContent{ - Type: &imageType, + Type: schemas.ImageContentTypeBase64, URL: fmt.Sprintf("data:%s;base64,%s", part.InlineData.MIMEType, base64.StdEncoding.EncodeToString(part.InlineData.Data)), MediaType: &part.InlineData.MIMEType, } @@ -181,9 +180,8 @@ func (r *GeminiChatRequest) convertContentToBifrostMessages(content genai_sdk.Co case part.FileData != nil: // Handle file references - imageType := "url" imageContent := schemas.ImageContent{ - Type: &imageType, + Type: schemas.ImageContentTypeURL, URL: part.FileData.FileURI, MediaType: &part.FileData.MIMEType, } diff --git a/transports/bifrost-http/integrations/litellm/router.go b/transports/bifrost-http/integrations/litellm/router.go new file mode 100644 index 0000000000..f8d2c25464 --- /dev/null +++ b/transports/bifrost-http/integrations/litellm/router.go @@ -0,0 +1,158 @@ +package litellm + +import ( + "encoding/json" + "errors" + "slices" + + bifrost "github.com/maximhq/bifrost/core" + "github.com/maximhq/bifrost/core/schemas" + "github.com/maximhq/bifrost/transports/bifrost-http/integrations" + "github.com/maximhq/bifrost/transports/bifrost-http/integrations/anthropic" + "github.com/maximhq/bifrost/transports/bifrost-http/integrations/genai" + "github.com/maximhq/bifrost/transports/bifrost-http/integrations/openai" + "github.com/valyala/fasthttp" +) + +// LiteLLMRequestWrapper wraps any provider-specific request type +type LiteLLMRequestWrapper struct { + Model string `json:"model"` + ActualRequest interface{} `json:"-"` // This will hold the actual provider-specific request + Provider schemas.ModelProvider `json:"-"` +} + +// LiteLLMRouter holds route registrations for LiteLLM endpoints. +// It supports standard chat completions and image-enabled vision capabilities. +// LiteLLM is fully OpenAI-compatible, so we reuse OpenAI types +// with aliases for clarity and minimal LiteLLM-specific extensions +type LiteLLMRouter struct { + *integrations.GenericRouter +} + +// NewLiteLLMRouter creates a new LiteLLMRouter with the given bifrost client. +func NewLiteLLMRouter(client *bifrost.Bifrost) *LiteLLMRouter { + paths := []string{ + "/chat/completions", + "/v1/messages", + } + + getRequestTypeInstance := func() interface{} { + return &LiteLLMRequestWrapper{} + } + + availableProviders := []schemas.ModelProvider{ + schemas.OpenAI, + schemas.Anthropic, + schemas.Vertex, + schemas.Azure, + } + + // Pre-hook to determine provider and parse request with correct type + preHook := func(ctx *fasthttp.RequestCtx, req interface{}) error { + wrapper, ok := req.(*LiteLLMRequestWrapper) + if !ok { + return errors.New("invalid request wrapper type") + } + + if wrapper.Model == "" { + return errors.New("model field is required") + } + + // Determine provider from model + provider := integrations.GetProviderFromModel(wrapper.Model) + if !slices.Contains(availableProviders, provider) { + return errors.New("unsupported provider: " + string(provider)) + } + + // Get the request body + body := ctx.Request.Body() + if len(body) == 0 { + return errors.New("request body is required") + } + + // Create the appropriate request type based on provider and re-parse + var actualReq interface{} + switch provider { + case schemas.OpenAI, schemas.Azure: + actualReq = &openai.OpenAIChatRequest{} + case schemas.Anthropic: + actualReq = &anthropic.AnthropicMessageRequest{} + case schemas.Vertex: + actualReq = &genai.GeminiChatRequest{} + default: + return errors.New("unsupported provider: " + string(provider)) + } + + // Parse the body into the correct request type + if err := json.Unmarshal(body, actualReq); err != nil { + return errors.New("failed to parse request for provider " + string(provider) + ": " + err.Error()) + } + + // Store the parsed request and provider in the wrapper + wrapper.ActualRequest = actualReq + wrapper.Provider = provider + + return nil + } + + requestConverter := func(req interface{}) (*schemas.BifrostRequest, error) { + wrapper, ok := req.(*LiteLLMRequestWrapper) + if !ok { + return nil, errors.New("invalid request wrapper type") + } + + if wrapper.ActualRequest == nil { + return nil, errors.New("request was not properly processed by pre-hook") + } + + // Handle different provider-specific request types + switch actualReq := wrapper.ActualRequest.(type) { + case *openai.OpenAIChatRequest: + bifrostReq := actualReq.ConvertToBifrostRequest() + bifrostReq.Provider = wrapper.Provider + return bifrostReq, nil + + case *anthropic.AnthropicMessageRequest: + bifrostReq := actualReq.ConvertToBifrostRequest() + bifrostReq.Provider = wrapper.Provider + return bifrostReq, nil + + case *genai.GeminiChatRequest: + bifrostReq := actualReq.ConvertToBifrostRequest() + bifrostReq.Provider = wrapper.Provider + return bifrostReq, nil + + default: + return nil, errors.New("unsupported request type") + } + } + + responseConverter := func(resp *schemas.BifrostResponse) (interface{}, error) { + switch resp.ExtraFields.Provider { + case schemas.OpenAI, schemas.Azure: + return openai.DeriveOpenAIFromBifrostResponse(resp), nil + case schemas.Anthropic: + return anthropic.DeriveAnthropicFromBifrostResponse(resp), nil + case schemas.Vertex: + return genai.DeriveGenAIFromBifrostResponse(resp), nil + default: + return resp, nil + } + } + + routes := []integrations.RouteConfig{} + for _, path := range paths { + routes = append(routes, integrations.RouteConfig{ + Path: "/litellm" + path, + Method: "POST", + GetRequestTypeInstance: getRequestTypeInstance, + RequestConverter: requestConverter, + ResponseConverter: responseConverter, + PreCallback: preHook, + }) + } + + return &LiteLLMRouter{ + GenericRouter: integrations.NewGenericRouter(client, routes), + } +} diff --git a/transports/bifrost-http/integrations/openai/router.go b/transports/bifrost-http/integrations/openai/router.go index 4daf2f587a..df09073f3b 100644 --- a/transports/bifrost-http/integrations/openai/router.go +++ b/transports/bifrost-http/integrations/openai/router.go @@ -1,6 +1,8 @@ package openai import ( + "errors" + bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" "github.com/maximhq/bifrost/transports/bifrost-http/integrations" @@ -21,14 +23,14 @@ func NewOpenAIRouter(client *bifrost.Bifrost) *OpenAIRouter { GetRequestTypeInstance: func() interface{} { return &OpenAIChatRequest{} }, - RequestConverter: func(req interface{}) *schemas.BifrostRequest { + RequestConverter: func(req interface{}) (*schemas.BifrostRequest, error) { if openaiReq, ok := req.(*OpenAIChatRequest); ok { - return openaiReq.ConvertToBifrostRequest() + return openaiReq.ConvertToBifrostRequest(), nil } - return nil + return nil, errors.New("invalid request type") }, - ResponseFunc: func(resp *schemas.BifrostResponse) interface{} { - return DeriveOpenAIFromBifrostResponse(resp) + ResponseConverter: func(resp *schemas.BifrostResponse) (interface{}, error) { + return DeriveOpenAIFromBifrostResponse(resp), nil }, }, } diff --git a/transports/bifrost-http/integrations/utils.go b/transports/bifrost-http/integrations/utils.go index 4a54ff8ee9..40390167f7 100644 --- a/transports/bifrost-http/integrations/utils.go +++ b/transports/bifrost-http/integrations/utils.go @@ -3,6 +3,7 @@ package integrations import ( "encoding/json" "log" + "strings" "github.com/fasthttp/router" bifrost "github.com/maximhq/bifrost/core" @@ -19,11 +20,11 @@ type ExtensionRouter interface { // RequestConverter is a function that converts integration-specific requests to Bifrost format. // It takes the parsed request object and returns a BifrostRequest ready for processing. -type RequestConverter func(req interface{}) *schemas.BifrostRequest +type RequestConverter func(req interface{}) (*schemas.BifrostRequest, error) // ResponseConverter is a function that converts Bifrost responses to integration-specific format. // It takes a BifrostResponse and returns the format expected by the specific integration. -type ResponseConverter func(*schemas.BifrostResponse) interface{} +type ResponseConverter func(*schemas.BifrostResponse) (interface{}, error) // PreRequestCallback is called before processing the request. // It can be used to modify the request object (e.g., extract model from URL parameters) @@ -42,7 +43,7 @@ type RouteConfig struct { Method string // HTTP method (POST, GET, PUT, DELETE) GetRequestTypeInstance func() interface{} // Factory function to create request instance (SHOULD NOT BE NIL) RequestConverter RequestConverter // Function to convert request to BifrostRequest (SHOULD NOT BE NIL) - ResponseFunc ResponseConverter // Function to convert BifrostResponse to integration format (SHOULD NOT BE NIL) + ResponseConverter ResponseConverter // Function to convert BifrostResponse to integration format (SHOULD NOT BE NIL) PreCallback PreRequestCallback // Optional: called before request processing PostCallback PostRequestCallback // Optional: called after request processing } @@ -77,8 +78,8 @@ func (g *GenericRouter) RegisterRoutes(r *router.Router) { log.Println("[WARN] route configuration is invalid: RequestConverter cannot be nil for route " + route.Path) continue } - if route.ResponseFunc == nil { - log.Println("[WARN] route configuration is invalid: ResponseFunc cannot be nil for route " + route.Path) + if route.ResponseConverter == nil { + log.Println("[WARN] route configuration is invalid: ResponseConverter cannot be nil for route " + route.Path) continue } @@ -89,14 +90,14 @@ func (g *GenericRouter) RegisterRoutes(r *router.Router) { } handler := g.createHandler(route) - switch route.Method { - case "POST": + switch strings.ToUpper(route.Method) { + case fasthttp.MethodPost: r.POST(route.Path, handler) - case "GET": + case fasthttp.MethodGet: r.GET(route.Path, handler) - case "PUT": + case fasthttp.MethodPut: r.PUT(route.Path, handler) - case "DELETE": + case fasthttp.MethodDelete: r.DELETE(route.Path, handler) default: r.POST(route.Path, handler) // Default to POST @@ -120,7 +121,7 @@ func (g *GenericRouter) createHandler(config RouteConfig) fasthttp.RequestHandle method := string(ctx.Method()) - if method != "GET" && method != "DELETE" { + if method != fasthttp.MethodGet && method != fasthttp.MethodDelete { // Use ctx.Request.Body() instead of ctx.PostBody() to support all HTTP methods body := ctx.Request.Body() if len(body) > 0 { @@ -142,7 +143,11 @@ func (g *GenericRouter) createHandler(config RouteConfig) fasthttp.RequestHandle } // Convert the integration-specific request to Bifrost format - bifrostReq := config.RequestConverter(req) + bifrostReq, err := config.RequestConverter(req) + if err != nil { + g.sendError(ctx, fasthttp.StatusBadRequest, err.Error()) + return + } if bifrostReq == nil { g.sendError(ctx, fasthttp.StatusBadRequest, "Invalid request") return @@ -154,13 +159,13 @@ func (g *GenericRouter) createHandler(config RouteConfig) fasthttp.RequestHandle // Execute the request through Bifrost bifrostCtx := lib.ConvertToBifrostContext(ctx) - result, err := g.client.ChatCompletionRequest(*bifrostCtx, bifrostReq) - if err != nil { + result, bifrostErr := g.client.ChatCompletionRequest(*bifrostCtx, bifrostReq) + if bifrostErr != nil { g.sendError(ctx, func() int { - if err.StatusCode != nil { - return *err.StatusCode + if bifrostErr.IsBifrostError { + return fasthttp.StatusInternalServerError } - return fasthttp.StatusInternalServerError + return fasthttp.StatusBadRequest }(), err) return } @@ -179,7 +184,11 @@ func (g *GenericRouter) createHandler(config RouteConfig) fasthttp.RequestHandle } // Convert Bifrost response to integration-specific format and send - response := config.ResponseFunc(result) + response, err := config.ResponseConverter(result) + if err != nil { + g.sendError(ctx, fasthttp.StatusInternalServerError, err.Error()) + return + } g.sendSuccess(ctx, response) } } @@ -215,3 +224,125 @@ func (g *GenericRouter) sendSuccess(ctx *fasthttp.RequestCtx, response interface ctx.SetBody(responseBody) } + +// GetProviderFromModel determines the appropriate provider based on model name patterns +// This function uses comprehensive pattern matching to identify the correct provider +// for various model naming conventions used across different AI providers. +func GetProviderFromModel(model string) schemas.ModelProvider { + // Normalize model name for case-insensitive matching + modelLower := strings.ToLower(strings.TrimSpace(model)) + + // Azure OpenAI Models - check first to prevent false positives from OpenAI "gpt" patterns + if isAzureModel(modelLower) { + return schemas.Azure + } + + // OpenAI Models - comprehensive pattern matching + if isOpenAIModel(modelLower) { + return schemas.OpenAI + } + + // Anthropic Models - Claude family + if isAnthropicModel(modelLower) { + return schemas.Anthropic + } + + // Google Vertex AI Models - Gemini and Palm family + if isVertexModel(modelLower) { + return schemas.Vertex + } + + // AWS Bedrock Models - various model providers through Bedrock + if isBedrockModel(modelLower) { + return schemas.Bedrock + } + + // Cohere Models - Command and Embed family + if isCohereModel(modelLower) { + return schemas.Cohere + } + + // Default to OpenAI for unknown models (most LiteLLM compatible) + return schemas.OpenAI +} + +// isOpenAIModel checks for OpenAI model patterns +func isOpenAIModel(model string) bool { + // Exclude Azure models to prevent overlap + if strings.Contains(model, "azure/") { + return false + } + + openaiPatterns := []string{ + "gpt", "davinci", "curie", "babbage", "ada", "o1", "o3", "o4", + "text-embedding", "dall-e", "whisper", "tts", "chatgpt", + } + + return matchesAnyPattern(model, openaiPatterns) +} + +// isAzureModel checks for Azure OpenAI specific patterns +func isAzureModel(model string) bool { + azurePatterns := []string{ + "azure", "model-router", "computer-use-preview", + } + + return matchesAnyPattern(model, azurePatterns) +} + +// isAnthropicModel checks for Anthropic Claude model patterns +func isAnthropicModel(model string) bool { + anthropicPatterns := []string{ + "claude", "anthropic/", + } + + return matchesAnyPattern(model, anthropicPatterns) +} + +// isVertexModel checks for Google Vertex AI model patterns +func isVertexModel(model string) bool { + vertexPatterns := []string{ + "gemini", "palm", "bison", "gecko", "vertex/", "google/", + } + + return matchesAnyPattern(model, vertexPatterns) +} + +// isBedrockModel checks for AWS Bedrock model patterns +func isBedrockModel(model string) bool { + bedrockPatterns := []string{ + "bedrock", "bedrock.amazonaws.com/", "bedrock/", + "amazon.titan", "amazon.nova", "aws/amazon.", + "ai21.jamba", "ai21.j2", "aws/ai21.", + "meta.llama", "aws/meta.", + "stability.stable-diffusion", "stability.sd3", "aws/stability.", + "anthropic.claude", "aws/anthropic.", + "cohere.command", "cohere.embed", "aws/cohere.", + "mistral.mistral", "mistral.mixtral", "aws/mistral.", + "titan-text", "titan-embed", "nova-micro", "nova-lite", "nova-pro", + "jamba-instruct", "j2-ultra", "j2-mid", + "llama-2", "llama-3", "llama-3.1", "llama-3.2", + "stable-diffusion-xl", "sd3-large", + } + + return matchesAnyPattern(model, bedrockPatterns) +} + +// isCohereModel checks for Cohere model patterns +func isCohereModel(model string) bool { + coherePatterns := []string{ + "command-", "embed-", "cohere", + } + + return matchesAnyPattern(model, coherePatterns) +} + +// matchesAnyPattern checks if the model matches any of the given patterns +func matchesAnyPattern(model string, patterns []string) bool { + for _, pattern := range patterns { + if strings.Contains(model, pattern) { + return true + } + } + return false +} diff --git a/transports/bifrost-http/main.go b/transports/bifrost-http/main.go index b74f3271b0..ed32e7cad1 100644 --- a/transports/bifrost-http/main.go +++ b/transports/bifrost-http/main.go @@ -47,6 +47,7 @@ import ( "github.com/maximhq/bifrost/transports/bifrost-http/integrations" "github.com/maximhq/bifrost/transports/bifrost-http/integrations/anthropic" "github.com/maximhq/bifrost/transports/bifrost-http/integrations/genai" + "github.com/maximhq/bifrost/transports/bifrost-http/integrations/litellm" "github.com/maximhq/bifrost/transports/bifrost-http/integrations/openai" "github.com/maximhq/bifrost/transports/bifrost-http/lib" "github.com/maximhq/bifrost/transports/bifrost-http/tracking" @@ -198,6 +199,7 @@ func main() { genai.NewGenAIRouter(client), openai.NewOpenAIRouter(client), anthropic.NewAnthropicRouter(client), + litellm.NewLiteLLMRouter(client), } r.POST("/v1/text/completions", func(ctx *fasthttp.RequestCtx) { @@ -218,7 +220,7 @@ func main() { r.NotFound = func(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fasthttp.StatusNotFound) ctx.SetContentType("text/plain") - ctx.SetBodyString("Route not found") + ctx.SetBodyString("Route not found: " + string(ctx.Path())) } server := &fasthttp.Server{ diff --git a/transports/go.mod b/transports/go.mod index b9cae973e6..da6c0f536e 100644 --- a/transports/go.mod +++ b/transports/go.mod @@ -4,7 +4,7 @@ go 1.24.1 require ( github.com/fasthttp/router v1.5.4 - github.com/maximhq/bifrost/core v1.0.9 + github.com/maximhq/bifrost/core v1.0.10 github.com/maximhq/bifrost/plugins/maxim v1.0.3 github.com/prometheus/client_golang v1.22.0 github.com/valyala/fasthttp v1.62.0 diff --git a/transports/go.sum b/transports/go.sum index f522c88484..8d7afb9170 100644 --- a/transports/go.sum +++ b/transports/go.sum @@ -67,8 +67,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/maximhq/bifrost/core v1.0.9 h1:OWUHCWCQsBH43YPIy2AsqNMZhoFXYe/qhJSCLbw5su8= -github.com/maximhq/bifrost/core v1.0.9/go.mod h1:8ycaWQ9bjQezoUT/x6a82VmPjoqLzyGglQ0RnnlZjqo= +github.com/maximhq/bifrost/core v1.0.10 h1:HLDOg11tFK7AwEHKqBhImUHbeWhFB775TiE/7BJMQhE= +github.com/maximhq/bifrost/core v1.0.10/go.mod h1:8ycaWQ9bjQezoUT/x6a82VmPjoqLzyGglQ0RnnlZjqo= github.com/maximhq/bifrost/plugins/maxim v1.0.3 h1:3m3BGfC30pNVVYdon77etOBinEaD9B9RVgsTB8HtuDU= github.com/maximhq/bifrost/plugins/maxim v1.0.3/go.mod h1:Zakfd201Id5uN368lFB09nrOJ3cCmGmzrKOFWq0KiAc= github.com/maximhq/maxim-go v0.1.3 h1:nVzdz3hEjZVxmWHARWIM+Yrn1Jp50qrsK4BA/sz2jj8=