@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,284 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * TypeScript Plugin Bridge Runner for Python
5
+ *
6
+ * Loads a TypeScript/JavaScript plugin and communicates with Python via
7
+ * newline-delimited JSON messages over stdin/stdout.
8
+ *
9
+ * This file is intentionally committed (not generated at runtime) to avoid
10
+ * “performative” behavior and to make audits/reviews reproducible.
11
+ */
12
+
13
+ import { createRequire } from "node:module";
14
+ import { resolve } from "node:path";
15
+ import { createInterface } from "node:readline";
16
+
17
+ const pluginPath = process.argv[2];
18
+ if (!pluginPath) {
19
+ process.stderr.write("Usage: ts_bridge_runner.mjs <plugin_path>\n");
20
+ process.exit(1);
21
+ }
22
+
23
+ const INCLUDE_DETAILS =
24
+ ["1", "true", "yes", "on"].includes(
25
+ String(process.env.ELIZA_INTEROP_DEBUG ?? process.env.LOG_DIAGNOSTIC ?? "")
26
+ .trim()
27
+ .toLowerCase(),
28
+ ) || false;
29
+
30
+ const MAX_MESSAGE_BYTES = Number.parseInt(
31
+ String(process.env.ELIZA_INTEROP_MAX_MESSAGE_BYTES ?? "1000000"),
32
+ 10,
33
+ );
34
+ const MAX_BUFFER_BYTES = Number.parseInt(
35
+ String(process.env.ELIZA_INTEROP_MAX_BUFFER_BYTES ?? "2000000"),
36
+ 10,
37
+ );
38
+
39
+ /** @param {string} s */
40
+ function byteLen(s) {
41
+ return Buffer.byteLength(s, "utf8");
42
+ }
43
+
44
+ /** @param {unknown} error */
45
+ function formatError(error) {
46
+ if (error instanceof Error) {
47
+ return {
48
+ message: error.message,
49
+ stack: INCLUDE_DETAILS ? error.stack : undefined,
50
+ name: INCLUDE_DETAILS ? error.name : undefined,
51
+ };
52
+ }
53
+ return { message: String(error) };
54
+ }
55
+
56
+ async function loadPlugin(absPath) {
57
+ // Prefer ESM import, fall back to CJS require.
58
+ try {
59
+ const mod = await import(absPath);
60
+ return mod.default ?? mod.plugin ?? mod;
61
+ } catch (_e) {
62
+ const require = createRequire(import.meta.url);
63
+ const mod = require(absPath);
64
+ return mod.default ?? mod.plugin ?? mod;
65
+ }
66
+ }
67
+
68
+ function indexByName(items) {
69
+ /** @type {Record<string, any>} */
70
+ const out = {};
71
+ for (const item of items ?? []) {
72
+ if (item && typeof item.name === "string") out[item.name] = item;
73
+ }
74
+ return out;
75
+ }
76
+
77
+ function buildManifest(plugin, actions, providers, evaluators) {
78
+ return {
79
+ name: plugin?.name ?? "unknown",
80
+ description: plugin?.description ?? "",
81
+ version: plugin?.version ?? "1.0.0",
82
+ language: "typescript",
83
+ config: plugin?.config,
84
+ dependencies: plugin?.dependencies,
85
+ actions: Object.values(actions).map((a) => ({
86
+ name: a.name,
87
+ description: a.description,
88
+ similes: a.similes,
89
+ })),
90
+ providers: Object.values(providers).map((p) => ({
91
+ name: p.name,
92
+ description: p.description,
93
+ dynamic: p.dynamic,
94
+ position: p.position,
95
+ private: p.private,
96
+ })),
97
+ evaluators: Object.values(evaluators).map((e) => ({
98
+ name: e.name,
99
+ description: e.description,
100
+ alwaysRun: e.alwaysRun,
101
+ similes: e.similes,
102
+ })),
103
+ };
104
+ }
105
+
106
+ /**
107
+ * @param {any} request
108
+ * @param {any} plugin
109
+ * @param {Record<string, any>} actions
110
+ * @param {Record<string, any>} providers
111
+ * @param {Record<string, any>} evaluators
112
+ */
113
+ async function handleRequest(request, plugin, actions, providers, evaluators) {
114
+ const type = request?.type;
115
+ const id = request?.id ?? "";
116
+
117
+ try {
118
+ switch (type) {
119
+ case "plugin.init": {
120
+ if (typeof plugin?.init === "function") {
121
+ await plugin.init(request.config ?? {}, null);
122
+ }
123
+ return { type: "plugin.init.result", id, success: true };
124
+ }
125
+
126
+ case "action.validate": {
127
+ const action = actions[request.action];
128
+ if (!action || typeof action.validate !== "function") {
129
+ return { type: "validate.result", id, valid: false };
130
+ }
131
+ const valid = await action.validate(
132
+ null,
133
+ request.memory,
134
+ request.state,
135
+ );
136
+ return { type: "validate.result", id, valid: Boolean(valid) };
137
+ }
138
+
139
+ case "action.invoke": {
140
+ const action = actions[request.action];
141
+ if (!action || typeof action.handler !== "function") {
142
+ return {
143
+ type: "action.result",
144
+ id,
145
+ result: {
146
+ success: false,
147
+ error: `Action not found: ${request.action}`,
148
+ },
149
+ };
150
+ }
151
+ const result = await action.handler(
152
+ null,
153
+ request.memory,
154
+ request.state,
155
+ request.options ?? null,
156
+ null,
157
+ null,
158
+ );
159
+ return {
160
+ type: "action.result",
161
+ id,
162
+ result: {
163
+ success: Boolean(result?.success ?? true),
164
+ text: result?.text ?? null,
165
+ error: result?.error?.message ?? result?.error ?? null,
166
+ data: result?.data ?? null,
167
+ values: result?.values ?? null,
168
+ },
169
+ };
170
+ }
171
+
172
+ case "provider.get": {
173
+ const provider = providers[request.provider];
174
+ if (!provider || typeof provider.get !== "function") {
175
+ return {
176
+ type: "provider.result",
177
+ id,
178
+ result: { text: null, values: null, data: null },
179
+ };
180
+ }
181
+ const result = await provider.get(null, request.memory, request.state);
182
+ return {
183
+ type: "provider.result",
184
+ id,
185
+ result: result ?? { text: null, values: null, data: null },
186
+ };
187
+ }
188
+
189
+ case "evaluator.invoke": {
190
+ const evaluator = evaluators[request.evaluator];
191
+ if (!evaluator || typeof evaluator.handler !== "function") {
192
+ return { type: "action.result", id, result: null };
193
+ }
194
+ const result = await evaluator.handler(
195
+ null,
196
+ request.memory,
197
+ request.state,
198
+ );
199
+ return {
200
+ type: "action.result",
201
+ id,
202
+ result: result
203
+ ? {
204
+ success: Boolean(result.success ?? true),
205
+ text: result.text ?? null,
206
+ error: result.error?.message ?? result.error ?? null,
207
+ data: result.data ?? null,
208
+ values: result.values ?? null,
209
+ }
210
+ : null,
211
+ };
212
+ }
213
+
214
+ default:
215
+ return {
216
+ type: "error",
217
+ id,
218
+ error: `Unknown request type: ${String(type)}`,
219
+ };
220
+ }
221
+ } catch (e) {
222
+ return {
223
+ type: "error",
224
+ id,
225
+ error: formatError(e).message,
226
+ details: INCLUDE_DETAILS ? formatError(e) : undefined,
227
+ };
228
+ }
229
+ }
230
+
231
+ (async () => {
232
+ const absPath = resolve(pluginPath);
233
+ const plugin = await loadPlugin(absPath);
234
+
235
+ const actions = indexByName(plugin?.actions);
236
+ const providers = indexByName(plugin?.providers);
237
+ const evaluators = indexByName(plugin?.evaluators);
238
+
239
+ const manifest = buildManifest(plugin, actions, providers, evaluators);
240
+
241
+ process.stdout.write(`${JSON.stringify({ type: "ready", manifest })}\n`);
242
+
243
+ const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
244
+
245
+ let bufferedBytes = 0;
246
+ rl.on("line", async (line) => {
247
+ const trimmed = line.trim();
248
+ if (!trimmed) return;
249
+
250
+ const len = byteLen(trimmed);
251
+ if (len > MAX_MESSAGE_BYTES) {
252
+ process.stderr.write(`IPC message too large (${len} bytes)\n`);
253
+ process.exit(2);
254
+ }
255
+
256
+ bufferedBytes += len;
257
+ if (bufferedBytes > MAX_BUFFER_BYTES) {
258
+ process.stderr.write("IPC buffer exceeded limit\n");
259
+ process.exit(2);
260
+ }
261
+
262
+ /** @type {any} */
263
+ let request;
264
+ try {
265
+ request = JSON.parse(trimmed);
266
+ } catch (e) {
267
+ // Protocol violation: fail closed.
268
+ process.stderr.write(`Invalid JSON from stdin: ${String(e)}\n`);
269
+ process.exit(2);
270
+ return;
271
+ } finally {
272
+ bufferedBytes -= len;
273
+ }
274
+
275
+ const response = await handleRequest(
276
+ request,
277
+ plugin,
278
+ actions,
279
+ providers,
280
+ evaluators,
281
+ );
282
+ process.stdout.write(`${JSON.stringify(response)}\n`);
283
+ });
284
+ })();