package backend import ( "bufio" "os" "strings" "ollie/paths" ) // BackendConfig holds the per-backend configuration from backends.conf. type BackendConfig struct { Key string // API key / token URL string // base URL override (empty = use backend's default) Model string // default model override CompactionModel string // model used for history compaction (empty = use Model) Host string // for ollama: host address } // configFile is the parsed contents of backends.conf: section name → key/value pairs. type configFile struct { Default string // top-level "backend = ..." selects the default Backends map[string]BackendConfig } // loadConfig reads ~/.config/ollie/backends.conf. // Returns zero value if the file doesn't exist or is unreadable. func loadConfig() configFile { return parseConfigFile(paths.CfgDir() + "/backends.conf") } // CompactionModel returns the configured compaction model for a backend. // It falls back to that backend's configured default model when no dedicated // compaction model is set. func CompactionModel(name string) string { cfg := loadConfig() bc := cfg.Backends[name] if bc.CompactionModel != "" { return bc.CompactionModel } return bc.Model } // parseConfigFile parses an INI-style config file. // Format: // // backend = openrouter (top-level: default backend selection) // // [section] // key = value // // Lines starting with # are comments. Blank lines are ignored. func parseConfigFile(path string) configFile { f, err := os.Open(path) if err != nil { return configFile{Backends: map[string]BackendConfig{}} } defer f.Close() cfg := configFile{Backends: map[string]BackendConfig{}} section := "" sc := bufio.NewScanner(f) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } // Section header. if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { section = strings.TrimSpace(line[1 : len(line)-1]) if _, ok := cfg.Backends[section]; !ok { cfg.Backends[section] = BackendConfig{} } continue } // Key = value. k, v, ok := strings.Cut(line, "=") if !ok { continue } k = strings.TrimSpace(k) v = strings.TrimSpace(v) // Strip surrounding quotes. if len(v) >= 2 && (v[0] == '"' && v[len(v)-1] == '"' || v[0] == '\'' && v[len(v)-1] == '\'') { v = v[1 : len(v)-1] } // Top-level keys (before any section). if section == "" { if k == "backend" { cfg.Default = v } continue } bc := cfg.Backends[section] switch k { case "key": bc.Key = v case "url": bc.URL = v case "model": bc.Model = v case "compactionModel", "compaction_model", "compaction-model": bc.CompactionModel = v case "host": bc.Host = v } cfg.Backends[section] = bc } return cfg }