mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
ef7078a356
Refactor command handlers into separate files to improve code organization and maintainability. Each command (agent, auth, cron, gateway, migrate, onboard, skills, status) now has its own dedicated file. Restructure provider creation to support new model_list configuration system that enables zero-code addition of OpenAI-compatible providers. Move legacy provider logic to separate file for backward compatibility. Move configuration functions from config.go to separate files (defaults.go, migration.go) for better organization.
477 lines
17 KiB
Go
477 lines
17 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/caarlos0/env/v11"
|
|
)
|
|
|
|
// FlexibleStringSlice is a []string that also accepts JSON numbers,
|
|
// so allow_from can contain both "123" and 123.
|
|
type FlexibleStringSlice []string
|
|
|
|
func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
|
// Try []string first
|
|
var ss []string
|
|
if err := json.Unmarshal(data, &ss); err == nil {
|
|
*f = ss
|
|
return nil
|
|
}
|
|
|
|
// Try []interface{} to handle mixed types
|
|
var raw []interface{}
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return err
|
|
}
|
|
|
|
result := make([]string, 0, len(raw))
|
|
for _, v := range raw {
|
|
switch val := v.(type) {
|
|
case string:
|
|
result = append(result, val)
|
|
case float64:
|
|
result = append(result, fmt.Sprintf("%.0f", val))
|
|
default:
|
|
result = append(result, fmt.Sprintf("%v", val))
|
|
}
|
|
}
|
|
*f = result
|
|
return nil
|
|
}
|
|
|
|
type Config struct {
|
|
Agents AgentsConfig `json:"agents"`
|
|
Channels ChannelsConfig `json:"channels"`
|
|
Providers ProvidersConfig `json:"providers"`
|
|
ModelList []ModelConfig `json:"model_list"` // New model-centric provider configuration
|
|
Gateway GatewayConfig `json:"gateway"`
|
|
Tools ToolsConfig `json:"tools"`
|
|
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
|
Devices DevicesConfig `json:"devices"`
|
|
mu sync.RWMutex
|
|
rrCounters map[string]*atomic.Uint64 // Round-robin counters for load balancing
|
|
}
|
|
|
|
type AgentsConfig struct {
|
|
Defaults AgentDefaults `json:"defaults"`
|
|
}
|
|
|
|
type AgentDefaults struct {
|
|
Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"`
|
|
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
|
|
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
|
|
Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"`
|
|
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
|
|
Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
|
|
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
|
}
|
|
|
|
type ChannelsConfig struct {
|
|
WhatsApp WhatsAppConfig `json:"whatsapp"`
|
|
Telegram TelegramConfig `json:"telegram"`
|
|
Feishu FeishuConfig `json:"feishu"`
|
|
Discord DiscordConfig `json:"discord"`
|
|
MaixCam MaixCamConfig `json:"maixcam"`
|
|
QQ QQConfig `json:"qq"`
|
|
DingTalk DingTalkConfig `json:"dingtalk"`
|
|
Slack SlackConfig `json:"slack"`
|
|
LINE LINEConfig `json:"line"`
|
|
OneBot OneBotConfig `json:"onebot"`
|
|
}
|
|
|
|
type WhatsAppConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_WHATSAPP_ENABLED"`
|
|
BridgeURL string `json:"bridge_url" env:"PICOCLAW_CHANNELS_WHATSAPP_BRIDGE_URL"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_WHATSAPP_ALLOW_FROM"`
|
|
}
|
|
|
|
type TelegramConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_TELEGRAM_ENABLED"`
|
|
Token string `json:"token" env:"PICOCLAW_CHANNELS_TELEGRAM_TOKEN"`
|
|
Proxy string `json:"proxy" env:"PICOCLAW_CHANNELS_TELEGRAM_PROXY"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_TELEGRAM_ALLOW_FROM"`
|
|
}
|
|
|
|
type FeishuConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_FEISHU_ENABLED"`
|
|
AppID string `json:"app_id" env:"PICOCLAW_CHANNELS_FEISHU_APP_ID"`
|
|
AppSecret string `json:"app_secret" env:"PICOCLAW_CHANNELS_FEISHU_APP_SECRET"`
|
|
EncryptKey string `json:"encrypt_key" env:"PICOCLAW_CHANNELS_FEISHU_ENCRYPT_KEY"`
|
|
VerificationToken string `json:"verification_token" env:"PICOCLAW_CHANNELS_FEISHU_VERIFICATION_TOKEN"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_FEISHU_ALLOW_FROM"`
|
|
}
|
|
|
|
type DiscordConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_DISCORD_ENABLED"`
|
|
Token string `json:"token" env:"PICOCLAW_CHANNELS_DISCORD_TOKEN"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_DISCORD_ALLOW_FROM"`
|
|
}
|
|
|
|
type MaixCamConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_MAIXCAM_ENABLED"`
|
|
Host string `json:"host" env:"PICOCLAW_CHANNELS_MAIXCAM_HOST"`
|
|
Port int `json:"port" env:"PICOCLAW_CHANNELS_MAIXCAM_PORT"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_MAIXCAM_ALLOW_FROM"`
|
|
}
|
|
|
|
type QQConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_QQ_ENABLED"`
|
|
AppID string `json:"app_id" env:"PICOCLAW_CHANNELS_QQ_APP_ID"`
|
|
AppSecret string `json:"app_secret" env:"PICOCLAW_CHANNELS_QQ_APP_SECRET"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_QQ_ALLOW_FROM"`
|
|
}
|
|
|
|
type DingTalkConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_DINGTALK_ENABLED"`
|
|
ClientID string `json:"client_id" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_ID"`
|
|
ClientSecret string `json:"client_secret" env:"PICOCLAW_CHANNELS_DINGTALK_CLIENT_SECRET"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_DINGTALK_ALLOW_FROM"`
|
|
}
|
|
|
|
type SlackConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_SLACK_ENABLED"`
|
|
BotToken string `json:"bot_token" env:"PICOCLAW_CHANNELS_SLACK_BOT_TOKEN"`
|
|
AppToken string `json:"app_token" env:"PICOCLAW_CHANNELS_SLACK_APP_TOKEN"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_SLACK_ALLOW_FROM"`
|
|
}
|
|
|
|
type LINEConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_LINE_ENABLED"`
|
|
ChannelSecret string `json:"channel_secret" env:"PICOCLAW_CHANNELS_LINE_CHANNEL_SECRET"`
|
|
ChannelAccessToken string `json:"channel_access_token" env:"PICOCLAW_CHANNELS_LINE_CHANNEL_ACCESS_TOKEN"`
|
|
WebhookHost string `json:"webhook_host" env:"PICOCLAW_CHANNELS_LINE_WEBHOOK_HOST"`
|
|
WebhookPort int `json:"webhook_port" env:"PICOCLAW_CHANNELS_LINE_WEBHOOK_PORT"`
|
|
WebhookPath string `json:"webhook_path" env:"PICOCLAW_CHANNELS_LINE_WEBHOOK_PATH"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_LINE_ALLOW_FROM"`
|
|
}
|
|
|
|
type OneBotConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_ONEBOT_ENABLED"`
|
|
WSUrl string `json:"ws_url" env:"PICOCLAW_CHANNELS_ONEBOT_WS_URL"`
|
|
AccessToken string `json:"access_token" env:"PICOCLAW_CHANNELS_ONEBOT_ACCESS_TOKEN"`
|
|
ReconnectInterval int `json:"reconnect_interval" env:"PICOCLAW_CHANNELS_ONEBOT_RECONNECT_INTERVAL"`
|
|
GroupTriggerPrefix []string `json:"group_trigger_prefix" env:"PICOCLAW_CHANNELS_ONEBOT_GROUP_TRIGGER_PREFIX"`
|
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_ONEBOT_ALLOW_FROM"`
|
|
}
|
|
|
|
type HeartbeatConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_HEARTBEAT_ENABLED"`
|
|
Interval int `json:"interval" env:"PICOCLAW_HEARTBEAT_INTERVAL"` // minutes, min 5
|
|
}
|
|
|
|
type DevicesConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_DEVICES_ENABLED"`
|
|
MonitorUSB bool `json:"monitor_usb" env:"PICOCLAW_DEVICES_MONITOR_USB"`
|
|
}
|
|
|
|
type ProvidersConfig struct {
|
|
Anthropic ProviderConfig `json:"anthropic"`
|
|
OpenAI ProviderConfig `json:"openai"`
|
|
OpenRouter ProviderConfig `json:"openrouter"`
|
|
Groq ProviderConfig `json:"groq"`
|
|
Zhipu ProviderConfig `json:"zhipu"`
|
|
VLLM ProviderConfig `json:"vllm"`
|
|
Gemini ProviderConfig `json:"gemini"`
|
|
Nvidia ProviderConfig `json:"nvidia"`
|
|
Ollama ProviderConfig `json:"ollama"`
|
|
Moonshot ProviderConfig `json:"moonshot"`
|
|
ShengSuanYun ProviderConfig `json:"shengsuanyun"`
|
|
DeepSeek ProviderConfig `json:"deepseek"`
|
|
Cerebras ProviderConfig `json:"cerebras"`
|
|
VolcEngine ProviderConfig `json:"volcengine"`
|
|
GitHubCopilot ProviderConfig `json:"github_copilot"`
|
|
Antigravity ProviderConfig `json:"antigravity"`
|
|
Qwen ProviderConfig `json:"qwen"`
|
|
}
|
|
|
|
type ProviderConfig struct {
|
|
APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"`
|
|
APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"`
|
|
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"`
|
|
AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"`
|
|
ConnectMode string `json:"connect_mode,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_CONNECT_MODE"` //only for Github Copilot, `stdio` or `grpc`
|
|
}
|
|
|
|
// ModelConfig represents a model-centric provider configuration.
|
|
// It allows adding new providers (especially OpenAI-compatible ones) via configuration only.
|
|
// The model field uses protocol prefix format: [protocol/]model-identifier
|
|
// Supported protocols: openai, anthropic, antigravity, claude-cli, codex-cli, github-copilot
|
|
// Default protocol is "openai" if no prefix is specified.
|
|
type ModelConfig struct {
|
|
// Required fields
|
|
ModelName string `json:"model_name"` // User-facing alias for the model
|
|
Model string `json:"model"` // Protocol/model-identifier (e.g., "openai/gpt-4o", "anthropic/claude-3")
|
|
|
|
// HTTP-based providers
|
|
APIBase string `json:"api_base,omitempty"` // API endpoint URL
|
|
APIKey string `json:"api_key,omitempty"` // API authentication key
|
|
Proxy string `json:"proxy,omitempty"` // HTTP proxy URL
|
|
|
|
// Special providers (CLI-based, OAuth, etc.)
|
|
AuthMethod string `json:"auth_method,omitempty"` // Authentication method: oauth, token
|
|
ConnectMode string `json:"connect_mode,omitempty"` // Connection mode: stdio, grpc
|
|
|
|
// Optional optimizations
|
|
RPM int `json:"rpm,omitempty"` // Requests per minute limit
|
|
MaxTokensField string `json:"max_tokens_field,omitempty"` // Field name for max tokens (e.g., "max_completion_tokens")
|
|
}
|
|
|
|
// Validate checks if the ModelConfig has all required fields.
|
|
func (c *ModelConfig) Validate() error {
|
|
if c.ModelName == "" {
|
|
return fmt.Errorf("model_name is required")
|
|
}
|
|
if c.Model == "" {
|
|
return fmt.Errorf("model is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type GatewayConfig struct {
|
|
Host string `json:"host" env:"PICOCLAW_GATEWAY_HOST"`
|
|
Port int `json:"port" env:"PICOCLAW_GATEWAY_PORT"`
|
|
}
|
|
|
|
type BraveConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_BRAVE_ENABLED"`
|
|
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_BRAVE_API_KEY"`
|
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_BRAVE_MAX_RESULTS"`
|
|
}
|
|
|
|
type DuckDuckGoConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_DUCKDUCKGO_ENABLED"`
|
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_DUCKDUCKGO_MAX_RESULTS"`
|
|
}
|
|
|
|
type PerplexityConfig struct {
|
|
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_ENABLED"`
|
|
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_API_KEY"`
|
|
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_MAX_RESULTS"`
|
|
}
|
|
|
|
type WebToolsConfig struct {
|
|
Brave BraveConfig `json:"brave"`
|
|
DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"`
|
|
Perplexity PerplexityConfig `json:"perplexity"`
|
|
}
|
|
|
|
type CronToolsConfig struct {
|
|
ExecTimeoutMinutes int `json:"exec_timeout_minutes" env:"PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES"` // 0 means no timeout
|
|
}
|
|
|
|
type ToolsConfig struct {
|
|
Web WebToolsConfig `json:"web"`
|
|
Cron CronToolsConfig `json:"cron"`
|
|
}
|
|
|
|
func LoadConfig(path string) (*Config, error) {
|
|
cfg := DefaultConfig()
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return cfg, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
if err := json.Unmarshal(data, cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := env.Parse(cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func SaveConfig(path string, cfg *Config) error {
|
|
cfg.mu.RLock()
|
|
defer cfg.mu.RUnlock()
|
|
|
|
data, err := json.MarshalIndent(cfg, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(path, data, 0600)
|
|
}
|
|
|
|
func (c *Config) WorkspacePath() string {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return expandHome(c.Agents.Defaults.Workspace)
|
|
}
|
|
|
|
func (c *Config) GetAPIKey() string {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
if c.Providers.OpenRouter.APIKey != "" {
|
|
return c.Providers.OpenRouter.APIKey
|
|
}
|
|
if c.Providers.Anthropic.APIKey != "" {
|
|
return c.Providers.Anthropic.APIKey
|
|
}
|
|
if c.Providers.OpenAI.APIKey != "" {
|
|
return c.Providers.OpenAI.APIKey
|
|
}
|
|
if c.Providers.Gemini.APIKey != "" {
|
|
return c.Providers.Gemini.APIKey
|
|
}
|
|
if c.Providers.Zhipu.APIKey != "" {
|
|
return c.Providers.Zhipu.APIKey
|
|
}
|
|
if c.Providers.Groq.APIKey != "" {
|
|
return c.Providers.Groq.APIKey
|
|
}
|
|
if c.Providers.VLLM.APIKey != "" {
|
|
return c.Providers.VLLM.APIKey
|
|
}
|
|
if c.Providers.ShengSuanYun.APIKey != "" {
|
|
return c.Providers.ShengSuanYun.APIKey
|
|
}
|
|
if c.Providers.Cerebras.APIKey != "" {
|
|
return c.Providers.Cerebras.APIKey
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (c *Config) GetAPIBase() string {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
if c.Providers.OpenRouter.APIKey != "" {
|
|
if c.Providers.OpenRouter.APIBase != "" {
|
|
return c.Providers.OpenRouter.APIBase
|
|
}
|
|
return "https://openrouter.ai/api/v1"
|
|
}
|
|
if c.Providers.Zhipu.APIKey != "" {
|
|
return c.Providers.Zhipu.APIBase
|
|
}
|
|
if c.Providers.VLLM.APIKey != "" && c.Providers.VLLM.APIBase != "" {
|
|
return c.Providers.VLLM.APIBase
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func expandHome(path string) string {
|
|
if path == "" {
|
|
return path
|
|
}
|
|
if path[0] == '~' {
|
|
home, _ := os.UserHomeDir()
|
|
if len(path) > 1 && path[1] == '/' {
|
|
return home + path[1:]
|
|
}
|
|
return home
|
|
}
|
|
return path
|
|
}
|
|
|
|
// GetModelConfig returns the ModelConfig for the given model name.
|
|
// If multiple configs exist with the same model_name, it uses round-robin
|
|
// selection for load balancing. Returns an error if the model is not found.
|
|
// Uses double-check locking for optimal read performance.
|
|
func (c *Config) GetModelConfig(modelName string) (*ModelConfig, error) {
|
|
// First pass: use read lock to find matches
|
|
c.mu.RLock()
|
|
matches := c.findMatchesLocked(modelName)
|
|
if len(matches) == 0 {
|
|
c.mu.RUnlock()
|
|
return nil, fmt.Errorf("model %q not found in model_list or providers", modelName)
|
|
}
|
|
if len(matches) == 1 {
|
|
c.mu.RUnlock()
|
|
return &matches[0], nil
|
|
}
|
|
|
|
// Multiple configs - check if counter exists
|
|
counter, ok := c.rrCounters[modelName]
|
|
c.mu.RUnlock()
|
|
|
|
// Double-check locking: only acquire write lock if counter needs initialization
|
|
if !ok {
|
|
c.mu.Lock()
|
|
// Re-check after acquiring write lock
|
|
if c.rrCounters == nil {
|
|
c.rrCounters = make(map[string]*atomic.Uint64)
|
|
}
|
|
if c.rrCounters[modelName] == nil {
|
|
c.rrCounters[modelName] = &atomic.Uint64{}
|
|
}
|
|
counter = c.rrCounters[modelName]
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// Re-fetch matches to ensure consistency (ModelList could have changed)
|
|
c.mu.RLock()
|
|
matches = c.findMatchesLocked(modelName)
|
|
c.mu.RUnlock()
|
|
|
|
if len(matches) == 0 {
|
|
return nil, fmt.Errorf("model %q not found in model_list or providers", modelName)
|
|
}
|
|
|
|
idx := counter.Add(1) % uint64(len(matches))
|
|
return &matches[idx], nil
|
|
}
|
|
|
|
// findMatchesLocked finds all ModelConfig entries with the given model_name.
|
|
// Must be called with c.mu locked (read or write).
|
|
func (c *Config) findMatchesLocked(modelName string) []ModelConfig {
|
|
var matches []ModelConfig
|
|
for i := range c.ModelList {
|
|
if c.ModelList[i].ModelName == modelName {
|
|
matches = append(matches, c.ModelList[i])
|
|
}
|
|
}
|
|
return matches
|
|
}
|
|
|
|
// HasProvidersConfig checks if any provider in the old providers config has configuration.
|
|
func (c *Config) HasProvidersConfig() bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
v := c.Providers
|
|
return v.Anthropic.APIKey != "" || v.Anthropic.APIBase != "" ||
|
|
v.OpenAI.APIKey != "" || v.OpenAI.APIBase != "" ||
|
|
v.OpenRouter.APIKey != "" || v.OpenRouter.APIBase != "" ||
|
|
v.Groq.APIKey != "" || v.Groq.APIBase != "" ||
|
|
v.Zhipu.APIKey != "" || v.Zhipu.APIBase != "" ||
|
|
v.VLLM.APIKey != "" || v.VLLM.APIBase != "" ||
|
|
v.Gemini.APIKey != "" || v.Gemini.APIBase != "" ||
|
|
v.Nvidia.APIKey != "" || v.Nvidia.APIBase != "" ||
|
|
v.Ollama.APIKey != "" || v.Ollama.APIBase != "" ||
|
|
v.Moonshot.APIKey != "" || v.Moonshot.APIBase != "" ||
|
|
v.ShengSuanYun.APIKey != "" || v.ShengSuanYun.APIBase != "" ||
|
|
v.DeepSeek.APIKey != "" || v.DeepSeek.APIBase != "" ||
|
|
v.Cerebras.APIKey != "" || v.Cerebras.APIBase != "" ||
|
|
v.VolcEngine.APIKey != "" || v.VolcEngine.APIBase != "" ||
|
|
v.GitHubCopilot.APIKey != "" || v.GitHubCopilot.APIBase != "" ||
|
|
v.Antigravity.APIKey != "" || v.Antigravity.APIBase != "" ||
|
|
v.Qwen.APIKey != "" || v.Qwen.APIBase != ""
|
|
}
|
|
|
|
// ValidateModelList validates all ModelConfig entries in the model_list.
|
|
// It checks that each model_name/model combination is valid.
|
|
func (c *Config) ValidateModelList() error {
|
|
for i := range c.ModelList {
|
|
if err := c.ModelList[i].Validate(); err != nil {
|
|
return fmt.Errorf("model_list[%d]: %w", i, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|