Skip to main content

systemprompt_models/execution/step/
mod.rs

1//! Execution step model — a single unit of an agent run with status,
2//! timing, and per-kind content payload.
3
4mod 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;