package synthesizer import ( "fmt" "strconv" "strings" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/diff" coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // ConfigSynthesizer generates Auth entries from configuration API keys. // It handles Gemini, Interactions, Claude, Codex, xAI, OpenAI-compat, and Vertex-compat providers. type ConfigSynthesizer struct{} // NewConfigSynthesizer creates a new ConfigSynthesizer instance. func NewConfigSynthesizer() *ConfigSynthesizer { return &ConfigSynthesizer{} } func addWeightToAttrs(weight *int, attrs map[string]string) { if weight == nil { return } normalized := *weight if normalized <= 0 { normalized = 0 } attrs[coreauth.AttributeWeight] = strconv.Itoa(normalized) } // Synthesize generates Auth entries from config API keys. func (s *ConfigSynthesizer) Synthesize(ctx *SynthesisContext) ([]*coreauth.Auth, error) { out := make([]*coreauth.Auth, 0, 32) if ctx == nil || ctx.Config == nil { return out, nil } if errValidate := ctx.Config.ValidateCredentialWeights(); errValidate != nil { return nil, fmt.Errorf("synthesize config API key auths: %w", errValidate) } // Gemini API Keys out = append(out, s.synthesizeGeminiKeys(ctx)...) // Native Interactions API Keys out = append(out, s.synthesizeInteractionsKeys(ctx)...) // Claude API Keys out = append(out, s.synthesizeClaudeKeys(ctx)...) // Codex API Keys out = append(out, s.synthesizeCodexKeys(ctx)...) // xAI API Keys out = append(out, s.synthesizeXAIKeys(ctx)...) // OpenAI-compat out = append(out, s.synthesizeOpenAICompat(ctx)...) // Vertex-compat out = append(out, s.synthesizeVertexCompat(ctx)...) return out, nil } // synthesizeGeminiKeys creates Auth entries for Gemini API keys. func (s *ConfigSynthesizer) synthesizeGeminiKeys(ctx *SynthesisContext) []*coreauth.Auth { return s.synthesizeGeminiKeyEntries(ctx, ctx.Config.GeminiKey, "gemini:apikey", "gemini", "gemini-apikey", constant.Gemini) } // synthesizeInteractionsKeys creates Auth entries for native Interactions API keys. func (s *ConfigSynthesizer) synthesizeInteractionsKeys(ctx *SynthesisContext) []*coreauth.Auth { return s.synthesizeGeminiKeyEntries(ctx, ctx.Config.InteractionsKey, "gemini-interactions:apikey", "interactions", "interactions-apikey", constant.GeminiInteractions) } func (s *ConfigSynthesizer) synthesizeGeminiKeyEntries(ctx *SynthesisContext, entries []config.GeminiKey, idKind, sourceName, label, provider string) []*coreauth.Auth { cfg := ctx.Config now := ctx.Now idGen := ctx.IDGenerator out := make([]*coreauth.Auth, 0, len(entries)) for i := range entries { entry := entries[i] key := strings.TrimSpace(entry.APIKey) base := strings.TrimSpace(entry.BaseURL) if key == "" && base == "" { continue } prefix := strings.TrimSpace(entry.Prefix) proxyURL := strings.TrimSpace(entry.ProxyURL) id, token := idGen.Next(idKind, key, base, proxyURL, prefix, config.FormatSortedHeaders(entry.Headers)) attrs := map[string]string{ "source": fmt.Sprintf("config:%s[%s]", sourceName, token), "config_index": strconv.Itoa(i), } if key != "" { attrs["api_key"] = key } metadata := map[string]any{} if entry.DisableCooling != nil { metadata["disable_cooling"] = *entry.DisableCooling } addRequestRetryToMetadata(entry.RequestRetry, metadata) addRequestScopedErrorsToMetadata(entry.RequestScopedErrors, metadata) if entry.Priority != 0 { attrs["priority"] = strconv.Itoa(entry.Priority) } addWeightToAttrs(entry.Weight, attrs) if base != "" { attrs["base_url"] = base } if hash := diff.ComputeGeminiModelsHash(entry.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(entry.Headers, attrs) a := &coreauth.Auth{ ID: id, Provider: provider, Label: label, Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, Metadata: metadata, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, entry.ExcludedModels, "apikey") if len(a.Metadata) == 0 { a.Metadata = nil } out = append(out, a) } return out } // synthesizeClaudeKeys creates Auth entries for Claude API keys. func (s *ConfigSynthesizer) synthesizeClaudeKeys(ctx *SynthesisContext) []*coreauth.Auth { cfg := ctx.Config now := ctx.Now idGen := ctx.IDGenerator out := make([]*coreauth.Auth, 0, len(cfg.ClaudeKey)) for i := range cfg.ClaudeKey { ck := cfg.ClaudeKey[i] key := strings.TrimSpace(ck.APIKey) base := strings.TrimSpace(ck.BaseURL) if key == "" && base == "" { continue } prefix := strings.TrimSpace(ck.Prefix) proxyURL := strings.TrimSpace(ck.ProxyURL) id, token := idGen.Next("claude:apikey", key, base, proxyURL, prefix, config.FormatSortedHeaders(ck.Headers)) attrs := map[string]string{ "source": fmt.Sprintf("config:claude[%s]", token), "config_index": strconv.Itoa(i), } if key != "" { attrs["api_key"] = key } metadata := map[string]any{} if ck.DisableCooling != nil { metadata["disable_cooling"] = *ck.DisableCooling } addRequestRetryToMetadata(ck.RequestRetry, metadata) addRequestScopedErrorsToMetadata(ck.RequestScopedErrors, metadata) if ck.Priority != 0 { attrs["priority"] = strconv.Itoa(ck.Priority) } addWeightToAttrs(ck.Weight, attrs) if base != "" { attrs["base_url"] = base } if ck.RebuildMidSystemMessage { attrs["rebuild_mid_system_message"] = "true" } if profile := strings.ToLower(strings.TrimSpace(ck.FingerprintProfile)); profile != "" { attrs["fingerprint_profile"] = profile } if hash := diff.ComputeClaudeModelsHash(ck.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(ck.Headers, attrs) a := &coreauth.Auth{ ID: id, Provider: "claude", Label: "claude-apikey", Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, Metadata: metadata, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, ck.ExcludedModels, "apikey") if len(a.Metadata) == 0 { a.Metadata = nil } out = append(out, a) } return out } // synthesizeCodexKeys creates Auth entries for Codex API keys. func (s *ConfigSynthesizer) synthesizeCodexKeys(ctx *SynthesisContext) []*coreauth.Auth { return s.synthesizeCodexStyleKeys(ctx, ctx.Config.CodexKey, "codex") } // synthesizeXAIKeys creates Auth entries for xAI API keys. func (s *ConfigSynthesizer) synthesizeXAIKeys(ctx *SynthesisContext) []*coreauth.Auth { return s.synthesizeCodexStyleKeys(ctx, ctx.Config.XAIKey, "xai") } func (s *ConfigSynthesizer) synthesizeCodexStyleKeys(ctx *SynthesisContext, entries []config.CodexKey, provider string) []*coreauth.Auth { cfg := ctx.Config now := ctx.Now idGen := ctx.IDGenerator out := make([]*coreauth.Auth, 0, len(entries)) for i := range entries { entry := entries[i] key := strings.TrimSpace(entry.APIKey) baseURL := strings.TrimSpace(entry.BaseURL) if key == "" && baseURL == "" { continue } prefix := strings.TrimSpace(entry.Prefix) proxyURL := strings.TrimSpace(entry.ProxyURL) id, token := idGen.Next(provider+":apikey", key, baseURL, proxyURL, prefix, config.FormatSortedHeaders(entry.Headers)) attrs := map[string]string{ "source": fmt.Sprintf("config:%s[%s]", provider, token), "config_index": strconv.Itoa(i), } if key != "" { attrs["api_key"] = key } metadata := map[string]any{} if entry.DisableCooling != nil { metadata["disable_cooling"] = *entry.DisableCooling } addRequestRetryToMetadata(entry.RequestRetry, metadata) addRequestScopedErrorsToMetadata(entry.RequestScopedErrors, metadata) if entry.Priority != 0 { attrs["priority"] = strconv.Itoa(entry.Priority) } addWeightToAttrs(entry.Weight, attrs) if baseURL != "" { attrs["base_url"] = baseURL } if entry.Websockets { attrs["websockets"] = "true" } if provider == "codex" && entry.AlphaSearch { attrs[coreauth.AttributeCodexAlphaSearch] = "true" } if hash := diff.ComputeCodexModelsHash(entry.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(entry.Headers, attrs) a := &coreauth.Auth{ ID: id, Provider: provider, Label: provider + "-apikey", Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: strings.TrimSpace(entry.ProxyURL), Attributes: attrs, Metadata: metadata, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, entry.ExcludedModels, "apikey") if len(a.Metadata) == 0 { a.Metadata = nil } out = append(out, a) } return out } // synthesizeOpenAICompat creates Auth entries for OpenAI-compatible providers. func (s *ConfigSynthesizer) synthesizeOpenAICompat(ctx *SynthesisContext) []*coreauth.Auth { cfg := ctx.Config now := ctx.Now idGen := ctx.IDGenerator out := make([]*coreauth.Auth, 0) for i := range cfg.OpenAICompatibility { compat := &cfg.OpenAICompatibility[i] if compat.Disabled { continue } prefix := strings.TrimSpace(compat.Prefix) providerName := strings.ToLower(strings.TrimSpace(compat.Name)) if providerName == "" { providerName = "openai-compatibility" } internalProviderKey := util.OpenAICompatibleProviderKey(providerName) base := strings.TrimSpace(compat.BaseURL) disableCooling := compat.DisableCooling // Handle new APIKeyEntries format (preferred) createdEntries := 0 for j := range compat.APIKeyEntries { entry := &compat.APIKeyEntries[j] key := strings.TrimSpace(entry.APIKey) proxyURL := strings.TrimSpace(entry.ProxyURL) idKind := fmt.Sprintf("openai-compatibility:%s", providerName) id, token := idGen.Next(idKind, key, base, proxyURL) attrs := map[string]string{ "source": fmt.Sprintf("config:%s[%s]", providerName, token), "base_url": base, "compat_name": compat.Name, "provider_key": internalProviderKey, "config_index": strconv.Itoa(i), } metadata := map[string]any{} if disableCooling != nil { metadata["disable_cooling"] = *disableCooling } addRequestRetryToMetadata(compat.RequestRetry, metadata) addRequestScopedErrorsToMetadata(compat.RequestScopedErrors, metadata) if compat.Priority != 0 { attrs["priority"] = strconv.Itoa(compat.Priority) } addWeightToAttrs(entry.Weight, attrs) if key != "" { attrs["api_key"] = key } if hash := diff.ComputeOpenAICompatModelsHash(compat.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(compat.Headers, attrs) a := &coreauth.Auth{ ID: id, Provider: internalProviderKey, Label: compat.Name, Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, Metadata: metadata, CreatedAt: now, UpdatedAt: now, } if len(a.Metadata) == 0 { a.Metadata = nil } out = append(out, a) createdEntries++ } // Fallback: create entry without API key if no APIKeyEntries if createdEntries == 0 { idKind := fmt.Sprintf("openai-compatibility:%s", providerName) id, token := idGen.Next(idKind, base) attrs := map[string]string{ "source": fmt.Sprintf("config:%s[%s]", providerName, token), "base_url": base, "compat_name": compat.Name, "provider_key": internalProviderKey, "config_index": strconv.Itoa(i), } metadata := map[string]any{} if disableCooling != nil { metadata["disable_cooling"] = *disableCooling } addRequestRetryToMetadata(compat.RequestRetry, metadata) addRequestScopedErrorsToMetadata(compat.RequestScopedErrors, metadata) if compat.Priority != 0 { attrs["priority"] = strconv.Itoa(compat.Priority) } if hash := diff.ComputeOpenAICompatModelsHash(compat.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(compat.Headers, attrs) a := &coreauth.Auth{ ID: id, Provider: internalProviderKey, Label: compat.Name, Prefix: prefix, Status: coreauth.StatusActive, Attributes: attrs, Metadata: metadata, CreatedAt: now, UpdatedAt: now, } if len(a.Metadata) == 0 { a.Metadata = nil } out = append(out, a) } } return out } // synthesizeVertexCompat creates Auth entries for Vertex-compatible providers. func (s *ConfigSynthesizer) synthesizeVertexCompat(ctx *SynthesisContext) []*coreauth.Auth { cfg := ctx.Config now := ctx.Now idGen := ctx.IDGenerator out := make([]*coreauth.Auth, 0, len(cfg.VertexCompatAPIKey)) for i := range cfg.VertexCompatAPIKey { compat := &cfg.VertexCompatAPIKey[i] providerName := "vertex" base := strings.TrimSpace(compat.BaseURL) key := strings.TrimSpace(compat.APIKey) prefix := strings.TrimSpace(compat.Prefix) proxyURL := strings.TrimSpace(compat.ProxyURL) idKind := "vertex:apikey" id, token := idGen.Next(idKind, key, base, proxyURL) attrs := map[string]string{ "source": fmt.Sprintf("config:vertex-apikey[%s]", token), "base_url": base, "provider_key": providerName, "config_index": strconv.Itoa(i), } if compat.Priority != 0 { attrs["priority"] = strconv.Itoa(compat.Priority) } addWeightToAttrs(compat.Weight, attrs) if key != "" { attrs["api_key"] = key } if hash := diff.ComputeVertexCompatModelsHash(compat.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(compat.Headers, attrs) metadata := map[string]any{} if compat.DisableCooling != nil { metadata["disable_cooling"] = *compat.DisableCooling } addRequestRetryToMetadata(compat.RequestRetry, metadata) a := &coreauth.Auth{ ID: id, Provider: providerName, Label: "vertex-apikey", Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, Metadata: metadata, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, compat.ExcludedModels, "apikey") if len(a.Metadata) == 0 { a.Metadata = nil } out = append(out, a) } return out }