use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
#[derive(Default)]
pub struct HeavyConfig {
pub query: String,
pub provider: Option<String>,
pub model: Option<String>,
pub captain_model: Option<String>,
pub max_agents: Option<usize>,
pub debate_rounds: usize,
pub max_turns: Option<usize>,
pub verbose: bool,
pub json_output: bool,
}
impl HeavyConfig {
pub fn provider(&self) -> &str {
self.provider.as_deref().unwrap_or("xai")
}
pub fn model(&self) -> &str {
self.model.as_deref().unwrap_or("grok-4-0709")
}
pub fn max_turns(&self) -> usize {
self.max_turns.unwrap_or(10)
}
pub fn apply_toml_defaults(&mut self, project_dir: &Path) -> Result<()> {
let toml_path = project_dir.join(".scud").join("heavy.toml");
if !toml_path.exists() {
return Ok(());
}
let content = std::fs::read_to_string(&toml_path)?;
let file: HeavyToml = toml::from_str(&content)?;
if let Some(defaults) = file.defaults {
if self.provider.is_none() {
self.provider = defaults.provider;
}
if self.model.is_none() {
self.model = defaults.model;
}
if self.captain_model.is_none() {
self.captain_model = defaults.captain_model;
}
if self.max_turns.is_none() {
self.max_turns = defaults.max_turns;
}
}
Ok(())
}
pub fn agent_tool_override(project_dir: &Path, agent_name: &str) -> Option<Vec<String>> {
let toml_path = project_dir.join(".scud").join("heavy.toml");
if !toml_path.exists() {
return None;
}
let content = std::fs::read_to_string(&toml_path).ok()?;
let file: HeavyToml = toml::from_str(&content).ok()?;
let agents = file.agents?;
let agent_config = agents.get(&agent_name.to_lowercase())?;
Some(agent_config.tools.clone())
}
}
#[derive(Deserialize, Default)]
struct HeavyToml {
defaults: Option<HeavyDefaults>,
agents: Option<HashMap<String, AgentTomlConfig>>,
}
#[derive(Deserialize, Default)]
struct HeavyDefaults {
provider: Option<String>,
model: Option<String>,
captain_model: Option<String>,
max_turns: Option<usize>,
}
#[derive(Deserialize)]
struct AgentTomlConfig {
tools: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = HeavyConfig::default();
assert!(config.provider.is_none());
assert!(config.model.is_none());
assert!(config.max_turns.is_none());
assert_eq!(config.provider(), "xai");
assert_eq!(config.model(), "grok-4-0709");
assert_eq!(config.max_turns(), 10);
assert_eq!(config.debate_rounds, 0);
assert!(!config.verbose);
assert!(!config.json_output);
}
#[test]
fn test_toml_missing_is_ok() {
let mut config = HeavyConfig::default();
let result = config.apply_toml_defaults(Path::new("/nonexistent"));
assert!(result.is_ok());
}
}