diff --git a/internal/api/modules/amp/amp.go b/internal/api/modules/amp/amp.go index 78e7aa50..2bd83865 100644 --- a/internal/api/modules/amp/amp.go +++ b/internal/api/modules/amp/amp.go @@ -190,6 +190,7 @@ func (m *AmpModule) OnConfigUpdated(cfg *config.Config) error { // If API key changed, invalidate the cache if m.secretSource != nil { if ms, ok := m.secretSource.(*MultiSourceSecret); ok { + ms.UpdateExplicitKey(settings.UpstreamAPIKey) ms.InvalidateCache() log.Debug("amp secret cache invalidated due to config update") } diff --git a/internal/api/modules/amp/secret.go b/internal/api/modules/amp/secret.go index a4af1414..a7ebf3cb 100644 --- a/internal/api/modules/amp/secret.go +++ b/internal/api/modules/amp/secret.go @@ -139,6 +139,17 @@ func (s *MultiSourceSecret) InvalidateCache() { s.cache = nil } +// UpdateExplicitKey refreshes the config-provided key and clears cache. +func (s *MultiSourceSecret) UpdateExplicitKey(key string) { + if s == nil { + return + } + s.mu.Lock() + s.explicitKey = strings.TrimSpace(key) + s.cache = nil + s.mu.Unlock() +} + // StaticSecretSource returns a fixed API key (for testing) type StaticSecretSource struct { key string diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index c582d1e3..1f4f9043 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -570,6 +570,35 @@ func summarizeExcludedModels(list []string) excludedModelsSummary { } } +type ampModelMappingsSummary struct { + hash string + count int +} + +func summarizeAmpModelMappings(mappings []config.AmpModelMapping) ampModelMappingsSummary { + if len(mappings) == 0 { + return ampModelMappingsSummary{} + } + entries := make([]string, 0, len(mappings)) + for _, mapping := range mappings { + from := strings.TrimSpace(mapping.From) + to := strings.TrimSpace(mapping.To) + if from == "" && to == "" { + continue + } + entries = append(entries, from+"->"+to) + } + if len(entries) == 0 { + return ampModelMappingsSummary{} + } + sort.Strings(entries) + sum := sha256.Sum256([]byte(strings.Join(entries, "|"))) + return ampModelMappingsSummary{ + hash: hex.EncodeToString(sum[:]), + count: len(entries), + } +} + func summarizeOAuthExcludedModels(entries map[string][]string) map[string]excludedModelsSummary { if len(entries) == 0 { return nil @@ -1762,6 +1791,31 @@ func buildConfigChangeDetails(oldCfg, newCfg *config.Config) []string { } } + // AmpCode settings (redacted where needed) + oldAmpURL := strings.TrimSpace(oldCfg.AmpCode.UpstreamURL) + newAmpURL := strings.TrimSpace(newCfg.AmpCode.UpstreamURL) + if oldAmpURL != newAmpURL { + changes = append(changes, fmt.Sprintf("ampcode.upstream-url: %s -> %s", oldAmpURL, newAmpURL)) + } + oldAmpKey := strings.TrimSpace(oldCfg.AmpCode.UpstreamAPIKey) + newAmpKey := strings.TrimSpace(newCfg.AmpCode.UpstreamAPIKey) + switch { + case oldAmpKey == "" && newAmpKey != "": + changes = append(changes, "ampcode.upstream-api-key: added") + case oldAmpKey != "" && newAmpKey == "": + changes = append(changes, "ampcode.upstream-api-key: removed") + case oldAmpKey != newAmpKey: + changes = append(changes, "ampcode.upstream-api-key: updated") + } + if oldCfg.AmpCode.RestrictManagementToLocalhost != newCfg.AmpCode.RestrictManagementToLocalhost { + changes = append(changes, fmt.Sprintf("ampcode.restrict-management-to-localhost: %t -> %t", oldCfg.AmpCode.RestrictManagementToLocalhost, newCfg.AmpCode.RestrictManagementToLocalhost)) + } + oldMappings := summarizeAmpModelMappings(oldCfg.AmpCode.ModelMappings) + newMappings := summarizeAmpModelMappings(newCfg.AmpCode.ModelMappings) + if oldMappings.hash != newMappings.hash { + changes = append(changes, fmt.Sprintf("ampcode.model-mappings: updated (%d -> %d entries)", oldMappings.count, newMappings.count)) + } + if entries, _ := diffOAuthExcludedModelChanges(oldCfg.OAuthExcludedModels, newCfg.OAuthExcludedModels); len(entries) > 0 { changes = append(changes, entries...) }