Skip to main content

brainwires_agent_network/client/
agent_ops.rs

1use serde::{Deserialize, Serialize};
2use serde_json::json;
3
4use super::client::AgentNetworkClient;
5use super::error::AgentNetworkClientError;
6
7#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8/// Configuration for spawning an agent.
9pub struct AgentConfig {
10    /// Maximum number of iterations.
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub max_iterations: Option<u32>,
13    /// Whether to enable validation.
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub enable_validation: Option<bool>,
16    /// Build system type (e.g. "typescript", "cargo").
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub build_type: Option<String>,
19    /// Whether to enable MDAP.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub enable_mdap: Option<bool>,
22    /// MDAP preset name.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub mdap_preset: Option<String>,
25}
26
27/// Result of an agent execution.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct AgentResult {
30    /// Agent unique identifier.
31    pub agent_id: String,
32    /// Whether the agent completed successfully.
33    pub success: bool,
34    /// Number of iterations used.
35    pub iterations: u32,
36    /// Summary of the result.
37    pub summary: String,
38    /// Raw output text.
39    pub raw_output: String,
40}
41
42/// Information about a running or completed agent.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct AgentInfo {
45    /// Agent unique identifier.
46    pub agent_id: String,
47    /// Current status.
48    pub status: String,
49    /// Description of the assigned task.
50    pub task_description: String,
51}
52
53impl AgentNetworkClient {
54    /// Spawn a new agent with the given description and config.
55    pub async fn spawn_agent(
56        &mut self,
57        description: &str,
58        working_dir: &str,
59        config: AgentConfig,
60    ) -> Result<String, AgentNetworkClientError> {
61        let mut args = json!({
62            "description": description,
63            "working_directory": working_dir,
64        });
65
66        if let Some(max_iter) = config.max_iterations {
67            args["max_iterations"] = json!(max_iter);
68        }
69        if let Some(enable_val) = config.enable_validation {
70            args["enable_validation"] = json!(enable_val);
71        }
72        if let Some(ref build_type) = config.build_type {
73            args["build_type"] = json!(build_type);
74        }
75        if let Some(enable_mdap) = config.enable_mdap {
76            args["enable_mdap"] = json!(enable_mdap);
77        }
78        if let Some(ref preset) = config.mdap_preset {
79            args["mdap_preset"] = json!(preset);
80        }
81
82        let result = self.call_tool("agent_spawn", args).await?;
83
84        // Extract agent_id from result
85        // The result from the MCP server is typically a CallToolResult with content
86        let agent_id = extract_agent_id(&result)?;
87        Ok(agent_id)
88    }
89
90    /// Wait for an agent to complete, with optional timeout.
91    pub async fn await_agent(
92        &mut self,
93        agent_id: &str,
94        timeout_secs: Option<u64>,
95    ) -> Result<AgentResult, AgentNetworkClientError> {
96        let mut args = json!({ "agent_id": agent_id });
97        if let Some(timeout) = timeout_secs {
98            args["timeout_secs"] = json!(timeout);
99        }
100
101        let result = self.call_tool("agent_await", args).await?;
102        parse_agent_result(&result, agent_id)
103    }
104
105    /// List all agents.
106    pub async fn list_agents(&mut self) -> Result<Vec<AgentInfo>, AgentNetworkClientError> {
107        let result = self.call_tool("agent_list", json!({})).await?;
108        parse_agent_list(&result)
109    }
110
111    /// Stop a running agent by ID.
112    pub async fn stop_agent(&mut self, agent_id: &str) -> Result<(), AgentNetworkClientError> {
113        self.call_tool("agent_stop", json!({ "agent_id": agent_id }))
114            .await?;
115        Ok(())
116    }
117
118    /// Get the current status of an agent by ID.
119    pub async fn get_agent_status(
120        &mut self,
121        agent_id: &str,
122    ) -> Result<AgentInfo, AgentNetworkClientError> {
123        let result = self
124            .call_tool("agent_status", json!({ "agent_id": agent_id }))
125            .await?;
126        parse_agent_info(&result, agent_id)
127    }
128}
129
130fn extract_agent_id(result: &serde_json::Value) -> Result<String, AgentNetworkClientError> {
131    // Try to extract from content array (CallToolResult format)
132    if let Some(content) = result.get("content").and_then(|c| c.as_array()) {
133        for item in content {
134            if let Some(text) = item.get("text").and_then(|t| t.as_str()) {
135                // Parse the text to find agent_id
136                if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(text)
137                    && let Some(id) = parsed.get("agent_id").and_then(|i| i.as_str())
138                {
139                    return Ok(id.to_string());
140                }
141                // Try to find agent_id pattern in text
142                if text.contains("agent_id") {
143                    // Simple extraction
144                    if let Some(start) = text.find("agent_id") {
145                        let rest = &text[start..];
146                        if let Some(colon) = rest.find(':') {
147                            let value_part = rest[colon + 1..].trim();
148                            let id = value_part
149                                .trim_start_matches('"')
150                                .split('"')
151                                .next()
152                                .unwrap_or(value_part.split_whitespace().next().unwrap_or(""));
153                            if !id.is_empty() {
154                                return Ok(id.to_string());
155                            }
156                        }
157                    }
158                }
159            }
160        }
161    }
162
163    // Direct field access
164    if let Some(id) = result.get("agent_id").and_then(|i| i.as_str()) {
165        return Ok(id.to_string());
166    }
167
168    Err(AgentNetworkClientError::Protocol(
169        "Could not extract agent_id from spawn result".to_string(),
170    ))
171}
172
173fn parse_agent_result(
174    result: &serde_json::Value,
175    agent_id: &str,
176) -> Result<AgentResult, AgentNetworkClientError> {
177    // Try to parse from content text
178    let raw = result.to_string();
179
180    let text = extract_text_content(result).unwrap_or_default();
181
182    // Try JSON parse
183    if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&text) {
184        return Ok(AgentResult {
185            agent_id: parsed
186                .get("agent_id")
187                .and_then(|i| i.as_str())
188                .unwrap_or(agent_id)
189                .to_string(),
190            success: parsed
191                .get("success")
192                .and_then(|s| s.as_bool())
193                .unwrap_or(false),
194            iterations: parsed
195                .get("iterations")
196                .and_then(|i| i.as_u64())
197                .unwrap_or(0) as u32,
198            summary: parsed
199                .get("summary")
200                .and_then(|s| s.as_str())
201                .unwrap_or("")
202                .to_string(),
203            raw_output: raw,
204        });
205    }
206
207    // Fallback
208    Ok(AgentResult {
209        agent_id: agent_id.to_string(),
210        success: text.contains("success") || text.contains("completed"),
211        iterations: 0,
212        summary: text,
213        raw_output: raw,
214    })
215}
216
217fn parse_agent_list(result: &serde_json::Value) -> Result<Vec<AgentInfo>, AgentNetworkClientError> {
218    let text = extract_text_content(result).unwrap_or_default();
219
220    if let Ok(agents) = serde_json::from_str::<Vec<AgentInfo>>(&text) {
221        return Ok(agents);
222    }
223
224    // Single agent info
225    if let Ok(info) = serde_json::from_str::<AgentInfo>(&text) {
226        return Ok(vec![info]);
227    }
228
229    Ok(Vec::new())
230}
231
232fn parse_agent_info(
233    result: &serde_json::Value,
234    agent_id: &str,
235) -> Result<AgentInfo, AgentNetworkClientError> {
236    let text = extract_text_content(result).unwrap_or_default();
237
238    if let Ok(info) = serde_json::from_str::<AgentInfo>(&text) {
239        return Ok(info);
240    }
241
242    Ok(AgentInfo {
243        agent_id: agent_id.to_string(),
244        status: "unknown".to_string(),
245        task_description: text,
246    })
247}
248
249fn extract_text_content(result: &serde_json::Value) -> Option<String> {
250    if let Some(content) = result.get("content").and_then(|c| c.as_array()) {
251        for item in content {
252            if let Some(text) = item.get("text").and_then(|t| t.as_str()) {
253                return Some(text.to_string());
254            }
255        }
256    }
257    // If result is a string itself
258    if let Some(s) = result.as_str() {
259        return Some(s.to_string());
260    }
261    None
262}