1use serde::{Deserialize, Serialize};
5
6use crate::defaults::{default_skill_paths, default_true};
7use crate::learning::LearningConfig;
8use crate::providers::ProviderName;
9use crate::security::TrustConfig;
10
11fn default_disambiguation_threshold() -> f32 {
12 0.20
13}
14
15fn default_rl_learning_rate() -> f32 {
16 0.01
17}
18
19fn default_rl_weight() -> f32 {
20 0.3
21}
22
23fn default_rl_persist_interval() -> u32 {
24 10
25}
26
27fn default_rl_warmup_updates() -> u32 {
28 50
29}
30
31fn default_min_injection_score() -> f32 {
32 0.20
33}
34
35fn default_cosine_weight() -> f32 {
36 0.7
37}
38
39fn default_hybrid_search() -> bool {
40 true
41}
42
43fn default_max_active_skills() -> usize {
44 5
45}
46
47fn default_index_watch() -> bool {
48 false
53}
54
55fn default_index_search_enabled() -> bool {
56 true
57}
58
59fn default_index_max_chunks() -> usize {
60 12
61}
62
63fn default_index_concurrency() -> usize {
64 4
65}
66
67fn default_index_batch_size() -> usize {
68 32
69}
70
71fn default_index_memory_batch_size() -> usize {
72 32
73}
74
75fn default_index_max_file_bytes() -> usize {
76 512 * 1024
77}
78
79fn default_index_embed_concurrency() -> usize {
80 2
81}
82
83fn default_index_score_threshold() -> f32 {
84 0.25
85}
86
87fn default_index_budget_ratio() -> f32 {
88 0.40
89}
90
91fn default_index_repo_map_tokens() -> usize {
92 500
93}
94
95fn default_repo_map_ttl_secs() -> u64 {
96 300
97}
98
99fn default_vault_backend() -> String {
100 "env".into()
101}
102
103fn default_max_daily_cents() -> u32 {
104 0
105}
106
107fn default_otlp_endpoint() -> String {
108 "http://localhost:4317".into()
109}
110
111fn default_pid_file() -> String {
112 "~/.zeph/zeph.pid".into()
113}
114
115fn default_health_interval() -> u64 {
116 30
117}
118
119fn default_max_restart_backoff() -> u64 {
120 60
121}
122
123fn default_scheduler_tick_interval() -> u64 {
124 60
125}
126
127fn default_scheduler_max_tasks() -> usize {
128 100
129}
130
131fn default_gateway_bind() -> String {
132 "127.0.0.1".into()
133}
134
135fn default_gateway_port() -> u16 {
136 8090
137}
138
139fn default_gateway_rate_limit() -> u32 {
140 120
141}
142
143fn default_gateway_max_body() -> usize {
144 1_048_576
145}
146
147#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
149#[serde(rename_all = "lowercase")]
150pub enum SkillPromptMode {
151 Full,
152 Compact,
153 #[default]
154 Auto,
155}
156
157#[derive(Debug, Deserialize, Serialize)]
158pub struct SkillsConfig {
159 #[serde(default = "default_skill_paths")]
160 pub paths: Vec<String>,
161 #[serde(default = "default_max_active_skills")]
162 pub max_active_skills: usize,
163 #[serde(default = "default_disambiguation_threshold")]
164 pub disambiguation_threshold: f32,
165 #[serde(default = "default_min_injection_score")]
166 pub min_injection_score: f32,
167 #[serde(default = "default_cosine_weight")]
168 pub cosine_weight: f32,
169 #[serde(default = "default_hybrid_search")]
170 pub hybrid_search: bool,
171 #[serde(default)]
172 pub learning: LearningConfig,
173 #[serde(default)]
174 pub trust: TrustConfig,
175 #[serde(default)]
176 pub prompt_mode: SkillPromptMode,
177 #[serde(default)]
180 pub two_stage_matching: bool,
181 #[serde(default)]
184 pub confusability_threshold: f32,
185
186 #[serde(default)]
189 pub rl_routing_enabled: bool,
190 #[serde(default = "default_rl_learning_rate")]
192 pub rl_learning_rate: f32,
193 #[serde(default = "default_rl_weight")]
195 pub rl_weight: f32,
196 #[serde(default = "default_rl_persist_interval")]
198 pub rl_persist_interval: u32,
199 #[serde(default = "default_rl_warmup_updates")]
201 pub rl_warmup_updates: u32,
202 #[serde(default)]
206 pub rl_embed_dim: Option<usize>,
207
208 #[serde(default)]
211 pub generation_provider: ProviderName,
212 #[serde(default)]
214 pub generation_output_dir: Option<String>,
215 #[serde(default)]
217 pub mining: SkillMiningConfig,
218}
219
220fn default_max_repos_per_query() -> usize {
221 20
222}
223
224fn default_dedup_threshold() -> f32 {
225 0.85
226}
227
228fn default_rate_limit_rpm() -> u32 {
229 25
230}
231
232#[derive(Debug, Default, Deserialize, Serialize)]
234pub struct SkillMiningConfig {
235 #[serde(default)]
237 pub queries: Vec<String>,
238 #[serde(default = "default_max_repos_per_query")]
240 pub max_repos_per_query: usize,
241 #[serde(default = "default_dedup_threshold")]
243 pub dedup_threshold: f32,
244 #[serde(default)]
246 pub output_dir: Option<String>,
247 #[serde(default)]
249 pub generation_provider: ProviderName,
250 #[serde(default)]
252 pub embedding_provider: ProviderName,
253 #[serde(default = "default_rate_limit_rpm")]
255 pub rate_limit_rpm: u32,
256}
257
258#[derive(Debug, Deserialize, Serialize)]
259#[allow(clippy::struct_excessive_bools)]
260pub struct IndexConfig {
261 #[serde(default)]
262 pub enabled: bool,
263 #[serde(default = "default_index_search_enabled")]
264 pub search_enabled: bool,
265 #[serde(default = "default_index_watch")]
266 pub watch: bool,
267 #[serde(default = "default_index_max_chunks")]
268 pub max_chunks: usize,
269 #[serde(default = "default_index_score_threshold")]
270 pub score_threshold: f32,
271 #[serde(default = "default_index_budget_ratio")]
272 pub budget_ratio: f32,
273 #[serde(default = "default_index_repo_map_tokens")]
274 pub repo_map_tokens: usize,
275 #[serde(default = "default_repo_map_ttl_secs")]
276 pub repo_map_ttl_secs: u64,
277 #[serde(default)]
281 pub mcp_enabled: bool,
282 #[serde(default)]
285 pub workspace_root: Option<std::path::PathBuf>,
286 #[serde(default = "default_index_concurrency")]
288 pub concurrency: usize,
289 #[serde(default = "default_index_batch_size")]
291 pub batch_size: usize,
292 #[serde(default = "default_index_memory_batch_size")]
296 pub memory_batch_size: usize,
297 #[serde(default = "default_index_max_file_bytes")]
301 pub max_file_bytes: usize,
302 #[serde(default)]
307 pub embed_provider: Option<String>,
308 #[serde(default = "default_index_embed_concurrency")]
311 pub embed_concurrency: usize,
312}
313
314impl Default for IndexConfig {
315 fn default() -> Self {
316 Self {
317 enabled: false,
318 search_enabled: default_index_search_enabled(),
319 watch: default_index_watch(),
320 max_chunks: default_index_max_chunks(),
321 score_threshold: default_index_score_threshold(),
322 budget_ratio: default_index_budget_ratio(),
323 repo_map_tokens: default_index_repo_map_tokens(),
324 repo_map_ttl_secs: default_repo_map_ttl_secs(),
325 mcp_enabled: false,
326 workspace_root: None,
327 concurrency: default_index_concurrency(),
328 batch_size: default_index_batch_size(),
329 memory_batch_size: default_index_memory_batch_size(),
330 max_file_bytes: default_index_max_file_bytes(),
331 embed_provider: None,
332 embed_concurrency: default_index_embed_concurrency(),
333 }
334 }
335}
336
337#[derive(Debug, Deserialize, Serialize)]
338pub struct VaultConfig {
339 #[serde(default = "default_vault_backend")]
340 pub backend: String,
341}
342
343impl Default for VaultConfig {
344 fn default() -> Self {
345 Self {
346 backend: default_vault_backend(),
347 }
348 }
349}
350
351#[derive(Debug, Deserialize, Serialize)]
352pub struct CostConfig {
353 #[serde(default = "default_true")]
354 pub enabled: bool,
355 #[serde(default = "default_max_daily_cents")]
356 pub max_daily_cents: u32,
357}
358
359impl Default for CostConfig {
360 fn default() -> Self {
361 Self {
362 enabled: true,
363 max_daily_cents: default_max_daily_cents(),
364 }
365 }
366}
367
368#[derive(Debug, Deserialize, Serialize)]
369pub struct ObservabilityConfig {
370 #[serde(default)]
371 pub exporter: String,
372 #[serde(default = "default_otlp_endpoint")]
373 pub endpoint: String,
374}
375
376impl Default for ObservabilityConfig {
377 fn default() -> Self {
378 Self {
379 exporter: String::new(),
380 endpoint: default_otlp_endpoint(),
381 }
382 }
383}
384
385#[derive(Debug, Clone, Deserialize, Serialize)]
386pub struct GatewayConfig {
387 #[serde(default)]
388 pub enabled: bool,
389 #[serde(default = "default_gateway_bind")]
390 pub bind: String,
391 #[serde(default = "default_gateway_port")]
392 pub port: u16,
393 #[serde(default)]
394 pub auth_token: Option<String>,
395 #[serde(default = "default_gateway_rate_limit")]
396 pub rate_limit: u32,
397 #[serde(default = "default_gateway_max_body")]
398 pub max_body_size: usize,
399}
400
401impl Default for GatewayConfig {
402 fn default() -> Self {
403 Self {
404 enabled: false,
405 bind: default_gateway_bind(),
406 port: default_gateway_port(),
407 auth_token: None,
408 rate_limit: default_gateway_rate_limit(),
409 max_body_size: default_gateway_max_body(),
410 }
411 }
412}
413
414#[derive(Debug, Clone, Deserialize, Serialize)]
415pub struct DaemonConfig {
416 #[serde(default)]
417 pub enabled: bool,
418 #[serde(default = "default_pid_file")]
419 pub pid_file: String,
420 #[serde(default = "default_health_interval")]
421 pub health_interval_secs: u64,
422 #[serde(default = "default_max_restart_backoff")]
423 pub max_restart_backoff_secs: u64,
424}
425
426impl Default for DaemonConfig {
427 fn default() -> Self {
428 Self {
429 enabled: false,
430 pid_file: default_pid_file(),
431 health_interval_secs: default_health_interval(),
432 max_restart_backoff_secs: default_max_restart_backoff(),
433 }
434 }
435}
436
437#[derive(Debug, Clone, Deserialize, Serialize)]
438pub struct SchedulerConfig {
439 #[serde(default)]
440 pub enabled: bool,
441 #[serde(default = "default_scheduler_tick_interval")]
442 pub tick_interval_secs: u64,
443 #[serde(default = "default_scheduler_max_tasks")]
444 pub max_tasks: usize,
445 #[serde(default)]
446 pub tasks: Vec<ScheduledTaskConfig>,
447}
448
449impl Default for SchedulerConfig {
450 fn default() -> Self {
451 Self {
452 enabled: true,
453 tick_interval_secs: default_scheduler_tick_interval(),
454 max_tasks: default_scheduler_max_tasks(),
455 tasks: Vec::new(),
456 }
457 }
458}
459
460#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
464#[serde(rename_all = "snake_case")]
465pub enum ScheduledTaskKind {
466 MemoryCleanup,
467 SkillRefresh,
468 HealthCheck,
469 UpdateCheck,
470 Experiment,
471 Custom(String),
472}
473
474#[derive(Debug, Clone, Deserialize, Serialize)]
475pub struct ScheduledTaskConfig {
476 pub name: String,
477 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub cron: Option<String>,
479 #[serde(default, skip_serializing_if = "Option::is_none")]
480 pub run_at: Option<String>,
481 pub kind: ScheduledTaskKind,
482 #[serde(default)]
483 pub config: serde_json::Value,
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[test]
491 fn index_config_defaults() {
492 let cfg = IndexConfig::default();
493 assert!(!cfg.enabled);
494 assert!(cfg.search_enabled);
495 assert!(!cfg.watch);
496 assert_eq!(cfg.concurrency, 4);
497 assert_eq!(cfg.batch_size, 32);
498 assert!(cfg.workspace_root.is_none());
499 }
500
501 #[test]
502 fn index_config_serde_roundtrip_with_new_fields() {
503 let toml = r#"
504 enabled = true
505 concurrency = 8
506 batch_size = 16
507 workspace_root = "/tmp/myproject"
508 "#;
509 let cfg: IndexConfig = toml::from_str(toml).unwrap();
510 assert!(cfg.enabled);
511 assert_eq!(cfg.concurrency, 8);
512 assert_eq!(cfg.batch_size, 16);
513 assert_eq!(
514 cfg.workspace_root,
515 Some(std::path::PathBuf::from("/tmp/myproject"))
516 );
517 let serialized = toml::to_string(&cfg).unwrap();
519 let cfg2: IndexConfig = toml::from_str(&serialized).unwrap();
520 assert_eq!(cfg2.concurrency, 8);
521 assert_eq!(cfg2.batch_size, 16);
522 }
523
524 #[test]
525 fn index_config_backward_compat_old_toml_without_new_fields() {
526 let toml = "
529 enabled = true
530 max_chunks = 20
531 score_threshold = 0.3
532 ";
533 let cfg: IndexConfig = toml::from_str(toml).unwrap();
534 assert!(cfg.enabled);
535 assert_eq!(cfg.max_chunks, 20);
536 assert!(cfg.workspace_root.is_none());
537 assert_eq!(cfg.concurrency, 4);
538 assert_eq!(cfg.batch_size, 32);
539 }
540
541 #[test]
542 fn index_config_workspace_root_none_by_default() {
543 let cfg: IndexConfig = toml::from_str("enabled = false").unwrap();
544 assert!(cfg.workspace_root.is_none());
545 }
546}
547
548fn default_trace_service_name() -> String {
549 "zeph".into()
550}
551
552#[derive(Debug, Clone, Deserialize, Serialize)]
559#[serde(default)]
560pub struct TraceConfig {
561 #[serde(default = "default_otlp_endpoint")]
564 pub otlp_endpoint: String,
565 #[serde(default = "default_trace_service_name")]
567 pub service_name: String,
568 #[serde(default = "default_true")]
570 pub redact: bool,
571}
572
573impl Default for TraceConfig {
574 fn default() -> Self {
575 Self {
576 otlp_endpoint: default_otlp_endpoint(),
577 service_name: default_trace_service_name(),
578 redact: true,
579 }
580 }
581}
582
583#[derive(Debug, Clone, Deserialize, Serialize)]
584#[serde(default)]
585pub struct DebugConfig {
586 pub enabled: bool,
588 #[serde(default = "crate::defaults::default_debug_output_dir")]
590 pub output_dir: std::path::PathBuf,
591 pub format: crate::dump_format::DumpFormat,
593 pub traces: TraceConfig,
595}
596
597impl Default for DebugConfig {
598 fn default() -> Self {
599 Self {
600 enabled: false,
601 output_dir: super::defaults::default_debug_output_dir(),
602 format: crate::dump_format::DumpFormat::default(),
603 traces: TraceConfig::default(),
604 }
605 }
606}