starweaver_runtime/
run.rs1use chrono::Utc;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use starweaver_core::{ConversationId, Metadata, RunId, TaskId};
7use starweaver_model::{ModelMessage, ModelResponse, ToolCallPart, ToolReturnPart};
8use starweaver_usage::Usage;
9
10#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
12#[serde(rename_all = "snake_case")]
13pub enum RunStatus {
14 Starting,
16 Running,
18 Waiting,
20 Completed,
22 Failed,
24 Cancelled,
26}
27
28#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
30pub struct AgentRunState {
31 pub run_id: RunId,
33 pub conversation_id: ConversationId,
35 pub message_history: Vec<ModelMessage>,
37 pub usage: Usage,
39 pub run_step: usize,
41 pub status: RunStatus,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub output: Option<String>,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub structured_output: Option<Value>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub latest_response: Option<ModelResponse>,
52 #[serde(default, skip_serializing_if = "Vec::is_empty")]
54 pub pending_tool_calls: Vec<ToolCallPart>,
55 #[serde(default, skip_serializing_if = "Vec::is_empty")]
57 pub pending_tool_returns: Vec<ToolReturnPart>,
58 #[serde(default, skip_serializing_if = "Vec::is_empty")]
60 pub pending_approval_tool_returns: Vec<ToolReturnPart>,
61 #[serde(default, skip_serializing_if = "Vec::is_empty")]
63 pub deferred_tool_returns: Vec<ToolReturnPart>,
64 #[serde(default, skip_serializing_if = "Vec::is_empty")]
66 pub idle_messages: Vec<String>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub parent_run_id: Option<RunId>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub parent_task_id: Option<TaskId>,
73 #[serde(default, skip_serializing_if = "Metadata::is_empty")]
75 pub metadata: Metadata,
76}
77
78impl AgentRunState {
79 #[must_use]
81 pub fn new(run_id: RunId, conversation_id: ConversationId) -> Self {
82 Self {
83 run_id,
84 conversation_id,
85 message_history: Vec::new(),
86 usage: Usage::default(),
87 run_step: 0,
88 status: RunStatus::Starting,
89 output: None,
90 structured_output: None,
91 latest_response: None,
92 pending_tool_calls: Vec::new(),
93 pending_tool_returns: Vec::new(),
94 pending_approval_tool_returns: Vec::new(),
95 deferred_tool_returns: Vec::new(),
96 idle_messages: Vec::new(),
97 parent_run_id: None,
98 parent_task_id: None,
99 metadata: Metadata::default(),
100 }
101 }
102
103 pub fn apply_model_response(&mut self, mut response: ModelResponse) {
105 response.run_id.get_or_insert_with(|| self.run_id.clone());
106 response
107 .conversation_id
108 .get_or_insert_with(|| self.conversation_id.clone());
109 response.timestamp.get_or_insert_with(Utc::now);
110 self.usage.add_assign(&response.usage);
111 self.message_history
112 .push(ModelMessage::Response(response.clone()));
113 self.latest_response = Some(response);
114 }
115
116 pub fn replace_latest_response(&mut self, response: ModelResponse) {
118 if let Some(ModelMessage::Response(history_response)) = self.message_history.last_mut() {
119 *history_response = response.clone();
120 }
121 self.latest_response = Some(response);
122 }
123
124 #[must_use]
126 pub const fn has_pending_hitl(&self) -> bool {
127 !self.pending_approval_tool_returns.is_empty() || !self.deferred_tool_returns.is_empty()
128 }
129
130 #[must_use]
132 pub fn pending_approvals(&self) -> &[ToolReturnPart] {
133 &self.pending_approval_tool_returns
134 }
135
136 #[must_use]
138 pub fn pending_deferred_tools(&self) -> &[ToolReturnPart] {
139 &self.deferred_tool_returns
140 }
141
142 pub fn pending_hitl_tool_returns(&self) -> impl Iterator<Item = &ToolReturnPart> {
144 self.pending_approval_tool_returns
145 .iter()
146 .chain(self.deferred_tool_returns.iter())
147 }
148}
149
150#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
152pub struct AgentRunResult {
153 pub output: String,
155 pub state: AgentRunState,
157}
158
159impl AgentRunResult {
160 #[must_use]
162 pub const fn has_pending_hitl(&self) -> bool {
163 self.state.has_pending_hitl()
164 }
165
166 #[must_use]
168 pub fn pending_approvals(&self) -> &[ToolReturnPart] {
169 self.state.pending_approvals()
170 }
171
172 #[must_use]
174 pub fn pending_deferred_tools(&self) -> &[ToolReturnPart] {
175 self.state.pending_deferred_tools()
176 }
177}