update config for router feature

This commit is contained in:
Luke Milby 2026-02-19 09:28:21 -05:00
parent eda6e37332
commit b301f2b15a
6 changed files with 224 additions and 215 deletions

View file

@ -599,8 +599,8 @@ func gatewayCmd() {
agentLoop.SetChannelManager(channelManager) agentLoop.SetChannelManager(channelManager)
var transcriber *voice.GroqTranscriber var transcriber *voice.GroqTranscriber
if cfg.Providers.Groq.APIKey != "" { if cfg.Providers["groq"].APIKey != "" {
transcriber = voice.NewGroqTranscriber(cfg.Providers.Groq.APIKey) transcriber = voice.NewGroqTranscriber(cfg.Providers["groq"].APIKey)
logger.InfoC("voice", "Groq voice transcription enabled") logger.InfoC("voice", "Groq voice transcription enabled")
} }
@ -722,13 +722,13 @@ func statusCmd() {
if _, err := os.Stat(configPath); err == nil { if _, err := os.Stat(configPath); err == nil {
fmt.Printf("Model: %s\n", cfg.Agents.Defaults.Model) fmt.Printf("Model: %s\n", cfg.Agents.Defaults.Model)
hasOpenRouter := cfg.Providers.OpenRouter.APIKey != "" hasOpenRouter := cfg.Providers["openrouter"].APIKey != ""
hasAnthropic := cfg.Providers.Anthropic.APIKey != "" hasAnthropic := cfg.Providers["anthropic"].APIKey != ""
hasOpenAI := cfg.Providers.OpenAI.APIKey != "" hasOpenAI := cfg.Providers["openai"].APIKey != ""
hasGemini := cfg.Providers.Gemini.APIKey != "" hasGemini := cfg.Providers["gemini"].APIKey != ""
hasZhipu := cfg.Providers.Zhipu.APIKey != "" hasZhipu := cfg.Providers["zhipu"].APIKey != ""
hasGroq := cfg.Providers.Groq.APIKey != "" hasGroq := cfg.Providers["groq"].APIKey != ""
hasVLLM := cfg.Providers.VLLM.APIBase != "" hasVLLM := cfg.Providers["vllm"].APIBase != ""
status := func(enabled bool) string { status := func(enabled bool) string {
if enabled { if enabled {
@ -743,7 +743,7 @@ func statusCmd() {
fmt.Println("Zhipu API:", status(hasZhipu)) fmt.Println("Zhipu API:", status(hasZhipu))
fmt.Println("Groq API:", status(hasGroq)) fmt.Println("Groq API:", status(hasGroq))
if hasVLLM { if hasVLLM {
fmt.Printf("vLLM/Local: ✓ %s\n", cfg.Providers.VLLM.APIBase) fmt.Printf("vLLM/Local: ✓ %s\n", cfg.Providers["vllm"].APIBase)
} else { } else {
fmt.Println("vLLM/Local: not set") fmt.Println("vLLM/Local: not set")
} }
@ -859,7 +859,8 @@ func authLoginOpenAI(useDeviceCode bool) {
appCfg, err := loadConfig() appCfg, err := loadConfig()
if err == nil { if err == nil {
appCfg.Providers.OpenAI.AuthMethod = "oauth" temp := appCfg.Providers["openai"]
temp.AuthMethod = "oauth"
if err := config.SaveConfig(getConfigPath(), appCfg); err != nil { if err := config.SaveConfig(getConfigPath(), appCfg); err != nil {
fmt.Printf("Warning: could not update config: %v\n", err) fmt.Printf("Warning: could not update config: %v\n", err)
} }
@ -887,9 +888,9 @@ func authLoginPasteToken(provider string) {
if err == nil { if err == nil {
switch provider { switch provider {
case "anthropic": case "anthropic":
appCfg.Providers.Anthropic.AuthMethod = "token" appCfg.Providers["anthropic"].AuthMethod = "token"
case "openai": case "openai":
appCfg.Providers.OpenAI.AuthMethod = "token" appCfg.Providers["openai"].AuthMethod = "token"
} }
if err := config.SaveConfig(getConfigPath(), appCfg); err != nil { if err := config.SaveConfig(getConfigPath(), appCfg); err != nil {
fmt.Printf("Warning: could not update config: %v\n", err) fmt.Printf("Warning: could not update config: %v\n", err)
@ -923,9 +924,9 @@ func authLogoutCmd() {
if err == nil { if err == nil {
switch provider { switch provider {
case "openai": case "openai":
appCfg.Providers.OpenAI.AuthMethod = "" appCfg.Providers["openai"].AuthMethod = ""
case "anthropic": case "anthropic":
appCfg.Providers.Anthropic.AuthMethod = "" appCfg.Providers["anthropic"].AuthMethod = ""
} }
config.SaveConfig(getConfigPath(), appCfg) config.SaveConfig(getConfigPath(), appCfg)
} }
@ -939,8 +940,8 @@ func authLogoutCmd() {
appCfg, err := loadConfig() appCfg, err := loadConfig()
if err == nil { if err == nil {
appCfg.Providers.OpenAI.AuthMethod = "" appCfg.Providers["openai"].AuthMethod = ""
appCfg.Providers.Anthropic.AuthMethod = "" appCfg.Providers["anthropic"].AuthMethod = ""
config.SaveConfig(getConfigPath(), appCfg) config.SaveConfig(getConfigPath(), appCfg)
} }

View file

@ -9,6 +9,11 @@
"max_tool_iterations": 20 "max_tool_iterations": 20
} }
}, },
"router": {
"enabled": false,
"heavy_model": "glm-4.7",
"light_model": "glm-4.7-flash"
},
"channels": { "channels": {
"telegram": { "telegram": {
"enabled": false, "enabled": false,

View file

@ -45,8 +45,9 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
type Config struct { type Config struct {
Agents AgentsConfig `json:"agents"` Agents AgentsConfig `json:"agents"`
Router RouterConfig `json:"router"`
Channels ChannelsConfig `json:"channels"` Channels ChannelsConfig `json:"channels"`
Providers ProvidersConfig `json:"providers"` Providers map[string]*ProviderConfig `json:"providers"`
Gateway GatewayConfig `json:"gateway"` Gateway GatewayConfig `json:"gateway"`
Tools ToolsConfig `json:"tools"` Tools ToolsConfig `json:"tools"`
Heartbeat HeartbeatConfig `json:"heartbeat"` Heartbeat HeartbeatConfig `json:"heartbeat"`
@ -54,6 +55,12 @@ type Config struct {
mu sync.RWMutex mu sync.RWMutex
} }
type RouterConfig struct {
Enabled bool `json:"enabled"`
HeavyModel string `json:"heavy_model"`
LightModel string `json:"light_model"`
}
type AgentsConfig struct { type AgentsConfig struct {
Defaults AgentDefaults `json:"defaults"` Defaults AgentDefaults `json:"defaults"`
} }
@ -168,7 +175,7 @@ type DevicesConfig struct {
type ProvidersConfig struct { type ProvidersConfig struct {
Anthropic ProviderConfig `json:"anthropic"` Anthropic ProviderConfig `json:"anthropic"`
OpenAI OpenAIProviderConfig `json:"openai"` OpenAI ProviderConfig `json:"openai"`
OpenRouter ProviderConfig `json:"openrouter"` OpenRouter ProviderConfig `json:"openrouter"`
Groq ProviderConfig `json:"groq"` Groq ProviderConfig `json:"groq"`
Zhipu ProviderConfig `json:"zhipu"` Zhipu ProviderConfig `json:"zhipu"`
@ -188,6 +195,7 @@ type ProviderConfig struct {
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"` Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"`
AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"` 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` 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 { type OpenAIProviderConfig struct {
@ -251,6 +259,11 @@ func DefaultConfig() *Config {
MaxToolIterations: 20, MaxToolIterations: 20,
}, },
}, },
Router: RouterConfig{
Enabled: false,
HeavyModel: "glm-4.7",
LightModel: "glm-4.7-flash",
},
Channels: ChannelsConfig{ Channels: ChannelsConfig{
WhatsApp: WhatsAppConfig{ WhatsApp: WhatsAppConfig{
Enabled: false, Enabled: false,
@ -317,17 +330,17 @@ func DefaultConfig() *Config {
AllowFrom: FlexibleStringSlice{}, AllowFrom: FlexibleStringSlice{},
}, },
}, },
Providers: ProvidersConfig{ Providers: map[string]*ProviderConfig{
Anthropic: ProviderConfig{}, "anthropic": &ProviderConfig{},
OpenAI: OpenAIProviderConfig{WebSearch: true}, "openai": &ProviderConfig{WebSearch: true},
OpenRouter: ProviderConfig{}, "openrouter": &ProviderConfig{},
Groq: ProviderConfig{}, "groq": &ProviderConfig{},
Zhipu: ProviderConfig{}, "zhipu": &ProviderConfig{},
VLLM: ProviderConfig{}, "vllm": &ProviderConfig{},
Gemini: ProviderConfig{}, "gemini": &ProviderConfig{},
Nvidia: ProviderConfig{}, "nvidia": &ProviderConfig{},
Moonshot: ProviderConfig{}, "moonshot": &ProviderConfig{},
ShengSuanYun: ProviderConfig{}, "shengsuanyun": &ProviderConfig{},
}, },
Gateway: GatewayConfig{ Gateway: GatewayConfig{
Host: "0.0.0.0", Host: "0.0.0.0",
@ -413,32 +426,23 @@ func (c *Config) WorkspacePath() string {
return expandHome(c.Agents.Defaults.Workspace) 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 { func (c *Config) GetAPIKey() string {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
if c.Providers.OpenRouter.APIKey != "" { for _, config := range c.Providers {
return c.Providers.OpenRouter.APIKey if config.APIKey != "" {
return config.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
} }
return "" return ""
} }
@ -446,17 +450,18 @@ func (c *Config) GetAPIKey() string {
func (c *Config) GetAPIBase() string { func (c *Config) GetAPIBase() string {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
if c.Providers.OpenRouter.APIKey != "" {
if c.Providers.OpenRouter.APIBase != "" { if c.Providers["openrouter"].APIKey != "" {
return c.Providers.OpenRouter.APIBase if c.Providers["openrouter"].APIBase != "" {
return c.Providers["openrouter"].APIBase
} }
return "https://openrouter.ai/api/v1" return "https://openrouter.ai/api/v1"
} }
if c.Providers.Zhipu.APIKey != "" { if c.Providers["zhipu"].APIKey != "" {
return c.Providers.Zhipu.APIBase return c.Providers["zhipu"].APIBase
} }
if c.Providers.VLLM.APIKey != "" && c.Providers.VLLM.APIBase != "" { if c.Providers["vllm"].APIKey != "" && c.Providers["vllm"].APIBase != "" {
return c.Providers.VLLM.APIBase return c.Providers["vllm"].APIBase
} }
return "" return ""
} }

View file

@ -106,22 +106,20 @@ func ConvertConfig(data map[string]interface{}) (*config.Config, []string, error
pc := config.ProviderConfig{APIKey: apiKey, APIBase: apiBase} pc := config.ProviderConfig{APIKey: apiKey, APIBase: apiBase}
switch name { switch name {
case "anthropic": case "anthropic":
cfg.Providers.Anthropic = pc cfg.Providers["anthropic"] = &pc
case "openai": case "openai":
cfg.Providers.OpenAI = config.OpenAIProviderConfig{ pc.WebSearch = getBoolOrDefault(pMap, "web_search", true)
ProviderConfig: pc, cfg.Providers["openai"] = &pc
WebSearch: getBoolOrDefault(pMap, "web_search", true),
}
case "openrouter": case "openrouter":
cfg.Providers.OpenRouter = pc cfg.Providers["openrouter"] = &pc
case "groq": case "groq":
cfg.Providers.Groq = pc cfg.Providers["groq"] = &pc
case "zhipu": case "zhipu":
cfg.Providers.Zhipu = pc cfg.Providers["zhipu"] = &pc
case "vllm": case "vllm":
cfg.Providers.VLLM = pc cfg.Providers["vllm"] = &pc
case "gemini": 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 { func MergeConfig(existing, incoming *config.Config) *config.Config {
if existing.Providers.Anthropic.APIKey == "" { for provider, cfg := range existing.Providers {
existing.Providers.Anthropic = incoming.Providers.Anthropic if incoming.Providers[provider].APIKey != "" {
incoming.Providers[provider] = cfg
} }
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
} }
if !existing.Channels.Telegram.Enabled && incoming.Channels.Telegram.Enabled { if !existing.Channels.Telegram.Enabled && incoming.Channels.Telegram.Enabled {

View file

@ -166,14 +166,14 @@ func TestConvertConfig(t *testing.T) {
if len(warnings) != 0 { if len(warnings) != 0 {
t.Errorf("expected no warnings, got %v", warnings) t.Errorf("expected no warnings, got %v", warnings)
} }
if 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") t.Errorf("Anthropic.APIKey = %q, want %q", cfg.Providers["anthropic"].APIKey, "sk-ant-test")
} }
if 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") t.Errorf("OpenRouter.APIKey = %q, want %q", cfg.Providers["openrouter"].APIKey, "sk-or-test")
} }
if 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") 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) { t.Run("fills empty fields", func(t *testing.T) {
existing := config.DefaultConfig() existing := config.DefaultConfig()
incoming := config.DefaultConfig() incoming := config.DefaultConfig()
incoming.Providers.Anthropic.APIKey = "sk-ant-incoming" incoming.Providers = map[string]config.ProviderConfig{
incoming.Providers.OpenRouter.APIKey = "sk-or-incoming" "anthropic": {
APIKey: "sk-ant-incoming",
},
"openrouter": {
APIKey: "sk-or-incoming",
},
}
result := MergeConfig(existing, incoming) result := MergeConfig(existing, incoming)
if 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") t.Errorf("Anthropic.APIKey = %q, want %q", result.Providers["anthropic"].APIKey, "sk-ant-incoming")
} }
if 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.Errorf("OpenRouter.APIKey = %q, want %q", result.Providers["openrouter"].APIKey, "sk-or-incoming")
} }
}) })
t.Run("preserves existing non-empty fields", func(t *testing.T) { t.Run("preserves existing non-empty fields", func(t *testing.T) {
existing := config.DefaultConfig() 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 := config.DefaultConfig()
incoming.Providers.Anthropic.APIKey = "sk-ant-incoming" incoming.Providers = map[string]config.ProviderConfig{
incoming.Providers.OpenAI.APIKey = "sk-oai-incoming" "anthropic": {
APIKey: "sk-ant-incoming",
},
"openai": {
APIKey: "sk-or-incoming",
},
}
result := MergeConfig(existing, incoming) result := MergeConfig(existing, incoming)
if result.Providers.Anthropic.APIKey != "sk-ant-existing" { if result.Providers["anthropic"].APIKey != "sk-ant-existing" {
t.Errorf("Anthropic.APIKey should be preserved, got %q", result.Providers.Anthropic.APIKey) t.Errorf("Anthropic.APIKey should be preserved, got %q", result.Providers["anthropic"].APIKey)
} }
if result.Providers.OpenAI.APIKey != "sk-oai-incoming" { if result.Providers["openai"].APIKey != "sk-oai-incoming" {
t.Errorf("OpenAI.APIKey should be filled, got %q", result.Providers.OpenAI.APIKey) 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 { if err != nil {
t.Fatalf("loading PicoClaw config: %v", err) t.Fatalf("loading PicoClaw config: %v", err)
} }
if 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") t.Errorf("Anthropic.APIKey = %q, want %q", picoConfig.Providers["anthropic"].APIKey, "sk-ant-migrate-test")
} }
if 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") t.Errorf("OpenRouter.APIKey = %q, want %q", picoConfig.Providers["openrouter"].APIKey, "sk-or-migrate-test")
} }
if !picoConfig.Channels.Telegram.Enabled { if !picoConfig.Channels.Telegram.Enabled {
t.Error("Telegram should be enabled") t.Error("Telegram should be enabled")

View file

@ -233,74 +233,74 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) {
if providerName != "" { if providerName != "" {
switch providerName { switch providerName {
case "groq": case "groq":
if cfg.Providers.Groq.APIKey != "" { if cfg.Providers["groq"].APIKey != "" {
apiKey = cfg.Providers.Groq.APIKey apiKey = cfg.Providers["groq"].APIKey
apiBase = cfg.Providers.Groq.APIBase apiBase = cfg.Providers["groq"].APIBase
if apiBase == "" { if apiBase == "" {
apiBase = "https://api.groq.com/openai/v1" apiBase = "https://api.groq.com/openai/v1"
} }
} }
case "openai", "gpt": case "openai", "gpt":
if cfg.Providers.OpenAI.APIKey != "" || cfg.Providers.OpenAI.AuthMethod != "" { if cfg.Providers["openai"].APIKey != "" || cfg.Providers["openai"].AuthMethod != "" {
if cfg.Providers.OpenAI.AuthMethod == "codex-cli" { if cfg.Providers["openai"].AuthMethod == "codex-cli" {
c := NewCodexProviderWithTokenSource("", "", CreateCodexCliTokenSource()) c := NewCodexProviderWithTokenSource("", "", CreateCodexCliTokenSource())
c.enableWebSearch = cfg.Providers.OpenAI.WebSearch c.enableWebSearch = cfg.Providers["openai"].WebSearch
return c, nil return c, nil
} }
if cfg.Providers.OpenAI.AuthMethod == "oauth" || cfg.Providers.OpenAI.AuthMethod == "token" { if cfg.Providers["openai"].AuthMethod == "oauth" || cfg.Providers["openai"].AuthMethod == "token" {
return createCodexAuthProvider(cfg.Providers.OpenAI.WebSearch) return createCodexAuthProvider(cfg.Providers["openai"].WebSearch)
} }
apiKey = cfg.Providers.OpenAI.APIKey apiKey = cfg.Providers["openai"].APIKey
apiBase = cfg.Providers.OpenAI.APIBase apiBase = cfg.Providers["openai"].APIBase
if apiBase == "" { if apiBase == "" {
apiBase = "https://api.openai.com/v1" apiBase = "https://api.openai.com/v1"
} }
} }
case "anthropic", "claude": case "anthropic", "claude":
if cfg.Providers.Anthropic.APIKey != "" || cfg.Providers.Anthropic.AuthMethod != "" { if cfg.Providers["anthropic"].APIKey != "" || cfg.Providers["anthropic"].AuthMethod != "" {
if cfg.Providers.Anthropic.AuthMethod == "oauth" || cfg.Providers.Anthropic.AuthMethod == "token" { if cfg.Providers["anthropic"].AuthMethod == "oauth" || cfg.Providers["anthropic"].AuthMethod == "token" {
return createClaudeAuthProvider() return createClaudeAuthProvider()
} }
apiKey = cfg.Providers.Anthropic.APIKey apiKey = cfg.Providers["anthropic"].APIKey
apiBase = cfg.Providers.Anthropic.APIBase apiBase = cfg.Providers["anthropic"].APIBase
if apiBase == "" { if apiBase == "" {
apiBase = "https://api.anthropic.com/v1" apiBase = "https://api.anthropic.com/v1"
} }
} }
case "openrouter": case "openrouter":
if cfg.Providers.OpenRouter.APIKey != "" { if cfg.Providers["openrouter"].APIKey != "" {
apiKey = cfg.Providers.OpenRouter.APIKey apiKey = cfg.Providers["openrouter"].APIKey
if cfg.Providers.OpenRouter.APIBase != "" { if cfg.Providers["openrouter"].APIBase != "" {
apiBase = cfg.Providers.OpenRouter.APIBase apiBase = cfg.Providers["openrouter"].APIBase
} else { } else {
apiBase = "https://openrouter.ai/api/v1" apiBase = "https://openrouter.ai/api/v1"
} }
} }
case "zhipu", "glm": case "zhipu", "glm":
if cfg.Providers.Zhipu.APIKey != "" { if cfg.Providers["zhipu"].APIKey != "" {
apiKey = cfg.Providers.Zhipu.APIKey apiKey = cfg.Providers["zhipu"].APIKey
apiBase = cfg.Providers.Zhipu.APIBase apiBase = cfg.Providers["zhipu"].APIBase
if apiBase == "" { if apiBase == "" {
apiBase = "https://open.bigmodel.cn/api/paas/v4" apiBase = "https://open.bigmodel.cn/api/paas/v4"
} }
} }
case "gemini", "google": case "gemini", "google":
if cfg.Providers.Gemini.APIKey != "" { if cfg.Providers["gemini"].APIKey != "" {
apiKey = cfg.Providers.Gemini.APIKey apiKey = cfg.Providers["gemini"].APIKey
apiBase = cfg.Providers.Gemini.APIBase apiBase = cfg.Providers["gemini"].APIBase
if apiBase == "" { if apiBase == "" {
apiBase = "https://generativelanguage.googleapis.com/v1beta" apiBase = "https://generativelanguage.googleapis.com/v1beta"
} }
} }
case "vllm": case "vllm":
if cfg.Providers.VLLM.APIBase != "" { if cfg.Providers["vllm"].APIBase != "" {
apiKey = cfg.Providers.VLLM.APIKey apiKey = cfg.Providers["vllm"].APIKey
apiBase = cfg.Providers.VLLM.APIBase apiBase = cfg.Providers["vllm"].APIBase
} }
case "shengsuanyun": case "shengsuanyun":
if cfg.Providers.ShengSuanYun.APIKey != "" { if cfg.Providers["shengsuanyun"].APIKey != "" {
apiKey = cfg.Providers.ShengSuanYun.APIKey apiKey = cfg.Providers["shengsuanyun"].APIKey
apiBase = cfg.Providers.ShengSuanYun.APIBase apiBase = cfg.Providers["shengsuanyun"].APIBase
if apiBase == "" { if apiBase == "" {
apiBase = "https://router.shengsuanyun.com/api/v1" apiBase = "https://router.shengsuanyun.com/api/v1"
} }
@ -318,9 +318,9 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) {
} }
return NewCodexCliProvider(workspace), nil return NewCodexCliProvider(workspace), nil
case "deepseek": case "deepseek":
if cfg.Providers.DeepSeek.APIKey != "" { if cfg.Providers["deepseek"].APIKey != "" {
apiKey = cfg.Providers.DeepSeek.APIKey apiKey = cfg.Providers["deepseek"].APIKey
apiBase = cfg.Providers.DeepSeek.APIBase apiBase = cfg.Providers["deepseek"].APIBase
if apiBase == "" { if apiBase == "" {
apiBase = "https://api.deepseek.com/v1" apiBase = "https://api.deepseek.com/v1"
} }
@ -329,12 +329,12 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) {
} }
} }
case "github_copilot", "copilot": case "github_copilot", "copilot":
if cfg.Providers.GitHubCopilot.APIBase != "" { if cfg.Providers["githubcopilot"].APIBase != "" {
apiBase = cfg.Providers.GitHubCopilot.APIBase apiBase = cfg.Providers["githubcopilot"].APIBase
} else { } else {
apiBase = "localhost:4321" 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 // Fallback: detect provider from model name
if apiKey == "" && apiBase == "" { if apiKey == "" && apiBase == "" {
switch { switch {
case (strings.Contains(lowerModel, "kimi") || strings.Contains(lowerModel, "moonshot") || strings.HasPrefix(model, "moonshot/")) && cfg.Providers.Moonshot.APIKey != "": case (strings.Contains(lowerModel, "kimi") || strings.Contains(lowerModel, "moonshot") || strings.HasPrefix(model, "moonshot/")) && cfg.Providers["moonshot"].APIKey != "":
apiKey = cfg.Providers.Moonshot.APIKey apiKey = cfg.Providers["moonshot"].APIKey
apiBase = cfg.Providers.Moonshot.APIBase apiBase = cfg.Providers["moonshot"].APIBase
proxy = cfg.Providers.Moonshot.Proxy proxy = cfg.Providers["moonshot"].Proxy
if apiBase == "" { if apiBase == "" {
apiBase = "https://api.moonshot.cn/v1" 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/"): 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 apiKey = cfg.Providers["openrouter"].APIKey
proxy = cfg.Providers.OpenRouter.Proxy proxy = cfg.Providers["openrouter"].Proxy
if cfg.Providers.OpenRouter.APIBase != "" { if cfg.Providers["openrouter"].APIBase != "" {
apiBase = cfg.Providers.OpenRouter.APIBase apiBase = cfg.Providers["openrouter"].APIBase
} else { } else {
apiBase = "https://openrouter.ai/api/v1" apiBase = "https://openrouter.ai/api/v1"
} }
case (strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/")) && (cfg.Providers.Anthropic.APIKey != "" || cfg.Providers.Anthropic.AuthMethod != ""): 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" { if cfg.Providers["anthropic"].AuthMethod == "oauth" || cfg.Providers["anthropic"].AuthMethod == "token" {
return createClaudeAuthProvider() return createClaudeAuthProvider()
} }
apiKey = cfg.Providers.Anthropic.APIKey apiKey = cfg.Providers["anthropic"].APIKey
apiBase = cfg.Providers.Anthropic.APIBase apiBase = cfg.Providers["anthropic"].APIBase
proxy = cfg.Providers.Anthropic.Proxy proxy = cfg.Providers["anthropic"].Proxy
if apiBase == "" { if apiBase == "" {
apiBase = "https://api.anthropic.com/v1" apiBase = "https://api.anthropic.com/v1"
} }
case (strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/")) && (cfg.Providers.OpenAI.APIKey != "" || cfg.Providers.OpenAI.AuthMethod != ""): 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" { if cfg.Providers["openai"].AuthMethod == "oauth" || cfg.Providers["openai"].AuthMethod == "token" {
return createCodexAuthProvider(cfg.Providers.OpenAI.WebSearch) return createCodexAuthProvider(cfg.Providers["openai"].WebSearch)
} }
apiKey = cfg.Providers.OpenAI.APIKey apiKey = cfg.Providers["openai"].APIKey
apiBase = cfg.Providers.OpenAI.APIBase apiBase = cfg.Providers["openai"].APIBase
proxy = cfg.Providers.OpenAI.Proxy proxy = cfg.Providers["openai"].Proxy
if apiBase == "" { if apiBase == "" {
apiBase = "https://api.openai.com/v1" apiBase = "https://api.openai.com/v1"
} }
case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && cfg.Providers.Gemini.APIKey != "": case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && cfg.Providers["gemini"].APIKey != "":
apiKey = cfg.Providers.Gemini.APIKey apiKey = cfg.Providers["gemini"].APIKey
apiBase = cfg.Providers.Gemini.APIBase apiBase = cfg.Providers["gemini"].APIBase
proxy = cfg.Providers.Gemini.Proxy proxy = cfg.Providers["gemini"].Proxy
if apiBase == "" { if apiBase == "" {
apiBase = "https://generativelanguage.googleapis.com/v1beta" apiBase = "https://generativelanguage.googleapis.com/v1beta"
} }
case (strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "zhipu") || strings.Contains(lowerModel, "zai")) && cfg.Providers.Zhipu.APIKey != "": case (strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "zhipu") || strings.Contains(lowerModel, "zai")) && cfg.Providers["zhipu"].APIKey != "":
apiKey = cfg.Providers.Zhipu.APIKey apiKey = cfg.Providers["zhipu"].APIKey
apiBase = cfg.Providers.Zhipu.APIBase apiBase = cfg.Providers["zhipu"].APIBase
proxy = cfg.Providers.Zhipu.Proxy proxy = cfg.Providers["zhipu"].Proxy
if apiBase == "" { if apiBase == "" {
apiBase = "https://open.bigmodel.cn/api/paas/v4" apiBase = "https://open.bigmodel.cn/api/paas/v4"
} }
case (strings.Contains(lowerModel, "groq") || strings.HasPrefix(model, "groq/")) && cfg.Providers.Groq.APIKey != "": case (strings.Contains(lowerModel, "groq") || strings.HasPrefix(model, "groq/")) && cfg.Providers["groq"].APIKey != "":
apiKey = cfg.Providers.Groq.APIKey apiKey = cfg.Providers["groq"].APIKey
apiBase = cfg.Providers.Groq.APIBase apiBase = cfg.Providers["groq"].APIBase
proxy = cfg.Providers.Groq.Proxy proxy = cfg.Providers["groq"].Proxy
if apiBase == "" { if apiBase == "" {
apiBase = "https://api.groq.com/openai/v1" apiBase = "https://api.groq.com/openai/v1"
} }
case (strings.Contains(lowerModel, "nvidia") || strings.HasPrefix(model, "nvidia/")) && cfg.Providers.Nvidia.APIKey != "": case (strings.Contains(lowerModel, "nvidia") || strings.HasPrefix(model, "nvidia/")) && cfg.Providers["nvidia"].APIKey != "":
apiKey = cfg.Providers.Nvidia.APIKey apiKey = cfg.Providers["nvidia"].APIKey
apiBase = cfg.Providers.Nvidia.APIBase apiBase = cfg.Providers["nvidia"].APIBase
proxy = cfg.Providers.Nvidia.Proxy proxy = cfg.Providers["nvidia"].Proxy
if apiBase == "" { if apiBase == "" {
apiBase = "https://integrate.api.nvidia.com/v1" 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") fmt.Println("Ollama provider selected based on model name prefix")
apiKey = cfg.Providers.Ollama.APIKey apiKey = cfg.Providers["ollama"].APIKey
apiBase = cfg.Providers.Ollama.APIBase apiBase = cfg.Providers["ollama"].APIBase
proxy = cfg.Providers.Ollama.Proxy proxy = cfg.Providers["ollama"].Proxy
if apiBase == "" { if apiBase == "" {
apiBase = "http://localhost:11434/v1" apiBase = "http://localhost:11434/v1"
} }
fmt.Println("Ollama apiBase:", apiBase) fmt.Println("Ollama apiBase:", apiBase)
case cfg.Providers.VLLM.APIBase != "": case cfg.Providers["vllm"].APIBase != "":
apiKey = cfg.Providers.VLLM.APIKey apiKey = cfg.Providers["vllm"].APIKey
apiBase = cfg.Providers.VLLM.APIBase apiBase = cfg.Providers["vllm"].APIBase
proxy = cfg.Providers.VLLM.Proxy proxy = cfg.Providers["vllm"].Proxy
default: default:
if cfg.Providers.OpenRouter.APIKey != "" { if cfg.Providers["openrouter"].APIKey != "" {
apiKey = cfg.Providers.OpenRouter.APIKey apiKey = cfg.Providers["openrouter"].APIKey
proxy = cfg.Providers.OpenRouter.Proxy proxy = cfg.Providers["openrouter"].Proxy
if cfg.Providers.OpenRouter.APIBase != "" { if cfg.Providers["openrouter"].APIBase != "" {
apiBase = cfg.Providers.OpenRouter.APIBase apiBase = cfg.Providers["openrouter"].APIBase
} else { } else {
apiBase = "https://openrouter.ai/api/v1" apiBase = "https://openrouter.ai/api/v1"
} }