1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ExecutionPlan {
6 pub id: String,
7 pub objective: String,
8 pub steps: Vec<PlanStep>,
9 pub status: PlanStatus,
10 #[serde(default)]
11 pub context: Value,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct PlanStep {
16 pub id: String,
17 pub description: String,
18 pub payload: Value,
22 #[serde(default)]
23 pub dependencies: Vec<String>,
24 pub status: StepStatus,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub result: Option<StepResult>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum PlanStatus {
31 Created,
32 Approved,
33 Executing,
34 Completed,
35 Failed,
36 Cancelled,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40pub enum StepStatus {
41 Pending,
42 Running,
43 Completed,
44 Failed,
45 Skipped,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct StepResult {
50 pub success: bool,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub output: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub error: Option<String>,
55 pub duration_ms: u64,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59pub enum RecoveryAction {
60 Retry,
61 Skip,
62 Abort,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct PlanStoreData {
67 pub plan: ExecutionPlan,
68 #[serde(default)]
69 pub metadata: Value,
70}
71
72impl ExecutionPlan {
73 pub fn new(id: impl Into<String>, objective: impl Into<String>) -> Self {
74 Self {
75 id: id.into(),
76 objective: objective.into(),
77 steps: Vec::new(),
78 status: PlanStatus::Created,
79 context: Value::Null,
80 }
81 }
82
83 pub fn current_step(&self) -> Option<&PlanStep> {
84 self.steps.iter().find(|s| s.status == StepStatus::Running)
85 }
86
87 pub fn next_pending_step(&self) -> Option<&PlanStep> {
88 self.steps.iter().find(|s| s.status == StepStatus::Pending)
89 }
90
91 pub fn is_completed(&self) -> bool {
92 self.status == PlanStatus::Completed
93 || self
94 .steps
95 .iter()
96 .all(|s| matches!(s.status, StepStatus::Completed | StepStatus::Skipped))
97 }
98
99 pub fn has_failed(&self) -> bool {
100 self.status == PlanStatus::Failed || self.steps.iter().any(|s| s.status == StepStatus::Failed)
101 }
102
103 pub fn progress(&self) -> (usize, usize) {
104 let total = self.steps.len();
105 let completed = self
106 .steps
107 .iter()
108 .filter(|s| matches!(s.status, StepStatus::Completed | StepStatus::Skipped))
109 .count();
110 (completed, total)
111 }
112}
113
114impl PlanStep {
115 pub fn new(
116 id: impl Into<String>,
117 description: impl Into<String>,
118 payload: Value,
119 ) -> Self {
120 Self {
121 id: id.into(),
122 description: description.into(),
123 payload,
124 dependencies: Vec::new(),
125 status: StepStatus::Pending,
126 result: None,
127 }
128 }
129
130 pub fn with_dependencies(mut self, dependencies: Vec<String>) -> Self {
131 self.dependencies = dependencies;
132 self
133 }
134}
135
136impl StepResult {
137 pub fn success(output: impl Into<String>, duration_ms: u64) -> Self {
138 Self {
139 success: true,
140 output: Some(output.into()),
141 error: None,
142 duration_ms,
143 }
144 }
145
146 pub fn failure(error: impl Into<String>, duration_ms: u64) -> Self {
147 Self {
148 success: false,
149 output: None,
150 error: Some(error.into()),
151 duration_ms,
152 }
153 }
154}