use serde::{Deserialize, Serialize};
use crate::defaults::{default_skill_paths, default_true};
use crate::learning::LearningConfig;
use crate::providers::ProviderName;
use crate::security::TrustConfig;
fn default_disambiguation_threshold() -> f32 {
0.20
}
fn default_rl_learning_rate() -> f32 {
0.01
}
fn default_rl_weight() -> f32 {
0.3
}
fn default_rl_persist_interval() -> u32 {
10
}
fn default_rl_warmup_updates() -> u32 {
50
}
fn default_min_injection_score() -> f32 {
0.20
}
fn default_cosine_weight() -> f32 {
0.7
}
fn default_hybrid_search() -> bool {
true
}
fn default_max_active_skills() -> usize {
5
}
fn default_index_watch() -> bool {
false
}
fn default_index_search_enabled() -> bool {
true
}
fn default_index_max_chunks() -> usize {
12
}
fn default_index_concurrency() -> usize {
4
}
fn default_index_batch_size() -> usize {
32
}
fn default_index_memory_batch_size() -> usize {
32
}
fn default_index_max_file_bytes() -> usize {
512 * 1024
}
fn default_index_embed_concurrency() -> usize {
2
}
fn default_index_score_threshold() -> f32 {
0.25
}
fn default_index_budget_ratio() -> f32 {
0.40
}
fn default_index_repo_map_tokens() -> usize {
500
}
fn default_repo_map_ttl_secs() -> u64 {
300
}
fn default_vault_backend() -> String {
"env".into()
}
fn default_max_daily_cents() -> u32 {
0
}
fn default_otlp_endpoint() -> String {
"http://localhost:4317".into()
}
fn default_pid_file() -> String {
"~/.zeph/zeph.pid".into()
}
fn default_health_interval() -> u64 {
30
}
fn default_max_restart_backoff() -> u64 {
60
}
fn default_scheduler_tick_interval() -> u64 {
60
}
fn default_scheduler_max_tasks() -> usize {
100
}
fn default_gateway_bind() -> String {
"127.0.0.1".into()
}
fn default_gateway_port() -> u16 {
8090
}
fn default_gateway_rate_limit() -> u32 {
120
}
fn default_gateway_max_body() -> usize {
1_048_576
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum SkillPromptMode {
Full,
Compact,
#[default]
Auto,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct SkillsConfig {
#[serde(default = "default_skill_paths")]
pub paths: Vec<String>,
#[serde(default = "default_max_active_skills")]
pub max_active_skills: usize,
#[serde(default = "default_disambiguation_threshold")]
pub disambiguation_threshold: f32,
#[serde(default = "default_min_injection_score")]
pub min_injection_score: f32,
#[serde(default = "default_cosine_weight")]
pub cosine_weight: f32,
#[serde(default = "default_hybrid_search")]
pub hybrid_search: bool,
#[serde(default)]
pub learning: LearningConfig,
#[serde(default)]
pub trust: TrustConfig,
#[serde(default)]
pub prompt_mode: SkillPromptMode,
#[serde(default)]
pub two_stage_matching: bool,
#[serde(default)]
pub confusability_threshold: f32,
#[serde(default)]
pub rl_routing_enabled: bool,
#[serde(default = "default_rl_learning_rate")]
pub rl_learning_rate: f32,
#[serde(default = "default_rl_weight")]
pub rl_weight: f32,
#[serde(default = "default_rl_persist_interval")]
pub rl_persist_interval: u32,
#[serde(default = "default_rl_warmup_updates")]
pub rl_warmup_updates: u32,
#[serde(default)]
pub rl_embed_dim: Option<usize>,
#[serde(default)]
pub generation_provider: ProviderName,
#[serde(default)]
pub generation_output_dir: Option<String>,
#[serde(default)]
pub mining: SkillMiningConfig,
}
fn default_max_repos_per_query() -> usize {
20
}
fn default_dedup_threshold() -> f32 {
0.85
}
fn default_rate_limit_rpm() -> u32 {
25
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct SkillMiningConfig {
#[serde(default)]
pub queries: Vec<String>,
#[serde(default = "default_max_repos_per_query")]
pub max_repos_per_query: usize,
#[serde(default = "default_dedup_threshold")]
pub dedup_threshold: f32,
#[serde(default)]
pub output_dir: Option<String>,
#[serde(default)]
pub generation_provider: ProviderName,
#[serde(default)]
pub embedding_provider: ProviderName,
#[serde(default = "default_rate_limit_rpm")]
pub rate_limit_rpm: u32,
}
#[derive(Debug, Deserialize, Serialize)]
#[allow(clippy::struct_excessive_bools)]
pub struct IndexConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_index_search_enabled")]
pub search_enabled: bool,
#[serde(default = "default_index_watch")]
pub watch: bool,
#[serde(default = "default_index_max_chunks")]
pub max_chunks: usize,
#[serde(default = "default_index_score_threshold")]
pub score_threshold: f32,
#[serde(default = "default_index_budget_ratio")]
pub budget_ratio: f32,
#[serde(default = "default_index_repo_map_tokens")]
pub repo_map_tokens: usize,
#[serde(default = "default_repo_map_ttl_secs")]
pub repo_map_ttl_secs: u64,
#[serde(default)]
pub mcp_enabled: bool,
#[serde(default)]
pub workspace_root: Option<std::path::PathBuf>,
#[serde(default = "default_index_concurrency")]
pub concurrency: usize,
#[serde(default = "default_index_batch_size")]
pub batch_size: usize,
#[serde(default = "default_index_memory_batch_size")]
pub memory_batch_size: usize,
#[serde(default = "default_index_max_file_bytes")]
pub max_file_bytes: usize,
#[serde(default)]
pub embed_provider: Option<String>,
#[serde(default = "default_index_embed_concurrency")]
pub embed_concurrency: usize,
}
impl Default for IndexConfig {
fn default() -> Self {
Self {
enabled: false,
search_enabled: default_index_search_enabled(),
watch: default_index_watch(),
max_chunks: default_index_max_chunks(),
score_threshold: default_index_score_threshold(),
budget_ratio: default_index_budget_ratio(),
repo_map_tokens: default_index_repo_map_tokens(),
repo_map_ttl_secs: default_repo_map_ttl_secs(),
mcp_enabled: false,
workspace_root: None,
concurrency: default_index_concurrency(),
batch_size: default_index_batch_size(),
memory_batch_size: default_index_memory_batch_size(),
max_file_bytes: default_index_max_file_bytes(),
embed_provider: None,
embed_concurrency: default_index_embed_concurrency(),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct VaultConfig {
#[serde(default = "default_vault_backend")]
pub backend: String,
}
impl Default for VaultConfig {
fn default() -> Self {
Self {
backend: default_vault_backend(),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CostConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_max_daily_cents")]
pub max_daily_cents: u32,
}
impl Default for CostConfig {
fn default() -> Self {
Self {
enabled: true,
max_daily_cents: default_max_daily_cents(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GatewayConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_gateway_bind")]
pub bind: String,
#[serde(default = "default_gateway_port")]
pub port: u16,
#[serde(default)]
pub auth_token: Option<String>,
#[serde(default = "default_gateway_rate_limit")]
pub rate_limit: u32,
#[serde(default = "default_gateway_max_body")]
pub max_body_size: usize,
}
impl Default for GatewayConfig {
fn default() -> Self {
Self {
enabled: false,
bind: default_gateway_bind(),
port: default_gateway_port(),
auth_token: None,
rate_limit: default_gateway_rate_limit(),
max_body_size: default_gateway_max_body(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DaemonConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_pid_file")]
pub pid_file: String,
#[serde(default = "default_health_interval")]
pub health_interval_secs: u64,
#[serde(default = "default_max_restart_backoff")]
pub max_restart_backoff_secs: u64,
}
impl Default for DaemonConfig {
fn default() -> Self {
Self {
enabled: false,
pid_file: default_pid_file(),
health_interval_secs: default_health_interval(),
max_restart_backoff_secs: default_max_restart_backoff(),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SchedulerConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_scheduler_tick_interval")]
pub tick_interval_secs: u64,
#[serde(default = "default_scheduler_max_tasks")]
pub max_tasks: usize,
#[serde(default)]
pub tasks: Vec<ScheduledTaskConfig>,
}
impl Default for SchedulerConfig {
fn default() -> Self {
Self {
enabled: true,
tick_interval_secs: default_scheduler_tick_interval(),
max_tasks: default_scheduler_max_tasks(),
tasks: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ScheduledTaskKind {
MemoryCleanup,
SkillRefresh,
HealthCheck,
UpdateCheck,
Experiment,
Custom(String),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ScheduledTaskConfig {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cron: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run_at: Option<String>,
pub kind: ScheduledTaskKind,
#[serde(default)]
pub config: serde_json::Value,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_config_defaults() {
let cfg = IndexConfig::default();
assert!(!cfg.enabled);
assert!(cfg.search_enabled);
assert!(!cfg.watch);
assert_eq!(cfg.concurrency, 4);
assert_eq!(cfg.batch_size, 32);
assert!(cfg.workspace_root.is_none());
}
#[test]
fn index_config_serde_roundtrip_with_new_fields() {
let toml = r#"
enabled = true
concurrency = 8
batch_size = 16
workspace_root = "/tmp/myproject"
"#;
let cfg: IndexConfig = toml::from_str(toml).unwrap();
assert!(cfg.enabled);
assert_eq!(cfg.concurrency, 8);
assert_eq!(cfg.batch_size, 16);
assert_eq!(
cfg.workspace_root,
Some(std::path::PathBuf::from("/tmp/myproject"))
);
let serialized = toml::to_string(&cfg).unwrap();
let cfg2: IndexConfig = toml::from_str(&serialized).unwrap();
assert_eq!(cfg2.concurrency, 8);
assert_eq!(cfg2.batch_size, 16);
}
#[test]
fn index_config_backward_compat_old_toml_without_new_fields() {
let toml = "
enabled = true
max_chunks = 20
score_threshold = 0.3
";
let cfg: IndexConfig = toml::from_str(toml).unwrap();
assert!(cfg.enabled);
assert_eq!(cfg.max_chunks, 20);
assert!(cfg.workspace_root.is_none());
assert_eq!(cfg.concurrency, 4);
assert_eq!(cfg.batch_size, 32);
}
#[test]
fn index_config_workspace_root_none_by_default() {
let cfg: IndexConfig = toml::from_str("enabled = false").unwrap();
assert!(cfg.workspace_root.is_none());
}
}
fn default_trace_service_name() -> String {
"zeph".into()
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct TraceConfig {
#[serde(default = "default_otlp_endpoint")]
pub otlp_endpoint: String,
#[serde(default = "default_trace_service_name")]
pub service_name: String,
#[serde(default = "default_true")]
pub redact: bool,
}
impl Default for TraceConfig {
fn default() -> Self {
Self {
otlp_endpoint: default_otlp_endpoint(),
service_name: default_trace_service_name(),
redact: true,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
pub struct DebugConfig {
pub enabled: bool,
#[serde(default = "crate::defaults::default_debug_output_dir")]
pub output_dir: std::path::PathBuf,
pub format: crate::dump_format::DumpFormat,
pub traces: TraceConfig,
}
impl Default for DebugConfig {
fn default() -> Self {
Self {
enabled: false,
output_dir: super::defaults::default_debug_output_dir(),
format: crate::dump_format::DumpFormat::default(),
traces: TraceConfig::default(),
}
}
}