1pub mod llm_planner;
9
10pub use llm_planner::{AchievementResult, LlmPlanner, Planner};
11
12use serde::{Deserialize, Serialize};
13use std::fmt;
14use std::str::FromStr;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
22#[serde(rename_all = "snake_case")]
23pub enum TaskStatus {
24 #[default]
26 Pending,
27 InProgress,
29 Completed,
31 Failed,
33 Skipped,
35 Cancelled,
37}
38
39impl fmt::Display for TaskStatus {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 TaskStatus::Pending => write!(f, "pending"),
43 TaskStatus::InProgress => write!(f, "in_progress"),
44 TaskStatus::Completed => write!(f, "completed"),
45 TaskStatus::Failed => write!(f, "failed"),
46 TaskStatus::Skipped => write!(f, "skipped"),
47 TaskStatus::Cancelled => write!(f, "cancelled"),
48 }
49 }
50}
51
52impl FromStr for TaskStatus {
53 type Err = std::convert::Infallible;
54
55 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 Ok(match s.to_lowercase().as_str() {
57 "pending" => TaskStatus::Pending,
58 "in_progress" | "inprogress" => TaskStatus::InProgress,
59 "completed" | "done" => TaskStatus::Completed,
60 "failed" => TaskStatus::Failed,
61 "skipped" => TaskStatus::Skipped,
62 "cancelled" | "canceled" => TaskStatus::Cancelled,
63 _ => TaskStatus::Pending,
64 })
65 }
66}
67
68impl TaskStatus {
69 pub fn is_active(&self) -> bool {
71 matches!(self, TaskStatus::Pending | TaskStatus::InProgress)
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
81#[serde(rename_all = "snake_case")]
82pub enum TaskPriority {
83 High,
85 #[default]
87 Medium,
88 Low,
90}
91
92impl fmt::Display for TaskPriority {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 TaskPriority::High => write!(f, "high"),
96 TaskPriority::Medium => write!(f, "medium"),
97 TaskPriority::Low => write!(f, "low"),
98 }
99 }
100}
101
102impl FromStr for TaskPriority {
103 type Err = std::convert::Infallible;
104
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 Ok(match s.to_lowercase().as_str() {
107 "high" | "h" | "1" => TaskPriority::High,
108 "medium" | "med" | "m" | "2" => TaskPriority::Medium,
109 "low" | "l" | "3" => TaskPriority::Low,
110 _ => TaskPriority::Medium,
111 })
112 }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct Task {
122 pub id: String,
124 #[serde(alias = "description")]
126 pub content: String,
127 pub status: TaskStatus,
129 #[serde(default)]
131 pub priority: TaskPriority,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub tool: Option<String>,
135 #[serde(default, skip_serializing_if = "Vec::is_empty")]
137 pub dependencies: Vec<String>,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub success_criteria: Option<String>,
141}
142
143impl Task {
144 pub fn new(id: impl Into<String>, content: impl Into<String>) -> Self {
146 Self {
147 id: id.into(),
148 content: content.into(),
149 status: TaskStatus::Pending,
150 priority: TaskPriority::Medium,
151 tool: None,
152 dependencies: Vec::new(),
153 success_criteria: None,
154 }
155 }
156
157 pub fn with_priority(mut self, priority: TaskPriority) -> Self {
159 self.priority = priority;
160 self
161 }
162
163 pub fn with_status(mut self, status: TaskStatus) -> Self {
165 self.status = status;
166 self
167 }
168
169 pub fn with_tool(mut self, tool: impl Into<String>) -> Self {
171 self.tool = Some(tool.into());
172 self
173 }
174
175 pub fn with_dependencies(mut self, deps: Vec<String>) -> Self {
177 self.dependencies = deps;
178 self
179 }
180
181 pub fn with_success_criteria(mut self, criteria: impl Into<String>) -> Self {
183 self.success_criteria = Some(criteria.into());
184 self
185 }
186
187 pub fn is_active(&self) -> bool {
189 self.status.is_active()
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
199pub enum Complexity {
200 Simple,
202 Medium,
204 Complex,
206 VeryComplex,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct ExecutionPlan {
213 pub goal: String,
215 pub steps: Vec<Task>,
217 pub complexity: Complexity,
219 pub required_tools: Vec<String>,
221 pub estimated_steps: usize,
223}
224
225impl ExecutionPlan {
226 pub fn new(goal: impl Into<String>, complexity: Complexity) -> Self {
227 Self {
228 goal: goal.into(),
229 steps: Vec::new(),
230 complexity,
231 required_tools: Vec::new(),
232 estimated_steps: 0,
233 }
234 }
235
236 pub fn add_step(&mut self, step: Task) {
237 self.steps.push(step);
238 self.estimated_steps = self.steps.len();
239 }
240
241 pub fn add_required_tool(&mut self, tool: impl Into<String>) {
242 let tool_str = tool.into();
243 if !self.required_tools.contains(&tool_str) {
244 self.required_tools.push(tool_str);
245 }
246 }
247
248 pub fn get_ready_steps(&self) -> Vec<&Task> {
250 self.steps
251 .iter()
252 .filter(|step| {
253 step.status == TaskStatus::Pending
254 && step.dependencies.iter().all(|dep_id| {
255 self.steps
256 .iter()
257 .find(|s| &s.id == dep_id)
258 .map(|s| s.status == TaskStatus::Completed)
259 .unwrap_or(false)
260 })
261 })
262 .collect()
263 }
264
265 pub fn mark_status(&mut self, step_id: &str, status: TaskStatus) {
267 if let Some(step) = self.steps.iter_mut().find(|s| s.id == step_id) {
268 step.status = status;
269 }
270 }
271
272 pub fn pending_count(&self) -> usize {
274 self.steps
275 .iter()
276 .filter(|s| s.status == TaskStatus::Pending)
277 .count()
278 }
279
280 pub fn has_deadlock(&self) -> bool {
285 self.pending_count() > 0 && self.get_ready_steps().is_empty()
286 }
287
288 pub fn progress(&self) -> f32 {
290 if self.steps.is_empty() {
291 return 0.0;
292 }
293 let completed = self
294 .steps
295 .iter()
296 .filter(|s| s.status == TaskStatus::Completed)
297 .count();
298 completed as f32 / self.steps.len() as f32
299 }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct AgentGoal {
309 pub description: String,
311 pub success_criteria: Vec<String>,
313 pub progress: f32,
315 pub achieved: bool,
317 pub created_at: i64,
319 pub achieved_at: Option<i64>,
321}
322
323impl AgentGoal {
324 pub fn new(description: impl Into<String>) -> Self {
325 Self {
326 description: description.into(),
327 success_criteria: Vec::new(),
328 progress: 0.0,
329 achieved: false,
330 created_at: chrono::Utc::now().timestamp(),
331 achieved_at: None,
332 }
333 }
334
335 pub fn with_criteria(mut self, criteria: Vec<String>) -> Self {
336 self.success_criteria = criteria;
337 self
338 }
339
340 pub fn update_progress(&mut self, progress: f32) {
341 self.progress = progress.clamp(0.0, 1.0);
342 }
343
344 pub fn mark_achieved(&mut self) {
345 self.achieved = true;
346 self.progress = 1.0;
347 self.achieved_at = Some(chrono::Utc::now().timestamp());
348 }
349}
350
351#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
364 fn test_task_status_display() {
365 assert_eq!(TaskStatus::Pending.to_string(), "pending");
366 assert_eq!(TaskStatus::InProgress.to_string(), "in_progress");
367 assert_eq!(TaskStatus::Completed.to_string(), "completed");
368 assert_eq!(TaskStatus::Failed.to_string(), "failed");
369 assert_eq!(TaskStatus::Skipped.to_string(), "skipped");
370 assert_eq!(TaskStatus::Cancelled.to_string(), "cancelled");
371 }
372
373 #[test]
374 fn test_task_status_from_str() {
375 assert_eq!(
376 TaskStatus::from_str("pending").unwrap(),
377 TaskStatus::Pending
378 );
379 assert_eq!(
380 TaskStatus::from_str("in_progress").unwrap(),
381 TaskStatus::InProgress
382 );
383 assert_eq!(
384 TaskStatus::from_str("inprogress").unwrap(),
385 TaskStatus::InProgress
386 );
387 assert_eq!(
388 TaskStatus::from_str("completed").unwrap(),
389 TaskStatus::Completed
390 );
391 assert_eq!(TaskStatus::from_str("done").unwrap(), TaskStatus::Completed);
392 assert_eq!(TaskStatus::from_str("failed").unwrap(), TaskStatus::Failed);
393 assert_eq!(
394 TaskStatus::from_str("skipped").unwrap(),
395 TaskStatus::Skipped
396 );
397 assert_eq!(
398 TaskStatus::from_str("cancelled").unwrap(),
399 TaskStatus::Cancelled
400 );
401 assert_eq!(
402 TaskStatus::from_str("canceled").unwrap(),
403 TaskStatus::Cancelled
404 );
405 assert_eq!(
406 TaskStatus::from_str("unknown").unwrap(),
407 TaskStatus::Pending
408 );
409 }
410
411 #[test]
412 fn test_task_status_is_active() {
413 assert!(TaskStatus::Pending.is_active());
414 assert!(TaskStatus::InProgress.is_active());
415 assert!(!TaskStatus::Completed.is_active());
416 assert!(!TaskStatus::Failed.is_active());
417 assert!(!TaskStatus::Skipped.is_active());
418 assert!(!TaskStatus::Cancelled.is_active());
419 }
420
421 #[test]
422 fn test_task_status_serialization() {
423 assert_eq!(
424 serde_json::to_string(&TaskStatus::InProgress).unwrap(),
425 "\"in_progress\""
426 );
427 assert_eq!(
428 serde_json::to_string(&TaskStatus::Failed).unwrap(),
429 "\"failed\""
430 );
431 }
432
433 #[test]
438 fn test_task_priority_display() {
439 assert_eq!(TaskPriority::High.to_string(), "high");
440 assert_eq!(TaskPriority::Medium.to_string(), "medium");
441 assert_eq!(TaskPriority::Low.to_string(), "low");
442 }
443
444 #[test]
445 fn test_task_priority_from_str() {
446 assert_eq!(TaskPriority::from_str("high").unwrap(), TaskPriority::High);
447 assert_eq!(TaskPriority::from_str("h").unwrap(), TaskPriority::High);
448 assert_eq!(
449 TaskPriority::from_str("medium").unwrap(),
450 TaskPriority::Medium
451 );
452 assert_eq!(TaskPriority::from_str("med").unwrap(), TaskPriority::Medium);
453 assert_eq!(TaskPriority::from_str("low").unwrap(), TaskPriority::Low);
454 assert_eq!(TaskPriority::from_str("l").unwrap(), TaskPriority::Low);
455 assert_eq!(
456 TaskPriority::from_str("unknown").unwrap(),
457 TaskPriority::Medium
458 );
459 }
460
461 #[test]
466 fn test_task_new() {
467 let task = Task::new("1", "Test task");
468 assert_eq!(task.id, "1");
469 assert_eq!(task.content, "Test task");
470 assert_eq!(task.status, TaskStatus::Pending);
471 assert_eq!(task.priority, TaskPriority::Medium);
472 assert!(task.tool.is_none());
473 assert!(task.dependencies.is_empty());
474 assert!(task.success_criteria.is_none());
475 }
476
477 #[test]
478 fn test_task_builder() {
479 let task = Task::new("1", "Test task")
480 .with_priority(TaskPriority::High)
481 .with_status(TaskStatus::InProgress)
482 .with_tool("bash")
483 .with_dependencies(vec!["step-0".to_string()])
484 .with_success_criteria("Command exits with 0");
485
486 assert_eq!(task.priority, TaskPriority::High);
487 assert_eq!(task.status, TaskStatus::InProgress);
488 assert_eq!(task.tool, Some("bash".to_string()));
489 assert_eq!(task.dependencies, vec!["step-0".to_string()]);
490 assert_eq!(
491 task.success_criteria,
492 Some("Command exits with 0".to_string())
493 );
494 }
495
496 #[test]
497 fn test_task_is_active() {
498 let pending = Task::new("1", "Pending task");
499 let in_progress = Task::new("2", "In progress").with_status(TaskStatus::InProgress);
500 let completed = Task::new("3", "Completed").with_status(TaskStatus::Completed);
501 let failed = Task::new("4", "Failed").with_status(TaskStatus::Failed);
502 let cancelled = Task::new("5", "Cancelled").with_status(TaskStatus::Cancelled);
503
504 assert!(pending.is_active());
505 assert!(in_progress.is_active());
506 assert!(!completed.is_active());
507 assert!(!failed.is_active());
508 assert!(!cancelled.is_active());
509 }
510
511 #[test]
512 fn test_task_serialization() {
513 let task = Task::new("1", "Test task")
514 .with_priority(TaskPriority::High)
515 .with_status(TaskStatus::InProgress);
516
517 let json = serde_json::to_string(&task).unwrap();
518 let parsed: Task = serde_json::from_str(&json).unwrap();
519
520 assert_eq!(parsed.id, task.id);
521 assert_eq!(parsed.content, task.content);
522 assert_eq!(parsed.status, task.status);
523 assert_eq!(parsed.priority, task.priority);
524 }
525
526 #[test]
527 fn test_task_deserialize_description_alias() {
528 let json = r#"{"id": "step-1", "description": "Test step", "status": "pending"}"#;
530 let task: Task = serde_json::from_str(json).unwrap();
531 assert_eq!(task.content, "Test step");
532 }
533
534 #[test]
539 fn test_execution_plan() {
540 let mut plan = ExecutionPlan::new("Test goal", Complexity::Medium);
541
542 plan.add_step(Task::new("step-1", "First step"));
543 plan.add_step(
544 Task::new("step-2", "Second step").with_dependencies(vec!["step-1".to_string()]),
545 );
546
547 assert_eq!(plan.steps.len(), 2);
548 assert_eq!(plan.estimated_steps, 2);
549 assert_eq!(plan.progress(), 0.0);
550
551 plan.steps[0].status = TaskStatus::Completed;
553 assert_eq!(plan.progress(), 0.5);
554
555 let ready = plan.get_ready_steps();
557 assert_eq!(ready.len(), 1);
558 assert_eq!(ready[0].id, "step-2");
559 }
560
561 #[test]
566 fn test_mark_status() {
567 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
568 plan.add_step(Task::new("s1", "Step 1"));
569 plan.add_step(Task::new("s2", "Step 2"));
570
571 assert_eq!(plan.steps[0].status, TaskStatus::Pending);
572 plan.mark_status("s1", TaskStatus::InProgress);
573 assert_eq!(plan.steps[0].status, TaskStatus::InProgress);
574 plan.mark_status("s1", TaskStatus::Completed);
575 assert_eq!(plan.steps[0].status, TaskStatus::Completed);
576 plan.mark_status("s999", TaskStatus::Failed);
578 assert_eq!(plan.steps[1].status, TaskStatus::Pending);
579 }
580
581 #[test]
582 fn test_pending_count() {
583 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
584 plan.add_step(Task::new("s1", "Step 1"));
585 plan.add_step(Task::new("s2", "Step 2"));
586 plan.add_step(Task::new("s3", "Step 3"));
587
588 assert_eq!(plan.pending_count(), 3);
589 plan.mark_status("s1", TaskStatus::Completed);
590 assert_eq!(plan.pending_count(), 2);
591 plan.mark_status("s2", TaskStatus::Failed);
592 assert_eq!(plan.pending_count(), 1);
593 plan.mark_status("s3", TaskStatus::InProgress);
594 assert_eq!(plan.pending_count(), 0);
595 }
596
597 #[test]
598 fn test_has_deadlock() {
599 let mut plan = ExecutionPlan::new("Test", Complexity::Simple);
601 plan.add_step(Task::new("s1", "Step 1").with_dependencies(vec!["s2".to_string()]));
602 plan.add_step(Task::new("s2", "Step 2").with_dependencies(vec!["s1".to_string()]));
603
604 assert!(plan.has_deadlock());
605
606 let mut plan2 = ExecutionPlan::new("Test", Complexity::Simple);
608 plan2.add_step(Task::new("s1", "Step 1"));
609 assert!(!plan2.has_deadlock());
610
611 let mut plan3 = ExecutionPlan::new("Test", Complexity::Simple);
613 plan3.add_step(Task::new("s1", "Step 1"));
614 plan3.add_step(Task::new("s2", "Step 2").with_dependencies(vec!["s1".to_string()]));
615 plan3.mark_status("s1", TaskStatus::Failed);
616 assert!(plan3.has_deadlock()); }
618
619 #[test]
620 fn test_get_ready_steps_parallel() {
621 let mut plan = ExecutionPlan::new("Test", Complexity::Medium);
623 plan.add_step(Task::new("s1", "Step 1"));
624 plan.add_step(Task::new("s2", "Step 2"));
625 plan.add_step(Task::new("s3", "Step 3"));
626
627 let ready = plan.get_ready_steps();
628 assert_eq!(ready.len(), 3);
629 }
630
631 #[test]
632 fn test_get_ready_steps_wave() {
633 let mut plan = ExecutionPlan::new("Test", Complexity::Medium);
635 plan.add_step(Task::new("s1", "Step 1"));
636 plan.add_step(Task::new("s2", "Step 2"));
637 plan.add_step(
638 Task::new("s3", "Step 3").with_dependencies(vec!["s1".to_string(), "s2".to_string()]),
639 );
640
641 let ready = plan.get_ready_steps();
643 assert_eq!(ready.len(), 2);
644 let ids: Vec<&str> = ready.iter().map(|s| s.id.as_str()).collect();
645 assert!(ids.contains(&"s1"));
646 assert!(ids.contains(&"s2"));
647
648 plan.mark_status("s1", TaskStatus::Completed);
650 plan.mark_status("s2", TaskStatus::Completed);
651
652 let ready = plan.get_ready_steps();
654 assert_eq!(ready.len(), 1);
655 assert_eq!(ready[0].id, "s3");
656 }
657
658 #[test]
663 fn test_agent_goal() {
664 let mut goal = AgentGoal::new("Complete task")
665 .with_criteria(vec!["Criterion 1".to_string(), "Criterion 2".to_string()]);
666
667 assert_eq!(goal.description, "Complete task");
668 assert_eq!(goal.success_criteria.len(), 2);
669 assert_eq!(goal.progress, 0.0);
670 assert!(!goal.achieved);
671
672 goal.update_progress(0.5);
673 assert_eq!(goal.progress, 0.5);
674
675 goal.mark_achieved();
676 assert!(goal.achieved);
677 assert_eq!(goal.progress, 1.0);
678 assert!(goal.achieved_at.is_some());
679 }
680
681 #[test]
682 fn test_complexity_levels() {
683 assert_eq!(
684 serde_json::to_string(&Complexity::Simple).unwrap(),
685 "\"Simple\""
686 );
687 assert_eq!(
688 serde_json::to_string(&Complexity::Complex).unwrap(),
689 "\"Complex\""
690 );
691 }
692}