@openpond/evals 0.7.4 → 0.7.6
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/JAVASCRIPT_ENVIRONMENTS.md +51 -2
- package/dist/environment-execution-services-contract.js +27 -0
- package/dist/environment-execution-services.js +100 -0
- package/dist/javascript-environment-contract.js +13 -0
- package/dist/javascript-environment-node.js +9 -1
- package/dist/javascript-environment.js +4 -2
- package/dist/javascript-isolate-process-entry.js +8 -0
- package/dist/javascript-isolate-process-source.js +1 -0
- package/dist/javascript-isolate-process.js +75 -0
- package/dist/javascript-isolate.js +39 -30
- package/dist/javascript-verifier-node.js +5 -0
- package/dist/javascript-verifier-worker-source.js +1 -1
- package/dist/sql-execution-contract.js +52 -0
- package/dist/sql-execution-engine.js +135 -0
- package/dist/sql-execution-node.js +58 -0
- package/dist/sql-execution-process-entry.js +3 -0
- package/dist/sql-execution-process-source.js +1 -0
- package/dist/sql-wasm-binary.js +1 -0
- package/dist/types/environment-execution-services-contract.d.ts +74 -0
- package/dist/types/environment-execution-services-contract.d.ts.map +1 -0
- package/dist/types/environment-execution-services.d.ts +17 -0
- package/dist/types/environment-execution-services.d.ts.map +1 -0
- package/dist/types/evidence/conformance.d.ts +4 -4
- package/dist/types/evidence/contracts.d.ts +4 -4
- package/dist/types/javascript-environment-contract.d.ts +121 -0
- package/dist/types/javascript-environment-contract.d.ts.map +1 -1
- package/dist/types/javascript-environment-node.d.ts +2 -0
- package/dist/types/javascript-environment-node.d.ts.map +1 -1
- package/dist/types/javascript-environment.d.ts +36 -0
- package/dist/types/javascript-environment.d.ts.map +1 -1
- package/dist/types/javascript-isolate-process-entry.d.ts +2 -0
- package/dist/types/javascript-isolate-process-entry.d.ts.map +1 -0
- package/dist/types/javascript-isolate-process-source.d.ts +2 -0
- package/dist/types/javascript-isolate-process.d.ts +4 -0
- package/dist/types/javascript-isolate-process.d.ts.map +1 -0
- package/dist/types/javascript-isolate.d.ts +4 -1
- package/dist/types/javascript-isolate.d.ts.map +1 -1
- package/dist/types/javascript-verifier-node.d.ts +2 -0
- package/dist/types/javascript-verifier-node.d.ts.map +1 -1
- package/dist/types/learning/contracts.d.ts +11 -11
- package/dist/types/learning/operations.d.ts +6 -6
- package/dist/types/learning/repository.d.ts +5 -5
- package/dist/types/learning/transport.d.ts +27 -27
- package/dist/types/sql-execution-contract.d.ts +72 -0
- package/dist/types/sql-execution-contract.d.ts.map +1 -0
- package/dist/types/sql-execution-engine.d.ts +4 -0
- package/dist/types/sql-execution-engine.d.ts.map +1 -0
- package/dist/types/sql-execution-node.d.ts +9 -0
- package/dist/types/sql-execution-node.d.ts.map +1 -0
- package/dist/types/sql-execution-process-entry.d.ts +2 -0
- package/dist/types/sql-execution-process-entry.d.ts.map +1 -0
- package/dist/types/sql-execution-process-source.d.ts +1 -0
- package/dist/types/sql-wasm-binary.d.ts +1 -0
- package/package.json +10 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { javascriptIsolateProcessSource } from "./javascript-isolate-process-source.js";
|
|
3
|
+
import { JavaScriptIsolateExecutionError, validateJavaScriptIsolateInput } from "./javascript-isolate.js";
|
|
4
|
+
import { assertBoundedTaskJson } from "./task-schema.js";
|
|
5
|
+
/** A Node subprocess owns the interpreter; completion always waits for its exit. */
|
|
6
|
+
export async function executeJavaScriptIsolateInProcess(input) {
|
|
7
|
+
input.signal?.throwIfAborted();
|
|
8
|
+
validateJavaScriptIsolateInput(input);
|
|
9
|
+
const { signal, ...data } = input;
|
|
10
|
+
const error = (suffix) => new Error(`${input.errorPrefix}_${suffix}`);
|
|
11
|
+
// The authored source remains JSON data passed to QuickJS, never host code.
|
|
12
|
+
const program = `globalThis.__openpondIsolateInput = JSON.parse(${JSON.stringify(JSON.stringify(data))});\n${javascriptIsolateProcessSource}`;
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const child = spawn("node", ["--max-old-space-size=64", "--input-type=commonjs", "-"], {
|
|
15
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
16
|
+
env: { PATH: process.env.PATH },
|
|
17
|
+
windowsHide: true,
|
|
18
|
+
});
|
|
19
|
+
let failure;
|
|
20
|
+
let response = "";
|
|
21
|
+
let responseBytes = 0;
|
|
22
|
+
let stderrBytes = 0;
|
|
23
|
+
const stop = (cause) => {
|
|
24
|
+
failure ??= cause;
|
|
25
|
+
child.kill("SIGKILL");
|
|
26
|
+
};
|
|
27
|
+
const cancel = () => stop(signal?.reason instanceof Error ? signal.reason : error("cancelled"));
|
|
28
|
+
const timer = setTimeout(() => stop(input.errorPrefix === "candidate" ? new JavaScriptIsolateExecutionError("candidate_timeout") : error("timeout")), input.timeoutMs);
|
|
29
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
30
|
+
child.stdout.setEncoding("utf8");
|
|
31
|
+
child.stdout.on("data", (chunk) => {
|
|
32
|
+
responseBytes += Buffer.byteLength(chunk);
|
|
33
|
+
if (responseBytes > input.maxResultBytes + 65_536)
|
|
34
|
+
stop(error("process_response_too_large"));
|
|
35
|
+
else
|
|
36
|
+
response += chunk;
|
|
37
|
+
});
|
|
38
|
+
child.stderr.on("data", (chunk) => {
|
|
39
|
+
stderrBytes += chunk.byteLength;
|
|
40
|
+
if (stderrBytes > 65_536)
|
|
41
|
+
stop(error("process_stderr_too_large"));
|
|
42
|
+
});
|
|
43
|
+
child.once("error", (cause) => { failure ??= cause; });
|
|
44
|
+
child.stdin.on("error", (cause) => stop(cause));
|
|
45
|
+
child.once("close", (code) => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
signal?.removeEventListener("abort", cancel);
|
|
48
|
+
if (failure) {
|
|
49
|
+
reject(failure);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
if (code !== 0)
|
|
54
|
+
throw error("process_exited_without_result");
|
|
55
|
+
const message = JSON.parse(response);
|
|
56
|
+
if (!message || typeof message !== "object" || !("ok" in message))
|
|
57
|
+
throw error("process_invalid_response");
|
|
58
|
+
if (message.ok !== true) {
|
|
59
|
+
const detail = "error" in message && typeof message.error === "string" ? message.error : `${input.errorPrefix}_execution_failed`;
|
|
60
|
+
throw "executionError" in message && message.executionError === true ? new JavaScriptIsolateExecutionError(detail) : new Error(detail);
|
|
61
|
+
}
|
|
62
|
+
if (!("result" in message))
|
|
63
|
+
throw error("process_invalid_response");
|
|
64
|
+
assertBoundedTaskJson(message.result, input.maxResultBytes);
|
|
65
|
+
resolve(message.result);
|
|
66
|
+
}
|
|
67
|
+
catch (cause) {
|
|
68
|
+
reject(cause instanceof Error ? cause : error("process_invalid_response"));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
child.stdin.end(program);
|
|
72
|
+
if (signal?.aborted)
|
|
73
|
+
cancel();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { newQuickJSWASMModuleFromVariant, Scope } from "quickjs-emscripten-core";
|
|
2
2
|
import variant from "@jitl/quickjs-singlefile-cjs-release-sync";
|
|
3
3
|
import { assertBoundedTaskJson } from "./task-schema.js";
|
|
4
|
+
export class JavaScriptIsolateExecutionError extends Error {
|
|
5
|
+
constructor(message) { super(message); this.name = "JavaScriptIsolateExecutionError"; }
|
|
6
|
+
}
|
|
4
7
|
/** Authored code is interpreter data, with no host functions or module loader. */
|
|
5
8
|
export async function executeJavaScriptIsolate(input) {
|
|
6
9
|
validateJavaScriptIsolateInput(input);
|
|
@@ -9,37 +12,43 @@ export async function executeJavaScriptIsolate(input) {
|
|
|
9
12
|
const deadline = Date.now() + input.timeoutMs;
|
|
10
13
|
const module = await newQuickJSWASMModuleFromVariant(variant);
|
|
11
14
|
input.signal?.throwIfAborted();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
15
|
+
try {
|
|
16
|
+
return Scope.withScope((scope) => {
|
|
17
|
+
const runtime = scope.manage(module.newRuntime());
|
|
18
|
+
runtime.setMemoryLimit(33_554_432);
|
|
19
|
+
runtime.setMaxStackSize(262_144);
|
|
20
|
+
runtime.setInterruptHandler(() => Date.now() >= deadline || input.signal?.aborted === true);
|
|
21
|
+
const context = scope.manage(runtime.newContext());
|
|
22
|
+
if (input.deterministic) {
|
|
23
|
+
scope.manage(context.unwrapResult(context.evalCode("globalThis.Date = undefined; Math.random = () => { throw new Error('environment_random_requires_explicit_seed'); };")));
|
|
24
|
+
}
|
|
25
|
+
const stringify = scope.manage(context.unwrapResult(context.evalCode("JSON.stringify")));
|
|
26
|
+
const argument = scope.manage(context.unwrapResult(context.evalCode(`JSON.parse(${JSON.stringify(JSON.stringify(input.value))})`)));
|
|
27
|
+
const exported = scope.manage(context.unwrapResult(context.evalCode(input.source, `${input.errorPrefix}.mjs`, { type: "module" })));
|
|
28
|
+
const namespace = scope.manage(settle(context, exported, deadline, input.signal, error));
|
|
29
|
+
const fn = scope.manage(context.getProp(namespace, input.exportName));
|
|
30
|
+
if (context.typeof(fn) !== "function")
|
|
31
|
+
throw error("function_export_missing");
|
|
32
|
+
const returned = scope.manage(context.unwrapResult(context.callFunction(fn, context.undefined, argument)));
|
|
33
|
+
const result = scope.manage(settle(context, returned, deadline, input.signal, error));
|
|
34
|
+
const serialized = scope.manage(context.unwrapResult(context.callFunction(stringify, context.undefined, result)));
|
|
35
|
+
if (context.typeof(serialized) !== "string")
|
|
36
|
+
throw error("result_not_json");
|
|
37
|
+
const json = context.getString(serialized);
|
|
38
|
+
if (new TextEncoder().encode(json).byteLength > input.maxResultBytes)
|
|
39
|
+
throw error("result_too_large");
|
|
40
|
+
input.signal?.throwIfAborted();
|
|
41
|
+
if (Date.now() >= deadline)
|
|
42
|
+
throw error("timeout");
|
|
43
|
+
const value = JSON.parse(json);
|
|
44
|
+
assertBoundedTaskJson(value, input.maxResultBytes);
|
|
45
|
+
return value;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
36
49
|
input.signal?.throwIfAborted();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const value = JSON.parse(json);
|
|
40
|
-
assertBoundedTaskJson(value, input.maxResultBytes);
|
|
41
|
-
return value;
|
|
42
|
-
});
|
|
50
|
+
throw new JavaScriptIsolateExecutionError(cause instanceof Error ? cause.message : "JavaScript execution failed.");
|
|
51
|
+
}
|
|
43
52
|
}
|
|
44
53
|
export function validateJavaScriptIsolateInput(input) {
|
|
45
54
|
if (!Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 300_000)
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { JavaScriptVerifierResultSchema } from "./javascript-verifier-contract.js";
|
|
2
2
|
import { executeJavaScriptIsolateInWorker } from "./javascript-isolate-node.js";
|
|
3
|
+
import { executeJavaScriptIsolateInProcess } from "./javascript-isolate-process.js";
|
|
3
4
|
/** Resolves/rejects only after the execution owner has stopped the worker. */
|
|
4
5
|
export async function executeJavaScriptVerifierInWorker(input) {
|
|
5
6
|
return JavaScriptVerifierResultSchema.parse(await executeJavaScriptIsolateInWorker({ ...input, exportName: input.exportName ?? "verify", maxResultBytes: 65_536, errorPrefix: "verifier" }));
|
|
6
7
|
}
|
|
8
|
+
/** Uses a Node child process and waits for exit on success, failure, or cancellation. */
|
|
9
|
+
export async function executeJavaScriptVerifierInProcess(input) {
|
|
10
|
+
return JavaScriptVerifierResultSchema.parse(await executeJavaScriptIsolateInProcess({ ...input, exportName: input.exportName ?? "verify", maxResultBytes: 65_536, errorPrefix: "verifier" }));
|
|
11
|
+
}
|