package synthesizer import ( "fmt" "strings" "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff" coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" ) // ConfigSynthesizer generates Auth entries from configuration API keys. // It handles Gemini, Claude, Codex, OpenAI-compat, and Vertex-compat providers. type ConfigSynthesizer struct{} // NewConfigSynthesizer creates a new ConfigSynthesizer instance. func NewConfigSynthesizer() *ConfigSynthesizer { return &ConfigSynthesizer{} } // 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 } // Gemini API Keys out = append(out, s.synthesizeGeminiKeys(ctx)...) // Claude API Keys out = append(out, s.synthesizeClaudeKeys(ctx)...) // Codex API Keys out = append(out, s.synthesizeCodexKeys(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 { cfg := ctx.Config now := ctx.Now idGen := ctx.IDGenerator out := make([]*coreauth.Auth, 0, len(cfg.GeminiKey)) for i := range cfg.GeminiKey { entry := cfg.GeminiKey[i] key := strings.TrimSpace(entry.APIKey) if key == "" { continue } prefix := strings.TrimSpace(entry.Prefix) base := strings.TrimSpace(entry.BaseURL) proxyURL := strings.TrimSpace(entry.ProxyURL) id, token := idGen.Next("gemini:apikey", key, base) attrs := map[string]string{ "source": fmt.Sprintf("config:gemini[%s]", token), "api_key": key, } 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: "gemini", Label: "gemini-apikey", Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, entry.ExcludedModels, "apikey") 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) if key == "" { continue } prefix := strings.TrimSpace(ck.Prefix) base := strings.TrimSpace(ck.BaseURL) id, token := idGen.Next("claude:apikey", key, base) attrs := map[string]string{ "source": fmt.Sprintf("config:claude[%s]", token), "api_key": key, } if base != "" { attrs["base_url"] = base } if hash := diff.ComputeClaudeModelsHash(ck.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(ck.Headers, attrs) proxyURL := strings.TrimSpace(ck.ProxyURL) a := &coreauth.Auth{ ID: id, Provider: "claude", Label: "claude-apikey", Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, ck.ExcludedModels, "apikey") out = append(out, a) } return out } // synthesizeCodexKeys creates Auth entries for Codex API keys. func (s *ConfigSynthesizer) synthesizeCodexKeys(ctx *SynthesisContext) []*coreauth.Auth { cfg := ctx.Config now := ctx.Now idGen := ctx.IDGenerator out := make([]*coreauth.Auth, 0, len(cfg.CodexKey)) for i := range cfg.CodexKey { ck := cfg.CodexKey[i] key := strings.TrimSpace(ck.APIKey) if key == "" { continue } prefix := strings.TrimSpace(ck.Prefix) id, token := idGen.Next("codex:apikey", key, ck.BaseURL) attrs := map[string]string{ "source": fmt.Sprintf("config:codex[%s]", token), "api_key": key, } if ck.BaseURL != "" { attrs["base_url"] = ck.BaseURL } if hash := diff.ComputeCodexModelsHash(ck.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(ck.Headers, attrs) proxyURL := strings.TrimSpace(ck.ProxyURL) a := &coreauth.Auth{ ID: id, Provider: "codex", Label: "codex-apikey", Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, ck.ExcludedModels, "apikey") 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] prefix := strings.TrimSpace(compat.Prefix) providerName := strings.ToLower(strings.TrimSpace(compat.Name)) if providerName == "" { providerName = "openai-compatibility" } base := strings.TrimSpace(compat.BaseURL) // 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": providerName, } 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: providerName, Label: compat.Name, Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, CreatedAt: now, UpdatedAt: now, } 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": providerName, } if hash := diff.ComputeOpenAICompatModelsHash(compat.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(compat.Headers, attrs) a := &coreauth.Auth{ ID: id, Provider: providerName, Label: compat.Name, Prefix: prefix, Status: coreauth.StatusActive, Attributes: attrs, CreatedAt: now, UpdatedAt: now, } 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, } if key != "" { attrs["api_key"] = key } if hash := diff.ComputeVertexCompatModelsHash(compat.Models); hash != "" { attrs["models_hash"] = hash } addConfigHeadersToAttrs(compat.Headers, attrs) a := &coreauth.Auth{ ID: id, Provider: providerName, Label: "vertex-apikey", Prefix: prefix, Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, nil, "apikey") out = append(out, a) } return out }