@elizaos/interop 2.0.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +436 -0
  3. package/dist/packages/interop/tsconfig.tsbuildinfo +1 -0
  4. package/dist/tsconfig.tsbuildinfo +1 -0
  5. package/dist/typescript/index.d.ts +33 -0
  6. package/dist/typescript/index.d.ts.map +1 -0
  7. package/dist/typescript/index.js +121 -0
  8. package/dist/typescript/python-bridge.d.ts +80 -0
  9. package/dist/typescript/python-bridge.d.ts.map +1 -0
  10. package/dist/typescript/python-bridge.js +334 -0
  11. package/dist/typescript/types.d.ts +301 -0
  12. package/dist/typescript/types.d.ts.map +1 -0
  13. package/dist/typescript/types.js +10 -0
  14. package/dist/typescript/wasm-loader.d.ts +32 -0
  15. package/dist/typescript/wasm-loader.d.ts.map +1 -0
  16. package/dist/typescript/wasm-loader.js +269 -0
  17. package/package.json +43 -0
  18. package/python/__init__.py +50 -0
  19. package/python/__pycache__/__init__.cpython-313.pyc +0 -0
  20. package/python/__pycache__/rust_ffi.cpython-313.pyc +0 -0
  21. package/python/__pycache__/ts_bridge.cpython-313.pyc +0 -0
  22. package/python/__pycache__/wasm_loader.cpython-313.pyc +0 -0
  23. package/python/bridge_server.py +505 -0
  24. package/python/rust_ffi.py +418 -0
  25. package/python/tests/__init__.py +1 -0
  26. package/python/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  27. package/python/tests/__pycache__/test_bridge_server.cpython-313-pytest-9.0.2.pyc +0 -0
  28. package/python/tests/__pycache__/test_interop.cpython-313-pytest-9.0.2.pyc +0 -0
  29. package/python/tests/__pycache__/test_rust_ffi.cpython-313-pytest-9.0.2.pyc +0 -0
  30. package/python/tests/__pycache__/test_rust_ffi_loader.cpython-313-pytest-9.0.2.pyc +0 -0
  31. package/python/tests/test_bridge_server.py +525 -0
  32. package/python/tests/test_interop.py +319 -0
  33. package/python/tests/test_rust_ffi.py +352 -0
  34. package/python/tests/test_rust_ffi_loader.py +71 -0
  35. package/python/ts_bridge.py +452 -0
  36. package/python/ts_bridge_runner.mjs +284 -0
  37. package/python/wasm_loader.py +517 -0
  38. package/rust/ffi_exports.rs +362 -0
  39. package/rust/mod.rs +21 -0
  40. package/rust/py_loader.rs +412 -0
  41. package/rust/ts_loader.rs +467 -0
  42. package/rust/wasm_plugin.rs +320 -0
@@ -0,0 +1,362 @@
1
+ //! FFI Exports for elizaOS Rust Plugins
2
+ //!
3
+ //! This module provides macros and utilities for exposing Rust plugins
4
+ //! to Python and other FFI-capable languages.
5
+ //!
6
+ //! # Example
7
+ //!
8
+ //! ```rust
9
+ //! use elizaos::interop::ffi_exports::*;
10
+ //! use elizaos::types::Plugin;
11
+ //!
12
+ //! // Create your plugin
13
+ //! let plugin = Plugin::new("my-plugin", "A cool plugin");
14
+ //!
15
+ //! // Export it for FFI
16
+ //! elizaos_export_plugin!(plugin);
17
+ //! ```
18
+
19
+ use std::ffi::{CStr, CString};
20
+ use std::os::raw::{c_char, c_int};
21
+ use std::sync::Mutex;
22
+
23
+ /// Global plugin instance storage
24
+ static PLUGIN_INSTANCE: Mutex<Option<Box<dyn PluginExport>>> = Mutex::new(None);
25
+
26
+ /// Trait for plugins that can be exported via FFI
27
+ pub trait PluginExport: Send + Sync {
28
+ /// Get the plugin manifest as JSON
29
+ fn get_manifest(&self) -> String;
30
+
31
+ /// Initialize the plugin with config JSON
32
+ fn init(&self, config_json: &str) -> Result<(), String>;
33
+
34
+ /// Validate an action
35
+ fn validate_action(&self, name: &str, memory_json: &str, state_json: &str) -> bool;
36
+
37
+ /// Invoke an action and return result JSON
38
+ fn invoke_action(
39
+ &self,
40
+ name: &str,
41
+ memory_json: &str,
42
+ state_json: &str,
43
+ options_json: &str,
44
+ ) -> String;
45
+
46
+ /// Get provider data and return result JSON
47
+ fn get_provider(&self, name: &str, memory_json: &str, state_json: &str) -> String;
48
+
49
+ /// Validate an evaluator
50
+ fn validate_evaluator(&self, name: &str, memory_json: &str, state_json: &str) -> bool;
51
+
52
+ /// Invoke an evaluator and return result JSON
53
+ fn invoke_evaluator(&self, name: &str, memory_json: &str, state_json: &str) -> String;
54
+ }
55
+
56
+ /// Register a plugin for FFI export
57
+ pub fn register_plugin<P: PluginExport + 'static>(plugin: P) {
58
+ let mut instance = PLUGIN_INSTANCE.lock().unwrap();
59
+ *instance = Some(Box::new(plugin));
60
+ }
61
+
62
+ /// Helper to convert C string to Rust string
63
+ fn cstr_to_string(ptr: *const c_char) -> Option<String> {
64
+ if ptr.is_null() {
65
+ return None;
66
+ }
67
+ unsafe {
68
+ match CStr::from_ptr(ptr).to_str() {
69
+ Ok(s) => Some(s.to_string()),
70
+ Err(_) => None,
71
+ }
72
+ }
73
+ }
74
+
75
+ /// Helper to convert Rust string to C string (caller must free)
76
+ fn string_to_cstr(s: String) -> *mut c_char {
77
+ match CString::new(s) {
78
+ Ok(cs) => cs.into_raw(),
79
+ Err(_) => std::ptr::null_mut(),
80
+ }
81
+ }
82
+
83
+ // ============================================================================
84
+ // FFI Export Functions
85
+ // ============================================================================
86
+
87
+ /// Get the plugin manifest as JSON
88
+ ///
89
+ /// # Safety
90
+ /// The returned string must be freed with `elizaos_free_string`
91
+ #[no_mangle]
92
+ pub extern "C" fn elizaos_get_manifest() -> *mut c_char {
93
+ let instance = PLUGIN_INSTANCE.lock().unwrap();
94
+ match &*instance {
95
+ Some(plugin) => string_to_cstr(plugin.get_manifest()),
96
+ None => string_to_cstr(r#"{"error": "No plugin registered"}"#.to_string()),
97
+ }
98
+ }
99
+
100
+ /// Initialize the plugin with configuration
101
+ ///
102
+ /// # Safety
103
+ /// `config_json` must be a valid null-terminated C string
104
+ #[no_mangle]
105
+ pub extern "C" fn elizaos_init(config_json: *const c_char) -> c_int {
106
+ let config = match cstr_to_string(config_json) {
107
+ Some(s) => s,
108
+ None => return -1,
109
+ };
110
+
111
+ let instance = PLUGIN_INSTANCE.lock().unwrap();
112
+ match &*instance {
113
+ Some(plugin) => match plugin.init(&config) {
114
+ Ok(()) => 0,
115
+ Err(_) => -1,
116
+ },
117
+ None => -1,
118
+ }
119
+ }
120
+
121
+ /// Validate an action
122
+ ///
123
+ /// # Safety
124
+ /// All string parameters must be valid null-terminated C strings
125
+ #[no_mangle]
126
+ pub extern "C" fn elizaos_validate_action(
127
+ name: *const c_char,
128
+ memory_json: *const c_char,
129
+ state_json: *const c_char,
130
+ ) -> c_int {
131
+ let name = match cstr_to_string(name) {
132
+ Some(s) => s,
133
+ None => return 0,
134
+ };
135
+ let memory = cstr_to_string(memory_json).unwrap_or_else(|| "null".to_string());
136
+ let state = cstr_to_string(state_json).unwrap_or_else(|| "null".to_string());
137
+
138
+ let instance = PLUGIN_INSTANCE.lock().unwrap();
139
+ match &*instance {
140
+ Some(plugin) => {
141
+ if plugin.validate_action(&name, &memory, &state) {
142
+ 1
143
+ } else {
144
+ 0
145
+ }
146
+ }
147
+ None => 0,
148
+ }
149
+ }
150
+
151
+ /// Invoke an action
152
+ ///
153
+ /// # Safety
154
+ /// All string parameters must be valid null-terminated C strings.
155
+ /// The returned string must be freed with `elizaos_free_string`
156
+ #[no_mangle]
157
+ pub extern "C" fn elizaos_invoke_action(
158
+ name: *const c_char,
159
+ memory_json: *const c_char,
160
+ state_json: *const c_char,
161
+ options_json: *const c_char,
162
+ ) -> *mut c_char {
163
+ let name = match cstr_to_string(name) {
164
+ Some(s) => s,
165
+ None => return string_to_cstr(r#"{"success": false, "error": "Invalid action name"}"#.to_string()),
166
+ };
167
+ let memory = cstr_to_string(memory_json).unwrap_or_else(|| "null".to_string());
168
+ let state = cstr_to_string(state_json).unwrap_or_else(|| "null".to_string());
169
+ let options = cstr_to_string(options_json).unwrap_or_else(|| "{}".to_string());
170
+
171
+ let instance = PLUGIN_INSTANCE.lock().unwrap();
172
+ match &*instance {
173
+ Some(plugin) => string_to_cstr(plugin.invoke_action(&name, &memory, &state, &options)),
174
+ None => string_to_cstr(r#"{"success": false, "error": "No plugin registered"}"#.to_string()),
175
+ }
176
+ }
177
+
178
+ /// Get provider data
179
+ ///
180
+ /// # Safety
181
+ /// All string parameters must be valid null-terminated C strings.
182
+ /// The returned string must be freed with `elizaos_free_string`
183
+ #[no_mangle]
184
+ pub extern "C" fn elizaos_get_provider(
185
+ name: *const c_char,
186
+ memory_json: *const c_char,
187
+ state_json: *const c_char,
188
+ ) -> *mut c_char {
189
+ let name = match cstr_to_string(name) {
190
+ Some(s) => s,
191
+ None => return string_to_cstr(r#"{"text": null, "values": null, "data": null}"#.to_string()),
192
+ };
193
+ let memory = cstr_to_string(memory_json).unwrap_or_else(|| "null".to_string());
194
+ let state = cstr_to_string(state_json).unwrap_or_else(|| "{}".to_string());
195
+
196
+ let instance = PLUGIN_INSTANCE.lock().unwrap();
197
+ match &*instance {
198
+ Some(plugin) => string_to_cstr(plugin.get_provider(&name, &memory, &state)),
199
+ None => string_to_cstr(r#"{"text": null, "values": null, "data": null}"#.to_string()),
200
+ }
201
+ }
202
+
203
+ /// Validate an evaluator
204
+ ///
205
+ /// # Safety
206
+ /// All string parameters must be valid null-terminated C strings
207
+ #[no_mangle]
208
+ pub extern "C" fn elizaos_validate_evaluator(
209
+ name: *const c_char,
210
+ memory_json: *const c_char,
211
+ state_json: *const c_char,
212
+ ) -> c_int {
213
+ let name = match cstr_to_string(name) {
214
+ Some(s) => s,
215
+ None => return 0,
216
+ };
217
+ let memory = cstr_to_string(memory_json).unwrap_or_else(|| "null".to_string());
218
+ let state = cstr_to_string(state_json).unwrap_or_else(|| "null".to_string());
219
+
220
+ let instance = PLUGIN_INSTANCE.lock().unwrap();
221
+ match &*instance {
222
+ Some(plugin) => {
223
+ if plugin.validate_evaluator(&name, &memory, &state) {
224
+ 1
225
+ } else {
226
+ 0
227
+ }
228
+ }
229
+ None => 0,
230
+ }
231
+ }
232
+
233
+ /// Invoke an evaluator
234
+ ///
235
+ /// # Safety
236
+ /// All string parameters must be valid null-terminated C strings.
237
+ /// The returned string must be freed with `elizaos_free_string`
238
+ #[no_mangle]
239
+ pub extern "C" fn elizaos_invoke_evaluator(
240
+ name: *const c_char,
241
+ memory_json: *const c_char,
242
+ state_json: *const c_char,
243
+ ) -> *mut c_char {
244
+ let name = match cstr_to_string(name) {
245
+ Some(s) => s,
246
+ None => return string_to_cstr("null".to_string()),
247
+ };
248
+ let memory = cstr_to_string(memory_json).unwrap_or_else(|| "null".to_string());
249
+ let state = cstr_to_string(state_json).unwrap_or_else(|| "null".to_string());
250
+
251
+ let instance = PLUGIN_INSTANCE.lock().unwrap();
252
+ match &*instance {
253
+ Some(plugin) => string_to_cstr(plugin.invoke_evaluator(&name, &memory, &state)),
254
+ None => string_to_cstr("null".to_string()),
255
+ }
256
+ }
257
+
258
+ /// Free a string returned by any of the above functions
259
+ ///
260
+ /// # Safety
261
+ /// `ptr` must be a string allocated by this library
262
+ #[no_mangle]
263
+ pub extern "C" fn elizaos_free_string(ptr: *mut c_char) {
264
+ if !ptr.is_null() {
265
+ unsafe {
266
+ let _ = CString::from_raw(ptr);
267
+ }
268
+ }
269
+ }
270
+
271
+ // ============================================================================
272
+ // Helper Macro for Plugin Export
273
+ // ============================================================================
274
+
275
+ /// Macro to export a plugin for FFI
276
+ ///
277
+ /// This macro should be called in your plugin's lib.rs to make it loadable
278
+ /// via FFI from Python or other languages.
279
+ ///
280
+ /// # Example
281
+ ///
282
+ /// ```rust,ignore
283
+ /// use elizaos::interop::ffi_exports::*;
284
+ ///
285
+ /// struct MyPlugin { /* ... */ }
286
+ ///
287
+ /// impl PluginExport for MyPlugin {
288
+ /// // ... implement trait methods ...
289
+ /// }
290
+ ///
291
+ /// #[no_mangle]
292
+ /// pub extern "C" fn elizaos_plugin_init() {
293
+ /// let plugin = MyPlugin::new();
294
+ /// register_plugin(plugin);
295
+ /// }
296
+ /// ```
297
+ #[macro_export]
298
+ macro_rules! elizaos_export_plugin {
299
+ ($plugin:expr) => {
300
+ #[no_mangle]
301
+ pub extern "C" fn elizaos_plugin_init() {
302
+ $crate::interop::ffi_exports::register_plugin($plugin);
303
+ }
304
+ };
305
+ }
306
+
307
+ #[cfg(test)]
308
+ mod tests {
309
+ use super::*;
310
+
311
+ struct TestPlugin {
312
+ name: String,
313
+ }
314
+
315
+ impl PluginExport for TestPlugin {
316
+ fn get_manifest(&self) -> String {
317
+ format!(r#"{{"name": "{}", "description": "Test plugin"}}"#, self.name)
318
+ }
319
+
320
+ fn init(&self, _config: &str) -> Result<(), String> {
321
+ Ok(())
322
+ }
323
+
324
+ fn validate_action(&self, _name: &str, _memory: &str, _state: &str) -> bool {
325
+ true
326
+ }
327
+
328
+ fn invoke_action(&self, _name: &str, _memory: &str, _state: &str, _options: &str) -> String {
329
+ r#"{"success": true, "text": "Hello from test"}"#.to_string()
330
+ }
331
+
332
+ fn get_provider(&self, _name: &str, _memory: &str, _state: &str) -> String {
333
+ r#"{"text": "Provider data"}"#.to_string()
334
+ }
335
+
336
+ fn validate_evaluator(&self, _name: &str, _memory: &str, _state: &str) -> bool {
337
+ true
338
+ }
339
+
340
+ fn invoke_evaluator(&self, _name: &str, _memory: &str, _state: &str) -> String {
341
+ r#"{"success": true}"#.to_string()
342
+ }
343
+ }
344
+
345
+ #[test]
346
+ fn test_plugin_registration() {
347
+ let plugin = TestPlugin {
348
+ name: "test".to_string(),
349
+ };
350
+ register_plugin(plugin);
351
+
352
+ let manifest_ptr = elizaos_get_manifest();
353
+ assert!(!manifest_ptr.is_null());
354
+
355
+ unsafe {
356
+ let manifest = CStr::from_ptr(manifest_ptr).to_str().unwrap();
357
+ assert!(manifest.contains("test"));
358
+ elizaos_free_string(manifest_ptr);
359
+ }
360
+ }
361
+ }
362
+
package/rust/mod.rs ADDED
@@ -0,0 +1,21 @@
1
+ //! elizaOS Cross-Language Interop - Rust
2
+ //!
3
+ //! This module provides utilities for:
4
+ //! - Exporting Rust plugins via FFI for Python
5
+ //! - Exporting Rust plugins via WASM for TypeScript
6
+ //! - Loading TypeScript plugins via IPC
7
+ //! - Loading Python plugins via IPC
8
+ //!
9
+ //! Import directly from submodules:
10
+ //! - ffi_exports for FFI export utilities
11
+ //! - ts_loader for TypeScript plugin loading
12
+ //! - py_loader for Python plugin loading
13
+ //! - wasm_plugin for WASM plugin support (with "wasm" feature)
14
+
15
+ pub mod ffi_exports;
16
+ pub mod ts_loader;
17
+ pub mod py_loader;
18
+
19
+ #[cfg(feature = "wasm")]
20
+ pub mod wasm_plugin;
21
+