mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 20:30:51 +08:00
fix: enable hot reload for amp-model-mappings config
- Store ampModule in Server struct to access it during config updates - Call ampModule.OnConfigUpdated() in UpdateClients() for hot reload - Watch config directory instead of file to handle atomic saves (vim, VSCode, etc.) - Improve config file event detection with basename matching - Add diagnostic logging for config reload tracing
This commit is contained in:
@@ -166,7 +166,10 @@ func (m *AmpModule) getAuthMiddleware(ctx modules.Context) gin.HandlerFunc {
|
|||||||
func (m *AmpModule) OnConfigUpdated(cfg *config.Config) error {
|
func (m *AmpModule) OnConfigUpdated(cfg *config.Config) error {
|
||||||
// Update model mappings (hot-reload supported)
|
// Update model mappings (hot-reload supported)
|
||||||
if m.modelMapper != nil {
|
if m.modelMapper != nil {
|
||||||
|
log.Infof("amp config updated: reloading %d model mapping(s)", len(cfg.AmpModelMappings))
|
||||||
m.modelMapper.UpdateMappings(cfg.AmpModelMappings)
|
m.modelMapper.UpdateMappings(cfg.AmpModelMappings)
|
||||||
|
} else {
|
||||||
|
log.Warnf("amp model mapper not initialized, skipping model mapping update")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.enabled {
|
if !m.enabled {
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ type Server struct {
|
|||||||
// management handler
|
// management handler
|
||||||
mgmt *managementHandlers.Handler
|
mgmt *managementHandlers.Handler
|
||||||
|
|
||||||
|
// ampModule is the Amp routing module for model mapping hot-reload
|
||||||
|
ampModule *ampmodule.AmpModule
|
||||||
|
|
||||||
// managementRoutesRegistered tracks whether the management routes have been attached to the engine.
|
// managementRoutesRegistered tracks whether the management routes have been attached to the engine.
|
||||||
managementRoutesRegistered atomic.Bool
|
managementRoutesRegistered atomic.Bool
|
||||||
// managementRoutesEnabled controls whether management endpoints serve real handlers.
|
// managementRoutesEnabled controls whether management endpoints serve real handlers.
|
||||||
@@ -268,14 +271,14 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
|
|||||||
s.setupRoutes()
|
s.setupRoutes()
|
||||||
|
|
||||||
// Register Amp module using V2 interface with Context
|
// Register Amp module using V2 interface with Context
|
||||||
ampModule := ampmodule.NewLegacy(accessManager, AuthMiddleware(accessManager))
|
s.ampModule = ampmodule.NewLegacy(accessManager, AuthMiddleware(accessManager))
|
||||||
ctx := modules.Context{
|
ctx := modules.Context{
|
||||||
Engine: engine,
|
Engine: engine,
|
||||||
BaseHandler: s.handlers,
|
BaseHandler: s.handlers,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
AuthMiddleware: AuthMiddleware(accessManager),
|
AuthMiddleware: AuthMiddleware(accessManager),
|
||||||
}
|
}
|
||||||
if err := modules.RegisterModule(ctx, ampModule); err != nil {
|
if err := modules.RegisterModule(ctx, s.ampModule); err != nil {
|
||||||
log.Errorf("Failed to register Amp module: %v", err)
|
log.Errorf("Failed to register Amp module: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,6 +919,16 @@ func (s *Server) UpdateClients(cfg *config.Config) {
|
|||||||
s.mgmt.SetAuthManager(s.handlers.AuthManager)
|
s.mgmt.SetAuthManager(s.handlers.AuthManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify Amp module of config changes (for model mapping hot-reload)
|
||||||
|
if s.ampModule != nil {
|
||||||
|
log.Debugf("triggering amp module config update")
|
||||||
|
if err := s.ampModule.OnConfigUpdated(cfg); err != nil {
|
||||||
|
log.Errorf("failed to update Amp module config: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warnf("amp module is nil, skipping config update")
|
||||||
|
}
|
||||||
|
|
||||||
// Count client sources from configuration and auth directory
|
// Count client sources from configuration and auth directory
|
||||||
authFiles := util.CountAuthFiles(cfg.AuthDir)
|
authFiles := util.CountAuthFiles(cfg.AuthDir)
|
||||||
geminiAPIKeyCount := len(cfg.GeminiKey)
|
geminiAPIKeyCount := len(cfg.GeminiKey)
|
||||||
|
|||||||
@@ -162,12 +162,14 @@ func NewWatcher(configPath, authDir string, reloadCallback func(*config.Config))
|
|||||||
|
|
||||||
// Start begins watching the configuration file and authentication directory
|
// Start begins watching the configuration file and authentication directory
|
||||||
func (w *Watcher) Start(ctx context.Context) error {
|
func (w *Watcher) Start(ctx context.Context) error {
|
||||||
// Watch the config file
|
// Watch the config file's parent directory instead of the file itself.
|
||||||
if errAddConfig := w.watcher.Add(w.configPath); errAddConfig != nil {
|
// This handles editors that use atomic save (write to temp, then rename).
|
||||||
log.Errorf("failed to watch config file %s: %v", w.configPath, errAddConfig)
|
configDir := filepath.Dir(w.configPath)
|
||||||
|
if errAddConfig := w.watcher.Add(configDir); errAddConfig != nil {
|
||||||
|
log.Errorf("failed to watch config directory %s: %v", configDir, errAddConfig)
|
||||||
return errAddConfig
|
return errAddConfig
|
||||||
}
|
}
|
||||||
log.Debugf("watching config file: %s", w.configPath)
|
log.Debugf("watching config directory: %s (for file: %s)", configDir, filepath.Base(w.configPath))
|
||||||
|
|
||||||
// Watch the auth directory
|
// Watch the auth directory
|
||||||
if errAddAuthDir := w.watcher.Add(w.authDir); errAddAuthDir != nil {
|
if errAddAuthDir := w.watcher.Add(w.authDir); errAddAuthDir != nil {
|
||||||
@@ -700,7 +702,23 @@ func (w *Watcher) isKnownAuthFile(path string) bool {
|
|||||||
func (w *Watcher) handleEvent(event fsnotify.Event) {
|
func (w *Watcher) handleEvent(event fsnotify.Event) {
|
||||||
// Filter only relevant events: config file or auth-dir JSON files.
|
// Filter only relevant events: config file or auth-dir JSON files.
|
||||||
configOps := fsnotify.Write | fsnotify.Create | fsnotify.Rename
|
configOps := fsnotify.Write | fsnotify.Create | fsnotify.Rename
|
||||||
isConfigEvent := event.Name == w.configPath && event.Op&configOps != 0
|
// Check if this event is for our config file (handle both exact match and basename match for directory watching)
|
||||||
|
isConfigEvent := false
|
||||||
|
if event.Op&configOps != 0 {
|
||||||
|
// Exact path match
|
||||||
|
if event.Name == w.configPath {
|
||||||
|
isConfigEvent = true
|
||||||
|
} else {
|
||||||
|
// Check if basename matches and it's in the config directory (for atomic save detection)
|
||||||
|
configDir := filepath.Dir(w.configPath)
|
||||||
|
configBase := filepath.Base(w.configPath)
|
||||||
|
eventDir := filepath.Dir(event.Name)
|
||||||
|
eventBase := filepath.Base(event.Name)
|
||||||
|
if eventDir == configDir && eventBase == configBase {
|
||||||
|
isConfigEvent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
authOps := fsnotify.Create | fsnotify.Write | fsnotify.Remove | fsnotify.Rename
|
authOps := fsnotify.Create | fsnotify.Write | fsnotify.Remove | fsnotify.Rename
|
||||||
isAuthJSON := strings.HasPrefix(event.Name, w.authDir) && strings.HasSuffix(event.Name, ".json") && event.Op&authOps != 0
|
isAuthJSON := strings.HasPrefix(event.Name, w.authDir) && strings.HasSuffix(event.Name, ".json") && event.Op&authOps != 0
|
||||||
if !isConfigEvent && !isAuthJSON {
|
if !isConfigEvent && !isAuthJSON {
|
||||||
|
|||||||
Reference in New Issue
Block a user