1pub const AGENTS_ALL: &str = "agents/#";
11
12pub const NODES_ALL: &str = "nodes/#";
14
15pub const NODES_HEARTBEAT: &str = "nodes/heartbeat";
17
18pub const SYSTEM_HEALTH: &str = "system/health";
20
21pub const SYSTEM_SHUTDOWN: &str = "system/shutdown";
23
24pub const SYSTEM_LLM_ERROR: &str = "system/llm/error";
27
28pub const SYSTEM_LLM_SWITCH: &str = "system/llm/switch";
31
32pub fn heartbeat(agent_id: &str) -> String {
36 format!("agents/{agent_id}/heartbeat")
37}
38
39pub fn status(agent_id: &str) -> String {
41 format!("agents/{agent_id}/status")
42}
43
44pub fn logs(agent_id: &str) -> String {
46 format!("agents/{agent_id}/logs")
47}
48
49pub fn alert(agent_id: &str) -> String {
51 format!("agents/{agent_id}/alert")
52}
53
54pub fn commands(agent_id: &str) -> String {
56 format!("agents/{agent_id}/commands")
57}
58
59pub fn result(agent_id: &str) -> String {
61 format!("agents/{agent_id}/result")
62}
63
64pub fn detections(agent_id: &str) -> String {
66 format!("agents/{agent_id}/detections")
67}
68
69pub fn chat(agent_id: &str) -> String {
71 format!("agents/{agent_id}/chat")
72}
73
74pub fn spawn(agent_id: &str) -> String {
76 format!("agents/{agent_id}/spawn")
77}
78
79pub const IO_CHAT: &str = "io/chat";
81
82pub fn parse_agent_topic(topic: &str) -> Option<(&str, &str)> {
88 let parts: Vec<&str> = topic.splitn(3, '/').collect();
89 match parts.as_slice() {
90 ["agents", id, event] => Some((id, event)),
91 _ => None,
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn topic_builders_are_correct() {
101 assert_eq!(heartbeat("abc"), "agents/abc/heartbeat");
102 assert_eq!(commands("xyz"), "agents/xyz/commands");
103 }
104
105 #[test]
106 fn parse_valid_agent_topic() {
107 assert_eq!(
108 parse_agent_topic("agents/abc-123/heartbeat"),
109 Some(("abc-123", "heartbeat"))
110 );
111 }
112
113 #[test]
114 fn parse_invalid_topic_returns_none() {
115 assert_eq!(parse_agent_topic("system/health"), None);
116 assert_eq!(parse_agent_topic("agents/only-two"), None);
117 }
118}