1pub const AGENTS_ALL: &str = "agents/#";
11
12pub const SYSTEM_HEALTH: &str = "system/health";
14
15pub const SYSTEM_SHUTDOWN: &str = "system/shutdown";
17
18pub const SYSTEM_LLM_ERROR: &str = "system/llm/error";
21
22pub const SYSTEM_LLM_SWITCH: &str = "system/llm/switch";
25
26pub fn heartbeat(agent_id: &str) -> String {
30 format!("agents/{agent_id}/heartbeat")
31}
32
33pub fn status(agent_id: &str) -> String {
35 format!("agents/{agent_id}/status")
36}
37
38pub fn logs(agent_id: &str) -> String {
40 format!("agents/{agent_id}/logs")
41}
42
43pub fn alert(agent_id: &str) -> String {
45 format!("agents/{agent_id}/alert")
46}
47
48pub fn commands(agent_id: &str) -> String {
50 format!("agents/{agent_id}/commands")
51}
52
53pub fn result(agent_id: &str) -> String {
55 format!("agents/{agent_id}/result")
56}
57
58pub fn detections(agent_id: &str) -> String {
60 format!("agents/{agent_id}/detections")
61}
62
63pub fn chat(agent_id: &str) -> String {
65 format!("agents/{agent_id}/chat")
66}
67
68pub fn spawn(agent_id: &str) -> String {
70 format!("agents/{agent_id}/spawn")
71}
72
73pub const IO_CHAT: &str = "io/chat";
75
76pub fn parse_agent_topic(topic: &str) -> Option<(&str, &str)> {
82 let parts: Vec<&str> = topic.splitn(3, '/').collect();
83 match parts.as_slice() {
84 ["agents", id, event] => Some((id, event)),
85 _ => None,
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn topic_builders_are_correct() {
95 assert_eq!(heartbeat("abc"), "agents/abc/heartbeat");
96 assert_eq!(commands("xyz"), "agents/xyz/commands");
97 }
98
99 #[test]
100 fn parse_valid_agent_topic() {
101 assert_eq!(
102 parse_agent_topic("agents/abc-123/heartbeat"),
103 Some(("abc-123", "heartbeat"))
104 );
105 }
106
107 #[test]
108 fn parse_invalid_topic_returns_none() {
109 assert_eq!(parse_agent_topic("system/health"), None);
110 assert_eq!(parse_agent_topic("agents/only-two"), None);
111 }
112}