fix(config): load provider env vars (#836)

This commit is contained in:
XYSK-lilong007 2026-03-13 02:26:18 +08:00
parent 9222351871
commit 1db860eba3
2 changed files with 73 additions and 30 deletions

View file

@ -505,29 +505,29 @@ type VoiceConfig struct {
} }
type ProvidersConfig struct { type ProvidersConfig struct {
Anthropic ProviderConfig `json:"anthropic"` Anthropic ProviderConfig `json:"anthropic" envPrefix:"PICOCLAW_PROVIDERS_ANTHROPIC_"`
OpenAI OpenAIProviderConfig `json:"openai"` OpenAI OpenAIProviderConfig `json:"openai" envPrefix:"PICOCLAW_PROVIDERS_OPENAI_"`
LiteLLM ProviderConfig `json:"litellm"` LiteLLM ProviderConfig `json:"litellm" envPrefix:"PICOCLAW_PROVIDERS_LITELLM_"`
OpenRouter ProviderConfig `json:"openrouter"` OpenRouter ProviderConfig `json:"openrouter" envPrefix:"PICOCLAW_PROVIDERS_OPENROUTER_"`
Groq ProviderConfig `json:"groq"` Groq ProviderConfig `json:"groq" envPrefix:"PICOCLAW_PROVIDERS_GROQ_"`
Zhipu ProviderConfig `json:"zhipu"` Zhipu ProviderConfig `json:"zhipu" envPrefix:"PICOCLAW_PROVIDERS_ZHIPU_"`
VLLM ProviderConfig `json:"vllm"` VLLM ProviderConfig `json:"vllm" envPrefix:"PICOCLAW_PROVIDERS_VLLM_"`
Gemini ProviderConfig `json:"gemini"` Gemini ProviderConfig `json:"gemini" envPrefix:"PICOCLAW_PROVIDERS_GEMINI_"`
Nvidia ProviderConfig `json:"nvidia"` Nvidia ProviderConfig `json:"nvidia" envPrefix:"PICOCLAW_PROVIDERS_NVIDIA_"`
Ollama ProviderConfig `json:"ollama"` Ollama ProviderConfig `json:"ollama" envPrefix:"PICOCLAW_PROVIDERS_OLLAMA_"`
Moonshot ProviderConfig `json:"moonshot"` Moonshot ProviderConfig `json:"moonshot" envPrefix:"PICOCLAW_PROVIDERS_MOONSHOT_"`
ShengSuanYun ProviderConfig `json:"shengsuanyun"` ShengSuanYun ProviderConfig `json:"shengsuanyun" envPrefix:"PICOCLAW_PROVIDERS_SHENGSUANYUN_"`
DeepSeek ProviderConfig `json:"deepseek"` DeepSeek ProviderConfig `json:"deepseek" envPrefix:"PICOCLAW_PROVIDERS_DEEPSEEK_"`
Cerebras ProviderConfig `json:"cerebras"` Cerebras ProviderConfig `json:"cerebras" envPrefix:"PICOCLAW_PROVIDERS_CEREBRAS_"`
Vivgrid ProviderConfig `json:"vivgrid"` Vivgrid ProviderConfig `json:"vivgrid" envPrefix:"PICOCLAW_PROVIDERS_VIVGRID_"`
VolcEngine ProviderConfig `json:"volcengine"` VolcEngine ProviderConfig `json:"volcengine" envPrefix:"PICOCLAW_PROVIDERS_VOLCENGINE_"`
GitHubCopilot ProviderConfig `json:"github_copilot"` GitHubCopilot ProviderConfig `json:"github_copilot" envPrefix:"PICOCLAW_PROVIDERS_GITHUB_COPILOT_"`
Antigravity ProviderConfig `json:"antigravity"` Antigravity ProviderConfig `json:"antigravity" envPrefix:"PICOCLAW_PROVIDERS_ANTIGRAVITY_"`
Qwen ProviderConfig `json:"qwen"` Qwen ProviderConfig `json:"qwen" envPrefix:"PICOCLAW_PROVIDERS_QWEN_"`
Mistral ProviderConfig `json:"mistral"` Mistral ProviderConfig `json:"mistral" envPrefix:"PICOCLAW_PROVIDERS_MISTRAL_"`
Avian ProviderConfig `json:"avian"` Avian ProviderConfig `json:"avian" envPrefix:"PICOCLAW_PROVIDERS_AVIAN_"`
Minimax ProviderConfig `json:"minimax"` Minimax ProviderConfig `json:"minimax" envPrefix:"PICOCLAW_PROVIDERS_MINIMAX_"`
LongCat ProviderConfig `json:"longcat"` LongCat ProviderConfig `json:"longcat" envPrefix:"PICOCLAW_PROVIDERS_LONGCAT_"`
} }
// IsEmpty checks if all provider configs are empty (no API keys or API bases set) // 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 { type ProviderConfig struct {
APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"` APIKey string `json:"api_key" env:"API_KEY"`
APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"` APIBase string `json:"api_base" env:"API_BASE"`
Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"` Proxy string `json:"proxy,omitempty" env:"PROXY"`
RequestTimeout int `json:"request_timeout,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_REQUEST_TIMEOUT"` RequestTimeout int `json:"request_timeout,omitempty" env:"REQUEST_TIMEOUT"`
AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"` AuthMethod string `json:"auth_method,omitempty" env:"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:"CONNECT_MODE"` // only for Github Copilot, `stdio` or `grpc`
} }
type OpenAIProviderConfig struct { type OpenAIProviderConfig struct {
ProviderConfig 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. // ModelConfig represents a model-centric provider configuration.

View file

@ -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_DMScope verifies the default dm_scope value
// TestDefaultConfig_SummarizationThresholds verifies summarization defaults // TestDefaultConfig_SummarizationThresholds verifies summarization defaults
func TestDefaultConfig_SummarizationThresholds(t *testing.T) { func TestDefaultConfig_SummarizationThresholds(t *testing.T) {