infinite_probability_core/config/
mod.rs1use anyhow::Result;
7use directories::ProjectDirs;
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ConfigLevel {
14 Default,
16 Global,
18 GlobalLocal,
20 Project,
22 ProjectLocal,
24 Environment,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(default)]
31#[derive(Default)]
32pub struct Config {
33 pub general: GeneralConfig,
34 pub aisp: AispConfig,
35 pub llm: LlmConfig,
36}
37
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(default)]
42pub struct GeneralConfig {
43 pub log_level: String,
44 pub telemetry: bool,
45}
46
47impl Default for GeneralConfig {
48 fn default() -> Self {
49 Self {
50 log_level: "info".to_string(),
51 telemetry: false,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(default)]
59pub struct AispConfig {
60 pub default_tier: String,
62 pub confidence_threshold: f64,
64 pub enable_llm_fallback: bool,
66 pub max_correction_attempts: usize,
68}
69
70impl Default for AispConfig {
71 fn default() -> Self {
72 Self {
73 default_tier: "auto".to_string(),
74 confidence_threshold: 0.8,
75 enable_llm_fallback: true,
76 max_correction_attempts: 3,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(default)]
84pub struct LlmConfig {
85 pub default_model: String,
87 pub simple_model: String,
89 pub complex_model: String,
91}
92
93impl Default for LlmConfig {
94 fn default() -> Self {
95 Self {
96 default_model: "sonnet".to_string(),
97 simple_model: "haiku".to_string(),
98 complex_model: "opus".to_string(),
99 }
100 }
101}
102
103impl Config {
104 pub fn load() -> Result<Self> {
106 let mut config = Self::default();
107
108 if let Some(global_path) = Self::global_config_path()
110 && global_path.exists() {
111 let content = std::fs::read_to_string(&global_path)?;
112 let global: Config = toml::from_str(&content)?;
113 config.merge(global);
114 }
115
116 let project_path = PathBuf::from(".infinite-probability/config.toml");
118 if project_path.exists() {
119 let content = std::fs::read_to_string(&project_path)?;
120 let project: Config = toml::from_str(&content)?;
121 config.merge(project);
122 }
123
124 config.apply_env();
126
127 Ok(config)
128 }
129
130 pub fn global_config_dir() -> Option<PathBuf> {
132 ProjectDirs::from("dev", "epiphytic", "infinite-probability").map(|dirs| dirs.config_dir().to_path_buf())
133 }
134
135 pub fn global_config_path() -> Option<PathBuf> {
137 Self::global_config_dir().map(|dir| dir.join("config.toml"))
138 }
139
140 fn merge(&mut self, other: Config) {
142 if other.general.log_level != GeneralConfig::default().log_level {
144 self.general.log_level = other.general.log_level;
145 }
146 if other.general.telemetry != GeneralConfig::default().telemetry {
147 self.general.telemetry = other.general.telemetry;
148 }
149
150 if other.aisp.default_tier != AispConfig::default().default_tier {
152 self.aisp.default_tier = other.aisp.default_tier;
153 }
154 if other.aisp.confidence_threshold != AispConfig::default().confidence_threshold {
155 self.aisp.confidence_threshold = other.aisp.confidence_threshold;
156 }
157 if other.aisp.enable_llm_fallback != AispConfig::default().enable_llm_fallback {
158 self.aisp.enable_llm_fallback = other.aisp.enable_llm_fallback;
159 }
160
161 if other.llm.default_model != LlmConfig::default().default_model {
163 self.llm.default_model = other.llm.default_model;
164 }
165 }
166
167 fn apply_env(&mut self) {
169 if let Ok(level) = std::env::var("INFINITE_PROBABILITY_LOG_LEVEL") {
170 self.general.log_level = level;
171 }
172 if let Ok(val) = std::env::var("INFINITE_PROBABILITY_AISP_TIER") {
173 self.aisp.default_tier = val;
174 }
175 if let Ok(val) = std::env::var("INFINITE_PROBABILITY_AISP_CONFIDENCE")
176 && let Ok(threshold) = val.parse() {
177 self.aisp.confidence_threshold = threshold;
178 }
179 if let Ok(val) = std::env::var("INFINITE_PROBABILITY_LLM_MODEL") {
180 self.llm.default_model = val;
181 }
182 }
183
184 pub fn save(&self, path: &PathBuf) -> Result<()> {
186 let content = toml::to_string_pretty(self)?;
187 if let Some(parent) = path.parent() {
188 std::fs::create_dir_all(parent)?;
189 }
190 std::fs::write(path, content)?;
191 Ok(())
192 }
193
194 pub fn get(&self, key: &str) -> Option<String> {
196 match key {
197 "general.log_level" => Some(self.general.log_level.clone()),
198 "general.telemetry" => Some(self.general.telemetry.to_string()),
199 "aisp.default_tier" => Some(self.aisp.default_tier.clone()),
200 "aisp.confidence_threshold" => Some(self.aisp.confidence_threshold.to_string()),
201 "aisp.enable_llm_fallback" => Some(self.aisp.enable_llm_fallback.to_string()),
202 "llm.default_model" => Some(self.llm.default_model.clone()),
203 "llm.simple_model" => Some(self.llm.simple_model.clone()),
204 "llm.complex_model" => Some(self.llm.complex_model.clone()),
205 _ => None,
206 }
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_default_config() {
216 let config = Config::default();
217 assert_eq!(config.general.log_level, "info");
218 assert_eq!(config.aisp.confidence_threshold, 0.8);
219 }
220
221 #[test]
222 fn test_get_config_value() {
223 let config = Config::default();
224 assert_eq!(config.get("general.log_level"), Some("info".to_string()));
225 assert_eq!(config.get("unknown.key"), None);
226 }
227}