@openpond/evals 0.7.5 → 0.8.0
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 +47 -0
- package/LEARNING.md +30 -0
- 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 +4 -2
- package/dist/javascript-environment.js +4 -2
- package/dist/javascript-isolate-process-entry.js +2 -2
- package/dist/javascript-isolate-process-source.js +1 -1
- package/dist/javascript-isolate-process.js +6 -4
- package/dist/javascript-isolate.js +39 -30
- package/dist/javascript-verifier-worker-source.js +1 -1
- package/dist/learning/authoring-service.js +1 -1
- package/dist/learning/authoring.js +9 -0
- package/dist/learning/index.js +4 -0
- package/dist/learning/operations.js +3 -1
- package/dist/learning/repository.js +2 -0
- package/dist/learning/reward-authoring.js +120 -0
- package/dist/learning/reward-check-service.js +31 -0
- package/dist/learning/reward-check-worker.js +114 -0
- package/dist/learning/reward-checks.js +81 -0
- package/dist/learning/reward-fixture-executor.js +29 -0
- package/dist/learning/service.js +7 -0
- package/dist/rewards.js +3 -0
- 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.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.d.ts.map +1 -1
- package/dist/types/javascript-isolate.d.ts +4 -1
- package/dist/types/javascript-isolate.d.ts.map +1 -1
- package/dist/types/learning/admission.d.ts +12 -0
- package/dist/types/learning/admission.d.ts.map +1 -1
- package/dist/types/learning/authoring-service.d.ts +172 -0
- package/dist/types/learning/authoring-service.d.ts.map +1 -1
- package/dist/types/learning/authoring.d.ts +121 -0
- package/dist/types/learning/authoring.d.ts.map +1 -1
- package/dist/types/learning/contracts.d.ts +23 -11
- package/dist/types/learning/contracts.d.ts.map +1 -1
- package/dist/types/learning/index.d.ts +4 -0
- package/dist/types/learning/index.d.ts.map +1 -1
- package/dist/types/learning/operations.d.ts +134 -6
- package/dist/types/learning/operations.d.ts.map +1 -1
- package/dist/types/learning/repository.d.ts +114 -5
- package/dist/types/learning/repository.d.ts.map +1 -1
- package/dist/types/learning/reward-authoring.d.ts +18 -0
- package/dist/types/learning/reward-authoring.d.ts.map +1 -0
- package/dist/types/learning/reward-check-service.d.ts +17 -0
- package/dist/types/learning/reward-check-service.d.ts.map +1 -0
- package/dist/types/learning/reward-check-worker.d.ts +27 -0
- package/dist/types/learning/reward-check-worker.d.ts.map +1 -0
- package/dist/types/learning/reward-checks.d.ts +177 -0
- package/dist/types/learning/reward-checks.d.ts.map +1 -0
- package/dist/types/learning/reward-fixture-executor.d.ts +9 -0
- package/dist/types/learning/reward-fixture-executor.d.ts.map +1 -0
- package/dist/types/learning/service.d.ts.map +1 -1
- package/dist/types/learning/transport.d.ts +200 -28
- package/dist/types/learning/transport.d.ts.map +1 -1
- package/dist/types/rewards.d.ts +24 -0
- package/dist/types/rewards.d.ts.map +1 -1
- 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
- package/schemas/learning/v1/command-request.schema.json +412 -248
- package/schemas/learning/v1/draft.schema.json +96 -0
- package/schemas/learning/v1/read-request.schema.json +1 -0
- package/schemas/learning/v1/reward.schema.json +3 -0
- package/schemas/learning/v1/reward_check.schema.json +343 -0
|
@@ -63,3 +63,50 @@ is cancelled, waits for worker termination, and fails explicitly when that bound
|
|
|
63
63
|
operation deadline is exceeded.
|
|
64
64
|
|
|
65
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.
|
package/LEARNING.md
CHANGED
|
@@ -79,6 +79,36 @@ for untrusted schemas and deliberately rejects unsupported capabilities.
|
|
|
79
79
|
|
|
80
80
|
## Host responsibilities
|
|
81
81
|
|
|
82
|
+
### Reward fixture checks
|
|
83
|
+
|
|
84
|
+
Reward drafts may contain up to 50 named fixtures. JSON and numeric editor fields
|
|
85
|
+
remain strings while a draft is unfinished. `compileRewardAuthoring` is the shared
|
|
86
|
+
source/rubric compiler; `compileRewardFixtures` validates executable fixture input.
|
|
87
|
+
Published fixtures are an immutable evaluator-private `fixtureSetRef` asset included
|
|
88
|
+
in the Reward's `assets`, so editing the next release restores the same examples.
|
|
89
|
+
|
|
90
|
+
`queue_reward_check` takes an exact current Reward draft reference, timeout and
|
|
91
|
+
explicit spend ceiling. It stores a `reward_check` resource without publishing a
|
|
92
|
+
Reward, Task format, source or task evidence. The job binds the compiled Reward and
|
|
93
|
+
fixture hashes. Its worker resolves historical draft bytes even if another editor
|
|
94
|
+
saves or publishes a newer revision. Retry uses the same operation receipt; a
|
|
95
|
+
different request under that operation ID conflicts.
|
|
96
|
+
|
|
97
|
+
Hosts run `createRewardCheckWorker` with an execution adapter that records its
|
|
98
|
+
runtime/package identity and settles only after cleanup. The isolated adapter runs
|
|
99
|
+
portable deterministic checks and authored JavaScript with the supplied interpreter.
|
|
100
|
+
Human review remains pending; missing judge/learned-model adapters remain unavailable.
|
|
101
|
+
No fixture result implicitly calibrates a judge or qualifies a training runtime.
|
|
102
|
+
Per-fixture results distinguish a scored rejection, unavailable grading and grader
|
|
103
|
+
failure, and record whether each outcome matched its authored expectation.
|
|
104
|
+
|
|
105
|
+
Checks are indexed by Reward ID, retain their original draft reference and remain
|
|
106
|
+
readable after publication. Editing source or fixtures must mark prior checks as
|
|
107
|
+
historical. `cancel_reward_check` requests cancellation with revision concurrency;
|
|
108
|
+
the worker publishes terminal cancellation only after execution-owner cleanup.
|
|
109
|
+
|
|
110
|
+
### Source and execution ownership
|
|
111
|
+
|
|
82
112
|
Authored verifier code and rubrics use immutable `asset` resources. Publish source
|
|
83
113
|
and its Reward with `publish_resources`; the service checks UTF-8 size, SHA-256,
|
|
84
114
|
full reference identity, scope and evaluator visibility in the same transaction.
|
|
@@ -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,13 +1,15 @@
|
|
|
1
1
|
import { JavaScriptEnvironmentOperationSchema, JavaScriptEnvironmentResultSchema } from "./javascript-environment-contract.js";
|
|
2
2
|
import { executeJavaScriptIsolateInWorker } from "./javascript-isolate-node.js";
|
|
3
3
|
import { executeJavaScriptIsolateInProcess } from "./javascript-isolate-process.js";
|
|
4
|
+
import { executeEnvironmentWithServices } from "./environment-execution-services.js";
|
|
5
|
+
import { executeSqlInProcess } from "./sql-execution-node.js";
|
|
4
6
|
/** The execution owner awaits termination before recording a step or cleanup. */
|
|
5
7
|
export async function executeJavaScriptEnvironmentInWorker(input) {
|
|
6
8
|
const operation = JavaScriptEnvironmentOperationSchema.parse(input.operation);
|
|
7
|
-
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" })) });
|
|
8
10
|
}
|
|
9
11
|
/** Uses a Node child process independently of the execution owner's runtime. */
|
|
10
12
|
export async function executeJavaScriptEnvironmentInProcess(input) {
|
|
11
13
|
const operation = JavaScriptEnvironmentOperationSchema.parse(input.operation);
|
|
12
|
-
return JavaScriptEnvironmentResultSchema.parse(await executeJavaScriptIsolateInProcess({ ...
|
|
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" })) });
|
|
13
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();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { executeJavaScriptIsolate } from "./javascript-isolate.js";
|
|
1
|
+
import { executeJavaScriptIsolate, JavaScriptIsolateExecutionError } from "./javascript-isolate.js";
|
|
2
2
|
// Only this bundled, trusted program runs in Node. Authored source is interpreter data.
|
|
3
3
|
const owner = globalThis;
|
|
4
4
|
const input = owner.__openpondIsolateInput;
|
|
5
5
|
delete owner.__openpondIsolateInput;
|
|
6
6
|
if (!input)
|
|
7
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, error: (error instanceof Error ? error.message : "JavaScript execution failed.").slice(0, 8_192) })));
|
|
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) })));
|