#![warn(missing_docs, unused_crate_dependencies)]
use std::collections::HashMap;
pub use cai_core::Result;
#[derive(Debug, Clone)]
pub struct PluginConfig {
pub version: String,
pub skills: Vec<String>,
pub commands: Vec<String>,
}
impl Default for PluginConfig {
fn default() -> Self {
Self {
version: env!("CARGO_PKG_VERSION").to_string(),
skills: vec![
"cai.query".into(),
"cai.ingest".into(),
"cai.stats".into(),
"cai.tui".into(),
"cai.web".into(),
],
commands: vec![
"cai.query".into(),
"cai.ingest".into(),
"cai.stats".into(),
"cai.tui".into(),
"cai.web".into(),
],
}
}
}
pub trait Plugin {
fn config(&self) -> &PluginConfig;
fn handle_skill(&mut self, skill: &str, params: &serde_json::Value) -> Result<String>;
fn handle_command(&mut self, cmd: &str, args: &[String]) -> Result<String>;
fn on_session_start(&mut self, ctx: &SessionContext) -> Result<()>;
fn on_session_end(&mut self, ctx: &SessionContext) -> Result<()>;
}
#[derive(Debug, Clone)]
pub struct SessionContext {
pub session_id: String,
pub work_dir: String,
pub env: HashMap<String, String>,
}
pub struct CaiPlugin {
config: PluginConfig,
}
impl Default for CaiPlugin {
fn default() -> Self {
Self::new()
}
}
impl CaiPlugin {
pub fn new() -> Self {
Self {
config: PluginConfig::default(),
}
}
pub fn info(&self) -> serde_json::Value {
serde_json::json!({
"name": "cai",
"version": self.config.version,
"skills": self.config.skills,
"commands": self.config.commands,
})
}
}
impl Plugin for CaiPlugin {
fn config(&self) -> &PluginConfig {
&self.config
}
fn handle_skill(&mut self, skill: &str, params: &serde_json::Value) -> Result<String> {
match skill {
"cai.query" => self.handle_query(params),
"cai.ingest" => self.handle_ingest(params),
"cai.stats" => self.handle_stats(params),
"cai.tui" => self.handle_tui(params),
"cai.web" => self.handle_web(params),
_ => Err(cai_core::Error::Message(format!(
"Unknown skill: {}",
skill
))),
}
}
fn handle_command(&mut self, cmd: &str, args: &[String]) -> Result<String> {
match cmd {
"cai.query" | "cai.ingest" | "cai.stats" | "cai.tui" | "cai.web" => {
Ok(format!("Command delegated: {} {:?}", cmd, args))
}
_ => Err(cai_core::Error::Message(format!(
"Unknown command: {}",
cmd
))),
}
}
fn on_session_start(&mut self, _ctx: &SessionContext) -> Result<()> {
Ok(())
}
fn on_session_end(&mut self, _ctx: &SessionContext) -> Result<()> {
Ok(())
}
}
impl CaiPlugin {
fn handle_query(&mut self, params: &serde_json::Value) -> Result<String> {
let sql = params["sql"]
.as_str()
.ok_or_else(|| cai_core::Error::Message("Missing 'sql' parameter".into()))?;
let output = params["output"].as_str().unwrap_or("table");
Ok(format!("Query: {} (output: {})", sql, output))
}
fn handle_ingest(&mut self, params: &serde_json::Value) -> Result<String> {
let source = params["source"]
.as_str()
.ok_or_else(|| cai_core::Error::Message("Missing 'source' parameter".into()))?;
Ok(format!("Ingest from: {}", source))
}
fn handle_stats(&mut self, _params: &serde_json::Value) -> Result<String> {
Ok("Statistics".to_string())
}
fn handle_tui(&mut self, _params: &serde_json::Value) -> Result<String> {
Ok("Launch TUI".to_string())
}
fn handle_web(&mut self, _params: &serde_json::Value) -> Result<String> {
Ok("Launch web dashboard".to_string())
}
}
#[no_mangle]
pub extern "C" fn cai_plugin_create() -> *mut CaiPlugin {
Box::into_raw(Box::new(CaiPlugin::new()))
}
#[no_mangle]
pub unsafe extern "C" fn cai_plugin_destroy(ptr: *mut CaiPlugin) {
unsafe {
if !ptr.is_null() {
let _ = Box::from_raw(ptr);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_config() {
let plugin = CaiPlugin::new();
assert_eq!(plugin.config().skills.len(), 5);
assert!(plugin.config().skills.contains(&"cai.query".to_string()));
}
#[test]
fn test_handle_query() {
let mut plugin = CaiPlugin::new();
let params = serde_json::json!({"sql": "SELECT * FROM entries"});
let result = plugin.handle_skill("cai.query", ¶ms);
assert!(result.is_ok());
}
}