Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288package config
import ( "encoding/json" "errors" "net/url" "os" "path/filepath" "regexp" "strings" "unicode")
var ( providerNameRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$`))
// HomeDir returns the albedo configuration directory.func HomeDir() string { if env := os.Getenv("ALBEDO_HOME"); env != "" { abs, err := filepath.Abs(env) if err == nil { return abs } return env } home, err := os.UserHomeDir() if err != nil { return ".albedo" } return filepath.Join(home, ".albedo")}
// Settings represents a provider configuration.type Settings struct { Extension string `json:"extension,omitempty"` BaseURL string `json:"baseUrl,omitempty"` APIKey string `json:"apiKey,omitempty"` Model string `json:"model"` Protocol string `json:"protocol"`}
func validateModel(value string) (string, error) { trimmed := strings.TrimSpace(value) if trimmed == "" || len(trimmed) > 512 { return "", errors.New("provider needs a model id of 1–512 characters") } for _, r := range trimmed { if r < 0x20 || r == 0x7f { return "", errors.New("provider needs a model id of 1–512 characters") } } return trimmed, nil}
// Validate checks whether the Settings instance is valid.func (s Settings) Validate() (Settings, error) { if s.Extension == "codex" { if s.Protocol != "responses" { return s, errors.New("codex requires the responses protocol") } model, err := validateModel(s.Model) if err != nil { return s, err } return Settings{ Extension: "codex", Model: model, Protocol: "responses", }, nil }
if s.Extension != "" && s.Extension != "openai" { // A sign-in provider owns its endpoint and credentials; its profile // carries only the protocol the daemon reported and the model. if s.Protocol != "responses" && s.Protocol != "chat_completions" { return s, errors.New("provider protocol must be responses or chat_completions") } model, err := validateModel(s.Model) if err != nil { return s, err } return Settings{Extension: s.Extension, Model: model, Protocol: s.Protocol}, nil }
if s.APIKey == "" { return s, errors.New("openai provider needs an endpoint, api key, model and valid protocol") } for _, r := range s.APIKey { if unicode.IsSpace(r) || r < 0x20 || r == 0x7f { return s, errors.New("openai provider needs an endpoint, api key, model and valid protocol") } }
if s.Protocol != "responses" && s.Protocol != "chat_completions" { return s, errors.New("openai provider needs an endpoint, api key, model and valid protocol") }
endpoint, err := ValidateEndpoint(s.BaseURL) if err != nil { return s, err }
model, err := validateModel(s.Model) if err != nil { return s, err }
return Settings{ Extension: "openai", BaseURL: endpoint, APIKey: s.APIKey, Model: model, Protocol: s.Protocol, }, nil}
// ValidateProviderName validates a profile name.func ValidateProviderName(value string) (string, error) { name := strings.TrimSpace(value) if !providerNameRegex.MatchString(name) { return "", errors.New("name must be 1–64 letters, numbers, dots, underscores or hyphens") } return name, nil}
// ValidateEndpoint parses and validates an HTTP/HTTPS API endpoint without query, credentials, or fragment.func ValidateEndpoint(value string) (string, error) { u, err := url.Parse(strings.TrimSpace(value)) if err != nil || u.Scheme == "" || u.Host == "" { return "", errors.New("enter an http or https api base url") } if u.Scheme != "http" && u.Scheme != "https" { return "", errors.New("enter an http or https api base url") } if u.User != nil || u.RawQuery != "" || u.Fragment != "" { return "", errors.New("use an http or https base url without credentials, query or fragment") } res := u.String() for strings.HasSuffix(res, "/") { res = strings.TrimSuffix(res, "/") } return res, nil}
// Profiles represents the stored provider configuration.type Profiles struct { Active string `json:"active,omitempty"` Providers map[string]Settings `json:"providers"`}
type rawConfigFile struct { Active *string `json:"active,omitempty"` Providers map[string]Settings `json:"providers,omitempty"` Extension string `json:"extension,omitempty"` BaseURL string `json:"baseUrl,omitempty"` APIKey string `json:"apiKey,omitempty"` Model string `json:"model,omitempty"` Protocol string `json:"protocol,omitempty"`}
// LoadProfiles reads and validates configuration from config.json in the specified directory.func LoadProfiles(directory string) (Profiles, error) { configPath := filepath.Join(directory, "config.json") fi, err := os.Stat(configPath) if err != nil { if os.IsNotExist(err) { return Profiles{Providers: make(map[string]Settings)}, nil } return Profiles{}, err } if fi.Size() > 2*1024*1024 { return Profiles{}, errors.New("invalid provider configuration in config.json; repair it before logging in") } data, err := os.ReadFile(configPath) if err != nil { if os.IsNotExist(err) { return Profiles{Providers: make(map[string]Settings)}, nil } return Profiles{}, err }
var rawMap map[string]json.RawMessage if err := json.Unmarshal(data, &rawMap); err != nil { return Profiles{}, errors.New("invalid provider configuration in config.json; repair it before logging in") }
if _, hasProviders := rawMap["providers"]; !hasProviders { var single Settings if err := json.Unmarshal(data, &single); err != nil { return Profiles{}, errors.New("invalid provider configuration in config.json; repair it before logging in") } validated, err := single.Validate() if err != nil { return Profiles{}, errors.New("invalid provider configuration in config.json; repair it before logging in") } return Profiles{ Active: "default", Providers: map[string]Settings{"default": validated}, }, nil }
var cfg rawConfigFile if err := json.Unmarshal(data, &cfg); err != nil { return Profiles{}, errors.New("invalid provider configuration in config.json; repair it before logging in") }
providers := make(map[string]Settings) for k, v := range cfg.Providers { name, err := ValidateProviderName(k) if err != nil { return Profiles{}, errors.New("invalid provider configuration in config.json; repair it before logging in") } validated, err := v.Validate() if err != nil { return Profiles{}, errors.New("invalid provider configuration in config.json; repair it before logging in") } providers[name] = validated }
var active string if cfg.Active != nil { active = *cfg.Active if _, ok := providers[active]; !ok { return Profiles{}, errors.New("invalid provider configuration in config.json; repair it before logging in") } }
return Profiles{ Active: active, Providers: providers, }, nil}
// SaveProvider stores a named provider configuration in config.json and makes// it active.func SaveProvider(directory, name string, s Settings) error { validatedName, err := ValidateProviderName(name) if err != nil { return err } validatedSettings, err := s.Validate() if err != nil { return err } return updateProfiles(directory, func(profiles *Profiles) { profiles.Active = validatedName profiles.Providers[validatedName] = validatedSettings })}
// RemoveProvider deletes a named provider from config.json. Removing the// active provider hands activity to the first remaining one by name, since the// daemon cannot start sessions without an active provider.func RemoveProvider(directory, name string) error { return updateProfiles(directory, func(profiles *Profiles) { delete(profiles.Providers, name) if _, ok := profiles.Providers[profiles.Active]; ok { return } profiles.Active = "" for remaining := range profiles.Providers { if profiles.Active == "" || remaining < profiles.Active { profiles.Active = remaining } } })}
// updateProfiles rewrites config.json under config.lock.func updateProfiles(directory string, update func(*Profiles)) error { err := lockedUpdate(directory, "config.lock", 1, func() error { saved, err := LoadProfiles(directory) if err != nil { return err } if saved.Providers == nil { saved.Providers = make(map[string]Settings) } update(&saved) return writeJSONAtomic(directory, "config.json", saved) }) if errors.Is(err, os.ErrExist) { return errors.New("another login is saving; retry, or remove config.lock if that process has stopped") } return err}