systemprompt_agent/services/agent_orchestration/
database.rs1use crate::repository::agent_service::AgentServiceRepository;
11use crate::services::agent_orchestration::{
12 AgentStatus, OrchestrationError, OrchestrationResult, process,
13};
14use crate::services::registry::AgentRegistry;
15use systemprompt_models::services::AgentConfig;
16
17#[derive(Debug)]
18pub struct AgentDatabaseService {
19 pub repository: AgentServiceRepository,
20 pub registry: AgentRegistry,
21}
22
23impl AgentDatabaseService {
24 pub fn new(repository: AgentServiceRepository) -> OrchestrationResult<Self> {
25 let registry = AgentRegistry::new().map_err(|e| {
26 OrchestrationError::Database(format!("Failed to load agent registry: {e}"))
27 })?;
28
29 Ok(Self {
30 repository,
31 registry,
32 })
33 }
34
35 pub async fn register_agent(
36 &self,
37 name: &str,
38 pid: u32,
39 port: u16,
40 ) -> OrchestrationResult<String> {
41 self.repository
42 .register_agent(name, pid, port)
43 .await
44 .map_err(|e| OrchestrationError::Database(e.to_string()))
45 }
46
47 pub async fn get_status(&self, agent_name: &str) -> OrchestrationResult<AgentStatus> {
48 let row = self
49 .repository
50 .get_agent_status(agent_name)
51 .await
52 .map_err(|e| OrchestrationError::Database(e.to_string()))?;
53
54 match row {
55 Some(r) => match (r.pid, r.status.as_str()) {
56 (Some(pid), "running") => {
57 let pid = pid as u32;
58 if process::process_exists(pid) {
59 Ok(AgentStatus::Running {
60 pid,
61 port: r.port as u16,
62 })
63 } else {
64 self.mark_failed(agent_name).await?;
65 Ok(AgentStatus::Failed {
66 reason: "Process died unexpectedly".to_owned(),
67 last_attempt: None,
68 retry_count: 0,
69 })
70 }
71 },
72 (_, "starting") => Ok(AgentStatus::Failed {
73 reason: "Agent is starting".to_owned(),
74 last_attempt: None,
75 retry_count: 0,
76 }),
77 (_, "failed" | "crashed" | "stopped") => {
78 let error_msg = self
79 .get_error_message(agent_name)
80 .await
81 .unwrap_or_else(|_| "Unknown failure".to_owned());
82 Ok(AgentStatus::Failed {
83 reason: error_msg,
84 last_attempt: None,
85 retry_count: 0,
86 })
87 },
88 _ => {
89 self.mark_failed(agent_name).await?;
90 Ok(AgentStatus::Failed {
91 reason: "Invalid database state".to_owned(),
92 last_attempt: None,
93 retry_count: 0,
94 })
95 },
96 },
97 None => Ok(AgentStatus::Failed {
98 reason: "No service record found".to_owned(),
99 last_attempt: None,
100 retry_count: 0,
101 }),
102 }
103 }
104
105 pub async fn mark_failed(&self, agent_name: &str) -> OrchestrationResult<()> {
106 self.repository
107 .mark_error(agent_name)
108 .await
109 .map_err(|e| OrchestrationError::Database(e.to_string()))?;
110
111 self.repository
112 .mark_crashed(agent_name)
113 .await
114 .map_err(|e| OrchestrationError::Database(e.to_string()))
115 }
116
117 pub async fn mark_crashed(&self, agent_name: &str) -> OrchestrationResult<()> {
118 self.mark_failed(agent_name).await
119 }
120
121 pub async fn get_error_message(&self, agent_name: &str) -> OrchestrationResult<String> {
122 let row = self
123 .repository
124 .get_agent_status(agent_name)
125 .await
126 .map_err(|e| OrchestrationError::Database(e.to_string()))?;
127
128 match row {
129 Some(r) => Ok(format!("Status: {}", r.status)),
130 None => Ok("No service record".to_owned()),
131 }
132 }
133
134 pub async fn mark_error(&self, agent_name: &str) -> OrchestrationResult<()> {
135 self.repository
136 .mark_error(agent_name)
137 .await
138 .map_err(|e| OrchestrationError::Database(e.to_string()))
139 }
140
141 pub async fn list_running_agents(&self) -> OrchestrationResult<Vec<String>> {
142 let rows = self
143 .repository
144 .list_running_agents()
145 .await
146 .map_err(|e| OrchestrationError::Database(e.to_string()))?;
147
148 Ok(rows.into_iter().map(|row| row.name).collect())
149 }
150
151 pub async fn list_all_agents(&self) -> OrchestrationResult<Vec<(String, AgentStatus)>> {
152 let agent_configs = self.registry.list_agents().await.map_err(|e| {
153 OrchestrationError::Database(format!("Failed to list agents from config: {e}"))
154 })?;
155
156 let mut agents = Vec::new();
157
158 for agent_config in agent_configs {
159 let agent_name = &agent_config.name;
160
161 let status = self.get_status(agent_name).await?;
162
163 agents.push((agent_name.clone(), status));
164 }
165
166 Ok(agents)
167 }
168
169 pub async fn agent_exists(&self, agent_name: &str) -> OrchestrationResult<bool> {
170 self.registry
171 .get_agent(agent_name)
172 .await
173 .map(|_| true)
174 .or_else(|_| Ok(false))
175 }
176
177 pub async fn get_agent_config(&self, agent_name: &str) -> OrchestrationResult<AgentConfig> {
178 let agent_config = self.registry.get_agent(agent_name).await.map_err(|e| {
179 OrchestrationError::AgentNotFound(format!(
180 "Agent {} not found in config: {}",
181 agent_name, e
182 ))
183 })?;
184
185 Ok(agent_config)
186 }
187
188 pub async fn cleanup_orphaned_services(&self) -> OrchestrationResult<u64> {
189 let rows = self
190 .repository
191 .list_running_agent_pids()
192 .await
193 .map_err(|e| OrchestrationError::Database(e.to_string()))?;
194
195 let mut cleaned = 0u64;
196
197 for row in rows {
198 let pid = row.pid as u32;
199 if !process::process_exists(pid) {
200 self.mark_crashed(&row.name).await?;
201 cleaned += 1;
202 }
203 }
204
205 Ok(cleaned)
206 }
207
208 pub async fn remove_agent_service(&self, agent_name: &str) -> OrchestrationResult<()> {
209 self.repository
210 .remove_agent_service(agent_name)
211 .await
212 .map_err(|e| OrchestrationError::Database(e.to_string()))
213 }
214
215 pub async fn update_health_status(
216 &self,
217 agent_name: &str,
218 health_status: &str,
219 ) -> OrchestrationResult<()> {
220 self.repository
221 .update_health_status(agent_name, health_status)
222 .await
223 .map_err(|e| OrchestrationError::Database(e.to_string()))
224 }
225
226 pub async fn update_agent_running(
227 &self,
228 agent_name: &str,
229 pid: u32,
230 port: u16,
231 ) -> OrchestrationResult<String> {
232 self.repository
233 .register_agent(agent_name, pid, port)
234 .await
235 .map_err(|e| OrchestrationError::Database(e.to_string()))
236 }
237
238 pub async fn update_agent_stopped(&self, agent_name: &str) -> OrchestrationResult<()> {
239 self.repository
240 .mark_stopped(agent_name)
241 .await
242 .map_err(|e| OrchestrationError::Database(e.to_string()))
243 }
244
245 pub async fn register_agent_starting(
246 &self,
247 agent_name: &str,
248 pid: u32,
249 port: u16,
250 ) -> OrchestrationResult<String> {
251 self.repository
252 .register_agent_starting(agent_name, pid, port)
253 .await
254 .map_err(|e| OrchestrationError::Database(e.to_string()))
255 }
256
257 pub async fn mark_running(&self, agent_name: &str) -> OrchestrationResult<()> {
258 self.repository
259 .mark_running(agent_name)
260 .await
261 .map_err(|e| OrchestrationError::Database(e.to_string()))
262 }
263
264 pub async fn get_unresponsive_agents(&self) -> OrchestrationResult<Vec<(String, Option<u32>)>> {
265 use crate::services::agent_orchestration::monitor::check_a2a_agent_health;
266
267 let agents = self.list_all_agents().await?;
268
269 let mut unresponsive = Vec::new();
270 for (agent_name, status) in agents {
271 if let AgentStatus::Running { pid, port, .. } = status {
272 let is_healthy = check_a2a_agent_health(port, 10).await.unwrap_or(false);
273
274 if !is_healthy {
275 unresponsive.push((agent_name, Some(pid)));
276 }
277 }
278 }
279
280 Ok(unresponsive)
281 }
282}