diff --git a/cmd/picoclaw/cmd_onboard.go b/cmd/picoclaw/cmd_onboard.go index 9c1e9916f..6e61e3267 100644 --- a/cmd/picoclaw/cmd_onboard.go +++ b/cmd/picoclaw/cmd_onboard.go @@ -43,7 +43,13 @@ func onboard() { fmt.Printf("%s picoclaw is ready!\n", logo) fmt.Println("\nNext steps:") fmt.Println(" 1. Add your API key to", configPath) - fmt.Println(" Get one at: https://openrouter.ai/keys") + fmt.Println("") + fmt.Println(" Recommended:") + fmt.Println(" - OpenRouter: https://openrouter.ai/keys (access 100+ models)") + fmt.Println(" - Ollama: https://ollama.com (local, free)") + fmt.Println("") + fmt.Println(" See README.md for 17+ supported providers.") + fmt.Println("") fmt.Println(" 2. Chat: picoclaw agent -m \"Hello!\"") } diff --git a/pkg/config/config.go b/pkg/config/config.go index 6c8d616f3..386b77da2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -49,7 +49,7 @@ type Config struct { Bindings []AgentBinding `json:"bindings,omitempty"` Session SessionConfig `json:"session,omitempty"` Channels ChannelsConfig `json:"channels"` - Providers ProvidersConfig `json:"providers"` + Providers ProvidersConfig `json:"providers,omitempty"` ModelList []ModelConfig `json:"model_list"` // New model-centric provider configuration Gateway GatewayConfig `json:"gateway"` Tools ToolsConfig `json:"tools"` @@ -59,6 +59,31 @@ type Config struct { rrCounters map[string]*atomic.Uint64 // Round-robin counters for load balancing } +// MarshalJSON implements custom JSON marshaling for Config +// to omit providers section when empty and session when empty +func (c Config) MarshalJSON() ([]byte, error) { + type Alias Config + aux := &struct { + Providers *ProvidersConfig `json:"providers,omitempty"` + Session *SessionConfig `json:"session,omitempty"` + *Alias + }{ + Alias: (*Alias)(&c), + } + + // Only include providers if not empty + if !c.Providers.IsEmpty() { + aux.Providers = &c.Providers + } + + // Only include session if not empty + if c.Session.DMScope != "" || len(c.Session.IdentityLinks) > 0 { + aux.Session = &c.Session + } + + return json.Marshal(aux) +} + type AgentsConfig struct { Defaults AgentDefaults `json:"defaults"` List []AgentConfig `json:"list,omitempty"` @@ -272,6 +297,38 @@ type ProvidersConfig struct { Qwen ProviderConfig `json:"qwen"` } +// IsEmpty checks if all provider configs are empty (no API keys or API bases set) +// Note: WebSearch is an optimization option and doesn't count as "non-empty" +func (p ProvidersConfig) IsEmpty() bool { + return p.Anthropic.APIKey == "" && p.Anthropic.APIBase == "" && + p.OpenAI.APIKey == "" && p.OpenAI.APIBase == "" && + p.OpenRouter.APIKey == "" && p.OpenRouter.APIBase == "" && + p.Groq.APIKey == "" && p.Groq.APIBase == "" && + p.Zhipu.APIKey == "" && p.Zhipu.APIBase == "" && + p.VLLM.APIKey == "" && p.VLLM.APIBase == "" && + p.Gemini.APIKey == "" && p.Gemini.APIBase == "" && + p.Nvidia.APIKey == "" && p.Nvidia.APIBase == "" && + p.Ollama.APIKey == "" && p.Ollama.APIBase == "" && + p.Moonshot.APIKey == "" && p.Moonshot.APIBase == "" && + p.ShengSuanYun.APIKey == "" && p.ShengSuanYun.APIBase == "" && + p.DeepSeek.APIKey == "" && p.DeepSeek.APIBase == "" && + p.Cerebras.APIKey == "" && p.Cerebras.APIBase == "" && + p.VolcEngine.APIKey == "" && p.VolcEngine.APIBase == "" && + p.GitHubCopilot.APIKey == "" && p.GitHubCopilot.APIBase == "" && + p.Antigravity.APIKey == "" && p.Antigravity.APIBase == "" && + p.Qwen.APIKey == "" && p.Qwen.APIBase == "" +} + +// MarshalJSON implements custom JSON marshaling for ProvidersConfig +// to omit the entire section when empty +func (p ProvidersConfig) MarshalJSON() ([]byte, error) { + if p.IsEmpty() { + return []byte("null"), nil + } + type Alias ProvidersConfig + return json.Marshal((*Alias)(&p)) +} + 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"` @@ -297,7 +354,7 @@ type ModelConfig struct { // HTTP-based providers APIBase string `json:"api_base,omitempty"` // API endpoint URL - APIKey string `json:"api_key,omitempty"` // API authentication key + APIKey string `json:"api_key"` // API authentication key Proxy string `json:"proxy,omitempty"` // HTTP proxy URL // Special providers (CLI-based, OAuth, etc.) diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 13d1dd156..174cc70c6 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -19,6 +19,8 @@ func DefaultConfig() *Config { MaxToolIterations: 20, }, }, + Bindings: []AgentBinding{}, + Session: SessionConfig{}, Channels: ChannelsConfig{ WhatsApp: WhatsAppConfig{ Enabled: false, @@ -86,23 +88,147 @@ func DefaultConfig() *Config { }, }, Providers: ProvidersConfig{ - Anthropic: ProviderConfig{}, - OpenAI: OpenAIProviderConfig{WebSearch: true}, - OpenRouter: ProviderConfig{}, - Groq: ProviderConfig{}, - Zhipu: ProviderConfig{}, - VLLM: ProviderConfig{}, - Gemini: ProviderConfig{}, - Nvidia: ProviderConfig{}, - Ollama: ProviderConfig{}, - Moonshot: ProviderConfig{}, - ShengSuanYun: ProviderConfig{}, - DeepSeek: ProviderConfig{}, - Cerebras: ProviderConfig{}, - VolcEngine: ProviderConfig{}, - GitHubCopilot: ProviderConfig{}, - Antigravity: ProviderConfig{}, - Qwen: ProviderConfig{}, + OpenAI: OpenAIProviderConfig{WebSearch: true}, + }, + ModelList: []ModelConfig{ + // ============================================ + // Add your API key to the model you want to use + // ============================================ + + // Zhipu AI (智谱) - https://open.bigmodel.cn/usercenter/apikeys + { + ModelName: "glm-4.7", + Model: "zhipu/glm-4.7", + APIBase: "https://open.bigmodel.cn/api/paas/v4", + APIKey: "", + }, + + // OpenAI - https://platform.openai.com/api-keys + { + ModelName: "gpt-4o", + Model: "openai/gpt-4o", + APIBase: "https://api.openai.com/v1", + APIKey: "", + }, + + // Anthropic Claude - https://console.anthropic.com/settings/keys + { + ModelName: "claude-sonnet-4", + Model: "anthropic/claude-sonnet-4-20250514", + APIBase: "https://api.anthropic.com/v1", + APIKey: "", + }, + + // DeepSeek - https://platform.deepseek.com/ + { + ModelName: "deepseek-chat", + Model: "deepseek/deepseek-chat", + APIBase: "https://api.deepseek.com/v1", + APIKey: "", + }, + + // Google Gemini - https://ai.google.dev/ + { + ModelName: "gemini-2.0-flash", + Model: "gemini/gemini-2.0-flash-exp", + APIBase: "https://generativelanguage.googleapis.com/v1beta", + APIKey: "", + }, + + // Qwen (通义千问) - https://dashscope.console.aliyun.com/apiKey + { + ModelName: "qwen-plus", + Model: "qwen/qwen-plus", + APIBase: "https://dashscope.aliyuncs.com/compatible-mode/v1", + APIKey: "", + }, + + // Moonshot (月之暗面) - https://platform.moonshot.cn/console/api-keys + { + ModelName: "moonshot-v1-8k", + Model: "moonshot/moonshot-v1-8k", + APIBase: "https://api.moonshot.cn/v1", + APIKey: "", + }, + + // Groq - https://console.groq.com/keys + { + ModelName: "llama-3.3-70b", + Model: "groq/llama-3.3-70b-versatile", + APIBase: "https://api.groq.com/openai/v1", + APIKey: "", + }, + + // OpenRouter (100+ models) - https://openrouter.ai/keys + { + ModelName: "openrouter-gpt-4o", + Model: "openrouter/openai/gpt-4o", + APIBase: "https://openrouter.ai/api/v1", + APIKey: "", + }, + + // NVIDIA - https://build.nvidia.com/ + { + ModelName: "nemotron-4-340b", + Model: "nvidia/nemotron-4-340b-instruct", + APIBase: "https://integrate.api.nvidia.com/v1", + APIKey: "", + }, + + // Cerebras - https://inference.cerebras.ai/ + { + ModelName: "cerebras-llama-3.3-70b", + Model: "cerebras/llama-3.3-70b", + APIBase: "https://api.cerebras.ai/v1", + APIKey: "", + }, + + // Volcengine (火山引擎) - https://console.volcengine.com/ark + { + ModelName: "doubao-pro", + Model: "volcengine/doubao-pro-32k", + APIBase: "https://ark.cn-beijing.volces.com/api/v3", + APIKey: "", + }, + + // ShengsuanYun (神算云) + { + ModelName: "deepseek-v3", + Model: "shengsuanyun/deepseek-v3", + APIBase: "https://api.shengsuanyun.com/v1", + APIKey: "", + }, + + // Antigravity (Google Cloud Code Assist) - OAuth only + { + ModelName: "gemini-flash", + Model: "antigravity/gemini-3-flash", + AuthMethod: "oauth", + }, + + // GitHub Copilot - https://github.com/settings/tokens + { + ModelName: "copilot-gpt-4o", + Model: "github-copilot/gpt-4o", + APIBase: "http://localhost:4321", + AuthMethod: "oauth", + }, + + // Ollama (local) - https://ollama.com + { + ModelName: "llama3", + Model: "ollama/llama3", + APIBase: "http://localhost:11434/v1", + APIKey: "ollama", + }, + + // VLLM (local) - http://localhost:8000 + { + ModelName: "local-model", + Model: "vllm/custom-model", + APIBase: "http://localhost:8000/v1", + APIKey: "", + }, }, Gateway: GatewayConfig{ Host: "0.0.0.0", @@ -126,12 +252,12 @@ func DefaultConfig() *Config { }, }, Cron: CronToolsConfig{ - ExecTimeoutMinutes: 5, // default 5 minutes for LLM operations + ExecTimeoutMinutes: 5, }, }, Heartbeat: HeartbeatConfig{ Enabled: true, - Interval: 30, // default 30 minutes + Interval: 30, }, Devices: DevicesConfig{ Enabled: false,