1use super::{Tool, ToolResult};
8use crate::bus::AgentBus;
9use crate::bus::relay::ProtocolRelayRuntime;
10use anyhow::Result;
11use async_trait::async_trait;
12use parking_lot::RwLock;
13use serde_json::{Value, json};
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::sync::OnceCell;
17use uuid::Uuid;
18
19lazy_static::lazy_static! {
20 static ref RELAY_STORE: RwLock<HashMap<String, Arc<ProtocolRelayRuntime>>> = RwLock::new(HashMap::new());
21 static ref AGENT_BUS: OnceCell<Arc<AgentBus>> = OnceCell::const_new();
22}
23
24async fn get_agent_bus() -> Result<Arc<AgentBus>> {
25 let bus = AGENT_BUS
26 .get_or_try_init(|| async {
27 let bus = AgentBus::new().into_arc();
28 Ok::<_, anyhow::Error>(bus)
29 })
30 .await?;
31 Ok(bus.clone())
32}
33
34pub struct RelayAutoChatTool;
35
36impl RelayAutoChatTool {
37 pub fn new() -> Self {
38 Self
39 }
40}
41
42#[async_trait]
43impl Tool for RelayAutoChatTool {
44 fn id(&self) -> &str {
45 "relay_autochat"
46 }
47
48 fn name(&self) -> &str {
49 "Relay AutoChat"
50 }
51
52 fn description(&self) -> &str {
53 "Autonomous relay communication between agents for task delegation and result aggregation. \
54 Actions: delegate (send task to target agent), handoff (pass context between agents), \
55 status (check relay status), list_agents (show available agents in relay), \
56 init (initialize a new relay with task), complete (finish relay and aggregate results)."
57 }
58
59 fn parameters(&self) -> Value {
60 json!({
61 "type": "object",
62 "properties": {
63 "action": {
64 "type": "string",
65 "enum": ["delegate", "handoff", "status", "list_agents", "init", "complete"],
66 "description": "Action to perform"
67 },
68 "target_agent": {
69 "type": "string",
70 "description": "Target agent name for delegation/handoff"
71 },
72 "message": {
73 "type": "string",
74 "description": "Message to send to the target agent"
75 },
76 "context": {
77 "type": "object",
78 "description": "Additional context to pass along (JSON object)"
79 },
80 "relay_id": {
81 "type": "string",
82 "description": "Relay ID to use (auto-generated if not provided)"
83 },
84 "okr_id": {
85 "type": "string",
86 "description": "Optional OKR ID to associate with this relay"
87 },
88 "task": {
89 "type": "string",
90 "description": "Task description for initializing a new relay"
91 }
92 },
93 "required": ["action"]
94 })
95 }
96
97 async fn execute(&self, params: Value) -> Result<ToolResult> {
98 let action = match params.get("action").and_then(|v| v.as_str()) {
99 Some(s) if !s.is_empty() => s.to_string(),
100 _ => {
101 return Ok(ToolResult::structured_error(
102 "MISSING_FIELD",
103 "relay_autochat",
104 "action is required. Valid actions: init, delegate, handoff, status, list_agents, complete",
105 Some(vec!["action"]),
106 Some(json!({
107 "action": "init",
108 "task": "description of the relay task"
109 })),
110 ));
111 }
112 };
113
114 let relay_id = params
115 .get("relay_id")
116 .and_then(|v| v.as_str())
117 .map(String::from);
118 let target_agent = params
119 .get("target_agent")
120 .and_then(|v| v.as_str())
121 .map(String::from);
122 let message = params
123 .get("message")
124 .and_then(|v| v.as_str())
125 .map(String::from);
126 let context = params.get("context").cloned();
127 let okr_id = params
128 .get("okr_id")
129 .and_then(|v| v.as_str())
130 .map(String::from);
131 let task = params
132 .get("task")
133 .and_then(|v| v.as_str())
134 .map(String::from);
135
136 match action.as_str() {
137 "init" => self.init_relay(relay_id, task, context, okr_id).await,
138 "delegate" => {
139 self.delegate_task(relay_id, target_agent, message, context, okr_id)
140 .await
141 }
142 "handoff" => {
143 self.handoff_context(relay_id, target_agent, message, context)
144 .await
145 }
146 "status" => self.get_status(relay_id).await,
147 "list_agents" => self.list_agents(relay_id).await,
148 "complete" => self.complete_relay(relay_id).await,
149 _ => Ok(ToolResult::structured_error(
150 "INVALID_ACTION",
151 "relay_autochat",
152 &format!(
153 "Unknown action: '{action}'. Valid actions: init, delegate, handoff, status, list_agents, complete"
154 ),
155 None,
156 Some(json!({
157 "action": "init",
158 "task": "description of the relay task"
159 })),
160 )),
161 }
162 }
163}
164
165impl RelayAutoChatTool {
166 async fn init_relay(
168 &self,
169 relay_id: Option<String>,
170 task: Option<String>,
171 _context: Option<Value>,
172 okr_id: Option<String>,
173 ) -> Result<ToolResult> {
174 let task = task.unwrap_or_else(|| "Unspecified task".to_string());
175 let relay_id =
176 relay_id.unwrap_or_else(|| format!("relay-{}", &Uuid::new_v4().to_string()[..8]));
177
178 let bus = get_agent_bus().await?;
179 let runtime = ProtocolRelayRuntime::with_relay_id(bus, relay_id.clone());
180
181 {
183 let mut store = RELAY_STORE.write();
184 store.insert(relay_id.clone(), Arc::new(runtime.clone()));
185 }
186
187 let response = json!({
188 "status": "initialized",
189 "relay_id": relay_id,
190 "task": task,
191 "okr_id": okr_id,
192 "message": "Relay initialized. Use 'delegate' to assign tasks to agents, or 'list_agents' to see available agents."
193 });
194
195 let mut result = ToolResult::success(
196 serde_json::to_string_pretty(&response).unwrap_or_else(|_| format!("{:?}", response)),
197 )
198 .with_metadata("relay_id", json!(relay_id));
199 if let Some(okr_id) = response.get("okr_id").and_then(|v| v.as_str()) {
200 result = result.with_metadata("okr_id", json!(okr_id));
201 }
202
203 Ok(result)
204 }
205
206 async fn delegate_task(
208 &self,
209 relay_id: Option<String>,
210 target_agent: Option<String>,
211 message: Option<String>,
212 context: Option<Value>,
213 okr_id: Option<String>,
214 ) -> Result<ToolResult> {
215 let relay_id = match relay_id {
216 Some(id) => id,
217 None => {
218 return Ok(ToolResult::structured_error(
219 "MISSING_FIELD",
220 "relay_autochat",
221 "relay_id is required for delegate action",
222 Some(vec!["relay_id"]),
223 Some(
224 json!({"action": "delegate", "relay_id": "relay-xxx", "target_agent": "agent-name", "message": "task description"}),
225 ),
226 ));
227 }
228 };
229 let target_agent = match target_agent {
230 Some(a) => a,
231 None => {
232 return Ok(ToolResult::structured_error(
233 "MISSING_FIELD",
234 "relay_autochat",
235 "target_agent is required for delegate action",
236 Some(vec!["target_agent"]),
237 Some(
238 json!({"action": "delegate", "relay_id": relay_id, "target_agent": "agent-name", "message": "task description"}),
239 ),
240 ));
241 }
242 };
243 let message = message.unwrap_or_else(|| "New task assigned".to_string());
244
245 let runtime = {
247 let store = RELAY_STORE.read();
248 store.get(&relay_id).cloned()
249 };
250
251 let runtime = match runtime {
252 Some(r) => r,
253 None => {
254 let bus = get_agent_bus().await?;
256 let new_runtime = ProtocolRelayRuntime::with_relay_id(bus, relay_id.clone());
257 let arc_runtime = Arc::new(new_runtime);
258 {
259 let mut store = RELAY_STORE.write();
260 store.insert(relay_id.clone(), arc_runtime.clone());
261 }
262 arc_runtime
263 }
264 };
265
266 let context_msg = if let Some(ref ctx) = context {
268 format!(
269 "{}\n\nContext: {}",
270 message,
271 serde_json::to_string_pretty(ctx).unwrap_or_default()
272 )
273 } else {
274 message.clone()
275 };
276
277 runtime.send_handoff("system", &target_agent, &context_msg);
279
280 let response = json!({
281 "status": "delegated",
282 "relay_id": relay_id,
283 "target_agent": target_agent,
284 "okr_id": okr_id,
285 "message": message,
286 "initial_results": {
287 "task_assigned": true,
288 "agent_notified": true
289 }
290 });
291
292 let mut result = ToolResult::success(
293 serde_json::to_string_pretty(&response).unwrap_or_else(|_| format!("{:?}", response)),
294 )
295 .with_metadata("relay_id", json!(relay_id))
296 .with_metadata("target_agent", json!(target_agent));
297 if let Some(okr_id) = response.get("okr_id").and_then(|v| v.as_str()) {
298 result = result.with_metadata("okr_id", json!(okr_id));
299 }
300
301 Ok(result)
302 }
303
304 async fn handoff_context(
306 &self,
307 relay_id: Option<String>,
308 target_agent: Option<String>,
309 message: Option<String>,
310 context: Option<Value>,
311 ) -> Result<ToolResult> {
312 let relay_id = match relay_id {
313 Some(id) => id,
314 None => {
315 return Ok(ToolResult::structured_error(
316 "MISSING_FIELD",
317 "relay_autochat",
318 "relay_id is required for handoff action",
319 Some(vec!["relay_id"]),
320 Some(
321 json!({"action": "handoff", "relay_id": "relay-xxx", "target_agent": "agent-name"}),
322 ),
323 ));
324 }
325 };
326 let target_agent = match target_agent {
327 Some(a) => a,
328 None => {
329 return Ok(ToolResult::structured_error(
330 "MISSING_FIELD",
331 "relay_autochat",
332 "target_agent is required for handoff action",
333 Some(vec!["target_agent"]),
334 Some(
335 json!({"action": "handoff", "relay_id": relay_id, "target_agent": "agent-name"}),
336 ),
337 ));
338 }
339 };
340 let message = message.unwrap_or_else(|| "Context handoff".to_string());
341
342 let store = RELAY_STORE.read();
343 let runtime = match store.get(&relay_id) {
344 Some(r) => r.clone(),
345 None => {
346 return Ok(ToolResult::structured_error(
347 "NOT_FOUND",
348 "relay_autochat",
349 &format!(
350 "Relay not found: {relay_id}. Use 'init' action to create a relay first."
351 ),
352 None,
353 Some(json!({"action": "init", "task": "description of the relay task"})),
354 ));
355 }
356 };
357 drop(store);
359
360 let context_msg = if let Some(ref ctx) = context {
362 format!(
363 "{}\n\nContext: {}",
364 message,
365 serde_json::to_string_pretty(ctx).unwrap_or_default()
366 )
367 } else {
368 message
369 };
370
371 runtime.send_handoff("previous_agent", &target_agent, &context_msg);
373
374 let response = json!({
375 "status": "handoff_complete",
376 "relay_id": relay_id,
377 "target_agent": target_agent,
378 "message": "Context successfully handed off to target agent"
379 });
380
381 Ok(ToolResult::success(
382 serde_json::to_string_pretty(&response).unwrap_or_else(|_| format!("{:?}", response)),
383 ))
384 }
385
386 async fn get_status(&self, relay_id: Option<String>) -> Result<ToolResult> {
388 let relay_id = match relay_id {
389 Some(id) => id,
390 None => {
391 return Ok(ToolResult::structured_error(
392 "MISSING_FIELD",
393 "relay_autochat",
394 "relay_id is required for status action",
395 Some(vec!["relay_id"]),
396 Some(json!({"action": "status", "relay_id": "relay-xxx"})),
397 ));
398 }
399 };
400
401 let store = RELAY_STORE.read();
402
403 if store.contains_key(&relay_id) {
404 let response = json!({
405 "status": "active",
406 "relay_id": relay_id,
407 "message": "Relay is active"
408 });
409
410 Ok(ToolResult::success(
411 serde_json::to_string_pretty(&response)
412 .unwrap_or_else(|_| format!("{:?}", response)),
413 ))
414 } else {
415 Ok(ToolResult::error(format!("Relay not found: {}", relay_id)))
416 }
417 }
418
419 async fn list_agents(&self, relay_id: Option<String>) -> Result<ToolResult> {
421 let relay_id = match relay_id {
422 Some(id) => id,
423 None => {
424 return Ok(ToolResult::structured_error(
425 "MISSING_FIELD",
426 "relay_autochat",
427 "relay_id is required for list_agents action",
428 Some(vec!["relay_id"]),
429 Some(json!({"action": "list_agents", "relay_id": "relay-xxx"})),
430 ));
431 }
432 };
433
434 let relay_exists = {
435 let store = RELAY_STORE.read();
436 store.contains_key(&relay_id)
437 };
438
439 if relay_exists {
440 let bus = get_agent_bus().await?;
441 let agents: Vec<Value> = bus
442 .registry
443 .agent_ids()
444 .iter()
445 .map(|name| json!({ "name": name }))
446 .collect();
447
448 let response = json!({
449 "relay_id": relay_id,
450 "agents": agents,
451 "count": agents.len()
452 });
453
454 Ok(ToolResult::success(
455 serde_json::to_string_pretty(&response)
456 .unwrap_or_else(|_| format!("{:?}", response)),
457 ))
458 } else {
459 Ok(ToolResult::error(format!("Relay not found: {}", relay_id)))
460 }
461 }
462
463 async fn complete_relay(&self, relay_id: Option<String>) -> Result<ToolResult> {
465 let relay_id = match relay_id {
466 Some(id) => id,
467 None => {
468 return Ok(ToolResult::structured_error(
469 "MISSING_FIELD",
470 "relay_autochat",
471 "relay_id is required for complete action",
472 Some(vec!["relay_id"]),
473 Some(json!({"action": "complete", "relay_id": "relay-xxx"})),
474 ));
475 }
476 };
477
478 let runtime = {
480 let mut store = RELAY_STORE.write();
481 store.remove(&relay_id)
482 };
483
484 if let Some(runtime) = runtime {
485 runtime.shutdown_agents(&[]); }
487
488 let response = json!({
489 "status": "completed",
490 "relay_id": relay_id,
491 "message": "Relay completed successfully. Results aggregated.",
492 "aggregated_results": {
493 "completed": true,
494 "total_agents": 0
495 }
496 });
497
498 Ok(ToolResult::success(
499 serde_json::to_string_pretty(&response).unwrap_or_else(|_| format!("{:?}", response)),
500 ))
501 }
502}