1#![warn(missing_docs, unused_crate_dependencies)]
6
7use std::collections::HashMap;
8
9pub use cai_core::Result;
10
11#[derive(Debug, Clone)]
13pub struct PluginConfig {
14 pub version: String,
16 pub skills: Vec<String>,
18 pub commands: Vec<String>,
20}
21
22impl Default for PluginConfig {
23 fn default() -> Self {
24 Self {
25 version: env!("CARGO_PKG_VERSION").to_string(),
26 skills: vec![
27 "cai.query".into(),
28 "cai.ingest".into(),
29 "cai.stats".into(),
30 "cai.tui".into(),
31 "cai.web".into(),
32 ],
33 commands: vec![
34 "cai.query".into(),
35 "cai.ingest".into(),
36 "cai.stats".into(),
37 "cai.tui".into(),
38 "cai.web".into(),
39 ],
40 }
41 }
42}
43
44pub trait Plugin {
46 fn config(&self) -> &PluginConfig;
48
49 fn handle_skill(&mut self, skill: &str, params: &serde_json::Value) -> Result<String>;
51
52 fn handle_command(&mut self, cmd: &str, args: &[String]) -> Result<String>;
54
55 fn on_session_start(&mut self, ctx: &SessionContext) -> Result<()>;
57
58 fn on_session_end(&mut self, ctx: &SessionContext) -> Result<()>;
60}
61
62#[derive(Debug, Clone)]
64pub struct SessionContext {
65 pub session_id: String,
67 pub work_dir: String,
69 pub env: HashMap<String, String>,
71}
72
73pub struct CaiPlugin {
75 config: PluginConfig,
76}
77
78impl Default for CaiPlugin {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84impl CaiPlugin {
85 pub fn new() -> Self {
87 Self {
88 config: PluginConfig::default(),
89 }
90 }
91
92 pub fn info(&self) -> serde_json::Value {
94 serde_json::json!({
95 "name": "cai",
96 "version": self.config.version,
97 "skills": self.config.skills,
98 "commands": self.config.commands,
99 })
100 }
101}
102
103impl Plugin for CaiPlugin {
104 fn config(&self) -> &PluginConfig {
105 &self.config
106 }
107
108 fn handle_skill(&mut self, skill: &str, params: &serde_json::Value) -> Result<String> {
109 match skill {
110 "cai.query" => self.handle_query(params),
111 "cai.ingest" => self.handle_ingest(params),
112 "cai.stats" => self.handle_stats(params),
113 "cai.tui" => self.handle_tui(params),
114 "cai.web" => self.handle_web(params),
115 _ => Err(cai_core::Error::Message(format!(
116 "Unknown skill: {}",
117 skill
118 ))),
119 }
120 }
121
122 fn handle_command(&mut self, cmd: &str, args: &[String]) -> Result<String> {
123 match cmd {
125 "cai.query" | "cai.ingest" | "cai.stats" | "cai.tui" | "cai.web" => {
126 Ok(format!("Command delegated: {} {:?}", cmd, args))
127 }
128 _ => Err(cai_core::Error::Message(format!(
129 "Unknown command: {}",
130 cmd
131 ))),
132 }
133 }
134
135 fn on_session_start(&mut self, _ctx: &SessionContext) -> Result<()> {
136 Ok(())
138 }
139
140 fn on_session_end(&mut self, _ctx: &SessionContext) -> Result<()> {
141 Ok(())
143 }
144}
145
146impl CaiPlugin {
147 fn handle_query(&mut self, params: &serde_json::Value) -> Result<String> {
148 let sql = params["sql"]
149 .as_str()
150 .ok_or_else(|| cai_core::Error::Message("Missing 'sql' parameter".into()))?;
151 let output = params["output"].as_str().unwrap_or("table");
152 Ok(format!("Query: {} (output: {})", sql, output))
153 }
154
155 fn handle_ingest(&mut self, params: &serde_json::Value) -> Result<String> {
156 let source = params["source"]
157 .as_str()
158 .ok_or_else(|| cai_core::Error::Message("Missing 'source' parameter".into()))?;
159 Ok(format!("Ingest from: {}", source))
160 }
161
162 fn handle_stats(&mut self, _params: &serde_json::Value) -> Result<String> {
163 Ok("Statistics".to_string())
164 }
165
166 fn handle_tui(&mut self, _params: &serde_json::Value) -> Result<String> {
167 Ok("Launch TUI".to_string())
168 }
169
170 fn handle_web(&mut self, _params: &serde_json::Value) -> Result<String> {
171 Ok("Launch web dashboard".to_string())
172 }
173}
174
175#[no_mangle]
185pub extern "C" fn cai_plugin_create() -> *mut CaiPlugin {
186 Box::into_raw(Box::new(CaiPlugin::new()))
187}
188
189#[no_mangle]
197pub unsafe extern "C" fn cai_plugin_destroy(ptr: *mut CaiPlugin) {
198 unsafe {
199 if !ptr.is_null() {
200 let _ = Box::from_raw(ptr);
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_plugin_config() {
211 let plugin = CaiPlugin::new();
212 assert_eq!(plugin.config().skills.len(), 5);
213 assert!(plugin.config().skills.contains(&"cai.query".to_string()));
214 }
215
216 #[test]
217 fn test_handle_query() {
218 let mut plugin = CaiPlugin::new();
219 let params = serde_json::json!({"sql": "SELECT * FROM entries"});
220 let result = plugin.handle_skill("cai.query", ¶ms);
221 assert!(result.is_ok());
222 }
223}