use crate::utils::file_utils::{read_file_with_context_sync, write_file_with_context_sync};
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ImprovementsConfig {
pub similarity: SimilarityConfig,
pub time_decay: TimeDecayConfig,
pub patterns: PatternConfig,
pub cache: CacheConfig,
pub context: ContextConfig,
pub fallback: FallbackConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimilarityConfig {
pub min_similarity_threshold: f32,
pub high_similarity_threshold: f32,
pub argument_weight: f32,
pub return_type_weight: f32,
pub description_weight: f32,
pub success_history_weight: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeDecayConfig {
pub decay_constant: f32,
pub half_life_hours: f32,
pub minimum_score: f32,
pub recent_window_hours: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternConfig {
pub min_sequence_length: usize,
pub pattern_window_seconds: u64,
pub confidence_threshold: f32,
pub max_patterns: usize,
pub enable_advanced_detection: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheConfig {
pub max_entries: usize,
pub ttl: Duration,
pub enable_result_cache: bool,
pub enable_metadata_cache: bool,
pub enable_pattern_cache: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextConfig {
pub max_context_tokens: usize,
pub truncation_threshold_percent: f32,
pub enable_compaction: bool,
pub max_history_entries: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FallbackConfig {
pub max_attempts: usize,
pub backoff_multiplier: f32,
pub initial_backoff_ms: u64,
pub max_backoff_ms: u64,
pub enable_exponential_backoff: bool,
}
impl Default for SimilarityConfig {
fn default() -> Self {
Self {
min_similarity_threshold: 0.6,
high_similarity_threshold: 0.8,
argument_weight: 0.4,
return_type_weight: 0.3,
description_weight: 0.2,
success_history_weight: 0.1,
}
}
}
impl Default for TimeDecayConfig {
fn default() -> Self {
Self {
decay_constant: 0.1,
half_life_hours: 24.0,
minimum_score: 0.1,
recent_window_hours: 1.0,
}
}
}
impl Default for PatternConfig {
fn default() -> Self {
Self {
min_sequence_length: 3,
pattern_window_seconds: 300,
confidence_threshold: 0.75,
max_patterns: 100,
enable_advanced_detection: true,
}
}
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
max_entries: 10_000,
ttl: Duration::from_secs(3600),
enable_result_cache: true,
enable_metadata_cache: true,
enable_pattern_cache: true,
}
}
}
impl Default for ContextConfig {
fn default() -> Self {
Self {
max_context_tokens: 100_000,
truncation_threshold_percent: 85.0,
enable_compaction: true,
max_history_entries: 100,
}
}
}
impl Default for FallbackConfig {
fn default() -> Self {
Self {
max_attempts: 3,
backoff_multiplier: 2.0,
initial_backoff_ms: 100,
max_backoff_ms: 5000,
enable_exponential_backoff: true,
}
}
}
impl ImprovementsConfig {
pub fn from_file(path: &str) -> anyhow::Result<Self> {
let content =
read_file_with_context_sync(std::path::Path::new(path), "improvements config")?;
toml::from_str(&content).map_err(|e| anyhow::anyhow!("failed to parse config: {}", e))
}
pub fn to_file(&self, path: &str) -> anyhow::Result<()> {
let content = toml::to_string_pretty(self)?;
write_file_with_context_sync(std::path::Path::new(path), &content, "improvements config")?;
Ok(())
}
pub fn validate(&self) -> Result<(), String> {
if !(0.0..=1.0).contains(&self.similarity.min_similarity_threshold) {
return Err("min_similarity_threshold must be between 0.0 and 1.0".to_string());
}
if !(0.0..=1.0).contains(&self.similarity.high_similarity_threshold) {
return Err("high_similarity_threshold must be between 0.0 and 1.0".to_string());
}
if self.time_decay.decay_constant <= 0.0 {
return Err("decay_constant must be positive".to_string());
}
if self.time_decay.half_life_hours <= 0.0 {
return Err("half_life_hours must be positive".to_string());
}
if self.patterns.min_sequence_length < 2 {
return Err("min_sequence_length must be at least 2".to_string());
}
if !(0.0..=1.0).contains(&self.patterns.confidence_threshold) {
return Err("confidence_threshold must be between 0.0 and 1.0".to_string());
}
if self.context.max_context_tokens == 0 {
return Err("max_context_tokens must be positive".to_string());
}
if !(0.0..=100.0).contains(&self.context.truncation_threshold_percent) {
return Err("truncation_threshold_percent must be between 0.0 and 100.0".to_string());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config_is_valid() {
let config = ImprovementsConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validation_similarity() {
let mut config = ImprovementsConfig::default();
config.similarity.min_similarity_threshold = 1.5;
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_decay() {
let mut config = ImprovementsConfig::default();
config.time_decay.decay_constant = -0.1;
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_pattern() {
let mut config = ImprovementsConfig::default();
config.patterns.min_sequence_length = 1;
assert!(config.validate().is_err());
}
#[test]
fn test_config_serialization() {
let config = ImprovementsConfig::default();
let toml_str = toml::to_string_pretty(&config).expect("serialization failed");
assert!(toml_str.contains("min_similarity_threshold"));
assert!(toml_str.contains("decay_constant"));
}
}