sqlite_graphrag/
runtime_config.rs1use crate::config;
8use std::sync::OnceLock;
9
10#[derive(Debug, Clone, Default)]
12pub struct RuntimeOverrides {
13 pub embedding_dim: Option<u32>,
14 pub claude_binary: Option<String>,
15 pub codex_binary: Option<String>,
16 pub opencode_binary: Option<String>, pub llm_model: Option<String>,
18 pub llm_fallback: Option<String>,
19 pub skip_embedding_on_failure: bool,
20 pub llm_max_host_concurrency: Option<usize>,
21 pub llm_slot_wait_secs: Option<u64>,
22 pub llm_slot_no_wait: bool,
23 pub strict_env_clear: bool,
24 pub log_level: Option<String>,
25 pub log_format: Option<String>,
26 pub lang: Option<String>,
27 pub display_tz: Option<String>,
28 pub db_path: Option<String>,
29}
30
31static RUNTIME: OnceLock<RuntimeOverrides> = OnceLock::new();
32
33pub fn init(overrides: RuntimeOverrides) {
35 let _ = RUNTIME.set(overrides);
36}
37
38pub fn get() -> RuntimeOverrides {
40 RUNTIME.get().cloned().unwrap_or_default()
41}
42
43pub fn resolve_string(flag: Option<&str>, xdg_key: &str, default: &str) -> String {
45 if let Some(v) = flag {
46 if !v.is_empty() {
47 return v.to_string();
48 }
49 }
50 if let Ok(Some(v)) = config::get_setting(xdg_key) {
51 if !v.is_empty() {
52 return v;
53 }
54 }
55 default.to_string()
56}
57
58pub fn resolve_optional_string(flag: Option<&str>, xdg_key: &str) -> Option<String> {
60 if let Some(v) = flag {
61 if !v.is_empty() {
62 return Some(v.to_string());
63 }
64 }
65 config::get_setting(xdg_key).ok().flatten().filter(|s| !s.is_empty())
66}
67
68pub fn resolve_usize(flag: Option<usize>, xdg_key: &str, default: usize) -> usize {
70 if let Some(v) = flag {
71 return v;
72 }
73 if let Ok(Some(v)) = config::get_setting(xdg_key) {
74 if let Ok(n) = v.parse::<usize>() {
75 return n;
76 }
77 }
78 default
79}
80
81pub fn resolve_u64(flag: Option<u64>, xdg_key: &str, default: u64) -> u64 {
83 if let Some(v) = flag {
84 return v;
85 }
86 if let Ok(Some(v)) = config::get_setting(xdg_key) {
87 if let Ok(n) = v.parse::<u64>() {
88 return n;
89 }
90 }
91 default
92}
93
94pub fn resolve_f64(flag: Option<f64>, xdg_key: &str, default: f64) -> f64 {
96 if let Some(v) = flag {
97 return v;
98 }
99 if let Ok(Some(v)) = config::get_setting(xdg_key) {
100 if let Ok(n) = v.parse::<f64>() {
101 return n;
102 }
103 }
104 default
105}
106
107pub fn resolve_bool(flag_set: bool, xdg_key: &str, default: bool) -> bool {
109 if flag_set {
110 return true;
111 }
112 if let Ok(Some(v)) = config::get_setting(xdg_key) {
113 let t = v.trim().to_ascii_lowercase();
114 return matches!(t.as_str(), "1" | "true" | "yes" | "on");
115 }
116 default
117}
118
119pub fn embedding_dim_override() -> Option<u32> {
121 let rt = get();
122 if let Some(d) = rt.embedding_dim {
123 return Some(d);
124 }
125 if let Ok(Some(v)) = config::get_setting("embedding.dim") {
126 if let Ok(n) = v.parse::<u32>() {
127 if (8..=4096).contains(&n) {
128 return Some(n);
129 }
130 }
131 }
132 None
133}
134
135pub fn skip_embedding_on_failure() -> bool {
137 let rt = get();
138 resolve_bool(
139 rt.skip_embedding_on_failure,
140 "llm.skip_embedding_on_failure",
141 false,
142 )
143}
144
145pub fn llm_max_host_concurrency(default: usize) -> usize {
147 let rt = get();
148 resolve_usize(
149 rt.llm_max_host_concurrency,
150 "llm.max_host_concurrency",
151 default,
152 )
153}
154
155pub fn llm_slot_wait_secs(default: u64) -> u64 {
156 let rt = get();
157 if rt.llm_slot_no_wait {
158 return 0;
159 }
160 resolve_u64(rt.llm_slot_wait_secs, "llm.slot_wait_secs", default)
161}
162
163pub fn llm_slot_no_wait() -> bool {
164 let rt = get();
165 resolve_bool(rt.llm_slot_no_wait, "llm.slot_no_wait", false)
166}
167
168pub fn claude_binary() -> Option<String> {
169 let rt = get();
170 resolve_optional_string(rt.claude_binary.as_deref(), "llm.claude_binary")
171}
172
173pub fn codex_binary() -> Option<String> {
174 let rt = get();
175 resolve_optional_string(rt.codex_binary.as_deref(), "llm.codex_binary")
176}
177
178pub fn opencode_binary() -> Option<String> {
179 let rt = get();
180 resolve_optional_string(rt.opencode_binary.as_deref(), "llm.opencode_binary")
181}
182
183pub fn llm_model() -> Option<String> {
184 let rt = get();
185 resolve_optional_string(rt.llm_model.as_deref(), "llm.model")
186}
187
188pub fn llm_fallback(default: &str) -> String {
189 let rt = get();
190 resolve_string(rt.llm_fallback.as_deref(), "llm.fallback", default)
191}
192
193pub fn log_level(default: &str) -> String {
194 let rt = get();
195 resolve_string(rt.log_level.as_deref(), "log.level", default)
196}
197
198pub fn log_format(default: &str) -> String {
199 let rt = get();
200 resolve_string(rt.log_format.as_deref(), "log.format", default)
201}
202
203pub fn max_entities_per_memory(default: usize) -> usize {
204 resolve_usize(None, "limits.max_entities_per_memory", default)
205}
206
207pub fn max_relations_per_memory(default: usize) -> usize {
208 resolve_usize(None, "limits.max_relations_per_memory", default)
209}
210
211pub fn openrouter_chat_url(default: &str) -> String {
214 resolve_string_with_aliases(
215 None,
216 &["network.openrouter.chat_url", "network.chat_url"],
217 default,
218 )
219}
220
221pub fn openrouter_embeddings_url(default: &str) -> String {
224 resolve_string_with_aliases(
225 None,
226 &[
227 "network.openrouter.embeddings_url",
228 "network.embed_url",
229 ],
230 default,
231 )
232}
233
234pub fn llm_probe_timeout_ms(default: u64) -> u64 {
236 resolve_u64(None, "llm.probe_timeout_ms", default)
237}
238
239fn resolve_string_with_aliases(flag: Option<&str>, keys: &[&str], default: &str) -> String {
241 if let Some(v) = flag {
242 if !v.is_empty() {
243 return v.to_string();
244 }
245 }
246 for key in keys {
247 if let Ok(Some(v)) = config::get_setting(key) {
248 if !v.is_empty() {
249 return v;
250 }
251 }
252 }
253 default.to_string()
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn resolve_string_prefers_flag() {
262 assert_eq!(
263 resolve_string(Some("from-flag"), "nonexistent.key.xyz", "def"),
264 "from-flag"
265 );
266 }
267
268 #[test]
269 fn resolve_string_falls_to_default() {
270 assert_eq!(
271 resolve_string(None, "nonexistent.key.xyz.zzz", "def"),
272 "def"
273 );
274 }
275}