142 lines
3.8 KiB
Go
142 lines
3.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"strings"
|
|
|
|
"chat-switchboard/config"
|
|
"chat-switchboard/crypto"
|
|
"chat-switchboard/models"
|
|
"chat-switchboard/store"
|
|
)
|
|
|
|
// Known provider default endpoints for seeding.
|
|
var seedDefaultEndpoints = map[string]string{
|
|
"openai": "https://api.openai.com/v1",
|
|
"anthropic": "https://api.anthropic.com",
|
|
"openrouter": "https://openrouter.ai/api/v1",
|
|
"venice": "https://api.venice.ai/api/v1",
|
|
"mistral": "https://api.mistral.ai/v1",
|
|
"groq": "https://api.groq.com/openai/v1",
|
|
"together": "https://api.together.xyz/v1",
|
|
"fireworks": "https://api.fireworks.ai/inference/v1",
|
|
"deepseek": "https://api.deepseek.com/v1",
|
|
"perplexity": "https://api.perplexity.ai",
|
|
}
|
|
|
|
// SeedProviders creates global providers from the SEED_PROVIDERS env var.
|
|
//
|
|
// Format: "provider:api_key[:name],provider:api_key[:name]"
|
|
//
|
|
// Examples:
|
|
//
|
|
// SEED_PROVIDERS="openai:sk-xxx,anthropic:sk-ant-xxx"
|
|
// SEED_PROVIDERS="openai:sk-xxx:My OpenAI,openrouter:sk-or-xxx"
|
|
//
|
|
// Idempotent: skips if a global provider with the same name already exists.
|
|
// Blocked in production.
|
|
func SeedProviders(cfg *config.Config, stores store.Stores, resolver *crypto.KeyResolver) {
|
|
if cfg.SeedProviders == "" {
|
|
return
|
|
}
|
|
|
|
if cfg.Environment == "production" {
|
|
log.Printf("⚠ SEED_PROVIDERS ignored in production environment")
|
|
return
|
|
}
|
|
|
|
ctx := context.Background()
|
|
entries := strings.Split(cfg.SeedProviders, ",")
|
|
log.Printf(" 🌱 SEED_PROVIDERS: %d entries to process", len(entries))
|
|
|
|
for _, entry := range entries {
|
|
entry = strings.TrimSpace(entry)
|
|
if entry == "" {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(entry, ":", 3)
|
|
if len(parts) < 2 {
|
|
log.Printf("⚠ Seed provider skipped (bad format, want provider:key[:name]): %q", entry)
|
|
continue
|
|
}
|
|
|
|
provider := strings.ToLower(strings.TrimSpace(parts[0]))
|
|
apiKey := strings.TrimSpace(parts[1])
|
|
|
|
// Derive name: explicit or "OpenAI (seed)", "Anthropic (seed)", etc.
|
|
name := ""
|
|
if len(parts) >= 3 {
|
|
name = strings.TrimSpace(parts[2])
|
|
}
|
|
if name == "" {
|
|
// Capitalize provider name
|
|
name = strings.ToUpper(provider[:1]) + provider[1:] + " (seed)"
|
|
}
|
|
|
|
// Resolve endpoint
|
|
endpoint, ok := seedDefaultEndpoints[provider]
|
|
if !ok {
|
|
// Unknown provider — treat as openai-compatible with custom endpoint
|
|
log.Printf("⚠ Seed provider '%s': unknown provider, skipping (no default endpoint)", provider)
|
|
continue
|
|
}
|
|
|
|
if apiKey == "" {
|
|
log.Printf("⚠ Seed provider '%s': empty API key, skipping", name)
|
|
continue
|
|
}
|
|
|
|
// Check if already exists (idempotent)
|
|
existing, _ := stores.Providers.ListGlobal(ctx)
|
|
found := false
|
|
for _, p := range existing {
|
|
if p.Name == name || (p.Provider == provider && p.Scope == models.ScopeGlobal) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
log.Printf(" 🌱 Seed provider '%s' already exists, skipping", name)
|
|
continue
|
|
}
|
|
|
|
// Create provider config
|
|
pcfg := &models.ProviderConfig{
|
|
Name: name,
|
|
Provider: provider,
|
|
Endpoint: endpoint,
|
|
Scope: models.ScopeGlobal,
|
|
KeyScope: models.ScopeGlobal,
|
|
IsActive: true,
|
|
}
|
|
|
|
// Encrypt API key
|
|
if resolver != nil {
|
|
enc, nonce, err := resolver.EncryptForScope(apiKey, "global", "")
|
|
if err != nil {
|
|
log.Printf("⚠ Seed provider '%s': encrypt failed: %v", name, err)
|
|
continue
|
|
}
|
|
pcfg.APIKeyEnc = enc
|
|
pcfg.KeyNonce = nonce
|
|
} else {
|
|
pcfg.APIKeyEnc = []byte(apiKey)
|
|
}
|
|
|
|
if err := stores.Providers.Create(ctx, pcfg); err != nil {
|
|
log.Printf("⚠ Seed provider '%s': create failed: %v", name, err)
|
|
continue
|
|
}
|
|
|
|
// Auto-fetch and enable models
|
|
result, err := syncAndEnableProviderModels(ctx, stores, pcfg, apiKey)
|
|
if err != nil {
|
|
log.Printf(" 🌱 Seed provider '%s' created (model fetch failed: %v)", name, err)
|
|
} else {
|
|
log.Printf(" 🌱 Seed provider '%s' created (%d models fetched)", name, result.Total)
|
|
}
|
|
}
|
|
}
|