@nylorun/runtime 0.15.2-beta → 0.16.0-beta
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/CHANGELOG.md +76 -0
- package/README.md +33 -6
- package/dist/adapters/execution/restate.d.ts +9 -1
- package/dist/adapters/execution/restate.js +64 -5
- package/dist/adapters/sandbox/local.d.ts +10 -0
- package/dist/adapters/sandbox/local.js +195 -0
- package/dist/adapters/sandbox/virtual.js +36 -1
- package/dist/admin-openapi.json +37 -3
- package/dist/api/a2a/tasks.js +6 -1
- package/dist/api/ag-ui/history.js +6 -1
- package/dist/api/ag-ui/routes.js +5 -1
- package/dist/api/components.d.ts +2673 -655
- package/dist/api/components.js +17 -1
- package/dist/api/gate/keys.d.ts +4 -3
- package/dist/api/gate/routes.d.ts +19 -3
- package/dist/api/gate/routes.js +162 -53
- package/dist/api/http/app.js +4 -0
- package/dist/api/http/define.d.ts +10 -0
- package/dist/api/http/define.js +2 -1
- package/dist/api/http/routes/artifacts.d.ts +11 -0
- package/dist/api/http/routes/artifacts.js +518 -0
- package/dist/api/http/routes/sandboxes.d.ts +9 -0
- package/dist/api/http/routes/sandboxes.js +152 -0
- package/dist/api/http/routes/sessions.js +3 -1
- package/dist/api/http/routes/tenant.js +36 -2
- package/dist/artifacts/config.d.ts +23 -0
- package/dist/artifacts/config.js +27 -0
- package/dist/artifacts/export.d.ts +8 -0
- package/dist/artifacts/export.js +205 -0
- package/dist/artifacts/files.d.ts +36 -0
- package/dist/artifacts/files.js +45 -0
- package/dist/artifacts/folders.d.ts +60 -0
- package/dist/artifacts/folders.js +209 -0
- package/dist/artifacts/links.d.ts +28 -0
- package/dist/artifacts/links.js +92 -0
- package/dist/artifacts/media-types.d.ts +13 -0
- package/dist/artifacts/media-types.js +80 -0
- package/dist/artifacts/parts.d.ts +34 -0
- package/dist/artifacts/parts.js +38 -0
- package/dist/artifacts/service.d.ts +74 -0
- package/dist/artifacts/service.js +301 -0
- package/dist/artifacts/workspace.d.ts +60 -0
- package/dist/artifacts/workspace.js +57 -0
- package/dist/artifacts/zip.d.ts +10 -0
- package/dist/artifacts/zip.js +126 -0
- package/dist/blob/fs.d.ts +13 -0
- package/dist/blob/fs.js +194 -0
- package/dist/blob/index.d.ts +9 -0
- package/dist/blob/index.js +9 -0
- package/dist/blob/s3.d.ts +42 -0
- package/dist/blob/s3.js +366 -0
- package/dist/blob/types.d.ts +108 -0
- package/dist/blob/types.js +85 -0
- package/dist/core/sandbox-routes.d.ts +2 -2
- package/dist/core/sandbox-routes.js +4 -3
- package/dist/execution/memory.d.ts +11 -1
- package/dist/execution/memory.js +77 -3
- package/dist/execution/types.d.ts +54 -1
- package/dist/execution/types.js +31 -0
- package/dist/gates/contract.d.ts +14 -13
- package/dist/gates/contract.js +13 -12
- package/dist/gates/egress.d.ts +72 -0
- package/dist/gates/egress.js +319 -0
- package/dist/gates/handler.d.ts +3 -0
- package/dist/gates/handler.js +3 -2
- package/dist/gates/http-client.d.ts +3 -2
- package/dist/gates/http-client.js +17 -6
- package/dist/gates/in-process.d.ts +13 -1
- package/dist/gates/in-process.js +11 -0
- package/dist/gates/inflight.d.ts +23 -4
- package/dist/gates/inflight.js +18 -4
- package/dist/gates/model-gate.d.ts +6 -2
- package/dist/gates/tool-calls.d.ts +9 -5
- package/dist/gates/tool-calls.js +6 -6
- package/dist/gates/tool-client.d.ts +9 -2
- package/dist/gates/tool-client.js +56 -22
- package/dist/gates/tool-contract.d.ts +12 -7
- package/dist/gates/tool-contract.js +10 -4
- package/dist/gates/tool-gate.d.ts +1 -0
- package/dist/harness/calls.d.ts +52 -0
- package/dist/harness/calls.js +113 -0
- package/dist/harness/executors.d.ts +22 -0
- package/dist/harness/executors.js +96 -0
- package/dist/harness/main.d.ts +6 -0
- package/dist/harness/main.js +103 -0
- package/dist/harness/pod.d.ts +21 -0
- package/dist/harness/pod.js +119 -0
- package/dist/harness/run-tokens.d.ts +18 -0
- package/dist/harness/run-tokens.js +15 -0
- package/dist/harness/service.d.ts +46 -0
- package/dist/harness/service.js +179 -0
- package/dist/harness/ws-client.d.ts +43 -0
- package/dist/harness/ws-client.js +122 -0
- package/dist/harness/ws-port.d.ts +22 -0
- package/dist/harness/ws-port.js +78 -0
- package/dist/harness-api/in-process.d.ts +24 -0
- package/dist/harness-api/in-process.js +119 -0
- package/dist/harness-api/record.d.ts +44 -0
- package/dist/harness-api/record.js +250 -0
- package/dist/harness-api/renew.d.ts +37 -0
- package/dist/harness-api/renew.js +77 -0
- package/dist/harness-api/server.d.ts +104 -0
- package/dist/harness-api/server.js +408 -0
- package/dist/harness-api/start.d.ts +36 -0
- package/dist/harness-api/start.js +118 -0
- package/dist/harness-api/workspace.d.ts +53 -0
- package/dist/harness-api/workspace.js +201 -0
- package/dist/harness-api/ws-server.d.ts +34 -0
- package/dist/harness-api/ws-server.js +227 -0
- package/dist/host/app.js +23 -2
- package/dist/host/gates.d.ts +27 -2
- package/dist/host/gates.js +46 -1
- package/dist/host/http.d.ts +4 -1
- package/dist/host/http.js +6 -1
- package/dist/host/main.js +135 -27
- package/dist/host/stack-config.d.ts +101 -1
- package/dist/host/stack-config.js +199 -8
- package/dist/mcp/snapshot.d.ts +8 -1
- package/dist/mcp/snapshot.js +28 -0
- package/dist/media.d.ts +5 -24
- package/dist/media.js +5 -0
- package/dist/model/pi-model.d.ts +6 -2
- package/dist/model/pi-model.js +39 -22
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +1 -1
- package/dist/openapi.json +15245 -8850
- package/dist/record/index.d.ts +1 -0
- package/dist/record/index.js +1 -0
- package/dist/record/sandbox.d.ts +25 -0
- package/dist/record/sandbox.js +43 -0
- package/dist/sandbox/capability.d.ts +3 -0
- package/dist/sandbox/capability.js +10 -0
- package/dist/sandbox/egress-token.d.ts +26 -0
- package/dist/sandbox/egress-token.js +8 -0
- package/dist/sandbox/join.d.ts +37 -0
- package/dist/sandbox/join.js +165 -0
- package/dist/sandbox/manager.d.ts +99 -34
- package/dist/sandbox/manager.js +216 -84
- package/dist/sandbox/placement.d.ts +27 -0
- package/dist/sandbox/placement.js +44 -0
- package/dist/sandbox/pods/client.d.ts +90 -0
- package/dist/sandbox/pods/client.js +79 -0
- package/dist/sandbox/pods/lifecycle.d.ts +91 -0
- package/dist/sandbox/pods/lifecycle.js +178 -0
- package/dist/sandbox/pods/name.d.ts +1 -0
- package/dist/sandbox/pods/name.js +28 -0
- package/dist/sandbox/pods/network-gate.d.ts +25 -0
- package/dist/sandbox/pods/network-gate.js +71 -0
- package/dist/sandbox/pods/reconcile.d.ts +11 -0
- package/dist/sandbox/pods/reconcile.js +152 -0
- package/dist/sandbox/pods/spec.d.ts +45 -0
- package/dist/sandbox/pods/spec.js +62 -0
- package/dist/sandbox/records.d.ts +29 -0
- package/dist/sandbox/records.js +79 -0
- package/dist/sandbox/resolve.d.ts +5 -0
- package/dist/sandbox/resolve.js +3 -2
- package/dist/sandbox/session-sandbox.js +7 -5
- package/dist/sandbox/share.d.ts +11 -0
- package/dist/sandbox/share.js +10 -0
- package/dist/sandbox/tenant-config.d.ts +16 -1
- package/dist/sandbox/tenant-config.js +9 -0
- package/dist/sandbox/types.d.ts +29 -1
- package/dist/sandbox/types.js +13 -1
- package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
- package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
- package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
- package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/record-writer.d.ts +6 -1
- package/dist/store/postgres/record-writer.js +20 -2
- package/dist/store/postgres/schema.d.ts +894 -0
- package/dist/store/postgres/schema.js +130 -2
- package/dist/store/postgres/store.js +289 -4
- package/dist/store/types.d.ts +143 -6
- package/dist/streams/control.d.ts +2 -0
- package/dist/streams/control.js +5 -0
- package/dist/streams/types.d.ts +10 -1
- package/dist/tenant/advance.js +218 -160
- package/dist/tenant/artifact-tool.d.ts +5 -0
- package/dist/tenant/artifact-tool.js +119 -0
- package/dist/tenant/commands.js +30 -5
- package/dist/tenant/context.d.ts +64 -5
- package/dist/tenant/effects.d.ts +19 -13
- package/dist/tenant/effects.js +5 -382
- package/dist/tenant/history.d.ts +13 -23
- package/dist/tenant/history.js +14 -62
- package/dist/tenant/host-token.d.ts +92 -0
- package/dist/tenant/host-token.js +179 -0
- package/dist/tenant/module.js +1 -0
- package/dist/tenant/paths.d.ts +2 -1
- package/dist/tenant/paths.js +3 -1
- package/dist/tenant/reset.d.ts +5 -2
- package/dist/tenant/reset.js +22 -2
- package/dist/tenant/run-grants.d.ts +49 -0
- package/dist/tenant/run-grants.js +62 -0
- package/dist/tenant/run-token.d.ts +70 -0
- package/dist/tenant/run-token.js +138 -0
- package/dist/tenant/runtime.d.ts +38 -0
- package/dist/tenant/runtime.js +120 -32
- package/dist/tenant/sandboxes.d.ts +119 -0
- package/dist/tenant/sandboxes.js +551 -0
- package/dist/tenant/session.d.ts +2 -13
- package/dist/tenant/session.js +4 -28
- package/dist/tenant/sessions.d.ts +5 -0
- package/dist/tenant/sessions.js +28 -1
- package/dist/tenant/status.d.ts +8 -3
- package/dist/tenant/status.js +5 -0
- package/dist/tenant/streams.d.ts +7 -0
- package/dist/tenant/streams.js +24 -1
- package/dist/tenant/sweep.js +37 -4
- package/dist/tenant/tokens.js +9 -2
- package/dist/tenant/types.d.ts +18 -1
- package/dist/tenant/worker.d.ts +11 -5
- package/dist/tenant/worker.js +12 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -10
- package/dist/adapters/media.d.ts +0 -24
- package/dist/adapters/media.js +0 -111
|
@@ -13,7 +13,15 @@
|
|
|
13
13
|
* listener (`NYLORUN_GATES_LISTEN_*`), and only one that runs loop parses
|
|
14
14
|
* where to reach the gate (`NYLORUN_GATES_URL`). Both read
|
|
15
15
|
* `NYLORUN_GATES_TOKEN`. In a container, loop requires the gate: the loop
|
|
16
|
-
* process must never hold a model credential.
|
|
16
|
+
* process must never hold a model credential. `egress` (egress-gate, F7.2) joins gates and keys
|
|
17
|
+
* in the gateway and parses its listener (`NYLORUN_EGRESS_LISTEN_*`, default `0.0.0.0:4200`).
|
|
18
|
+
*
|
|
19
|
+
* `harness` (F6.2) runs alone: it holds the harness credential and nothing else. It connects to
|
|
20
|
+
* core's Harness API listener (`NYLORUN_HARNESS_URL`, `NYLORUN_HARNESS_TOKEN`), calls models and
|
|
21
|
+
* remote MCP servers through the gates service (`NYLORUN_GATES_URL`, with run tokens only), and
|
|
22
|
+
* keeps its workspaces under `NYLORUN_HARNESS_ROOT`. It refuses to start with a database, the
|
|
23
|
+
* gates' or keys' credential, or Restate settings. A process running core starts the Harness API
|
|
24
|
+
* listener when `NYLORUN_HARNESS=remote` (`NYLORUN_HARNESS_LISTEN_*`, `NYLORUN_HARNESS_TOKEN`).
|
|
17
25
|
*
|
|
18
26
|
* Two listen modes:
|
|
19
27
|
* - **local** (no `NYLORUN_LISTEN_*` / `NYLORUN_ALLOWED_HOSTS`): the Host binds
|
|
@@ -24,21 +32,31 @@
|
|
|
24
32
|
* probes from inside the container.
|
|
25
33
|
*
|
|
26
34
|
* The Postgres, Restate and S2 endpoints are parsed and validated here;
|
|
27
|
-
* `infra/*` builds the clients from them. So is
|
|
35
|
+
* `infra/*` builds the clients from them. So is the Object store (`NYLORUN_OBJECT_STORE_*`),
|
|
36
|
+
* which every service may read; `host/main.ts` builds the `BlobStore` from it. So is who the Host's Tenant is when its database
|
|
28
37
|
* holds none yet (`NYLORUN_TENANT_ID`, `NYLORUN_TENANT_NAME`, `NYLORUN_DERIVED_PRINCIPALS`).
|
|
29
38
|
*/
|
|
30
39
|
import { DERIVED_PRINCIPAL_ID_PATTERN, isTenantId } from "@nylorun/core/compatibility";
|
|
31
|
-
|
|
40
|
+
import { blockedAddresses } from "../sandbox/pods/network-gate.js";
|
|
41
|
+
export const RUNTIME_SERVICES = ["core", "loop", "gates", "keys", "egress", "harness"];
|
|
32
42
|
/**
|
|
33
43
|
* Services that may share a process (D12): they hold the same secrets and parse the same
|
|
34
|
-
* trust class of input. keys joins gates (F4.2)
|
|
44
|
+
* trust class of input. keys joins gates (F4.2), and egress joins them (F7.2). The harness
|
|
45
|
+
* (F6.2) runs agent code's neighbours and holds only its own credential: it shares with nothing.
|
|
35
46
|
*/
|
|
36
47
|
const SERVICE_GROUPS = [
|
|
37
48
|
["core", "loop"],
|
|
38
|
-
["gates", "keys"],
|
|
49
|
+
["gates", "keys", "egress"],
|
|
50
|
+
["harness"],
|
|
39
51
|
];
|
|
40
52
|
/** Where the gates service listens by default. */
|
|
41
53
|
export const DEFAULT_GATES_LISTEN_PORT = 4100;
|
|
54
|
+
/** Where egress-gate listens by default. */
|
|
55
|
+
export const DEFAULT_EGRESS_LISTEN_PORT = 4200;
|
|
56
|
+
/** Where core's Harness API listener listens by default. */
|
|
57
|
+
export const DEFAULT_HARNESS_LISTEN_PORT = 4200;
|
|
58
|
+
/** Where a harness process answers `/health` (loopback only). */
|
|
59
|
+
export const DEFAULT_HARNESS_HEALTH_PORT = 4300;
|
|
42
60
|
/** What a process runs without `--service`: core and loop, as `--role all` did. */
|
|
43
61
|
export const DEFAULT_SERVICES = new Set([
|
|
44
62
|
"core",
|
|
@@ -46,8 +64,6 @@ export const DEFAULT_SERVICES = new Set([
|
|
|
46
64
|
]);
|
|
47
65
|
/** Services of the blueprint this release doesn't have yet. */
|
|
48
66
|
const LATER_SERVICES = [
|
|
49
|
-
"egress",
|
|
50
|
-
"harness",
|
|
51
67
|
"sandboxd",
|
|
52
68
|
];
|
|
53
69
|
const ROLE_SERVICES = {
|
|
@@ -74,7 +90,7 @@ function read(env, name) {
|
|
|
74
90
|
const trimmed = value.trim();
|
|
75
91
|
return trimmed === "" ? undefined : trimmed;
|
|
76
92
|
}
|
|
77
|
-
const USAGE = "Usage: main.js [--service core,loop|gates]";
|
|
93
|
+
const USAGE = "Usage: main.js [--service core,loop|gates,keys,egress|harness]";
|
|
78
94
|
/** Parses `--service a,b` (or the deprecated `--role`); throws `StackConfigError`. */
|
|
79
95
|
export function parseServices(argv) {
|
|
80
96
|
let service;
|
|
@@ -248,11 +264,14 @@ function parseIdentityKeys(env) {
|
|
|
248
264
|
/** Parse the stack configuration; throws `StackConfigError` naming the variable. */
|
|
249
265
|
export function parseStackConfig(env, argv) {
|
|
250
266
|
const { services, deprecatedRole } = parseServices(argv);
|
|
267
|
+
if (services.has("harness"))
|
|
268
|
+
return { services, harness: parseHarnessService(env), endpoints: {} };
|
|
251
269
|
// The image sets NYLORUN_LISTEN_*: only the API's processes read them.
|
|
252
270
|
const servesApi = services.has("core") || services.has("loop");
|
|
253
271
|
const listen = servesApi ? parseListen(env) : undefined;
|
|
254
272
|
const operator = servesApi ? parseAdminListen(env) : undefined;
|
|
255
273
|
const gates = services.has("gates") || services.has("keys") ? parseGates(env) : undefined;
|
|
274
|
+
const egress = services.has("egress") ? parseEgress(env) : undefined;
|
|
256
275
|
// The gates service's clients: the loop's model and tool calls, and core's endpoint pings.
|
|
257
276
|
const modelGate = servesApi ? parseModelGate(env) : undefined;
|
|
258
277
|
// The keys service is the gateway's unless NYLORUN_KEYS_URL names another listener.
|
|
@@ -302,12 +321,23 @@ export function parseStackConfig(env, argv) {
|
|
|
302
321
|
const rawBrowser = read(env, "NYLORUN_BROWSER_ACCESS");
|
|
303
322
|
if (rawBrowser !== undefined && rawBrowser !== "on" && rawBrowser !== "off")
|
|
304
323
|
throw new StackConfigError(`NYLORUN_BROWSER_ACCESS must be on or off, not ${rawBrowser}`);
|
|
324
|
+
const harnessMode = servesApi ? parseHarnessMode(env) : undefined;
|
|
325
|
+
const sandboxes = servesApi ? parseSandboxes(env) : undefined;
|
|
326
|
+
// Pods connect to the Harness API listener even when the Tenant runs its own harness.
|
|
327
|
+
const harnessListener = harnessMode === "remote" || sandboxes
|
|
328
|
+
? parseHarnessListener(env, harnessMode === "remote")
|
|
329
|
+
: undefined;
|
|
330
|
+
if (harnessListener &&
|
|
331
|
+
[listen?.port, operator?.port].includes(harnessListener.listen.port))
|
|
332
|
+
throw new StackConfigError("NYLORUN_HARNESS_LISTEN_PORT must differ from NYLORUN_LISTEN_PORT and NYLORUN_ADMIN_LISTEN_PORT");
|
|
305
333
|
const delivery = parseDelivery(env);
|
|
306
334
|
const tenant = servesApi ? parseTenant(env) : undefined;
|
|
335
|
+
const objectStore = parseObjectStore(env);
|
|
307
336
|
return {
|
|
308
337
|
services,
|
|
309
338
|
...(deprecatedRole ? { deprecatedRole } : {}),
|
|
310
339
|
...(gates ? { gates } : {}),
|
|
340
|
+
...(egress ? { egress } : {}),
|
|
311
341
|
...(modelGate ? { modelGate } : {}),
|
|
312
342
|
...(keys ? { keys } : {}),
|
|
313
343
|
...(rawPacking ? { packing: rawPacking } : {}),
|
|
@@ -315,11 +345,37 @@ export function parseStackConfig(env, argv) {
|
|
|
315
345
|
endpoints,
|
|
316
346
|
...(publicUrl ? { publicUrl } : {}),
|
|
317
347
|
...(rawBrowser === undefined ? {} : { browserAccess: rawBrowser === "on" }),
|
|
348
|
+
...(harnessMode ? { harnessMode } : {}),
|
|
349
|
+
...(harnessListener ? { harnessListener } : {}),
|
|
350
|
+
...(sandboxes ? { sandboxes } : {}),
|
|
318
351
|
...(operator ? { operator } : {}),
|
|
319
352
|
...(delivery ? { delivery } : {}),
|
|
320
353
|
...(tenant ? { tenant } : {}),
|
|
354
|
+
...(objectStore ? { objectStore } : {}),
|
|
321
355
|
};
|
|
322
356
|
}
|
|
357
|
+
/** S3 bucket names: 3 to 63 lowercase letters, digits, dots and hyphens. */
|
|
358
|
+
const BUCKET = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
|
|
359
|
+
/** `StackConfig.objectStore` from `NYLORUN_OBJECT_STORE_*`, or `undefined` without an endpoint. */
|
|
360
|
+
function parseObjectStore(env) {
|
|
361
|
+
const endpoint = parseUrl(env, "NYLORUN_OBJECT_STORE_ENDPOINT", ["http:", "https:"]);
|
|
362
|
+
const accessKeyId = read(env, "NYLORUN_OBJECT_STORE_ACCESS_KEY");
|
|
363
|
+
const secretAccessKey = read(env, "NYLORUN_OBJECT_STORE_SECRET_KEY");
|
|
364
|
+
if (endpoint === undefined) {
|
|
365
|
+
if (accessKeyId !== undefined || secretAccessKey !== undefined)
|
|
366
|
+
throw new StackConfigError("NYLORUN_OBJECT_STORE_ACCESS_KEY or _SECRET_KEY is set without NYLORUN_OBJECT_STORE_ENDPOINT: set the S3 endpoint, e.g. http://rustfs:9000");
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
if (accessKeyId === undefined || secretAccessKey === undefined)
|
|
370
|
+
throw new StackConfigError("NYLORUN_OBJECT_STORE_ACCESS_KEY and NYLORUN_OBJECT_STORE_SECRET_KEY are required with NYLORUN_OBJECT_STORE_ENDPOINT (`nylorun start` sets them)");
|
|
371
|
+
const bucket = read(env, "NYLORUN_OBJECT_STORE_BUCKET") ?? "nylorun";
|
|
372
|
+
if (!BUCKET.test(bucket))
|
|
373
|
+
throw new StackConfigError(`NYLORUN_OBJECT_STORE_BUCKET must be an S3 bucket name (3 to 63 of a-z 0-9 . -), not ${bucket}`);
|
|
374
|
+
const region = read(env, "NYLORUN_OBJECT_STORE_REGION") ?? "us-east-1";
|
|
375
|
+
if (!/^[a-z0-9-]+$/.test(region))
|
|
376
|
+
throw new StackConfigError(`NYLORUN_OBJECT_STORE_REGION is not a region: ${region}`);
|
|
377
|
+
return { endpoint: endpoint.replace(/\/+$/, ""), bucket, region, accessKeyId, secretAccessKey };
|
|
378
|
+
}
|
|
323
379
|
function parseTenant(env) {
|
|
324
380
|
const id = read(env, "NYLORUN_TENANT_ID");
|
|
325
381
|
if (id !== undefined && !isTenantId(id))
|
|
@@ -336,6 +392,132 @@ function parseTenant(env) {
|
|
|
336
392
|
};
|
|
337
393
|
}
|
|
338
394
|
const GATES_TOKEN = /^[0-9a-f]{64,}$/i;
|
|
395
|
+
/** `NYLORUN_HARNESS`: `in-process` (the default when unset) or `remote`. */
|
|
396
|
+
function parseHarnessMode(env) {
|
|
397
|
+
const raw = read(env, "NYLORUN_HARNESS");
|
|
398
|
+
if (raw === undefined)
|
|
399
|
+
return undefined;
|
|
400
|
+
if (raw !== "in-process" && raw !== "remote")
|
|
401
|
+
throw new StackConfigError(`NYLORUN_HARNESS must be in-process or remote, not ${raw}`);
|
|
402
|
+
return raw;
|
|
403
|
+
}
|
|
404
|
+
function parseHarnessToken(env, why) {
|
|
405
|
+
const token = read(env, "NYLORUN_HARNESS_TOKEN");
|
|
406
|
+
if (token === undefined)
|
|
407
|
+
throw new StackConfigError(`NYLORUN_HARNESS_TOKEN is required ${why} (\`nylorun start\` sets it)`);
|
|
408
|
+
if (!GATES_TOKEN.test(token))
|
|
409
|
+
throw new StackConfigError("NYLORUN_HARNESS_TOKEN must be at least 32 bytes as hex");
|
|
410
|
+
return token;
|
|
411
|
+
}
|
|
412
|
+
/** `StackConfig.sandboxes` from `NYLORUN_SANDBOXES_*`, or undefined without a URL. */
|
|
413
|
+
function parseSandboxes(env) {
|
|
414
|
+
const url = parseUrl(env, "NYLORUN_SANDBOXES_URL", ["http:", "https:"]);
|
|
415
|
+
if (url === undefined)
|
|
416
|
+
return undefined;
|
|
417
|
+
const token = read(env, "NYLORUN_SANDBOXES_TOKEN");
|
|
418
|
+
if (token === undefined || token.length < 32)
|
|
419
|
+
throw new StackConfigError("NYLORUN_SANDBOXES_TOKEN (at least 32 characters) is required with NYLORUN_SANDBOXES_URL (`nylorun sandbox enable` sets both)");
|
|
420
|
+
const harnessImage = read(env, "NYLORUN_SANDBOX_HARNESS_IMAGE");
|
|
421
|
+
if (harnessImage === undefined || /\s/.test(harnessImage))
|
|
422
|
+
throw new StackConfigError("NYLORUN_SANDBOX_HARNESS_IMAGE is required with NYLORUN_SANDBOXES_URL: the Runtime image sandbox pods copy the engine from (`nylorun start` sets it)");
|
|
423
|
+
return { url: url.replace(/\/+$/, ""), token, harnessImage };
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* `StackConfig.harnessListener` from `NYLORUN_HARNESS_LISTEN_*` and `NYLORUN_HARNESS_TOKEN`
|
|
427
|
+
* (required with `NYLORUN_HARNESS=remote`; optional when only sandbox pods connect).
|
|
428
|
+
*/
|
|
429
|
+
function parseHarnessListener(env, tokenRequired = true) {
|
|
430
|
+
const host = read(env, "NYLORUN_HARNESS_LISTEN_HOST") ?? DEFAULT_CONTAINER_LISTEN_HOST;
|
|
431
|
+
if (/\s|\//.test(host))
|
|
432
|
+
throw new StackConfigError(`NYLORUN_HARNESS_LISTEN_HOST is not an address: ${host}`);
|
|
433
|
+
const rawPort = read(env, "NYLORUN_HARNESS_LISTEN_PORT");
|
|
434
|
+
const port = rawPort === undefined
|
|
435
|
+
? DEFAULT_HARNESS_LISTEN_PORT
|
|
436
|
+
: parsePort("NYLORUN_HARNESS_LISTEN_PORT", rawPort);
|
|
437
|
+
const rawAllowed = read(env, "NYLORUN_HARNESS_ALLOWED_HOSTS");
|
|
438
|
+
const explicit = rawAllowed === undefined
|
|
439
|
+
? []
|
|
440
|
+
: rawAllowed
|
|
441
|
+
.split(",")
|
|
442
|
+
.map((entry) => entry.trim())
|
|
443
|
+
.filter((entry) => entry !== "")
|
|
444
|
+
.map((entry) => normalizeAllowedHost("NYLORUN_HARNESS_ALLOWED_HOSTS", entry));
|
|
445
|
+
if (explicit.length === 0 && !isLoopbackAddress(host))
|
|
446
|
+
throw new StackConfigError(`NYLORUN_HARNESS_ALLOWED_HOSTS is required when NYLORUN_HARNESS_LISTEN_HOST is ${host}: list the Host headers harnesses send, e.g. runtime:${port}`);
|
|
447
|
+
const token = tokenRequired || read(env, "NYLORUN_HARNESS_TOKEN") !== undefined
|
|
448
|
+
? parseHarnessToken(env, "with NYLORUN_HARNESS=remote: the credential harnesses present")
|
|
449
|
+
: undefined;
|
|
450
|
+
return {
|
|
451
|
+
listen: { host, port, allowedHosts: [...new Set([...explicit, ...loopbackForms(port)])] },
|
|
452
|
+
...(token ? { token } : {}),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
/** What a harness process must not hold: it runs next to agent code. */
|
|
456
|
+
const HARNESS_REFUSED = ["NYLORUN_DATABASE_URL", "NYLORUN_GATES_TOKEN", "NYLORUN_KEYS_URL"];
|
|
457
|
+
/** `StackConfig.harness` from `NYLORUN_HARNESS_*` and `NYLORUN_GATES_URL`. */
|
|
458
|
+
function parseHarnessService(env) {
|
|
459
|
+
const held = [
|
|
460
|
+
...HARNESS_REFUSED.filter((name) => read(env, name) !== undefined),
|
|
461
|
+
...Object.keys(env)
|
|
462
|
+
.filter((name) => name.startsWith("NYLORUN_RESTATE_") && read(env, name) !== undefined)
|
|
463
|
+
.sort(),
|
|
464
|
+
];
|
|
465
|
+
if (held.length > 0)
|
|
466
|
+
throw new StackConfigError(`--service harness refuses to start with ${held.join(", ")} set: a harness reaches no database, no gate or keys credential and no Restate`);
|
|
467
|
+
const pod = read(env, "NYLORUN_SANDBOX_KIND") === "pod";
|
|
468
|
+
const url = parseUrl(env, "NYLORUN_HARNESS_URL", pod ? ["http:", "https:", "ws:", "wss:"] : ["ws:", "wss:"]);
|
|
469
|
+
if (url === undefined)
|
|
470
|
+
throw new StackConfigError("NYLORUN_HARNESS_URL is required for --service harness: core's Harness API, e.g. ws://runtime:4200/nylorun/harness/v1");
|
|
471
|
+
const gatesUrl = parseUrl(env, "NYLORUN_GATES_URL", ["http:", "https:"]);
|
|
472
|
+
if (gatesUrl === undefined)
|
|
473
|
+
throw new StackConfigError("NYLORUN_GATES_URL is required for --service harness: its model and MCP calls go through the gates service, e.g. http://gateway:4100");
|
|
474
|
+
const root = read(env, "NYLORUN_HARNESS_ROOT") ?? "/harness";
|
|
475
|
+
if (!root.startsWith("/"))
|
|
476
|
+
throw new StackConfigError(`NYLORUN_HARNESS_ROOT must be an absolute path, not ${root}`);
|
|
477
|
+
const rawHealth = read(env, "NYLORUN_HARNESS_HEALTH_PORT");
|
|
478
|
+
const podHost = pod ? parsePodHost(env, url) : undefined;
|
|
479
|
+
return {
|
|
480
|
+
url: podHost ? podHost.wsUrl : url,
|
|
481
|
+
...(podHost
|
|
482
|
+
? { pod: podHost.config }
|
|
483
|
+
: { token: parseHarnessToken(env, "for --service harness: the credential core's Harness API accepts") }),
|
|
484
|
+
gatesUrl: gatesUrl.replace(/\/+$/, ""),
|
|
485
|
+
root,
|
|
486
|
+
healthPort: rawHealth === undefined
|
|
487
|
+
? DEFAULT_HARNESS_HEALTH_PORT
|
|
488
|
+
: parsePort("NYLORUN_HARNESS_HEALTH_PORT", rawHealth),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The engine in a pod sandbox: the pod's identity and its join file, from the variables the
|
|
493
|
+
* sandboxes service renders into the pod. `NYLORUN_HARNESS_URL` is the listener's address on the
|
|
494
|
+
* Docker host (`http://<host>:<port>`); the WebSocket URL adds the Harness API path.
|
|
495
|
+
*/
|
|
496
|
+
function parsePodHost(env, url) {
|
|
497
|
+
const need = (name) => {
|
|
498
|
+
const value = read(env, name);
|
|
499
|
+
if (value === undefined)
|
|
500
|
+
throw new StackConfigError(`${name} is required for the engine in a sandbox pod (NYLORUN_SANDBOX_KIND=pod)`);
|
|
501
|
+
return value;
|
|
502
|
+
};
|
|
503
|
+
if (read(env, "NYLORUN_HARNESS_TOKEN") !== undefined)
|
|
504
|
+
throw new StackConfigError("A sandbox pod's engine holds no NYLORUN_HARNESS_TOKEN: it joins with its join token");
|
|
505
|
+
const base = new URL(url);
|
|
506
|
+
const secure = base.protocol === "https:" || base.protocol === "wss:";
|
|
507
|
+
const origin = `${secure ? "https" : "http"}://${base.host}`;
|
|
508
|
+
const egress = read(env, "NYLORUN_EGRESS_PROXY");
|
|
509
|
+
return {
|
|
510
|
+
wsUrl: `${secure ? "wss" : "ws"}://${base.host}/nylorun/harness/v1`,
|
|
511
|
+
config: {
|
|
512
|
+
sandboxId: need("NYLORUN_SANDBOX_ID"),
|
|
513
|
+
podUid: need("NYLORUN_POD_UID"),
|
|
514
|
+
joinFile: need("NYLORUN_SANDBOX_JOIN_FILE"),
|
|
515
|
+
httpUrl: origin,
|
|
516
|
+
...(egress ? { egressProxy: egress.replace(/\/+$/, "") } : {}),
|
|
517
|
+
blocked: blockedAddresses(env),
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
}
|
|
339
521
|
/** `StackConfig.gates` from `NYLORUN_GATES_*`. */
|
|
340
522
|
function parseGates(env) {
|
|
341
523
|
const host = read(env, "NYLORUN_GATES_LISTEN_HOST") ?? DEFAULT_CONTAINER_LISTEN_HOST;
|
|
@@ -365,6 +547,15 @@ function parseGates(env) {
|
|
|
365
547
|
token,
|
|
366
548
|
};
|
|
367
549
|
}
|
|
550
|
+
/** `StackConfig.egress` from `NYLORUN_EGRESS_LISTEN_*`. CONNECT has no Host header to check. */
|
|
551
|
+
function parseEgress(env) {
|
|
552
|
+
const host = read(env, "NYLORUN_EGRESS_LISTEN_HOST") ?? DEFAULT_CONTAINER_LISTEN_HOST;
|
|
553
|
+
if (/\s|\//.test(host))
|
|
554
|
+
throw new StackConfigError(`NYLORUN_EGRESS_LISTEN_HOST is not an address: ${host}`);
|
|
555
|
+
const rawPort = read(env, "NYLORUN_EGRESS_LISTEN_PORT");
|
|
556
|
+
const port = rawPort === undefined ? DEFAULT_EGRESS_LISTEN_PORT : parsePort("NYLORUN_EGRESS_LISTEN_PORT", rawPort);
|
|
557
|
+
return { listen: { host, port } };
|
|
558
|
+
}
|
|
368
559
|
/** `StackConfig.modelGate` from `NYLORUN_GATES_URL` and `NYLORUN_GATES_TOKEN`. */
|
|
369
560
|
function parseModelGate(env) {
|
|
370
561
|
const url = parseUrl(env, "NYLORUN_GATES_URL", ["http:", "https:"]);
|
package/dist/mcp/snapshot.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JsonObject } from "@nylorun/core/define";
|
|
2
|
-
import type
|
|
2
|
+
import { type AgentManifest } from "@nylorun/core/define";
|
|
3
|
+
import type { DurableSessionTool, HostEffect } from "@nylorun/harness/run";
|
|
3
4
|
export interface McpToolRecord {
|
|
4
5
|
/** The agent used as a tool that declares the server; absent for the session's root agent. */
|
|
5
6
|
readonly agentId?: string;
|
|
@@ -27,3 +28,9 @@ export interface McpDiagnostic {
|
|
|
27
28
|
}
|
|
28
29
|
export declare function modelToolName(serverName: string, serverToolName: string): string;
|
|
29
30
|
export declare function declaredToolNames(manifest: AgentManifest): Set<string>;
|
|
31
|
+
/** The tools of a snapshot, as the engine advertises them. */
|
|
32
|
+
export declare function sessionToolsOf(snapshot: McpSnapshot | undefined): readonly DurableSessionTool[] | undefined;
|
|
33
|
+
/** The snapshot's record of an MCP tool call, or undefined when the call is not an MCP tool. */
|
|
34
|
+
export declare function mcpToolOf(snapshot: McpSnapshot | undefined, request: Pick<HostEffect, "agent" | "capabilityId" | "toolName">): McpToolRecord | undefined;
|
|
35
|
+
/** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
|
|
36
|
+
export declare function manifestFor(manifest: AgentManifest, agent: HostEffect["agent"]): AgentManifest | undefined;
|
package/dist/mcp/snapshot.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { delegateManifest } from "@nylorun/core/define";
|
|
1
2
|
export function modelToolName(serverName, serverToolName) {
|
|
2
3
|
return `${serverName}__${serverToolName}`;
|
|
3
4
|
}
|
|
@@ -8,3 +9,30 @@ export function declaredToolNames(manifest) {
|
|
|
8
9
|
names.add(tool.name);
|
|
9
10
|
return names;
|
|
10
11
|
}
|
|
12
|
+
/** The tools of a snapshot, as the engine advertises them. */
|
|
13
|
+
export function sessionToolsOf(snapshot) {
|
|
14
|
+
if (!snapshot?.mcpTools.length)
|
|
15
|
+
return undefined;
|
|
16
|
+
return snapshot.mcpTools.map((tool) => ({
|
|
17
|
+
...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
|
|
18
|
+
capabilityId: tool.capabilityId,
|
|
19
|
+
name: tool.name,
|
|
20
|
+
...(tool.description === undefined
|
|
21
|
+
? {}
|
|
22
|
+
: { description: tool.description }),
|
|
23
|
+
inputSchema: tool.inputSchema,
|
|
24
|
+
...(tool.outputSchema === undefined
|
|
25
|
+
? {}
|
|
26
|
+
: { outputSchema: tool.outputSchema }),
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
/** The snapshot's record of an MCP tool call, or undefined when the call is not an MCP tool. */
|
|
30
|
+
export function mcpToolOf(snapshot, request) {
|
|
31
|
+
return snapshot?.mcpTools.find((tool) => tool.agentId === request.agent?.id &&
|
|
32
|
+
tool.capabilityId === request.capabilityId &&
|
|
33
|
+
tool.name === request.toolName);
|
|
34
|
+
}
|
|
35
|
+
/** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
|
|
36
|
+
export function manifestFor(manifest, agent) {
|
|
37
|
+
return agent ? delegateManifest(manifest, agent.id) : manifest;
|
|
38
|
+
}
|
package/dist/media.d.ts
CHANGED
|
@@ -1,29 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image checks for code that handles image bytes itself (an image tool in an Action endpoint):
|
|
3
|
+
* the supported types, the size cap and a signature check. Storing images is the job of file
|
|
4
|
+
* artifacts (`artifacts/`, protocol 6), which replaced `MediaStore`.
|
|
5
|
+
*/
|
|
1
6
|
export declare const IMAGE_MEDIA_TYPES: readonly string[];
|
|
2
7
|
export declare const MAX_IMAGE_BYTES: number;
|
|
3
|
-
export interface MediaAsset {
|
|
4
|
-
readonly id: string;
|
|
5
|
-
readonly mediaType: string;
|
|
6
|
-
readonly bytes: number;
|
|
7
|
-
readonly kind: "input" | "generated";
|
|
8
|
-
}
|
|
9
|
-
/** Opaque reference retained by Harness and resolved by the configured media adapter. */
|
|
10
|
-
export interface MediaReference {
|
|
11
|
-
readonly agentId: string;
|
|
12
|
-
readonly assetId: string;
|
|
13
|
-
}
|
|
14
8
|
export declare function decodeImageBase64(mediaType: string, base64: string): Uint8Array;
|
|
15
9
|
/** Verify the asserted media type and lightweight file signature for a supported image. */
|
|
16
10
|
export declare function validateImageBytes(mediaType: string, bytes: Uint8Array): void;
|
|
17
|
-
export interface RuntimeMedia {
|
|
18
|
-
saveInput(agentId: string, sessionId: string, mediaType: string, base64: string): Promise<MediaAsset>;
|
|
19
|
-
saveGenerated(agentId: string, sessionId: string, mediaType: string, bytes: Uint8Array): Promise<MediaAsset>;
|
|
20
|
-
dataUrl(reference: MediaReference, sessionId: string): Promise<{
|
|
21
|
-
readonly asset: MediaAsset;
|
|
22
|
-
readonly url: string;
|
|
23
|
-
} | undefined>;
|
|
24
|
-
latestInput(agentId: string, sessionId: string): Promise<MediaAsset | undefined>;
|
|
25
|
-
read(agentId: string, sessionId: string, assetId: string): Promise<{
|
|
26
|
-
asset: MediaAsset;
|
|
27
|
-
bytes: Uint8Array;
|
|
28
|
-
} | undefined>;
|
|
29
|
-
}
|
package/dist/media.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image checks for code that handles image bytes itself (an image tool in an Action endpoint):
|
|
3
|
+
* the supported types, the size cap and a signature check. Storing images is the job of file
|
|
4
|
+
* artifacts (`artifacts/`, protocol 6), which replaced `MediaStore`.
|
|
5
|
+
*/
|
|
1
6
|
export const IMAGE_MEDIA_TYPES = Object.freeze([
|
|
2
7
|
"image/jpeg",
|
|
3
8
|
"image/png",
|
package/dist/model/pi-model.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Credential } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { RuntimeModelAdapter } from "../contracts.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type FileResolver } from "../artifacts/files.js";
|
|
4
4
|
import type { HostModelSecret } from "../vault/service.js";
|
|
5
5
|
export interface ModelPreview {
|
|
6
6
|
readonly invocationId: string;
|
|
@@ -20,7 +20,11 @@ export interface ModelCallSettings {
|
|
|
20
20
|
export interface PiModelOptions {
|
|
21
21
|
readonly root?: string;
|
|
22
22
|
readonly onPreview?: (preview: ModelPreview) => void;
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the files a prompt names (media parts whose reference is an artifact version) to
|
|
25
|
+
* their bytes. Model-gate passes one over the Tenant's artifacts (`artifactFiles`).
|
|
26
|
+
*/
|
|
27
|
+
readonly files?: FileResolver;
|
|
24
28
|
readonly readHostModel?: () => HostModelSecret | undefined | Promise<HostModelSecret | undefined>;
|
|
25
29
|
readonly writeHostCredential?: (credential: Credential) => void | Promise<void>;
|
|
26
30
|
readonly settings?: ModelCallSettings;
|
package/dist/model/pi-model.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { parseJsonWithRepair, retryAssistantCall, } from "@earendil-works/pi-ai";
|
|
2
|
+
import { FileUnavailableError, } from "../artifacts/files.js";
|
|
3
|
+
import { essence, isModelImage, isText } from "../artifacts/media-types.js";
|
|
2
4
|
import { scrub } from "../redact.js";
|
|
3
5
|
import { classifyAssistantError, failure } from "./classify.js";
|
|
4
6
|
import { modelsFor } from "./models.js";
|
|
@@ -227,28 +229,43 @@ async function buildContext(call, selected, options) {
|
|
|
227
229
|
if (part.type === "text")
|
|
228
230
|
result.push({ type: "text", text: part.text });
|
|
229
231
|
else if (part.type === "media") {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
232
|
+
// A file the message named by artifact (protocol 6): its bytes go only into the
|
|
233
|
+
// provider request built here, never into the transcript or the journal.
|
|
234
|
+
if (!options.files)
|
|
235
|
+
throw new InvalidRequest("Files are unavailable; pass a file resolver to piModel({ files }).");
|
|
236
|
+
let file;
|
|
237
|
+
try {
|
|
238
|
+
file = await options.files(part.reference);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (error instanceof FileUnavailableError)
|
|
242
|
+
throw new InvalidRequest(error.message);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
if (isModelImage(file.mediaType)) {
|
|
246
|
+
if (!selected.input.includes("image"))
|
|
247
|
+
throw new InvalidRequest("The configured model does not accept images.");
|
|
248
|
+
result.push({
|
|
249
|
+
type: "image",
|
|
250
|
+
data: Buffer.from(file.bytes).toString("base64"),
|
|
251
|
+
mimeType: essence(file.mediaType),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else if (isText(file.mediaType)) {
|
|
255
|
+
let text;
|
|
256
|
+
try {
|
|
257
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(file.bytes);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
throw new InvalidRequest(`${file.name} is not UTF-8 text.`);
|
|
261
|
+
}
|
|
262
|
+
result.push({
|
|
263
|
+
type: "text",
|
|
264
|
+
text: `<file name=${JSON.stringify(file.name)} type=${JSON.stringify(essence(file.mediaType))}>\n${text}\n</file>`,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
else
|
|
268
|
+
throw new InvalidRequest(`The model reads images and text files; ${file.name} is ${essence(file.mediaType)}.`);
|
|
252
269
|
}
|
|
253
270
|
}
|
|
254
271
|
return result;
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { localMedia, MediaStore } from "../adapters/media.js";
|
|
2
1
|
export { jsonlObserver } from "../adapters/observe.js";
|
|
3
2
|
export { piModel, type PiModelOptions } from "../model/pi-model.js";
|
|
3
|
+
export { FileUnavailableError, type FileResolver, type ResolvedFile, } from "../artifacts/files.js";
|
|
4
4
|
export { projectAsset } from "../assets.js";
|
|
5
5
|
export { probeSandboxBackends, type SandboxSelectionReport, } from "../sandbox/select.js";
|
package/dist/node/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { localMedia, MediaStore } from "../adapters/media.js";
|
|
2
1
|
export { jsonlObserver } from "../adapters/observe.js";
|
|
3
2
|
export { piModel } from "../model/pi-model.js";
|
|
3
|
+
export { FileUnavailableError, } from "../artifacts/files.js";
|
|
4
4
|
export { projectAsset } from "../assets.js";
|
|
5
5
|
export { probeSandboxBackends, } from "../sandbox/select.js";
|