@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.
- package/LICENSE +21 -0
- package/README.md +436 -0
- package/dist/packages/interop/tsconfig.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/typescript/index.d.ts +33 -0
- package/dist/typescript/index.d.ts.map +1 -0
- package/dist/typescript/index.js +121 -0
- package/dist/typescript/python-bridge.d.ts +80 -0
- package/dist/typescript/python-bridge.d.ts.map +1 -0
- package/dist/typescript/python-bridge.js +334 -0
- package/dist/typescript/types.d.ts +301 -0
- package/dist/typescript/types.d.ts.map +1 -0
- package/dist/typescript/types.js +10 -0
- package/dist/typescript/wasm-loader.d.ts +32 -0
- package/dist/typescript/wasm-loader.d.ts.map +1 -0
- package/dist/typescript/wasm-loader.js +269 -0
- package/package.json +43 -0
- package/python/__init__.py +50 -0
- package/python/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/__pycache__/rust_ffi.cpython-313.pyc +0 -0
- package/python/__pycache__/ts_bridge.cpython-313.pyc +0 -0
- package/python/__pycache__/wasm_loader.cpython-313.pyc +0 -0
- package/python/bridge_server.py +505 -0
- package/python/rust_ffi.py +418 -0
- package/python/tests/__init__.py +1 -0
- package/python/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/tests/__pycache__/test_bridge_server.cpython-313-pytest-9.0.2.pyc +0 -0
- package/python/tests/__pycache__/test_interop.cpython-313-pytest-9.0.2.pyc +0 -0
- package/python/tests/__pycache__/test_rust_ffi.cpython-313-pytest-9.0.2.pyc +0 -0
- package/python/tests/__pycache__/test_rust_ffi_loader.cpython-313-pytest-9.0.2.pyc +0 -0
- package/python/tests/test_bridge_server.py +525 -0
- package/python/tests/test_interop.py +319 -0
- package/python/tests/test_rust_ffi.py +352 -0
- package/python/tests/test_rust_ffi_loader.py +71 -0
- package/python/ts_bridge.py +452 -0
- package/python/ts_bridge_runner.mjs +284 -0
- package/python/wasm_loader.py +517 -0
- package/rust/ffi_exports.rs +362 -0
- package/rust/mod.rs +21 -0
- package/rust/py_loader.rs +412 -0
- package/rust/ts_loader.rs +467 -0
- 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
|
+
|