use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use crate::TetradResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub general: GeneralConfig,
#[serde(default)]
pub executors: ExecutorsConfig,
#[serde(default)]
pub consensus: ConsensusConfig,
#[serde(default)]
pub reasoning: ReasoningConfig,
#[serde(default)]
pub cache: CacheConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneralConfig {
#[serde(default = "default_log_level")]
pub log_level: String,
#[serde(default = "default_log_format")]
pub log_format: String,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
}
impl Default for GeneralConfig {
fn default() -> Self {
Self {
log_level: default_log_level(),
log_format: default_log_format(),
timeout_secs: default_timeout(),
}
}
}
fn default_log_level() -> String {
"info".to_string()
}
fn default_log_format() -> String {
"text".to_string()
}
fn default_timeout() -> u64 {
60
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutorsConfig {
#[serde(default)]
pub codex: ExecutorConfig,
#[serde(default)]
pub gemini: ExecutorConfig,
#[serde(default)]
pub qwen: ExecutorConfig,
}
impl Default for ExecutorsConfig {
fn default() -> Self {
Self {
codex: ExecutorConfig::new("codex", &["exec", "--json"]),
gemini: ExecutorConfig::new("gemini", &["-o", "json"]),
qwen: ExecutorConfig::new("qwen", &[]),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutorConfig {
#[serde(default = "default_true")]
pub enabled: bool,
pub command: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default = "default_executor_timeout")]
pub timeout_secs: u64,
#[serde(default = "default_weight")]
pub weight: u8,
}
impl ExecutorConfig {
pub fn new(command: &str, args: &[&str]) -> Self {
Self {
enabled: true,
command: command.to_string(),
args: args.iter().map(|s| s.to_string()).collect(),
timeout_secs: default_executor_timeout(),
weight: default_weight(),
}
}
}
impl Default for ExecutorConfig {
fn default() -> Self {
Self {
enabled: true,
command: String::new(),
args: Vec::new(),
timeout_secs: default_executor_timeout(),
weight: default_weight(),
}
}
}
fn default_true() -> bool {
true
}
fn default_executor_timeout() -> u64 {
30
}
fn default_weight() -> u8 {
5
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsensusConfig {
#[serde(default = "default_consensus_rule")]
pub default_rule: ConsensusRule,
#[serde(default = "default_min_score")]
pub min_score: u8,
#[serde(default = "default_max_loops")]
pub max_loops: u8,
}
impl Default for ConsensusConfig {
fn default() -> Self {
Self {
default_rule: default_consensus_rule(),
min_score: default_min_score(),
max_loops: default_max_loops(),
}
}
}
fn default_consensus_rule() -> ConsensusRule {
ConsensusRule::Strong
}
fn default_min_score() -> u8 {
70
}
fn default_max_loops() -> u8 {
3
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ConsensusRule {
Golden,
Weak,
Strong,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReasoningConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_db_path")]
pub db_path: PathBuf,
#[serde(default = "default_max_patterns")]
pub max_patterns_per_query: usize,
#[serde(default = "default_consolidation_interval")]
pub consolidation_interval: usize,
}
impl Default for ReasoningConfig {
fn default() -> Self {
Self {
enabled: true,
db_path: default_db_path(),
max_patterns_per_query: default_max_patterns(),
consolidation_interval: default_consolidation_interval(),
}
}
}
fn default_db_path() -> PathBuf {
PathBuf::from("tetrad.db")
}
fn default_max_patterns() -> usize {
10
}
fn default_consolidation_interval() -> usize {
100
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_cache_capacity")]
pub capacity: usize,
#[serde(default = "default_cache_ttl")]
pub ttl_secs: u64,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
enabled: true,
capacity: default_cache_capacity(),
ttl_secs: default_cache_ttl(),
}
}
}
fn default_cache_capacity() -> usize {
1000
}
fn default_cache_ttl() -> u64 {
300 }
impl Config {
pub fn load<P: AsRef<Path>>(path: P) -> TetradResult<Self> {
let content = std::fs::read_to_string(path)?;
let config: Config = toml::from_str(&content)?;
Ok(config)
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> TetradResult<()> {
let content = toml::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn default_config() -> Self {
Self {
general: GeneralConfig::default(),
executors: ExecutorsConfig::default(),
consensus: ConsensusConfig::default(),
reasoning: ReasoningConfig::default(),
cache: CacheConfig::default(),
}
}
pub fn load_or_default() -> Self {
Self::load("tetrad.toml").unwrap_or_else(|_| Self::default_config())
}
}
impl Default for Config {
fn default() -> Self {
Self::default_config()
}
}