systemprompt_agent/repository/execution/
mutations.rs1use chrono::{DateTime, Utc};
5use systemprompt_identifiers::TaskId;
6use systemprompt_models::{ExecutionStep, PlannedTool, StepContent, StepId, StepStatus};
7use systemprompt_traits::RepositoryError;
8
9use super::ExecutionStepRepository;
10use super::parse::{ParseStepParams, parse_step};
11
12impl ExecutionStepRepository {
13 pub async fn create(&self, step: &ExecutionStep) -> Result<(), RepositoryError> {
14 let step_id_str = step.step_id.as_str();
15 let task_id = &step.task_id;
16 let status_str = step.status.to_string();
17 let step_type_str = step.content.step_type().to_string();
18 let title = step.content.title();
19 let content_json = serde_json::to_value(&step.content).map_err(|e| {
20 RepositoryError::Internal(format!("Failed to serialize step content: {e}"))
21 })?;
22 sqlx::query!(
23 r#"INSERT INTO task_execution_steps (
24 step_id, task_id, step_type, title, status, content, started_at, completed_at, duration_ms, error_message
25 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)"#,
26 step_id_str,
27 task_id.as_str(),
28 step_type_str,
29 title,
30 status_str,
31 content_json,
32 step.started_at,
33 step.completed_at,
34 step.duration_ms,
35 step.error_message
36 )
37 .execute(&*self.write_pool)
38 .await
39 .map_err(|e| RepositoryError::Internal(format!("Failed to create execution step: {e}")))?;
40 Ok(())
41 }
42
43 pub async fn complete_step(
44 &self,
45 step_id: &StepId,
46 started_at: DateTime<Utc>,
47 tool_result: Option<serde_json::Value>,
48 ) -> Result<(), RepositoryError> {
49 let completed_at = Utc::now();
50 let duration_ms = (completed_at - started_at).num_milliseconds() as i32;
51 let step_id_str = step_id.as_str();
52 let status_str = StepStatus::Completed.to_string();
53
54 if let Some(result) = tool_result {
55 sqlx::query!(
56 r#"UPDATE task_execution_steps SET
57 status = $2,
58 completed_at = $3,
59 duration_ms = $4,
60 content = jsonb_set(content, '{tool_result}', $5)
61 WHERE step_id = $1"#,
62 step_id_str,
63 status_str,
64 completed_at,
65 duration_ms,
66 result
67 )
68 .execute(&*self.write_pool)
69 .await
70 .map_err(|e| {
71 RepositoryError::Internal(format!(
72 "Failed to complete execution step: {step_id}: {e}"
73 ))
74 })?;
75 } else {
76 sqlx::query!(
77 r#"UPDATE task_execution_steps SET
78 status = $2,
79 completed_at = $3,
80 duration_ms = $4
81 WHERE step_id = $1"#,
82 step_id_str,
83 status_str,
84 completed_at,
85 duration_ms
86 )
87 .execute(&*self.write_pool)
88 .await
89 .map_err(|e| {
90 RepositoryError::Internal(format!(
91 "Failed to complete execution step: {step_id}: {e}"
92 ))
93 })?;
94 }
95
96 Ok(())
97 }
98
99 pub async fn fail_step(
100 &self,
101 step_id: &StepId,
102 started_at: DateTime<Utc>,
103 error_message: &str,
104 ) -> Result<(), RepositoryError> {
105 let completed_at = Utc::now();
106 let duration_ms = (completed_at - started_at).num_milliseconds() as i32;
107 let step_id_str = step_id.as_str();
108 let status_str = StepStatus::Failed.to_string();
109
110 sqlx::query!(
111 r#"UPDATE task_execution_steps SET
112 status = $2,
113 completed_at = $3,
114 duration_ms = $4,
115 error_message = $5
116 WHERE step_id = $1"#,
117 step_id_str,
118 status_str,
119 completed_at,
120 duration_ms,
121 error_message
122 )
123 .execute(&*self.write_pool)
124 .await
125 .map_err(|e| {
126 RepositoryError::Internal(format!("Failed to fail execution step: {step_id}: {e}"))
127 })?;
128
129 Ok(())
130 }
131
132 pub async fn fail_in_progress_steps_for_task(
133 &self,
134 task_id: &TaskId,
135 error_message: &str,
136 ) -> Result<u64, RepositoryError> {
137 let completed_at = Utc::now();
138 let in_progress_str = StepStatus::InProgress.to_string();
139 let failed_str = StepStatus::Failed.to_string();
140 let task_id_str = task_id.as_str();
141
142 let result = sqlx::query!(
143 r#"UPDATE task_execution_steps SET
144 status = $3,
145 completed_at = $4,
146 error_message = $5
147 WHERE task_id = $1 AND status = $2"#,
148 task_id_str,
149 in_progress_str,
150 failed_str,
151 completed_at,
152 error_message
153 )
154 .execute(&*self.write_pool)
155 .await
156 .map_err(|e| {
157 RepositoryError::Internal(format!(
158 "Failed to fail in-progress steps for task: {task_id}: {e}"
159 ))
160 })?;
161
162 Ok(result.rows_affected())
163 }
164
165 pub async fn complete_planning_step(
166 &self,
167 step_id: &StepId,
168 started_at: DateTime<Utc>,
169 reasoning: Option<String>,
170 planned_tools: Option<Vec<PlannedTool>>,
171 ) -> Result<ExecutionStep, RepositoryError> {
172 let completed_at = Utc::now();
173 let duration_ms = (completed_at - started_at).num_milliseconds() as i32;
174 let step_id_str = step_id.as_str();
175 let status_str = StepStatus::Completed.to_string();
176
177 let content = StepContent::planning(reasoning, planned_tools);
178 let content_json = serde_json::to_value(&content).map_err(|e| {
179 RepositoryError::Internal(format!("Failed to serialize planning content: {e}"))
180 })?;
181
182 let row = sqlx::query!(
183 r#"UPDATE task_execution_steps SET
184 status = $2,
185 completed_at = $3,
186 duration_ms = $4,
187 content = $5
188 WHERE step_id = $1
189 RETURNING step_id, task_id as "task_id!: TaskId", status, content,
190 started_at as "started_at!", completed_at, duration_ms, error_message"#,
191 step_id_str,
192 status_str,
193 completed_at,
194 duration_ms,
195 content_json
196 )
197 .fetch_one(&*self.write_pool)
198 .await
199 .map_err(|e| {
200 RepositoryError::Internal(format!("Failed to complete planning step: {step_id}: {e}"))
201 })?;
202
203 parse_step(ParseStepParams {
204 step_id: row.step_id,
205 task_id: row.task_id,
206 status: row.status,
207 content: row.content,
208 started_at: row.started_at,
209 completed_at: row.completed_at,
210 duration_ms: row.duration_ms,
211 error_message: row.error_message,
212 })
213 }
214}