refactor(config): remove deprecated legacy API key fields

This commit is contained in:
hkfires
2025-12-03 11:01:56 +08:00
parent bd1678457b
commit 06c0d2bab2
5 changed files with 136 additions and 143 deletions

View File

@@ -17,7 +17,6 @@ func (h *Handler) GetConfig(c *gin.Context) {
return return
} }
cfgCopy := *h.cfg cfgCopy := *h.cfg
cfgCopy.GlAPIKey = geminiKeyStringsFromConfig(h.cfg)
c.JSON(200, &cfgCopy) c.JSON(200, &cfgCopy)
} }

View File

@@ -147,7 +147,6 @@ func (h *Handler) applyLegacyKeys(keys []string) {
} }
} }
h.cfg.GeminiKey = newList h.cfg.GeminiKey = newList
h.cfg.GlAPIKey = sanitized
h.cfg.SanitizeGeminiKeys() h.cfg.SanitizeGeminiKeys()
} }
@@ -409,15 +408,14 @@ func (h *Handler) PutOpenAICompat(c *gin.Context) {
} }
arr = obj.Items arr = obj.Items
} }
arr = migrateLegacyOpenAICompatibilityKeys(arr)
// Filter out providers with empty base-url -> remove provider entirely
filtered := make([]config.OpenAICompatibility, 0, len(arr)) filtered := make([]config.OpenAICompatibility, 0, len(arr))
for i := range arr { for i := range arr {
normalizeOpenAICompatibilityEntry(&arr[i])
if strings.TrimSpace(arr[i].BaseURL) != "" { if strings.TrimSpace(arr[i].BaseURL) != "" {
filtered = append(filtered, arr[i]) filtered = append(filtered, arr[i])
} }
} }
h.cfg.OpenAICompatibility = migrateLegacyOpenAICompatibilityKeys(filtered) h.cfg.OpenAICompatibility = filtered
h.cfg.SanitizeOpenAICompatibility() h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
} }
@@ -431,7 +429,6 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) {
c.JSON(400, gin.H{"error": "invalid body"}) c.JSON(400, gin.H{"error": "invalid body"})
return return
} }
h.cfg.OpenAICompatibility = migrateLegacyOpenAICompatibilityKeys(h.cfg.OpenAICompatibility)
normalizeOpenAICompatibilityEntry(body.Value) normalizeOpenAICompatibilityEntry(body.Value)
// If base-url becomes empty, delete the provider instead of updating // If base-url becomes empty, delete the provider instead of updating
if strings.TrimSpace(body.Value.BaseURL) == "" { if strings.TrimSpace(body.Value.BaseURL) == "" {
@@ -731,28 +728,6 @@ func normalizeOpenAICompatibilityEntry(entry *config.OpenAICompatibility) {
existing[trimmed] = struct{}{} existing[trimmed] = struct{}{}
} }
} }
if len(entry.APIKeys) == 0 {
return
}
for _, legacyKey := range entry.APIKeys {
trimmed := strings.TrimSpace(legacyKey)
if trimmed == "" {
continue
}
if _, ok := existing[trimmed]; ok {
continue
}
entry.APIKeyEntries = append(entry.APIKeyEntries, config.OpenAICompatibilityAPIKey{APIKey: trimmed})
existing[trimmed] = struct{}{}
}
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 { func normalizedOpenAICompatibilityEntries(entries []config.OpenAICompatibility) []config.OpenAICompatibility {
@@ -765,9 +740,6 @@ func normalizedOpenAICompatibilityEntries(entries []config.OpenAICompatibility)
if len(copyEntry.APIKeyEntries) > 0 { if len(copyEntry.APIKeyEntries) > 0 {
copyEntry.APIKeyEntries = append([]config.OpenAICompatibilityAPIKey(nil), copyEntry.APIKeyEntries...) copyEntry.APIKeyEntries = append([]config.OpenAICompatibilityAPIKey(nil), copyEntry.APIKeyEntries...)
} }
if len(copyEntry.APIKeys) > 0 {
copyEntry.APIKeys = append([]string(nil), copyEntry.APIKeys...)
}
normalizeOpenAICompatibilityEntry(&copyEntry) normalizeOpenAICompatibilityEntry(&copyEntry)
out[i] = copyEntry out[i] = copyEntry
} }

View File

@@ -938,11 +938,7 @@ func (s *Server) UpdateClients(cfg *config.Config) {
openAICompatCount := 0 openAICompatCount := 0
for i := range cfg.OpenAICompatibility { for i := range cfg.OpenAICompatibility {
entry := cfg.OpenAICompatibility[i] entry := cfg.OpenAICompatibility[i]
if len(entry.APIKeyEntries) > 0 { openAICompatCount += len(entry.APIKeyEntries)
openAICompatCount += len(entry.APIKeyEntries)
continue
}
openAICompatCount += len(entry.APIKeys)
} }
total := authFiles + geminiAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + vertexAICompatCount + openAICompatCount total := authFiles + geminiAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + vertexAICompatCount + openAICompatCount

View File

@@ -58,9 +58,6 @@ type Config struct {
// GeminiKey defines Gemini API key configurations with optional routing overrides. // GeminiKey defines Gemini API key configurations with optional routing overrides.
GeminiKey []GeminiKey `yaml:"gemini-api-key" json:"gemini-api-key"` GeminiKey []GeminiKey `yaml:"gemini-api-key" json:"gemini-api-key"`
// GlAPIKey exposes the legacy generative language API key list for backward compatibility.
GlAPIKey []string `yaml:"generative-language-api-key" json:"generative-language-api-key"`
// Codex defines a list of Codex API key configurations as specified in the YAML configuration file. // Codex defines a list of Codex API key configurations as specified in the YAML configuration file.
CodexKey []CodexKey `yaml:"codex-api-key" json:"codex-api-key"` CodexKey []CodexKey `yaml:"codex-api-key" json:"codex-api-key"`
@@ -170,6 +167,17 @@ type PayloadModelRule struct {
Protocol string `yaml:"protocol" json:"protocol"` Protocol string `yaml:"protocol" json:"protocol"`
} }
type legacyConfigData struct {
LegacyGeminiKeys []string `yaml:"generative-language-api-key"`
OpenAICompat []legacyOpenAICompatibility `yaml:"openai-compatibility"`
}
type legacyOpenAICompatibility struct {
Name string `yaml:"name"`
BaseURL string `yaml:"base-url"`
APIKeys []string `yaml:"api-keys"`
}
// ClaudeKey represents the configuration for a Claude API key, // ClaudeKey represents the configuration for a Claude API key,
// including the API key itself and an optional base URL for the API endpoint. // including the API key itself and an optional base URL for the API endpoint.
type ClaudeKey struct { type ClaudeKey struct {
@@ -250,10 +258,6 @@ type OpenAICompatibility struct {
// BaseURL is the base URL for the external OpenAI-compatible API endpoint. // BaseURL is the base URL for the external OpenAI-compatible API endpoint.
BaseURL string `yaml:"base-url" json:"base-url"` BaseURL string `yaml:"base-url" json:"base-url"`
// APIKeys are the authentication keys for accessing the external API services.
// Deprecated: Use APIKeyEntries instead to support per-key proxy configuration.
APIKeys []string `yaml:"api-keys,omitempty" json:"api-keys,omitempty"`
// APIKeyEntries defines API keys with optional per-key proxy configuration. // APIKeyEntries defines API keys with optional per-key proxy configuration.
APIKeyEntries []OpenAICompatibilityAPIKey `yaml:"api-key-entries,omitempty" json:"api-key-entries,omitempty"` APIKeyEntries []OpenAICompatibilityAPIKey `yaml:"api-key-entries,omitempty" json:"api-key-entries,omitempty"`
@@ -333,6 +337,12 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
return nil, fmt.Errorf("failed to parse config file: %w", err) return nil, fmt.Errorf("failed to parse config file: %w", err)
} }
var legacy legacyConfigData
if errLegacy := yaml.Unmarshal(data, &legacy); errLegacy == nil {
cfg.migrateLegacyGeminiKeys(legacy.LegacyGeminiKeys)
cfg.migrateLegacyOpenAICompatibilityKeys(legacy.OpenAICompat)
}
// Hash remote management key if plaintext is detected (nested) // Hash remote management key if plaintext is detected (nested)
// We consider a value to be already hashed if it looks like a bcrypt hash ($2a$, $2b$, or $2y$ prefix). // We consider a value to be already hashed if it looks like a bcrypt hash ($2a$, $2b$, or $2y$ prefix).
if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) { if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) {
@@ -451,22 +461,94 @@ func (cfg *Config) SanitizeGeminiKeys() {
out = append(out, entry) out = append(out, entry)
} }
cfg.GeminiKey = out cfg.GeminiKey = out
}
if len(cfg.GlAPIKey) > 0 { func (cfg *Config) migrateLegacyGeminiKeys(legacy []string) {
for _, raw := range cfg.GlAPIKey { if cfg == nil || len(legacy) == 0 {
key := strings.TrimSpace(raw) return
if key == "" { }
continue seen := make(map[string]struct{}, len(cfg.GeminiKey))
} for i := range cfg.GeminiKey {
if _, exists := seen[key]; exists { key := strings.TrimSpace(cfg.GeminiKey[i].APIKey)
continue if key == "" {
} continue
cfg.GeminiKey = append(cfg.GeminiKey, GeminiKey{APIKey: key}) }
seen[key] = struct{}{} seen[key] = struct{}{}
}
for _, raw := range legacy {
key := strings.TrimSpace(raw)
if key == "" {
continue
}
if _, exists := seen[key]; exists {
continue
}
cfg.GeminiKey = append(cfg.GeminiKey, GeminiKey{APIKey: key})
seen[key] = struct{}{}
}
}
func (cfg *Config) migrateLegacyOpenAICompatibilityKeys(legacy []legacyOpenAICompatibility) {
if cfg == nil || len(cfg.OpenAICompatibility) == 0 || len(legacy) == 0 {
return
}
lookup := make(map[string]*OpenAICompatibility, len(cfg.OpenAICompatibility))
for i := range cfg.OpenAICompatibility {
if key := legacyOpenAICompatKey(cfg.OpenAICompatibility[i].Name, cfg.OpenAICompatibility[i].BaseURL); key != "" {
lookup[key] = &cfg.OpenAICompatibility[i]
} }
} }
for _, legacyEntry := range legacy {
if len(legacyEntry.APIKeys) == 0 {
continue
}
key := legacyOpenAICompatKey(legacyEntry.Name, legacyEntry.BaseURL)
if key == "" {
continue
}
target := lookup[key]
if target == nil {
continue
}
mergeLegacyOpenAICompatAPIKeys(target, legacyEntry.APIKeys)
}
}
cfg.GlAPIKey = nil func mergeLegacyOpenAICompatAPIKeys(entry *OpenAICompatibility, keys []string) {
if entry == nil || len(keys) == 0 {
return
}
existing := make(map[string]struct{}, len(entry.APIKeyEntries))
for i := range entry.APIKeyEntries {
key := strings.TrimSpace(entry.APIKeyEntries[i].APIKey)
if key == "" {
continue
}
existing[key] = struct{}{}
}
for _, raw := range keys {
key := strings.TrimSpace(raw)
if key == "" {
continue
}
if _, ok := existing[key]; ok {
continue
}
entry.APIKeyEntries = append(entry.APIKeyEntries, OpenAICompatibilityAPIKey{APIKey: key})
existing[key] = struct{}{}
}
}
func legacyOpenAICompatKey(name, baseURL string) string {
trimmedName := strings.ToLower(strings.TrimSpace(name))
if trimmedName != "" {
return "name:" + trimmedName
}
trimmedBase := strings.ToLower(strings.TrimSpace(baseURL))
if trimmedBase != "" {
return "base:" + trimmedBase
}
return ""
} }
func syncInlineAccessProvider(cfg *Config) { func syncInlineAccessProvider(cfg *Config) {
@@ -605,6 +687,7 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error {
// Remove deprecated auth block before merging to avoid persisting it again. // Remove deprecated auth block before merging to avoid persisting it again.
removeMapKey(original.Content[0], "auth") removeMapKey(original.Content[0], "auth")
removeLegacyOpenAICompatAPIKeys(original.Content[0]) removeLegacyOpenAICompatAPIKeys(original.Content[0])
removeMapKey(original.Content[0], "generative-language-api-key")
pruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], "oauth-excluded-models") pruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], "oauth-excluded-models")
// Merge generated into original in-place, preserving comments/order of existing nodes. // Merge generated into original in-place, preserving comments/order of existing nodes.

View File

@@ -1162,71 +1162,37 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
// Handle new APIKeyEntries format (preferred) // Handle new APIKeyEntries format (preferred)
createdEntries := 0 createdEntries := 0
if len(compat.APIKeyEntries) > 0 { for j := range compat.APIKeyEntries {
for j := range compat.APIKeyEntries { entry := &compat.APIKeyEntries[j]
entry := &compat.APIKeyEntries[j] key := strings.TrimSpace(entry.APIKey)
key := strings.TrimSpace(entry.APIKey) proxyURL := strings.TrimSpace(entry.ProxyURL)
proxyURL := strings.TrimSpace(entry.ProxyURL) idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
idKind := fmt.Sprintf("openai-compatibility:%s", providerName) id, token := idGen.next(idKind, key, base, proxyURL)
id, token := idGen.next(idKind, key, base, proxyURL) attrs := map[string]string{
attrs := map[string]string{ "source": fmt.Sprintf("config:%s[%s]", providerName, token),
"source": fmt.Sprintf("config:%s[%s]", providerName, token), "base_url": base,
"base_url": base, "compat_name": compat.Name,
"compat_name": compat.Name, "provider_key": providerName,
"provider_key": providerName,
}
if key != "" {
attrs["api_key"] = key
}
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
attrs["models_hash"] = hash
}
addConfigHeadersToAttrs(compat.Headers, attrs)
a := &coreauth.Auth{
ID: id,
Provider: providerName,
Label: compat.Name,
Status: coreauth.StatusActive,
ProxyURL: proxyURL,
Attributes: attrs,
CreatedAt: now,
UpdatedAt: now,
}
out = append(out, a)
createdEntries++
} }
} else { if key != "" {
// Handle legacy APIKeys format for backward compatibility
for j := range compat.APIKeys {
key := strings.TrimSpace(compat.APIKeys[j])
if key == "" {
continue
}
idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
id, token := idGen.next(idKind, key, base)
attrs := map[string]string{
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
"base_url": base,
"compat_name": compat.Name,
"provider_key": providerName,
}
attrs["api_key"] = key attrs["api_key"] = key
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
attrs["models_hash"] = hash
}
addConfigHeadersToAttrs(compat.Headers, attrs)
a := &coreauth.Auth{
ID: id,
Provider: providerName,
Label: compat.Name,
Status: coreauth.StatusActive,
Attributes: attrs,
CreatedAt: now,
UpdatedAt: now,
}
out = append(out, a)
createdEntries++
} }
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
attrs["models_hash"] = hash
}
addConfigHeadersToAttrs(compat.Headers, attrs)
a := &coreauth.Auth{
ID: id,
Provider: providerName,
Label: compat.Name,
Status: coreauth.StatusActive,
ProxyURL: proxyURL,
Attributes: attrs,
CreatedAt: now,
UpdatedAt: now,
}
out = append(out, a)
createdEntries++
} }
if createdEntries == 0 { if createdEntries == 0 {
idKind := fmt.Sprintf("openai-compatibility:%s", providerName) idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
@@ -1530,12 +1496,7 @@ func BuildAPIKeyClients(cfg *config.Config) (int, int, int, int, int) {
if len(cfg.OpenAICompatibility) > 0 { if len(cfg.OpenAICompatibility) > 0 {
// Do not construct legacy clients for OpenAI-compat providers; these are handled by the stateless executor. // Do not construct legacy clients for OpenAI-compat providers; these are handled by the stateless executor.
for _, compatConfig := range cfg.OpenAICompatibility { for _, compatConfig := range cfg.OpenAICompatibility {
// Count from new APIKeyEntries format if present, otherwise fall back to legacy APIKeys openAICompatCount += len(compatConfig.APIKeyEntries)
if len(compatConfig.APIKeyEntries) > 0 {
openAICompatCount += len(compatConfig.APIKeyEntries)
} else {
openAICompatCount += len(compatConfig.APIKeys)
}
} }
} }
return geminiAPIKeyCount, vertexCompatAPIKeyCount, claudeAPIKeyCount, codexAPIKeyCount, openAICompatCount return geminiAPIKeyCount, vertexCompatAPIKeyCount, claudeAPIKeyCount, codexAPIKeyCount, openAICompatCount
@@ -1612,24 +1573,9 @@ func describeOpenAICompatibilityUpdate(oldEntry, newEntry config.OpenAICompatibi
} }
func countAPIKeys(entry config.OpenAICompatibility) int { func countAPIKeys(entry config.OpenAICompatibility) int {
// Prefer new APIKeyEntries format
if len(entry.APIKeyEntries) > 0 {
count := 0
for _, keyEntry := range entry.APIKeyEntries {
if strings.TrimSpace(keyEntry.APIKey) != "" {
count++
}
}
return count
}
// Fall back to legacy APIKeys format
return countNonEmptyStrings(entry.APIKeys)
}
func countNonEmptyStrings(values []string) int {
count := 0 count := 0
for _, value := range values { for _, keyEntry := range entry.APIKeyEntries {
if strings.TrimSpace(value) != "" { if strings.TrimSpace(keyEntry.APIKey) != "" {
count++ count++
} }
} }
@@ -1754,9 +1700,6 @@ func buildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
changes = append(changes, fmt.Sprintf("gemini[%d].excluded-models: updated (%d -> %d entries)", i, oldExcluded.count, newExcluded.count)) changes = append(changes, fmt.Sprintf("gemini[%d].excluded-models: updated (%d -> %d entries)", i, oldExcluded.count, newExcluded.count))
} }
} }
if !reflect.DeepEqual(trimStrings(oldCfg.GlAPIKey), trimStrings(newCfg.GlAPIKey)) {
changes = append(changes, "generative-language-api-key: values updated (legacy view, redacted)")
}
} }
// Claude keys (do not print key material) // Claude keys (do not print key material)