1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8pub type AgentId = uuid::Uuid;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum AgentStatus {
14 Starting,
16 Running,
18 Idle,
20 Stopped,
22 Failed,
24 Completed,
26}
27
28impl std::fmt::Display for AgentStatus {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 AgentStatus::Starting => write!(f, "starting"),
32 AgentStatus::Running => write!(f, "running"),
33 AgentStatus::Idle => write!(f, "idle"),
34 AgentStatus::Stopped => write!(f, "stopped"),
35 AgentStatus::Failed => write!(f, "failed"),
36 AgentStatus::Completed => write!(f, "completed"),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct AgentInfo {
44 pub id: AgentId,
46 pub name: String,
48 pub status: AgentStatus,
50 pub created_at: DateTime<Utc>,
52 pub seed_id: Option<uuid::Uuid>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub project_id: Option<uuid::Uuid>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub started_at: Option<DateTime<Utc>>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub completed_at: Option<DateTime<Utc>>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub error: Option<String>,
66 #[serde(default)]
68 pub steps_completed: usize,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub steps_total: Option<usize>,
72 #[serde(default, skip_serializing_if = "Vec::is_empty")]
74 pub tool_calls: Vec<ToolCallRecord>,
75 #[serde(default)]
77 pub tokens_input: u64,
78 #[serde(default)]
80 pub tokens_output: u64,
81 #[serde(default)]
83 pub cost_usd: f64,
84 #[serde(default, skip_serializing_if = "String::is_empty")]
86 pub model_id: String,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub session_id: Option<String>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ToolCallRecord {
95 pub tool: String,
97 pub input: String,
99 pub output: String,
101 pub duration_ms: u64,
103 #[serde(default)]
105 pub is_error: bool,
106 #[serde(default, skip_serializing_if = "String::is_empty")]
108 pub tool_call_id: String,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub timestamp: Option<DateTime<Utc>>,
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_agent_status_display_all_variants() {
120 assert_eq!(AgentStatus::Starting.to_string(), "starting");
121 assert_eq!(AgentStatus::Running.to_string(), "running");
122 assert_eq!(AgentStatus::Idle.to_string(), "idle");
123 assert_eq!(AgentStatus::Stopped.to_string(), "stopped");
124 assert_eq!(AgentStatus::Failed.to_string(), "failed");
125 }
126
127 #[test]
128 fn test_agent_status_equality() {
129 assert_eq!(AgentStatus::Running, AgentStatus::Running);
130 assert_ne!(AgentStatus::Running, AgentStatus::Idle);
131 }
132
133 #[test]
134 fn test_agent_status_serialization_roundtrip() {
135 for status in [
136 AgentStatus::Starting,
137 AgentStatus::Running,
138 AgentStatus::Idle,
139 AgentStatus::Stopped,
140 AgentStatus::Failed,
141 ] {
142 let json = serde_json::to_string(&status).unwrap();
143 let restored: AgentStatus = serde_json::from_str(&json).unwrap();
144 assert_eq!(status, restored);
145 }
146 }
147
148 #[test]
149 fn test_agent_info_construction() {
150 let id = AgentId::new_v4();
151 let seed_id = uuid::Uuid::new_v4();
152 let now = Utc::now();
153
154 let info = AgentInfo {
155 id,
156 name: "test-agent".to_string(),
157 status: AgentStatus::Running,
158 created_at: now,
159 seed_id: Some(seed_id),
160 project_id: None,
161 started_at: None,
162 completed_at: None,
163 error: None,
164 steps_completed: 0,
165 steps_total: None,
166 tool_calls: vec![],
167 tokens_input: 0,
168 tokens_output: 0,
169 cost_usd: 0.0,
170 model_id: String::new(),
171 session_id: None,
172 };
173
174 assert_eq!(info.id, id);
175 assert_eq!(info.name, "test-agent");
176 assert_eq!(info.status, AgentStatus::Running);
177 assert_eq!(info.created_at, now);
178 assert_eq!(info.seed_id, Some(seed_id));
179 }
180
181 #[test]
182 fn test_agent_info_serialization_roundtrip() {
183 let info = AgentInfo {
184 id: AgentId::new_v4(),
185 name: "serializer".to_string(),
186 status: AgentStatus::Idle,
187 created_at: Utc::now(),
188 seed_id: None,
189 project_id: None,
190 started_at: None,
191 completed_at: None,
192 error: None,
193 steps_completed: 3,
194 steps_total: Some(5),
195 tool_calls: vec![],
196 tokens_input: 100,
197 tokens_output: 50,
198 cost_usd: 0.002,
199 model_id: String::new(),
200 session_id: None,
201 };
202
203 let json = serde_json::to_string(&info).unwrap();
204 let restored: AgentInfo = serde_json::from_str(&json).unwrap();
205 assert_eq!(restored.id, info.id);
206 assert_eq!(restored.name, info.name);
207 assert_eq!(restored.status, info.status);
208 assert_eq!(restored.seed_id, None);
209 assert_eq!(restored.steps_completed, 3);
210 }
211
212 #[test]
213 fn test_agent_status_copy() {
214 let status = AgentStatus::Running;
215 let copied = status; assert_eq!(status, copied); }
218}