Skip to main content

batuta/serve/banco/
config.rs

1//! Banco configuration persistence.
2//!
3//! Loads from `~/.banco/config.toml` at startup. Falls back to defaults
4//! if the file doesn't exist or is malformed (warn, don't fail).
5
6use crate::serve::backends::PrivacyTier;
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10/// Top-level Banco configuration.
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12pub struct BancoConfig {
13    #[serde(default)]
14    pub server: ServerConfig,
15    #[serde(default)]
16    pub inference: InferenceConfig,
17    #[serde(default)]
18    pub budget: BudgetConfig,
19}
20
21/// Server binding and privacy settings.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ServerConfig {
24    #[serde(default = "default_host")]
25    pub host: String,
26    #[serde(default = "default_port")]
27    pub port: u16,
28    #[serde(default)]
29    pub privacy_tier: PrivacyTierConfig,
30}
31
32/// Inference parameter defaults.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct InferenceConfig {
35    #[serde(default = "default_temperature")]
36    pub temperature: f32,
37    #[serde(default = "default_top_p")]
38    pub top_p: f32,
39    #[serde(default = "default_max_tokens")]
40    pub max_tokens: u32,
41}
42
43/// Cost circuit breaker budget settings.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct BudgetConfig {
46    #[serde(default = "default_daily_limit")]
47    pub daily_limit_usd: f64,
48    #[serde(default = "default_max_request")]
49    pub max_request_usd: f64,
50}
51
52/// Privacy tier as a string for TOML ergonomics.
53#[derive(Debug, Clone, Default, Serialize, Deserialize)]
54#[serde(rename_all = "lowercase")]
55pub enum PrivacyTierConfig {
56    Sovereign,
57    Private,
58    #[default]
59    Standard,
60}
61
62impl From<PrivacyTierConfig> for PrivacyTier {
63    fn from(c: PrivacyTierConfig) -> Self {
64        match c {
65            PrivacyTierConfig::Sovereign => Self::Sovereign,
66            PrivacyTierConfig::Private => Self::Private,
67            PrivacyTierConfig::Standard => Self::Standard,
68        }
69    }
70}
71
72fn default_host() -> String {
73    "127.0.0.1".to_string()
74}
75fn default_port() -> u16 {
76    8090
77}
78fn default_temperature() -> f32 {
79    0.7
80}
81fn default_top_p() -> f32 {
82    1.0
83}
84fn default_max_tokens() -> u32 {
85    256
86}
87fn default_daily_limit() -> f64 {
88    10.0
89}
90fn default_max_request() -> f64 {
91    1.0
92}
93
94impl Default for ServerConfig {
95    fn default() -> Self {
96        Self {
97            host: default_host(),
98            port: default_port(),
99            privacy_tier: PrivacyTierConfig::default(),
100        }
101    }
102}
103
104impl Default for InferenceConfig {
105    fn default() -> Self {
106        Self {
107            temperature: default_temperature(),
108            top_p: default_top_p(),
109            max_tokens: default_max_tokens(),
110        }
111    }
112}
113
114impl Default for BudgetConfig {
115    fn default() -> Self {
116        Self { daily_limit_usd: default_daily_limit(), max_request_usd: default_max_request() }
117    }
118}
119
120impl BancoConfig {
121    /// Default config directory: `~/.banco/`
122    #[must_use]
123    pub fn config_dir() -> Option<PathBuf> {
124        dirs::home_dir().map(|h| h.join(".banco"))
125    }
126
127    /// Default config file: `~/.banco/config.toml`
128    #[must_use]
129    pub fn config_path() -> Option<PathBuf> {
130        Self::config_dir().map(|d| d.join("config.toml"))
131    }
132
133    /// Load from `~/.banco/config.toml`. Returns defaults if file missing or malformed.
134    #[must_use]
135    pub fn load() -> Self {
136        let Some(path) = Self::config_path() else {
137            return Self::default();
138        };
139        match std::fs::read_to_string(&path) {
140            Ok(content) => match toml::from_str(&content) {
141                Ok(config) => config,
142                Err(e) => {
143                    eprintln!("[banco] Warning: failed to parse {}: {e}", path.display());
144                    Self::default()
145                }
146            },
147            Err(_) => Self::default(),
148        }
149    }
150
151    /// Save current config to `~/.banco/config.toml`.
152    pub fn save(&self) -> anyhow::Result<()> {
153        let Some(dir) = Self::config_dir() else {
154            anyhow::bail!("Cannot determine home directory");
155        };
156        std::fs::create_dir_all(&dir)?;
157        let content = toml::to_string_pretty(self)?;
158        let path = dir.join("config.toml");
159        std::fs::write(&path, content)?;
160        Ok(())
161    }
162}