fix: check for missing api_base field to avoid default value pollution
fix: remove default ModelList from default config. add it back only when necessary
This commit is contained in:
parent
7cbfa89a96
commit
80f17964e3
4 changed files with 233 additions and 156 deletions
|
|
@ -32,6 +32,7 @@ func onboard() {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := config.DefaultConfig()
|
cfg := config.DefaultConfig()
|
||||||
|
cfg.ModelList = config.DefaultModelList() // Populate default model list
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||||
fmt.Printf("Error saving config: %v\n", err)
|
fmt.Printf("Error saving config: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
||||||
|
|
@ -503,6 +503,19 @@ func LoadConfig(path string) (*Config, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var raw map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, hasModelList := raw["model_list"]; !hasModelList {
|
||||||
|
// only set the default model list if the config file doesn't have the old providers config either
|
||||||
|
if _, hasProviders := raw["providers"]; !hasProviders {
|
||||||
|
cfg.ModelList = DefaultModelList()
|
||||||
|
}
|
||||||
|
// otherwise, ConvertProvidersToModelList(cfg) will handle the default model list based on the providers config
|
||||||
|
}
|
||||||
|
|
||||||
if err := env.Parse(cfg); err != nil {
|
if err := env.Parse(cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,11 @@ func TestDefaultConfig_OpenAIWebSearchEnabled(t *testing.T) {
|
||||||
func TestLoadConfig_OpenAIWebSearchDefaultsTrueWhenUnset(t *testing.T) {
|
func TestLoadConfig_OpenAIWebSearchDefaultsTrueWhenUnset(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
configPath := filepath.Join(dir, "config.json")
|
configPath := filepath.Join(dir, "config.json")
|
||||||
if err := os.WriteFile(configPath, []byte(`{"providers":{"openai":{"api_base":""}}}`), 0o600); err != nil {
|
if err := os.WriteFile(
|
||||||
|
configPath,
|
||||||
|
[]byte(`{"providers":{"openai":{"api_base":""}},"model_list":[{"model_name":"gpt-5","model":"openai/gpt-5"}]}`),
|
||||||
|
0o600,
|
||||||
|
); err != nil {
|
||||||
t.Fatalf("WriteFile() error: %v", err)
|
t.Fatalf("WriteFile() error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,7 +384,13 @@ func TestLoadConfig_OpenAIWebSearchDefaultsTrueWhenUnset(t *testing.T) {
|
||||||
func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
|
func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
configPath := filepath.Join(dir, "config.json")
|
configPath := filepath.Join(dir, "config.json")
|
||||||
if err := os.WriteFile(configPath, []byte(`{"providers":{"openai":{"web_search":false}}}`), 0o600); err != nil {
|
if err := os.WriteFile(
|
||||||
|
configPath,
|
||||||
|
[]byte(
|
||||||
|
`{"providers":{"openai":{"web_search":false}},"model_list":[{"model_name":"gpt-5","model":"openai/gpt-5"}]}`,
|
||||||
|
),
|
||||||
|
0o600,
|
||||||
|
); err != nil {
|
||||||
t.Fatalf("WriteFile() error: %v", err)
|
t.Fatalf("WriteFile() error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -392,3 +402,56 @@ func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
|
||||||
t.Fatal("OpenAI codex web search should be false when disabled in config file")
|
t.Fatal("OpenAI codex web search should be false when disabled in config file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadConfig_NoErrorsWhenModelListMissing(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
configPath := filepath.Join(dir, "config.json")
|
||||||
|
|
||||||
|
data := `{
|
||||||
|
"providers": {
|
||||||
|
"zhipu": {
|
||||||
|
"api_key": "abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
if err := os.WriteFile(configPath, []byte(data), 0o600); err != nil {
|
||||||
|
t.Fatalf("WriteFile() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected LoadConfig to pass even when model_list is missing, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadConfig_AllowsModelListWithoutAPIBaseField(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
configPath := filepath.Join(dir, "config.json")
|
||||||
|
|
||||||
|
data := `{
|
||||||
|
"model_list": [
|
||||||
|
{
|
||||||
|
"model_name": "gpt-5.3-codex",
|
||||||
|
"model": "openai/gpt-5.3-codex",
|
||||||
|
"api_key": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
if err := os.WriteFile(configPath, []byte(data), 0o600); err != nil {
|
||||||
|
t.Fatalf("WriteFile() error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() should allow model_list entries without explicit api_base field, got error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.ModelList) != 1 {
|
||||||
|
t.Fatalf("expected one model_list entry, got %d", len(cfg.ModelList))
|
||||||
|
}
|
||||||
|
if cfg.ModelList[0].APIBase != "" {
|
||||||
|
t.Fatalf("expected api_base to remain empty when omitted, got %q", cfg.ModelList[0].APIBase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,159 @@
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
func DefaultModelList() []ModelConfig {
|
||||||
|
return []ModelConfig{
|
||||||
|
// OpenAI - https://platform.openai.com/api-keys
|
||||||
|
{
|
||||||
|
ModelName: "gpt-5.2",
|
||||||
|
Model: "openai/gpt-5.2",
|
||||||
|
APIBase: "https://api.openai.com/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Anthropic Claude - https://console.anthropic.com/settings/keys
|
||||||
|
{
|
||||||
|
ModelName: "claude-sonnet-4.6",
|
||||||
|
Model: "anthropic/claude-sonnet-4.6",
|
||||||
|
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-auto",
|
||||||
|
Model: "openrouter/auto",
|
||||||
|
APIBase: "https://openrouter.ai/api/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ModelName: "openrouter-gpt-5.2",
|
||||||
|
Model: "openrouter/openai/gpt-5.2",
|
||||||
|
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-5.2",
|
||||||
|
Model: "github-copilot/gpt-5.2",
|
||||||
|
APIBase: "http://localhost:4321",
|
||||||
|
AuthMethod: "oauth",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Ollama (local) - https://ollama.com
|
||||||
|
{
|
||||||
|
ModelName: "llama3",
|
||||||
|
Model: "ollama/llama3",
|
||||||
|
APIBase: "http://localhost:11434/v1",
|
||||||
|
APIKey: "ollama",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mistral AI - https://console.mistral.ai/api-keys
|
||||||
|
{
|
||||||
|
ModelName: "mistral-small",
|
||||||
|
Model: "mistral/mistral-small-latest",
|
||||||
|
APIBase: "https://api.mistral.ai/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
// VLLM (local) - http://localhost:8000
|
||||||
|
{
|
||||||
|
ModelName: "local-model",
|
||||||
|
Model: "vllm/custom-model",
|
||||||
|
APIBase: "http://localhost:8000/v1",
|
||||||
|
APIKey: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultConfig returns the default configuration for PicoClaw.
|
// DefaultConfig returns the default configuration for PicoClaw.
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
|
|
@ -117,160 +270,7 @@ func DefaultConfig() *Config {
|
||||||
Providers: ProvidersConfig{
|
Providers: ProvidersConfig{
|
||||||
OpenAI: OpenAIProviderConfig{WebSearch: true},
|
OpenAI: OpenAIProviderConfig{WebSearch: true},
|
||||||
},
|
},
|
||||||
ModelList: []ModelConfig{
|
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-5.2",
|
|
||||||
Model: "openai/gpt-5.2",
|
|
||||||
APIBase: "https://api.openai.com/v1",
|
|
||||||
APIKey: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Anthropic Claude - https://console.anthropic.com/settings/keys
|
|
||||||
{
|
|
||||||
ModelName: "claude-sonnet-4.6",
|
|
||||||
Model: "anthropic/claude-sonnet-4.6",
|
|
||||||
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-auto",
|
|
||||||
Model: "openrouter/auto",
|
|
||||||
APIBase: "https://openrouter.ai/api/v1",
|
|
||||||
APIKey: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ModelName: "openrouter-gpt-5.2",
|
|
||||||
Model: "openrouter/openai/gpt-5.2",
|
|
||||||
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-5.2",
|
|
||||||
Model: "github-copilot/gpt-5.2",
|
|
||||||
APIBase: "http://localhost:4321",
|
|
||||||
AuthMethod: "oauth",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Ollama (local) - https://ollama.com
|
|
||||||
{
|
|
||||||
ModelName: "llama3",
|
|
||||||
Model: "ollama/llama3",
|
|
||||||
APIBase: "http://localhost:11434/v1",
|
|
||||||
APIKey: "ollama",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Mistral AI - https://console.mistral.ai/api-keys
|
|
||||||
{
|
|
||||||
ModelName: "mistral-small",
|
|
||||||
Model: "mistral/mistral-small-latest",
|
|
||||||
APIBase: "https://api.mistral.ai/v1",
|
|
||||||
APIKey: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
// VLLM (local) - http://localhost:8000
|
|
||||||
{
|
|
||||||
ModelName: "local-model",
|
|
||||||
Model: "vllm/custom-model",
|
|
||||||
APIBase: "http://localhost:8000/v1",
|
|
||||||
APIKey: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Gateway: GatewayConfig{
|
Gateway: GatewayConfig{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 18790,
|
Port: 18790,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue