Files
bifrost/examples/plugins/multi-interface/main.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

315 lines
11 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/maximhq/bifrost/core/schemas"
)
// Plugin configuration
type PluginConfig struct {
EnableHTTPHooks bool `json:"enable_http_hooks"` // Enable HTTP transport hooks
EnableLLMHooks bool `json:"enable_llm_hooks"` // Enable LLM hooks
EnableMCPHooks bool `json:"enable_mcp_hooks"` // Enable MCP hooks
EnableObservability bool `json:"enable_observability"` // Enable observability/trace injection
EnableLogging bool `json:"enable_logging"` // Enable detailed logging
TrackRequests bool `json:"track_requests"` // Track request count
InjectUptime bool `json:"inject_uptime"` // Inject server uptime in system messages
CustomHeaderPrefix string `json:"custom_header_prefix"` // Custom prefix for plugin headers
}
var (
// Default configuration
pluginConfig = &PluginConfig{
EnableHTTPHooks: true,
EnableLLMHooks: true,
EnableMCPHooks: true,
EnableObservability: true,
EnableLogging: true,
TrackRequests: true,
InjectUptime: true,
CustomHeaderPrefix: "X-Multi-Plugin",
}
// Plugin state
requestCount int64
startTime time.Time
)
// Init is called when the plugin is loaded (optional)
func Init(config any) error {
fmt.Println("[Multi-Interface Plugin] Init called")
startTime = time.Now()
// Parse configuration
if configMap, ok := config.(map[string]interface{}); ok {
if enableHTTP, ok := configMap["enable_http_hooks"].(bool); ok {
pluginConfig.EnableHTTPHooks = enableHTTP
}
if enableLLM, ok := configMap["enable_llm_hooks"].(bool); ok {
pluginConfig.EnableLLMHooks = enableLLM
}
if enableMCP, ok := configMap["enable_mcp_hooks"].(bool); ok {
pluginConfig.EnableMCPHooks = enableMCP
}
if enableObs, ok := configMap["enable_observability"].(bool); ok {
pluginConfig.EnableObservability = enableObs
}
if enableLogging, ok := configMap["enable_logging"].(bool); ok {
pluginConfig.EnableLogging = enableLogging
}
if trackReq, ok := configMap["track_requests"].(bool); ok {
pluginConfig.TrackRequests = trackReq
}
if injectUptime, ok := configMap["inject_uptime"].(bool); ok {
pluginConfig.InjectUptime = injectUptime
}
if headerPrefix, ok := configMap["custom_header_prefix"].(string); ok {
pluginConfig.CustomHeaderPrefix = headerPrefix
}
}
fmt.Printf("[Multi-Interface Plugin] Configuration loaded:\n")
fmt.Printf(" HTTP Hooks: %v\n", pluginConfig.EnableHTTPHooks)
fmt.Printf(" LLM Hooks: %v\n", pluginConfig.EnableLLMHooks)
fmt.Printf(" MCP Hooks: %v\n", pluginConfig.EnableMCPHooks)
fmt.Printf(" Observability: %v\n", pluginConfig.EnableObservability)
fmt.Printf(" Request Tracking: %v\n", pluginConfig.TrackRequests)
return nil
}
// GetName returns the name of the plugin (required)
// This is the system identifier - not editable by users
// Users can set a custom display_name in the config for the UI
func GetName() string {
return "multi-interface"
}
// ============================================================================
// HTTPTransportPlugin Interface
// ============================================================================
// HTTPTransportPreHook handles HTTP-layer request interception
func HTTPTransportPreHook(ctx *schemas.BifrostContext, req *schemas.HTTPRequest) (*schemas.HTTPResponse, error) {
if !pluginConfig.EnableHTTPHooks {
return nil, nil
}
if pluginConfig.EnableLogging {
fmt.Println("[Multi-Interface Plugin] HTTPTransportPreHook called")
}
// Add request tracking (configurable)
if pluginConfig.TrackRequests {
requestCount++
req.Headers[fmt.Sprintf("%s-Request-Number", pluginConfig.CustomHeaderPrefix)] = fmt.Sprintf("%d", requestCount)
}
// Store HTTP metadata in context for later hooks
ctx.SetValue(schemas.BifrostContextKey("multi-http-request-time"), time.Now())
ctx.SetValue(schemas.BifrostContextKey("multi-http-path"), req.Path)
return nil, nil
}
// HTTPTransportPostHook handles HTTP-layer response interception
func HTTPTransportPostHook(ctx *schemas.BifrostContext, req *schemas.HTTPRequest, resp *schemas.HTTPResponse) error {
if !pluginConfig.EnableHTTPHooks {
return nil
}
if pluginConfig.EnableLogging {
fmt.Println("[Multi-Interface Plugin] HTTPTransportPostHook called")
}
// Calculate HTTP duration
if startTime, ok := ctx.Value(schemas.BifrostContextKey("multi-http-request-time")).(time.Time); ok {
duration := time.Since(startTime)
resp.Headers[fmt.Sprintf("%s-Duration-Ms", pluginConfig.CustomHeaderPrefix)] = fmt.Sprintf("%d", duration.Milliseconds())
}
// Add plugin info header
var interfaces []string
if pluginConfig.EnableHTTPHooks {
interfaces = append(interfaces, "http")
}
if pluginConfig.EnableLLMHooks {
interfaces = append(interfaces, "llm")
}
if pluginConfig.EnableMCPHooks {
interfaces = append(interfaces, "mcp")
}
if pluginConfig.EnableObservability {
interfaces = append(interfaces, "observability")
}
resp.Headers[fmt.Sprintf("%s-Interfaces", pluginConfig.CustomHeaderPrefix)] = fmt.Sprintf("%v", interfaces)
return nil
}
// ============================================================================
// LLMPlugin Interface
// ============================================================================
// PreLLMHook is called before the LLM provider is invoked
func PreLLMHook(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) (*schemas.BifrostRequest, *schemas.LLMPluginShortCircuit, error) {
if !pluginConfig.EnableLLMHooks {
return req, nil, nil
}
if pluginConfig.EnableLogging {
fmt.Println("[Multi-Interface Plugin] PreLLMHook called")
httpPath := ctx.Value(schemas.BifrostContextKey("multi-http-path"))
fmt.Printf("[Multi-Interface Plugin] Processing LLM request from path: %v\n", httpPath)
}
// Store LLM metadata
ctx.SetValue(schemas.BifrostContextKey("multi-llm-start-time"), time.Now())
// Example: Add system prompt with uptime (configurable)
if pluginConfig.InjectUptime && req.ChatRequest != nil && req.ChatRequest.Input != nil {
var content string
if pluginConfig.TrackRequests {
content = fmt.Sprintf("Processing request #%d. Server uptime: %v", requestCount, time.Since(startTime))
} else {
content = fmt.Sprintf("Server uptime: %v", time.Since(startTime))
}
systemMsg := schemas.ChatMessage{
Role: "system",
Content: &schemas.ChatMessageContent{ContentStr: &content},
}
req.ChatRequest.Input = append([]schemas.ChatMessage{systemMsg}, req.ChatRequest.Input...)
}
return req, nil, nil
}
// PostLLMHook is called after the LLM provider responds
func PostLLMHook(ctx *schemas.BifrostContext, resp *schemas.BifrostResponse, bifrostErr *schemas.BifrostError) (*schemas.BifrostResponse, *schemas.BifrostError, error) {
if !pluginConfig.EnableLLMHooks {
return resp, bifrostErr, nil
}
if pluginConfig.EnableLogging {
fmt.Println("[Multi-Interface Plugin] PostLLMHook called")
}
// Calculate LLM duration
if startTime, ok := ctx.Value(schemas.BifrostContextKey("multi-llm-start-time")).(time.Time); ok {
duration := time.Since(startTime)
if pluginConfig.EnableLogging {
fmt.Printf("[Multi-Interface Plugin] LLM call took: %v\n", duration)
}
// Store for observability
ctx.SetValue(schemas.BifrostContextKey("multi-llm-duration"), duration)
}
return resp, bifrostErr, nil
}
// ============================================================================
// MCPPlugin Interface
// ============================================================================
// PreMCPHook is called before MCP tool/resource calls are executed
func PreMCPHook(ctx *schemas.BifrostContext, req *schemas.BifrostMCPRequest) (*schemas.BifrostMCPRequest, *schemas.MCPPluginShortCircuit, error) {
if !pluginConfig.EnableMCPHooks {
return req, nil, nil
}
if pluginConfig.EnableLogging {
fmt.Println("[Multi-Interface Plugin] PreMCPHook called")
httpPath := ctx.Value(schemas.BifrostContextKey("multi-http-path"))
fmt.Printf("[Multi-Interface Plugin] Processing MCP request from path: %v\n", httpPath)
}
// Store MCP metadata
ctx.SetValue(schemas.BifrostContextKey("multi-mcp-start-time"), time.Now())
ctx.SetValue(schemas.BifrostContextKey("multi-mcp-type"), req.RequestType)
// Example: Log the MCP call
if pluginConfig.EnableLogging && req.ChatAssistantMessageToolCall != nil && req.ChatAssistantMessageToolCall.Function.Name != nil {
fmt.Printf("[Multi-Interface Plugin] MCP tool call: %s\n", *req.ChatAssistantMessageToolCall.Function.Name)
}
return req, nil, nil
}
// PostMCPHook is called after MCP tool/resource calls complete
func PostMCPHook(ctx *schemas.BifrostContext, resp *schemas.BifrostMCPResponse, bifrostErr *schemas.BifrostError) (*schemas.BifrostMCPResponse, *schemas.BifrostError, error) {
if !pluginConfig.EnableMCPHooks {
return resp, bifrostErr, nil
}
if pluginConfig.EnableLogging {
fmt.Println("[Multi-Interface Plugin] PostMCPHook called")
}
// Calculate MCP duration
if startTime, ok := ctx.Value(schemas.BifrostContextKey("multi-mcp-start-time")).(time.Time); ok {
duration := time.Since(startTime)
if pluginConfig.EnableLogging {
fmt.Printf("[Multi-Interface Plugin] MCP call took: %v\n", duration)
}
// Store for observability
ctx.SetValue(schemas.BifrostContextKey("multi-mcp-duration"), duration)
}
return resp, bifrostErr, nil
}
// ============================================================================
// ObservabilityPlugin Interface
// ============================================================================
// Inject receives completed traces for forwarding to observability backends
func Inject(ctx context.Context, trace *schemas.Trace) error {
if !pluginConfig.EnableObservability {
return nil
}
if pluginConfig.EnableLogging {
fmt.Println("[Multi-Interface Plugin] Inject called - sending trace to observability backend")
}
// Example: Format trace as JSON
traceJSON, err := json.MarshalIndent(trace, "", " ")
if err != nil {
fmt.Printf("[Multi-Interface Plugin] Failed to marshal trace: %v\n", err)
return err
}
// Example: Log the trace (in production, send to OTEL, Datadog, etc.)
if pluginConfig.EnableLogging {
fmt.Printf("[Multi-Interface Plugin] Trace data:\n%s\n", string(traceJSON))
}
// In production, you would send this to your observability backend here
// Example: sendToDatadog(traceJSON)
// Example: sendToOTEL(trace)
return nil
}
// ============================================================================
// Cleanup
// ============================================================================
// Cleanup is called when the plugin is unloaded (required)
func Cleanup() error {
uptime := time.Since(startTime)
if pluginConfig.TrackRequests {
fmt.Printf("[Multi-Interface Plugin] Cleanup called - processed %d requests over %v\n",
requestCount, uptime)
} else {
fmt.Printf("[Multi-Interface Plugin] Cleanup called - uptime: %v\n", uptime)
}
return nil
}