package config import ( "fmt" "os" "gopkg.in/yaml.v3" ) type Config struct { Port int `yaml:"port"` APIKeys []string `yaml:"api_keys"` ClaudeBinary string `yaml:"claude_binary"` Sanitize SanitizeConfig `yaml:"sanitize"` Logging LoggingConfig `yaml:"logging"` Telemetry TelemetryConfig `yaml:"telemetry"` } type SanitizeConfig struct { Tools []RenameRule `yaml:"tools"` System []ReplaceRule `yaml:"system"` Body []ReplaceRule `yaml:"body"` } type RenameRule struct { From string `yaml:"from"` To string `yaml:"to"` } type ReplaceRule struct { Match string `yaml:"match"` Replace string `yaml:"replace"` } type TelemetryConfig struct { Export ExportConfig `yaml:"export"` Embedded EmbeddedConfig `yaml:"embedded"` ServiceName string `yaml:"service_name"` } type ExportConfig struct { Endpoint string `yaml:"endpoint"` Insecure bool `yaml:"insecure"` Headers map[string]string `yaml:"headers"` } func (e ExportConfig) Enabled() bool { return e.Endpoint != "" } type EmbeddedConfig struct { Enabled bool `yaml:"enabled"` Port int `yaml:"port"` PersesBinary string `yaml:"perses_binary"` VMBinary string `yaml:"vm_binary"` VMPort int `yaml:"vm_port"` BinDir string `yaml:"bin_dir"` } type LoggingConfig struct { Level string `yaml:"level"` File string `yaml:"file"` MaxSizeMB int `yaml:"max_size_mb"` MaxBackups int `yaml:"max_backups"` MaxAgeDays int `yaml:"max_age_days"` Compress bool `yaml:"compress"` } func Load(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read config %s: %w", path, err) } cfg := &Config{Port: 8080} if err := yaml.Unmarshal(data, cfg); err != nil { return nil, fmt.Errorf("parse config: %w", err) } if cfg.Logging.Level == "" { cfg.Logging.Level = "info" } if cfg.Logging.MaxSizeMB == 0 { cfg.Logging.MaxSizeMB = 100 } if cfg.Logging.MaxBackups == 0 { cfg.Logging.MaxBackups = 5 } if cfg.Logging.MaxAgeDays == 0 { cfg.Logging.MaxAgeDays = 30 } if cfg.Telemetry.ServiceName == "" { cfg.Telemetry.ServiceName = "anthropic-proxy" } if cfg.Telemetry.Embedded.Port == 0 { cfg.Telemetry.Embedded.Port = 8080 } if cfg.Telemetry.Embedded.PersesBinary == "" { cfg.Telemetry.Embedded.PersesBinary = "perses" } if cfg.Telemetry.Embedded.VMBinary == "" { cfg.Telemetry.Embedded.VMBinary = "victoria-metrics" } if cfg.Telemetry.Embedded.VMPort == 0 { cfg.Telemetry.Embedded.VMPort = 8428 } // Check for deprecated claude_credentials field var rawCfg map[string]interface{} if err := yaml.Unmarshal(data, &rawCfg); err == nil { if _, exists := rawCfg["claude_credentials"]; exists { if val, ok := rawCfg["claude_credentials"].(string); ok && val != "" { return nil, fmt.Errorf("claude_credentials is no longer supported, remove it from config.yaml — the proxy now manages credentials at ~/.claude/.credentials.json") } } } return cfg, nil }