diff --git a/internal/api/handlers/management/handler.go b/internal/api/handlers/management/handler.go index c5b6daa6..78fd505d 100644 --- a/internal/api/handlers/management/handler.go +++ b/internal/api/handlers/management/handler.go @@ -411,7 +411,13 @@ func (h *Handler) persistLocked(c *gin.Context) bool { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", err)}) return false } + snapshot := h.reloadSnapshotConfigLocked() c.JSON(http.StatusOK, gin.H{"status": "ok"}) + var reqCtx context.Context + if c != nil && c.Request != nil { + reqCtx = c.Request.Context() + } + h.reloadConfigAfterManagementSaveAsync(reqCtx, snapshot) return true } diff --git a/internal/watcher/config_reload.go b/internal/watcher/config_reload.go index 0471f8b3..92c38649 100644 --- a/internal/watcher/config_reload.go +++ b/internal/watcher/config_reload.go @@ -40,6 +40,14 @@ func (w *Watcher) scheduleConfigReload() { }) } +// ReloadConfigIfChanged runs the same config reload path used by filesystem events. +func (w *Watcher) ReloadConfigIfChanged() { + if w == nil { + return + } + w.reloadConfigIfChanged() +} + func (w *Watcher) reloadConfigIfChanged() { data, err := os.ReadFile(w.configPath) if err != nil { diff --git a/sdk/cliproxy/builder.go b/sdk/cliproxy/builder.go index 91c24913..24ac43c3 100644 --- a/sdk/cliproxy/builder.go +++ b/sdk/cliproxy/builder.go @@ -289,8 +289,8 @@ func (b *Builder) Build() (*Service, error) { service.serverOptions = append(service.serverOptions, api.WithPostAuthPersistHook(service.runtimeAuthSyncHook()), api.WithPluginHost(pluginHost), - api.WithConfigReloadHook(func(ctx context.Context, cfg *config.Config) { - service.applyConfigUpdate(cfg) + api.WithConfigReloadHook(func(_ context.Context, _ *config.Config) { + service.reloadConfigFromWatcher() }), ) return service, nil diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index bb5f08f0..ab09f91f 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -1097,6 +1097,14 @@ func (s *Service) tryRegisterPluginModelsForAuth(ctx context.Context, a *coreaut } func (s *Service) applyConfigUpdate(newCfg *config.Config) { + s.applyConfigUpdateWithAuthSynthesis(newCfg, true) +} + +func (s *Service) applyWatcherConfigUpdate(newCfg *config.Config) { + s.applyConfigUpdateWithAuthSynthesis(newCfg, false) +} + +func (s *Service) applyConfigUpdateWithAuthSynthesis(newCfg *config.Config, synthesizeConfigAuths bool) { if s == nil { return } @@ -1190,10 +1198,19 @@ func (s *Service) applyConfigUpdate(newCfg *config.Config) { auths: auths, }) ctx := coreauth.WithSkipPersist(context.Background()) - s.registerConfigAPIKeyAuths(ctx, newCfg) + if synthesizeConfigAuths { + s.registerConfigAPIKeyAuths(ctx, newCfg) + } s.syncPluginRuntime(ctx) } +func (s *Service) reloadConfigFromWatcher() bool { + if s == nil || s.watcher == nil { + return false + } + return s.watcher.ReloadConfigIfChanged() +} + func (s *Service) registerConfigAPIKeyAuths(ctx context.Context, cfg *config.Config) { if s == nil || s.coreManager == nil || cfg == nil { return @@ -1442,6 +1459,7 @@ func (s *Service) Run(ctx context.Context) error { if errLoad := s.coreManager.Load(ctx); errLoad != nil { log.Warnf("failed to load auth store: %v", errLoad) } + s.registerConfigAPIKeyAuths(coreauth.WithSkipPersist(ctx), s.cfg) } if !homeEnabled { @@ -1532,7 +1550,7 @@ func (s *Service) Run(ctx context.Context) error { if !homeEnabled { var watcherWrapper *WatcherWrapper - reloadCallback := func(newCfg *config.Config) { s.applyConfigUpdate(newCfg) } + reloadCallback := func(newCfg *config.Config) { s.applyWatcherConfigUpdate(newCfg) } watcherWrapper, errCreate := s.watcherFactory(s.configPath, s.cfg.AuthDir, reloadCallback) if errCreate != nil { diff --git a/sdk/cliproxy/types.go b/sdk/cliproxy/types.go index 3d6ae352..719e0309 100644 --- a/sdk/cliproxy/types.go +++ b/sdk/cliproxy/types.go @@ -97,6 +97,7 @@ type WatcherWrapper struct { dispatchRuntimeUpdate func(update watcher.AuthUpdate) bool dispatchPersistedAuth func(update watcher.AuthUpdate) bool setPluginAuthParser func(parser PluginAuthParser) + reloadConfigIfChanged func() } // Start proxies to the underlying watcher Start implementation. @@ -123,6 +124,15 @@ func (w *WatcherWrapper) SetConfig(cfg *config.Config) { w.setConfig(cfg) } +// ReloadConfigIfChanged asks the underlying watcher to reload config from disk. +func (w *WatcherWrapper) ReloadConfigIfChanged() bool { + if w == nil || w.reloadConfigIfChanged == nil { + return false + } + w.reloadConfigIfChanged() + return true +} + // SetPluginAuthParser updates the plugin auth parser used by the watcher. func (w *WatcherWrapper) SetPluginAuthParser(parser PluginAuthParser) { if w == nil || w.setPluginAuthParser == nil { diff --git a/sdk/cliproxy/watcher.go b/sdk/cliproxy/watcher.go index 865b2f95..886b5564 100644 --- a/sdk/cliproxy/watcher.go +++ b/sdk/cliproxy/watcher.go @@ -37,5 +37,8 @@ func defaultWatcherFactory(configPath, authDir string, reload func(*config.Confi setPluginAuthParser: func(parser PluginAuthParser) { w.SetPluginAuthParser(parser) }, + reloadConfigIfChanged: func() { + w.ReloadConfigIfChanged() + }, }, nil }