use std::sync::Arc;
use serde_json::Value;
use tokio_util::sync::CancellationToken;
use tracing::warn;
use crate::loop_::AgentEvent;
use crate::policy::{PostLoopPolicy, PostTurnPolicy, PreDispatchPolicy, PreTurnPolicy};
use crate::tool::{AgentTool, AgentToolResult, ToolFuture, ToolMetadata};
use crate::tool_name::compose_provider_safe_tool_name;
#[cfg(test)]
use crate::tool_name::{MAX_TOOL_NAME_LEN, TOOL_NAME_HASH_HEX_LEN};
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
fn priority(&self) -> i32 {
0
}
fn on_init(&self, _agent: &crate::Agent) {
}
fn pre_turn_policies(&self) -> Vec<Arc<dyn PreTurnPolicy>> {
vec![]
}
fn pre_dispatch_policies(&self) -> Vec<Arc<dyn PreDispatchPolicy>> {
vec![]
}
fn post_turn_policies(&self) -> Vec<Arc<dyn PostTurnPolicy>> {
vec![]
}
fn post_loop_policies(&self) -> Vec<Arc<dyn PostLoopPolicy>> {
vec![]
}
fn on_event(&self, _event: &AgentEvent) {
}
fn tools(&self) -> Vec<Arc<dyn AgentTool>> {
vec![]
}
}
pub(crate) fn dedup_insert_plugin(plugins: &mut Vec<Arc<dyn Plugin>>, plugin: Arc<dyn Plugin>) {
let name = plugin.name().to_owned();
if let Some(pos) = plugins.iter().position(|p| p.name() == name) {
warn!(plugin = %name, "replacing duplicate plugin");
plugins[pos] = plugin;
} else {
plugins.push(plugin);
}
}
pub(crate) fn priority_desc(plugin: &Arc<dyn Plugin>) -> std::cmp::Reverse<i32> {
std::cmp::Reverse(plugin.priority())
}
pub struct PluginRegistry {
plugins: Vec<Arc<dyn Plugin>>,
}
impl PluginRegistry {
pub fn new() -> Self {
Self {
plugins: Vec::new(),
}
}
pub fn register(&mut self, plugin: Arc<dyn Plugin>) {
dedup_insert_plugin(&mut self.plugins, plugin);
}
pub fn unregister(&mut self, name: &str) {
self.plugins.retain(|p| p.name() != name);
}
pub fn get(&self, name: &str) -> Option<&Arc<dyn Plugin>> {
self.plugins.iter().find(|p| p.name() == name)
}
pub fn list(&self) -> Vec<&Arc<dyn Plugin>> {
let mut sorted: Vec<_> = self.plugins.iter().collect();
sorted.sort_by_key(|p| priority_desc(p));
sorted
}
pub fn is_empty(&self) -> bool {
self.plugins.is_empty()
}
pub fn len(&self) -> usize {
self.plugins.len()
}
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new()
}
}
fn compose_namespaced_name(plugin_name: &str, tool_name: &str) -> String {
compose_provider_safe_tool_name(Some(plugin_name), tool_name)
}
pub struct NamespacedTool {
prefixed_name: String,
plugin_name: String,
inner: Arc<dyn AgentTool>,
}
impl NamespacedTool {
pub fn new(plugin_name: impl Into<String>, inner: Arc<dyn AgentTool>) -> Self {
let plugin_name = plugin_name.into();
let prefixed_name = compose_namespaced_name(&plugin_name, inner.name());
Self::with_name(plugin_name, inner, prefixed_name)
}
pub fn with_name(
plugin_name: impl Into<String>,
inner: Arc<dyn AgentTool>,
prefixed_name: impl Into<String>,
) -> Self {
let plugin_name = plugin_name.into();
Self {
prefixed_name: prefixed_name.into(),
plugin_name,
inner,
}
}
}
impl AgentTool for NamespacedTool {
fn name(&self) -> &str {
&self.prefixed_name
}
fn label(&self) -> &str {
self.inner.label()
}
fn description(&self) -> &str {
self.inner.description()
}
fn parameters_schema(&self) -> &Value {
self.inner.parameters_schema()
}
fn requires_approval(&self) -> bool {
self.inner.requires_approval()
}
fn metadata(&self) -> Option<ToolMetadata> {
let mut meta = self.inner.metadata().unwrap_or_default();
meta.namespace = Some(self.plugin_name.clone());
Some(meta)
}
fn execution_root(&self) -> Option<&std::path::Path> {
self.inner.execution_root()
}
fn approval_context(&self, params: &Value) -> Option<Value> {
self.inner.approval_context(params)
}
fn auth_config(&self) -> Option<crate::credential::AuthConfig> {
self.inner.auth_config()
}
fn execute(
&self,
tool_call_id: &str,
params: Value,
cancellation_token: CancellationToken,
on_update: Option<Box<dyn Fn(AgentToolResult) + Send + Sync>>,
state: Arc<std::sync::RwLock<crate::SessionState>>,
credential: Option<crate::credential::ResolvedCredential>,
) -> ToolFuture<'_> {
self.inner.execute(
tool_call_id,
params,
cancellation_token,
on_update,
state,
credential,
)
}
}
impl std::fmt::Debug for NamespacedTool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NamespacedTool")
.field("prefixed_name", &self.prefixed_name)
.field("plugin_name", &self.plugin_name)
.finish_non_exhaustive()
}
}
#[cfg(test)]
#[path = "plugin_tests.rs"]
mod tests;