nexus_memory_hooks/
factory.rs1use std::collections::HashMap;
4
5use crate::agents::{
6 CLIHook, ClaudeCodeHook, GeminiHook, OhMyPiHook, PiMonoHook, PiSkillsHook, QwenHook,
7};
8use crate::base::AgentHook;
9use crate::error::{HookError, Result};
10use crate::types::AgentType;
11
12pub struct HookFactory {
38 supported: HashMap<String, AgentType>,
40
41 aliases: HashMap<String, String>,
43}
44
45impl HookFactory {
46 pub fn new() -> Self {
48 let mut supported = HashMap::new();
49 let mut aliases = HashMap::new();
50
51 for agent_type in &[
53 AgentType::ClaudeCode,
54 AgentType::Gemini,
55 AgentType::Qwen,
56 AgentType::PiMono,
57 AgentType::OhMyPi,
58 AgentType::PiSkills,
59 AgentType::OpenCode,
60 AgentType::Codex,
61 AgentType::Amp,
62 AgentType::Droid,
63 AgentType::Generic,
64 ] {
65 supported.insert(agent_type.to_string(), *agent_type);
66 }
67
68 aliases.insert("claude".to_string(), "claude-code".to_string());
70 aliases.insert("pimono".to_string(), "pi-mono".to_string());
71 aliases.insert("pi".to_string(), "pi-mono".to_string());
72 aliases.insert("omp".to_string(), "oh-my-pi".to_string());
73 aliases.insert("ohmypi".to_string(), "oh-my-pi".to_string());
74
75 Self { supported, aliases }
76 }
77
78 pub fn create_hook(&self, agent_type: &str) -> Result<Box<dyn AgentHook>> {
80 let normalized = self.normalize_agent_type(agent_type);
82
83 let agent_type_enum = self.supported.get(&normalized).copied();
85
86 match agent_type_enum {
87 Some(AgentType::ClaudeCode) => Ok(Box::new(ClaudeCodeHook::new())),
88 Some(AgentType::Gemini) => Ok(Box::new(GeminiHook::new())),
89 Some(AgentType::Qwen) => Ok(Box::new(QwenHook::new())),
90 Some(AgentType::PiMono) => Ok(Box::new(PiMonoHook::new())),
91 Some(AgentType::OhMyPi) => Ok(Box::new(OhMyPiHook::new())),
92 Some(AgentType::PiSkills) => Ok(Box::new(PiSkillsHook::new())),
93 Some(AgentType::OpenCode)
94 | Some(AgentType::Codex)
95 | Some(AgentType::Amp)
96 | Some(AgentType::Droid)
97 | Some(AgentType::Generic) => Ok(Box::new(CLIHook::new(normalized.clone()))),
98 None => Err(HookError::AgentNotFound(format!(
99 "Unknown agent type: {}",
100 agent_type
101 ))),
102 }
103 }
104
105 pub fn is_supported(&self, agent_type: &str) -> bool {
107 let normalized = self.normalize_agent_type(agent_type);
108 self.supported.contains_key(&normalized)
109 }
110
111 pub fn supported_agents(&self) -> Vec<String> {
113 self.supported.keys().cloned().collect()
114 }
115
116 pub fn get_agent_info(&self, agent_type: &str) -> Option<AgentInfo> {
118 let normalized = self.normalize_agent_type(agent_type);
119 self.supported.get(&normalized).map(|&t| AgentInfo {
120 agent_type: t.to_string(),
121 detection_layer: t.detection_layer(),
122 process_names: t.process_names().iter().map(|s| s.to_string()).collect(),
123 config_dir: t.config_dir().to_string(),
124 })
125 }
126
127 fn normalize_agent_type(&self, agent_type: &str) -> String {
129 let lower = agent_type.to_lowercase();
130
131 if let Some(alias) = self.aliases.get(&lower) {
133 alias.clone()
134 } else {
135 lower
136 }
137 }
138
139 pub fn register_alias(&mut self, alias: &str, target: &str) {
141 self.aliases
142 .insert(alias.to_lowercase(), target.to_lowercase());
143 }
144}
145
146impl Default for HookFactory {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct AgentInfo {
155 pub agent_type: String,
156 pub detection_layer: crate::types::DetectionLayer,
157 pub process_names: Vec<String>,
158 pub config_dir: String,
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_factory_new() {
167 let factory = HookFactory::new();
168 assert!(factory.is_supported("claude-code"));
169 assert!(factory.is_supported("pi-mono"));
170 assert!(factory.is_supported("oh-my-pi"));
171 }
172
173 #[test]
174 fn test_factory_aliases() {
175 let factory = HookFactory::new();
176
177 assert!(factory.is_supported("claude"));
178 assert!(factory.is_supported("pi"));
179 assert!(factory.is_supported("omp"));
180 assert!(factory.is_supported("ohmypi"));
181 }
182
183 #[test]
184 fn test_factory_create_hook() {
185 let factory = HookFactory::new();
186
187 let hook = factory.create_hook("claude-code").unwrap();
188 assert_eq!(hook.agent_type(), "claude-code");
189
190 let hook = factory.create_hook("pi-mono").unwrap();
191 assert_eq!(hook.agent_type(), "pi-mono");
192
193 let hook = factory.create_hook("oh-my-pi").unwrap();
194 assert_eq!(hook.agent_type(), "oh-my-pi");
195 }
196
197 #[test]
198 fn test_factory_create_hook_alias() {
199 let factory = HookFactory::new();
200
201 let hook = factory.create_hook("claude").unwrap();
202 assert_eq!(hook.agent_type(), "claude-code");
203
204 let hook = factory.create_hook("omp").unwrap();
205 assert_eq!(hook.agent_type(), "oh-my-pi");
206 }
207
208 #[test]
209 fn test_factory_unsupported() {
210 let factory = HookFactory::new();
211
212 let result = factory.create_hook("unknown-agent");
213 assert!(result.is_err());
214 }
215
216 #[test]
217 fn test_factory_supported_agents() {
218 let factory = HookFactory::new();
219 let agents = factory.supported_agents();
220
221 assert!(agents.contains(&"claude-code".to_string()));
222 assert!(agents.contains(&"pi-mono".to_string()));
223 assert!(agents.contains(&"oh-my-pi".to_string()));
224 }
225
226 #[test]
227 fn test_factory_get_agent_info() {
228 let factory = HookFactory::new();
229
230 let info = factory.get_agent_info("pi-mono").unwrap();
231 assert_eq!(info.agent_type, "pi-mono");
232 assert_eq!(info.config_dir, ".pi");
233 }
234
235 #[test]
236 fn test_factory_register_alias() {
237 let mut factory = HookFactory::new();
238 factory.register_alias("my-agent", "claude-code");
239
240 assert!(factory.is_supported("my-agent"));
241 let hook = factory.create_hook("my-agent").unwrap();
242 assert_eq!(hook.agent_type(), "claude-code");
243 }
244}