@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,418 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rust Plugin FFI Loader for elizaOS
|
|
3
|
+
|
|
4
|
+
This module provides utilities for loading Rust plugins into the Python runtime
|
|
5
|
+
via ctypes/cffi FFI bindings.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ctypes
|
|
11
|
+
import json
|
|
12
|
+
import platform
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Callable, Awaitable
|
|
15
|
+
|
|
16
|
+
from elizaos.types.plugin import Plugin
|
|
17
|
+
from elizaos.types.memory import Memory
|
|
18
|
+
from elizaos.types.state import State
|
|
19
|
+
from elizaos.types.components import (
|
|
20
|
+
Action,
|
|
21
|
+
ActionResult,
|
|
22
|
+
Provider,
|
|
23
|
+
ProviderResult,
|
|
24
|
+
Evaluator,
|
|
25
|
+
HandlerOptions,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_lib_extension() -> str:
|
|
30
|
+
"""Get the shared library extension for the current platform."""
|
|
31
|
+
system = platform.system()
|
|
32
|
+
if system == "Darwin":
|
|
33
|
+
return ".dylib"
|
|
34
|
+
elif system == "Windows":
|
|
35
|
+
return ".dll"
|
|
36
|
+
else:
|
|
37
|
+
return ".so"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_lib_prefix() -> str:
|
|
41
|
+
"""Get the shared library prefix for the current platform."""
|
|
42
|
+
system = platform.system()
|
|
43
|
+
if system == "Windows":
|
|
44
|
+
return ""
|
|
45
|
+
else:
|
|
46
|
+
return "lib"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RustPluginFFI:
|
|
50
|
+
"""
|
|
51
|
+
FFI wrapper for Rust plugins.
|
|
52
|
+
|
|
53
|
+
The Rust plugin must export these functions:
|
|
54
|
+
- elizaos_get_manifest() -> *const c_char
|
|
55
|
+
- elizaos_init(config_json: *const c_char) -> c_int
|
|
56
|
+
- elizaos_validate_action(name: *const c_char, memory: *const c_char, state: *const c_char) -> c_int
|
|
57
|
+
- elizaos_invoke_action(name: *const c_char, memory: *const c_char, state: *const c_char, options: *const c_char) -> *const c_char
|
|
58
|
+
- elizaos_get_provider(name: *const c_char, memory: *const c_char, state: *const c_char) -> *const c_char
|
|
59
|
+
- elizaos_validate_evaluator(name: *const c_char, memory: *const c_char, state: *const c_char) -> c_int
|
|
60
|
+
- elizaos_invoke_evaluator(name: *const c_char, memory: *const c_char, state: *const c_char) -> *const c_char
|
|
61
|
+
- elizaos_free_string(ptr: *const c_char) -> void
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, lib_path: str | Path) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Initialize the FFI wrapper.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
lib_path: Path to the shared library (.so/.dylib/.dll)
|
|
70
|
+
"""
|
|
71
|
+
self.lib_path = Path(lib_path)
|
|
72
|
+
if not self.lib_path.exists():
|
|
73
|
+
raise FileNotFoundError(f"Shared library not found: {lib_path}")
|
|
74
|
+
|
|
75
|
+
# Load the library
|
|
76
|
+
self.lib = ctypes.CDLL(str(self.lib_path))
|
|
77
|
+
self._setup_bindings()
|
|
78
|
+
self.manifest: dict[str, Any] | None = None
|
|
79
|
+
|
|
80
|
+
def _setup_bindings(self) -> None:
|
|
81
|
+
"""Set up ctypes function bindings."""
|
|
82
|
+
# elizaos_get_manifest() -> *const c_char
|
|
83
|
+
self.lib.elizaos_get_manifest.argtypes = []
|
|
84
|
+
self.lib.elizaos_get_manifest.restype = ctypes.c_char_p
|
|
85
|
+
|
|
86
|
+
# elizaos_init(config_json: *const c_char) -> c_int
|
|
87
|
+
self.lib.elizaos_init.argtypes = [ctypes.c_char_p]
|
|
88
|
+
self.lib.elizaos_init.restype = ctypes.c_int
|
|
89
|
+
|
|
90
|
+
# elizaos_validate_action(...)
|
|
91
|
+
self.lib.elizaos_validate_action.argtypes = [
|
|
92
|
+
ctypes.c_char_p, # action name
|
|
93
|
+
ctypes.c_char_p, # memory json
|
|
94
|
+
ctypes.c_char_p, # state json
|
|
95
|
+
]
|
|
96
|
+
self.lib.elizaos_validate_action.restype = ctypes.c_int
|
|
97
|
+
|
|
98
|
+
# elizaos_invoke_action(...)
|
|
99
|
+
self.lib.elizaos_invoke_action.argtypes = [
|
|
100
|
+
ctypes.c_char_p, # action name
|
|
101
|
+
ctypes.c_char_p, # memory json
|
|
102
|
+
ctypes.c_char_p, # state json
|
|
103
|
+
ctypes.c_char_p, # options json
|
|
104
|
+
]
|
|
105
|
+
self.lib.elizaos_invoke_action.restype = ctypes.c_char_p
|
|
106
|
+
|
|
107
|
+
# elizaos_get_provider(...)
|
|
108
|
+
self.lib.elizaos_get_provider.argtypes = [
|
|
109
|
+
ctypes.c_char_p, # provider name
|
|
110
|
+
ctypes.c_char_p, # memory json
|
|
111
|
+
ctypes.c_char_p, # state json
|
|
112
|
+
]
|
|
113
|
+
self.lib.elizaos_get_provider.restype = ctypes.c_char_p
|
|
114
|
+
|
|
115
|
+
# elizaos_validate_evaluator(...)
|
|
116
|
+
self.lib.elizaos_validate_evaluator.argtypes = [
|
|
117
|
+
ctypes.c_char_p, # evaluator name
|
|
118
|
+
ctypes.c_char_p, # memory json
|
|
119
|
+
ctypes.c_char_p, # state json
|
|
120
|
+
]
|
|
121
|
+
self.lib.elizaos_validate_evaluator.restype = ctypes.c_int
|
|
122
|
+
|
|
123
|
+
# elizaos_invoke_evaluator(...)
|
|
124
|
+
self.lib.elizaos_invoke_evaluator.argtypes = [
|
|
125
|
+
ctypes.c_char_p, # evaluator name
|
|
126
|
+
ctypes.c_char_p, # memory json
|
|
127
|
+
ctypes.c_char_p, # state json
|
|
128
|
+
]
|
|
129
|
+
self.lib.elizaos_invoke_evaluator.restype = ctypes.c_char_p
|
|
130
|
+
|
|
131
|
+
# elizaos_free_string(ptr: *const c_char)
|
|
132
|
+
self.lib.elizaos_free_string.argtypes = [ctypes.c_char_p]
|
|
133
|
+
self.lib.elizaos_free_string.restype = None
|
|
134
|
+
|
|
135
|
+
def _to_c_string(self, s: str | None) -> ctypes.c_char_p:
|
|
136
|
+
"""Convert Python string to C string."""
|
|
137
|
+
if s is None:
|
|
138
|
+
return ctypes.c_char_p(None)
|
|
139
|
+
return ctypes.c_char_p(s.encode("utf-8"))
|
|
140
|
+
|
|
141
|
+
def _from_c_string(self, ptr: ctypes.c_char_p) -> str | None:
|
|
142
|
+
"""Convert C string to Python string and free the C memory."""
|
|
143
|
+
if not ptr:
|
|
144
|
+
return None
|
|
145
|
+
result = ptr.decode("utf-8")
|
|
146
|
+
self.lib.elizaos_free_string(ptr)
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
def get_manifest(self) -> dict[str, Any]:
|
|
150
|
+
"""Get the plugin manifest."""
|
|
151
|
+
if self.manifest:
|
|
152
|
+
return self.manifest
|
|
153
|
+
|
|
154
|
+
ptr = self.lib.elizaos_get_manifest()
|
|
155
|
+
json_str = self._from_c_string(ptr)
|
|
156
|
+
if not json_str:
|
|
157
|
+
raise RuntimeError("Failed to get manifest from Rust plugin")
|
|
158
|
+
|
|
159
|
+
self.manifest = json.loads(json_str)
|
|
160
|
+
return self.manifest
|
|
161
|
+
|
|
162
|
+
def init(self, config: dict[str, str]) -> None:
|
|
163
|
+
"""Initialize the plugin with configuration."""
|
|
164
|
+
config_json = json.dumps(config)
|
|
165
|
+
result = self.lib.elizaos_init(self._to_c_string(config_json))
|
|
166
|
+
if result != 0:
|
|
167
|
+
raise RuntimeError(f"Plugin initialization failed with code {result}")
|
|
168
|
+
|
|
169
|
+
def validate_action(self, name: str, memory: Memory, state: State | None) -> bool:
|
|
170
|
+
"""Validate an action."""
|
|
171
|
+
memory_json = json.dumps(memory.model_dump() if hasattr(memory, "model_dump") else memory)
|
|
172
|
+
state_json = json.dumps(
|
|
173
|
+
state.model_dump() if state and hasattr(state, "model_dump") else state
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
result = self.lib.elizaos_validate_action(
|
|
177
|
+
self._to_c_string(name),
|
|
178
|
+
self._to_c_string(memory_json),
|
|
179
|
+
self._to_c_string(state_json),
|
|
180
|
+
)
|
|
181
|
+
return result != 0
|
|
182
|
+
|
|
183
|
+
def invoke_action(
|
|
184
|
+
self, name: str, memory: Memory, state: State | None, options: dict[str, Any] | None
|
|
185
|
+
) -> ActionResult:
|
|
186
|
+
"""Invoke an action."""
|
|
187
|
+
memory_json = json.dumps(memory.model_dump() if hasattr(memory, "model_dump") else memory)
|
|
188
|
+
state_json = json.dumps(
|
|
189
|
+
state.model_dump() if state and hasattr(state, "model_dump") else state
|
|
190
|
+
)
|
|
191
|
+
options_json = json.dumps(options or {})
|
|
192
|
+
|
|
193
|
+
result_ptr = self.lib.elizaos_invoke_action(
|
|
194
|
+
self._to_c_string(name),
|
|
195
|
+
self._to_c_string(memory_json),
|
|
196
|
+
self._to_c_string(state_json),
|
|
197
|
+
self._to_c_string(options_json),
|
|
198
|
+
)
|
|
199
|
+
result_json = self._from_c_string(result_ptr)
|
|
200
|
+
if not result_json:
|
|
201
|
+
return ActionResult(success=False, error="No result from Rust plugin")
|
|
202
|
+
|
|
203
|
+
result_data = json.loads(result_json)
|
|
204
|
+
return ActionResult(**result_data)
|
|
205
|
+
|
|
206
|
+
def get_provider(self, name: str, memory: Memory, state: State) -> ProviderResult:
|
|
207
|
+
"""Get provider data."""
|
|
208
|
+
memory_json = json.dumps(memory.model_dump() if hasattr(memory, "model_dump") else memory)
|
|
209
|
+
state_json = json.dumps(state.model_dump() if hasattr(state, "model_dump") else state)
|
|
210
|
+
|
|
211
|
+
result_ptr = self.lib.elizaos_get_provider(
|
|
212
|
+
self._to_c_string(name),
|
|
213
|
+
self._to_c_string(memory_json),
|
|
214
|
+
self._to_c_string(state_json),
|
|
215
|
+
)
|
|
216
|
+
result_json = self._from_c_string(result_ptr)
|
|
217
|
+
if not result_json:
|
|
218
|
+
return ProviderResult()
|
|
219
|
+
|
|
220
|
+
result_data = json.loads(result_json)
|
|
221
|
+
return ProviderResult(**result_data)
|
|
222
|
+
|
|
223
|
+
def validate_evaluator(self, name: str, memory: Memory, state: State | None) -> bool:
|
|
224
|
+
"""Validate an evaluator."""
|
|
225
|
+
memory_json = json.dumps(memory.model_dump() if hasattr(memory, "model_dump") else memory)
|
|
226
|
+
state_json = json.dumps(
|
|
227
|
+
state.model_dump() if state and hasattr(state, "model_dump") else state
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
result = self.lib.elizaos_validate_evaluator(
|
|
231
|
+
self._to_c_string(name),
|
|
232
|
+
self._to_c_string(memory_json),
|
|
233
|
+
self._to_c_string(state_json),
|
|
234
|
+
)
|
|
235
|
+
return result != 0
|
|
236
|
+
|
|
237
|
+
def invoke_evaluator(self, name: str, memory: Memory, state: State | None) -> ActionResult | None:
|
|
238
|
+
"""Invoke an evaluator."""
|
|
239
|
+
memory_json = json.dumps(memory.model_dump() if hasattr(memory, "model_dump") else memory)
|
|
240
|
+
state_json = json.dumps(
|
|
241
|
+
state.model_dump() if state and hasattr(state, "model_dump") else state
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
result_ptr = self.lib.elizaos_invoke_evaluator(
|
|
245
|
+
self._to_c_string(name),
|
|
246
|
+
self._to_c_string(memory_json),
|
|
247
|
+
self._to_c_string(state_json),
|
|
248
|
+
)
|
|
249
|
+
result_json = self._from_c_string(result_ptr)
|
|
250
|
+
if not result_json or result_json == "null":
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
result_data = json.loads(result_json)
|
|
254
|
+
return ActionResult(**result_data)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def load_rust_plugin(lib_path: str | Path) -> Plugin:
|
|
258
|
+
"""
|
|
259
|
+
Load a Rust plugin from a shared library.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
lib_path: Path to the shared library
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
elizaOS Plugin instance
|
|
266
|
+
"""
|
|
267
|
+
ffi = RustPluginFFI(lib_path)
|
|
268
|
+
manifest = ffi.get_manifest()
|
|
269
|
+
|
|
270
|
+
# Create action wrappers
|
|
271
|
+
actions: list[Action] = []
|
|
272
|
+
for action_def in manifest.get("actions", []):
|
|
273
|
+
action_name = action_def["name"]
|
|
274
|
+
|
|
275
|
+
def make_validate(name: str) -> Callable[..., Awaitable[bool]]:
|
|
276
|
+
async def validate(runtime: Any, message: Memory, state: State | None) -> bool:
|
|
277
|
+
return ffi.validate_action(name, message, state)
|
|
278
|
+
|
|
279
|
+
return validate
|
|
280
|
+
|
|
281
|
+
def make_handler(name: str) -> Callable[..., Awaitable[ActionResult | None]]:
|
|
282
|
+
async def handler(
|
|
283
|
+
runtime: Any,
|
|
284
|
+
message: Memory,
|
|
285
|
+
state: State | None,
|
|
286
|
+
options: HandlerOptions | None,
|
|
287
|
+
callback: Any,
|
|
288
|
+
responses: Any,
|
|
289
|
+
) -> ActionResult | None:
|
|
290
|
+
return ffi.invoke_action(
|
|
291
|
+
name, message, state, options.model_dump() if options else None
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return handler
|
|
295
|
+
|
|
296
|
+
validate_fn = make_validate(action_name)
|
|
297
|
+
handler_fn = make_handler(action_name)
|
|
298
|
+
|
|
299
|
+
actions.append(
|
|
300
|
+
Action(
|
|
301
|
+
name=action_name,
|
|
302
|
+
description=action_def.get("description", ""),
|
|
303
|
+
similes=action_def.get("similes"),
|
|
304
|
+
examples=action_def.get("examples"),
|
|
305
|
+
validate=validate_fn, # type: ignore
|
|
306
|
+
handler=handler_fn, # type: ignore
|
|
307
|
+
)
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Create provider wrappers
|
|
311
|
+
providers: list[Provider] = []
|
|
312
|
+
for provider_def in manifest.get("providers", []):
|
|
313
|
+
provider_name = provider_def["name"]
|
|
314
|
+
|
|
315
|
+
def make_get(name: str) -> Callable[..., Awaitable[ProviderResult]]:
|
|
316
|
+
async def get(runtime: Any, message: Memory, state: State) -> ProviderResult:
|
|
317
|
+
return ffi.get_provider(name, message, state)
|
|
318
|
+
|
|
319
|
+
return get
|
|
320
|
+
|
|
321
|
+
get_fn = make_get(provider_name)
|
|
322
|
+
|
|
323
|
+
providers.append(
|
|
324
|
+
Provider(
|
|
325
|
+
name=provider_name,
|
|
326
|
+
description=provider_def.get("description"),
|
|
327
|
+
dynamic=provider_def.get("dynamic"),
|
|
328
|
+
position=provider_def.get("position"),
|
|
329
|
+
private=provider_def.get("private"),
|
|
330
|
+
get=get_fn, # type: ignore
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Create evaluator wrappers
|
|
335
|
+
evaluators: list[Evaluator] = []
|
|
336
|
+
for eval_def in manifest.get("evaluators", []):
|
|
337
|
+
eval_name = eval_def["name"]
|
|
338
|
+
|
|
339
|
+
def make_eval_validate(name: str) -> Callable[..., Awaitable[bool]]:
|
|
340
|
+
async def validate(runtime: Any, message: Memory, state: State | None) -> bool:
|
|
341
|
+
return ffi.validate_evaluator(name, message, state)
|
|
342
|
+
|
|
343
|
+
return validate
|
|
344
|
+
|
|
345
|
+
def make_eval_handler(name: str) -> Callable[..., Awaitable[ActionResult | None]]:
|
|
346
|
+
async def handler(
|
|
347
|
+
runtime: Any,
|
|
348
|
+
message: Memory,
|
|
349
|
+
state: State | None,
|
|
350
|
+
options: HandlerOptions | None,
|
|
351
|
+
callback: Any,
|
|
352
|
+
responses: Any,
|
|
353
|
+
) -> ActionResult | None:
|
|
354
|
+
return ffi.invoke_evaluator(name, message, state)
|
|
355
|
+
|
|
356
|
+
return handler
|
|
357
|
+
|
|
358
|
+
validate_fn = make_eval_validate(eval_name)
|
|
359
|
+
handler_fn = make_eval_handler(eval_name)
|
|
360
|
+
|
|
361
|
+
evaluators.append(
|
|
362
|
+
Evaluator(
|
|
363
|
+
name=eval_name,
|
|
364
|
+
description=eval_def.get("description", ""),
|
|
365
|
+
always_run=eval_def.get("alwaysRun"),
|
|
366
|
+
similes=eval_def.get("similes"),
|
|
367
|
+
examples=[],
|
|
368
|
+
validate=validate_fn, # type: ignore
|
|
369
|
+
handler=handler_fn, # type: ignore
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Create init function
|
|
374
|
+
async def init(config: dict[str, str], runtime: Any) -> None:
|
|
375
|
+
ffi.init(config)
|
|
376
|
+
|
|
377
|
+
return Plugin(
|
|
378
|
+
name=manifest["name"],
|
|
379
|
+
description=manifest["description"],
|
|
380
|
+
init=init,
|
|
381
|
+
config=manifest.get("config"),
|
|
382
|
+
dependencies=manifest.get("dependencies"),
|
|
383
|
+
actions=actions if actions else None,
|
|
384
|
+
providers=providers if providers else None,
|
|
385
|
+
evaluators=evaluators if evaluators else None,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def find_rust_plugin(name: str, search_paths: list[str | Path] | None = None) -> Path | None:
|
|
390
|
+
"""
|
|
391
|
+
Find a Rust plugin by name in common locations.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
name: Plugin name (without lib prefix or extension)
|
|
395
|
+
search_paths: Additional paths to search
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Path to the shared library, or None if not found
|
|
399
|
+
"""
|
|
400
|
+
prefix = get_lib_prefix()
|
|
401
|
+
ext = get_lib_extension()
|
|
402
|
+
lib_name = f"{prefix}{name}{ext}"
|
|
403
|
+
|
|
404
|
+
paths_to_search = search_paths or []
|
|
405
|
+
paths_to_search.extend([
|
|
406
|
+
Path.cwd() / "target" / "release",
|
|
407
|
+
Path.cwd() / "target" / "debug",
|
|
408
|
+
Path.cwd() / "dist",
|
|
409
|
+
Path.home() / ".elizaos" / "plugins",
|
|
410
|
+
])
|
|
411
|
+
|
|
412
|
+
for path in paths_to_search:
|
|
413
|
+
lib_path = Path(path) / lib_name
|
|
414
|
+
if lib_path.exists():
|
|
415
|
+
return lib_path
|
|
416
|
+
|
|
417
|
+
return None
|
|
418
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Test suite for elizaOS Python interop."""
|
|
Binary file
|