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,
84 ) -> Self {
85 Self::new(
86 task_id,
87 StepContent::tool_execution(tool_name, tool_arguments),
88 )
89 }
90
91 #[must_use]
92 pub fn completion(task_id: TaskId) -> Self {
93 Self::new(task_id, StepContent::completion())
94 }
95
96 #[must_use]
97 pub const fn step_type(&self) -> StepType {
98 self.content.step_type()
99 }
100
101 #[must_use]
102 pub fn title(&self) -> String {
103 self.content.title()
104 }
105
106 #[must_use]
107 pub fn tool_name(&self) -> Option<&str> {
108 self.content.tool_name()
109 }
110
111 #[must_use]
112 pub const fn tool_arguments(&self) -> Option<&serde_json::Value> {
113 self.content.tool_arguments()
114 }
115
116 #[must_use]
117 pub const fn tool_result(&self) -> Option<&serde_json::Value> {
118 self.content.tool_result()
119 }
120
121 #[must_use]
122 pub fn reasoning(&self) -> Option<&str> {
123 self.content.reasoning()
124 }
125
126 pub fn complete(&mut self, result: Option<serde_json::Value>) {
127 let now = Utc::now();
128 self.status = StepStatus::Completed;
129 self.completed_at = Some(now);
130 let duration = (now - self.started_at).num_milliseconds();
131 self.duration_ms = Some(i32::try_from(duration).unwrap_or(i32::MAX));
132 if let Some(r) = result {
133 self.content = self.content.clone().with_tool_result(r);
134 }
135 }
136
137 pub fn fail(&mut self, error: String) {
138 let now = Utc::now();
139 self.status = StepStatus::Failed;
140 self.completed_at = Some(now);
141 let duration = (now - self.started_at).num_milliseconds();
142 self.duration_ms = Some(i32::try_from(duration).unwrap_or(i32::MAX));
143 self.error_message = Some(error);
144 }
145}
146
147#[derive(Debug, Clone)]
148pub struct TrackedStep {
149 pub step_id: StepId,
150 pub started_at: DateTime<Utc>,
151}
152
153pub type StepDetail = StepContent;