Skip to main content

zeph_config/
env.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::num::NonZeroUsize;
5
6use crate::ProviderName;
7use crate::providers::{ProviderEntry, SttConfig, default_stt_language};
8use crate::root::Config;
9
10impl Config {
11    pub fn apply_env_overrides(&mut self) {
12        self.apply_env_overrides_core();
13        self.apply_env_overrides_security();
14    }
15
16    fn apply_env_overrides_core(&mut self) {
17        self.apply_env_overrides_core_1();
18        self.apply_env_overrides_core_1b();
19        self.apply_env_overrides_core_2();
20        self.apply_env_overrides_core_2b();
21        self.apply_env_overrides_core_2c();
22    }
23
24    fn apply_env_overrides_core_1(&mut self) {
25        let has_provider_env = std::env::var("ZEPH_LLM_PROVIDER").is_ok()
26            || std::env::var("ZEPH_LLM_BASE_URL").is_ok()
27            || std::env::var("ZEPH_LLM_MODEL").is_ok();
28        if has_provider_env && self.llm.providers.is_empty() {
29            self.llm.providers.push(ProviderEntry::default());
30        }
31
32        if let Ok(v) = std::env::var("ZEPH_LLM_PROVIDER")
33            && let Some(entry) = self.llm.providers.first_mut()
34        {
35            if let Ok(kind) = serde_json::from_value(serde_json::Value::String(v.clone())) {
36                entry.provider_type = kind;
37            } else {
38                tracing::warn!("ignoring invalid ZEPH_LLM_PROVIDER value: {v}");
39            }
40        }
41        if let Ok(v) = std::env::var("ZEPH_LLM_BASE_URL")
42            && let Some(entry) = self.llm.providers.first_mut()
43        {
44            entry.base_url = Some(v);
45        }
46        if let Ok(v) = std::env::var("ZEPH_LLM_MODEL")
47            && let Some(entry) = self.llm.providers.first_mut()
48        {
49            entry.model = Some(v);
50        }
51        if let Ok(v) = std::env::var("ZEPH_LLM_EMBEDDING_MODEL") {
52            self.llm.embedding_model = v;
53        }
54        if let Ok(v) = std::env::var("ZEPH_SQLITE_PATH") {
55            self.memory.sqlite_path = v;
56        }
57        if let Ok(v) = std::env::var("ZEPH_QDRANT_URL") {
58            self.memory.qdrant_url = v;
59        }
60        if let Ok(v) = std::env::var("ZEPH_MEMORY_SEMANTIC_ENABLED")
61            && let Ok(enabled) = v.parse::<bool>()
62        {
63            self.memory.semantic.enabled = enabled;
64        }
65        if let Ok(v) = std::env::var("ZEPH_MEMORY_RECALL_LIMIT")
66            && let Ok(limit) = v.parse::<usize>()
67        {
68            self.memory.semantic.recall_limit = limit;
69        }
70        if let Ok(v) = std::env::var("ZEPH_MEMORY_SUMMARIZATION_THRESHOLD")
71            && let Ok(threshold) = v.parse::<usize>()
72        {
73            self.memory.summarization_threshold = threshold;
74        }
75        if let Ok(v) = std::env::var("ZEPH_MEMORY_CONTEXT_BUDGET_TOKENS")
76            && let Ok(tokens) = v.parse::<usize>()
77        {
78            self.memory.context_budget_tokens = tokens;
79        }
80        if let Ok(v) = std::env::var("ZEPH_MEMORY_COMPACTION_THRESHOLD")
81            && let Ok(threshold) = v.parse::<f32>()
82        {
83            self.memory.hard_compaction_threshold = threshold;
84        }
85        if let Ok(v) = std::env::var("ZEPH_MEMORY_SOFT_COMPACTION_THRESHOLD")
86            && let Ok(threshold) = v.parse::<f32>()
87        {
88            self.memory.soft_compaction_threshold = threshold;
89        }
90        if let Ok(v) = std::env::var("ZEPH_MEMORY_COMPACTION_PRESERVE_TAIL")
91            && let Ok(tail) = v.parse::<usize>()
92        {
93            self.memory.compaction_preserve_tail = tail;
94        }
95        if let Ok(v) = std::env::var("ZEPH_MEMORY_AUTO_BUDGET")
96            && let Ok(enabled) = v.parse::<bool>()
97        {
98            self.memory.auto_budget = enabled;
99        }
100        if let Ok(v) = std::env::var("ZEPH_MEMORY_PRUNE_PROTECT_TOKENS")
101            && let Ok(tokens) = v.parse::<usize>()
102        {
103            self.memory.prune_protect_tokens = tokens;
104        }
105        if let Ok(v) = std::env::var("ZEPH_MEMORY_VECTOR_BACKEND") {
106            match v.to_lowercase().as_str() {
107                "sqlite" => {
108                    self.memory.vector_backend = crate::memory::VectorBackend::Sqlite;
109                }
110                "qdrant" => {
111                    self.memory.vector_backend = crate::memory::VectorBackend::Qdrant;
112                }
113                _ => {}
114            }
115        }
116    }
117
118    fn apply_env_overrides_core_1b(&mut self) {
119        if let Ok(v) = std::env::var("ZEPH_SAFE_MODE")
120            && let Ok(enabled) = v.parse::<bool>()
121        {
122            self.cli.safe_mode = enabled;
123        }
124        if let Ok(v) = std::env::var("ZEPH_SKILLS_MAX_ACTIVE")
125            && let Ok(n) = v.parse::<usize>()
126            && let Some(nz) = NonZeroUsize::new(n)
127        {
128            self.skills.max_active_skills = nz;
129        }
130        if let Ok(v) = std::env::var("ZEPH_SKILLS_LEARNING_ENABLED")
131            && let Ok(enabled) = v.parse::<bool>()
132        {
133            self.skills.learning.enabled = enabled;
134        }
135        if let Ok(v) = std::env::var("ZEPH_SKILLS_LEARNING_AUTO_ACTIVATE")
136            && let Ok(auto_activate) = v.parse::<bool>()
137        {
138            self.skills.learning.auto_activate = auto_activate;
139        }
140        if let Ok(v) = std::env::var("ZEPH_SKILLS_PROMPT_MODE") {
141            match v.to_lowercase().as_str() {
142                "full" => self.skills.prompt_mode = crate::features::SkillPromptMode::Full,
143                "compact" => self.skills.prompt_mode = crate::features::SkillPromptMode::Compact,
144                "auto" => self.skills.prompt_mode = crate::features::SkillPromptMode::Auto,
145                _ => {}
146            }
147        }
148        if let Ok(v) = std::env::var("ZEPH_MEMORY_SEMANTIC_TEMPORAL_DECAY_ENABLED")
149            && let Ok(enabled) = v.parse::<bool>()
150        {
151            self.memory.semantic.temporal_decay_enabled = enabled;
152        }
153        if let Ok(v) = std::env::var("ZEPH_MEMORY_SEMANTIC_TEMPORAL_DECAY_HALF_LIFE_DAYS")
154            && let Ok(days) = v.parse::<u32>()
155        {
156            self.memory.semantic.temporal_decay_half_life_days = days;
157        }
158        if let Ok(v) = std::env::var("ZEPH_MEMORY_SEMANTIC_MMR_ENABLED")
159            && let Ok(enabled) = v.parse::<bool>()
160        {
161            self.memory.semantic.mmr_enabled = enabled;
162        }
163        if let Ok(v) = std::env::var("ZEPH_MEMORY_SEMANTIC_MMR_LAMBDA")
164            && let Ok(lambda) = v.parse::<f32>()
165        {
166            self.memory.semantic.mmr_lambda = lambda;
167        }
168        if let Ok(v) = std::env::var("ZEPH_MEMORY_TOKEN_SAFETY_MARGIN")
169            && let Ok(margin) = v.parse::<f32>()
170        {
171            self.memory.token_safety_margin = margin.clamp(0.1, 10.0);
172        }
173        if let Ok(v) = std::env::var("ZEPH_TOOLS_SUMMARIZE_OUTPUT")
174            && let Ok(enabled) = v.parse::<bool>()
175        {
176            self.tools.summarize_output = enabled;
177        }
178        if let Ok(v) = std::env::var("ZEPH_TOOLS_SHELL_ALLOWED_COMMANDS") {
179            self.tools.shell.allowed_commands = v
180                .split(',')
181                .map(|s| s.trim().to_owned())
182                .filter(|s| !s.is_empty())
183                .collect();
184        }
185        if let Ok(v) = std::env::var("ZEPH_TOOLS_TIMEOUT")
186            && let Ok(secs) = v.parse::<u64>()
187        {
188            self.tools.shell.timeout = secs;
189        }
190        if let Ok(v) = std::env::var("ZEPH_TOOLS_SCRAPE_TIMEOUT")
191            && let Ok(secs) = v.parse::<u64>()
192        {
193            self.tools.scrape.timeout = secs;
194        }
195        if let Ok(v) = std::env::var("ZEPH_TOOLS_SCRAPE_MAX_BODY")
196            && let Ok(bytes) = v.parse::<usize>()
197        {
198            self.tools.scrape.max_body_bytes = bytes;
199        }
200    }
201
202    fn apply_env_overrides_core_2(&mut self) {
203        if let Ok(v) = std::env::var("ZEPH_INDEX_ENABLED")
204            && let Ok(enabled) = v.parse::<bool>()
205        {
206            self.index.enabled = enabled;
207        }
208        if let Ok(v) = std::env::var("ZEPH_INDEX_MAX_CHUNKS")
209            && let Ok(n) = v.parse::<usize>()
210        {
211            self.index.max_chunks = n;
212        }
213        if let Ok(v) = std::env::var("ZEPH_INDEX_SCORE_THRESHOLD")
214            && let Ok(t) = v.parse::<f32>()
215        {
216            self.index.score_threshold = t.clamp(0.0, 1.0);
217        }
218        if let Ok(v) = std::env::var("ZEPH_INDEX_BUDGET_RATIO")
219            && let Ok(r) = v.parse::<f32>()
220        {
221            self.index.budget_ratio = r.clamp(0.0, 1.0);
222        }
223        if let Ok(v) = std::env::var("ZEPH_INDEX_REPO_MAP_TOKENS")
224            && let Ok(n) = v.parse::<usize>()
225        {
226            self.index.repo_map_tokens = n;
227        }
228        if let Ok(v) = std::env::var("ZEPH_STT_PROVIDER") {
229            let stt = self.llm.stt.get_or_insert_with(|| SttConfig {
230                provider: ProviderName::default(),
231                language: default_stt_language(),
232            });
233            stt.provider = ProviderName::new(v);
234        }
235        if let Ok(v) = std::env::var("ZEPH_STT_LANGUAGE") {
236            let stt = self.llm.stt.get_or_insert_with(|| SttConfig {
237                provider: ProviderName::default(),
238                language: default_stt_language(),
239            });
240            stt.language = v;
241        }
242        if std::env::var("ZEPH_STT_MODEL").is_ok() {
243            tracing::warn!(
244                "env var ZEPH_STT_MODEL is no longer supported; set `stt_model` on the \
245                 [[llm.providers]] entry instead"
246            );
247        }
248        if std::env::var("ZEPH_STT_BASE_URL").is_ok() {
249            tracing::warn!(
250                "env var ZEPH_STT_BASE_URL is no longer supported; set `base_url` on the \
251                 [[llm.providers]] entry instead"
252            );
253        }
254    }
255
256    fn apply_env_overrides_core_2b(&mut self) {
257        if let Ok(v) = std::env::var("ZEPH_AUTO_UPDATE_CHECK")
258            && let Ok(enabled) = v.parse::<bool>()
259        {
260            self.agent.auto_update_check = enabled;
261        }
262        if let Ok(v) = std::env::var("ZEPH_A2A_ENABLED")
263            && let Ok(enabled) = v.parse::<bool>()
264        {
265            self.a2a.enabled = enabled;
266        }
267        if let Ok(v) = std::env::var("ZEPH_A2A_HOST") {
268            self.a2a.host = v;
269        }
270        if let Ok(v) = std::env::var("ZEPH_A2A_PORT")
271            && let Ok(port) = v.parse::<u16>()
272        {
273            self.a2a.port = port;
274        }
275        if let Ok(v) = std::env::var("ZEPH_A2A_PUBLIC_URL") {
276            self.a2a.public_url = v;
277        }
278        if let Ok(v) = std::env::var("ZEPH_A2A_RATE_LIMIT")
279            && let Ok(rate) = v.parse::<u32>()
280        {
281            self.a2a.rate_limit = rate;
282        }
283        if let Ok(v) = std::env::var("ZEPH_MEMORY_AUTOSAVE_ASSISTANT")
284            && let Ok(enabled) = v.parse::<bool>()
285        {
286            self.memory.autosave_assistant = enabled;
287        }
288        if let Ok(v) = std::env::var("ZEPH_MEMORY_AUTOSAVE_MIN_LENGTH")
289            && let Ok(len) = v.parse::<usize>()
290        {
291            self.memory.autosave_min_length = len;
292        }
293        if let Ok(v) = std::env::var("ZEPH_LLM_RESPONSE_CACHE_ENABLED")
294            && let Ok(enabled) = v.parse::<bool>()
295        {
296            self.llm.response_cache_enabled = enabled;
297        }
298        if let Ok(v) = std::env::var("ZEPH_LLM_RESPONSE_CACHE_TTL_SECS")
299            && let Ok(secs) = v.parse::<u64>()
300        {
301            self.llm.response_cache_ttl_secs = secs;
302        }
303        if let Ok(v) = std::env::var("ZEPH_LLM_SEMANTIC_CACHE_ENABLED")
304            && let Ok(enabled) = v.parse::<bool>()
305        {
306            self.llm.semantic_cache_enabled = enabled;
307        }
308        if let Ok(v) = std::env::var("ZEPH_LLM_SEMANTIC_CACHE_THRESHOLD")
309            && let Ok(threshold) = v.parse::<f32>()
310        {
311            self.llm.semantic_cache_threshold = threshold;
312        }
313        if let Ok(v) = std::env::var("ZEPH_LLM_SEMANTIC_CACHE_MAX_CANDIDATES")
314            && let Ok(max) = v.parse::<u32>()
315        {
316            self.llm.semantic_cache_max_candidates = max;
317        }
318        if let Ok(v) = std::env::var("ZEPH_ACP_ENABLED")
319            && let Ok(enabled) = v.parse::<bool>()
320        {
321            self.acp.enabled = enabled;
322        }
323        if let Ok(v) = std::env::var("ZEPH_ACP_AGENT_NAME") {
324            self.acp.agent_name = v;
325        }
326        if let Ok(v) = std::env::var("ZEPH_ACP_AGENT_VERSION") {
327            self.acp.agent_version = v;
328        }
329        if let Ok(v) = std::env::var("ZEPH_ACP_MAX_SESSIONS")
330            && let Ok(n) = v.parse::<usize>()
331        {
332            self.acp.max_sessions = n;
333        }
334        if let Ok(v) = std::env::var("ZEPH_ACP_SESSION_IDLE_TIMEOUT_SECS")
335            && let Ok(secs) = v.parse::<u64>()
336        {
337            self.acp.session_idle_timeout_secs = secs;
338        }
339        if let Ok(v) = std::env::var("ZEPH_ACP_PERMISSION_FILE") {
340            self.acp.permission_file = Some(std::path::PathBuf::from(v));
341        }
342        if let Ok(v) = std::env::var("ZEPH_ACP_AUTH_TOKEN") {
343            self.acp.auth_token = Some(v);
344        }
345        if let Ok(v) = std::env::var("ZEPH_ACP_DISCOVERY_ENABLED")
346            && let Ok(enabled) = v.parse::<bool>()
347        {
348            self.acp.discovery_enabled = enabled;
349        }
350        if let Ok(v) = std::env::var("ZEPH_LOG_FILE") {
351            self.logging.file = v;
352        }
353        if let Ok(v) = std::env::var("ZEPH_LOG_LEVEL") {
354            self.logging.level = v;
355        }
356    }
357
358    fn apply_env_overrides_core_2c(&mut self) {
359        if let Ok(v) = std::env::var("ZEPH_AGENTS_FORWARD_TRANSCRIPT")
360            && let Ok(enabled) = v.parse::<bool>()
361        {
362            self.agents.forward_transcript = enabled;
363        }
364        if let Ok(v) = std::env::var("ZEPH_AGENTS_DELEGATION_MODE") {
365            match v.as_str() {
366                "disabled" => self.agents.delegation_mode = crate::DelegationMode::Disabled,
367                "explicit_request_only" => {
368                    self.agents.delegation_mode = crate::DelegationMode::ExplicitRequestOnly;
369                }
370                "proactive" => self.agents.delegation_mode = crate::DelegationMode::Proactive,
371                other => tracing::warn!(
372                    value = other,
373                    "ZEPH_AGENTS_DELEGATION_MODE: invalid value, ignoring \
374                     (expected disabled|explicit_request_only|proactive)"
375                ),
376            }
377        }
378    }
379
380    fn apply_env_overrides_security(&mut self) {
381        if let Ok(v) = std::env::var("ZEPH_TOOLS_SHELL_ALLOWED_PATHS") {
382            self.tools.shell.allowed_paths = v
383                .split(',')
384                .map(|s| s.trim().to_owned())
385                .filter(|s| !s.is_empty())
386                .collect();
387        }
388        if let Ok(v) = std::env::var("ZEPH_TOOLS_SHELL_ALLOW_NETWORK")
389            && let Ok(allow) = v.parse::<bool>()
390        {
391            self.tools.shell.allow_network = allow;
392        }
393        if let Ok(v) = std::env::var("ZEPH_TOOLS_AUDIT_ENABLED")
394            && let Ok(enabled) = v.parse::<bool>()
395        {
396            self.tools.audit.enabled = enabled;
397        }
398        if let Ok(v) = std::env::var("ZEPH_TOOLS_AUDIT_DESTINATION") {
399            self.tools.audit.destination = match v.as_str() {
400                "stdout" => crate::AuditDestination::Stdout,
401                "stderr" => crate::AuditDestination::Stderr,
402                path => crate::AuditDestination::File(std::path::PathBuf::from(path)),
403            };
404        }
405        if let Ok(v) = std::env::var("ZEPH_SECURITY_REDACT_SECRETS")
406            && let Ok(redact) = v.parse::<bool>()
407        {
408            self.security.redact_secrets = redact;
409        }
410        if let Ok(v) = std::env::var("ZEPH_TIMEOUT_LLM")
411            && let Ok(secs) = v.parse::<u64>()
412        {
413            self.timeouts.llm_seconds = secs;
414        }
415        if let Ok(v) = std::env::var("ZEPH_TIMEOUT_LLM_REQUEST")
416            && let Ok(secs) = v.parse::<u64>()
417        {
418            self.timeouts.llm_request_timeout_secs = secs;
419        }
420        if let Ok(v) = std::env::var("ZEPH_TIMEOUT_EMBEDDING")
421            && let Ok(secs) = v.parse::<u64>()
422        {
423            self.timeouts.embedding_seconds = secs;
424        }
425        if let Ok(v) = std::env::var("ZEPH_TIMEOUT_A2A")
426            && let Ok(secs) = v.parse::<u64>()
427        {
428            self.timeouts.a2a_seconds = secs;
429        }
430        if let Ok(v) = std::env::var("ZEPH_A2A_MAX_BODY_SIZE")
431            && let Ok(size) = v.parse::<usize>()
432        {
433            self.a2a.max_body_size = size;
434        }
435        if let Ok(v) = std::env::var("ZEPH_A2A_CLIENT_REQUIRE_TLS")
436            && let Ok(require) = v.parse::<bool>()
437        {
438            self.a2a_client.require_tls = require;
439        }
440        if let Ok(v) = std::env::var("ZEPH_A2A_CLIENT_SSRF_PROTECTION")
441            && let Ok(ssrf) = v.parse::<bool>()
442        {
443            self.a2a_client.ssrf_protection = ssrf;
444        }
445        if let Ok(v) = std::env::var("ZEPH_A2A_CARD_TRUST_POLICY") {
446            match v.to_ascii_lowercase().as_str() {
447                "ignore" => {
448                    self.a2a_client.card_trust_policy = crate::channels::CardTrustPolicy::Ignore;
449                }
450                "prefer" => {
451                    self.a2a_client.card_trust_policy = crate::channels::CardTrustPolicy::Prefer;
452                }
453                "require" => {
454                    self.a2a_client.card_trust_policy = crate::channels::CardTrustPolicy::Require;
455                }
456                _ => {}
457            }
458        }
459    }
460}