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