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 #[serde(skip_serializing_if = "Option::is_none")]
67 pub project_id: Option<uuid::Uuid>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub started_at: Option<DateTime<Utc>>,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub completed_at: Option<DateTime<Utc>>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub error: Option<String>,
77 #[serde(default)]
79 pub steps_completed: usize,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub steps_total: Option<usize>,
83 #[serde(default, skip_serializing_if = "Vec::is_empty")]
85 pub tool_calls: Vec<ToolCallRecord>,
86 #[serde(default)]
88 pub tokens_input: u64,
89 #[serde(default)]
91 pub tokens_output: u64,
92 #[serde(default)]
94 pub cost_usd: f64,
95 #[serde(default, skip_serializing_if = "String::is_empty")]
97 pub model_id: String,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub session_id: Option<String>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ToolCallRecord {
106 pub tool: String,
108 pub input: String,
110 pub output: String,
112 pub duration_ms: u64,
114 #[serde(default)]
116 pub is_error: bool,
117 #[serde(default, skip_serializing_if = "String::is_empty")]
119 pub tool_call_id: String,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub timestamp: Option<DateTime<Utc>>,
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_agent_status_display_all_variants() {
131 assert_eq!(AgentStatus::Starting.to_string(), "starting");
132 assert_eq!(AgentStatus::Running.to_string(), "running");
133 assert_eq!(AgentStatus::Idle.to_string(), "idle");
134 assert_eq!(AgentStatus::Stopped.to_string(), "stopped");
135 assert_eq!(AgentStatus::Failed.to_string(), "failed");
136 }
137
138 #[test]
139 fn test_agent_status_equality() {
140 assert_eq!(AgentStatus::Running, AgentStatus::Running);
141 assert_ne!(AgentStatus::Running, AgentStatus::Idle);
142 }
143
144 #[test]
145 fn test_agent_status_serialization_roundtrip() {
146 for status in [
147 AgentStatus::Starting,
148 AgentStatus::Running,
149 AgentStatus::Idle,
150 AgentStatus::Stopped,
151 AgentStatus::Failed,
152 ] {
153 let json = serde_json::to_string(&status).unwrap();
154 let restored: AgentStatus = serde_json::from_str(&json).unwrap();
155 assert_eq!(status, restored);
156 }
157 }
158
159 #[test]
160 fn test_agent_info_construction() {
161 let id = AgentId::new_v4();
162
163 let now = Utc::now();
164
165 let info = AgentInfo {
166 id,
167 name: "test-agent".to_string(),
168 status: AgentStatus::Running,
169 created_at: now,
170 project_id: None,
171 started_at: None,
172 completed_at: None,
173 error: None,
174 steps_completed: 0,
175 steps_total: None,
176 tool_calls: vec![],
177 tokens_input: 0,
178 tokens_output: 0,
179 cost_usd: 0.0,
180 model_id: String::new(),
181 session_id: None,
182 };
183
184 assert_eq!(info.id, id);
185 assert_eq!(info.name, "test-agent");
186 assert_eq!(info.status, AgentStatus::Running);
187 assert_eq!(info.created_at, now);
188 }
189
190 #[test]
191 fn test_agent_info_serialization_roundtrip() {
192 let info = AgentInfo {
193 id: AgentId::new_v4(),
194 name: "serializer".to_string(),
195 status: AgentStatus::Idle,
196 created_at: Utc::now(),
197 project_id: None,
198 started_at: None,
199 completed_at: None,
200 error: None,
201 steps_completed: 3,
202 steps_total: Some(5),
203 tool_calls: vec![],
204 tokens_input: 100,
205 tokens_output: 50,
206 cost_usd: 0.002,
207 model_id: String::new(),
208 session_id: None,
209 };
210
211 let json = serde_json::to_string(&info).unwrap();
212 let restored: AgentInfo = serde_json::from_str(&json).unwrap();
213 assert_eq!(restored.id, info.id);
214 assert_eq!(restored.name, info.name);
215 assert_eq!(restored.status, info.status);
216 assert_eq!(restored.steps_completed, 3);
217 }
218
219 #[test]
220 fn test_agent_status_copy() {
221 let status = AgentStatus::Running;
222 let copied = status; assert_eq!(status, copied); }
225}