-
-
Notifications
You must be signed in to change notification settings - Fork 66
chore: errors standardized and general refactoring #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f776f8d
bab8391
cb82c33
255200a
6dd1e73
8c09aa1
b8dd547
5d011c6
3694a37
a5168d3
13c685a
81eeb49
5c5dd5e
7046dbc
11a985c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,80 +2,155 @@ | |
| package config | ||
|
|
||
| import ( | ||
| "os" | ||
| "strings" | ||
|
|
||
| "github.com/joho/godotenv" | ||
| "github.com/spf13/viper" | ||
| ) | ||
|
|
||
| // Config holds the application configuration | ||
| type Config struct { | ||
| Server ServerConfig `mapstructure:"server"` | ||
| OpenAI OpenAIConfig `mapstructure:"openai"` | ||
| Anthropic AnthropicConfig `mapstructure:"anthropic"` | ||
| Gemini GeminiConfig `mapstructure:"gemini"` | ||
| Server ServerConfig `mapstructure:"server"` | ||
| Providers map[string]ProviderConfig `mapstructure:"providers"` | ||
| } | ||
|
|
||
| // ServerConfig holds HTTP server configuration | ||
| type ServerConfig struct { | ||
| Port string `mapstructure:"port"` | ||
| } | ||
|
|
||
| // OpenAIConfig holds OpenAI-specific configuration | ||
| type OpenAIConfig struct { | ||
| APIKey string `mapstructure:"api_key"` | ||
| } | ||
|
|
||
| // AnthropicConfig holds Anthropic-specific configuration | ||
| type AnthropicConfig struct { | ||
| APIKey string `mapstructure:"api_key"` | ||
| } | ||
|
|
||
| // GeminiConfig holds Google Gemini-specific configuration | ||
| type GeminiConfig struct { | ||
| APIKey string `mapstructure:"api_key"` | ||
| // ProviderConfig holds generic provider configuration | ||
| type ProviderConfig struct { | ||
| Type string `mapstructure:"type"` // e.g., "openai", "anthropic", "gemini" | ||
| APIKey string `mapstructure:"api_key"` // API key for authentication | ||
| BaseURL string `mapstructure:"base_url"` // Optional: override default base URL | ||
| Models []string `mapstructure:"models"` // Optional: restrict to specific models | ||
| } | ||
|
|
||
| // Load reads configuration from file and environment | ||
| func Load() (*Config, error) { | ||
| // Load .env file directly into environment variables | ||
| // This ensures os.Getenv works for variables defined in .env | ||
| _ = godotenv.Load() // Ignore error (e.g., file not found) | ||
|
|
||
| // Load .env file using Viper (optional, won't fail if not found) | ||
| viper.SetConfigName(".env") | ||
|
|
||
| viper.SetConfigType("env") | ||
| viper.AddConfigPath(".") | ||
| _ = viper.ReadInConfig() // Ignore error if .env file doesn't exist | ||
|
|
||
| // Set defaults | ||
| viper.SetDefault("PORT", "8080") | ||
| viper.SetDefault("server.port", "8080") | ||
|
|
||
| // Enable automatic environment variable reading | ||
| viper.AutomaticEnv() | ||
|
|
||
| // Commented out: config.yaml reading (not used anymore) | ||
| // viper.SetConfigName("config") | ||
| // viper.SetConfigType("yaml") | ||
| // viper.AddConfigPath("./config") | ||
| // viper.AddConfigPath(".") | ||
| // | ||
| // // Read config file (optional, won't fail if not found) | ||
| // _ = viper.ReadInConfig() //nolint:errcheck | ||
| // | ||
| // var cfg Config | ||
| // if err := viper.Unmarshal(&cfg); err != nil { | ||
| // return nil, err | ||
| // } | ||
|
|
||
| // Read configuration from environment variables using Viper | ||
| cfg := &Config{ | ||
| Server: ServerConfig{ | ||
| Port: viper.GetString("PORT"), | ||
| }, | ||
| OpenAI: OpenAIConfig{ | ||
| APIKey: viper.GetString("OPENAI_API_KEY"), | ||
| }, | ||
| Anthropic: AnthropicConfig{ | ||
| APIKey: viper.GetString("ANTHROPIC_API_KEY"), | ||
| }, | ||
| Gemini: GeminiConfig{ | ||
| APIKey: viper.GetString("GEMINI_API_KEY"), | ||
| }, | ||
| // Try to read config.yaml | ||
| viper.SetConfigName("config") | ||
| viper.SetConfigType("yaml") | ||
| viper.AddConfigPath("./config") | ||
| viper.AddConfigPath(".") | ||
|
|
||
| var cfg Config | ||
|
|
||
| // Read config file (optional, won't fail if not found) | ||
| if err := viper.ReadInConfig(); err == nil { | ||
| // Config file found, unmarshal it | ||
| if err := viper.Unmarshal(&cfg); err != nil { | ||
| return nil, err | ||
| } | ||
| // Expand environment variables in config values | ||
| cfg = expandEnvVars(cfg) | ||
| // Remove providers with unresolved environment variables | ||
| cfg = removeEmptyProviders(cfg) | ||
| } else { | ||
| // No config file, use environment variables (legacy support) | ||
| cfg = Config{ | ||
| Server: ServerConfig{ | ||
| Port: viper.GetString("PORT"), | ||
| }, | ||
| Providers: make(map[string]ProviderConfig), | ||
| } | ||
|
|
||
| // Add providers from environment variables if available | ||
| if apiKey := viper.GetString("OPENAI_API_KEY"); apiKey != "" { | ||
| cfg.Providers["openai-primary"] = ProviderConfig{ | ||
| Type: "openai", | ||
| APIKey: apiKey, | ||
| } | ||
| } | ||
| if apiKey := viper.GetString("ANTHROPIC_API_KEY"); apiKey != "" { | ||
| cfg.Providers["anthropic-primary"] = ProviderConfig{ | ||
| Type: "anthropic", | ||
| APIKey: apiKey, | ||
| } | ||
| } | ||
| if apiKey := viper.GetString("GEMINI_API_KEY"); apiKey != "" { | ||
| cfg.Providers["gemini-primary"] = ProviderConfig{ | ||
| Type: "gemini", | ||
| APIKey: apiKey, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return cfg, nil | ||
| return &cfg, nil | ||
| } | ||
|
|
||
| // expandEnvVars expands environment variable references in configuration values | ||
| func expandEnvVars(cfg Config) Config { | ||
| // Expand server port | ||
| cfg.Server.Port = expandString(cfg.Server.Port) | ||
|
|
||
| // Expand provider configurations | ||
| for name, pCfg := range cfg.Providers { | ||
| pCfg.APIKey = expandString(pCfg.APIKey) | ||
| pCfg.BaseURL = expandString(pCfg.BaseURL) | ||
| cfg.Providers[name] = pCfg | ||
| } | ||
|
|
||
| return cfg | ||
| } | ||
|
|
||
| // expandString expands environment variable references like ${VAR_NAME} or ${VAR_NAME:-default} in a string | ||
| func expandString(s string) string { | ||
| if s == "" { | ||
| return s | ||
| } | ||
| return os.Expand(s, func(key string) string { | ||
| // Check for default value syntax ${VAR:-default} | ||
| varname := key | ||
| defaultValue := "" | ||
| if strings.Contains(key, ":-") { | ||
| parts := strings.SplitN(key, ":-", 2) | ||
| varname = parts[0] | ||
| defaultValue = parts[1] | ||
| } | ||
|
|
||
| // Try to get from environment | ||
| value := os.Getenv(varname) | ||
| if value == "" { | ||
| if defaultValue != "" { | ||
| return defaultValue | ||
| } | ||
| // If not in environment and no default, return the original placeholder | ||
| // This allows config to work with or without env vars | ||
| return "${" + key + "}" | ||
| } | ||
| return value | ||
| }) | ||
|
Comment on lines
+121
to
+142
|
||
| } | ||
|
|
||
| // removeEmptyProviders removes providers with empty API keys | ||
| func removeEmptyProviders(cfg Config) Config { | ||
| filteredProviders := make(map[string]ProviderConfig) | ||
| for name, pCfg := range cfg.Providers { | ||
| // Keep provider only if API key doesn't contain unexpanded placeholders | ||
| if pCfg.APIKey != "" && !strings.Contains(pCfg.APIKey, "${") { | ||
| filteredProviders[name] = pCfg | ||
| } | ||
| } | ||
| cfg.Providers = filteredProviders | ||
| return cfg | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,33 @@ | ||
| server: | ||
| port: "${PORT:-8080}" | ||
|
|
||
| providers: | ||
| openai-primary: | ||
| type: "openai" | ||
| api_key: "${OPENAI_API_KEY}" | ||
|
|
||
| anthropic-primary: | ||
| type: "anthropic" | ||
| api_key: "${ANTHROPIC_API_KEY}" | ||
|
|
||
| gemini-primary: | ||
| type: "gemini" | ||
| api_key: "${GEMINI_API_KEY}" | ||
|
|
||
| # Example: Groq (OpenAI-compatible) | ||
| # groq: | ||
| # type: "openai" | ||
| # base_url: "https://api.groq.com/openai/v1" | ||
| # api_key: "${GROQ_API_KEY}" | ||
|
|
||
| # Example: Azure OpenAI | ||
| # azure-openai: | ||
| # type: "openai" | ||
| # base_url: "https://your-resource.openai.azure.com/openai/deployments/your-deployment" | ||
| # api_key: "${AZURE_OPENAI_API_KEY}" | ||
|
|
||
| # Example: DeepSeek (OpenAI-compatible) | ||
| # deepseek: | ||
| # type: "openai" | ||
| # base_url: "https://api.deepseek.com/v1" | ||
| # api_key: "${DEEPSEEK_API_KEY}" |
Uh oh!
There was an error while loading. Please reload this page.