Files
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

209 lines
6.6 KiB
Go

package server
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/bytedance/sonic"
"github.com/maximhq/bifrost/core/schemas"
)
// GetDefaultConfigDir returns the OS-specific default configuration directory for Bifrost.
// This follows standard conventions:
// - Linux/macOS: ~/.config/bifrost
// - Windows: %APPDATA%\bifrost
// - If appDir is provided (non-empty), it returns that instead
func GetDefaultConfigDir(appDir string) string {
// If appDir is provided, use it directly
if appDir != "" {
return appDir
}
// Get OS-specific config directory
var configDir string
switch runtime.GOOS {
case "windows":
// Windows: %APPDATA%\bifrost
if appData := os.Getenv("APPDATA"); appData != "" {
configDir = filepath.Join(appData, "bifrost")
} else {
// Fallback to user home directory
if homeDir, err := os.UserHomeDir(); err == nil {
configDir = filepath.Join(homeDir, "AppData", "Roaming", "bifrost")
}
}
default:
// Linux, macOS and other Unix-like systems: ~/.config/bifrost
if homeDir, err := os.UserHomeDir(); err == nil {
configDir = filepath.Join(homeDir, ".config", "bifrost")
}
}
// If we couldn't determine the config directory, fall back to current directory
if configDir == "" {
configDir = "./bifrost-data"
}
return configDir
}
// registerPluginWithStatus instantiates, registers, and updates status for a plugin (used by builtin plugins)
func (s *BifrostHTTPServer) registerPluginWithStatus(ctx context.Context, name string, path *string, config any, failOnError bool) error {
plugin, err := InstantiatePlugin(ctx, name, path, config, s.Config)
if err != nil {
logger.Error("failed to initialize %s plugin: %v", name, err)
// Use name since plugin may be nil when InstantiatePlugin returns an error
s.Config.UpdatePluginOverallStatus(name, name, schemas.PluginStatusError,
[]string{fmt.Sprintf("error initializing %s plugin: %v", name, err)}, []schemas.PluginType{})
if failOnError {
return err
}
return nil
}
// Ensure plugin is not nil before using it (defensive check)
if plugin == nil {
logger.Error("plugin %s instantiated but returned nil", name)
s.Config.UpdatePluginOverallStatus(name, name, schemas.PluginStatusError,
[]string{fmt.Sprintf("plugin %s instantiated but returned nil", name)}, []schemas.PluginType{})
if failOnError {
return fmt.Errorf("plugin %s instantiated but returned nil", name)
}
return nil
}
s.Config.ReloadPlugin(plugin)
s.Config.UpdatePluginOverallStatus(name, name, schemas.PluginStatusActive,
[]string{fmt.Sprintf("%s plugin initialized successfully", name)}, InferPluginTypes(plugin))
return nil
}
// CollectObservabilityPlugins gathers all loaded plugins that implement ObservabilityPlugin interface
func (s *BifrostHTTPServer) CollectObservabilityPlugins() []schemas.ObservabilityPlugin {
var observabilityPlugins []schemas.ObservabilityPlugin
// Check LLM plugins
for _, plugin := range s.Config.GetLoadedLLMPlugins() {
if observabilityPlugin, ok := plugin.(schemas.ObservabilityPlugin); ok {
observabilityPlugins = append(observabilityPlugins, observabilityPlugin)
}
}
// Check MCP plugins
for _, plugin := range s.Config.GetLoadedMCPPlugins() {
if observabilityPlugin, ok := plugin.(schemas.ObservabilityPlugin); ok {
observabilityPlugins = append(observabilityPlugins, observabilityPlugin)
}
}
return observabilityPlugins
}
// MarshalPluginConfig marshals the plugin configuration
func MarshalPluginConfig[T any](source any) (*T, error) {
// If its a *T, then we will confirm
if config, ok := source.(*T); ok {
return config, nil
}
// Initialize a new instance for unmarshaling
config := new(T)
// If its a map[string]any, then we will JSON parse and confirm
if configMap, ok := source.(map[string]any); ok {
configString, err := sonic.Marshal(configMap)
if err != nil {
return nil, err
}
if err := sonic.Unmarshal([]byte(configString), config); err != nil {
return nil, err
}
return config, nil
}
// If its a string, then we will JSON parse and confirm
if configStr, ok := source.(string); ok {
if err := sonic.Unmarshal([]byte(configStr), config); err != nil {
return nil, err
}
return config, nil
}
return nil, fmt.Errorf("invalid config type")
}
// updateKeyStatus updates the model discovery status for keys or providers based on key statuses.
// For keyed providers: updates individual key status
// For keyless providers: updates provider-level status
func (s *BifrostHTTPServer) updateKeyStatus(
ctx context.Context,
keyStatuses []schemas.KeyStatus,
) {
if s.Config == nil || s.Config.ConfigStore == nil || len(keyStatuses) == 0 {
return
}
// Update each key/provider status individually
for _, ks := range keyStatuses {
errorMsg := ""
if ks.Error != nil && ks.Error.Error != nil {
errorMsg = ks.Error.Error.Message
}
if err := s.Config.ConfigStore.UpdateStatus(ctx, ks.Provider, ks.KeyID, string(ks.Status), errorMsg); err != nil {
target := ks.KeyID
if target == "" {
target = string(ks.Provider)
}
logger.Error("failed to update model discovery status for %s: %v", target, err)
continue // Skip in-memory update if DB update failed
}
s.Config.Mu.Lock()
providerConfig, exists := s.Config.Providers[ks.Provider]
if !exists {
s.Config.Mu.Unlock()
logger.Warn("provider %s not found in memory during status update", ks.Provider)
continue
}
isKeylessProvider := providerConfig.CustomProviderConfig != nil && providerConfig.CustomProviderConfig.IsKeyLess
if ks.KeyID == "" {
if !isKeylessProvider {
logger.Warn("received provider-level status update for keyed provider %s; skipping in-memory update", ks.Provider)
s.Config.Mu.Unlock()
continue
}
providerConfig.Status = string(ks.Status)
providerConfig.Description = errorMsg
s.Config.Providers[ks.Provider] = providerConfig
logger.Debug("updated in-memory status for keyless provider %s", ks.Provider)
s.Config.Mu.Unlock()
continue
}
// Find and update the specific key in the Keys slice
updated := false
for i := range providerConfig.Keys {
if providerConfig.Keys[i].ID == ks.KeyID {
// Update Status and Description fields
providerConfig.Keys[i].Status = ks.Status
providerConfig.Keys[i].Description = errorMsg
updated = true
break
}
}
if updated {
// Write the modified config back to the map
s.Config.Providers[ks.Provider] = providerConfig
logger.Debug("updated in-memory status for key %s of provider %s", ks.KeyID, ks.Provider)
} else {
logger.Warn("key %s not found in provider %s during in-memory update", ks.KeyID, ks.Provider)
}
s.Config.Mu.Unlock()
}
}