use serde::{Deserialize, Serialize};
pub const DEFAULT_IMPL_TOOL_CALL_CAP: u32 = 15;
pub const DEFAULT_RESEARCH_TOOL_CALL_CAP: u32 = 5;
pub const DEFAULT_IMPL_OUTPUT_TOKEN_CAP: u32 = 20_000;
pub const DEFAULT_RESEARCH_OUTPUT_TOKEN_CAP: u32 = 10_000;
pub const MAX_CONCURRENT_AGENTS: u32 = 2;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Plan {
pub agent: Agent,
pub success: Success,
pub budget: Budget,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Agent {
#[serde(rename = "type")]
pub ty: AgentType,
pub scope: String,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AgentType {
Implementation,
Research,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Success {
#[serde(default)]
pub compile: bool,
#[serde(default)]
pub tests: Vec<String>,
#[serde(default)]
pub visual_capture: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Budget {
pub tool_calls: u32,
pub output_tokens: u32,
}
pub fn parse_plan(s: &str) -> Result<Plan, toml::de::Error> {
toml::from_str(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_reference_plan() {
let src = r#"
[agent]
type = "implementation"
scope = "src/foo.rs:10-50"
[success]
compile = true
tests = ["tests/it::foo"]
[budget]
tool_calls = 15
output_tokens = 20000
"#;
let p = parse_plan(src).unwrap();
assert_eq!(p.agent.ty, AgentType::Implementation);
assert_eq!(p.budget.tool_calls, 15);
assert_eq!(p.budget.output_tokens, 20_000);
assert!(p.success.compile);
assert_eq!(p.success.tests.len(), 1);
}
#[test]
fn defaults_are_documented_values() {
assert_eq!(DEFAULT_IMPL_TOOL_CALL_CAP, 15);
assert_eq!(DEFAULT_RESEARCH_TOOL_CALL_CAP, 5);
assert_eq!(MAX_CONCURRENT_AGENTS, 2);
}
}