diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 128f8c421..cde190010 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -599,8 +599,8 @@ func gatewayCmd() { agentLoop.SetChannelManager(channelManager) var transcriber *voice.GroqTranscriber - if cfg.Providers.Groq.APIKey != "" { - transcriber = voice.NewGroqTranscriber(cfg.Providers.Groq.APIKey) + if cfg.Providers["groq"].APIKey != "" { + transcriber = voice.NewGroqTranscriber(cfg.Providers["groq"].APIKey) logger.InfoC("voice", "Groq voice transcription enabled") } @@ -722,13 +722,13 @@ func statusCmd() { if _, err := os.Stat(configPath); err == nil { fmt.Printf("Model: %s\n", cfg.Agents.Defaults.Model) - hasOpenRouter := cfg.Providers.OpenRouter.APIKey != "" - hasAnthropic := cfg.Providers.Anthropic.APIKey != "" - hasOpenAI := cfg.Providers.OpenAI.APIKey != "" - hasGemini := cfg.Providers.Gemini.APIKey != "" - hasZhipu := cfg.Providers.Zhipu.APIKey != "" - hasGroq := cfg.Providers.Groq.APIKey != "" - hasVLLM := cfg.Providers.VLLM.APIBase != "" + hasOpenRouter := cfg.Providers["openrouter"].APIKey != "" + hasAnthropic := cfg.Providers["anthropic"].APIKey != "" + hasOpenAI := cfg.Providers["openai"].APIKey != "" + hasGemini := cfg.Providers["gemini"].APIKey != "" + hasZhipu := cfg.Providers["zhipu"].APIKey != "" + hasGroq := cfg.Providers["groq"].APIKey != "" + hasVLLM := cfg.Providers["vllm"].APIBase != "" status := func(enabled bool) string { if enabled { @@ -743,7 +743,7 @@ func statusCmd() { fmt.Println("Zhipu API:", status(hasZhipu)) fmt.Println("Groq API:", status(hasGroq)) if hasVLLM { - fmt.Printf("vLLM/Local: ✓ %s\n", cfg.Providers.VLLM.APIBase) + fmt.Printf("vLLM/Local: ✓ %s\n", cfg.Providers["vllm"].APIBase) } else { fmt.Println("vLLM/Local: not set") } @@ -859,7 +859,8 @@ func authLoginOpenAI(useDeviceCode bool) { appCfg, err := loadConfig() if err == nil { - appCfg.Providers.OpenAI.AuthMethod = "oauth" + temp := appCfg.Providers["openai"] + temp.AuthMethod = "oauth" if err := config.SaveConfig(getConfigPath(), appCfg); err != nil { fmt.Printf("Warning: could not update config: %v\n", err) } @@ -887,9 +888,9 @@ func authLoginPasteToken(provider string) { if err == nil { switch provider { case "anthropic": - appCfg.Providers.Anthropic.AuthMethod = "token" + appCfg.Providers["anthropic"].AuthMethod = "token" case "openai": - appCfg.Providers.OpenAI.AuthMethod = "token" + appCfg.Providers["openai"].AuthMethod = "token" } if err := config.SaveConfig(getConfigPath(), appCfg); err != nil { fmt.Printf("Warning: could not update config: %v\n", err) @@ -923,9 +924,9 @@ func authLogoutCmd() { if err == nil { switch provider { case "openai": - appCfg.Providers.OpenAI.AuthMethod = "" + appCfg.Providers["openai"].AuthMethod = "" case "anthropic": - appCfg.Providers.Anthropic.AuthMethod = "" + appCfg.Providers["anthropic"].AuthMethod = "" } config.SaveConfig(getConfigPath(), appCfg) } @@ -939,8 +940,8 @@ func authLogoutCmd() { appCfg, err := loadConfig() if err == nil { - appCfg.Providers.OpenAI.AuthMethod = "" - appCfg.Providers.Anthropic.AuthMethod = "" + appCfg.Providers["openai"].AuthMethod = "" + appCfg.Providers["anthropic"].AuthMethod = "" config.SaveConfig(getConfigPath(), appCfg) } diff --git a/config/config.example.json b/config/config.example.json index 37c2bcd81..896298c3e 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -9,6 +9,11 @@ "max_tool_iterations": 20 } }, + "router": { + "enabled": false, + "heavy_model": "glm-4.7", + "light_model": "glm-4.7-flash" + }, "channels": { "telegram": { "enabled": false, diff --git a/pkg/config/config.go b/pkg/config/config.go index a1cc978b6..ec16af899 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -44,16 +44,23 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error { } type Config struct { - Agents AgentsConfig `json:"agents"` - Channels ChannelsConfig `json:"channels"` - Providers ProvidersConfig `json:"providers"` - Gateway GatewayConfig `json:"gateway"` - Tools ToolsConfig `json:"tools"` - Heartbeat HeartbeatConfig `json:"heartbeat"` - Devices DevicesConfig `json:"devices"` + Agents AgentsConfig `json:"agents"` + Router RouterConfig `json:"router"` + Channels ChannelsConfig `json:"channels"` + Providers map[string]*ProviderConfig `json:"providers"` + Gateway GatewayConfig `json:"gateway"` + Tools ToolsConfig `json:"tools"` + Heartbeat HeartbeatConfig `json:"heartbeat"` + Devices DevicesConfig `json:"devices"` mu sync.RWMutex } +type RouterConfig struct { + Enabled bool `json:"enabled"` + HeavyModel string `json:"heavy_model"` + LightModel string `json:"light_model"` +} + type AgentsConfig struct { Defaults AgentDefaults `json:"defaults"` } @@ -167,19 +174,19 @@ type DevicesConfig struct { } type ProvidersConfig struct { - Anthropic ProviderConfig `json:"anthropic"` - OpenAI OpenAIProviderConfig `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"` - GitHubCopilot ProviderConfig `json:"github_copilot"` + 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"` + GitHubCopilot ProviderConfig `json:"github_copilot"` } type ProviderConfig struct { @@ -188,6 +195,7 @@ type ProviderConfig struct { 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` + WebSearch bool `json:"web_search" env:"PICOCLAW_PROVIDERS_{{.Name}}_WEB_SEARCH"` } type OpenAIProviderConfig struct { @@ -251,6 +259,11 @@ func DefaultConfig() *Config { MaxToolIterations: 20, }, }, + Router: RouterConfig{ + Enabled: false, + HeavyModel: "glm-4.7", + LightModel: "glm-4.7-flash", + }, Channels: ChannelsConfig{ WhatsApp: WhatsAppConfig{ Enabled: false, @@ -317,17 +330,17 @@ func DefaultConfig() *Config { AllowFrom: FlexibleStringSlice{}, }, }, - Providers: ProvidersConfig{ - Anthropic: ProviderConfig{}, - OpenAI: OpenAIProviderConfig{WebSearch: true}, - OpenRouter: ProviderConfig{}, - Groq: ProviderConfig{}, - Zhipu: ProviderConfig{}, - VLLM: ProviderConfig{}, - Gemini: ProviderConfig{}, - Nvidia: ProviderConfig{}, - Moonshot: ProviderConfig{}, - ShengSuanYun: ProviderConfig{}, + Providers: map[string]*ProviderConfig{ + "anthropic": &ProviderConfig{}, + "openai": &ProviderConfig{WebSearch: true}, + "openrouter": &ProviderConfig{}, + "groq": &ProviderConfig{}, + "zhipu": &ProviderConfig{}, + "vllm": &ProviderConfig{}, + "gemini": &ProviderConfig{}, + "nvidia": &ProviderConfig{}, + "moonshot": &ProviderConfig{}, + "shengsuanyun": &ProviderConfig{}, }, Gateway: GatewayConfig{ Host: "0.0.0.0", @@ -413,32 +426,23 @@ func (c *Config) WorkspacePath() string { return expandHome(c.Agents.Defaults.Workspace) } +//func (c *Config) GetEnabledProviders() { +// c.mu.RLock() +// defer c.mu.RUnlock() +// +// for _, provider := range c.Providers { +// +// } +// +//} + 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 + for _, config := range c.Providers { + if config.APIKey != "" { + return config.APIKey + } } return "" } @@ -446,17 +450,18 @@ func (c *Config) GetAPIKey() string { 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 + + 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["zhipu"].APIKey != "" { + return c.Providers["zhipu"].APIBase } - if c.Providers.VLLM.APIKey != "" && c.Providers.VLLM.APIBase != "" { - return c.Providers.VLLM.APIBase + if c.Providers["vllm"].APIKey != "" && c.Providers["vllm"].APIBase != "" { + return c.Providers["vllm"].APIBase } return "" } diff --git a/pkg/migrate/config.go b/pkg/migrate/config.go index 57032e566..564258b88 100644 --- a/pkg/migrate/config.go +++ b/pkg/migrate/config.go @@ -106,22 +106,20 @@ func ConvertConfig(data map[string]interface{}) (*config.Config, []string, error pc := config.ProviderConfig{APIKey: apiKey, APIBase: apiBase} switch name { case "anthropic": - cfg.Providers.Anthropic = pc + cfg.Providers["anthropic"] = &pc case "openai": - cfg.Providers.OpenAI = config.OpenAIProviderConfig{ - ProviderConfig: pc, - WebSearch: getBoolOrDefault(pMap, "web_search", true), - } + pc.WebSearch = getBoolOrDefault(pMap, "web_search", true) + cfg.Providers["openai"] = &pc case "openrouter": - cfg.Providers.OpenRouter = pc + cfg.Providers["openrouter"] = &pc case "groq": - cfg.Providers.Groq = pc + cfg.Providers["groq"] = &pc case "zhipu": - cfg.Providers.Zhipu = pc + cfg.Providers["zhipu"] = &pc case "vllm": - cfg.Providers.VLLM = pc + cfg.Providers["vllm"] = &pc case "gemini": - cfg.Providers.Gemini = pc + cfg.Providers["gemini"] = &pc } } } @@ -235,26 +233,10 @@ func ConvertConfig(data map[string]interface{}) (*config.Config, []string, error } func MergeConfig(existing, incoming *config.Config) *config.Config { - if existing.Providers.Anthropic.APIKey == "" { - existing.Providers.Anthropic = incoming.Providers.Anthropic - } - if existing.Providers.OpenAI.APIKey == "" { - existing.Providers.OpenAI = incoming.Providers.OpenAI - } - if existing.Providers.OpenRouter.APIKey == "" { - existing.Providers.OpenRouter = incoming.Providers.OpenRouter - } - if existing.Providers.Groq.APIKey == "" { - existing.Providers.Groq = incoming.Providers.Groq - } - if existing.Providers.Zhipu.APIKey == "" { - existing.Providers.Zhipu = incoming.Providers.Zhipu - } - if existing.Providers.VLLM.APIKey == "" && existing.Providers.VLLM.APIBase == "" { - existing.Providers.VLLM = incoming.Providers.VLLM - } - if existing.Providers.Gemini.APIKey == "" { - existing.Providers.Gemini = incoming.Providers.Gemini + for provider, cfg := range existing.Providers { + if incoming.Providers[provider].APIKey != "" { + incoming.Providers[provider] = cfg + } } if !existing.Channels.Telegram.Enabled && incoming.Channels.Telegram.Enabled { diff --git a/pkg/migrate/migrate_test.go b/pkg/migrate/migrate_test.go index be2360aac..53f54e440 100644 --- a/pkg/migrate/migrate_test.go +++ b/pkg/migrate/migrate_test.go @@ -166,14 +166,14 @@ func TestConvertConfig(t *testing.T) { if len(warnings) != 0 { t.Errorf("expected no warnings, got %v", warnings) } - if cfg.Providers.Anthropic.APIKey != "sk-ant-test" { - t.Errorf("Anthropic.APIKey = %q, want %q", cfg.Providers.Anthropic.APIKey, "sk-ant-test") + if cfg.Providers["anthropic"].APIKey != "sk-ant-test" { + t.Errorf("Anthropic.APIKey = %q, want %q", cfg.Providers["anthropic"].APIKey, "sk-ant-test") } - if cfg.Providers.OpenRouter.APIKey != "sk-or-test" { - t.Errorf("OpenRouter.APIKey = %q, want %q", cfg.Providers.OpenRouter.APIKey, "sk-or-test") + if cfg.Providers["openrouter"].APIKey != "sk-or-test" { + t.Errorf("OpenRouter.APIKey = %q, want %q", cfg.Providers["openrouter"].APIKey, "sk-or-test") } - if cfg.Providers.Groq.APIKey != "gsk-test" { - t.Errorf("Groq.APIKey = %q, want %q", cfg.Providers.Groq.APIKey, "gsk-test") + if cfg.Providers["groq"].APIKey != "gsk-test" { + t.Errorf("Groq.APIKey = %q, want %q", cfg.Providers["groq"].APIKey, "gsk-test") } }) @@ -303,32 +303,48 @@ func TestMergeConfig(t *testing.T) { t.Run("fills empty fields", func(t *testing.T) { existing := config.DefaultConfig() incoming := config.DefaultConfig() - incoming.Providers.Anthropic.APIKey = "sk-ant-incoming" - incoming.Providers.OpenRouter.APIKey = "sk-or-incoming" + incoming.Providers = map[string]config.ProviderConfig{ + "anthropic": { + APIKey: "sk-ant-incoming", + }, + "openrouter": { + APIKey: "sk-or-incoming", + }, + } result := MergeConfig(existing, incoming) - if result.Providers.Anthropic.APIKey != "sk-ant-incoming" { - t.Errorf("Anthropic.APIKey = %q, want %q", result.Providers.Anthropic.APIKey, "sk-ant-incoming") + if result.Providers["anthropic"].APIKey != "sk-ant-incoming" { + t.Errorf("Anthropic.APIKey = %q, want %q", result.Providers["anthropic"].APIKey, "sk-ant-incoming") } - if result.Providers.OpenRouter.APIKey != "sk-or-incoming" { - t.Errorf("OpenRouter.APIKey = %q, want %q", result.Providers.OpenRouter.APIKey, "sk-or-incoming") + if result.Providers["openrouter"].APIKey != "sk-or-incoming" { + t.Errorf("OpenRouter.APIKey = %q, want %q", result.Providers["openrouter"].APIKey, "sk-or-incoming") } }) t.Run("preserves existing non-empty fields", func(t *testing.T) { existing := config.DefaultConfig() - existing.Providers.Anthropic.APIKey = "sk-ant-existing" + existing.Providers = map[string]config.ProviderConfig{ + "anthropic": { + APIKey: "sk-ant-existing", + }, + } incoming := config.DefaultConfig() - incoming.Providers.Anthropic.APIKey = "sk-ant-incoming" - incoming.Providers.OpenAI.APIKey = "sk-oai-incoming" + incoming.Providers = map[string]config.ProviderConfig{ + "anthropic": { + APIKey: "sk-ant-incoming", + }, + "openai": { + APIKey: "sk-or-incoming", + }, + } result := MergeConfig(existing, incoming) - if result.Providers.Anthropic.APIKey != "sk-ant-existing" { - t.Errorf("Anthropic.APIKey should be preserved, got %q", result.Providers.Anthropic.APIKey) + if result.Providers["anthropic"].APIKey != "sk-ant-existing" { + t.Errorf("Anthropic.APIKey should be preserved, got %q", result.Providers["anthropic"].APIKey) } - if result.Providers.OpenAI.APIKey != "sk-oai-incoming" { - t.Errorf("OpenAI.APIKey should be filled, got %q", result.Providers.OpenAI.APIKey) + if result.Providers["openai"].APIKey != "sk-oai-incoming" { + t.Errorf("OpenAI.APIKey should be filled, got %q", result.Providers["openai"].APIKey) } }) @@ -682,11 +698,11 @@ func TestRunFullMigration(t *testing.T) { if err != nil { t.Fatalf("loading PicoClaw config: %v", err) } - if picoConfig.Providers.Anthropic.APIKey != "sk-ant-migrate-test" { - t.Errorf("Anthropic.APIKey = %q, want %q", picoConfig.Providers.Anthropic.APIKey, "sk-ant-migrate-test") + if picoConfig.Providers["anthropic"].APIKey != "sk-ant-migrate-test" { + t.Errorf("Anthropic.APIKey = %q, want %q", picoConfig.Providers["anthropic"].APIKey, "sk-ant-migrate-test") } - if picoConfig.Providers.OpenRouter.APIKey != "sk-or-migrate-test" { - t.Errorf("OpenRouter.APIKey = %q, want %q", picoConfig.Providers.OpenRouter.APIKey, "sk-or-migrate-test") + if picoConfig.Providers["openrouter"].APIKey != "sk-or-migrate-test" { + t.Errorf("OpenRouter.APIKey = %q, want %q", picoConfig.Providers["openrouter"].APIKey, "sk-or-migrate-test") } if !picoConfig.Channels.Telegram.Enabled { t.Error("Telegram should be enabled") diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 946aa29d2..df4134846 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -233,74 +233,74 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { if providerName != "" { switch providerName { case "groq": - if cfg.Providers.Groq.APIKey != "" { - apiKey = cfg.Providers.Groq.APIKey - apiBase = cfg.Providers.Groq.APIBase + if cfg.Providers["groq"].APIKey != "" { + apiKey = cfg.Providers["groq"].APIKey + apiBase = cfg.Providers["groq"].APIBase if apiBase == "" { apiBase = "https://api.groq.com/openai/v1" } } case "openai", "gpt": - if cfg.Providers.OpenAI.APIKey != "" || cfg.Providers.OpenAI.AuthMethod != "" { - if cfg.Providers.OpenAI.AuthMethod == "codex-cli" { + if cfg.Providers["openai"].APIKey != "" || cfg.Providers["openai"].AuthMethod != "" { + if cfg.Providers["openai"].AuthMethod == "codex-cli" { c := NewCodexProviderWithTokenSource("", "", CreateCodexCliTokenSource()) - c.enableWebSearch = cfg.Providers.OpenAI.WebSearch + c.enableWebSearch = cfg.Providers["openai"].WebSearch return c, nil } - if cfg.Providers.OpenAI.AuthMethod == "oauth" || cfg.Providers.OpenAI.AuthMethod == "token" { - return createCodexAuthProvider(cfg.Providers.OpenAI.WebSearch) + if cfg.Providers["openai"].AuthMethod == "oauth" || cfg.Providers["openai"].AuthMethod == "token" { + return createCodexAuthProvider(cfg.Providers["openai"].WebSearch) } - apiKey = cfg.Providers.OpenAI.APIKey - apiBase = cfg.Providers.OpenAI.APIBase + apiKey = cfg.Providers["openai"].APIKey + apiBase = cfg.Providers["openai"].APIBase if apiBase == "" { apiBase = "https://api.openai.com/v1" } } case "anthropic", "claude": - if cfg.Providers.Anthropic.APIKey != "" || cfg.Providers.Anthropic.AuthMethod != "" { - if cfg.Providers.Anthropic.AuthMethod == "oauth" || cfg.Providers.Anthropic.AuthMethod == "token" { + if cfg.Providers["anthropic"].APIKey != "" || cfg.Providers["anthropic"].AuthMethod != "" { + if cfg.Providers["anthropic"].AuthMethod == "oauth" || cfg.Providers["anthropic"].AuthMethod == "token" { return createClaudeAuthProvider() } - apiKey = cfg.Providers.Anthropic.APIKey - apiBase = cfg.Providers.Anthropic.APIBase + apiKey = cfg.Providers["anthropic"].APIKey + apiBase = cfg.Providers["anthropic"].APIBase if apiBase == "" { apiBase = "https://api.anthropic.com/v1" } } case "openrouter": - if cfg.Providers.OpenRouter.APIKey != "" { - apiKey = cfg.Providers.OpenRouter.APIKey - if cfg.Providers.OpenRouter.APIBase != "" { - apiBase = cfg.Providers.OpenRouter.APIBase + if cfg.Providers["openrouter"].APIKey != "" { + apiKey = cfg.Providers["openrouter"].APIKey + if cfg.Providers["openrouter"].APIBase != "" { + apiBase = cfg.Providers["openrouter"].APIBase } else { apiBase = "https://openrouter.ai/api/v1" } } case "zhipu", "glm": - if cfg.Providers.Zhipu.APIKey != "" { - apiKey = cfg.Providers.Zhipu.APIKey - apiBase = cfg.Providers.Zhipu.APIBase + if cfg.Providers["zhipu"].APIKey != "" { + apiKey = cfg.Providers["zhipu"].APIKey + apiBase = cfg.Providers["zhipu"].APIBase if apiBase == "" { apiBase = "https://open.bigmodel.cn/api/paas/v4" } } case "gemini", "google": - if cfg.Providers.Gemini.APIKey != "" { - apiKey = cfg.Providers.Gemini.APIKey - apiBase = cfg.Providers.Gemini.APIBase + if cfg.Providers["gemini"].APIKey != "" { + apiKey = cfg.Providers["gemini"].APIKey + apiBase = cfg.Providers["gemini"].APIBase if apiBase == "" { apiBase = "https://generativelanguage.googleapis.com/v1beta" } } case "vllm": - if cfg.Providers.VLLM.APIBase != "" { - apiKey = cfg.Providers.VLLM.APIKey - apiBase = cfg.Providers.VLLM.APIBase + if cfg.Providers["vllm"].APIBase != "" { + apiKey = cfg.Providers["vllm"].APIKey + apiBase = cfg.Providers["vllm"].APIBase } case "shengsuanyun": - if cfg.Providers.ShengSuanYun.APIKey != "" { - apiKey = cfg.Providers.ShengSuanYun.APIKey - apiBase = cfg.Providers.ShengSuanYun.APIBase + if cfg.Providers["shengsuanyun"].APIKey != "" { + apiKey = cfg.Providers["shengsuanyun"].APIKey + apiBase = cfg.Providers["shengsuanyun"].APIBase if apiBase == "" { apiBase = "https://router.shengsuanyun.com/api/v1" } @@ -318,9 +318,9 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { } return NewCodexCliProvider(workspace), nil case "deepseek": - if cfg.Providers.DeepSeek.APIKey != "" { - apiKey = cfg.Providers.DeepSeek.APIKey - apiBase = cfg.Providers.DeepSeek.APIBase + if cfg.Providers["deepseek"].APIKey != "" { + apiKey = cfg.Providers["deepseek"].APIKey + apiBase = cfg.Providers["deepseek"].APIBase if apiBase == "" { apiBase = "https://api.deepseek.com/v1" } @@ -329,12 +329,12 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { } } case "github_copilot", "copilot": - if cfg.Providers.GitHubCopilot.APIBase != "" { - apiBase = cfg.Providers.GitHubCopilot.APIBase + if cfg.Providers["githubcopilot"].APIBase != "" { + apiBase = cfg.Providers["githubcopilot"].APIBase } else { apiBase = "localhost:4321" } - return NewGitHubCopilotProvider(apiBase, cfg.Providers.GitHubCopilot.ConnectMode, model) + return NewGitHubCopilotProvider(apiBase, cfg.Providers["githubcopilot"].ConnectMode, model) } @@ -343,96 +343,96 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { // Fallback: detect provider from model name if apiKey == "" && apiBase == "" { switch { - case (strings.Contains(lowerModel, "kimi") || strings.Contains(lowerModel, "moonshot") || strings.HasPrefix(model, "moonshot/")) && cfg.Providers.Moonshot.APIKey != "": - apiKey = cfg.Providers.Moonshot.APIKey - apiBase = cfg.Providers.Moonshot.APIBase - proxy = cfg.Providers.Moonshot.Proxy + case (strings.Contains(lowerModel, "kimi") || strings.Contains(lowerModel, "moonshot") || strings.HasPrefix(model, "moonshot/")) && cfg.Providers["moonshot"].APIKey != "": + apiKey = cfg.Providers["moonshot"].APIKey + apiBase = cfg.Providers["moonshot"].APIBase + proxy = cfg.Providers["moonshot"].Proxy if apiBase == "" { apiBase = "https://api.moonshot.cn/v1" } case strings.HasPrefix(model, "openrouter/") || strings.HasPrefix(model, "anthropic/") || strings.HasPrefix(model, "openai/") || strings.HasPrefix(model, "meta-llama/") || strings.HasPrefix(model, "deepseek/") || strings.HasPrefix(model, "google/"): - apiKey = cfg.Providers.OpenRouter.APIKey - proxy = cfg.Providers.OpenRouter.Proxy - if cfg.Providers.OpenRouter.APIBase != "" { - apiBase = cfg.Providers.OpenRouter.APIBase + apiKey = cfg.Providers["openrouter"].APIKey + proxy = cfg.Providers["openrouter"].Proxy + if cfg.Providers["openrouter"].APIBase != "" { + apiBase = cfg.Providers["openrouter"].APIBase } else { apiBase = "https://openrouter.ai/api/v1" } - case (strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/")) && (cfg.Providers.Anthropic.APIKey != "" || cfg.Providers.Anthropic.AuthMethod != ""): - if cfg.Providers.Anthropic.AuthMethod == "oauth" || cfg.Providers.Anthropic.AuthMethod == "token" { + case (strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/")) && (cfg.Providers["anthropic"].APIKey != "" || cfg.Providers["anthropic"].AuthMethod != ""): + if cfg.Providers["anthropic"].AuthMethod == "oauth" || cfg.Providers["anthropic"].AuthMethod == "token" { return createClaudeAuthProvider() } - apiKey = cfg.Providers.Anthropic.APIKey - apiBase = cfg.Providers.Anthropic.APIBase - proxy = cfg.Providers.Anthropic.Proxy + apiKey = cfg.Providers["anthropic"].APIKey + apiBase = cfg.Providers["anthropic"].APIBase + proxy = cfg.Providers["anthropic"].Proxy if apiBase == "" { apiBase = "https://api.anthropic.com/v1" } - case (strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/")) && (cfg.Providers.OpenAI.APIKey != "" || cfg.Providers.OpenAI.AuthMethod != ""): - if cfg.Providers.OpenAI.AuthMethod == "oauth" || cfg.Providers.OpenAI.AuthMethod == "token" { - return createCodexAuthProvider(cfg.Providers.OpenAI.WebSearch) + case (strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/")) && (cfg.Providers["openai"].APIKey != "" || cfg.Providers["openai"].AuthMethod != ""): + if cfg.Providers["openai"].AuthMethod == "oauth" || cfg.Providers["openai"].AuthMethod == "token" { + return createCodexAuthProvider(cfg.Providers["openai"].WebSearch) } - apiKey = cfg.Providers.OpenAI.APIKey - apiBase = cfg.Providers.OpenAI.APIBase - proxy = cfg.Providers.OpenAI.Proxy + apiKey = cfg.Providers["openai"].APIKey + apiBase = cfg.Providers["openai"].APIBase + proxy = cfg.Providers["openai"].Proxy if apiBase == "" { apiBase = "https://api.openai.com/v1" } - case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && cfg.Providers.Gemini.APIKey != "": - apiKey = cfg.Providers.Gemini.APIKey - apiBase = cfg.Providers.Gemini.APIBase - proxy = cfg.Providers.Gemini.Proxy + case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && cfg.Providers["gemini"].APIKey != "": + apiKey = cfg.Providers["gemini"].APIKey + apiBase = cfg.Providers["gemini"].APIBase + proxy = cfg.Providers["gemini"].Proxy if apiBase == "" { apiBase = "https://generativelanguage.googleapis.com/v1beta" } - case (strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "zhipu") || strings.Contains(lowerModel, "zai")) && cfg.Providers.Zhipu.APIKey != "": - apiKey = cfg.Providers.Zhipu.APIKey - apiBase = cfg.Providers.Zhipu.APIBase - proxy = cfg.Providers.Zhipu.Proxy + case (strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "zhipu") || strings.Contains(lowerModel, "zai")) && cfg.Providers["zhipu"].APIKey != "": + apiKey = cfg.Providers["zhipu"].APIKey + apiBase = cfg.Providers["zhipu"].APIBase + proxy = cfg.Providers["zhipu"].Proxy if apiBase == "" { apiBase = "https://open.bigmodel.cn/api/paas/v4" } - case (strings.Contains(lowerModel, "groq") || strings.HasPrefix(model, "groq/")) && cfg.Providers.Groq.APIKey != "": - apiKey = cfg.Providers.Groq.APIKey - apiBase = cfg.Providers.Groq.APIBase - proxy = cfg.Providers.Groq.Proxy + case (strings.Contains(lowerModel, "groq") || strings.HasPrefix(model, "groq/")) && cfg.Providers["groq"].APIKey != "": + apiKey = cfg.Providers["groq"].APIKey + apiBase = cfg.Providers["groq"].APIBase + proxy = cfg.Providers["groq"].Proxy if apiBase == "" { apiBase = "https://api.groq.com/openai/v1" } - case (strings.Contains(lowerModel, "nvidia") || strings.HasPrefix(model, "nvidia/")) && cfg.Providers.Nvidia.APIKey != "": - apiKey = cfg.Providers.Nvidia.APIKey - apiBase = cfg.Providers.Nvidia.APIBase - proxy = cfg.Providers.Nvidia.Proxy + case (strings.Contains(lowerModel, "nvidia") || strings.HasPrefix(model, "nvidia/")) && cfg.Providers["nvidia"].APIKey != "": + apiKey = cfg.Providers["nvidia"].APIKey + apiBase = cfg.Providers["nvidia"].APIBase + proxy = cfg.Providers["nvidia"].Proxy if apiBase == "" { apiBase = "https://integrate.api.nvidia.com/v1" } - case (strings.Contains(lowerModel, "ollama") || strings.HasPrefix(model, "ollama/")) && cfg.Providers.Ollama.APIKey != "": + case (strings.Contains(lowerModel, "ollama") || strings.HasPrefix(model, "ollama/")) && cfg.Providers["ollama"].APIKey != "": fmt.Println("Ollama provider selected based on model name prefix") - apiKey = cfg.Providers.Ollama.APIKey - apiBase = cfg.Providers.Ollama.APIBase - proxy = cfg.Providers.Ollama.Proxy + apiKey = cfg.Providers["ollama"].APIKey + apiBase = cfg.Providers["ollama"].APIBase + proxy = cfg.Providers["ollama"].Proxy if apiBase == "" { apiBase = "http://localhost:11434/v1" } fmt.Println("Ollama apiBase:", apiBase) - case cfg.Providers.VLLM.APIBase != "": - apiKey = cfg.Providers.VLLM.APIKey - apiBase = cfg.Providers.VLLM.APIBase - proxy = cfg.Providers.VLLM.Proxy + case cfg.Providers["vllm"].APIBase != "": + apiKey = cfg.Providers["vllm"].APIKey + apiBase = cfg.Providers["vllm"].APIBase + proxy = cfg.Providers["vllm"].Proxy default: - if cfg.Providers.OpenRouter.APIKey != "" { - apiKey = cfg.Providers.OpenRouter.APIKey - proxy = cfg.Providers.OpenRouter.Proxy - if cfg.Providers.OpenRouter.APIBase != "" { - apiBase = cfg.Providers.OpenRouter.APIBase + if cfg.Providers["openrouter"].APIKey != "" { + apiKey = cfg.Providers["openrouter"].APIKey + proxy = cfg.Providers["openrouter"].Proxy + if cfg.Providers["openrouter"].APIBase != "" { + apiBase = cfg.Providers["openrouter"].APIBase } else { apiBase = "https://openrouter.ai/api/v1" }