llm_memory_graph/plugin/
mod.rs

1//! Plugin system for extending LLM-Memory-Graph functionality
2//!
3//! This module provides a flexible plugin architecture that allows users to extend
4//! the core functionality of LLM-Memory-Graph with custom behavior. Plugins can hook
5//! into various operations to provide:
6//!
7//! - **Validation**: Content validation and rule enforcement
8//! - **Enrichment**: Automatic metadata enhancement
9//! - **Transformation**: Data transformation and normalization
10//! - **Auditing**: Custom audit logging and compliance tracking
11//! - **Integration**: External system integration
12//!
13//! # Architecture
14//!
15//! The plugin system is designed around these core concepts:
16//!
17//! - **Plugin Trait**: The main interface all plugins must implement
18//! - **Plugin Context**: Provides plugins with access to operation data
19//! - **Hook Points**: Specific points in the execution flow where plugins are called
20//! - **Plugin Manager**: Manages plugin lifecycle and execution
21//!
22//! # Example
23//!
24//! ```rust
25//! use llm_memory_graph::plugin::{Plugin, PluginBuilder, PluginContext, PluginError, PluginMetadata};
26//! use async_trait::async_trait;
27//!
28//! struct MyValidationPlugin {
29//!     metadata: PluginMetadata,
30//! }
31//!
32//! impl MyValidationPlugin {
33//!     pub fn new() -> Self {
34//!         let metadata = PluginBuilder::new("my_validator", "1.0.0")
35//!             .author("Your Name")
36//!             .description("Custom validation plugin")
37//!             .capability("validation")
38//!             .build();
39//!
40//!         Self { metadata }
41//!     }
42//! }
43//!
44//! #[async_trait]
45//! impl Plugin for MyValidationPlugin {
46//!     fn metadata(&self) -> &PluginMetadata {
47//!         &self.metadata
48//!     }
49//!
50//!     async fn before_create_node(&self, context: &PluginContext) -> Result<(), PluginError> {
51//!         // Custom validation logic
52//!         Ok(())
53//!     }
54//! }
55//! ```
56
57use async_trait::async_trait;
58use serde_json::Value;
59use std::collections::HashMap;
60use std::fmt;
61
62pub mod hooks;
63pub mod manager;
64pub mod registry;
65
66pub use hooks::{HookExecutor, HookPoint, HookRegistry};
67pub use manager::PluginManager;
68pub use registry::{PluginDiscovery, PluginRegistry};
69
70/// Plugin error type
71#[derive(Debug, thiserror::Error)]
72pub enum PluginError {
73    /// Plugin initialization failed
74    #[error("Plugin initialization failed: {0}")]
75    InitFailed(String),
76
77    /// Plugin hook execution failed
78    #[error("Plugin hook execution failed: {0}")]
79    HookFailed(String),
80
81    /// Plugin not found
82    #[error("Plugin not found: {0}")]
83    NotFound(String),
84
85    /// Plugin version incompatible
86    #[error("Plugin version incompatible: {0}")]
87    VersionMismatch(String),
88
89    /// Plugin configuration error
90    #[error("Plugin configuration error: {0}")]
91    ConfigError(String),
92
93    /// Plugin already registered
94    #[error("Plugin already registered: {0}")]
95    AlreadyRegistered(String),
96
97    /// Plugin disabled
98    #[error("Plugin disabled: {0}")]
99    Disabled(String),
100
101    /// General plugin error
102    #[error("Plugin error: {0}")]
103    General(String),
104}
105
106/// Plugin metadata
107///
108/// Contains information about a plugin, including its name, version,
109/// author, description, and capabilities.
110#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
111pub struct PluginMetadata {
112    /// Plugin name (unique identifier)
113    pub name: String,
114
115    /// Plugin version (semantic versioning)
116    pub version: String,
117
118    /// Plugin author
119    pub author: String,
120
121    /// Plugin description
122    pub description: String,
123
124    /// API version this plugin is compatible with
125    pub api_version: String,
126
127    /// List of capabilities this plugin provides
128    pub capabilities: Vec<String>,
129
130    /// Optional plugin configuration schema
131    pub config_schema: Option<Value>,
132}
133
134impl fmt::Display for PluginMetadata {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(
137            f,
138            "{} v{} by {} - {} (API: {})",
139            self.name, self.version, self.author, self.description, self.api_version
140        )
141    }
142}
143
144/// Plugin context for hooks
145///
146/// Provides plugins with information about the current operation,
147/// including the operation type, data being processed, and metadata.
148#[derive(Debug, Clone)]
149pub struct PluginContext {
150    /// Operation being performed (e.g., "create_node", "create_session")
151    pub operation: String,
152
153    /// Data associated with the operation (JSON format)
154    pub data: Value,
155
156    /// Additional metadata (key-value pairs)
157    pub metadata: HashMap<String, String>,
158
159    /// Timestamp when the context was created
160    pub timestamp: chrono::DateTime<chrono::Utc>,
161}
162
163impl PluginContext {
164    /// Create a new plugin context
165    pub fn new(operation: impl Into<String>, data: Value) -> Self {
166        Self {
167            operation: operation.into(),
168            data,
169            metadata: HashMap::new(),
170            timestamp: chrono::Utc::now(),
171        }
172    }
173
174    /// Add metadata to the context
175    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
176        self.metadata.insert(key.into(), value.into());
177        self
178    }
179
180    /// Get a metadata value
181    pub fn get_metadata(&self, key: &str) -> Option<&String> {
182        self.metadata.get(key)
183    }
184
185    /// Set a metadata value
186    pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
187        self.metadata.insert(key.into(), value.into());
188    }
189
190    /// Get the operation type
191    pub fn operation(&self) -> &str {
192        &self.operation
193    }
194
195    /// Get the data
196    pub fn data(&self) -> &Value {
197        &self.data
198    }
199}
200
201/// Plugin trait - all plugins must implement this
202///
203/// Plugins can selectively implement hooks they're interested in.
204/// All hooks are async and return a Result to allow for error handling.
205#[async_trait]
206pub trait Plugin: Send + Sync {
207    /// Get plugin metadata
208    fn metadata(&self) -> &PluginMetadata;
209
210    /// Initialize plugin
211    ///
212    /// Called once when the plugin is registered and enabled.
213    /// Use this to set up any resources, connections, or state.
214    /// Plugins should use interior mutability (e.g., Mutex, RwLock) for any state changes.
215    async fn init(&self) -> Result<(), PluginError> {
216        Ok(())
217    }
218
219    /// Shutdown plugin
220    ///
221    /// Called when the plugin is being disabled or the system is shutting down.
222    /// Use this to clean up resources, close connections, etc.
223    /// Plugins should use interior mutability (e.g., Mutex, RwLock) for any state changes.
224    async fn shutdown(&self) -> Result<(), PluginError> {
225        Ok(())
226    }
227
228    /// Hook: Before node creation
229    ///
230    /// Called before a node is created in the graph.
231    /// Can be used for validation, transformation, or enrichment.
232    async fn before_create_node(&self, _context: &PluginContext) -> Result<(), PluginError> {
233        Ok(())
234    }
235
236    /// Hook: After node creation
237    ///
238    /// Called after a node is successfully created.
239    /// Can be used for logging, notifications, or follow-up actions.
240    async fn after_create_node(&self, _context: &PluginContext) -> Result<(), PluginError> {
241        Ok(())
242    }
243
244    /// Hook: Before session creation
245    ///
246    /// Called before a session is created.
247    /// Can be used for validation, quota checking, or initialization.
248    async fn before_create_session(&self, _context: &PluginContext) -> Result<(), PluginError> {
249        Ok(())
250    }
251
252    /// Hook: After session creation
253    ///
254    /// Called after a session is successfully created.
255    /// Can be used for registration, logging, or setup.
256    async fn after_create_session(&self, _context: &PluginContext) -> Result<(), PluginError> {
257        Ok(())
258    }
259
260    /// Hook: Before query execution
261    ///
262    /// Called before a query is executed.
263    /// Can be used for query validation, transformation, or access control.
264    async fn before_query(&self, _context: &PluginContext) -> Result<(), PluginError> {
265        Ok(())
266    }
267
268    /// Hook: After query execution
269    ///
270    /// Called after a query is successfully executed.
271    /// Can be used for result transformation, caching, or logging.
272    async fn after_query(&self, _context: &PluginContext) -> Result<(), PluginError> {
273        Ok(())
274    }
275
276    /// Hook: Before edge creation
277    ///
278    /// Called before an edge is created in the graph.
279    /// Can be used for relationship validation or enforcement.
280    async fn before_create_edge(&self, _context: &PluginContext) -> Result<(), PluginError> {
281        Ok(())
282    }
283
284    /// Hook: After edge creation
285    ///
286    /// Called after an edge is successfully created.
287    /// Can be used for graph analysis or notifications.
288    async fn after_create_edge(&self, _context: &PluginContext) -> Result<(), PluginError> {
289        Ok(())
290    }
291
292    /// Generic hook execution (before)
293    ///
294    /// Routes to the appropriate before hook based on the hook name.
295    async fn before_hook(
296        &self,
297        hook_name: &str,
298        context: &PluginContext,
299    ) -> Result<(), PluginError> {
300        match hook_name {
301            "before_create_node" => self.before_create_node(context).await,
302            "before_create_session" => self.before_create_session(context).await,
303            "before_query" => self.before_query(context).await,
304            "before_create_edge" => self.before_create_edge(context).await,
305            _ => Ok(()),
306        }
307    }
308
309    /// Generic hook execution (after)
310    ///
311    /// Routes to the appropriate after hook based on the hook name.
312    async fn after_hook(
313        &self,
314        hook_name: &str,
315        context: &PluginContext,
316    ) -> Result<(), PluginError> {
317        match hook_name {
318            "after_create_node" => self.after_create_node(context).await,
319            "after_create_session" => self.after_create_session(context).await,
320            "after_query" => self.after_query(context).await,
321            "after_create_edge" => self.after_create_edge(context).await,
322            _ => Ok(()),
323        }
324    }
325}
326
327/// Plugin builder for configuration
328///
329/// Provides a fluent API for building plugin metadata.
330///
331/// # Example
332///
333/// ```rust
334/// use llm_memory_graph::plugin::PluginBuilder;
335///
336/// let metadata = PluginBuilder::new("my_plugin", "1.0.0")
337///     .author("John Doe")
338///     .description("My custom plugin")
339///     .capability("validation")
340///     .capability("enrichment")
341///     .build();
342/// ```
343pub struct PluginBuilder {
344    metadata: PluginMetadata,
345}
346
347impl PluginBuilder {
348    /// Create a new plugin builder
349    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
350        Self {
351            metadata: PluginMetadata {
352                name: name.into(),
353                version: version.into(),
354                author: String::new(),
355                description: String::new(),
356                api_version: "1.0.0".to_string(),
357                capabilities: Vec::new(),
358                config_schema: None,
359            },
360        }
361    }
362
363    /// Set the plugin author
364    pub fn author(mut self, author: impl Into<String>) -> Self {
365        self.metadata.author = author.into();
366        self
367    }
368
369    /// Set the plugin description
370    pub fn description(mut self, description: impl Into<String>) -> Self {
371        self.metadata.description = description.into();
372        self
373    }
374
375    /// Set the API version
376    pub fn api_version(mut self, version: impl Into<String>) -> Self {
377        self.metadata.api_version = version.into();
378        self
379    }
380
381    /// Add a capability
382    pub fn capability(mut self, capability: impl Into<String>) -> Self {
383        self.metadata.capabilities.push(capability.into());
384        self
385    }
386
387    /// Set the configuration schema
388    pub fn config_schema(mut self, schema: Value) -> Self {
389        self.metadata.config_schema = Some(schema);
390        self
391    }
392
393    /// Build the plugin metadata
394    pub fn build(self) -> PluginMetadata {
395        self.metadata
396    }
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402
403    #[test]
404    fn test_plugin_builder() {
405        let metadata = PluginBuilder::new("test_plugin", "1.0.0")
406            .author("Test Author")
407            .description("Test plugin")
408            .capability("validation")
409            .capability("enrichment")
410            .build();
411
412        assert_eq!(metadata.name, "test_plugin");
413        assert_eq!(metadata.version, "1.0.0");
414        assert_eq!(metadata.author, "Test Author");
415        assert_eq!(metadata.capabilities.len(), 2);
416    }
417
418    #[test]
419    fn test_plugin_context() {
420        let context = PluginContext::new("test_operation", serde_json::json!({"key": "value"}))
421            .with_metadata("test_key", "test_value");
422
423        assert_eq!(context.operation(), "test_operation");
424        assert_eq!(
425            context.get_metadata("test_key"),
426            Some(&"test_value".to_string())
427        );
428    }
429}