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