agtrace_runtime/
config.rs1use crate::{Error, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6pub fn resolve_workspace_path(explicit_path: Option<&str>) -> Result<PathBuf> {
12 agtrace_core::resolve_workspace_path(explicit_path).map_err(|e| match e {
13 agtrace_core::path::Error::Io(io_err) => Error::Io(io_err),
14 agtrace_core::path::Error::Config(msg) => Error::Config(msg),
15 })
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ProviderConfig {
20 pub enabled: bool,
21 pub log_root: PathBuf,
22 #[serde(default)]
23 pub context_window_override: Option<u64>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27pub struct Config {
28 #[serde(default)]
29 pub providers: HashMap<String, ProviderConfig>,
30}
31
32impl Config {
33 pub fn load() -> Result<Self> {
34 let config_path = Self::default_path()?;
35 Self::load_from(&config_path)
36 }
37
38 pub fn load_from(path: &PathBuf) -> Result<Self> {
39 if !path.exists() {
40 return Ok(Self::default());
41 }
42
43 let content = std::fs::read_to_string(path)?;
44 let config: Config = toml::from_str(&content)?;
45 Ok(config)
46 }
47
48 pub fn save(&self) -> Result<()> {
49 let config_path = Self::default_path()?;
50 self.save_to(&config_path)
51 }
52
53 pub fn save_to(&self, path: &PathBuf) -> Result<()> {
54 if let Some(parent) = path.parent() {
55 std::fs::create_dir_all(parent)?;
56 }
57
58 let content = toml::to_string_pretty(self)?;
59 std::fs::write(path, content)?;
60 Ok(())
61 }
62
63 pub fn default_path() -> Result<PathBuf> {
64 Ok(resolve_workspace_path(None)?.join("config.toml"))
65 }
66
67 pub fn detect_providers() -> Result<Self> {
68 let mut providers = HashMap::new();
69
70 for (name, path) in agtrace_providers::get_default_log_paths() {
71 if path.exists() {
72 providers.insert(
73 name,
74 ProviderConfig {
75 enabled: true,
76 log_root: path,
77 context_window_override: None,
78 },
79 );
80 }
81 }
82
83 Ok(Config { providers })
84 }
85
86 pub fn enabled_providers(&self) -> Vec<(&String, &ProviderConfig)> {
87 self.providers
88 .iter()
89 .filter(|(_, config)| config.enabled)
90 .collect()
91 }
92
93 pub fn set_provider(&mut self, name: String, config: ProviderConfig) {
94 self.providers.insert(name, config);
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use tempfile::TempDir;
102
103 #[test]
104 fn test_config_default() {
105 let config = Config::default();
106 assert_eq!(config.providers.len(), 0);
107 }
108
109 #[test]
110 fn test_config_save_and_load() -> Result<()> {
111 let temp_dir = TempDir::new()?;
112 let config_path = temp_dir.path().join("config.toml");
113
114 let mut config = Config::default();
115 config.set_provider(
116 "claude".to_string(),
117 ProviderConfig {
118 enabled: true,
119 log_root: PathBuf::from("/home/user/.claude/projects"),
120 context_window_override: None,
121 },
122 );
123
124 config.save_to(&config_path)?;
125 assert!(config_path.exists());
126
127 let loaded = Config::load_from(&config_path)?;
128 assert_eq!(loaded.providers.len(), 1);
129 assert!(loaded.providers.contains_key("claude"));
130 assert!(loaded.providers.get("claude").unwrap().enabled);
131
132 Ok(())
133 }
134
135 #[test]
136 fn test_enabled_providers() {
137 let mut config = Config::default();
138 config.set_provider(
139 "claude".to_string(),
140 ProviderConfig {
141 enabled: true,
142 log_root: PathBuf::from("/test/claude"),
143 context_window_override: None,
144 },
145 );
146 config.set_provider(
147 "codex".to_string(),
148 ProviderConfig {
149 enabled: false,
150 log_root: PathBuf::from("/test/codex"),
151 context_window_override: None,
152 },
153 );
154
155 let enabled = config.enabled_providers();
156 assert_eq!(enabled.len(), 1);
157 assert_eq!(enabled[0].0, "claude");
158 }
159
160 #[test]
161 fn test_load_nonexistent_returns_default() -> Result<()> {
162 let temp_dir = TempDir::new()?;
163 let config_path = temp_dir.path().join("nonexistent.toml");
164
165 let config = Config::load_from(&config_path)?;
166 assert_eq!(config.providers.len(), 0);
167
168 Ok(())
169 }
170}