1use serde::{Deserialize, Serialize};
14use tokio::sync::broadcast;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct InterAgentMessage {
19 pub from: String,
21 pub to: Option<String>,
23 pub message_type: String,
25 pub payload: serde_json::Value,
27 pub timestamp_ms: u64,
29}
30
31impl InterAgentMessage {
32 pub fn direct(
34 from: impl Into<String>,
35 to: impl Into<String>,
36 message_type: impl Into<String>,
37 payload: serde_json::Value,
38 ) -> Self {
39 Self {
40 from: from.into(),
41 to: Some(to.into()),
42 message_type: message_type.into(),
43 payload,
44 timestamp_ms: std::time::SystemTime::now()
45 .duration_since(std::time::UNIX_EPOCH)
46 .unwrap_or_default()
47 .as_millis() as u64,
48 }
49 }
50
51 pub fn broadcast(
53 from: impl Into<String>,
54 message_type: impl Into<String>,
55 payload: serde_json::Value,
56 ) -> Self {
57 Self {
58 from: from.into(),
59 to: None,
60 message_type: message_type.into(),
61 payload,
62 timestamp_ms: std::time::SystemTime::now()
63 .duration_since(std::time::UNIX_EPOCH)
64 .unwrap_or_default()
65 .as_millis() as u64,
66 }
67 }
68
69 pub fn is_for(&self, agent_id: &str) -> bool {
71 self.to.as_deref() == Some(agent_id) || self.to.is_none()
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum PublishResult {
78 Delivered {
80 n: usize,
82 },
83 NoSubscribers,
85}
86
87impl PublishResult {
88 pub fn delivered_count(&self) -> usize {
91 match self {
92 PublishResult::Delivered { n } => *n,
93 PublishResult::NoSubscribers => 0,
94 }
95 }
96}
97
98#[derive(Clone)]
103pub struct MessageBus {
104 sender: broadcast::Sender<InterAgentMessage>,
105 capacity: usize,
106}
107
108impl MessageBus {
109 pub fn new(capacity: usize) -> Self {
111 let (tx, _rx) = broadcast::channel(capacity);
112 Self {
113 sender: tx,
114 capacity,
115 }
116 }
117
118 pub fn publish(&self, msg: InterAgentMessage) -> PublishResult {
124 match self.sender.send(msg) {
125 Ok(n) => PublishResult::Delivered { n },
126 Err(broadcast::error::SendError(msg)) => {
127 tracing::warn!(
128 from = %msg.from,
129 message_type = %msg.message_type,
130 "MessageBus publish dropped message: no active subscribers"
131 );
132 PublishResult::NoSubscribers
133 }
134 }
135 }
136
137 pub fn subscribe(&self) -> broadcast::Receiver<InterAgentMessage> {
144 self.sender.subscribe()
145 }
146
147 pub fn subscribe_lag_aware(&self) -> LagAwareReceiver {
152 LagAwareReceiver {
153 inner: self.sender.subscribe(),
154 total_skipped: std::sync::atomic::AtomicU64::new(0),
155 }
156 }
157
158 pub fn subscriber_count(&self) -> usize {
160 self.sender.receiver_count()
161 }
162
163 pub fn capacity(&self) -> usize {
165 self.capacity
166 }
167}
168
169pub struct LagAwareReceiver {
174 inner: broadcast::Receiver<InterAgentMessage>,
175 total_skipped: std::sync::atomic::AtomicU64,
176}
177
178impl LagAwareReceiver {
179 pub async fn recv(&mut self) -> Option<InterAgentMessage> {
186 loop {
187 match self.inner.recv().await {
188 Ok(msg) => return Some(msg),
189 Err(broadcast::error::RecvError::Lagged(n)) => {
190 let prev = self
191 .total_skipped
192 .fetch_add(n, std::sync::atomic::Ordering::Relaxed);
193 tracing::warn!(
194 skipped_now = n,
195 total_skipped = prev + n,
196 "MessageBus receiver lagged — messages were dropped"
197 );
198 continue;
199 }
200 Err(broadcast::error::RecvError::Closed) => return None,
201 }
202 }
203 }
204
205 pub fn try_recv(&mut self) -> Result<InterAgentMessage, broadcast::error::TryRecvError> {
210 loop {
211 match self.inner.try_recv() {
212 Ok(msg) => return Ok(msg),
213 Err(broadcast::error::TryRecvError::Lagged(n)) => {
214 let prev = self
215 .total_skipped
216 .fetch_add(n, std::sync::atomic::Ordering::Relaxed);
217 tracing::warn!(
218 skipped_now = n,
219 total_skipped = prev + n,
220 "MessageBus receiver lagged — messages were dropped"
221 );
222 continue;
223 }
224 Err(e) => return Err(e),
225 }
226 }
227 }
228
229 pub fn total_skipped(&self) -> u64 {
232 self.total_skipped
233 .load(std::sync::atomic::Ordering::Relaxed)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use serde_json::json;
241
242 #[test]
243 fn test_direct_message() {
244 let msg = InterAgentMessage::direct(
245 "agent-1",
246 "agent-2",
247 "task_complete",
248 json!({"result": "ok"}),
249 );
250 assert_eq!(msg.from, "agent-1");
251 assert_eq!(msg.to, Some("agent-2".to_string()));
252 assert!(msg.is_for("agent-2"));
253 assert!(!msg.is_for("agent-1"));
254 assert!(!msg.is_for("agent-3"));
255 }
256
257 #[test]
258 fn test_broadcast_message() {
259 let msg =
260 InterAgentMessage::broadcast("agent-1", "status_update", json!({"status": "idle"}));
261 assert_eq!(msg.from, "agent-1");
262 assert!(msg.to.is_none());
263 assert!(msg.is_for("agent-2"));
264 assert!(msg.is_for("agent-3"));
265 }
266
267 #[tokio::test]
268 async fn test_message_bus_pub_sub() {
269 let bus = MessageBus::new(16);
270 let mut rx = bus.subscribe();
271
272 let msg = InterAgentMessage::broadcast("agent-1", "ping", json!("pong"));
273 let result = bus.publish(msg.clone());
274 assert_eq!(result.delivered_count(), 1);
275
276 let received = rx.try_recv().expect("should receive message");
277 assert_eq!(received.from, "agent-1");
278 assert_eq!(received.message_type, "ping");
279 }
280
281 #[tokio::test]
282 async fn test_message_bus_multiple_subscribers() {
283 let bus = MessageBus::new(16);
284 let mut rx1 = bus.subscribe();
285 let mut rx2 = bus.subscribe();
286
287 assert_eq!(bus.subscriber_count(), 2);
288
289 let msg = InterAgentMessage::broadcast("coordinator", "start", json!({}));
290 let result = bus.publish(msg);
291 assert_eq!(result, PublishResult::Delivered { n: 2 });
292
293 assert!(rx1.try_recv().is_ok());
294 assert!(rx2.try_recv().is_ok());
295 }
296
297 #[test]
298 fn test_message_bus_no_subscribers() {
299 let bus = MessageBus::new(16);
300 let msg = InterAgentMessage::broadcast("agent-1", "ping", json!("pong"));
302 let result = bus.publish(msg);
303 assert_eq!(result, PublishResult::NoSubscribers);
304 }
305
306 #[test]
307 fn test_message_serialization() {
308 let msg = InterAgentMessage::direct("a", "b", "test", json!({"key": "value"}));
309 let json_str = serde_json::to_string(&msg).unwrap();
310 let deserialized: InterAgentMessage = serde_json::from_str(&json_str).unwrap();
311 assert_eq!(deserialized.from, "a");
312 assert_eq!(deserialized.to, Some("b".to_string()));
313 }
314
315 #[tokio::test]
316 async fn test_lag_aware_receiver() {
317 let bus = MessageBus::new(2);
318 let mut rx = bus.subscribe_lag_aware();
319
320 for i in 0..5 {
322 bus.publish(InterAgentMessage::broadcast("sender", "test", json!(i)));
323 }
324
325 let mut received = Vec::new();
327 for _ in 0..3 {
328 match rx.try_recv() {
329 Ok(msg) => received.push(msg),
330 Err(_) => break,
331 }
332 }
333
334 assert!(!received.is_empty());
336 assert!(rx.total_skipped() > 0);
338 }
339
340 #[tokio::test]
341 async fn test_lag_aware_receiver_recv() {
342 let bus = MessageBus::new(4);
343 let mut rx = bus.subscribe_lag_aware();
344
345 bus.publish(InterAgentMessage::broadcast("a", "ping", json!(1)));
346 bus.publish(InterAgentMessage::broadcast("a", "pong", json!(2)));
347
348 let msg = rx.recv().await.expect("should receive");
349 assert_eq!(msg.message_type, "ping");
350 let msg = rx.recv().await.expect("should receive");
351 assert_eq!(msg.message_type, "pong");
352 }
353
354 #[test]
355 fn test_publish_result_delivered_count() {
356 assert_eq!(PublishResult::Delivered { n: 3 }.delivered_count(), 3);
357 assert_eq!(PublishResult::NoSubscribers.delivered_count(), 0);
358 }
359}