cersei_tools/
send_message.rs1use super::*;
4use serde::Deserialize;
5
6static INBOX_REGISTRY: once_cell::sync::Lazy<dashmap::DashMap<String, Vec<InboxMessage>>> =
8 once_cell::sync::Lazy::new(dashmap::DashMap::new);
9
10#[derive(Debug, Clone, serde::Serialize)]
11pub struct InboxMessage {
12 pub from: String,
13 pub content: String,
14 pub timestamp: String,
15}
16
17pub fn drain_inbox(session_id: &str) -> Vec<InboxMessage> {
19 INBOX_REGISTRY
20 .remove(session_id)
21 .map(|(_, v)| v)
22 .unwrap_or_default()
23}
24
25pub fn peek_inbox(session_id: &str) -> Vec<InboxMessage> {
27 INBOX_REGISTRY
28 .get(session_id)
29 .map(|v| v.clone())
30 .unwrap_or_default()
31}
32
33pub struct SendMessageTool;
34
35#[async_trait]
36impl Tool for SendMessageTool {
37 fn name(&self) -> &str {
38 "SendMessage"
39 }
40 fn description(&self) -> &str {
41 "Send a message to another agent or session by ID."
42 }
43 fn permission_level(&self) -> PermissionLevel {
44 PermissionLevel::None
45 }
46 fn category(&self) -> ToolCategory {
47 ToolCategory::Orchestration
48 }
49
50 fn input_schema(&self) -> Value {
51 serde_json::json!({
52 "type": "object",
53 "properties": {
54 "to": { "type": "string", "description": "Target session/agent ID" },
55 "content": { "type": "string", "description": "Message content" }
56 },
57 "required": ["to", "content"]
58 })
59 }
60
61 async fn execute(&self, input: Value, ctx: &ToolContext) -> ToolResult {
62 #[derive(Deserialize)]
63 struct Input {
64 to: String,
65 content: String,
66 }
67
68 let input: Input = match serde_json::from_value(input) {
69 Ok(i) => i,
70 Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
71 };
72
73 let msg = InboxMessage {
74 from: ctx.session_id.clone(),
75 content: input.content.clone(),
76 timestamp: chrono::Utc::now().to_rfc3339(),
77 };
78
79 INBOX_REGISTRY
80 .entry(input.to.clone())
81 .or_default()
82 .push(msg);
83
84 ToolResult::success(format!("Message sent to '{}'", input.to))
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::permissions::AllowAll;
92
93 fn test_ctx() -> ToolContext {
94 ToolContext {
95 working_dir: std::env::temp_dir(),
96 session_id: "agent-a".into(),
97 permissions: Arc::new(AllowAll),
98 cost_tracker: Arc::new(CostTracker::new()),
99 mcp_manager: None,
100 extensions: Extensions::default(),
101 }
102 }
103
104 #[tokio::test]
105 async fn test_send_and_receive() {
106 let tool = SendMessageTool;
107 tool.execute(
108 serde_json::json!({"to": "agent-b", "content": "Hello B!"}),
109 &test_ctx(),
110 )
111 .await;
112
113 let msgs = peek_inbox("agent-b");
114 assert_eq!(msgs.len(), 1);
115 assert_eq!(msgs[0].from, "agent-a");
116 assert_eq!(msgs[0].content, "Hello B!");
117
118 let drained = drain_inbox("agent-b");
119 assert_eq!(drained.len(), 1);
120 assert!(peek_inbox("agent-b").is_empty());
121 }
122}