lean_ctx/core/config/
memory.rs1use serde::{Deserialize, Serialize};
4
5use super::Config;
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
11#[serde(rename_all = "lowercase")]
12pub enum MemoryCleanup {
13 Aggressive,
14 #[default]
15 Shared,
16}
17
18impl MemoryCleanup {
19 pub fn from_env() -> Option<Self> {
20 std::env::var("LEAN_CTX_MEMORY_CLEANUP").ok().and_then(|v| {
21 match v.trim().to_lowercase().as_str() {
22 "aggressive" => Some(Self::Aggressive),
23 "shared" => Some(Self::Shared),
24 _ => None,
25 }
26 })
27 }
28
29 pub fn effective(config: &Config) -> Self {
30 if let Some(env_val) = Self::from_env() {
31 return env_val;
32 }
33 config.memory_cleanup.clone()
34 }
35
36 pub fn idle_ttl_secs(&self) -> u64 {
38 match self {
39 Self::Aggressive => 300,
40 Self::Shared => 3600,
41 }
42 }
43
44 pub fn index_retention_multiplier(&self) -> f64 {
46 match self {
47 Self::Aggressive => 1.0,
48 Self::Shared => 3.0,
49 }
50 }
51}
52
53#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
58#[serde(rename_all = "lowercase")]
59pub enum MemoryProfile {
60 Low,
61 Balanced,
62 #[default]
63 Performance,
64}
65
66impl MemoryProfile {
67 pub fn from_env() -> Option<Self> {
68 std::env::var("LEAN_CTX_MEMORY_PROFILE").ok().and_then(|v| {
69 match v.trim().to_lowercase().as_str() {
70 "low" => Some(Self::Low),
71 "balanced" => Some(Self::Balanced),
72 "performance" => Some(Self::Performance),
73 _ => None,
74 }
75 })
76 }
77
78 pub fn effective(config: &Config) -> Self {
79 if let Some(env_val) = Self::from_env() {
80 return env_val;
81 }
82 config.memory_profile.clone()
83 }
84
85 pub fn bm25_max_cache_mb(&self) -> u64 {
86 match self {
87 Self::Low => 64,
88 Self::Balanced => 128,
89 Self::Performance => 512,
90 }
91 }
92
93 pub fn semantic_cache_enabled(&self) -> bool {
94 !matches!(self, Self::Low)
95 }
96
97 pub fn embeddings_enabled(&self) -> bool {
98 !matches!(self, Self::Low)
99 }
100
101 pub fn lsp_idle_ttl(&self) -> std::time::Duration {
103 std::time::Duration::from_mins(match self {
104 Self::Low => 5,
105 Self::Balanced => 15,
106 Self::Performance => 30,
107 })
108 }
109}
110
111#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
119#[serde(rename_all = "lowercase")]
120pub enum SavingsFooter {
121 Auto,
122 Always,
123 #[default]
124 Never,
125}
126
127impl SavingsFooter {
128 pub fn from_env() -> Option<Self> {
129 std::env::var("LEAN_CTX_SAVINGS_FOOTER").ok().and_then(|v| {
130 match v.trim().to_lowercase().as_str() {
131 "auto" => Some(Self::Auto),
132 "always" => Some(Self::Always),
133 "never" => Some(Self::Never),
134 _ => None,
135 }
136 })
137 }
138
139 pub fn effective() -> Self {
140 if let Some(env_val) = Self::from_env() {
141 return env_val;
142 }
143 let cfg = super::Config::load();
144 cfg.savings_footer.clone()
145 }
146}
147
148#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
154#[serde(rename_all = "lowercase")]
155pub enum CompressionAnnotation {
156 Full,
157 #[default]
158 Quantized,
159 None,
160}
161
162impl CompressionAnnotation {
163 pub fn effective() -> Self {
164 if let Ok(v) = std::env::var("LEAN_CTX_COMPRESSION_ANNOTATION") {
165 return match v.trim().to_lowercase().as_str() {
166 "full" => Self::Full,
167 "quantized" => Self::Quantized,
168 "none" => Self::None,
169 _ => Self::default(),
170 };
171 }
172 let cfg = super::Config::load();
173 cfg.compression_annotation.clone()
174 }
175}
176
177pub struct MemoryGuardConfig {
179 pub max_ram_percent: u8,
180}
181
182impl MemoryGuardConfig {
183 pub fn effective(config: &Config) -> Self {
184 let base_pct = std::env::var("LEAN_CTX_MAX_RAM_PERCENT")
185 .ok()
186 .and_then(|v| v.parse::<u8>().ok())
187 .unwrap_or(config.max_ram_percent)
188 .clamp(1, 50);
189 let profile = MemoryProfile::effective(config);
192 let pct = if profile == MemoryProfile::Low {
193 (base_pct / 2).max(3)
194 } else {
195 base_pct
196 };
197 Self {
198 max_ram_percent: pct,
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn savings_footer_defaults_to_never() {
209 assert_eq!(SavingsFooter::default(), SavingsFooter::Never);
210 }
211
212 #[test]
213 fn savings_footer_from_env_accepts_auto() {
214 let _guard = crate::core::data_dir::test_env_lock();
215 crate::test_env::set_var("LEAN_CTX_SAVINGS_FOOTER", "auto");
216 assert_eq!(SavingsFooter::from_env(), Some(SavingsFooter::Auto));
217 crate::test_env::remove_var("LEAN_CTX_SAVINGS_FOOTER");
218 }
219}