1pub mod llm_planner;
9
10pub use llm_planner::{AchievementResult, LlmPlanner};
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 progress(&self) -> f32 {
267 if self.steps.is_empty() {
268 return 0.0;
269 }
270 let completed = self
271 .steps
272 .iter()
273 .filter(|s| s.status == TaskStatus::Completed)
274 .count();
275 completed as f32 / self.steps.len() as f32
276 }
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct AgentGoal {
286 pub description: String,
288 pub success_criteria: Vec<String>,
290 pub progress: f32,
292 pub achieved: bool,
294 pub created_at: i64,
296 pub achieved_at: Option<i64>,
298}
299
300impl AgentGoal {
301 pub fn new(description: impl Into<String>) -> Self {
302 Self {
303 description: description.into(),
304 success_criteria: Vec::new(),
305 progress: 0.0,
306 achieved: false,
307 created_at: chrono::Utc::now().timestamp(),
308 achieved_at: None,
309 }
310 }
311
312 pub fn with_criteria(mut self, criteria: Vec<String>) -> Self {
313 self.success_criteria = criteria;
314 self
315 }
316
317 pub fn update_progress(&mut self, progress: f32) {
318 self.progress = progress.clamp(0.0, 1.0);
319 }
320
321 pub fn mark_achieved(&mut self) {
322 self.achieved = true;
323 self.progress = 1.0;
324 self.achieved_at = Some(chrono::Utc::now().timestamp());
325 }
326}
327
328#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
341 fn test_task_status_display() {
342 assert_eq!(TaskStatus::Pending.to_string(), "pending");
343 assert_eq!(TaskStatus::InProgress.to_string(), "in_progress");
344 assert_eq!(TaskStatus::Completed.to_string(), "completed");
345 assert_eq!(TaskStatus::Failed.to_string(), "failed");
346 assert_eq!(TaskStatus::Skipped.to_string(), "skipped");
347 assert_eq!(TaskStatus::Cancelled.to_string(), "cancelled");
348 }
349
350 #[test]
351 fn test_task_status_from_str() {
352 assert_eq!(
353 TaskStatus::from_str("pending").unwrap(),
354 TaskStatus::Pending
355 );
356 assert_eq!(
357 TaskStatus::from_str("in_progress").unwrap(),
358 TaskStatus::InProgress
359 );
360 assert_eq!(
361 TaskStatus::from_str("inprogress").unwrap(),
362 TaskStatus::InProgress
363 );
364 assert_eq!(
365 TaskStatus::from_str("completed").unwrap(),
366 TaskStatus::Completed
367 );
368 assert_eq!(TaskStatus::from_str("done").unwrap(), TaskStatus::Completed);
369 assert_eq!(TaskStatus::from_str("failed").unwrap(), TaskStatus::Failed);
370 assert_eq!(
371 TaskStatus::from_str("skipped").unwrap(),
372 TaskStatus::Skipped
373 );
374 assert_eq!(
375 TaskStatus::from_str("cancelled").unwrap(),
376 TaskStatus::Cancelled
377 );
378 assert_eq!(
379 TaskStatus::from_str("canceled").unwrap(),
380 TaskStatus::Cancelled
381 );
382 assert_eq!(
383 TaskStatus::from_str("unknown").unwrap(),
384 TaskStatus::Pending
385 );
386 }
387
388 #[test]
389 fn test_task_status_is_active() {
390 assert!(TaskStatus::Pending.is_active());
391 assert!(TaskStatus::InProgress.is_active());
392 assert!(!TaskStatus::Completed.is_active());
393 assert!(!TaskStatus::Failed.is_active());
394 assert!(!TaskStatus::Skipped.is_active());
395 assert!(!TaskStatus::Cancelled.is_active());
396 }
397
398 #[test]
399 fn test_task_status_serialization() {
400 assert_eq!(
401 serde_json::to_string(&TaskStatus::InProgress).unwrap(),
402 "\"in_progress\""
403 );
404 assert_eq!(
405 serde_json::to_string(&TaskStatus::Failed).unwrap(),
406 "\"failed\""
407 );
408 }
409
410 #[test]
415 fn test_task_priority_display() {
416 assert_eq!(TaskPriority::High.to_string(), "high");
417 assert_eq!(TaskPriority::Medium.to_string(), "medium");
418 assert_eq!(TaskPriority::Low.to_string(), "low");
419 }
420
421 #[test]
422 fn test_task_priority_from_str() {
423 assert_eq!(TaskPriority::from_str("high").unwrap(), TaskPriority::High);
424 assert_eq!(TaskPriority::from_str("h").unwrap(), TaskPriority::High);
425 assert_eq!(
426 TaskPriority::from_str("medium").unwrap(),
427 TaskPriority::Medium
428 );
429 assert_eq!(TaskPriority::from_str("med").unwrap(), TaskPriority::Medium);
430 assert_eq!(TaskPriority::from_str("low").unwrap(), TaskPriority::Low);
431 assert_eq!(TaskPriority::from_str("l").unwrap(), TaskPriority::Low);
432 assert_eq!(
433 TaskPriority::from_str("unknown").unwrap(),
434 TaskPriority::Medium
435 );
436 }
437
438 #[test]
443 fn test_task_new() {
444 let task = Task::new("1", "Test task");
445 assert_eq!(task.id, "1");
446 assert_eq!(task.content, "Test task");
447 assert_eq!(task.status, TaskStatus::Pending);
448 assert_eq!(task.priority, TaskPriority::Medium);
449 assert!(task.tool.is_none());
450 assert!(task.dependencies.is_empty());
451 assert!(task.success_criteria.is_none());
452 }
453
454 #[test]
455 fn test_task_builder() {
456 let task = Task::new("1", "Test task")
457 .with_priority(TaskPriority::High)
458 .with_status(TaskStatus::InProgress)
459 .with_tool("bash")
460 .with_dependencies(vec!["step-0".to_string()])
461 .with_success_criteria("Command exits with 0");
462
463 assert_eq!(task.priority, TaskPriority::High);
464 assert_eq!(task.status, TaskStatus::InProgress);
465 assert_eq!(task.tool, Some("bash".to_string()));
466 assert_eq!(task.dependencies, vec!["step-0".to_string()]);
467 assert_eq!(
468 task.success_criteria,
469 Some("Command exits with 0".to_string())
470 );
471 }
472
473 #[test]
474 fn test_task_is_active() {
475 let pending = Task::new("1", "Pending task");
476 let in_progress = Task::new("2", "In progress").with_status(TaskStatus::InProgress);
477 let completed = Task::new("3", "Completed").with_status(TaskStatus::Completed);
478 let failed = Task::new("4", "Failed").with_status(TaskStatus::Failed);
479 let cancelled = Task::new("5", "Cancelled").with_status(TaskStatus::Cancelled);
480
481 assert!(pending.is_active());
482 assert!(in_progress.is_active());
483 assert!(!completed.is_active());
484 assert!(!failed.is_active());
485 assert!(!cancelled.is_active());
486 }
487
488 #[test]
489 fn test_task_serialization() {
490 let task = Task::new("1", "Test task")
491 .with_priority(TaskPriority::High)
492 .with_status(TaskStatus::InProgress);
493
494 let json = serde_json::to_string(&task).unwrap();
495 let parsed: Task = serde_json::from_str(&json).unwrap();
496
497 assert_eq!(parsed.id, task.id);
498 assert_eq!(parsed.content, task.content);
499 assert_eq!(parsed.status, task.status);
500 assert_eq!(parsed.priority, task.priority);
501 }
502
503 #[test]
504 fn test_task_deserialize_description_alias() {
505 let json = r#"{"id": "step-1", "description": "Test step", "status": "pending"}"#;
507 let task: Task = serde_json::from_str(json).unwrap();
508 assert_eq!(task.content, "Test step");
509 }
510
511 #[test]
516 fn test_execution_plan() {
517 let mut plan = ExecutionPlan::new("Test goal", Complexity::Medium);
518
519 plan.add_step(Task::new("step-1", "First step"));
520 plan.add_step(
521 Task::new("step-2", "Second step").with_dependencies(vec!["step-1".to_string()]),
522 );
523
524 assert_eq!(plan.steps.len(), 2);
525 assert_eq!(plan.estimated_steps, 2);
526 assert_eq!(plan.progress(), 0.0);
527
528 plan.steps[0].status = TaskStatus::Completed;
530 assert_eq!(plan.progress(), 0.5);
531
532 let ready = plan.get_ready_steps();
534 assert_eq!(ready.len(), 1);
535 assert_eq!(ready[0].id, "step-2");
536 }
537
538 #[test]
543 fn test_agent_goal() {
544 let mut goal = AgentGoal::new("Complete task")
545 .with_criteria(vec!["Criterion 1".to_string(), "Criterion 2".to_string()]);
546
547 assert_eq!(goal.description, "Complete task");
548 assert_eq!(goal.success_criteria.len(), 2);
549 assert_eq!(goal.progress, 0.0);
550 assert!(!goal.achieved);
551
552 goal.update_progress(0.5);
553 assert_eq!(goal.progress, 0.5);
554
555 goal.mark_achieved();
556 assert!(goal.achieved);
557 assert_eq!(goal.progress, 1.0);
558 assert!(goal.achieved_at.is_some());
559 }
560
561 #[test]
562 fn test_complexity_levels() {
563 assert_eq!(
564 serde_json::to_string(&Complexity::Simple).unwrap(),
565 "\"Simple\""
566 );
567 assert_eq!(
568 serde_json::to_string(&Complexity::Complex).unwrap(),
569 "\"Complex\""
570 );
571 }
572}