-
Notifications
You must be signed in to change notification settings - Fork 82
Add management endpoints support #493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,11 +2,15 @@ package anthropic | |
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "log" | ||
| "strings" | ||
|
|
||
| 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/lib" | ||
| "github.com/valyala/fasthttp" | ||
| ) | ||
|
|
||
| // AnthropicRouter handles Anthropic-compatible API endpoints | ||
|
|
@@ -16,35 +20,43 @@ type AnthropicRouter struct { | |
|
|
||
| // CreateAnthropicRouteConfigs creates route configurations for Anthropic endpoints. | ||
| func CreateAnthropicRouteConfigs(pathPrefix string) []integrations.RouteConfig { | ||
| return []integrations.RouteConfig{ | ||
| { | ||
| Path: pathPrefix + "/v1/messages", | ||
| Method: "POST", | ||
| GetRequestTypeInstance: func() interface{} { | ||
| return &AnthropicMessageRequest{} | ||
| }, | ||
| RequestConverter: func(req interface{}) (*schemas.BifrostRequest, error) { | ||
| if anthropicReq, ok := req.(*AnthropicMessageRequest); ok { | ||
| return anthropicReq.ConvertToBifrostRequest(), nil | ||
| } | ||
| return nil, errors.New("invalid request type") | ||
| }, | ||
| var routes []integrations.RouteConfig | ||
|
|
||
| // Messages endpoint | ||
| routes = append(routes, integrations.RouteConfig{ | ||
| Path: pathPrefix + "/v1/messages", | ||
| Method: "POST", | ||
| GetRequestTypeInstance: func() interface{} { | ||
| return &AnthropicMessageRequest{} | ||
| }, | ||
| RequestConverter: func(req interface{}) (*schemas.BifrostRequest, error) { | ||
| if anthropicReq, ok := req.(*AnthropicMessageRequest); ok { | ||
| return anthropicReq.ConvertToBifrostRequest(), nil | ||
| } | ||
| return nil, errors.New("invalid request type") | ||
| }, | ||
| ResponseConverter: func(resp *schemas.BifrostResponse) (interface{}, error) { | ||
| return DeriveAnthropicFromBifrostResponse(resp), nil | ||
| }, | ||
| ErrorConverter: func(err *schemas.BifrostError) interface{} { | ||
| return DeriveAnthropicErrorFromBifrostError(err) | ||
| }, | ||
| StreamConfig: &integrations.StreamConfig{ | ||
| ResponseConverter: func(resp *schemas.BifrostResponse) (interface{}, error) { | ||
| return DeriveAnthropicFromBifrostResponse(resp), nil | ||
| return DeriveAnthropicStreamFromBifrostResponse(resp), nil | ||
| }, | ||
| ErrorConverter: func(err *schemas.BifrostError) interface{} { | ||
| return DeriveAnthropicErrorFromBifrostError(err) | ||
| }, | ||
| StreamConfig: &integrations.StreamConfig{ | ||
| ResponseConverter: func(resp *schemas.BifrostResponse) (interface{}, error) { | ||
| return DeriveAnthropicStreamFromBifrostResponse(resp), nil | ||
| }, | ||
| ErrorConverter: func(err *schemas.BifrostError) interface{} { | ||
| return DeriveAnthropicStreamFromBifrostError(err) | ||
| }, | ||
| return DeriveAnthropicStreamFromBifrostError(err) | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| // Add management endpoints only for primary Anthropic integration | ||
| if pathPrefix == "/anthropic" { | ||
| routes = append(routes, createAnthropicManagementRoutes(pathPrefix)...) | ||
| } | ||
|
|
||
| return routes | ||
| } | ||
|
|
||
| // NewAnthropicRouter creates a new AnthropicRouter with the given bifrost client. | ||
|
|
@@ -53,3 +65,89 @@ func NewAnthropicRouter(client *bifrost.Bifrost, handlerStore lib.HandlerStore) | |
| GenericRouter: integrations.NewGenericRouter(client, handlerStore, CreateAnthropicRouteConfigs("/anthropic")), | ||
| } | ||
| } | ||
|
|
||
| // createAnthropicManagementRoutes creates route configurations for Anthropic management endpoints | ||
| func createAnthropicManagementRoutes(pathPrefix string) []integrations.RouteConfig { | ||
| var routes []integrations.RouteConfig | ||
|
|
||
| // Management endpoints - following the same for-loop pattern as other routes | ||
| for _, path := range []string{ | ||
| "/v1/models", | ||
| "/v1/usage", | ||
| } { | ||
| log.Printf("Creating management route: %s", pathPrefix + path) | ||
| routes = append(routes, integrations.RouteConfig{ | ||
| Path: pathPrefix + path, | ||
| Method: "GET", | ||
| GetRequestTypeInstance: func() interface{} { | ||
| return &integrations.ManagementRequest{} | ||
| }, | ||
| RequestConverter: func(req interface{}) (*schemas.BifrostRequest, error) { | ||
| // For management endpoints, we create a minimal BifrostRequest | ||
| // The actual API call is handled by the PreCallback (handleAnthropicManagementRequest) | ||
| return &schemas.BifrostRequest{ | ||
| Provider: schemas.Anthropic, | ||
| Model: "management", // Special model type for management requests | ||
| Input: schemas.RequestInput{}, // Empty input - management doesn't need chat data | ||
| }, nil | ||
| }, | ||
| ResponseConverter: func(resp *schemas.BifrostResponse) (interface{}, error) { | ||
| return map[string]interface{}{ | ||
| "object": "list", | ||
| "data": []interface{}{}, | ||
| }, nil | ||
| }, | ||
| ErrorConverter: func(err *schemas.BifrostError) interface{} { | ||
| return map[string]interface{}{ | ||
| "object": "list", | ||
| "data": []interface{}{}, | ||
| } | ||
| }, | ||
| PreCallback: handleAnthropicManagementRequest, | ||
| }) | ||
| } | ||
|
|
||
| return routes | ||
| } | ||
|
|
||
| // handleAnthropicManagementRequest handles management endpoint requests by forwarding directly to Anthropic API | ||
| func handleAnthropicManagementRequest(ctx *fasthttp.RequestCtx, req interface{}) error { | ||
| // Extract API key from request | ||
| apiKey, err := integrations.ExtractAPIKeyFromContext(ctx) | ||
| if err != nil { | ||
| integrations.SendManagementError(ctx, err, 401) | ||
| return err | ||
| } | ||
|
|
||
|
Comment on lines
+116
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Short‑circuit after writing errors to avoid double handling. Apply this diff to consistently mark as handled and return nil after writing the response: @@
- if err != nil {
- integrations.SendManagementError(ctx, err, 401)
- return err
- }
+ if err != nil {
+ integrations.SendManagementError(ctx, err, 401)
+ ctx.SetUserValue("management_handled", true)
+ return nil
+ }
@@
- default:
- integrations.SendManagementError(ctx, fmt.Errorf("unknown management endpoint"), 404)
- return fmt.Errorf("unknown management endpoint")
+ default:
+ err = fmt.Errorf("unknown management endpoint")
+ integrations.SendManagementError(ctx, err, 404)
+ ctx.SetUserValue("management_handled", true)
+ return nilAlso applies to: 133-136 🤖 Prompt for AI Agents |
||
| // Extract query parameters | ||
| queryParams := integrations.ExtractQueryParams(ctx) | ||
|
|
||
| // Determine the endpoint based on the path | ||
| var endpoint string | ||
| path := string(ctx.Path()) | ||
| switch { | ||
| case strings.HasSuffix(path, "/v1/models"): | ||
| endpoint = "/v1/models" | ||
| case strings.HasSuffix(path, "/v1/usage"): | ||
| endpoint = "/v1/usage" | ||
| default: | ||
| integrations.SendManagementError(ctx, fmt.Errorf("unknown management endpoint"), 404) | ||
| return fmt.Errorf("unknown management endpoint") | ||
| } | ||
|
|
||
| // Create management client and forward the request | ||
| client := integrations.NewManagementAPIClient() | ||
| response, err := client.ForwardRequest(ctx, schemas.Anthropic, endpoint, apiKey, queryParams) | ||
| if err != nil { | ||
| log.Printf("Failed to forward request to Anthropic: %v", err) | ||
| integrations.SendManagementError(ctx, err, 500) | ||
| ctx.SetUserValue("management_handled", true) | ||
| return nil | ||
| } | ||
| log.Printf("Response status code: %v", response.StatusCode) | ||
|
|
||
| // Send the successful response | ||
| integrations.SendManagementResponse(ctx, response.Data, response.StatusCode) | ||
| ctx.SetUserValue("management_handled", true) | ||
| return nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Deduplicate ManagementResponse type (reuse schemas across packages).
An identical ManagementResponse exists in integrations/utils.go. Keep a single source of truth here and remove the duplicate from integrations to prevent drift.
Apply this diff in transports/bifrost-http/integrations/utils.go to reuse the schema type:
And update ForwardRequest’s return type:
Also update the return statement:
🤖 Prompt for AI Agents