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)]
172pub struct SkillsConfig {
173 #[serde(default = "default_skill_paths")]
175 pub paths: Vec<String>,
176 #[serde(default = "default_max_active_skills")]
177 pub max_active_skills: usize,
178 #[serde(default = "default_disambiguation_threshold")]
179 pub disambiguation_threshold: f32,
180 #[serde(default = "default_min_injection_score")]
181 pub min_injection_score: f32,
182 #[serde(default = "default_cosine_weight")]
183 pub cosine_weight: f32,
184 #[serde(default = "default_hybrid_search")]
185 pub hybrid_search: bool,
186 #[serde(default)]
187 pub learning: LearningConfig,
188 #[serde(default)]
189 pub trust: TrustConfig,
190 #[serde(default)]
191 pub prompt_mode: SkillPromptMode,
192 #[serde(default)]
195 pub two_stage_matching: bool,
196 #[serde(default)]
199 pub confusability_threshold: f32,
200
201 #[serde(default)]
204 pub rl_routing_enabled: bool,
205 #[serde(default = "default_rl_learning_rate")]
207 pub rl_learning_rate: f32,
208 #[serde(default = "default_rl_weight")]
210 pub rl_weight: f32,
211 #[serde(default = "default_rl_persist_interval")]
213 pub rl_persist_interval: u32,
214 #[serde(default = "default_rl_warmup_updates")]
216 pub rl_warmup_updates: u32,
217 #[serde(default)]
221 pub rl_embed_dim: Option<usize>,
222
223 #[serde(default)]
226 pub generation_provider: ProviderName,
227 #[serde(default)]
229 pub generation_output_dir: Option<String>,
230 #[serde(default)]
232 pub mining: SkillMiningConfig,
233}
234
235fn default_max_repos_per_query() -> usize {
236 20
237}
238
239fn default_dedup_threshold() -> f32 {
240 0.85
241}
242
243fn default_rate_limit_rpm() -> u32 {
244 25
245}
246
247#[derive(Debug, Default, Deserialize, Serialize)]
249pub struct SkillMiningConfig {
250 #[serde(default)]
252 pub queries: Vec<String>,
253 #[serde(default = "default_max_repos_per_query")]
255 pub max_repos_per_query: usize,
256 #[serde(default = "default_dedup_threshold")]
258 pub dedup_threshold: f32,
259 #[serde(default)]
261 pub output_dir: Option<String>,
262 #[serde(default)]
264 pub generation_provider: ProviderName,
265 #[serde(default)]
267 pub embedding_provider: ProviderName,
268 #[serde(default = "default_rate_limit_rpm")]
270 pub rate_limit_rpm: u32,
271}
272
273#[derive(Debug, Deserialize, Serialize)]
289#[allow(clippy::struct_excessive_bools)]
290pub struct IndexConfig {
291 #[serde(default)]
293 pub enabled: bool,
294 #[serde(default = "default_index_search_enabled")]
296 pub search_enabled: bool,
297 #[serde(default = "default_index_watch")]
298 pub watch: bool,
299 #[serde(default = "default_index_max_chunks")]
300 pub max_chunks: usize,
301 #[serde(default = "default_index_score_threshold")]
302 pub score_threshold: f32,
303 #[serde(default = "default_index_budget_ratio")]
304 pub budget_ratio: f32,
305 #[serde(default = "default_index_repo_map_tokens")]
306 pub repo_map_tokens: usize,
307 #[serde(default = "default_repo_map_ttl_secs")]
308 pub repo_map_ttl_secs: u64,
309 #[serde(default)]
313 pub mcp_enabled: bool,
314 #[serde(default)]
317 pub workspace_root: Option<std::path::PathBuf>,
318 #[serde(default = "default_index_concurrency")]
320 pub concurrency: usize,
321 #[serde(default = "default_index_batch_size")]
323 pub batch_size: usize,
324 #[serde(default = "default_index_memory_batch_size")]
328 pub memory_batch_size: usize,
329 #[serde(default = "default_index_max_file_bytes")]
333 pub max_file_bytes: usize,
334 #[serde(default)]
339 pub embed_provider: Option<String>,
340 #[serde(default = "default_index_embed_concurrency")]
343 pub embed_concurrency: usize,
344}
345
346impl Default for IndexConfig {
347 fn default() -> Self {
348 Self {
349 enabled: false,
350 search_enabled: default_index_search_enabled(),
351 watch: default_index_watch(),
352 max_chunks: default_index_max_chunks(),
353 score_threshold: default_index_score_threshold(),
354 budget_ratio: default_index_budget_ratio(),
355 repo_map_tokens: default_index_repo_map_tokens(),
356 repo_map_ttl_secs: default_repo_map_ttl_secs(),
357 mcp_enabled: false,
358 workspace_root: None,
359 concurrency: default_index_concurrency(),
360 batch_size: default_index_batch_size(),
361 memory_batch_size: default_index_memory_batch_size(),
362 max_file_bytes: default_index_max_file_bytes(),
363 embed_provider: None,
364 embed_concurrency: default_index_embed_concurrency(),
365 }
366 }
367}
368
369#[derive(Debug, Deserialize, Serialize)]
380pub struct VaultConfig {
381 #[serde(default = "default_vault_backend")]
383 pub backend: String,
384}
385
386impl Default for VaultConfig {
387 fn default() -> Self {
388 Self {
389 backend: default_vault_backend(),
390 }
391 }
392}
393
394#[derive(Debug, Deserialize, Serialize)]
408pub struct CostConfig {
409 #[serde(default = "default_true")]
411 pub enabled: bool,
412 #[serde(default = "default_max_daily_cents")]
414 pub max_daily_cents: u32,
415}
416
417impl Default for CostConfig {
418 fn default() -> Self {
419 Self {
420 enabled: true,
421 max_daily_cents: default_max_daily_cents(),
422 }
423 }
424}
425
426#[derive(Debug, Clone, Deserialize, Serialize)]
443pub struct GatewayConfig {
444 #[serde(default)]
446 pub enabled: bool,
447 #[serde(default = "default_gateway_bind")]
449 pub bind: String,
450 #[serde(default = "default_gateway_port")]
452 pub port: u16,
453 #[serde(default)]
456 pub auth_token: Option<String>,
457 #[serde(default = "default_gateway_rate_limit")]
459 pub rate_limit: u32,
460 #[serde(default = "default_gateway_max_body")]
462 pub max_body_size: usize,
463}
464
465impl Default for GatewayConfig {
466 fn default() -> Self {
467 Self {
468 enabled: false,
469 bind: default_gateway_bind(),
470 port: default_gateway_port(),
471 auth_token: None,
472 rate_limit: default_gateway_rate_limit(),
473 max_body_size: default_gateway_max_body(),
474 }
475 }
476}
477
478#[derive(Debug, Clone, Deserialize, Serialize)]
492pub struct DaemonConfig {
493 #[serde(default)]
495 pub enabled: bool,
496 #[serde(default = "default_pid_file")]
498 pub pid_file: String,
499 #[serde(default = "default_health_interval")]
501 pub health_interval_secs: u64,
502 #[serde(default = "default_max_restart_backoff")]
504 pub max_restart_backoff_secs: u64,
505}
506
507impl Default for DaemonConfig {
508 fn default() -> Self {
509 Self {
510 enabled: false,
511 pid_file: default_pid_file(),
512 health_interval_secs: default_health_interval(),
513 max_restart_backoff_secs: default_max_restart_backoff(),
514 }
515 }
516}
517
518#[derive(Debug, Clone, Deserialize, Serialize)]
538pub struct SchedulerConfig {
539 #[serde(default)]
541 pub enabled: bool,
542 #[serde(default = "default_scheduler_tick_interval")]
544 pub tick_interval_secs: u64,
545 #[serde(default = "default_scheduler_max_tasks")]
547 pub max_tasks: usize,
548 #[serde(default)]
550 pub tasks: Vec<ScheduledTaskConfig>,
551}
552
553impl Default for SchedulerConfig {
554 fn default() -> Self {
555 Self {
556 enabled: true,
557 tick_interval_secs: default_scheduler_tick_interval(),
558 max_tasks: default_scheduler_max_tasks(),
559 tasks: Vec::new(),
560 }
561 }
562}
563
564#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
568#[serde(rename_all = "snake_case")]
569pub enum ScheduledTaskKind {
570 MemoryCleanup,
571 SkillRefresh,
572 HealthCheck,
573 UpdateCheck,
574 Experiment,
575 Custom(String),
576}
577
578#[derive(Debug, Clone, Deserialize, Serialize)]
582pub struct ScheduledTaskConfig {
583 pub name: String,
585 #[serde(default, skip_serializing_if = "Option::is_none")]
587 pub cron: Option<String>,
588 #[serde(default, skip_serializing_if = "Option::is_none")]
590 pub run_at: Option<String>,
591 pub kind: ScheduledTaskKind,
593 #[serde(default)]
595 pub config: serde_json::Value,
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601
602 #[test]
603 fn index_config_defaults() {
604 let cfg = IndexConfig::default();
605 assert!(!cfg.enabled);
606 assert!(cfg.search_enabled);
607 assert!(!cfg.watch);
608 assert_eq!(cfg.concurrency, 4);
609 assert_eq!(cfg.batch_size, 32);
610 assert!(cfg.workspace_root.is_none());
611 }
612
613 #[test]
614 fn index_config_serde_roundtrip_with_new_fields() {
615 let toml = r#"
616 enabled = true
617 concurrency = 8
618 batch_size = 16
619 workspace_root = "/tmp/myproject"
620 "#;
621 let cfg: IndexConfig = toml::from_str(toml).unwrap();
622 assert!(cfg.enabled);
623 assert_eq!(cfg.concurrency, 8);
624 assert_eq!(cfg.batch_size, 16);
625 assert_eq!(
626 cfg.workspace_root,
627 Some(std::path::PathBuf::from("/tmp/myproject"))
628 );
629 let serialized = toml::to_string(&cfg).unwrap();
631 let cfg2: IndexConfig = toml::from_str(&serialized).unwrap();
632 assert_eq!(cfg2.concurrency, 8);
633 assert_eq!(cfg2.batch_size, 16);
634 }
635
636 #[test]
637 fn index_config_backward_compat_old_toml_without_new_fields() {
638 let toml = "
641 enabled = true
642 max_chunks = 20
643 score_threshold = 0.3
644 ";
645 let cfg: IndexConfig = toml::from_str(toml).unwrap();
646 assert!(cfg.enabled);
647 assert_eq!(cfg.max_chunks, 20);
648 assert!(cfg.workspace_root.is_none());
649 assert_eq!(cfg.concurrency, 4);
650 assert_eq!(cfg.batch_size, 32);
651 }
652
653 #[test]
654 fn index_config_workspace_root_none_by_default() {
655 let cfg: IndexConfig = toml::from_str("enabled = false").unwrap();
656 assert!(cfg.workspace_root.is_none());
657 }
658}
659
660fn default_trace_service_name() -> String {
661 "zeph".into()
662}
663
664#[derive(Debug, Clone, Deserialize, Serialize)]
671#[serde(default)]
672pub struct TraceConfig {
673 #[serde(default = "default_otlp_endpoint")]
676 pub otlp_endpoint: String,
677 #[serde(default = "default_trace_service_name")]
679 pub service_name: String,
680 #[serde(default = "default_true")]
682 pub redact: bool,
683}
684
685impl Default for TraceConfig {
686 fn default() -> Self {
687 Self {
688 otlp_endpoint: default_otlp_endpoint(),
689 service_name: default_trace_service_name(),
690 redact: true,
691 }
692 }
693}
694
695#[derive(Debug, Clone, Deserialize, Serialize)]
708#[serde(default)]
709pub struct DebugConfig {
710 pub enabled: bool,
712 #[serde(default = "crate::defaults::default_debug_output_dir")]
714 pub output_dir: std::path::PathBuf,
715 pub format: crate::dump_format::DumpFormat,
717 pub traces: TraceConfig,
719}
720
721impl Default for DebugConfig {
722 fn default() -> Self {
723 Self {
724 enabled: false,
725 output_dir: super::defaults::default_debug_output_dir(),
726 format: crate::dump_format::DumpFormat::default(),
727 traces: TraceConfig::default(),
728 }
729 }
730}