openai_agents_rust/plugin/
loader.rs

1use crate::plugin::traits::Plugin;
2use libloading;
3use std::path::Path;
4
5/// Simple plugin registry that holds a list of plugins.
6pub struct PluginRegistry {
7    plugins: Vec<Box<dyn Plugin + Send + Sync>>,
8}
9
10impl PluginRegistry {
11    /// Creates a new, empty registry.
12    pub fn new() -> Self {
13        Self {
14            plugins: Vec::new(),
15        }
16    }
17
18    /// Registers a plugin.
19    pub fn register<P: Plugin + Send + Sync + 'static>(&mut self, plugin: P) {
20        self.plugins.push(Box::new(plugin));
21    }
22
23    /// Register a plugin that is already boxed.
24    pub fn register_box(&mut self, plugin: Box<dyn Plugin + Send + Sync>) {
25        self.plugins.push(plugin);
26    }
27
28    /// Returns an iterator over the registered plugins.
29    pub fn iter(&self) -> impl Iterator<Item = &Box<dyn Plugin + Send + Sync>> {
30        self.plugins.iter()
31    }
32
33    /// Load plugins from a directory.
34    ///
35    /// This implementation scans the given directory for shared library files
36    /// (`.so` on Linux, `.dylib` on macOS). Each library is expected to expose a
37    /// C‑compatible symbol named `plugin_create` with the signature:
38    ///
39    /// ```text
40    /// unsafe extern "C" fn() -> *mut dyn Plugin
41    /// ```
42    ///
43    /// The function should allocate a concrete type that implements `Plugin` and
44    /// return a raw pointer. The loader will convert the raw pointer into a
45    /// `Box<dyn Plugin>` and register it. The loaded library is deliberately
46    /// leaked (`std::mem::forget`) to keep it alive for the duration of the
47    /// program; a production implementation would store the `Library` handles
48    /// inside the registry to manage their lifetimes.
49    pub fn load_from_dir<P: AsRef<Path>>(path: P) -> Result<Self, crate::error::AgentError> {
50        let mut registry = Self::new();
51
52        let entries =
53            std::fs::read_dir(&path).map_err(|e| crate::error::AgentError::Other(e.to_string()))?;
54
55        for entry in entries {
56            let entry = entry.map_err(|e| crate::error::AgentError::Other(e.to_string()))?;
57            let lib_path = entry.path();
58
59            // Only consider files with typical shared‑library extensions.
60            if let Some(ext) = lib_path.extension().and_then(|s| s.to_str()) {
61                if ext != "so" && ext != "dylib" {
62                    continue;
63                }
64            } else {
65                continue;
66            }
67
68            // Load the library.
69            let lib = unsafe {
70                libloading::Library::new(&lib_path)
71                    .map_err(|e| crate::error::AgentError::Other(e.to_string()))?
72            };
73
74            // Look for the expected symbol.
75            unsafe {
76                // The plugin constructor must return a boxed plugin that satisfies Send + Sync.
77                let ctor: libloading::Symbol<unsafe fn() -> *mut (dyn Plugin + Send + Sync)> = lib
78                    .get(b"plugin_create")
79                    .map_err(|e| crate::error::AgentError::Other(e.to_string()))?;
80
81                // Call the constructor to obtain a raw pointer.
82                let raw = ctor();
83
84                // Register the plugin directly from the raw pointer.
85                registry.register_box(Box::from_raw(raw));
86            }
87
88            // Keep the library alive for the program's lifetime.
89            // In this simple implementation we deliberately leak it.
90            std::mem::forget(lib);
91        }
92
93        Ok(registry)
94    }
95}
96impl Default for PluginRegistry {
97    fn default() -> Self {
98        Self::new()
99    }
100}