vibe-proxy/backend/internal/watcher/synthesizer/config.go
2026-08-24 00:10:41 +02:00

452 lines
14 KiB
Go

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
}