129 lines
5.3 KiB
Go
129 lines
5.3 KiB
Go
// Package config implements pigo's optional user config file at
|
|
// ~/.config/pigo/config.toml (honoring $XDG_CONFIG_HOME when set) plus the
|
|
// provider-agnostic base-url env-var name derivation. Values in the file
|
|
// replace pigo's built-in defaults, but an explicit command-line flag always
|
|
// wins over the file:
|
|
//
|
|
// command-line flag > config.toml > built-in default
|
|
//
|
|
// A missing file is not an error (defaults apply); a malformed file is surfaced
|
|
// to the caller so it can warn rather than silently ignore user intent.
|
|
//
|
|
// The package is intentionally free of any cliOptions/run-assembly concern: it
|
|
// only loads and decodes the file and derives env-var names. Overlaying a
|
|
// FileConfig onto the parsed CLI options lives in cmd/pigo, alongside the
|
|
// options struct it mutates.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
// FileConfig is the on-disk shape of config.toml. Every field is optional; an
|
|
// absent (zero-value) field leaves the corresponding default/flag untouched.
|
|
// Keys are snake_case to read naturally in TOML.
|
|
type FileConfig struct {
|
|
Model string `toml:"model"`
|
|
BaseURL string `toml:"base_url"`
|
|
APIKey string `toml:"api_key"`
|
|
Protocol string `toml:"protocol"`
|
|
Provider string `toml:"provider"`
|
|
ThinkingLevel string `toml:"thinking_level"`
|
|
OutputFormat string `toml:"output_format"`
|
|
NoTools bool `toml:"no_tools"`
|
|
NoSkills bool `toml:"no_skills"`
|
|
Approve bool `toml:"approve"`
|
|
SystemPrompt string `toml:"system_prompt"`
|
|
// AllowedTools and DisallowedTools are the tool-level admission boundary:
|
|
// the config-file tier of --allowed-tools / --disallowed-tools. Names match
|
|
// case-insensitively and DisallowedTools wins when a name appears in both.
|
|
// A CLI flag replaces the file value wholesale rather than merging with it,
|
|
// so passing --allowed-tools can widen a boundary the file narrowed.
|
|
AllowedTools []string `toml:"allowed_tools"`
|
|
DisallowedTools []string `toml:"disallowed_tools"`
|
|
// Prompts is the config.toml `prompts` array: paths (files or dirs) to load
|
|
// prompt templates from at the settings tier (mirrors pi's settings prompts).
|
|
Prompts []string `toml:"prompts"`
|
|
// Memory, Checkpoint, and Compaction are nested TOML tables for the
|
|
// persistent-memory / infinite-context feature. They are pure config
|
|
// plumbing here; defaults/parsing live in memory.go (Resolve* helpers) and
|
|
// the overlay into runtime options lives in cmd/pigo. See
|
|
// tasks/spec-persistent-memory-infinite-context.md §3/§4/§5.2.
|
|
Memory MemoryConfig `toml:"memory"`
|
|
Checkpoint CheckpointConfig `toml:"checkpoint"`
|
|
Compaction CompactionConfig `toml:"compaction"`
|
|
// Dream is the [dream] TOML table for the /dream memory-consolidation
|
|
// feature. Pure config plumbing here; defaults/normalization live in
|
|
// internal/dream (Config). See tasks/spec-dream-memory-consolidation.md
|
|
// §3.3.
|
|
Dream DreamConfig `toml:"dream"`
|
|
}
|
|
|
|
// DreamConfig is the [dream] TOML table for /dream memory consolidation.
|
|
// Enabled is a pointer so an absent key (nil) is distinguishable from an
|
|
// explicit false: nil is treated as true, only enabled = false disables
|
|
// auto-trigger. IntervalDays and RecentSessions use zero as "apply default"
|
|
// (7 and 20 respectively); normalization lives in dream.Config.
|
|
type DreamConfig struct {
|
|
Enabled *bool `toml:"enabled"`
|
|
IntervalDays int `toml:"interval_days"`
|
|
RecentSessions int `toml:"recent_sessions"`
|
|
}
|
|
|
|
// FileConfigPath returns the path to the user config file:
|
|
// $XDG_CONFIG_HOME/pigo/config.toml, or ~/.config/pigo/config.toml by default.
|
|
// It returns "" when neither can be resolved, so the caller treats the file as
|
|
// absent.
|
|
func FileConfigPath() string {
|
|
if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" {
|
|
return filepath.Join(dir, "pigo", "config.toml")
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return filepath.Join(home, ".config", "pigo", "config.toml")
|
|
}
|
|
|
|
// LoadFileConfig reads and decodes config.toml. A missing file (or an empty
|
|
// path) returns a zero config with no error; a malformed file is an error.
|
|
func LoadFileConfig(path string) (FileConfig, error) {
|
|
if path == "" {
|
|
return FileConfig{}, nil
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return FileConfig{}, nil
|
|
}
|
|
return FileConfig{}, fmt.Errorf("read config %s: %w", path, err)
|
|
}
|
|
var cfg FileConfig
|
|
if err := toml.Unmarshal(data, &cfg); err != nil {
|
|
return FileConfig{}, fmt.Errorf("parse config %s: %w", path, err)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// GenericBaseURLEnvVar derives the generic base-url override env var name for a
|
|
// provider: the provider name uppercased with hyphens rewritten to underscores,
|
|
// suffixed with _BASE_URL. For example "zai-coding-cn" → "ZAI_CODING_CN_BASE_URL"
|
|
// and "deepseek" → "DEEPSEEK_BASE_URL". An empty provider name yields "".
|
|
//
|
|
// It lives here (not with ResolveBaseURL) because it is a pure name derivation
|
|
// with no dependency on the provider registry — the provider-agnostic part of
|
|
// base-url resolution. ResolveBaseURL itself lives in internal/provider.
|
|
func GenericBaseURLEnvVar(providerName string) string {
|
|
n := strings.TrimSpace(providerName)
|
|
if n == "" {
|
|
return ""
|
|
}
|
|
n = strings.ReplaceAll(n, "-", "_")
|
|
return strings.ToUpper(n) + "_BASE_URL"
|
|
}
|