1use chrono::{DateTime, Utc};
8use serde_json::Value;
9
10use behest_provider::{FinishReason, ModelName, ProviderId, TokenUsage};
11
12use super::event::AgentEvent;
13use super::run::{RunId, RunRecord, RunStatus};
14use super::store::RunEventRecord;
15
16#[derive(Debug, Clone)]
24pub struct RunState {
25 pub run_id: RunId,
27 pub session_id: uuid::Uuid,
29 pub status: RunStatus,
31 pub provider: ProviderId,
33 pub model: ModelName,
35 pub metadata: Value,
37 pub iteration: usize,
39 pub total_usage: TokenUsage,
41 pub last_finish: Option<FinishReason>,
43 pub last_error: Option<String>,
45 pub event_count: usize,
47 pub created_at: DateTime<Utc>,
49 pub updated_at: DateTime<Utc>,
51}
52
53impl RunState {
54 #[must_use]
61 pub fn create(record: &RunRecord, events: &[RunEventRecord]) -> Self {
62 let updated_at = events.last().map_or(record.updated_at, |e| e.timestamp);
63
64 let mut state = RunState {
65 run_id: record.id,
66 session_id: record.session_id,
67 status: RunStatus::Pending,
68 provider: record.provider.clone(),
69 model: record.model.clone(),
70 metadata: record.metadata.clone(),
71 iteration: 0,
72 total_usage: TokenUsage::new(0, 0),
73 last_finish: None,
74 last_error: None,
75 event_count: events.len(),
76 created_at: record.created_at,
77 updated_at,
78 };
79
80 for event_record in events {
81 state.apply(&event_record.event);
82 }
83
84 state
85 }
86
87 pub fn apply(&mut self, event: &AgentEvent) {
94 match event {
95 AgentEvent::RunStarted(e) => {
96 self.status = RunStatus::SessionLoaded;
97 self.provider = e.provider.clone();
99 self.model = e.model.clone();
100 self.updated_at = e.timestamp;
101 }
102 AgentEvent::ModelStarted(e) => {
103 self.iteration = e.iteration;
104 self.status = RunStatus::CallingModel;
105 self.updated_at = e.timestamp;
106 }
107 AgentEvent::UsageRecorded(e) => {
108 self.total_usage = self.total_usage.merge(e.usage);
109 self.updated_at = e.timestamp;
110 }
111 AgentEvent::CacheMetrics(e) => {
112 self.updated_at = e.timestamp;
113 }
114 AgentEvent::RunCompleted(e) => {
115 self.status = RunStatus::Completed;
116 self.last_finish = Some(e.finish_reason.clone());
117 self.updated_at = e.timestamp;
118 }
119 AgentEvent::RunFailed(e) => {
120 self.status = RunStatus::Failed;
121 self.last_error = Some(e.error.clone());
122 self.updated_at = e.timestamp;
123 }
124 AgentEvent::RunCancelled(e) => {
125 self.status = RunStatus::Cancelled;
126 self.updated_at = e.timestamp;
127 }
128 AgentEvent::ContextBuilt(e) => {
129 self.updated_at = e.timestamp;
130 }
131 AgentEvent::TextDelta(e) => {
132 self.updated_at = e.timestamp;
133 }
134 AgentEvent::ToolCallStarted(e) => {
135 self.updated_at = e.timestamp;
136 }
137 AgentEvent::ToolCallDelta(e) => {
138 self.updated_at = e.timestamp;
139 }
140 AgentEvent::ToolCallCompleted(e) => {
141 self.updated_at = e.timestamp;
142 }
143 AgentEvent::ToolExecutionStarted(e) => {
144 self.updated_at = e.timestamp;
145 }
146 AgentEvent::ToolExecutionFinished(e) => {
147 self.updated_at = e.timestamp;
148 }
149 AgentEvent::AssistantMessageCommitted(e) | AgentEvent::ToolMessageCommitted(e) => {
150 self.updated_at = e.timestamp;
151 }
152 AgentEvent::DoomLoopDetected(e) => {
153 self.updated_at = e.timestamp;
154 }
155 AgentEvent::CompactionCircuitOpened(e) => {
156 self.updated_at = e.timestamp;
157 }
158 }
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::super::event::{
165 ModelStarted as ModelStartedEvent, RunCompleted as RunCompletedEvent,
166 RunFailed as RunFailedEvent, RunStarted as RunStartedEvent,
167 UsageRecorded as UsageRecordedEvent,
168 };
169 use super::*;
170
171 fn make_record() -> RunRecord {
172 RunRecord::new(
173 RunId::new(),
174 uuid::Uuid::new_v4(),
175 ProviderId::new("test-provider"),
176 ModelName::new("test-model"),
177 Value::Null,
178 None,
179 )
180 }
181
182 #[test]
183 fn rebuilds_completed_run_from_events() {
184 let record = make_record();
185 let run_id = record.id;
186 let session_id = record.session_id;
187
188 let events = vec![
189 RunEventRecord::new(
190 0,
191 run_id,
192 AgentEvent::RunStarted(RunStartedEvent {
193 run_id,
194 session_id,
195 provider: record.provider.clone(),
196 model: record.model.clone(),
197 timestamp: Utc::now(),
198 }),
199 ),
200 RunEventRecord::new(
201 1,
202 run_id,
203 AgentEvent::ModelStarted(ModelStartedEvent {
204 run_id,
205 provider: record.provider.clone(),
206 model: record.model.clone(),
207 iteration: 1,
208 timestamp: Utc::now(),
209 }),
210 ),
211 RunEventRecord::new(
212 2,
213 run_id,
214 AgentEvent::UsageRecorded(UsageRecordedEvent {
215 run_id,
216 usage: TokenUsage::new(100, 50),
217 timestamp: Utc::now(),
218 }),
219 ),
220 RunEventRecord::new(
221 3,
222 run_id,
223 AgentEvent::RunCompleted(RunCompletedEvent {
224 run_id,
225 finish_reason: FinishReason::Stop,
226 iterations: 1,
227 timestamp: Utc::now(),
228 }),
229 ),
230 ];
231
232 let state = RunState::create(&record, &events);
233
234 assert_eq!(state.run_id, run_id);
235 assert_eq!(state.session_id, session_id);
236 assert_eq!(state.status, RunStatus::Completed);
237 assert_eq!(state.provider, record.provider);
238 assert_eq!(state.model, record.model);
239 assert_eq!(state.iteration, 1);
240 assert_eq!(state.total_usage, TokenUsage::new(100, 50));
241 assert_eq!(state.last_finish, Some(FinishReason::Stop));
242 assert!(state.last_error.is_none());
243 assert_eq!(state.event_count, 4);
244 }
245
246 #[test]
247 fn rebuilds_failed_run_from_events() {
248 let record = make_record();
249 let run_id = record.id;
250 let session_id = record.session_id;
251
252 let events = vec![
253 RunEventRecord::new(
254 0,
255 run_id,
256 AgentEvent::RunStarted(RunStartedEvent {
257 run_id,
258 session_id,
259 provider: record.provider.clone(),
260 model: record.model.clone(),
261 timestamp: Utc::now(),
262 }),
263 ),
264 RunEventRecord::new(
265 1,
266 run_id,
267 AgentEvent::RunFailed(RunFailedEvent {
268 run_id,
269 error: "something broke".to_string(),
270 timestamp: Utc::now(),
271 }),
272 ),
273 ];
274
275 let state = RunState::create(&record, &events);
276
277 assert_eq!(state.status, RunStatus::Failed);
278 assert_eq!(state.last_error, Some("something broke".to_string()));
279 assert!(state.last_finish.is_none());
280 }
281
282 #[test]
283 fn empty_events_returns_pending() {
284 let record = make_record();
285 let state = RunState::create(&record, &[]);
286
287 assert_eq!(state.status, RunStatus::Pending);
288 assert_eq!(state.iteration, 0);
289 assert_eq!(state.event_count, 0);
290 }
291}