133 lines
4.1 KiB
Go
133 lines
4.1 KiB
Go
// Package providers — custom.go
|
|
//
|
|
// v0.29.1 CS4: Config-file provider types. Registers OpenAI-compatible
|
|
// provider types from a JSON file at startup. No code deploy needed to
|
|
// add Ollama, LiteLLM, vLLM, or any other OpenAI-compatible endpoint.
|
|
//
|
|
// File format (array of objects):
|
|
//
|
|
// [
|
|
// {
|
|
// "id": "ollama",
|
|
// "name": "Ollama",
|
|
// "description": "Local Ollama instance",
|
|
// "default_endpoint": "http://localhost:11434/v1",
|
|
// "api": "openai"
|
|
// },
|
|
// {
|
|
// "id": "litellm",
|
|
// "name": "LiteLLM Proxy",
|
|
// "description": "LiteLLM unified proxy",
|
|
// "default_endpoint": "http://litellm:4000/v1",
|
|
// "api": "openai"
|
|
// }
|
|
// ]
|
|
//
|
|
// Fields:
|
|
// - id: Unique provider type ID (used in provider_configs.provider column)
|
|
// - name: Human-readable label for admin UI
|
|
// - description: Help text shown in provider creation form
|
|
// - default_endpoint: Pre-filled endpoint URL for new configs
|
|
// - api: Wire protocol — "openai" (required, only supported value for now)
|
|
//
|
|
// The api field determines which Provider implementation handles
|
|
// requests. Currently only "openai" is supported (covers Ollama,
|
|
// LiteLLM, vLLM, LocalAI, and any other OpenAI-compatible API).
|
|
// Future: "anthropic" for Anthropic-compatible proxies.
|
|
//
|
|
// Built-in provider IDs (openai, anthropic, venice, openrouter) cannot
|
|
// be overridden by config-file entries — they are silently skipped.
|
|
package providers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
)
|
|
|
|
// CustomProviderType represents one entry in the provider types JSON file.
|
|
type CustomProviderType struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
DefaultEndpoint string `json:"default_endpoint"`
|
|
API string `json:"api"` // "openai" (required)
|
|
}
|
|
|
|
// builtinIDs is the set of provider IDs registered by Init().
|
|
// Config-file entries with these IDs are silently skipped.
|
|
var builtinIDs = map[string]bool{
|
|
"openai": true,
|
|
"anthropic": true,
|
|
"venice": true,
|
|
"openrouter": true,
|
|
}
|
|
|
|
// LoadCustomTypes reads a JSON file of provider type definitions and
|
|
// registers each as a new provider type. Only OpenAI-compatible APIs
|
|
// are currently supported (api="openai").
|
|
//
|
|
// Returns the number of providers registered. Errors are non-fatal —
|
|
// individual invalid entries are logged and skipped.
|
|
//
|
|
// Call after Init() so that built-in types are already registered
|
|
// and can be detected for skip logic.
|
|
func LoadCustomTypes(path string) (int, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("reading provider types file: %w", err)
|
|
}
|
|
|
|
var entries []CustomProviderType
|
|
if err := json.Unmarshal(data, &entries); err != nil {
|
|
return 0, fmt.Errorf("parsing provider types file: %w", err)
|
|
}
|
|
|
|
registered := 0
|
|
for _, entry := range entries {
|
|
// Validate required fields
|
|
if entry.ID == "" {
|
|
log.Printf("⚠️ custom provider: skipping entry with empty id")
|
|
continue
|
|
}
|
|
if entry.Name == "" {
|
|
log.Printf("⚠️ custom provider %q: skipping — name is required", entry.ID)
|
|
continue
|
|
}
|
|
if entry.API == "" {
|
|
log.Printf("⚠️ custom provider %q: skipping — api is required", entry.ID)
|
|
continue
|
|
}
|
|
|
|
// Skip built-in provider IDs
|
|
if builtinIDs[entry.ID] {
|
|
log.Printf("⚠️ custom provider %q: skipping — conflicts with built-in provider", entry.ID)
|
|
continue
|
|
}
|
|
|
|
// Resolve API implementation
|
|
var impl Provider
|
|
switch entry.API {
|
|
case "openai":
|
|
impl = &OpenAIProvider{}
|
|
default:
|
|
log.Printf("⚠️ custom provider %q: unsupported api %q (only \"openai\" supported)", entry.ID, entry.API)
|
|
continue
|
|
}
|
|
|
|
RegisterType(ProviderTypeMeta{
|
|
ID: entry.ID,
|
|
Name: entry.Name,
|
|
Description: entry.Description,
|
|
DefaultEndpoint: entry.DefaultEndpoint,
|
|
ProfileSchema: GetProfileSchema(entry.API), // inherit schema from API type
|
|
}, impl)
|
|
|
|
registered++
|
|
log.Printf(" 📦 Custom provider type: %s (%s) → %s", entry.ID, entry.Name, entry.DefaultEndpoint)
|
|
}
|
|
|
|
return registered, nil
|
|
}
|