@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,505 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
elizaOS Python Plugin Bridge Server
|
|
4
|
+
|
|
5
|
+
This server is spawned by the TypeScript runtime to load and execute
|
|
6
|
+
Python plugins via JSON-RPC over stdin/stdout.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import asyncio
|
|
13
|
+
import importlib
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import traceback
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
# Import elizaos types (required). Fail closed if unavailable.
|
|
21
|
+
try:
|
|
22
|
+
from elizaos.types.plugin import Plugin
|
|
23
|
+
from elizaos.types.memory import Memory
|
|
24
|
+
from elizaos.types.state import State
|
|
25
|
+
from elizaos.types.components import ActionResult, ProviderResult, HandlerOptions
|
|
26
|
+
except ImportError as e:
|
|
27
|
+
sys.stderr.write(
|
|
28
|
+
"elizaOS interop bridge_server requires the 'elizaos' python package in the environment.\n"
|
|
29
|
+
)
|
|
30
|
+
sys.stderr.write(f"ImportError: {e}\n")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _include_error_details() -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Whether to include stack traces in error responses.
|
|
37
|
+
|
|
38
|
+
Default is off to avoid leaking secrets/PII via tracebacks over IPC.
|
|
39
|
+
"""
|
|
40
|
+
value = os.environ.get("ELIZA_INTEROP_DEBUG") or os.environ.get("LOG_DIAGNOSTIC") or ""
|
|
41
|
+
normalized = value.strip().lower()
|
|
42
|
+
return normalized in {"1", "true", "yes", "on"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PluginBridgeServer:
|
|
46
|
+
"""JSON-RPC bridge server for Python plugins."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, module_name: str) -> None:
|
|
49
|
+
self.module_name = module_name
|
|
50
|
+
self.plugin: Plugin | None = None
|
|
51
|
+
self.actions: dict[str, Any] = {}
|
|
52
|
+
self.providers: dict[str, Any] = {}
|
|
53
|
+
self.evaluators: dict[str, Any] = {}
|
|
54
|
+
self.services: dict[str, Any] = {}
|
|
55
|
+
self.routes: dict[str, Any] = {}
|
|
56
|
+
self.initialized = False
|
|
57
|
+
|
|
58
|
+
async def load_plugin(self) -> None:
|
|
59
|
+
"""Load the Python plugin module."""
|
|
60
|
+
try:
|
|
61
|
+
module = importlib.import_module(self.module_name)
|
|
62
|
+
|
|
63
|
+
# Look for plugin export
|
|
64
|
+
if hasattr(module, "plugin"):
|
|
65
|
+
self.plugin = module.plugin
|
|
66
|
+
elif hasattr(module, "default"):
|
|
67
|
+
self.plugin = module.default
|
|
68
|
+
else:
|
|
69
|
+
raise RuntimeError(f"Module {self.module_name} has no plugin export")
|
|
70
|
+
|
|
71
|
+
# Index components for fast lookup
|
|
72
|
+
if hasattr(self.plugin, "actions") and self.plugin.actions:
|
|
73
|
+
for action in self.plugin.actions:
|
|
74
|
+
self.actions[action.name] = action
|
|
75
|
+
|
|
76
|
+
if hasattr(self.plugin, "providers") and self.plugin.providers:
|
|
77
|
+
for provider in self.plugin.providers:
|
|
78
|
+
self.providers[provider.name] = provider
|
|
79
|
+
|
|
80
|
+
if hasattr(self.plugin, "evaluators") and self.plugin.evaluators:
|
|
81
|
+
for evaluator in self.plugin.evaluators:
|
|
82
|
+
self.evaluators[evaluator.name] = evaluator
|
|
83
|
+
|
|
84
|
+
if hasattr(self.plugin, "services") and self.plugin.services:
|
|
85
|
+
for service in self.plugin.services:
|
|
86
|
+
service_name = getattr(service, "service_type", None) or getattr(service, "name", str(service))
|
|
87
|
+
self.services[service_name] = service
|
|
88
|
+
|
|
89
|
+
if hasattr(self.plugin, "routes") and self.plugin.routes:
|
|
90
|
+
for route in self.plugin.routes:
|
|
91
|
+
route_path = route.path if hasattr(route, "path") else route.get("path", "")
|
|
92
|
+
self.routes[route_path] = route
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
raise RuntimeError(f"Failed to load plugin: {e}") from e
|
|
96
|
+
|
|
97
|
+
def get_manifest(self) -> dict[str, Any]:
|
|
98
|
+
"""Get the plugin manifest as a dictionary."""
|
|
99
|
+
if not self.plugin:
|
|
100
|
+
raise RuntimeError("Plugin not loaded")
|
|
101
|
+
|
|
102
|
+
manifest: dict[str, Any] = {
|
|
103
|
+
"name": self.plugin.name,
|
|
104
|
+
"description": self.plugin.description,
|
|
105
|
+
"version": getattr(self.plugin, "version", "1.0.0"),
|
|
106
|
+
"language": "python",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if hasattr(self.plugin, "config") and self.plugin.config:
|
|
110
|
+
manifest["config"] = self.plugin.config
|
|
111
|
+
|
|
112
|
+
if hasattr(self.plugin, "dependencies") and self.plugin.dependencies:
|
|
113
|
+
manifest["dependencies"] = self.plugin.dependencies
|
|
114
|
+
|
|
115
|
+
if hasattr(self.plugin, "actions") and self.plugin.actions:
|
|
116
|
+
manifest["actions"] = [
|
|
117
|
+
{
|
|
118
|
+
"name": a.name,
|
|
119
|
+
"description": a.description,
|
|
120
|
+
"similes": getattr(a, "similes", None),
|
|
121
|
+
}
|
|
122
|
+
for a in self.plugin.actions
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
if hasattr(self.plugin, "providers") and self.plugin.providers:
|
|
126
|
+
manifest["providers"] = [
|
|
127
|
+
{
|
|
128
|
+
"name": p.name,
|
|
129
|
+
"description": getattr(p, "description", None),
|
|
130
|
+
"dynamic": getattr(p, "dynamic", None),
|
|
131
|
+
"position": getattr(p, "position", None),
|
|
132
|
+
"private": getattr(p, "private", None),
|
|
133
|
+
}
|
|
134
|
+
for p in self.plugin.providers
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
if hasattr(self.plugin, "evaluators") and self.plugin.evaluators:
|
|
138
|
+
manifest["evaluators"] = [
|
|
139
|
+
{
|
|
140
|
+
"name": e.name,
|
|
141
|
+
"description": e.description,
|
|
142
|
+
"alwaysRun": getattr(e, "always_run", None),
|
|
143
|
+
"similes": getattr(e, "similes", None),
|
|
144
|
+
}
|
|
145
|
+
for e in self.plugin.evaluators
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
if hasattr(self.plugin, "services") and self.plugin.services:
|
|
149
|
+
manifest["services"] = [
|
|
150
|
+
{
|
|
151
|
+
"type": getattr(s, "service_type", None) or getattr(s, "name", str(s)),
|
|
152
|
+
"description": getattr(s, "description", None),
|
|
153
|
+
}
|
|
154
|
+
for s in self.plugin.services
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
if hasattr(self.plugin, "routes") and self.plugin.routes:
|
|
158
|
+
manifest["routes"] = [
|
|
159
|
+
{
|
|
160
|
+
"path": r.path if hasattr(r, "path") else r.get("path", ""),
|
|
161
|
+
"type": r.type if hasattr(r, "type") else r.get("type", "GET"),
|
|
162
|
+
"public": r.public if hasattr(r, "public") else r.get("public", False),
|
|
163
|
+
"name": getattr(r, "name", None) or r.get("name"),
|
|
164
|
+
}
|
|
165
|
+
for r in self.plugin.routes
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
return manifest
|
|
169
|
+
|
|
170
|
+
async def handle_request(self, request: dict[str, Any]) -> dict[str, Any]:
|
|
171
|
+
"""Handle an incoming JSON-RPC request."""
|
|
172
|
+
req_type = request.get("type", "")
|
|
173
|
+
req_id = request.get("id", "")
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
if req_type == "plugin.init":
|
|
177
|
+
config = request.get("config", {})
|
|
178
|
+
if self.plugin and hasattr(self.plugin, "init") and self.plugin.init:
|
|
179
|
+
await self.plugin.init(config, None) # type: ignore
|
|
180
|
+
self.initialized = True
|
|
181
|
+
return {"type": "plugin.init.result", "id": req_id, "success": True}
|
|
182
|
+
|
|
183
|
+
elif req_type == "action.validate":
|
|
184
|
+
action_name = request.get("action", "")
|
|
185
|
+
action = self.actions.get(action_name)
|
|
186
|
+
if not action:
|
|
187
|
+
return {"type": "validate.result", "id": req_id, "valid": False}
|
|
188
|
+
|
|
189
|
+
memory = self._parse_memory(request.get("memory"))
|
|
190
|
+
state = self._parse_state(request.get("state"))
|
|
191
|
+
|
|
192
|
+
# Call validate function
|
|
193
|
+
valid = await action.validate(None, memory, state) # type: ignore
|
|
194
|
+
return {"type": "validate.result", "id": req_id, "valid": valid}
|
|
195
|
+
|
|
196
|
+
elif req_type == "action.invoke":
|
|
197
|
+
action_name = request.get("action", "")
|
|
198
|
+
action = self.actions.get(action_name)
|
|
199
|
+
if not action:
|
|
200
|
+
return {
|
|
201
|
+
"type": "action.result",
|
|
202
|
+
"id": req_id,
|
|
203
|
+
"result": {"success": False, "error": f"Action not found: {action_name}"},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
memory = self._parse_memory(request.get("memory"))
|
|
207
|
+
state = self._parse_state(request.get("state"))
|
|
208
|
+
options = request.get("options") or {}
|
|
209
|
+
|
|
210
|
+
# Call handler
|
|
211
|
+
result = await action.handler(
|
|
212
|
+
None, # runtime
|
|
213
|
+
memory,
|
|
214
|
+
state,
|
|
215
|
+
HandlerOptions(**options) if isinstance(options, dict) else None,
|
|
216
|
+
None, # callback
|
|
217
|
+
None, # responses
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
"type": "action.result",
|
|
222
|
+
"id": req_id,
|
|
223
|
+
"result": self._serialize_action_result(result),
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
elif req_type == "provider.get":
|
|
227
|
+
provider_name = request.get("provider", "")
|
|
228
|
+
provider = self.providers.get(provider_name)
|
|
229
|
+
if not provider:
|
|
230
|
+
return {
|
|
231
|
+
"type": "provider.result",
|
|
232
|
+
"id": req_id,
|
|
233
|
+
"result": {"text": None, "values": None, "data": None},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
memory = self._parse_memory(request.get("memory"))
|
|
237
|
+
state = self._parse_state(request.get("state"))
|
|
238
|
+
|
|
239
|
+
result = await provider.get(None, memory, state) # type: ignore
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
"type": "provider.result",
|
|
243
|
+
"id": req_id,
|
|
244
|
+
"result": self._serialize_provider_result(result),
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
elif req_type == "evaluator.invoke":
|
|
248
|
+
evaluator_name = request.get("evaluator", "")
|
|
249
|
+
evaluator = self.evaluators.get(evaluator_name)
|
|
250
|
+
if not evaluator:
|
|
251
|
+
return {
|
|
252
|
+
"type": "action.result",
|
|
253
|
+
"id": req_id,
|
|
254
|
+
"result": None,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
memory = self._parse_memory(request.get("memory"))
|
|
258
|
+
state = self._parse_state(request.get("state"))
|
|
259
|
+
|
|
260
|
+
result = await evaluator.handler(
|
|
261
|
+
None, # runtime
|
|
262
|
+
memory,
|
|
263
|
+
state,
|
|
264
|
+
None, # options
|
|
265
|
+
None, # callback
|
|
266
|
+
None, # responses
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
"type": "action.result",
|
|
271
|
+
"id": req_id,
|
|
272
|
+
"result": self._serialize_action_result(result) if result else None,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
elif req_type == "service.start":
|
|
276
|
+
service_type = request.get("serviceType", "")
|
|
277
|
+
service_class = self.services.get(service_type)
|
|
278
|
+
if not service_class:
|
|
279
|
+
return {
|
|
280
|
+
"type": "service.result",
|
|
281
|
+
"id": req_id,
|
|
282
|
+
"success": False,
|
|
283
|
+
"error": f"Service not found: {service_type}",
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# Start the service
|
|
287
|
+
try:
|
|
288
|
+
if hasattr(service_class, "start"):
|
|
289
|
+
service_instance = await service_class.start(None) # runtime
|
|
290
|
+
elif callable(service_class):
|
|
291
|
+
service_instance = service_class(None) # runtime
|
|
292
|
+
if hasattr(service_instance, "start"):
|
|
293
|
+
await service_instance.start()
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
"type": "service.result",
|
|
297
|
+
"id": req_id,
|
|
298
|
+
"success": True,
|
|
299
|
+
"serviceType": service_type,
|
|
300
|
+
}
|
|
301
|
+
except Exception as e:
|
|
302
|
+
return {
|
|
303
|
+
"type": "service.result",
|
|
304
|
+
"id": req_id,
|
|
305
|
+
"success": False,
|
|
306
|
+
"error": str(e),
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
elif req_type == "service.stop":
|
|
310
|
+
service_type = request.get("serviceType", "")
|
|
311
|
+
# Note: In a real implementation, we'd track running service instances
|
|
312
|
+
return {
|
|
313
|
+
"type": "service.result",
|
|
314
|
+
"id": req_id,
|
|
315
|
+
"success": True,
|
|
316
|
+
"serviceType": service_type,
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
elif req_type == "route.handle":
|
|
320
|
+
route_path = request.get("path", "")
|
|
321
|
+
route = self.routes.get(route_path)
|
|
322
|
+
if not route:
|
|
323
|
+
return {
|
|
324
|
+
"type": "route.result",
|
|
325
|
+
"id": req_id,
|
|
326
|
+
"status": 404,
|
|
327
|
+
"body": {"error": f"Route not found: {route_path}"},
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
handler = getattr(route, "handler", None) or route.get("handler")
|
|
331
|
+
if not handler:
|
|
332
|
+
return {
|
|
333
|
+
"type": "route.result",
|
|
334
|
+
"id": req_id,
|
|
335
|
+
"status": 501,
|
|
336
|
+
"body": {"error": "Route has no handler"},
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
# Create mock request/response objects
|
|
340
|
+
req_data = request.get("request", {})
|
|
341
|
+
mock_req = {
|
|
342
|
+
"body": req_data.get("body", {}),
|
|
343
|
+
"params": req_data.get("params", {}),
|
|
344
|
+
"query": req_data.get("query", {}),
|
|
345
|
+
"headers": req_data.get("headers", {}),
|
|
346
|
+
"method": req_data.get("method", "GET"),
|
|
347
|
+
"path": route_path,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
response_data: dict[str, Any] = {"status": 200, "body": None, "headers": {}}
|
|
351
|
+
|
|
352
|
+
class MockResponse:
|
|
353
|
+
def status(self, code: int) -> "MockResponse":
|
|
354
|
+
response_data["status"] = code
|
|
355
|
+
return self
|
|
356
|
+
|
|
357
|
+
def json(self, data: Any) -> "MockResponse":
|
|
358
|
+
response_data["body"] = data
|
|
359
|
+
return self
|
|
360
|
+
|
|
361
|
+
def send(self, data: Any) -> "MockResponse":
|
|
362
|
+
response_data["body"] = data
|
|
363
|
+
return self
|
|
364
|
+
|
|
365
|
+
def end(self) -> "MockResponse":
|
|
366
|
+
return self
|
|
367
|
+
|
|
368
|
+
def setHeader(self, name: str, value: str) -> "MockResponse":
|
|
369
|
+
response_data["headers"][name] = value
|
|
370
|
+
return self
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
if asyncio.iscoroutinefunction(handler):
|
|
374
|
+
await handler(mock_req, MockResponse(), None) # runtime
|
|
375
|
+
else:
|
|
376
|
+
handler(mock_req, MockResponse(), None) # runtime
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"type": "route.result",
|
|
380
|
+
"id": req_id,
|
|
381
|
+
"status": response_data["status"],
|
|
382
|
+
"body": response_data["body"],
|
|
383
|
+
"headers": response_data["headers"],
|
|
384
|
+
}
|
|
385
|
+
except Exception as e:
|
|
386
|
+
return {
|
|
387
|
+
"type": "route.result",
|
|
388
|
+
"id": req_id,
|
|
389
|
+
"status": 500,
|
|
390
|
+
"body": {"error": str(e)},
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
else:
|
|
394
|
+
return {
|
|
395
|
+
"type": "error",
|
|
396
|
+
"id": req_id,
|
|
397
|
+
"error": f"Unknown request type: {req_type}",
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
except Exception as e:
|
|
401
|
+
error_response: dict[str, Any] = {
|
|
402
|
+
"type": "error",
|
|
403
|
+
"id": req_id,
|
|
404
|
+
"error": str(e),
|
|
405
|
+
}
|
|
406
|
+
if _include_error_details():
|
|
407
|
+
error_response["details"] = traceback.format_exc()
|
|
408
|
+
return error_response
|
|
409
|
+
|
|
410
|
+
def _parse_memory(self, data: dict[str, Any] | None) -> Memory:
|
|
411
|
+
"""Parse memory from JSON."""
|
|
412
|
+
if data is None:
|
|
413
|
+
return Memory() # type: ignore
|
|
414
|
+
if isinstance(data, dict):
|
|
415
|
+
return Memory(**data) # type: ignore
|
|
416
|
+
return data # type: ignore
|
|
417
|
+
|
|
418
|
+
def _parse_state(self, data: dict[str, Any] | None) -> State | None:
|
|
419
|
+
"""Parse state from JSON."""
|
|
420
|
+
if data is None:
|
|
421
|
+
return None
|
|
422
|
+
if isinstance(data, dict):
|
|
423
|
+
return State(**data) # type: ignore
|
|
424
|
+
return data # type: ignore
|
|
425
|
+
|
|
426
|
+
def _serialize_action_result(self, result: ActionResult | None) -> dict[str, Any] | None:
|
|
427
|
+
"""Serialize action result to JSON-compatible dict."""
|
|
428
|
+
if result is None:
|
|
429
|
+
return None
|
|
430
|
+
|
|
431
|
+
if isinstance(result, dict):
|
|
432
|
+
return result
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
"success": result.success,
|
|
436
|
+
"text": result.text,
|
|
437
|
+
"error": str(result.error) if result.error else None,
|
|
438
|
+
"data": result.data,
|
|
439
|
+
"values": result.values,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
def _serialize_provider_result(self, result: ProviderResult) -> dict[str, Any]:
|
|
443
|
+
"""Serialize provider result to JSON-compatible dict."""
|
|
444
|
+
if isinstance(result, dict):
|
|
445
|
+
return result
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
"text": result.text,
|
|
449
|
+
"values": result.values,
|
|
450
|
+
"data": result.data,
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
async def main(module_name: str) -> None:
|
|
455
|
+
"""Main entry point for the bridge server."""
|
|
456
|
+
server = PluginBridgeServer(module_name)
|
|
457
|
+
|
|
458
|
+
# Load the plugin
|
|
459
|
+
await server.load_plugin()
|
|
460
|
+
|
|
461
|
+
# Send ready message with manifest
|
|
462
|
+
manifest = server.get_manifest()
|
|
463
|
+
ready_msg = {"type": "ready", "manifest": manifest}
|
|
464
|
+
print(json.dumps(ready_msg), flush=True)
|
|
465
|
+
|
|
466
|
+
# Process requests from stdin
|
|
467
|
+
reader = asyncio.StreamReader()
|
|
468
|
+
protocol = asyncio.StreamReaderProtocol(reader)
|
|
469
|
+
await asyncio.get_event_loop().connect_read_pipe(lambda: protocol, sys.stdin)
|
|
470
|
+
|
|
471
|
+
while True:
|
|
472
|
+
try:
|
|
473
|
+
line = await reader.readline()
|
|
474
|
+
if not line:
|
|
475
|
+
break
|
|
476
|
+
|
|
477
|
+
line_str = line.decode("utf-8").strip()
|
|
478
|
+
if not line_str:
|
|
479
|
+
continue
|
|
480
|
+
|
|
481
|
+
request = json.loads(line_str)
|
|
482
|
+
response = await server.handle_request(request)
|
|
483
|
+
print(json.dumps(response), flush=True)
|
|
484
|
+
|
|
485
|
+
except json.JSONDecodeError as e:
|
|
486
|
+
error_response = {"type": "error", "id": "", "error": f"JSON parse error: {e}"}
|
|
487
|
+
print(json.dumps(error_response), flush=True)
|
|
488
|
+
except Exception as e:
|
|
489
|
+
error_response: dict[str, Any] = {
|
|
490
|
+
"type": "error",
|
|
491
|
+
"id": "",
|
|
492
|
+
"error": str(e),
|
|
493
|
+
}
|
|
494
|
+
if _include_error_details():
|
|
495
|
+
error_response["details"] = traceback.format_exc()
|
|
496
|
+
print(json.dumps(error_response), flush=True)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
if __name__ == "__main__":
|
|
500
|
+
parser = argparse.ArgumentParser(description="elizaOS Python Plugin Bridge Server")
|
|
501
|
+
parser.add_argument("--module", "-m", required=True, help="Python module name to load")
|
|
502
|
+
args = parser.parse_args()
|
|
503
|
+
|
|
504
|
+
asyncio.run(main(args.module))
|
|
505
|
+
|