From 1db860eba367cdc3ec7723139af27a0b94c86231 Mon Sep 17 00:00:00 2001
From: XYSK-lilong007 <267018309+XYSK-lilong007@users.noreply.github.com>
Date: Fri, 13 Mar 2026 02:26:18 +0800
Subject: [PATCH 1/3] fix(config): load provider env vars (#836)
---
pkg/config/config.go | 60 +++++++++++++++++++--------------------
pkg/config/config_test.go | 43 ++++++++++++++++++++++++++++
2 files changed, 73 insertions(+), 30 deletions(-)
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 7a7edb4894..e95d38902b 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -505,29 +505,29 @@ type VoiceConfig struct {
}
type ProvidersConfig struct {
- Anthropic ProviderConfig `json:"anthropic"`
- OpenAI OpenAIProviderConfig `json:"openai"`
- LiteLLM ProviderConfig `json:"litellm"`
- 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"`
- Cerebras ProviderConfig `json:"cerebras"`
- Vivgrid ProviderConfig `json:"vivgrid"`
- VolcEngine ProviderConfig `json:"volcengine"`
- GitHubCopilot ProviderConfig `json:"github_copilot"`
- Antigravity ProviderConfig `json:"antigravity"`
- Qwen ProviderConfig `json:"qwen"`
- Mistral ProviderConfig `json:"mistral"`
- Avian ProviderConfig `json:"avian"`
- Minimax ProviderConfig `json:"minimax"`
- LongCat ProviderConfig `json:"longcat"`
+ Anthropic ProviderConfig `json:"anthropic" envPrefix:"PICOCLAW_PROVIDERS_ANTHROPIC_"`
+ OpenAI OpenAIProviderConfig `json:"openai" envPrefix:"PICOCLAW_PROVIDERS_OPENAI_"`
+ LiteLLM ProviderConfig `json:"litellm" envPrefix:"PICOCLAW_PROVIDERS_LITELLM_"`
+ OpenRouter ProviderConfig `json:"openrouter" envPrefix:"PICOCLAW_PROVIDERS_OPENROUTER_"`
+ Groq ProviderConfig `json:"groq" envPrefix:"PICOCLAW_PROVIDERS_GROQ_"`
+ Zhipu ProviderConfig `json:"zhipu" envPrefix:"PICOCLAW_PROVIDERS_ZHIPU_"`
+ VLLM ProviderConfig `json:"vllm" envPrefix:"PICOCLAW_PROVIDERS_VLLM_"`
+ Gemini ProviderConfig `json:"gemini" envPrefix:"PICOCLAW_PROVIDERS_GEMINI_"`
+ Nvidia ProviderConfig `json:"nvidia" envPrefix:"PICOCLAW_PROVIDERS_NVIDIA_"`
+ Ollama ProviderConfig `json:"ollama" envPrefix:"PICOCLAW_PROVIDERS_OLLAMA_"`
+ Moonshot ProviderConfig `json:"moonshot" envPrefix:"PICOCLAW_PROVIDERS_MOONSHOT_"`
+ ShengSuanYun ProviderConfig `json:"shengsuanyun" envPrefix:"PICOCLAW_PROVIDERS_SHENGSUANYUN_"`
+ DeepSeek ProviderConfig `json:"deepseek" envPrefix:"PICOCLAW_PROVIDERS_DEEPSEEK_"`
+ Cerebras ProviderConfig `json:"cerebras" envPrefix:"PICOCLAW_PROVIDERS_CEREBRAS_"`
+ Vivgrid ProviderConfig `json:"vivgrid" envPrefix:"PICOCLAW_PROVIDERS_VIVGRID_"`
+ VolcEngine ProviderConfig `json:"volcengine" envPrefix:"PICOCLAW_PROVIDERS_VOLCENGINE_"`
+ GitHubCopilot ProviderConfig `json:"github_copilot" envPrefix:"PICOCLAW_PROVIDERS_GITHUB_COPILOT_"`
+ Antigravity ProviderConfig `json:"antigravity" envPrefix:"PICOCLAW_PROVIDERS_ANTIGRAVITY_"`
+ Qwen ProviderConfig `json:"qwen" envPrefix:"PICOCLAW_PROVIDERS_QWEN_"`
+ Mistral ProviderConfig `json:"mistral" envPrefix:"PICOCLAW_PROVIDERS_MISTRAL_"`
+ Avian ProviderConfig `json:"avian" envPrefix:"PICOCLAW_PROVIDERS_AVIAN_"`
+ Minimax ProviderConfig `json:"minimax" envPrefix:"PICOCLAW_PROVIDERS_MINIMAX_"`
+ LongCat ProviderConfig `json:"longcat" envPrefix:"PICOCLAW_PROVIDERS_LONGCAT_"`
}
// IsEmpty checks if all provider configs are empty (no API keys or API bases set)
@@ -569,17 +569,17 @@ func (p ProvidersConfig) MarshalJSON() ([]byte, error) {
}
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"`
- Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"`
- RequestTimeout int `json:"request_timeout,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_REQUEST_TIMEOUT"`
- 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`
+ APIKey string `json:"api_key" env:"API_KEY"`
+ APIBase string `json:"api_base" env:"API_BASE"`
+ Proxy string `json:"proxy,omitempty" env:"PROXY"`
+ RequestTimeout int `json:"request_timeout,omitempty" env:"REQUEST_TIMEOUT"`
+ AuthMethod string `json:"auth_method,omitempty" env:"AUTH_METHOD"`
+ ConnectMode string `json:"connect_mode,omitempty" env:"CONNECT_MODE"` // only for Github Copilot, `stdio` or `grpc`
}
type OpenAIProviderConfig struct {
ProviderConfig
- WebSearch bool `json:"web_search" env:"PICOCLAW_PROVIDERS_OPENAI_WEB_SEARCH"`
+ WebSearch bool `json:"web_search" env:"WEB_SEARCH"`
}
// ModelConfig represents a model-centric provider configuration.
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index ad89d6d2e7..13d9c4ed8d 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -460,6 +460,49 @@ func TestLoadConfig_WebToolsProxy(t *testing.T) {
}
}
+func TestLoadConfig_ProviderEnvVarsOverrideFileValues(t *testing.T) {
+ tmpDir := t.TempDir()
+ configPath := filepath.Join(tmpDir, "config.json")
+ configJSON := `{
+ "providers": {
+ "gemini": {
+ "api_key": "from-file"
+ }
+ }
+}`
+ if err := os.WriteFile(configPath, []byte(configJSON), 0o600); err != nil {
+ t.Fatalf("os.WriteFile() error: %v", err)
+ }
+
+ t.Setenv("PICOCLAW_PROVIDERS_GEMINI_API_KEY", "from-env")
+
+ cfg, err := LoadConfig(configPath)
+ if err != nil {
+ t.Fatalf("LoadConfig() error: %v", err)
+ }
+ if cfg.Providers.Gemini.APIKey != "from-env" {
+ t.Fatalf("Providers.Gemini.APIKey = %q, want %q", cfg.Providers.Gemini.APIKey, "from-env")
+ }
+}
+
+func TestLoadConfig_OpenAIProviderEnvVarsUseNestedPrefix(t *testing.T) {
+ tmpDir := t.TempDir()
+ configPath := filepath.Join(tmpDir, "config.json")
+ if err := os.WriteFile(configPath, []byte(`{}`), 0o600); err != nil {
+ t.Fatalf("os.WriteFile() error: %v", err)
+ }
+
+ t.Setenv("PICOCLAW_PROVIDERS_OPENAI_WEB_SEARCH", "false")
+
+ cfg, err := LoadConfig(configPath)
+ if err != nil {
+ t.Fatalf("LoadConfig() error: %v", err)
+ }
+ if cfg.Providers.OpenAI.WebSearch {
+ t.Fatal("Providers.OpenAI.WebSearch should be false when disabled via environment")
+ }
+}
+
// TestDefaultConfig_DMScope verifies the default dm_scope value
// TestDefaultConfig_SummarizationThresholds verifies summarization defaults
func TestDefaultConfig_SummarizationThresholds(t *testing.T) {
From b02da1a4ccd39f2c108724a58396ce6cc6ae6a14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=BE=99=200668001470?=
Date: Mon, 16 Mar 2026 10:04:18 +0800
Subject: [PATCH 2/3] fix(config): apply provider env prefixes during load
---
pkg/config/config.go | 14 +++++++++++---
pkg/config/config_test.go | 6 +++---
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/pkg/config/config.go b/pkg/config/config.go
index e95d38902b..2b88f7b11c 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -222,8 +222,8 @@ type AgentDefaults struct {
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
AllowReadOutsideWorkspace bool `json:"allow_read_outside_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_ALLOW_READ_OUTSIDE_WORKSPACE"`
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
- ModelName string `json:"model_name,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"`
- Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead
+ ModelName string `json:"model_name" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"`
+ Model string `json:"model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead
ModelFallbacks []string `json:"model_fallbacks,omitempty"`
ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"`
ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"`
@@ -528,6 +528,7 @@ type ProvidersConfig struct {
Avian ProviderConfig `json:"avian" envPrefix:"PICOCLAW_PROVIDERS_AVIAN_"`
Minimax ProviderConfig `json:"minimax" envPrefix:"PICOCLAW_PROVIDERS_MINIMAX_"`
LongCat ProviderConfig `json:"longcat" envPrefix:"PICOCLAW_PROVIDERS_LONGCAT_"`
+ ModelScope ProviderConfig `json:"modelscope" envPrefix:"PICOCLAW_PROVIDERS_MODELSCOPE_"`
}
// IsEmpty checks if all provider configs are empty (no API keys or API bases set)
@@ -555,7 +556,8 @@ func (p ProvidersConfig) IsEmpty() bool {
p.Mistral.APIKey == "" && p.Mistral.APIBase == "" &&
p.Avian.APIKey == "" && p.Avian.APIBase == "" &&
p.Minimax.APIKey == "" && p.Minimax.APIBase == "" &&
- p.LongCat.APIKey == "" && p.LongCat.APIBase == ""
+ p.LongCat.APIKey == "" && p.LongCat.APIBase == "" &&
+ p.ModelScope.APIKey == "" && p.ModelScope.APIBase == ""
}
// MarshalJSON implements custom JSON marshaling for ProvidersConfig
@@ -711,6 +713,7 @@ type ExecConfig struct {
type SkillsToolsConfig struct {
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_SKILLS_"`
Registries SkillsRegistriesConfig ` json:"registries"`
+ Github SkillsGithubConfig ` json:"github"`
MaxConcurrentSearches int ` json:"max_concurrent_searches" env:"PICOCLAW_TOOLS_SKILLS_MAX_CONCURRENT_SEARCHES"`
SearchCache SearchCacheConfig ` json:"search_cache"`
}
@@ -760,6 +763,11 @@ type SkillsRegistriesConfig struct {
ClawHub ClawHubRegistryConfig `json:"clawhub"`
}
+type SkillsGithubConfig struct {
+ Token string `json:"token,omitempty" env:"PICOCLAW_TOOLS_SKILLS_GITHUB_AUTH_TOKEN"`
+ Proxy string `json:"proxy,omitempty" env:"PICOCLAW_TOOLS_SKILLS_GITHUB_PROXY"`
+}
+
type ClawHubRegistryConfig struct {
Enabled bool `json:"enabled" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_ENABLED"`
BaseURL string `json:"base_url" env:"PICOCLAW_SKILLS_REGISTRIES_CLAWHUB_BASE_URL"`
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 13d9c4ed8d..1cc6069305 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -342,8 +342,8 @@ func TestSaveConfig_IncludesEmptyLegacyModelField(t *testing.T) {
t.Fatalf("ReadFile failed: %v", err)
}
- if !strings.Contains(string(data), `"model": ""`) {
- t.Fatalf("saved config should include empty legacy model field, got: %s", string(data))
+ if !strings.Contains(string(data), `"model_name": ""`) {
+ t.Fatalf("saved config should include empty legacy model_name field, got: %s", string(data))
}
}
@@ -444,7 +444,7 @@ func TestLoadConfig_WebToolsProxy(t *testing.T) {
configPath := filepath.Join(tmpDir, "config.json")
configJSON := `{
"agents": {"defaults":{"workspace":"./workspace","model":"gpt4","max_tokens":8192,"max_tool_iterations":20}},
- "model_list": [{"model_name":"gpt4","model":"openai/gpt-5.2","api_key":"x"}],
+ "model_list": [{"model_name":"gpt4","model":"openai/gpt-5.4","api_key":"x"}],
"tools": {"web":{"proxy":"http://127.0.0.1:7890"}}
}`
if err := os.WriteFile(configPath, []byte(configJSON), 0o600); err != nil {
From 41a898b4211b95393e2003a44744ab7533a70e85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=BE=99=200668001470?=
Date: Mon, 16 Mar 2026 10:36:05 +0800
Subject: [PATCH 3/3] fix(config): apply provider env overrides after parse
---
pkg/config/config.go | 127 ++++++++++++++++++++++++++++++++-----------
1 file changed, 96 insertions(+), 31 deletions(-)
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 2b88f7b11c..ffda5e6bda 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"os"
+ "reflect"
+ "strconv"
"strings"
"sync/atomic"
@@ -505,30 +507,30 @@ type VoiceConfig struct {
}
type ProvidersConfig struct {
- Anthropic ProviderConfig `json:"anthropic" envPrefix:"PICOCLAW_PROVIDERS_ANTHROPIC_"`
- OpenAI OpenAIProviderConfig `json:"openai" envPrefix:"PICOCLAW_PROVIDERS_OPENAI_"`
- LiteLLM ProviderConfig `json:"litellm" envPrefix:"PICOCLAW_PROVIDERS_LITELLM_"`
- OpenRouter ProviderConfig `json:"openrouter" envPrefix:"PICOCLAW_PROVIDERS_OPENROUTER_"`
- Groq ProviderConfig `json:"groq" envPrefix:"PICOCLAW_PROVIDERS_GROQ_"`
- Zhipu ProviderConfig `json:"zhipu" envPrefix:"PICOCLAW_PROVIDERS_ZHIPU_"`
- VLLM ProviderConfig `json:"vllm" envPrefix:"PICOCLAW_PROVIDERS_VLLM_"`
- Gemini ProviderConfig `json:"gemini" envPrefix:"PICOCLAW_PROVIDERS_GEMINI_"`
- Nvidia ProviderConfig `json:"nvidia" envPrefix:"PICOCLAW_PROVIDERS_NVIDIA_"`
- Ollama ProviderConfig `json:"ollama" envPrefix:"PICOCLAW_PROVIDERS_OLLAMA_"`
- Moonshot ProviderConfig `json:"moonshot" envPrefix:"PICOCLAW_PROVIDERS_MOONSHOT_"`
- ShengSuanYun ProviderConfig `json:"shengsuanyun" envPrefix:"PICOCLAW_PROVIDERS_SHENGSUANYUN_"`
- DeepSeek ProviderConfig `json:"deepseek" envPrefix:"PICOCLAW_PROVIDERS_DEEPSEEK_"`
- Cerebras ProviderConfig `json:"cerebras" envPrefix:"PICOCLAW_PROVIDERS_CEREBRAS_"`
- Vivgrid ProviderConfig `json:"vivgrid" envPrefix:"PICOCLAW_PROVIDERS_VIVGRID_"`
- VolcEngine ProviderConfig `json:"volcengine" envPrefix:"PICOCLAW_PROVIDERS_VOLCENGINE_"`
- GitHubCopilot ProviderConfig `json:"github_copilot" envPrefix:"PICOCLAW_PROVIDERS_GITHUB_COPILOT_"`
- Antigravity ProviderConfig `json:"antigravity" envPrefix:"PICOCLAW_PROVIDERS_ANTIGRAVITY_"`
- Qwen ProviderConfig `json:"qwen" envPrefix:"PICOCLAW_PROVIDERS_QWEN_"`
- Mistral ProviderConfig `json:"mistral" envPrefix:"PICOCLAW_PROVIDERS_MISTRAL_"`
- Avian ProviderConfig `json:"avian" envPrefix:"PICOCLAW_PROVIDERS_AVIAN_"`
- Minimax ProviderConfig `json:"minimax" envPrefix:"PICOCLAW_PROVIDERS_MINIMAX_"`
- LongCat ProviderConfig `json:"longcat" envPrefix:"PICOCLAW_PROVIDERS_LONGCAT_"`
- ModelScope ProviderConfig `json:"modelscope" envPrefix:"PICOCLAW_PROVIDERS_MODELSCOPE_"`
+ Anthropic ProviderConfig `json:"anthropic"`
+ OpenAI OpenAIProviderConfig `json:"openai"`
+ LiteLLM ProviderConfig `json:"litellm"`
+ 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"`
+ Cerebras ProviderConfig `json:"cerebras"`
+ Vivgrid ProviderConfig `json:"vivgrid"`
+ VolcEngine ProviderConfig `json:"volcengine"`
+ GitHubCopilot ProviderConfig `json:"github_copilot"`
+ Antigravity ProviderConfig `json:"antigravity"`
+ Qwen ProviderConfig `json:"qwen"`
+ Mistral ProviderConfig `json:"mistral"`
+ Avian ProviderConfig `json:"avian"`
+ Minimax ProviderConfig `json:"minimax"`
+ LongCat ProviderConfig `json:"longcat"`
+ ModelScope ProviderConfig `json:"modelscope"`
}
// IsEmpty checks if all provider configs are empty (no API keys or API bases set)
@@ -571,17 +573,17 @@ func (p ProvidersConfig) MarshalJSON() ([]byte, error) {
}
type ProviderConfig struct {
- APIKey string `json:"api_key" env:"API_KEY"`
- APIBase string `json:"api_base" env:"API_BASE"`
- Proxy string `json:"proxy,omitempty" env:"PROXY"`
- RequestTimeout int `json:"request_timeout,omitempty" env:"REQUEST_TIMEOUT"`
- AuthMethod string `json:"auth_method,omitempty" env:"AUTH_METHOD"`
- ConnectMode string `json:"connect_mode,omitempty" env:"CONNECT_MODE"` // only for Github Copilot, `stdio` or `grpc`
+ APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"`
+ APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"`
+ Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"`
+ RequestTimeout int `json:"request_timeout,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_REQUEST_TIMEOUT"`
+ 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`
}
type OpenAIProviderConfig struct {
ProviderConfig
- WebSearch bool `json:"web_search" env:"WEB_SEARCH"`
+ WebSearch bool `json:"web_search" env:"PICOCLAW_PROVIDERS_OPENAI_WEB_SEARCH"`
}
// ModelConfig represents a model-centric provider configuration.
@@ -840,6 +842,7 @@ func LoadConfig(path string) (*Config, error) {
if err := env.Parse(cfg); err != nil {
return nil, err
}
+ cfg.applyProviderEnvOverrides()
// Migrate legacy channel config fields to new unified structures
cfg.migrateChannelConfigs()
@@ -857,6 +860,68 @@ func LoadConfig(path string) (*Config, error) {
return cfg, nil
}
+func (c *Config) applyProviderEnvOverrides() {
+ providersValue := reflect.ValueOf(&c.Providers).Elem()
+ providersType := providersValue.Type()
+
+ for i := 0; i < providersValue.NumField(); i++ {
+ fieldValue := providersValue.Field(i)
+ fieldType := providersType.Field(i)
+ jsonName := strings.Split(fieldType.Tag.Get("json"), ",")[0]
+ if jsonName == "" {
+ continue
+ }
+ prefix := "PICOCLAW_PROVIDERS_" + strings.ToUpper(jsonName) + "_"
+ applyProviderConfigEnv(fieldValue, prefix)
+ }
+}
+
+func applyProviderConfigEnv(fieldValue reflect.Value, prefix string) {
+ if fieldValue.Kind() != reflect.Struct {
+ return
+ }
+
+ if nested := fieldValue.FieldByName("ProviderConfig"); nested.IsValid() {
+ applyProviderConfigFields(nested, prefix)
+ } else {
+ applyProviderConfigFields(fieldValue, prefix)
+ }
+
+ if webSearch := fieldValue.FieldByName("WebSearch"); webSearch.IsValid() && webSearch.CanSet() {
+ if raw, ok := os.LookupEnv(prefix + "WEB_SEARCH"); ok {
+ if parsed, err := strconv.ParseBool(raw); err == nil {
+ webSearch.SetBool(parsed)
+ }
+ }
+ }
+}
+
+func applyProviderConfigFields(fieldValue reflect.Value, prefix string) {
+ setStringFieldFromEnv(fieldValue, "APIKey", prefix+"API_KEY")
+ setStringFieldFromEnv(fieldValue, "APIBase", prefix+"API_BASE")
+ setStringFieldFromEnv(fieldValue, "Proxy", prefix+"PROXY")
+ setStringFieldFromEnv(fieldValue, "AuthMethod", prefix+"AUTH_METHOD")
+ setStringFieldFromEnv(fieldValue, "ConnectMode", prefix+"CONNECT_MODE")
+
+ if requestTimeout := fieldValue.FieldByName("RequestTimeout"); requestTimeout.IsValid() && requestTimeout.CanSet() {
+ if raw, ok := os.LookupEnv(prefix + "REQUEST_TIMEOUT"); ok {
+ if parsed, err := strconv.Atoi(raw); err == nil {
+ requestTimeout.SetInt(int64(parsed))
+ }
+ }
+ }
+}
+
+func setStringFieldFromEnv(fieldValue reflect.Value, fieldName, envName string) {
+ target := fieldValue.FieldByName(fieldName)
+ if !target.IsValid() || !target.CanSet() {
+ return
+ }
+ if raw, ok := os.LookupEnv(envName); ok {
+ target.SetString(raw)
+ }
+}
+
func (c *Config) migrateChannelConfigs() {
// Discord: mention_only -> group_trigger.mention_only
if c.Channels.Discord.MentionOnly && !c.Channels.Discord.GroupTrigger.MentionOnly {