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}