agentroot_core/config/
mod.rs1pub mod virtual_path;
4
5use crate::error::Result;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Serialize, Deserialize, Default)]
12pub struct Config {
13 #[serde(default)]
15 pub global_context: Option<String>,
16
17 #[serde(default)]
19 pub collections: HashMap<String, CollectionConfig>,
20
21 #[serde(default)]
23 pub llm_service: LLMServiceConfig,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct LLMServiceConfig {
29 pub url: String,
31
32 #[serde(default = "default_chat_model")]
34 pub model: String,
35
36 #[serde(default)]
38 pub embedding_url: Option<String>,
39
40 #[serde(default = "default_embedding_model")]
42 pub embedding_model: String,
43
44 #[serde(default)]
46 pub embedding_dimensions: Option<usize>,
47
48 #[serde(default)]
50 pub api_key: Option<String>,
51
52 #[serde(default = "default_timeout")]
54 pub timeout_secs: u64,
55}
56
57impl LLMServiceConfig {
58 pub fn embeddings_url(&self) -> &str {
60 self.embedding_url.as_deref().unwrap_or(&self.url)
61 }
62}
63
64impl Default for LLMServiceConfig {
65 fn default() -> Self {
66 Self {
67 url: std::env::var("AGENTROOT_LLM_URL")
68 .unwrap_or_else(|_| "http://localhost:8000".to_string()),
69 model: default_chat_model(),
70 embedding_url: std::env::var("AGENTROOT_EMBEDDING_URL").ok(),
71 embedding_model: default_embedding_model(),
72 embedding_dimensions: std::env::var("AGENTROOT_EMBEDDING_DIMS")
73 .ok()
74 .and_then(|s| s.parse().ok()),
75 api_key: std::env::var("AGENTROOT_LLM_API_KEY").ok(),
76 timeout_secs: default_timeout(),
77 }
78 }
79}
80
81fn default_chat_model() -> String {
82 std::env::var("AGENTROOT_LLM_MODEL")
83 .unwrap_or_else(|_| "meta-llama/Llama-3.1-8B-Instruct".to_string())
84}
85
86fn default_embedding_model() -> String {
87 std::env::var("AGENTROOT_EMBEDDING_MODEL")
88 .unwrap_or_else(|_| "sentence-transformers/all-MiniLM-L6-v2".to_string())
89}
90
91fn default_timeout() -> u64 {
92 30
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct CollectionConfig {
98 pub path: PathBuf,
100
101 #[serde(default = "default_pattern")]
103 pub pattern: String,
104
105 #[serde(default)]
107 pub context: HashMap<String, String>,
108
109 #[serde(default)]
111 pub update: Option<String>,
112}
113
114fn default_pattern() -> String {
115 "**/*.md".to_string()
116}
117
118impl Config {
119 pub fn load() -> Result<Self> {
121 let path = Self::default_path();
122 if path.exists() {
123 let content = std::fs::read_to_string(&path)?;
124 let config: Config = serde_yaml::from_str(&content)?;
125 Ok(config)
126 } else {
127 Ok(Config::default())
128 }
129 }
130
131 pub fn save(&self) -> Result<()> {
133 let path = Self::default_path();
134 if let Some(parent) = path.parent() {
135 std::fs::create_dir_all(parent)?;
136 }
137 let content = serde_yaml::to_string(self)?;
138 std::fs::write(path, content)?;
139 Ok(())
140 }
141
142 pub fn default_path() -> PathBuf {
144 dirs::config_dir()
145 .unwrap_or_else(|| PathBuf::from("."))
146 .join(crate::CONFIG_DIR_NAME)
147 .join("config.yml")
148 }
149
150 pub fn get_context_for_path(&self, collection: &str, path: &str) -> Option<String> {
152 let collection_config = self.collections.get(collection)?;
153
154 let mut matching: Vec<(&str, &str)> = collection_config
156 .context
157 .iter()
158 .filter(|(prefix, _)| path.starts_with(*prefix) || prefix.is_empty() || *prefix == "/")
159 .map(|(prefix, ctx)| (prefix.as_str(), ctx.as_str()))
160 .collect();
161
162 matching.sort_by_key(|(prefix, _)| prefix.len());
164
165 if matching.is_empty() {
167 self.global_context.clone()
168 } else {
169 let combined: Vec<&str> = matching.iter().map(|(_, ctx)| *ctx).collect();
170 let mut result = combined.join("\n\n");
171 if let Some(ref global) = self.global_context {
172 result = format!("{}\n\n{}", global, result);
173 }
174 Some(result)
175 }
176 }
177}