agent_base/types/
plan_update.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum PlanStepStatus {
7 Pending,
8 InProgress,
9 Completed,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct PlanItem {
18 pub step: String,
19 pub status: PlanStepStatus,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct UpdatePlanArgs {
28 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub objective: Option<String>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub explanation: Option<String>,
36 pub plan: Vec<PlanItem>,
38}
39
40impl UpdatePlanArgs {
41 pub fn validate(&self) -> Result<(), String> {
49 if let Some(ref objective) = self.objective {
50 let objective_trimmed = objective.trim();
51 if objective_trimmed.is_empty() {
52 return Err("objective must not be empty when provided".to_string());
53 }
54 if objective_trimmed.chars().count() > 200 {
55 return Err(format!(
56 "objective must be at most 200 characters, got {}",
57 objective_trimmed.chars().count()
58 ));
59 }
60 }
61 if self.plan.is_empty() {
62 return Err("plan must contain at least one step".to_string());
63 }
64 if self.plan.len() > 50 {
65 return Err(format!(
66 "plan must contain at most 50 steps, got {}",
67 self.plan.len()
68 ));
69 }
70
71 let in_progress_count = self
72 .plan
73 .iter()
74 .filter(|item| item.status == PlanStepStatus::InProgress)
75 .count();
76
77 if in_progress_count > 1 {
78 return Err(format!(
79 "at most one step may be in_progress, found {}",
80 in_progress_count
81 ));
82 }
83
84 for (i, item) in self.plan.iter().enumerate() {
85 if item.step.trim().is_empty() {
86 return Err(format!("step[{}] must not be empty", i));
87 }
88 }
89
90 Ok(())
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_optional_objective_deserialization() {
100 let json =
102 r#"{"objective": "Install Docker", "plan": [{"step": "Step 1", "status": "pending"}]}"#;
103 let args: UpdatePlanArgs = serde_json::from_str(json).unwrap();
104 assert_eq!(args.objective.as_deref(), Some("Install Docker"));
105 assert!(args.validate().is_ok());
106
107 let json = r#"{"plan": [{"step": "Step 1", "status": "pending"}]}"#;
109 let args: UpdatePlanArgs = serde_json::from_str(json).unwrap();
110 assert!(args.objective.is_none());
111 assert!(args.validate().is_ok());
112
113 let json = r#"{"objective": " ", "plan": [{"step": "Step 1", "status": "pending"}]}"#;
115 let args: UpdatePlanArgs = serde_json::from_str(json).unwrap();
116 assert!(args.validate().is_err());
117 }
118}