use std::sync::Arc;
use async_trait::async_trait;
use serde_json::Value;
#[async_trait]
pub trait LlmHandle: Send + Sync {
async fn complete(&self, prompt: &str) -> Result<String, String>;
}
#[derive(Debug, Clone)]
pub enum PluginEvent {
SessionStart { session_id: String },
SessionEnd { session_id: String },
BeforeToolCall { tool: String, args: Value },
AfterToolCall { tool: String, result: String },
}
pub struct PluginContext {
pub session_id: String,
pub llm: Option<Arc<dyn LlmHandle>>,
}
pub trait EdgecrabPlugin: Send + Sync {
fn name(&self) -> &str;
fn provider_aliases(&self) -> Vec<String> {
Vec::new()
}
fn tool_override_hint(&self, _tool_name: &str) -> Option<String> {
None
}
fn on_event(&self, _event: &PluginEvent, _ctx: &PluginContext) {}
}
#[derive(Default)]
pub struct PluginHost {
plugins: Vec<Arc<dyn EdgecrabPlugin>>,
}
impl PluginHost {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, plugin: Arc<dyn EdgecrabPlugin>) {
self.plugins.push(plugin);
}
pub fn plugins(&self) -> &[Arc<dyn EdgecrabPlugin>] {
&self.plugins
}
pub fn provider_aliases(&self) -> Vec<String> {
let mut out = Vec::new();
for p in &self.plugins {
out.extend(p.provider_aliases());
}
out.sort();
out.dedup();
out
}
pub fn emit(&self, event: &PluginEvent, ctx: &PluginContext) {
for p in &self.plugins {
p.on_event(event, ctx);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Dummy;
impl EdgecrabPlugin for Dummy {
fn name(&self) -> &str {
"dummy"
}
fn provider_aliases(&self) -> Vec<String> {
vec!["dummy/echo".into()]
}
}
#[test]
fn host_collects_aliases() {
let mut host = PluginHost::new();
host.register(Arc::new(Dummy));
assert_eq!(host.provider_aliases(), vec!["dummy/echo".to_string()]);
}
}