Skip to main content

rskit_agent/command/
mod.rs

1//! Slash-command registry for interactive agent sessions.
2//!
3//! Commands are prefixed with `/` and dispatched through a [`CommandRegistry`].
4//! Call [`register_builtins`] to register the default `/help`, `/clear`,
5//! `/model`, and `/compact` commands.
6
7mod builtins;
8mod registry;
9
10pub use builtins::register_builtins;
11pub use registry::{Command, CommandHandler, CommandRegistry};
12
13#[cfg(test)]
14mod tests {
15    use super::*;
16
17    #[test]
18    fn test_is_command() {
19        assert!(CommandRegistry::is_command("/help"));
20        assert!(CommandRegistry::is_command("  /clear  "));
21        assert!(!CommandRegistry::is_command("hello"));
22        assert!(!CommandRegistry::is_command("/ nope"));
23        assert!(!CommandRegistry::is_command("/"));
24        assert!(!CommandRegistry::is_command(""));
25    }
26
27    #[test]
28    fn test_parse_command_no_args() {
29        let (name, args) = CommandRegistry::parse_command("/help").unwrap();
30        assert_eq!(name, "help");
31        assert_eq!(args, "");
32    }
33
34    #[test]
35    fn test_parse_command_with_args() {
36        let (name, args) = CommandRegistry::parse_command("/model gpt-4o").unwrap();
37        assert_eq!(name, "model");
38        assert_eq!(args, "gpt-4o");
39    }
40
41    #[test]
42    fn test_parse_command_not_a_command() {
43        assert!(CommandRegistry::parse_command("hello").is_none());
44    }
45
46    #[test]
47    fn test_register_and_get() {
48        let mut reg = CommandRegistry::new();
49        reg.register(Command {
50            name: "ping".to_string(),
51            description: "Ping".to_string(),
52            usage: "/ping".to_string(),
53            handler: Box::new(|_: &str| Ok("pong".to_string())),
54        })
55        .unwrap();
56        assert!(reg.get("ping").is_some());
57        assert!(reg.get("missing").is_none());
58    }
59
60    #[test]
61    fn test_list_sorted() {
62        let mut reg = CommandRegistry::new();
63        for name in &["zebra", "alpha", "mid"] {
64            reg.register(Command {
65                name: name.to_string(),
66                description: String::new(),
67                usage: String::new(),
68                handler: Box::new(|_: &str| Ok(String::new())),
69            })
70            .unwrap();
71        }
72        let names: Vec<&str> = reg.list().iter().map(|c| c.name.as_str()).collect();
73        assert_eq!(names, vec!["alpha", "mid", "zebra"]);
74    }
75
76    #[test]
77    fn test_execute_known_command() {
78        let mut reg = CommandRegistry::new();
79        reg.register(Command {
80            name: "echo".to_string(),
81            description: "Echo args".to_string(),
82            usage: "/echo <text>".to_string(),
83            handler: Box::new(|args: &str| Ok(format!("echo: {args}"))),
84        })
85        .unwrap();
86        let result = reg.execute("/echo hello world").unwrap();
87        assert_eq!(result, "echo: hello world");
88    }
89
90    #[test]
91    fn test_execute_unknown_command() {
92        let reg = CommandRegistry::new();
93        let err = reg.execute("/nope").unwrap_err();
94        assert!(err.message().contains("unknown command"));
95    }
96
97    #[test]
98    fn test_builtins() {
99        let mut reg = CommandRegistry::new();
100        register_builtins(&mut reg).unwrap();
101
102        assert!(reg.get("help").is_some());
103        assert!(reg.get("clear").is_some());
104        assert!(reg.get("model").is_some());
105        assert!(reg.get("compact").is_some());
106
107        let help_out = reg.execute("/help").unwrap();
108        assert!(help_out.contains("/help"));
109
110        let model_out = reg.execute("/model gpt-4o").unwrap();
111        assert!(model_out.contains("gpt-4o"));
112    }
113}