1use serde::{Deserialize, Serialize};
11
12pub const DEFAULT_IMPL_TOOL_CALL_CAP: u32 = 15;
13pub const DEFAULT_RESEARCH_TOOL_CALL_CAP: u32 = 5;
14pub const DEFAULT_IMPL_OUTPUT_TOKEN_CAP: u32 = 20_000;
15pub const DEFAULT_RESEARCH_OUTPUT_TOKEN_CAP: u32 = 10_000;
16pub const MAX_CONCURRENT_AGENTS: u32 = 2;
17
18#[derive(Clone, Debug, Deserialize, Serialize)]
20pub struct Plan {
21 pub agent: Agent,
22 pub success: Success,
23 pub budget: Budget,
24}
25
26#[derive(Clone, Debug, Deserialize, Serialize)]
27pub struct Agent {
28 #[serde(rename = "type")]
29 pub ty: AgentType,
30 pub scope: String,
31}
32
33#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
34#[serde(rename_all = "lowercase")]
35pub enum AgentType {
36 Implementation,
37 Research,
38}
39
40#[derive(Clone, Debug, Deserialize, Serialize)]
41pub struct Success {
42 #[serde(default)]
43 pub compile: bool,
44 #[serde(default)]
45 pub tests: Vec<String>,
46 #[serde(default)]
47 pub visual_capture: bool,
48}
49
50#[derive(Clone, Debug, Deserialize, Serialize)]
51pub struct Budget {
52 pub tool_calls: u32,
53 pub output_tokens: u32,
54}
55
56pub fn parse_plan(s: &str) -> Result<Plan, toml::de::Error> {
57 toml::from_str(s)
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn parses_reference_plan() {
66 let src = r#"
67[agent]
68type = "implementation"
69scope = "src/foo.rs:10-50"
70
71[success]
72compile = true
73tests = ["tests/it::foo"]
74
75[budget]
76tool_calls = 15
77output_tokens = 20000
78"#;
79 let p = parse_plan(src).unwrap();
80 assert_eq!(p.agent.ty, AgentType::Implementation);
81 assert_eq!(p.budget.tool_calls, 15);
82 assert_eq!(p.budget.output_tokens, 20_000);
83 assert!(p.success.compile);
84 assert_eq!(p.success.tests.len(), 1);
85 }
86
87 #[test]
88 fn defaults_are_documented_values() {
89 assert_eq!(DEFAULT_IMPL_TOOL_CALL_CAP, 15);
90 assert_eq!(DEFAULT_RESEARCH_TOOL_CALL_CAP, 5);
91 assert_eq!(MAX_CONCURRENT_AGENTS, 2);
92 }
93}