use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrphanWatchdogConfig {
#[serde(default = "super::default_true")]
pub enabled: bool,
#[serde(default = "default_orphan_scan_interval")]
pub scan_interval_secs: u64,
#[serde(default = "default_orphan_timeout")]
pub timeout_secs: u64,
}
impl Default for OrphanWatchdogConfig {
fn default() -> Self {
Self {
enabled: true,
scan_interval_secs: default_orphan_scan_interval(),
timeout_secs: default_orphan_timeout(),
}
}
}
impl OrphanWatchdogConfig {
const MIN_TIMEOUT_SECS: u64 = 90;
pub fn validate(&self) -> Result<(), String> {
if !(Self::MIN_TIMEOUT_SECS..=3600).contains(&self.timeout_secs) {
return Err(format!(
"orphan_watchdog.timeout_secs must be {}-3600, got {}",
Self::MIN_TIMEOUT_SECS,
self.timeout_secs
));
}
if self.scan_interval_secs == 0 {
return Err("orphan_watchdog.scan_interval_secs must be > 0".to_owned());
}
Ok(())
}
}
fn default_orphan_scan_interval() -> u64 {
60
}
fn default_orphan_timeout() -> u64 {
300
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThreadSummaryWorkerConfig {
#[serde(default = "super::default_true")]
pub enabled: bool,
#[serde(default = "default_ts_reconcile_interval")]
pub reconcile_interval_secs: u64,
#[serde(default = "default_ts_claim_timeout")]
pub claim_timeout_secs: u64,
#[serde(default = "default_ts_max_attempts")]
pub max_attempts: u32,
#[serde(default = "default_compression_threshold")]
pub compression_threshold_pct: u32,
#[serde(default)]
pub summary_model_id: String,
#[serde(default = "default_summary_system_prompt")]
pub summary_system_prompt: String,
#[serde(default = "default_message_content_limit")]
pub message_content_limit: usize,
}
impl Default for ThreadSummaryWorkerConfig {
fn default() -> Self {
Self {
enabled: true,
reconcile_interval_secs: default_ts_reconcile_interval(),
claim_timeout_secs: default_ts_claim_timeout(),
max_attempts: default_ts_max_attempts(),
compression_threshold_pct: default_compression_threshold(),
summary_model_id: String::new(),
summary_system_prompt: default_summary_system_prompt(),
message_content_limit: default_message_content_limit(),
}
}
}
impl ThreadSummaryWorkerConfig {
pub fn validate(&self) -> Result<(), String> {
if self.reconcile_interval_secs == 0 {
return Err("thread_summary_worker.reconcile_interval_secs must be > 0".to_owned());
}
if self.claim_timeout_secs == 0 {
return Err("thread_summary_worker.claim_timeout_secs must be > 0".to_owned());
}
if self.max_attempts == 0 {
return Err("thread_summary_worker.max_attempts must be > 0".to_owned());
}
if self.compression_threshold_pct == 0 || self.compression_threshold_pct > 99 {
return Err(format!(
"thread_summary_worker.compression_threshold_pct must be 1-99, got {}",
self.compression_threshold_pct
));
}
Ok(())
}
}
fn default_ts_reconcile_interval() -> u64 {
60
}
fn default_ts_claim_timeout() -> u64 {
300
}
fn default_ts_max_attempts() -> u32 {
3
}
fn default_compression_threshold() -> u32 {
80
}
fn default_summary_system_prompt() -> String {
"You are a conversation summarizer. Given a conversation (and optionally an existing \
summary), produce a detailed structured summary. Respond with an <analysis> block \
(your reasoning) followed by a <summary> block (the final summary). Only the \
<summary> content will be stored. Do not invent information not present in the \
conversation."
.to_owned()
}
fn default_message_content_limit() -> usize {
4000
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CleanupWorkerConfig {
#[serde(default = "super::default_true")]
pub enabled: bool,
#[serde(default = "default_cleanup_poll_interval")]
pub poll_interval_secs: u64,
#[serde(default = "default_cleanup_reconcile_interval")]
pub reconcile_interval_secs: u64,
#[serde(default = "default_cleanup_stale_timeout")]
pub stale_in_progress_timeout_secs: u64,
#[serde(default = "default_cleanup_batch_size")]
pub batch_size: u32,
#[serde(default = "default_cleanup_max_attempts")]
pub max_attempts: u32,
}
impl Default for CleanupWorkerConfig {
fn default() -> Self {
Self {
enabled: true,
poll_interval_secs: default_cleanup_poll_interval(),
reconcile_interval_secs: default_cleanup_reconcile_interval(),
stale_in_progress_timeout_secs: default_cleanup_stale_timeout(),
batch_size: default_cleanup_batch_size(),
max_attempts: default_cleanup_max_attempts(),
}
}
}
impl CleanupWorkerConfig {
pub fn validate(&self) -> Result<(), String> {
if self.poll_interval_secs == 0 {
return Err("cleanup_worker.poll_interval_secs must be > 0".to_owned());
}
if self.reconcile_interval_secs == 0 {
return Err("cleanup_worker.reconcile_interval_secs must be > 0".to_owned());
}
if self.batch_size == 0 {
return Err("cleanup_worker.batch_size must be > 0".to_owned());
}
if self.max_attempts == 0 {
return Err("cleanup_worker.max_attempts must be > 0".to_owned());
}
if self.stale_in_progress_timeout_secs == 0 {
return Err("cleanup_worker.stale_in_progress_timeout_secs must be > 0".to_owned());
}
Ok(())
}
}
fn default_cleanup_poll_interval() -> u64 {
60
}
fn default_cleanup_reconcile_interval() -> u64 {
300
}
fn default_cleanup_stale_timeout() -> u64 {
900
}
fn default_cleanup_batch_size() -> u32 {
32
}
fn default_cleanup_max_attempts() -> u32 {
5
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_worker_configs_are_valid() {
OrphanWatchdogConfig::default().validate().unwrap();
ThreadSummaryWorkerConfig::default().validate().unwrap();
CleanupWorkerConfig::default().validate().unwrap();
}
}