backend: move default backend selection to backends.conf

backends.conf now supports a top-level 'backend = name' line that
selects the default backend for new sessions. Lookup order:

  1. backends.conf 'backend = ...'
  2. OLLIE_BACKEND env var (fallback)
  3. session-level backend= parameter (override)

No env file needed for backend/model config anymore.
This commit is contained in:
Ollie Agent 2026-08-08 22:57:37 +02:00
parent c9df895445
commit d596c31606
3 changed files with 42 additions and 20 deletions

View File

@ -17,10 +17,13 @@ type BackendConfig struct {
} }
// configFile is the parsed contents of backends.conf: section name → key/value pairs. // configFile is the parsed contents of backends.conf: section name → key/value pairs.
type configFile map[string]BackendConfig type configFile struct {
Default string // top-level "backend = ..." selects the default
Backends map[string]BackendConfig
}
// loadConfig reads ~/.config/ollie/backends.conf. // loadConfig reads ~/.config/ollie/backends.conf.
// Returns an empty map (not nil) if the file doesn't exist or is unreadable. // Returns zero value if the file doesn't exist or is unreadable.
func loadConfig() configFile { func loadConfig() configFile {
return parseConfigFile(paths.CfgDir() + "/backends.conf") return parseConfigFile(paths.CfgDir() + "/backends.conf")
} }
@ -28,6 +31,8 @@ func loadConfig() configFile {
// parseConfigFile parses an INI-style config file. // parseConfigFile parses an INI-style config file.
// Format: // Format:
// //
// backend = openrouter (top-level: default backend selection)
//
// [section] // [section]
// key = value // key = value
// //
@ -35,11 +40,11 @@ func loadConfig() configFile {
func parseConfigFile(path string) configFile { func parseConfigFile(path string) configFile {
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
return configFile{} return configFile{Backends: map[string]BackendConfig{}}
} }
defer f.Close() defer f.Close()
cfg := configFile{} cfg := configFile{Backends: map[string]BackendConfig{}}
section := "" section := ""
sc := bufio.NewScanner(f) sc := bufio.NewScanner(f)
@ -51,15 +56,12 @@ func parseConfigFile(path string) configFile {
// Section header. // Section header.
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
section = strings.TrimSpace(line[1 : len(line)-1]) section = strings.TrimSpace(line[1 : len(line)-1])
if _, ok := cfg[section]; !ok { if _, ok := cfg.Backends[section]; !ok {
cfg[section] = BackendConfig{} cfg.Backends[section] = BackendConfig{}
} }
continue continue
} }
// Key = value. // Key = value.
if section == "" {
continue // ignore keys before first section
}
k, v, ok := strings.Cut(line, "=") k, v, ok := strings.Cut(line, "=")
if !ok { if !ok {
continue continue
@ -71,7 +73,15 @@ func parseConfigFile(path string) configFile {
v = v[1 : len(v)-1] v = v[1 : len(v)-1]
} }
bc := cfg[section] // Top-level keys (before any section).
if section == "" {
if k == "backend" {
cfg.Default = v
}
continue
}
bc := cfg.Backends[section]
switch k { switch k {
case "key": case "key":
bc.Key = v bc.Key = v
@ -82,7 +92,7 @@ func parseConfigFile(path string) configFile {
case "host": case "host":
bc.Host = v bc.Host = v
} }
cfg[section] = bc cfg.Backends[section] = bc
} }
return cfg return cfg
} }

View File

@ -6,20 +6,27 @@ import (
) )
// NewWithName constructs a Backend for the given backend name. // NewWithName constructs a Backend for the given backend name.
// If name is empty, falls back to OLLIE_BACKEND; errors if neither is set. // If name is empty, falls back to backends.conf default, then OLLIE_BACKEND env.
func NewWithName(name string) (Backend, error) { func NewWithName(name string) (Backend, error) {
cfg := loadConfig()
if name == "" {
name = cfg.Default
}
if name == "" { if name == "" {
name = os.Getenv("OLLIE_BACKEND") name = os.Getenv("OLLIE_BACKEND")
if name == "" {
return nil, fmt.Errorf("no backend specified: set OLLIE_BACKEND or pass backend= to the session")
}
} }
return newBackend(name) if name == "" {
return nil, fmt.Errorf("no backend specified: set 'backend' in backends.conf, OLLIE_BACKEND, or pass backend= to the session")
}
return newBackendFromConfig(name, cfg)
} }
func newBackend(which string) (Backend, error) { func newBackend(which string) (Backend, error) {
cfg := loadConfig() return newBackendFromConfig(which, loadConfig())
bc := cfg[which] }
func newBackendFromConfig(which string, cfg configFile) (Backend, error) {
bc := cfg.Backends[which]
switch which { switch which {
case "ollama": case "ollama":

View File

@ -1,13 +1,18 @@
# Ollie backend configuration. # Ollie backend configuration.
# Each section configures a backend. The session selects which backend to use # Each section configures a backend. The session selects which backend to use
# via OLLIE_BACKEND env var or backend= parameter. # via the top-level 'backend' setting or backend= parameter.
# #
# Fields: # Top-level:
# backend = name (default backend for new sessions)
#
# Per-backend fields:
# key = API key or token # key = API key or token
# url = base URL override (optional; each backend has a sensible default) # url = base URL override (optional; each backend has a sensible default)
# model = default model (optional; overridden by session model= parameter) # model = default model (optional; overridden by session model= parameter)
# host = host address (ollama only) # host = host address (ollama only)
backend = openrouter
[openrouter] [openrouter]
# key = sk-or-v1-... # key = sk-or-v1-...
# model = qwen/qwen3-235b-a22b # model = qwen/qwen3-235b-a22b