package config import ( "encoding/json" "fmt" "os" "path/filepath" "time" "github.com/fujin/anthropic-proxy/internal/auth" "gopkg.in/yaml.v3" ) type Config struct { Port int `yaml:"port"` APIKeys []string `yaml:"api_keys"` AuthDir string `yaml:"auth_dir"` ClaudeCredentials string `yaml:"claude_credentials"` ClaudeBinary string `yaml:"claude_binary"` } type authFileJSON struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` Email string `json:"email"` Expired string `json:"expired"` Type string `json:"type"` } type claudeCredentialsJSON struct { ClaudeAiOauth struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` ExpiresAt int64 `json:"expiresAt"` SubscriptionType string `json:"subscriptionType"` } `json:"claudeAiOauth"` } 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) } return cfg, nil } func LoadCredentials(cfg *Config) ([]*auth.Credential, error) { var creds []*auth.Credential if cfg.ClaudeCredentials != "" { cred, err := loadClaudeCredentials(cfg.ClaudeCredentials) if err != nil { return nil, fmt.Errorf("load claude credentials: %w", err) } creds = append(creds, cred) } if cfg.AuthDir != "" { dirCreds, err := loadAuthDir(cfg.AuthDir) if err != nil { return nil, fmt.Errorf("load auth dir: %w", err) } creds = append(creds, dirCreds...) } return creds, nil } func loadClaudeCredentials(path string) (*auth.Credential, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var cf claudeCredentialsJSON if err := json.Unmarshal(data, &cf); err != nil { return nil, err } oauth := cf.ClaudeAiOauth if oauth.AccessToken == "" { return nil, fmt.Errorf("no access token in %s", path) } return &auth.Credential{ ID: "claude-native", Email: oauth.SubscriptionType, AccessToken: oauth.AccessToken, RefreshToken: oauth.RefreshToken, ExpiresAt: time.UnixMilli(oauth.ExpiresAt), FilePath: path, }, nil } func loadAuthDir(authDir string) ([]*auth.Credential, error) { pattern := filepath.Join(authDir, "*.json") files, err := filepath.Glob(pattern) if err != nil { return nil, fmt.Errorf("glob auth files: %w", err) } var creds []*auth.Credential for _, f := range files { cred, err := loadAuthFile(f) if err != nil { return nil, fmt.Errorf("load auth file %s: %w", f, err) } creds = append(creds, cred) } return creds, nil } func loadAuthFile(path string) (*auth.Credential, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var af authFileJSON if err := json.Unmarshal(data, &af); err != nil { return nil, err } expiresAt, err := time.Parse(time.RFC3339, af.Expired) if err != nil { expiresAt, err = time.Parse("2006-01-02T15:04:05", af.Expired) if err != nil { expiresAt = time.Now() } } return &auth.Credential{ ID: filepath.Base(path), Email: af.Email, AccessToken: af.AccessToken, RefreshToken: af.RefreshToken, ExpiresAt: expiresAt, FilePath: path, }, nil }