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,122 +5,15 @@
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
// DefaultConfig returns the default configuration for PicoClaw.
|
func DefaultModelList() []ModelConfig {
|
||||||
func DefaultConfig() *Config {
|
return []ModelConfig{
|
||||||
return &Config{
|
// OpenAI - https://platform.openai.com/api-keys
|
||||||
Agents: AgentsConfig{
|
{
|
||||||
Defaults: AgentDefaults{
|
ModelName: "gpt-5.2",
|
||||||
Workspace: "~/.picoclaw/workspace",
|
Model: "openai/gpt-5.2",
|
||||||
RestrictToWorkspace: true,
|
APIBase: "https://api.openai.com/v1",
|
||||||
Provider: "",
|
APIKey: "",
|
||||||
Model: "glm-4.7",
|
|
||||||
MaxTokens: 8192,
|
|
||||||
Temperature: nil, // nil means use provider default
|
|
||||||
MaxToolIterations: 20,
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
Bindings: []AgentBinding{},
|
|
||||||
Session: SessionConfig{
|
|
||||||
DMScope: "main",
|
|
||||||
},
|
|
||||||
Channels: ChannelsConfig{
|
|
||||||
WhatsApp: WhatsAppConfig{
|
|
||||||
Enabled: false,
|
|
||||||
BridgeURL: "ws://localhost:3001",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
Telegram: TelegramConfig{
|
|
||||||
Enabled: false,
|
|
||||||
Token: "",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
Feishu: FeishuConfig{
|
|
||||||
Enabled: false,
|
|
||||||
AppID: "",
|
|
||||||
AppSecret: "",
|
|
||||||
EncryptKey: "",
|
|
||||||
VerificationToken: "",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
Discord: DiscordConfig{
|
|
||||||
Enabled: false,
|
|
||||||
Token: "",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
MentionOnly: false,
|
|
||||||
},
|
|
||||||
MaixCam: MaixCamConfig{
|
|
||||||
Enabled: false,
|
|
||||||
Host: "0.0.0.0",
|
|
||||||
Port: 18790,
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
QQ: QQConfig{
|
|
||||||
Enabled: false,
|
|
||||||
AppID: "",
|
|
||||||
AppSecret: "",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
DingTalk: DingTalkConfig{
|
|
||||||
Enabled: false,
|
|
||||||
ClientID: "",
|
|
||||||
ClientSecret: "",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
Slack: SlackConfig{
|
|
||||||
Enabled: false,
|
|
||||||
BotToken: "",
|
|
||||||
AppToken: "",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
LINE: LINEConfig{
|
|
||||||
Enabled: false,
|
|
||||||
ChannelSecret: "",
|
|
||||||
ChannelAccessToken: "",
|
|
||||||
WebhookHost: "0.0.0.0",
|
|
||||||
WebhookPort: 18791,
|
|
||||||
WebhookPath: "/webhook/line",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
OneBot: OneBotConfig{
|
|
||||||
Enabled: false,
|
|
||||||
WSUrl: "ws://127.0.0.1:3001",
|
|
||||||
AccessToken: "",
|
|
||||||
ReconnectInterval: 5,
|
|
||||||
GroupTriggerPrefix: []string{},
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
},
|
|
||||||
WeCom: WeComConfig{
|
|
||||||
Enabled: false,
|
|
||||||
Token: "",
|
|
||||||
EncodingAESKey: "",
|
|
||||||
WebhookURL: "",
|
|
||||||
WebhookHost: "0.0.0.0",
|
|
||||||
WebhookPort: 18793,
|
|
||||||
WebhookPath: "/webhook/wecom",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
ReplyTimeout: 5,
|
|
||||||
},
|
|
||||||
WeComApp: WeComAppConfig{
|
|
||||||
Enabled: false,
|
|
||||||
CorpID: "",
|
|
||||||
CorpSecret: "",
|
|
||||||
AgentID: 0,
|
|
||||||
Token: "",
|
|
||||||
EncodingAESKey: "",
|
|
||||||
WebhookHost: "0.0.0.0",
|
|
||||||
WebhookPort: 18792,
|
|
||||||
WebhookPath: "/webhook/wecom-app",
|
|
||||||
AllowFrom: FlexibleStringSlice{},
|
|
||||||
ReplyTimeout: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Providers: ProvidersConfig{
|
|
||||||
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
|
// Zhipu AI (智谱) - https://open.bigmodel.cn/usercenter/apikeys
|
||||||
{
|
{
|
||||||
|
|
@ -130,14 +23,6 @@ func DefaultConfig() *Config {
|
||||||
APIKey: "",
|
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
|
// Anthropic Claude - https://console.anthropic.com/settings/keys
|
||||||
{
|
{
|
||||||
ModelName: "claude-sonnet-4.6",
|
ModelName: "claude-sonnet-4.6",
|
||||||
|
|
@ -270,7 +155,122 @@ func DefaultConfig() *Config {
|
||||||
APIBase: "http://localhost:8000/v1",
|
APIBase: "http://localhost:8000/v1",
|
||||||
APIKey: "",
|
APIKey: "",
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfig returns the default configuration for PicoClaw.
|
||||||
|
func DefaultConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
Agents: AgentsConfig{
|
||||||
|
Defaults: AgentDefaults{
|
||||||
|
Workspace: "~/.picoclaw/workspace",
|
||||||
|
RestrictToWorkspace: true,
|
||||||
|
Provider: "",
|
||||||
|
Model: "glm-4.7",
|
||||||
|
MaxTokens: 8192,
|
||||||
|
Temperature: nil, // nil means use provider default
|
||||||
|
MaxToolIterations: 20,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
Bindings: []AgentBinding{},
|
||||||
|
Session: SessionConfig{
|
||||||
|
DMScope: "main",
|
||||||
|
},
|
||||||
|
Channels: ChannelsConfig{
|
||||||
|
WhatsApp: WhatsAppConfig{
|
||||||
|
Enabled: false,
|
||||||
|
BridgeURL: "ws://localhost:3001",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
Telegram: TelegramConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Token: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
Feishu: FeishuConfig{
|
||||||
|
Enabled: false,
|
||||||
|
AppID: "",
|
||||||
|
AppSecret: "",
|
||||||
|
EncryptKey: "",
|
||||||
|
VerificationToken: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
Discord: DiscordConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Token: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
MentionOnly: false,
|
||||||
|
},
|
||||||
|
MaixCam: MaixCamConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Host: "0.0.0.0",
|
||||||
|
Port: 18790,
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
QQ: QQConfig{
|
||||||
|
Enabled: false,
|
||||||
|
AppID: "",
|
||||||
|
AppSecret: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
DingTalk: DingTalkConfig{
|
||||||
|
Enabled: false,
|
||||||
|
ClientID: "",
|
||||||
|
ClientSecret: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
Slack: SlackConfig{
|
||||||
|
Enabled: false,
|
||||||
|
BotToken: "",
|
||||||
|
AppToken: "",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
LINE: LINEConfig{
|
||||||
|
Enabled: false,
|
||||||
|
ChannelSecret: "",
|
||||||
|
ChannelAccessToken: "",
|
||||||
|
WebhookHost: "0.0.0.0",
|
||||||
|
WebhookPort: 18791,
|
||||||
|
WebhookPath: "/webhook/line",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
OneBot: OneBotConfig{
|
||||||
|
Enabled: false,
|
||||||
|
WSUrl: "ws://127.0.0.1:3001",
|
||||||
|
AccessToken: "",
|
||||||
|
ReconnectInterval: 5,
|
||||||
|
GroupTriggerPrefix: []string{},
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
},
|
||||||
|
WeCom: WeComConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Token: "",
|
||||||
|
EncodingAESKey: "",
|
||||||
|
WebhookURL: "",
|
||||||
|
WebhookHost: "0.0.0.0",
|
||||||
|
WebhookPort: 18793,
|
||||||
|
WebhookPath: "/webhook/wecom",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
ReplyTimeout: 5,
|
||||||
|
},
|
||||||
|
WeComApp: WeComAppConfig{
|
||||||
|
Enabled: false,
|
||||||
|
CorpID: "",
|
||||||
|
CorpSecret: "",
|
||||||
|
AgentID: 0,
|
||||||
|
Token: "",
|
||||||
|
EncodingAESKey: "",
|
||||||
|
WebhookHost: "0.0.0.0",
|
||||||
|
WebhookPort: 18792,
|
||||||
|
WebhookPath: "/webhook/wecom-app",
|
||||||
|
AllowFrom: FlexibleStringSlice{},
|
||||||
|
ReplyTimeout: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Providers: ProvidersConfig{
|
||||||
|
OpenAI: OpenAIProviderConfig{WebSearch: true},
|
||||||
|
},
|
||||||
|
ModelList: []ModelConfig{},
|
||||||
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