systemprompt_models/execution/step/
mod.rs1mod content;
8mod enums;
9
10pub use content::{PlannedTool, StepContent};
11pub use enums::{StepId, StepStatus, StepType};
12
13use chrono::{DateTime, Utc};
14use serde::{Deserialize, Serialize};
15use systemprompt_identifiers::{SkillId, TaskId};
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18#[serde(rename_all = "camelCase")]
19pub struct ExecutionStep {
20 pub step_id: StepId,
21 pub task_id: TaskId,
22 pub status: StepStatus,
23 pub started_at: DateTime<Utc>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub completed_at: Option<DateTime<Utc>>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub duration_ms: Option<i32>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub error_message: Option<String>,
30 pub content: StepContent,
31}
32
33impl ExecutionStep {
34 #[must_use]
35 pub fn new(task_id: TaskId, content: StepContent) -> Self {
36 let status = if content.is_instant() {
37 StepStatus::Completed
38 } else {
39 StepStatus::InProgress
40 };
41 let now = Utc::now();
42 let (completed_at, duration_ms) = if content.is_instant() {
43 (Some(now), Some(0))
44 } else {
45 (None, None)
46 };
47
48 Self {
49 step_id: StepId::new(),
50 task_id,
51 status,
52 started_at: now,
53 completed_at,
54 duration_ms,
55 error_message: None,
56 content,
57 }
58 }
59
60 #[must_use]
61 pub fn understanding(task_id: TaskId) -> Self {
62 Self::new(task_id, StepContent::understanding())
63 }
64
65 #[must_use]
66 pub fn planning(
67 task_id: TaskId,
68 reasoning: Option<String>,
69 planned_tools: Option<Vec<PlannedTool>>,
70 ) -> Self {
71 Self::new(task_id, StepContent::planning(reasoning, planned_tools))
72 }
73
74 #[must_use]
75 pub fn skill_usage(task_id: TaskId, skill_id: SkillId, skill_name: impl Into<String>) -> Self {
76 Self::new(task_id, StepContent::skill_usage(skill_id, skill_name))
77 }
78
79 #[must_use]
80 pub fn tool_execution(
81 task_id: TaskId,
82 tool_name: impl Into<String>,
83 tool_arguments: serde_json::Value,
85 ) -> Self {
86 Self::new(
87 task_id,
88 StepContent::tool_execution(tool_name, tool_arguments),
89 )
90 }
91
92 #[must_use]
93 pub fn completion(task_id: TaskId) -> Self {
94 Self::new(task_id, StepContent::completion())
95 }
96
97 #[must_use]
98 pub const fn step_type(&self) -> StepType {
99 self.content.step_type()
100 }
101
102 #[must_use]
103 pub fn title(&self) -> String {
104 self.content.title()
105 }
106
107 #[must_use]
108 pub fn tool_name(&self) -> Option<&str> {
109 self.content.tool_name()
110 }
111
112 #[must_use]
113 pub const fn tool_arguments(&self) -> Option<&serde_json::Value> {
115 self.content.tool_arguments()
116 }
117
118 #[must_use]
119 pub const fn tool_result(&self) -> Option<&serde_json::Value> {
121 self.content.tool_result()
122 }
123
124 #[must_use]
125 pub fn reasoning(&self) -> Option<&str> {
126 self.content.reasoning()
127 }
128
129 pub fn complete(&mut self, result: Option<serde_json::Value>) {
131 let now = Utc::now();
132 self.status = StepStatus::Completed;
133 self.completed_at = Some(now);
134 let duration = (now - self.started_at).num_milliseconds();
135 self.duration_ms = Some(i32::try_from(duration).unwrap_or(i32::MAX));
136 if let Some(r) = result {
137 self.content = self.content.clone().with_tool_result(r);
138 }
139 }
140
141 pub fn fail(&mut self, error: String) {
142 let now = Utc::now();
143 self.status = StepStatus::Failed;
144 self.completed_at = Some(now);
145 let duration = (now - self.started_at).num_milliseconds();
146 self.duration_ms = Some(i32::try_from(duration).unwrap_or(i32::MAX));
147 self.error_message = Some(error);
148 }
149}
150
151#[derive(Debug, Clone)]
152pub struct TrackedStep {
153 pub step_id: StepId,
154 pub started_at: DateTime<Utc>,
155}
156
157pub type StepDetail = StepContent;