package config import ( "strings" "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" ) // VertexCompatKey represents the configuration for Vertex AI-compatible API keys. // This supports third-party services that use Vertex AI-style endpoint paths // (/publishers/google/models/{model}:streamGenerateContent) but authenticate // with simple API keys instead of Google Cloud service account credentials. // // Example services: zenmux.ai and similar Vertex-compatible providers. type VertexCompatKey struct { // APIKey is the authentication key for accessing the Vertex-compatible API. // Maps to the x-goog-api-key header. APIKey string `yaml:"api-key" json:"api-key"` // Priority controls selection preference when multiple credentials match. // Higher values are preferred; defaults to 0. Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` // Weight controls proportional selection under weighted-round-robin. // An omitted value defaults to 1; non-positive values exclude this credential; maximum 1,000,000. Weight *int `yaml:"weight,omitempty" json:"weight,omitempty"` // Prefix optionally namespaces model aliases for this credential (e.g., "teamA/vertex-pro"). Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` // BaseURL optionally overrides the Vertex-compatible API endpoint. // The executor will append "/v1/publishers/google/models/{model}:action" to this. // When empty, requests fall back to the default Vertex API base URL. BaseURL string `yaml:"base-url,omitempty" json:"base-url,omitempty"` // ProxyURL optionally overrides the global proxy for this API key. ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` // Headers optionally adds extra HTTP headers for requests sent with this key. // Commonly used for cookies, user-agent, and other authentication headers. Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` // Models defines the model configurations including aliases for routing. Models []VertexCompatModel `yaml:"models,omitempty" json:"models,omitempty"` // ExcludedModels lists model IDs that should be excluded for this provider. ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` // DisableCooling overrides the global cooling policy for this credential when set. // True disables auth/model cooldowns; false explicitly enables them. DisableCooling *bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"` // RequestRetry optionally overrides the global request-retry for this credential. // Nil or a negative value means "use the global request-retry". 0 disables additional retry rounds. RequestRetry *int `yaml:"request-retry,omitempty" json:"request-retry,omitempty"` } func (k VertexCompatKey) GetAPIKey() string { return k.APIKey } func (k VertexCompatKey) GetBaseURL() string { return k.BaseURL } func (k VertexCompatKey) GetPrefix() string { return k.Prefix } func (k VertexCompatKey) GetProxyURL() string { return k.ProxyURL } // VertexCompatModel represents a model configuration for Vertex compatibility, // including the actual model name and its alias for API routing. type VertexCompatModel struct { // Name is the actual model name used by the external provider. Name string `yaml:"name" json:"name"` // Alias is the model name alias that clients will use to reference this model. Alias string `yaml:"alias" json:"alias"` // DisplayName is the optional human-readable name shown in model catalogs. DisplayName string `yaml:"display-name,omitempty" json:"display-name,omitempty"` // ForceMapping rewrites upstream response model fields back to Alias. ForceMapping bool `yaml:"force-mapping,omitempty" json:"force-mapping,omitempty"` // Thinking configures the thinking/reasoning capability for this model. Thinking *registry.ThinkingSupport `yaml:"thinking,omitempty" json:"thinking,omitempty"` } func (m VertexCompatModel) GetName() string { return m.Name } func (m VertexCompatModel) GetAlias() string { return m.Alias } func (m VertexCompatModel) GetDisplayName() string { return m.DisplayName } func (m VertexCompatModel) GetForceMapping() bool { return m.ForceMapping } func (m VertexCompatModel) GetThinking() *registry.ThinkingSupport { return m.Thinking } // SanitizeVertexCompatKeys deduplicates and normalizes Vertex-compatible API key credentials. func (cfg *Config) SanitizeVertexCompatKeys() { if cfg == nil { return } seen := make(map[string]struct{}, len(cfg.VertexCompatAPIKey)) out := cfg.VertexCompatAPIKey[:0] for i := range cfg.VertexCompatAPIKey { entry := cfg.VertexCompatAPIKey[i] entry.APIKey = strings.TrimSpace(entry.APIKey) if entry.APIKey == "" { continue } entry.Prefix = normalizeModelPrefix(entry.Prefix) entry.BaseURL = strings.TrimSpace(entry.BaseURL) entry.ProxyURL = strings.TrimSpace(entry.ProxyURL) entry.Headers = NormalizeHeaders(entry.Headers) entry.ExcludedModels = NormalizeExcludedModels(entry.ExcludedModels) // Sanitize models: remove entries without valid alias sanitizedModels := make([]VertexCompatModel, 0, len(entry.Models)) for _, model := range entry.Models { model.Alias = strings.TrimSpace(model.Alias) model.Name = strings.TrimSpace(model.Name) if model.Alias != "" && model.Name != "" { sanitizedModels = append(sanitizedModels, model) } } entry.Models = sanitizedModels // Use API key + base URL as uniqueness key uniqueKey := entry.APIKey + "|" + entry.BaseURL if _, exists := seen[uniqueKey]; exists { continue } seen[uniqueKey] = struct{}{} out = append(out, entry) } cfg.VertexCompatAPIKey = out }