1use 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}