use serde::{Deserialize, Serialize};
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
pub type PluginResult<T> = anyhow::Result<T>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReloadStrategy {
Immediate,
Debounced(std::time::Duration),
Manual,
OnIdle,
}
impl Default for ReloadStrategy {
fn default() -> Self {
Self::Debounced(std::time::Duration::from_secs(1))
}
}
#[derive(Debug, Clone)]
pub struct HotReloadConfig {
pub strategy: ReloadStrategy,
pub preserve_state: bool,
pub auto_rollback: bool,
pub max_reload_attempts: u32,
pub reload_cooldown: std::time::Duration,
}
impl Default for HotReloadConfig {
fn default() -> Self {
Self {
strategy: ReloadStrategy::default(),
preserve_state: true,
auto_rollback: true,
max_reload_attempts: 3,
reload_cooldown: std::time::Duration::from_secs(5),
}
}
}
impl HotReloadConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_strategy(mut self, strategy: ReloadStrategy) -> Self {
self.strategy = strategy;
self
}
pub fn with_preserve_state(mut self, preserve: bool) -> Self {
self.preserve_state = preserve;
self
}
pub fn with_auto_rollback(mut self, auto_rollback: bool) -> Self {
self.auto_rollback = auto_rollback;
self
}
pub fn with_max_attempts(mut self, max_attempts: u32) -> Self {
self.max_reload_attempts = max_attempts;
self
}
pub fn with_reload_cooldown(mut self, cooldown: std::time::Duration) -> Self {
self.reload_cooldown = cooldown;
self
}
}
#[derive(Debug, Clone)]
pub enum ReloadEvent {
ReloadStarted {
plugin_id: String,
path: std::path::PathBuf,
},
ReloadCompleted {
plugin_id: String,
path: std::path::PathBuf,
success: bool,
duration: std::time::Duration,
},
ReloadFailed {
plugin_id: String,
path: std::path::PathBuf,
error: String,
attempt: u32,
},
RollbackTriggered { plugin_id: String, reason: String },
PluginDiscovered { path: std::path::PathBuf },
PluginRemoved {
plugin_id: String,
path: std::path::PathBuf,
},
StatePreserved { plugin_id: String },
StateRestored { plugin_id: String },
}
#[async_trait::async_trait]
pub trait HotReloadable: Send + Sync {
async fn refresh(&self) -> PluginResult<()>;
async fn save_state(&self) -> PluginResult<()> {
Ok(())
}
async fn restore_state(&self) -> PluginResult<()> {
Ok(())
}
}
#[async_trait::async_trait]
pub trait AgentPlugin: Send + Sync {
fn metadata(&self) -> &PluginMetadata;
fn plugin_id(&self) -> &str {
&self.metadata().id
}
fn plugin_type(&self) -> PluginType {
self.metadata().plugin_type.clone()
}
fn state(&self) -> PluginState;
async fn load(&mut self, ctx: &PluginContext) -> PluginResult<()>;
async fn init_plugin(&mut self) -> PluginResult<()>;
async fn start(&mut self) -> PluginResult<()>;
async fn pause(&mut self) -> PluginResult<()> {
Ok(())
}
async fn resume(&mut self) -> PluginResult<()> {
Ok(())
}
async fn stop(&mut self) -> PluginResult<()>;
async fn unload(&mut self) -> PluginResult<()>;
async fn execute(&mut self, input: String) -> PluginResult<String>;
async fn health_check(&self) -> PluginResult<bool> {
Ok(self.state() == PluginState::Running)
}
fn stats(&self) -> HashMap<String, serde_json::Value> {
HashMap::new()
}
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PluginType {
LLM,
Tool,
Storage,
Memory,
VectorDB,
Communication,
Monitor,
Skill,
Custom(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PluginState {
Unloaded,
Loading,
Loaded,
Running,
Paused,
Error(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
pub enum PluginPriority {
Low = 0,
#[default]
Normal = 50,
High = 100,
Critical = 200,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub id: String,
pub name: String,
pub version: String,
pub description: String,
pub plugin_type: PluginType,
pub priority: PluginPriority,
pub dependencies: Vec<String>,
pub capabilities: Vec<String>,
pub author: Option<String>,
}
impl PluginMetadata {
pub fn new(id: &str, name: &str, plugin_type: PluginType) -> Self {
Self {
id: id.to_string(),
name: name.to_string(),
version: "1.0.0".to_string(),
description: String::new(),
plugin_type,
priority: PluginPriority::Normal,
dependencies: Vec::new(),
capabilities: Vec::new(),
author: None,
}
}
pub fn with_version(mut self, version: &str) -> Self {
self.version = version.to_string();
self
}
pub fn with_description(mut self, desc: &str) -> Self {
self.description = desc.to_string();
self
}
pub fn with_priority(mut self, priority: PluginPriority) -> Self {
self.priority = priority;
self
}
pub fn with_dependency(mut self, dep_id: &str) -> Self {
self.dependencies.push(dep_id.to_string());
self
}
pub fn with_capability(mut self, cap: &str) -> Self {
self.capabilities.push(cap.to_string());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PluginConfig {
pub settings: HashMap<String, serde_json::Value>,
pub enabled: bool,
pub auto_start: bool,
}
impl PluginConfig {
pub fn new() -> Self {
Self {
settings: HashMap::new(),
enabled: true,
auto_start: true,
}
}
pub fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
self.settings
.get(key)
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn set<T: Serialize>(&mut self, key: &str, value: T) {
if let Ok(v) = serde_json::to_value(value) {
self.settings.insert(key.to_string(), v);
}
}
pub fn get_string(&self, key: &str) -> Option<String> {
self.get(key)
}
pub fn get_bool(&self, key: &str) -> Option<bool> {
self.get(key)
}
pub fn get_i64(&self, key: &str) -> Option<i64> {
self.get(key)
}
}
#[derive(Debug, Default)]
pub struct PluginContext {
pub agent_id: String,
shared_state: Arc<RwLock<HashMap<String, Box<dyn Any + Send + Sync>>>>,
pub config: PluginConfig,
event_tx: Option<tokio::sync::mpsc::Sender<PluginEvent>>,
}
impl PluginContext {
pub fn new(agent_id: &str) -> Self {
Self {
agent_id: agent_id.to_string(),
shared_state: Arc::new(RwLock::new(HashMap::new())),
config: PluginConfig::new(),
event_tx: None,
}
}
pub fn with_config(mut self, config: PluginConfig) -> Self {
self.config = config;
self
}
pub fn with_event_sender(mut self, tx: tokio::sync::mpsc::Sender<PluginEvent>) -> Self {
self.event_tx = Some(tx);
self
}
pub async fn get_state<T: Clone + Send + Sync + 'static>(&self, key: &str) -> Option<T> {
let state = self.shared_state.read().await;
state.get(key).and_then(|v| v.downcast_ref::<T>().cloned())
}
pub async fn set_state<T: Clone + Send + Sync + 'static>(&self, key: &str, value: T) {
let mut state = self.shared_state.write().await;
state.insert(key.to_string(), Box::new(value));
}
pub async fn emit_event(&self, event: PluginEvent) -> anyhow::Result<()> {
if let Some(ref tx) = self.event_tx {
tx.send(event)
.await
.map_err(|e| anyhow::anyhow!("Failed to send event: {}", e))?;
}
Ok(())
}
}
impl Clone for PluginContext {
fn clone(&self) -> Self {
Self {
agent_id: self.agent_id.clone(),
shared_state: self.shared_state.clone(),
config: self.config.clone(),
event_tx: self.event_tx.clone(),
}
}
}
#[derive(Debug, Clone)]
pub enum PluginEvent {
PluginLoaded { plugin_id: String },
PluginUnloaded { plugin_id: String },
StateChanged {
plugin_id: String,
old_state: PluginState,
new_state: PluginState,
},
PluginError { plugin_id: String, error: String },
Custom {
plugin_id: String,
event_type: String,
data: Vec<u8>,
},
}