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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
42pub enum Priority {
43 Low = 0,
45 #[default]
47 Normal = 1,
48 High = 2,
50 Critical = 3,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct AgentInfo {
57 pub id: AgentId,
59 pub name: String,
61 pub status: AgentStatus,
63 pub created_at: DateTime<Utc>,
65 pub seed_id: Option<uuid::Uuid>,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub project_id: Option<uuid::Uuid>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub started_at: Option<DateTime<Utc>>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub completed_at: Option<DateTime<Utc>>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub error: Option<String>,
79 #[serde(default)]
81 pub steps_completed: usize,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub steps_total: Option<usize>,
85 #[serde(default, skip_serializing_if = "Vec::is_empty")]
87 pub tool_calls: Vec<ToolCallRecord>,
88 #[serde(default)]
90 pub tokens_input: u64,
91 #[serde(default)]
93 pub tokens_output: u64,
94 #[serde(default)]
96 pub cost_usd: f64,
97 #[serde(default, skip_serializing_if = "String::is_empty")]
99 pub model_id: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub session_id: Option<String>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct ToolCallRecord {
108 pub tool: String,
110 pub input: String,
112 pub output: String,
114 pub duration_ms: u64,
116 #[serde(default)]
118 pub is_error: bool,
119 #[serde(default, skip_serializing_if = "String::is_empty")]
121 pub tool_call_id: String,
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub timestamp: Option<DateTime<Utc>>,
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_agent_status_display_all_variants() {
133 assert_eq!(AgentStatus::Starting.to_string(), "starting");
134 assert_eq!(AgentStatus::Running.to_string(), "running");
135 assert_eq!(AgentStatus::Idle.to_string(), "idle");
136 assert_eq!(AgentStatus::Stopped.to_string(), "stopped");
137 assert_eq!(AgentStatus::Failed.to_string(), "failed");
138 }
139
140 #[test]
141 fn test_agent_status_equality() {
142 assert_eq!(AgentStatus::Running, AgentStatus::Running);
143 assert_ne!(AgentStatus::Running, AgentStatus::Idle);
144 }
145
146 #[test]
147 fn test_agent_status_serialization_roundtrip() {
148 for status in [
149 AgentStatus::Starting,
150 AgentStatus::Running,
151 AgentStatus::Idle,
152 AgentStatus::Stopped,
153 AgentStatus::Failed,
154 ] {
155 let json = serde_json::to_string(&status).unwrap();
156 let restored: AgentStatus = serde_json::from_str(&json).unwrap();
157 assert_eq!(status, restored);
158 }
159 }
160
161 #[test]
162 fn test_agent_info_construction() {
163 let id = AgentId::new_v4();
164 let seed_id = uuid::Uuid::new_v4();
165 let now = Utc::now();
166
167 let info = AgentInfo {
168 id,
169 name: "test-agent".to_string(),
170 status: AgentStatus::Running,
171 created_at: now,
172 seed_id: Some(seed_id),
173 project_id: None,
174 started_at: None,
175 completed_at: None,
176 error: None,
177 steps_completed: 0,
178 steps_total: None,
179 tool_calls: vec![],
180 tokens_input: 0,
181 tokens_output: 0,
182 cost_usd: 0.0,
183 model_id: String::new(),
184 session_id: None,
185 };
186
187 assert_eq!(info.id, id);
188 assert_eq!(info.name, "test-agent");
189 assert_eq!(info.status, AgentStatus::Running);
190 assert_eq!(info.created_at, now);
191 assert_eq!(info.seed_id, Some(seed_id));
192 }
193
194 #[test]
195 fn test_agent_info_serialization_roundtrip() {
196 let info = AgentInfo {
197 id: AgentId::new_v4(),
198 name: "serializer".to_string(),
199 status: AgentStatus::Idle,
200 created_at: Utc::now(),
201 seed_id: None,
202 project_id: None,
203 started_at: None,
204 completed_at: None,
205 error: None,
206 steps_completed: 3,
207 steps_total: Some(5),
208 tool_calls: vec![],
209 tokens_input: 100,
210 tokens_output: 50,
211 cost_usd: 0.002,
212 model_id: String::new(),
213 session_id: None,
214 };
215
216 let json = serde_json::to_string(&info).unwrap();
217 let restored: AgentInfo = serde_json::from_str(&json).unwrap();
218 assert_eq!(restored.id, info.id);
219 assert_eq!(restored.name, info.name);
220 assert_eq!(restored.status, info.status);
221 assert_eq!(restored.seed_id, None);
222 assert_eq!(restored.steps_completed, 3);
223 }
224
225 #[test]
226 fn test_agent_status_copy() {
227 let status = AgentStatus::Running;
228 let copied = status; assert_eq!(status, copied); }
231}