package config import ( "math" "os" "strconv" "strings" "time" "github.com/pelletier/go-toml/v2" ) // File is the on-disk /etc/sift/config.toml. type File struct { Listen string `toml:"listen"` UserAgent string `toml:"user_agent"` DenyPrefixes []string `toml:"deny_prefixes"` SelfIP SelfIP `toml:"self_ip"` Dixie Dixie `toml:"dixie"` Digest Digest `toml:"digest"` Context Context `toml:"context"` Search Search `toml:"search"` Batch Batch `toml:"batch"` } // Batch is the [batch] block. Batch is on by default: no block means the runner runs with its own // defaults. disabled = true turns it off and the endpoints answer 503. type Batch struct { Disabled bool `toml:"disabled"` TargetsPerJob int `toml:"targets_per_job"` JobsPerCaller int `toml:"jobs_per_caller"` JobsTotal int `toml:"jobs_total"` Workers int `toml:"workers"` TTL string `toml:"ttl"` MaxBytes string `toml:"max_bytes"` } type Dixie struct { Chat string `toml:"chat"` Embed string `toml:"embed"` EmbedModel string `toml:"embed_model"` MaxInflight int `toml:"max_inflight"` QueueDepth int `toml:"queue_depth"` QueueWait string `toml:"queue_wait"` } type Digest struct { Model string `toml:"model"` Thinking bool `toml:"thinking"` Cache string `toml:"cache"` } type Context struct { Honcho string `toml:"honcho"` Workspaces []string `toml:"workspaces"` PermitRestricted []string `toml:"permit_restricted"` } // Search is the [search] block. Sources default off; bake-off picks defaults. type Search struct { K int `toml:"k"` Timeout string `toml:"timeout"` Rerank *bool `toml:"rerank"` Digest bool `toml:"digest"` CacheTTL string `toml:"cache_ttl"` SearxNG SearchSource `toml:"searxng"` BraveLLM SearchSource `toml:"brave_llm"` Tavily TavilySource `toml:"tavily"` } type SearchSource struct { Enabled bool `toml:"enabled"` URL string `toml:"url"` NoRerank bool `toml:"no_rerank"` } type TavilySource struct { Enabled bool `toml:"enabled"` URL string `toml:"url"` DailyCreditCap int `toml:"daily_credit_cap"` NoRerank bool `toml:"no_rerank"` } func (s Search) RerankOn() bool { if s.Rerank == nil { return true } return *s.Rerank } func (s Search) AnyEnabled() bool { return s.SearxNG.Enabled || s.BraveLLM.Enabled || s.Tavily.Enabled } // SelfIP is the [self_ip] block. type SelfIP struct { Sources []string `toml:"sources"` Interval string `toml:"interval"` KeepPrevious string `toml:"keep_previous"` Path string `toml:"path"` } // Load reads path. A missing file returns a zero File and a nil error. func Load(path string) (File, error) { var f File b, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return f, nil } return f, err } if err := toml.Unmarshal(b, &f); err != nil { return File{}, err } return f, nil } func ParseDuration(s string, def time.Duration) time.Duration { if s == "" { return def } d, err := time.ParseDuration(s) if err != nil { return def } return d } // ParseSize parses a positive byte count: a plain integer, or one with a binary suffix KiB, MiB or // GiB. Anything else, zero or negative returns def. func ParseSize(s string, def int64) int64 { s = strings.TrimSpace(s) if s == "" { return def } var mult int64 switch { case strings.HasSuffix(s, "KiB"): mult, s = 1<<10, s[:len(s)-3] case strings.HasSuffix(s, "MiB"): mult, s = 1<<20, s[:len(s)-3] case strings.HasSuffix(s, "GiB"): mult, s = 1<<30, s[:len(s)-3] default: mult = 1 } n, err := strconv.ParseInt(s, 10, 64) if err != nil || n <= 0 || n > math.MaxInt64/mult { // the last: n*mult would overflow return def } return n * mult }