diff --git a/internal/api/handlers/management/config_lists.go b/internal/api/handlers/management/config_lists.go index 04afca01..b4b43b0f 100644 --- a/internal/api/handlers/management/config_lists.go +++ b/internal/api/handlers/management/config_lists.go @@ -408,9 +408,7 @@ func (h *Handler) PutOpenAICompat(c *gin.Context) { } arr = obj.Items } - for i := range arr { - normalizeOpenAICompatibilityEntry(&arr[i]) - } + arr = migrateLegacyOpenAICompatibilityKeys(arr) // Filter out providers with empty base-url -> remove provider entirely filtered := make([]config.OpenAICompatibility, 0, len(arr)) for i := range arr { @@ -418,7 +416,7 @@ func (h *Handler) PutOpenAICompat(c *gin.Context) { filtered = append(filtered, arr[i]) } } - h.cfg.OpenAICompatibility = filtered + h.cfg.OpenAICompatibility = migrateLegacyOpenAICompatibilityKeys(filtered) h.cfg.SanitizeOpenAICompatibility() h.persist(c) } @@ -432,6 +430,7 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) { c.JSON(400, gin.H{"error": "invalid body"}) return } + h.cfg.OpenAICompatibility = migrateLegacyOpenAICompatibilityKeys(h.cfg.OpenAICompatibility) normalizeOpenAICompatibilityEntry(body.Value) // If base-url becomes empty, delete the provider instead of updating if strings.TrimSpace(body.Value.BaseURL) == "" { @@ -661,6 +660,13 @@ func normalizeOpenAICompatibilityEntry(entry *config.OpenAICompatibility) { entry.APIKeys = nil } +func migrateLegacyOpenAICompatibilityKeys(entries []config.OpenAICompatibility) []config.OpenAICompatibility { + for i := range entries { + normalizeOpenAICompatibilityEntry(&entries[i]) + } + return entries +} + func normalizedOpenAICompatibilityEntries(entries []config.OpenAICompatibility) []config.OpenAICompatibility { if len(entries) == 0 { return nil diff --git a/internal/config/config.go b/internal/config/config.go index ee0da787..58d4b20c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -479,6 +479,7 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error { // Remove deprecated auth block before merging to avoid persisting it again. removeMapKey(original.Content[0], "auth") + removeLegacyOpenAICompatAPIKeys(original.Content[0]) // Merge generated into original in-place, preserving comments/order of existing nodes. mergeMappingPreserve(original.Content[0], generated.Content[0]) @@ -935,6 +936,25 @@ func removeMapKey(mapNode *yaml.Node, key string) { } } +func removeLegacyOpenAICompatAPIKeys(root *yaml.Node) { + if root == nil || root.Kind != yaml.MappingNode { + return + } + idx := findMapKeyIndex(root, "openai-compatibility") + if idx < 0 || idx+1 >= len(root.Content) { + return + } + seq := root.Content[idx+1] + if seq == nil || seq.Kind != yaml.SequenceNode { + return + } + for i := range seq.Content { + if seq.Content[i] != nil && seq.Content[i].Kind == yaml.MappingNode { + removeMapKey(seq.Content[i], "api-keys") + } + } +} + // normalizeCollectionNodeStyles forces YAML collections to use block notation, keeping // lists and maps readable. Empty sequences retain flow style ([]) so empty list markers // remain compact.