@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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
These entrypoints execute authored tool modules over owner-held JSON state:
|
|
4
4
|
|
|
5
5
|
- `@openpond/evals/javascript-environment` validates immutable definitions and creates an episode with `step`, `collect`, `snapshot` and `destroy`.
|
|
6
|
-
- `@openpond/evals/javascript-environment/node`
|
|
6
|
+
- `@openpond/evals/javascript-environment/node` exposes worker and Node subprocess owners around the isolated interpreter.
|
|
7
7
|
- `@openpond/evals/javascript-environment/attempt` runs a policy adapter through the declared tools and returns its messages, output and independently recorded state.
|
|
8
8
|
|
|
9
9
|
The interpreter exposes no host functions, filesystem, network, timers or module loader. Environment code receives an explicit seed and supplied inputs; ambient `Date` and `Math.random` are unavailable. A module cannot call a production connector or launch a process. Tools that require those capabilities need a separate host adapter and its permissions.
|
|
@@ -53,7 +53,9 @@ Hosts persist the returned attempt under their task/model/release identity and g
|
|
|
53
53
|
|
|
54
54
|
## Lifecycle ownership
|
|
55
55
|
|
|
56
|
-
Server hosts should pass `
|
|
56
|
+
Server hosts that require process isolation should pass `executeJavaScriptEnvironmentInProcess` as the session or attempt runner. It requires Node 22.14–24 on `PATH` and gives each operation a fresh Node child and QuickJS interpreter. The child inherits only `PATH`, uses a bounded heap and response stream, and is killed on timeout or cancellation. Authored source travels as JSON interpreter data. A promise settles only after the child exits. This keeps interpreter allocation outside the execution owner's worker-thread lifecycle. `executeJavaScriptVerifierInProcess` provides the same ownership for graders from `@openpond/evals/javascript-verifier/node`.
|
|
57
|
+
|
|
58
|
+
Node hosts can also use `executeJavaScriptEnvironmentInWorker`. Each worker runs a fresh interpreter and settles only after termination. Both owners include startup in the declared deadline; neither retries failed operations. The session rejects overlapping operations and waits for active execution to stop before cleanup. Call `destroy` in `finally` when using the session API directly; the attempt runner does this automatically.
|
|
57
59
|
|
|
58
60
|
Cleanup receives the definition's `operationTimeoutMs`, including interpreter and
|
|
59
61
|
worker startup, with no shorter implicit deadline. It still runs after the episode
|
|
@@ -61,3 +63,50 @@ is cancelled, waits for worker termination, and fails explicitly when that bound
|
|
|
61
63
|
operation deadline is exceeded.
|
|
62
64
|
|
|
63
65
|
The policy adapter must honor its supplied abort signal and settle after cancelling its own request. `environmentCleanupComplete` describes environment cleanup only; it is not a provider billing or infrastructure-cleanup receipt. Failed creation, collection, cancellation and cleanup remain explicit states, without fabricated scores.
|
|
66
|
+
|
|
67
|
+
## Submitted SQL and JavaScript
|
|
68
|
+
|
|
69
|
+
An optional `executionServices` array on the immutable environment definition binds
|
|
70
|
+
an owner-controlled service to a named `step` tool or to `collect`. Omission preserves
|
|
71
|
+
existing definition hashes. Each service declares an identity, deadline and explicit
|
|
72
|
+
value references: `{ scope: "input" | "initialState" | "state" | "arguments", path: [...] }`.
|
|
73
|
+
References traverse own JSON properties only. The controller receives service results
|
|
74
|
+
as `services[id]`; candidates cannot supply that field.
|
|
75
|
+
|
|
76
|
+
A `sqlite.v1` binding declares `sql`, `snapshot`, `maxRows` and `maxResultBytes`.
|
|
77
|
+
Snapshots contain named tables, typed columns (`INTEGER`, `REAL`, `TEXT`, `BLOB`) and
|
|
78
|
+
array rows. SQLite executes exactly one statement in a fresh in-memory database.
|
|
79
|
+
The Node adapter enforces read-only authorization, a deterministic function allowlist,
|
|
80
|
+
a 32 MiB WASM memory maximum, value/SQL/result limits and a parent-owned deadline.
|
|
81
|
+
Writes, attachment, pragma changes, extensions and nondeterministic functions are
|
|
82
|
+
rejected. Results retain ordered column names and array rows, including duplicates.
|
|
83
|
+
SQL null is JSON null; exact large integers use `{ integer: "9223372036854775807" }`
|
|
84
|
+
and blobs use `{ blobBase64: "AP8=" }`. JSON numbers retain their ordinary IEEE-754
|
|
85
|
+
semantics. Use tagged integers when exact 64-bit storage is required.
|
|
86
|
+
|
|
87
|
+
A `javascript.v1` binding declares `source`, `cases`, `exportName`, `maxCases` and
|
|
88
|
+
`maxResultBytes`. Cases are owner-held objects with an `input` property. Each case
|
|
89
|
+
runs in a fresh QuickJS interpreter, and only its `input` crosses into the candidate.
|
|
90
|
+
Other properties, including expected results, remain with the owner. Put hidden cases
|
|
91
|
+
and expectations in private `initialState`, not task `input`: task input is part of
|
|
92
|
+
the policy transcript. The authored controller grades returned case values against
|
|
93
|
+
its own expectations. No host functions, module loader, filesystem or network are
|
|
94
|
+
exposed to candidate JavaScript.
|
|
95
|
+
|
|
96
|
+
Service outcomes are either completed results or explicit rejected submissions.
|
|
97
|
+
Syntax, authorization, output and engine-memory failures cannot manufacture a passing
|
|
98
|
+
answer. A service timeout produces a rejected outcome only after child termination;
|
|
99
|
+
owner cancellation and infrastructure setup failures still fail the operation.
|
|
100
|
+
Service preparation and the authored controller share the operation's overall budget
|
|
101
|
+
and its tracked promise. Collection remains unable to mutate session state, and
|
|
102
|
+
`destroy()` aborts and awaits all active work before running authored cleanup.
|
|
103
|
+
|
|
104
|
+
`executeJavaScriptEnvironmentInProcess` and `executeJavaScriptEnvironmentInWorker`
|
|
105
|
+
provide these services on Node hosts; service children always use Node processes.
|
|
106
|
+
The portable in-process interpreter supports candidate JavaScript and rejects a SQL
|
|
107
|
+
binding without a Node SQL executor. The standalone Node API is
|
|
108
|
+
`executeSqlInProcess({ request, timeoutMs, signal })` from
|
|
109
|
+
`@openpond/evals/sql-execution/node`; request/result schemas are available from
|
|
110
|
+
`@openpond/evals/sql-execution`. The SQLite WASM bytes are embedded in the trusted
|
|
111
|
+
child program, so execution survives installation and host bundling without resolving
|
|
112
|
+
an application-relative WASM file or loading submitted code as host JavaScript.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SqlExecutionRequestSchema } from "./sql-execution-contract.js";
|
|
3
|
+
/** Explicit owner-side paths; resolution never evaluates code or traverses prototypes. */
|
|
4
|
+
export const EnvironmentValueReferenceSchema = z.object({
|
|
5
|
+
scope: z.enum(["input", "initialState", "state", "arguments"]),
|
|
6
|
+
path: z.array(z.string().min(1).max(256)).max(16),
|
|
7
|
+
}).strict();
|
|
8
|
+
const common = {
|
|
9
|
+
id: z.string().min(1).max(128),
|
|
10
|
+
operation: z.enum(["step", "collect"]),
|
|
11
|
+
toolName: z.string().min(1).max(500).optional(),
|
|
12
|
+
timeoutMs: z.number().int().min(1).max(20_000),
|
|
13
|
+
};
|
|
14
|
+
export const EnvironmentExecutionServiceSchema = z.discriminatedUnion("kind", [
|
|
15
|
+
z.object({ ...common, kind: z.literal("sqlite.v1"), sql: EnvironmentValueReferenceSchema, snapshot: EnvironmentValueReferenceSchema,
|
|
16
|
+
maxRows: SqlExecutionRequestSchema.shape.maxRows, maxResultBytes: SqlExecutionRequestSchema.shape.maxResultBytes }).strict(),
|
|
17
|
+
z.object({ ...common, kind: z.literal("javascript.v1"), source: EnvironmentValueReferenceSchema, cases: EnvironmentValueReferenceSchema,
|
|
18
|
+
exportName: z.string().regex(/^[A-Za-z_$][A-Za-z0-9_$]*$/).max(128), maxCases: z.number().int().min(1).max(32), maxResultBytes: z.number().int().min(256).max(262_144) }).strict(),
|
|
19
|
+
]).superRefine((service, context) => {
|
|
20
|
+
if ((service.operation === "step") !== (service.toolName !== undefined))
|
|
21
|
+
context.addIssue({ code: "custom", path: ["toolName"], message: "Only step services require a declared tool name." });
|
|
22
|
+
if (service.operation === "collect") {
|
|
23
|
+
const references = service.kind === "sqlite.v1" ? [service.sql, service.snapshot] : [service.source, service.cases];
|
|
24
|
+
if (references.some(reference => reference.scope === "arguments"))
|
|
25
|
+
context.addIssue({ code: "custom", message: "Collection has no tool arguments." });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { EnvironmentExecutionServiceSchema } from "./environment-execution-services-contract.js";
|
|
2
|
+
import { JavaScriptIsolateExecutionError } from "./javascript-isolate.js";
|
|
3
|
+
import { assertBoundedTaskJson } from "./task-schema.js";
|
|
4
|
+
import { assertSqlExecutionRequest } from "./sql-execution-contract.js";
|
|
5
|
+
function resolveValue(value, reference) {
|
|
6
|
+
let resolved = reference.scope === "arguments" ? value.action?.arguments : value[reference.scope];
|
|
7
|
+
for (const key of reference.path) {
|
|
8
|
+
if (!resolved || typeof resolved !== "object" || !Object.hasOwn(resolved, key))
|
|
9
|
+
throw new Error("environment_service_reference_missing");
|
|
10
|
+
resolved = resolved[key];
|
|
11
|
+
}
|
|
12
|
+
return resolved;
|
|
13
|
+
}
|
|
14
|
+
/** The single returned promise owns service work, controller execution and cancellation. */
|
|
15
|
+
export async function executeEnvironmentWithServices(input, runners) {
|
|
16
|
+
if (!input.executionServices?.length)
|
|
17
|
+
return runners.controller(input);
|
|
18
|
+
if (!Number.isSafeInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30_000)
|
|
19
|
+
throw new Error("environment_timeout_invalid");
|
|
20
|
+
assertBoundedTaskJson(input.value, 4_194_304);
|
|
21
|
+
const services = input.executionServices.map(service => EnvironmentExecutionServiceSchema.parse(service));
|
|
22
|
+
if (services.length > 16)
|
|
23
|
+
throw new Error("environment_service_limit");
|
|
24
|
+
if (new Set(services.map(service => service.id)).size !== services.length)
|
|
25
|
+
throw new Error("environment_service_identity_collision");
|
|
26
|
+
const started = Date.now();
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const signal = input.signal ? AbortSignal.any([input.signal, controller.signal]) : controller.signal;
|
|
29
|
+
const timer = setTimeout(() => controller.abort(new Error("environment_timeout")), input.timeoutMs);
|
|
30
|
+
try {
|
|
31
|
+
const results = Object.create(null);
|
|
32
|
+
for (const service of services) {
|
|
33
|
+
signal.throwIfAborted();
|
|
34
|
+
if (service.operation !== input.operation || (service.operation === "step" && service.toolName !== input.value.action?.name))
|
|
35
|
+
throw new Error("environment_service_operation_mismatch");
|
|
36
|
+
const budget = Math.min(service.timeoutMs, input.timeoutMs - (Date.now() - started));
|
|
37
|
+
if (budget < 1)
|
|
38
|
+
throw new Error("environment_timeout");
|
|
39
|
+
const deadline = new AbortController();
|
|
40
|
+
const serviceSignal = AbortSignal.any([signal, deadline.signal]);
|
|
41
|
+
const serviceTimer = setTimeout(() => deadline.abort(new Error("environment_service_timeout")), budget);
|
|
42
|
+
const serviceStarted = Date.now();
|
|
43
|
+
try {
|
|
44
|
+
if (service.kind === "sqlite.v1") {
|
|
45
|
+
if (!runners.sql)
|
|
46
|
+
throw new Error("environment_sql_executor_required");
|
|
47
|
+
const sql = resolveValue(input.value, service.sql);
|
|
48
|
+
if (typeof sql !== "string" || !sql.length || new TextEncoder().encode(sql).byteLength > 65_536 || sql.includes("\0")) {
|
|
49
|
+
results[service.id] = { status: "rejected", code: "invalid_submission" };
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const request = assertSqlExecutionRequest({ schemaVersion: "openpond.sqlExecution.v1", snapshot: resolveValue(input.value, service.snapshot), sql, maxRows: service.maxRows, maxResultBytes: service.maxResultBytes });
|
|
53
|
+
results[service.id] = await runners.sql({ request, timeoutMs: input.timeoutMs, signal: serviceSignal });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const source = resolveValue(input.value, service.source), cases = resolveValue(input.value, service.cases);
|
|
57
|
+
if (typeof source !== "string" || new TextEncoder().encode(source).byteLength > 524_288) {
|
|
58
|
+
results[service.id] = { status: "rejected", code: "invalid_submission" };
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (!Array.isArray(cases) || !cases.length || cases.length > service.maxCases || cases.some(item => !item || typeof item !== "object" || !Object.hasOwn(item, "input")))
|
|
62
|
+
throw new Error("environment_service_cases_invalid");
|
|
63
|
+
const values = [];
|
|
64
|
+
for (const item of cases) {
|
|
65
|
+
const remaining = budget - (Date.now() - serviceStarted);
|
|
66
|
+
if (remaining < 1) {
|
|
67
|
+
deadline.abort(new Error("environment_service_timeout"));
|
|
68
|
+
serviceSignal.throwIfAborted();
|
|
69
|
+
}
|
|
70
|
+
// Hidden expected results and all other case fields remain with the owner.
|
|
71
|
+
values.push(await runners.candidate({ source, exportName: service.exportName, value: structuredClone(item.input), timeoutMs: Math.max(1, remaining), maxResultBytes: service.maxResultBytes, deterministic: true, errorPrefix: "candidate", signal: serviceSignal }));
|
|
72
|
+
if (new TextEncoder().encode(JSON.stringify({ status: "completed", cases: values })).byteLength > service.maxResultBytes)
|
|
73
|
+
throw new JavaScriptIsolateExecutionError("candidate_result_too_large");
|
|
74
|
+
}
|
|
75
|
+
results[service.id] = { status: "completed", cases: values };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
signal.throwIfAborted();
|
|
80
|
+
if (deadline.signal.aborted)
|
|
81
|
+
results[service.id] = { status: "rejected", code: "timeout" };
|
|
82
|
+
else if (error instanceof JavaScriptIsolateExecutionError)
|
|
83
|
+
results[service.id] = { status: "rejected", code: "execution_failed" };
|
|
84
|
+
else
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
clearTimeout(serviceTimer);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
signal.throwIfAborted();
|
|
92
|
+
const remaining = input.timeoutMs - (Date.now() - started);
|
|
93
|
+
if (remaining < 1)
|
|
94
|
+
throw new Error("environment_timeout");
|
|
95
|
+
return await runners.controller({ ...input, executionServices: undefined, value: { ...input.value, services: results }, timeoutMs: remaining, signal });
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ImmutableAssetRefSchema, ToolDeclarationSchema, contentHash } from "@openpond/harness";
|
|
3
3
|
import { assertBoundedTaskJson, validateTaskSchema } from "./task-schema-validation.js";
|
|
4
|
+
import { EnvironmentExecutionServiceSchema } from "./environment-execution-services-contract.js";
|
|
5
|
+
export * from "./environment-execution-services-contract.js";
|
|
4
6
|
export const JavaScriptEnvironmentDefinitionContentSchema = z.object({
|
|
5
7
|
schemaVersion: z.literal("openpond.javascriptEnvironment.v1"),
|
|
6
8
|
id: z.string().trim().min(1).max(500),
|
|
@@ -11,6 +13,7 @@ export const JavaScriptEnvironmentDefinitionContentSchema = z.object({
|
|
|
11
13
|
maxStateBytes: z.number().int().min(1).max(1_048_576),
|
|
12
14
|
maxObservationBytes: z.number().int().min(1).max(262_144),
|
|
13
15
|
operationTimeoutMs: z.number().int().min(1).max(30_000),
|
|
16
|
+
executionServices: z.array(EnvironmentExecutionServiceSchema).min(1).max(16).optional(),
|
|
14
17
|
}).strict().superRefine((value, context) => {
|
|
15
18
|
if (new Set(value.tools.map(tool => tool.name)).size !== value.tools.length)
|
|
16
19
|
context.addIssue({ code: "custom", path: ["tools"], message: "Environment tool names must be unique." });
|
|
@@ -20,6 +23,16 @@ export const JavaScriptEnvironmentDefinitionContentSchema = z.object({
|
|
|
20
23
|
if (tool.inputSchemaHash !== contentHash(tool.inputSchema))
|
|
21
24
|
context.addIssue({ code: "custom", path: ["tools", index, "inputSchemaHash"], message: "Tool schema identity differs from its declared schema." });
|
|
22
25
|
});
|
|
26
|
+
const services = value.executionServices ?? [];
|
|
27
|
+
if (new Set(services.map(service => service.id)).size !== services.length)
|
|
28
|
+
context.addIssue({ code: "custom", path: ["executionServices"], message: "Execution service identities must be unique." });
|
|
29
|
+
services.forEach((service, index) => {
|
|
30
|
+
const tool = value.tools.find(tool => tool.name === service.toolName);
|
|
31
|
+
if (service.operation === "step" && !tool)
|
|
32
|
+
context.addIssue({ code: "custom", path: ["executionServices", index, "toolName"], message: "Execution services must target a declared tool." });
|
|
33
|
+
if (service.timeoutMs >= Math.min(value.operationTimeoutMs, tool?.timeoutMs ?? value.operationTimeoutMs))
|
|
34
|
+
context.addIssue({ code: "custom", path: ["executionServices", index, "timeoutMs"], message: "Service budgets must leave time for the authored controller." });
|
|
35
|
+
});
|
|
23
36
|
});
|
|
24
37
|
export const JavaScriptEnvironmentDefinitionSchema = JavaScriptEnvironmentDefinitionContentSchema.safeExtend({ contentHash: z.string().regex(/^[a-f0-9]{64}$/) });
|
|
25
38
|
export const JavaScriptEnvironmentOperationSchema = z.enum(["create", "reset", "step", "collect", "destroy"]);
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { JavaScriptEnvironmentOperationSchema, JavaScriptEnvironmentResultSchema } from "./javascript-environment-contract.js";
|
|
2
2
|
import { executeJavaScriptIsolateInWorker } from "./javascript-isolate-node.js";
|
|
3
|
+
import { executeJavaScriptIsolateInProcess } from "./javascript-isolate-process.js";
|
|
4
|
+
import { executeEnvironmentWithServices } from "./environment-execution-services.js";
|
|
5
|
+
import { executeSqlInProcess } from "./sql-execution-node.js";
|
|
3
6
|
/** The execution owner awaits termination before recording a step or cleanup. */
|
|
4
7
|
export async function executeJavaScriptEnvironmentInWorker(input) {
|
|
5
8
|
const operation = JavaScriptEnvironmentOperationSchema.parse(input.operation);
|
|
6
|
-
return JavaScriptEnvironmentResultSchema.parse(await executeJavaScriptIsolateInWorker({ ...
|
|
9
|
+
return executeEnvironmentWithServices(input, { sql: executeSqlInProcess, candidate: executeJavaScriptIsolateInProcess, controller: async (prepared) => JavaScriptEnvironmentResultSchema.parse(await executeJavaScriptIsolateInWorker({ ...prepared, exportName: operation, maxResultBytes: 1_572_864, deterministic: true, errorPrefix: "environment" })) });
|
|
10
|
+
}
|
|
11
|
+
/** Uses a Node child process independently of the execution owner's runtime. */
|
|
12
|
+
export async function executeJavaScriptEnvironmentInProcess(input) {
|
|
13
|
+
const operation = JavaScriptEnvironmentOperationSchema.parse(input.operation);
|
|
14
|
+
return executeEnvironmentWithServices(input, { sql: executeSqlInProcess, candidate: executeJavaScriptIsolateInProcess, controller: async (prepared) => JavaScriptEnvironmentResultSchema.parse(await executeJavaScriptIsolateInProcess({ ...prepared, exportName: operation, maxResultBytes: 1_572_864, deterministic: true, errorPrefix: "environment" })) });
|
|
7
15
|
}
|
|
@@ -2,12 +2,13 @@ import { contentHash } from "@openpond/harness";
|
|
|
2
2
|
import { verifyLearningTextAsset } from "./learning/assets.js";
|
|
3
3
|
import { assertBoundedTaskJson, validateTaskValue } from "./task-schema.js";
|
|
4
4
|
import { executeJavaScriptIsolate } from "./javascript-isolate.js";
|
|
5
|
+
import { executeEnvironmentWithServices } from "./environment-execution-services.js";
|
|
5
6
|
import { assertJavaScriptEnvironmentDefinition, JavaScriptEnvironmentOperationSchema, JavaScriptEnvironmentResultSchema } from "./javascript-environment-contract.js";
|
|
6
7
|
export * from "./javascript-environment-contract.js";
|
|
7
8
|
/** Pure, bounded module execution. Server hosts should provide the worker runner. */
|
|
8
9
|
export async function executeJavaScriptEnvironment(input) {
|
|
9
10
|
const operation = JavaScriptEnvironmentOperationSchema.parse(input.operation);
|
|
10
|
-
return JavaScriptEnvironmentResultSchema.parse(await executeJavaScriptIsolate({ ...
|
|
11
|
+
return executeEnvironmentWithServices(input, { candidate: executeJavaScriptIsolate, controller: async (prepared) => JavaScriptEnvironmentResultSchema.parse(await executeJavaScriptIsolate({ ...prepared, exportName: operation, maxResultBytes: 1_572_864, deterministic: true, errorPrefix: "environment" })) });
|
|
11
12
|
}
|
|
12
13
|
export class JavaScriptEnvironmentActionError extends Error {
|
|
13
14
|
code;
|
|
@@ -59,7 +60,8 @@ export async function createJavaScriptEnvironmentSession(input) {
|
|
|
59
60
|
throw new JavaScriptEnvironmentActionError("unknown_tool");
|
|
60
61
|
if (action && tool && !validateTaskValue(tool.inputSchema, action.arguments).valid)
|
|
61
62
|
throw new JavaScriptEnvironmentActionError("invalid_arguments");
|
|
62
|
-
const
|
|
63
|
+
const executionServices = definition.executionServices?.filter(service => service.operation === operation && (operation !== "step" || service.toolName === action?.name));
|
|
64
|
+
const promise = execute({ source, operation, value: { input: taskInput, initialState, seed, state: structuredClone(state), sequence: steps, action: action ? structuredClone(action) : null }, timeoutMs: Math.min(definition.operationTimeoutMs, tool?.timeoutMs ?? definition.operationTimeoutMs), signal, ...(executionServices?.length ? { executionServices } : {}) });
|
|
63
65
|
active = promise;
|
|
64
66
|
const result = JavaScriptEnvironmentResultSchema.parse(await promise);
|
|
65
67
|
signal.throwIfAborted();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { executeJavaScriptIsolate, JavaScriptIsolateExecutionError } from "./javascript-isolate.js";
|
|
2
|
+
// Only this bundled, trusted program runs in Node. Authored source is interpreter data.
|
|
3
|
+
const owner = globalThis;
|
|
4
|
+
const input = owner.__openpondIsolateInput;
|
|
5
|
+
delete owner.__openpondIsolateInput;
|
|
6
|
+
if (!input)
|
|
7
|
+
throw new Error("JavaScript process requires its execution owner.");
|
|
8
|
+
void executeJavaScriptIsolate(input).then((result) => process.stdout.write(JSON.stringify({ ok: true, result })), (error) => process.stdout.write(JSON.stringify({ ok: false, executionError: error instanceof JavaScriptIsolateExecutionError, error: (error instanceof Error ? error.message : "JavaScript execution failed.").slice(0, 8_192) })));
|