@elizaos/interop 2.0.0-alpha.2 → 2.0.0-alpha.201
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/README.md +1 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/{typescript/python-bridge.d.ts → python-bridge.d.ts} +16 -0
- package/dist/python-bridge.d.ts.map +1 -0
- package/dist/{typescript/python-bridge.js → python-bridge.js} +61 -4
- package/dist/types.d.ts.map +1 -0
- package/dist/{typescript/wasm-loader.d.ts → wasm-loader.d.ts} +4 -0
- package/dist/wasm-loader.d.ts.map +1 -0
- package/dist/{typescript/wasm-loader.js → wasm-loader.js} +53 -8
- package/package.json +6 -6
- package/python/tests/test_rust_ffi_loader.py +2 -2
- package/dist/packages/interop/tsconfig.tsbuildinfo +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/typescript/index.d.ts.map +0 -1
- package/dist/typescript/python-bridge.d.ts.map +0 -1
- package/dist/typescript/types.d.ts.map +0 -1
- package/dist/typescript/wasm-loader.d.ts.map +0 -1
- 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/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/dist/{typescript/index.d.ts → index.d.ts} +0 -0
- /package/dist/{typescript/index.js → index.js} +0 -0
- /package/dist/{typescript/types.d.ts → types.d.ts} +0 -0
- /package/dist/{typescript/types.js → types.js} +0 -0
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ await runtime.register_plugin(plugin)
|
|
|
116
116
|
|
|
117
117
|
## Plugin Manifest
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
Cross-language plugins are described by a manifest-like metadata object (format may vary by transport and host).
|
|
120
120
|
|
|
121
121
|
```json
|
|
122
122
|
{
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../typescript/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EAEf,UAAU,EACV,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,eAAe,EAEf,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnE;;GAEG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,KAAK,EAAuB,cAAc,EAAE,MAAM,SAAS,CAAC;AAGnE,MAAM,WAAW,sBAAsB;IACrC,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC,CA4CjB"}
|
|
@@ -19,10 +19,25 @@ export interface PythonBridgeOptions {
|
|
|
19
19
|
cwd?: string;
|
|
20
20
|
/** Additional environment variables */
|
|
21
21
|
env?: Record<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Whether to inherit the parent process environment variables.
|
|
24
|
+
*
|
|
25
|
+
* Defaults to true for compatibility. For tighter isolation, set to false and pass only
|
|
26
|
+
* explicit `env` entries.
|
|
27
|
+
*/
|
|
28
|
+
inheritEnv?: boolean;
|
|
29
|
+
/** Environment variable names to remove when inheriting. */
|
|
30
|
+
envDenylist?: string[];
|
|
22
31
|
/** Path to the bridge script */
|
|
23
32
|
bridgeScriptPath?: string;
|
|
24
33
|
/** Connection timeout in milliseconds */
|
|
25
34
|
timeout?: number;
|
|
35
|
+
/** Maximum number of in-flight IPC requests (prevents unbounded memory growth). */
|
|
36
|
+
maxPendingRequests?: number;
|
|
37
|
+
/** Maximum size (bytes) of a single newline-delimited IPC message. */
|
|
38
|
+
maxMessageBytes?: number;
|
|
39
|
+
/** Maximum size (bytes) of the internal stdout buffer. */
|
|
40
|
+
maxBufferBytes?: number;
|
|
26
41
|
}
|
|
27
42
|
/**
|
|
28
43
|
* Python plugin bridge that communicates via subprocess
|
|
@@ -36,6 +51,7 @@ export declare class PythonPluginBridge extends EventEmitter {
|
|
|
36
51
|
private initialized;
|
|
37
52
|
private requestCounter;
|
|
38
53
|
constructor(options: PythonBridgeOptions);
|
|
54
|
+
private buildChildEnv;
|
|
39
55
|
/**
|
|
40
56
|
* Start the Python subprocess
|
|
41
57
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python-bridge.d.ts","sourceRoot":"","sources":["../typescript/python-bridge.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EASV,MAAM,EAKP,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAGV,UAAU,EACV,WAAW,EACX,cAAc,EAGf,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,gCAAgC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAWD;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAStC,OAAO,CAAC,OAAO;IAR3B,OAAO,CAAC,OAAO,CACR;IACP,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,cAAc,CAAa;gBAEf,OAAO,EAAE,mBAAmB;IAIhD,OAAO,CAAC,aAAa;IA0BrB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuD5B;;OAEG;YACW,YAAY;IAsB1B;;OAEG;IACH,OAAO,CAAC,UAAU;IAuDlB;;OAEG;IACH,OAAO,CAAC,aAAa;IAkBrB;;OAEG;IACG,WAAW,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IAkCzE;;OAEG;IACH,WAAW,IAAI,cAAc,GAAG,IAAI;IAIpC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B3B;;OAEG;IACH,OAAO,CAAC,OAAO;CAWhB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAUjB;AA8KD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOpE"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* to the TypeScript Plugin interface.
|
|
6
6
|
*/
|
|
7
7
|
import { EventEmitter } from "node:events";
|
|
8
|
+
import { logger } from "@elizaos/core";
|
|
8
9
|
/**
|
|
9
10
|
* Python plugin bridge that communicates via subprocess
|
|
10
11
|
*/
|
|
@@ -20,6 +21,27 @@ export class PythonPluginBridge extends EventEmitter {
|
|
|
20
21
|
super();
|
|
21
22
|
this.options = options;
|
|
22
23
|
}
|
|
24
|
+
buildChildEnv() {
|
|
25
|
+
const inherit = this.options.inheritEnv !== false;
|
|
26
|
+
const base = {};
|
|
27
|
+
if (inherit) {
|
|
28
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
29
|
+
if (typeof v === "string") {
|
|
30
|
+
base[k] = v;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const deny = new Set(this.options.envDenylist ?? []);
|
|
35
|
+
for (const key of deny) {
|
|
36
|
+
delete base[key];
|
|
37
|
+
}
|
|
38
|
+
if (this.options.env) {
|
|
39
|
+
for (const [k, v] of Object.entries(this.options.env)) {
|
|
40
|
+
base[k] = v;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return base;
|
|
44
|
+
}
|
|
23
45
|
/**
|
|
24
46
|
* Start the Python subprocess
|
|
25
47
|
*/
|
|
@@ -30,7 +52,7 @@ export class PythonPluginBridge extends EventEmitter {
|
|
|
30
52
|
new URL("../python/bridge_server.py", import.meta.url).pathname;
|
|
31
53
|
this.process = spawn(pythonPath, ["-u", bridgeScript, "--module", this.options.moduleName], {
|
|
32
54
|
cwd: this.options.cwd,
|
|
33
|
-
env:
|
|
55
|
+
env: this.buildChildEnv(),
|
|
34
56
|
stdio: ["pipe", "pipe", "pipe"],
|
|
35
57
|
});
|
|
36
58
|
// Handle stdout (JSON-RPC messages)
|
|
@@ -42,7 +64,12 @@ export class PythonPluginBridge extends EventEmitter {
|
|
|
42
64
|
// Handle stderr (logging)
|
|
43
65
|
if (this.process.stderr) {
|
|
44
66
|
this.process.stderr.on("data", (data) => {
|
|
45
|
-
|
|
67
|
+
logger.error({
|
|
68
|
+
src: "interop:python-bridge",
|
|
69
|
+
event: "interop.ipc.stderr",
|
|
70
|
+
moduleName: this.options.moduleName,
|
|
71
|
+
stream: "stderr",
|
|
72
|
+
}, data.toString());
|
|
46
73
|
});
|
|
47
74
|
}
|
|
48
75
|
// Handle process exit
|
|
@@ -80,18 +107,44 @@ export class PythonPluginBridge extends EventEmitter {
|
|
|
80
107
|
* Handle incoming data from subprocess
|
|
81
108
|
*/
|
|
82
109
|
handleData(data) {
|
|
110
|
+
const maxBufferBytes = this.options.maxBufferBytes ?? 2_000_000;
|
|
111
|
+
const maxMessageBytes = this.options.maxMessageBytes ?? 1_000_000;
|
|
83
112
|
this.messageBuffer += data;
|
|
113
|
+
if (Buffer.byteLength(this.messageBuffer, "utf8") > maxBufferBytes) {
|
|
114
|
+
logger.error({
|
|
115
|
+
src: "interop:python-bridge",
|
|
116
|
+
event: "interop.ipc.stdout_buffer_exceeded",
|
|
117
|
+
moduleName: this.options.moduleName,
|
|
118
|
+
}, `IPC stdout buffer exceeded limit (${maxBufferBytes} bytes); terminating bridge`);
|
|
119
|
+
this.process?.kill("SIGKILL");
|
|
120
|
+
this.cleanup();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
84
123
|
// Process complete JSON messages (newline-delimited)
|
|
85
124
|
const lines = this.messageBuffer.split("\n");
|
|
86
125
|
this.messageBuffer = lines.pop() ?? "";
|
|
87
126
|
for (const line of lines) {
|
|
88
127
|
if (line.trim()) {
|
|
128
|
+
if (Buffer.byteLength(line, "utf8") > maxMessageBytes) {
|
|
129
|
+
logger.error({
|
|
130
|
+
src: "interop:python-bridge",
|
|
131
|
+
event: "interop.ipc.message_exceeded",
|
|
132
|
+
moduleName: this.options.moduleName,
|
|
133
|
+
}, `IPC message exceeded limit (${maxMessageBytes} bytes); terminating bridge`);
|
|
134
|
+
this.process?.kill("SIGKILL");
|
|
135
|
+
this.cleanup();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
89
138
|
try {
|
|
90
139
|
const message = JSON.parse(line);
|
|
91
140
|
this.handleMessage(message);
|
|
92
141
|
}
|
|
93
|
-
catch (
|
|
94
|
-
|
|
142
|
+
catch (_error) {
|
|
143
|
+
logger.error({
|
|
144
|
+
src: "interop:python-bridge",
|
|
145
|
+
event: "interop.ipc.parse_failed",
|
|
146
|
+
moduleName: this.options.moduleName,
|
|
147
|
+
}, `Failed to parse IPC message: ${line}`);
|
|
95
148
|
}
|
|
96
149
|
}
|
|
97
150
|
}
|
|
@@ -123,6 +176,10 @@ export class PythonPluginBridge extends EventEmitter {
|
|
|
123
176
|
if (!this.process || !this.initialized) {
|
|
124
177
|
throw new Error("Python bridge not started");
|
|
125
178
|
}
|
|
179
|
+
const maxPendingRequests = this.options.maxPendingRequests ?? 1000;
|
|
180
|
+
if (this.pendingRequests.size >= maxPendingRequests) {
|
|
181
|
+
throw new Error(`Too many pending IPC requests (max=${maxPendingRequests})`);
|
|
182
|
+
}
|
|
126
183
|
const id = `req_${++this.requestCounter}`;
|
|
127
184
|
const requestWithId = { ...request, id };
|
|
128
185
|
return new Promise((resolve, reject) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../typescript/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,10 @@ export interface WasmLoaderOptions {
|
|
|
16
16
|
manifestPath?: string;
|
|
17
17
|
/** Import object for WASM instantiation */
|
|
18
18
|
imports?: WebAssembly.Imports;
|
|
19
|
+
/** Maximum allowed WASM binary size in bytes. */
|
|
20
|
+
maxWasmBytes?: number;
|
|
21
|
+
/** Maximum allowed initial memory size in bytes (post-instantiation). */
|
|
22
|
+
maxMemoryBytes?: number;
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
25
|
* Load a WASM plugin and return an elizaOS Plugin interface
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm-loader.d.ts","sourceRoot":"","sources":["../typescript/wasm-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EASV,MAAM,EAKP,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAEV,cAAc,EAIf,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC;IAC9B,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAQD;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAmZD;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAClE,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAkBD"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Loads Rust (or other) plugins compiled to WebAssembly and adapts them
|
|
5
5
|
* to the TypeScript Plugin interface.
|
|
6
6
|
*/
|
|
7
|
+
import { logger } from "@elizaos/core";
|
|
7
8
|
/**
|
|
8
9
|
* Text encoder/decoder for string passing
|
|
9
10
|
*/
|
|
@@ -15,7 +16,7 @@ const decoder = new TextDecoder();
|
|
|
15
16
|
export async function loadWasmPlugin(options) {
|
|
16
17
|
const { wasmPath, manifestPath } = options;
|
|
17
18
|
// Load the WASM module
|
|
18
|
-
const wasmInstance = await loadWasmModule(wasmPath, options
|
|
19
|
+
const wasmInstance = await loadWasmModule(wasmPath, options);
|
|
19
20
|
// Get the manifest from the WASM module or external file
|
|
20
21
|
let manifest;
|
|
21
22
|
if (manifestPath) {
|
|
@@ -32,7 +33,7 @@ export async function loadWasmPlugin(options) {
|
|
|
32
33
|
/**
|
|
33
34
|
* Load a WASM module and instantiate it
|
|
34
35
|
*/
|
|
35
|
-
async function loadWasmModule(wasmPath,
|
|
36
|
+
async function loadWasmModule(wasmPath, options) {
|
|
36
37
|
// Default imports for WASM
|
|
37
38
|
const defaultImports = {
|
|
38
39
|
env: {
|
|
@@ -66,7 +67,7 @@ async function loadWasmModule(wasmPath, customImports) {
|
|
|
66
67
|
},
|
|
67
68
|
},
|
|
68
69
|
};
|
|
69
|
-
const imports = { ...defaultImports, ...
|
|
70
|
+
const imports = { ...defaultImports, ...options.imports };
|
|
70
71
|
// Determine if we're in Node.js or browser
|
|
71
72
|
const isNode = typeof globalThis.process !== "undefined" &&
|
|
72
73
|
globalThis.process.versions &&
|
|
@@ -75,32 +76,76 @@ async function loadWasmModule(wasmPath, customImports) {
|
|
|
75
76
|
if (isNode) {
|
|
76
77
|
// Node.js: read file
|
|
77
78
|
const fs = await import("node:fs/promises");
|
|
79
|
+
if (typeof options.maxWasmBytes === "number") {
|
|
80
|
+
const st = await fs.stat(wasmPath);
|
|
81
|
+
if (st.size > options.maxWasmBytes) {
|
|
82
|
+
throw new Error(`WASM binary too large (${st.size} bytes > ${options.maxWasmBytes} bytes)`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
78
85
|
const wasmBuffer = await fs.readFile(wasmPath);
|
|
86
|
+
if (typeof options.maxWasmBytes === "number" &&
|
|
87
|
+
wasmBuffer.byteLength > options.maxWasmBytes) {
|
|
88
|
+
throw new Error(`WASM binary too large (${wasmBuffer.byteLength} bytes > ${options.maxWasmBytes} bytes)`);
|
|
89
|
+
}
|
|
79
90
|
wasmModule = await WebAssembly.compile(wasmBuffer);
|
|
80
91
|
}
|
|
81
92
|
else {
|
|
82
93
|
// Browser: fetch
|
|
83
94
|
const response = await fetch(wasmPath);
|
|
84
95
|
const wasmBuffer = await response.arrayBuffer();
|
|
96
|
+
if (typeof options.maxWasmBytes === "number" &&
|
|
97
|
+
wasmBuffer.byteLength > options.maxWasmBytes) {
|
|
98
|
+
throw new Error(`WASM binary too large (${wasmBuffer.byteLength} bytes > ${options.maxWasmBytes} bytes)`);
|
|
99
|
+
}
|
|
85
100
|
wasmModule = await WebAssembly.compile(wasmBuffer);
|
|
86
101
|
}
|
|
87
|
-
|
|
88
|
-
instance = await WebAssembly.instantiate(wasmModule, imports);
|
|
102
|
+
const instance = await WebAssembly.instantiate(wasmModule, imports);
|
|
89
103
|
// Set up console logging now that we have memory
|
|
90
104
|
const memory = instance.exports.memory;
|
|
105
|
+
if (typeof options.maxMemoryBytes === "number" &&
|
|
106
|
+
memory.buffer.byteLength > options.maxMemoryBytes) {
|
|
107
|
+
throw new Error(`WASM memory too large (${memory.buffer.byteLength} bytes > ${options.maxMemoryBytes} bytes)`);
|
|
108
|
+
}
|
|
91
109
|
if (imports.env) {
|
|
92
110
|
const env = imports.env;
|
|
93
111
|
env.console_log = (ptr, len) => {
|
|
94
112
|
const bytes = new Uint8Array(memory.buffer, ptr, len);
|
|
95
|
-
|
|
113
|
+
logger.info({ src: "interop:wasm", event: "interop.wasm.stdout", stream: "stdout" }, decoder.decode(bytes));
|
|
96
114
|
};
|
|
97
115
|
env.console_error = (ptr, len) => {
|
|
98
116
|
const bytes = new Uint8Array(memory.buffer, ptr, len);
|
|
99
|
-
|
|
117
|
+
logger.error({ src: "interop:wasm", event: "interop.wasm.stderr", stream: "stderr" }, decoder.decode(bytes));
|
|
100
118
|
};
|
|
101
119
|
}
|
|
120
|
+
// Provide secure randomness for WASI random_get if present
|
|
121
|
+
if (imports.wasi_snapshot_preview1) {
|
|
122
|
+
const wasi = imports.wasi_snapshot_preview1;
|
|
123
|
+
wasi.random_get = (buf, len) => {
|
|
124
|
+
const view = new Uint8Array(memory.buffer, buf, len);
|
|
125
|
+
const cryptoObj = globalThis.crypto;
|
|
126
|
+
if (!cryptoObj || typeof cryptoObj.getRandomValues !== "function") {
|
|
127
|
+
throw new Error("No secure random source available for WASI random_get");
|
|
128
|
+
}
|
|
129
|
+
cryptoObj.getRandomValues(view);
|
|
130
|
+
return 0;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Type guard to validate exports match WasmPluginExports interface
|
|
134
|
+
const exports = instance.exports;
|
|
135
|
+
const typedExports = {
|
|
136
|
+
get_manifest: exports.get_manifest,
|
|
137
|
+
init: exports.init,
|
|
138
|
+
validate_action: exports.validate_action,
|
|
139
|
+
invoke_action: exports.invoke_action,
|
|
140
|
+
get_provider: exports.get_provider,
|
|
141
|
+
validate_evaluator: exports.validate_evaluator,
|
|
142
|
+
invoke_evaluator: exports.invoke_evaluator,
|
|
143
|
+
handle_route: exports.handle_route,
|
|
144
|
+
alloc: exports.alloc,
|
|
145
|
+
dealloc: exports.dealloc,
|
|
146
|
+
};
|
|
102
147
|
return {
|
|
103
|
-
exports:
|
|
148
|
+
exports: typedExports,
|
|
104
149
|
memory: { buffer: memory.buffer },
|
|
105
150
|
};
|
|
106
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/interop",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.201",
|
|
4
4
|
"description": "Cross-language plugin interoperability for elizaOS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,22 +22,22 @@
|
|
|
22
22
|
"build": "tsc",
|
|
23
23
|
"test": "vitest run --pool=threads",
|
|
24
24
|
"test:watch": "vitest --pool=threads",
|
|
25
|
-
"test:python": "cd python && pytest tests/ -v -p no:anchorpy --asyncio-mode=auto
|
|
26
|
-
"lint": "bunx @biomejs/biome check --write
|
|
25
|
+
"test:python": "cd python && tmpdir=$(mktemp -d) && python3 -m venv \"$tmpdir\" && \"$tmpdir/bin/python\" -m pip install -e ../python -e '.[dev]' -q && \"$tmpdir/bin/python\" -m pytest tests/ -v -p no:anchorpy --asyncio-mode=auto; status=$?; rm -rf \"$tmpdir\"; exit $status",
|
|
26
|
+
"lint": "bunx @biomejs/biome check --write .",
|
|
27
27
|
"lint:check": "bunx @biomejs/biome check .",
|
|
28
28
|
"typecheck": "tsc --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@elizaos/core": "2.0.0-alpha.
|
|
31
|
+
"@elizaos/core": "2.0.0-alpha.201"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^25.0.3",
|
|
35
|
-
"typescript": "^
|
|
35
|
+
"typescript": "^6.0.0"
|
|
36
36
|
},
|
|
37
37
|
"author": "elizaOS",
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "8be84790e151372fc31f4f2ed5640613511bbe6b"
|
|
43
43
|
}
|
|
@@ -51,7 +51,7 @@ async def test_load_rust_plugin_produces_awaitable_handlers(monkeypatch: pytest.
|
|
|
51
51
|
provider = plugin.providers[0]
|
|
52
52
|
evaluator = plugin.evaluators[0]
|
|
53
53
|
|
|
54
|
-
valid = await action.
|
|
54
|
+
valid = await action.validate(None, {"content": {}}, None) # type: ignore[arg-type]
|
|
55
55
|
assert valid is True
|
|
56
56
|
|
|
57
57
|
result = await action.handler(None, {"content": {}}, None, None, None, None) # type: ignore[arg-type]
|
|
@@ -62,7 +62,7 @@ async def test_load_rust_plugin_produces_awaitable_handlers(monkeypatch: pytest.
|
|
|
62
62
|
prov_result = await provider.get(None, {"content": {}}, {"values": {}, "data": {}, "text": ""}) # type: ignore[arg-type]
|
|
63
63
|
assert prov_result.text == "prov:PROV"
|
|
64
64
|
|
|
65
|
-
eval_valid = await evaluator.
|
|
65
|
+
eval_valid = await evaluator.validate(None, {"content": {}}, None) # type: ignore[arg-type]
|
|
66
66
|
assert eval_valid is True
|
|
67
67
|
|
|
68
68
|
eval_result = await evaluator.handler(None, {"content": {}}, None, None, None, None) # type: ignore[arg-type]
|