@nylorun/runtime 0.25.0-beta → 0.27.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 +169 -0
- package/README.md +26 -16
- package/dist/adapters/execution/restate.d.ts +15 -18
- package/dist/adapters/execution/restate.js +31 -27
- package/dist/adapters/replication/pgoutput.d.ts +1 -1
- package/dist/adapters/replication/pgoutput.js +3 -2
- package/dist/adapters/sandbox/local.js +8 -5
- package/dist/adapters/sandbox/virtual.js +2 -2
- package/dist/adapters/streams/s2.d.ts +1 -1
- package/dist/adapters/streams/s2.js +18 -14
- package/dist/api/a2a/card.js +1 -1
- package/dist/api/a2a/endpoint.js +3 -4
- package/dist/api/a2a/protocol.js +4 -0
- package/dist/api/a2a/routes.d.ts +2 -6
- package/dist/api/a2a/routes.js +12 -18
- package/dist/api/ag-ui/endpoint.js +2 -3
- package/dist/api/ag-ui/routes.js +4 -4
- package/dist/api/components.d.ts +326 -45
- package/dist/api/components.js +5 -1
- package/dist/api/gate/keys.js +7 -4
- package/dist/api/gate/routes.d.ts +6 -2
- package/dist/api/gate/routes.js +15 -11
- package/dist/api/http/app.d.ts +2 -2
- package/dist/api/http/app.js +2 -2
- package/dist/api/http/body.d.ts +15 -3
- package/dist/api/http/body.js +15 -7
- package/dist/api/http/define.d.ts +1 -1
- package/dist/api/http/respond.js +7 -3
- package/dist/api/http/routes/access.js +5 -4
- package/dist/api/http/routes/artifacts.js +19 -7
- package/dist/api/http/routes/coverage.js +1 -1
- package/dist/api/http/routes/environment-manifests.js +1 -1
- package/dist/api/http/routes/environments.js +6 -6
- package/dist/api/http/routes/files.d.ts +2 -1
- package/dist/api/http/routes/files.js +31 -5
- package/dist/api/http/routes/mcp.js +1 -1
- package/dist/api/http/routes/reads.d.ts +35 -0
- package/dist/api/http/routes/reads.js +50 -22
- package/dist/api/http/routes/sessions.js +12 -11
- package/dist/api/http/routes/tenant.d.ts +2 -2
- package/dist/api/http/routes/tenant.js +69 -16
- package/dist/api/inbound-files.d.ts +3 -9
- package/dist/api/inbound-files.js +18 -24
- package/dist/api/openapi.js +6 -2
- package/dist/artifacts/disk.d.ts +0 -4
- package/dist/artifacts/disk.js +77 -73
- package/dist/artifacts/files.d.ts +13 -4
- package/dist/artifacts/files.js +27 -9
- package/dist/artifacts/folder-upload.d.ts +75 -0
- package/dist/artifacts/folder-upload.js +263 -0
- package/dist/artifacts/folders.d.ts +14 -8
- package/dist/artifacts/folders.js +19 -11
- package/dist/artifacts/links.js +7 -6
- package/dist/artifacts/parts.d.ts +9 -14
- package/dist/artifacts/parts.js +11 -9
- package/dist/artifacts/pipe.d.ts +15 -0
- package/dist/artifacts/pipe.js +71 -0
- package/dist/artifacts/quota.d.ts +36 -0
- package/dist/artifacts/quota.js +44 -0
- package/dist/artifacts/service.d.ts +25 -1
- package/dist/artifacts/service.js +70 -25
- package/dist/blob/fs.js +2 -1
- package/dist/blob/s3.d.ts +6 -2
- package/dist/blob/s3.js +9 -7
- package/dist/blob/types.d.ts +1 -1
- package/dist/blob/types.js +1 -1
- package/dist/core/limits.d.ts +11 -35
- package/dist/core/limits.js +5 -73
- package/dist/core/sandbox-routes.js +0 -1
- package/dist/core/turn-manifest.d.ts +1 -2
- package/dist/core/turn-manifest.js +4 -28
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +8 -6
- package/dist/execution/types.d.ts +1 -6
- package/dist/execution/types.js +0 -5
- package/dist/gates/contract.d.ts +3 -2
- package/dist/gates/contract.js +1 -1
- package/dist/gates/egress-credentials.js +4 -3
- package/dist/gates/egress.d.ts +9 -9
- package/dist/gates/egress.js +19 -19
- package/dist/gates/fixture-model.js +13 -2
- package/dist/gates/handler.d.ts +1 -3
- package/dist/gates/handler.js +4 -3
- package/dist/gates/http-client.d.ts +4 -3
- package/dist/gates/http-client.js +5 -7
- package/dist/gates/http-tool.d.ts +24 -4
- package/dist/gates/http-tool.js +53 -17
- package/dist/gates/in-process.d.ts +2 -1
- package/dist/gates/in-process.js +17 -9
- package/dist/gates/inflight.d.ts +2 -2
- package/dist/gates/inflight.js +3 -2
- package/dist/gates/journal.js +5 -7
- package/dist/gates/mcp-handler.d.ts +4 -3
- package/dist/gates/mcp-handler.js +15 -5
- package/dist/gates/model-gate.d.ts +11 -2
- package/dist/gates/policy.d.ts +28 -5
- package/dist/gates/policy.js +60 -17
- package/dist/gates/provider-gate.d.ts +22 -0
- package/dist/gates/provider-gate.js +30 -0
- package/dist/gates/tool-calls.d.ts +1 -3
- package/dist/gates/tool-calls.js +2 -3
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +5 -6
- package/dist/gates/tool-contract.d.ts +8 -5
- package/dist/gates/tool-contract.js +1 -1
- package/dist/gates/tool-gate.d.ts +18 -7
- package/dist/gates/tool-gate.js +49 -7
- package/dist/harness/calls.d.ts +27 -25
- package/dist/harness/calls.js +40 -46
- package/dist/harness/executors.d.ts +10 -3
- package/dist/harness/executors.js +53 -23
- package/dist/harness/main.js +2 -2
- package/dist/harness/pod.d.ts +0 -2
- package/dist/harness/pod.js +7 -6
- package/dist/harness/run-tokens.d.ts +4 -1
- package/dist/harness/run-tokens.js +6 -2
- package/dist/harness/service.d.ts +17 -14
- package/dist/harness/service.js +21 -35
- package/dist/harness/setup.d.ts +0 -2
- package/dist/harness/setup.js +4 -9
- package/dist/harness/ws-client.d.ts +2 -2
- package/dist/harness/ws-client.js +7 -5
- package/dist/harness/ws-port.d.ts +3 -4
- package/dist/harness/ws-port.js +3 -4
- package/dist/harness-api/artifacts.d.ts +20 -0
- package/dist/harness-api/artifacts.js +478 -0
- package/dist/harness-api/in-process.d.ts +2 -2
- package/dist/harness-api/in-process.js +18 -16
- package/dist/harness-api/record.d.ts +10 -9
- package/dist/harness-api/record.js +102 -157
- package/dist/harness-api/renew.d.ts +1 -1
- package/dist/harness-api/renew.js +2 -2
- package/dist/harness-api/server.d.ts +1 -16
- package/dist/harness-api/server.js +82 -73
- package/dist/harness-api/start.d.ts +20 -2
- package/dist/harness-api/start.js +57 -18
- package/dist/harness-api/tool-journal.d.ts +15 -10
- package/dist/harness-api/tool-journal.js +26 -24
- package/dist/harness-api/workspace.d.ts +0 -4
- package/dist/harness-api/workspace.js +2 -45
- package/dist/harness-api/ws-server.js +10 -10
- package/dist/host/create-host.js +2 -3
- package/dist/host/execution.js +2 -3
- package/dist/host/gates.d.ts +2 -9
- package/dist/host/gates.js +15 -12
- package/dist/host/http.d.ts +0 -2
- package/dist/host/http.js +0 -27
- package/dist/host/main.js +10 -3
- package/dist/host/operate.js +12 -3
- package/dist/host/stack-config.d.ts +11 -0
- package/dist/host/stack-config.js +21 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/index.js +2 -1
- package/dist/infra/readiness.d.ts +0 -2
- package/dist/infra/readiness.js +4 -3
- package/dist/keys/client.d.ts +1 -0
- package/dist/keys/client.js +2 -3
- package/dist/keys/contract.d.ts +1 -3
- package/dist/keys/contract.js +1 -3
- package/dist/keys/egress-ca.d.ts +0 -2
- package/dist/keys/egress-ca.js +2 -3
- package/dist/keys/keys.js +6 -1
- package/dist/keys/x509.d.ts +8 -4
- package/dist/keys/x509.js +14 -5
- package/dist/management-openapi.json +1961 -994
- package/dist/mcp/connect.d.ts +35 -26
- package/dist/mcp/connect.js +112 -141
- package/dist/mcp/pool.d.ts +27 -22
- package/dist/mcp/pool.js +34 -48
- package/dist/mcp/preview.d.ts +13 -6
- package/dist/mcp/preview.js +37 -20
- package/dist/mcp/snapshot.d.ts +35 -86
- package/dist/mcp/snapshot.js +93 -135
- package/dist/model/auth-store.js +5 -3
- package/dist/model/capabilities.d.ts +1 -1
- package/dist/model/catalog.d.ts +9 -36
- package/dist/model/catalog.js +6 -33
- package/dist/model/descriptor.d.ts +51 -0
- package/dist/model/descriptor.js +83 -0
- package/dist/model/models.d.ts +0 -3
- package/dist/model/models.js +3 -5
- package/dist/model/native-blocks.js +1 -1
- package/dist/model/pi-model.d.ts +16 -5
- package/dist/model/pi-model.js +86 -105
- package/dist/openapi.json +866 -1218
- package/dist/reads/cursor.js +3 -1
- package/dist/record/append.d.ts +34 -2
- package/dist/record/append.js +50 -1
- package/dist/record/environment.d.ts +2 -0
- package/dist/record/environment.js +2 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/redact.d.ts +1 -1
- package/dist/redact.js +3 -2
- package/dist/sandbox/egress-token.d.ts +3 -3
- package/dist/sandbox/egress-token.js +3 -3
- package/dist/sandbox/join.js +3 -2
- package/dist/sandbox/manager.d.ts +41 -41
- package/dist/sandbox/manager.js +112 -44
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +5 -8
- package/dist/sandbox/pods/client.d.ts +1 -10
- package/dist/sandbox/pods/client.js +8 -4
- package/dist/sandbox/pods/lifecycle.d.ts +19 -11
- package/dist/sandbox/pods/lifecycle.js +45 -23
- package/dist/sandbox/pods/network-gate.d.ts +4 -5
- package/dist/sandbox/pods/network-gate.js +7 -7
- package/dist/sandbox/pods/reconcile.js +4 -1
- package/dist/sandbox/pods/spec.js +8 -5
- package/dist/sandbox/policy.d.ts +7 -1
- package/dist/sandbox/policy.js +10 -3
- package/dist/sandbox/resolve.d.ts +8 -3
- package/dist/sandbox/resolve.js +12 -2
- package/dist/sandbox/setup.d.ts +3 -1
- package/dist/sandbox/setup.js +34 -11
- package/dist/sandbox/share.d.ts +5 -14
- package/dist/sandbox/share.js +5 -34
- package/dist/sandbox/skills.d.ts +7 -13
- package/dist/sandbox/skills.js +16 -31
- package/dist/sandbox/tenant-config.d.ts +2 -2
- package/dist/sandbox/tenant-config.js +4 -3
- package/dist/sandbox/tools.d.ts +7 -1
- package/dist/sandbox/tools.js +26 -26
- package/dist/sandbox/types.d.ts +10 -3
- package/dist/sandbox/types.js +9 -0
- package/dist/store/postgres/connect.d.ts +6 -5
- package/dist/store/postgres/connect.js +14 -11
- package/dist/store/postgres/control.js +13 -13
- package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
- package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
- package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
- package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/migrate.js +1 -1
- package/dist/store/postgres/reads.js +10 -2
- package/dist/store/postgres/schema.d.ts +19 -0
- package/dist/store/postgres/schema.js +7 -0
- package/dist/store/postgres/store.js +55 -26
- package/dist/store/types.d.ts +17 -7
- package/dist/streams/relay/core.d.ts +3 -3
- package/dist/streams/relay/core.js +9 -10
- package/dist/tenant/advance.js +21 -31
- package/dist/tenant/commands.js +9 -16
- package/dist/tenant/context.d.ts +14 -17
- package/dist/tenant/definition-files.d.ts +25 -4
- package/dist/tenant/definition-files.js +85 -47
- package/dist/tenant/effects.js +9 -36
- package/dist/tenant/environment-manifests.js +9 -1
- package/dist/tenant/environments.d.ts +0 -2
- package/dist/tenant/environments.js +54 -30
- package/dist/tenant/ephemeral.d.ts +1 -1
- package/dist/tenant/ephemeral.js +2 -1
- package/dist/tenant/history.d.ts +1 -1
- package/dist/tenant/history.js +1 -1
- package/dist/tenant/host-token.d.ts +3 -3
- package/dist/tenant/host-token.js +12 -11
- package/dist/tenant/http.d.ts +5 -8
- package/dist/tenant/http.js +9 -0
- package/dist/tenant/identity-file.d.ts +24 -1
- package/dist/tenant/identity-file.js +35 -7
- package/dist/tenant/issuers.d.ts +5 -5
- package/dist/tenant/issuers.js +31 -23
- package/dist/tenant/jwt.d.ts +8 -2
- package/dist/tenant/jwt.js +10 -2
- package/dist/tenant/limits.d.ts +90 -0
- package/dist/tenant/limits.js +175 -0
- package/dist/tenant/module.d.ts +4 -1
- package/dist/tenant/module.js +9 -8
- package/dist/tenant/outbound.d.ts +17 -19
- package/dist/tenant/outbound.js +18 -21
- package/dist/tenant/run-grants.js +3 -2
- package/dist/tenant/run-token.d.ts +0 -4
- package/dist/tenant/run-token.js +6 -11
- package/dist/tenant/runtime-limits.d.ts +24 -0
- package/dist/tenant/runtime-limits.js +27 -0
- package/dist/tenant/runtime.js +30 -43
- package/dist/tenant/session-streams.js +7 -6
- package/dist/tenant/session.d.ts +1 -1
- package/dist/tenant/session.js +1 -1
- package/dist/tenant/sessions.js +32 -29
- package/dist/tenant/signing-keys.d.ts +1 -1
- package/dist/tenant/signing-keys.js +5 -6
- package/dist/tenant/status.js +4 -3
- package/dist/tenant/streams.js +18 -20
- package/dist/tenant/sweep.d.ts +1 -21
- package/dist/tenant/sweep.js +39 -48
- package/dist/tenant/transcript.d.ts +70 -23
- package/dist/tenant/transcript.js +28 -22
- package/dist/tenant/types.d.ts +11 -16
- package/dist/tenant/worker.d.ts +2 -10
- package/dist/tenant/worker.js +5 -14
- package/dist/traces/ids.js +5 -1
- package/dist/traces/map.d.ts +9 -6
- package/dist/traces/map.js +9 -11
- package/dist/traces/otlp.d.ts +3 -3
- package/dist/traces/otlp.js +5 -3
- package/dist/traces/projector.d.ts +9 -4
- package/dist/traces/projector.js +13 -7
- package/dist/traces/read.js +6 -1
- package/dist/tuning.d.ts +383 -0
- package/dist/tuning.js +658 -0
- package/dist/vault/headers.js +4 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/artifacts/export.d.ts +0 -8
- package/dist/artifacts/export.js +0 -204
- package/dist/artifacts/media-types.d.ts +0 -30
- package/dist/artifacts/media-types.js +0 -137
- package/dist/artifacts/workspace.d.ts +0 -60
- package/dist/artifacts/workspace.js +0 -45
- package/dist/media.d.ts +0 -10
- package/dist/media.js +0 -58
- package/dist/sandbox/runtime-tools.d.ts +0 -9
- package/dist/sandbox/runtime-tools.js +0 -16
- package/dist/sandbox/session-sandbox.d.ts +0 -16
- package/dist/sandbox/session-sandbox.js +0 -77
- package/dist/tenant/artifact-tool.d.ts +0 -13
- package/dist/tenant/artifact-tool.js +0 -243
- package/dist/tenant/skill-tool.d.ts +0 -20
- package/dist/tenant/skill-tool.js +0 -67
- package/dist/tenant/tool-results.d.ts +0 -57
- package/dist/tenant/tool-results.js +0 -298
- package/dist/tenant/tool-search.d.ts +0 -39
- package/dist/tenant/tool-search.js +0 -103
package/dist/artifacts/disk.js
CHANGED
|
@@ -25,22 +25,27 @@ import { newArtifactId } from "@nylorun/core/compatibility";
|
|
|
25
25
|
import { FOLDER_CONTENT_TYPE, FOLDER_MANIFEST_FORMAT_V2, } from "@nylorun/core/contracts";
|
|
26
26
|
import { sandboxSpecOf } from "../sandbox/resolve.js";
|
|
27
27
|
import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
|
|
28
|
-
import { assertReaches, diskHeld, liveEnvironment, newVolume,
|
|
29
|
-
import { fail } from "../tenant/http.js";
|
|
30
|
-
import {
|
|
28
|
+
import { assertReaches, diskHeld, liveEnvironment, newVolume, wakePod, } from "../tenant/environments.js";
|
|
29
|
+
import { fail, failRetryLater } from "../tenant/http.js";
|
|
30
|
+
import { hostTuning } from "../tuning.js";
|
|
31
|
+
import { artifactQuota, totalSubject } from "./quota.js";
|
|
32
|
+
import { limitsInForce, readTenantLimits } from "../tenant/limits.js";
|
|
33
|
+
import { readRuntimeLimits } from "../tenant/runtime-limits.js";
|
|
31
34
|
import { collectContent, contentEpoch, contentKey, encodeManifest, readManifest, sha256Hex } from "./folders.js";
|
|
32
|
-
import { contentTypeFor } from "
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
import { contentTypeFor } from "@nylorun/core/contracts";
|
|
36
|
+
async function copyLimits(ctx, environment) {
|
|
37
|
+
return ctx.store.tx(async (t) => {
|
|
38
|
+
const runtime = await readRuntimeLimits(t);
|
|
39
|
+
const disk = limitsInForce(await readTenantLimits(t), environment?.applied.limits).disk;
|
|
40
|
+
return {
|
|
41
|
+
versionEntries: disk.versionEntries,
|
|
42
|
+
versionBytes: disk.versionBytes,
|
|
43
|
+
chunkBytes: runtime.chunkBytes,
|
|
44
|
+
leaseMs: runtime.diskLeaseMs,
|
|
45
|
+
podWaitMs: runtime.diskPodWaitMs,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
44
49
|
/** The name of an Environment's disk artifact. */
|
|
45
50
|
export const DISK_ARTIFACT_NAME = "disk";
|
|
46
51
|
/** What the disk operations of Environment `environment` act through: a ref and its spec. */
|
|
@@ -51,7 +56,6 @@ async function diskTarget(ctx, environment) {
|
|
|
51
56
|
ref: {
|
|
52
57
|
id: `disk:${environment.id}`,
|
|
53
58
|
activeTurnId: null,
|
|
54
|
-
manifest: { manifestSchemaVersion: 7, id: "disk" },
|
|
55
59
|
environmentId: environment.id,
|
|
56
60
|
// Not through a host a restore or a lost volume retired: its connection outlives the commit.
|
|
57
61
|
...(environment.pod === undefined ? {} : { hostEpoch: environment.pod.hostEpoch }),
|
|
@@ -70,30 +74,30 @@ async function diskOp(ctx, target, op) {
|
|
|
70
74
|
* Takes the disk lease of Environment `id` for `kind`: refused (`409 environment_busy`) while a
|
|
71
75
|
* turn runs or another copy holds it; `400` for kind `none`, which has no disk.
|
|
72
76
|
*/
|
|
73
|
-
async function lease(ctx, id, kind, version) {
|
|
77
|
+
async function lease(ctx, id, kind, leaseMs, version) {
|
|
74
78
|
return ctx.store.tx(async (t) => {
|
|
75
79
|
const environment = (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Environment not found");
|
|
76
80
|
if (environment.kind === "none")
|
|
77
81
|
fail(400, `Environment ${id} is of kind none: it has no disk.`);
|
|
78
82
|
const busy = (await t.sessionsOnEnvironment(id)).some((session) => session.activeTurnId);
|
|
79
83
|
if (busy || diskHeld(environment))
|
|
80
|
-
|
|
81
|
-
code: "environment_busy",
|
|
82
|
-
details: { retryAfterSeconds: 5 },
|
|
83
|
-
}, { "retry-after": "5" });
|
|
84
|
+
failRetryLater(`Environment ${id} has a turn running or its disk is being copied. Try again shortly.`, "environment_busy");
|
|
84
85
|
const now = new Date();
|
|
85
86
|
const operation = {
|
|
86
87
|
kind,
|
|
87
88
|
...(version === undefined ? {} : { version }),
|
|
88
89
|
startedAt: now.toISOString(),
|
|
89
|
-
until: new Date(now.getTime() +
|
|
90
|
+
until: new Date(now.getTime() + leaseMs).toISOString(),
|
|
90
91
|
};
|
|
91
92
|
await t.updateEnvironment(id, { diskOp: operation }, now.toISOString());
|
|
92
93
|
return { ...environment, diskOp: operation };
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
|
-
/**
|
|
96
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Renews the lease for `ms` more every quarter of `limits.leaseMs` until `stop()`, which ends it
|
|
98
|
+
* (and, with `seed`, the seed).
|
|
99
|
+
*/
|
|
100
|
+
function heartbeat(ctx, id, limits, ms = limits.leaseMs) {
|
|
97
101
|
const timer = setInterval(() => {
|
|
98
102
|
void ctx.store
|
|
99
103
|
.tx(async (t) => {
|
|
@@ -104,7 +108,7 @@ function heartbeat(ctx, id, ms = LEASE_MS) {
|
|
|
104
108
|
await t.updateEnvironment(id, { diskOp: { ...environment.diskOp, until: new Date(now.getTime() + ms).toISOString() } }, now.toISOString());
|
|
105
109
|
})
|
|
106
110
|
.catch(() => undefined);
|
|
107
|
-
},
|
|
111
|
+
}, limits.leaseMs / 4);
|
|
108
112
|
timer.unref();
|
|
109
113
|
return {
|
|
110
114
|
async stop(options = {}) {
|
|
@@ -125,25 +129,22 @@ function heartbeat(ctx, id, ms = LEASE_MS) {
|
|
|
125
129
|
};
|
|
126
130
|
}
|
|
127
131
|
/** Waits until the engine of pod Environment `id` is connected, starting the pod when it is stopped. */
|
|
128
|
-
async function awaitEngine(ctx, id) {
|
|
132
|
+
async function awaitEngine(ctx, id, waitMs) {
|
|
129
133
|
await wakePod(ctx, id);
|
|
130
|
-
const deadline = Date.now() +
|
|
134
|
+
const deadline = Date.now() + waitMs;
|
|
131
135
|
while (!ctx.harness.hosting(id)) {
|
|
132
136
|
if (Date.now() > deadline)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
details: { retryAfterSeconds: 5 },
|
|
136
|
-
}, { "retry-after": "5" });
|
|
137
|
-
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
137
|
+
failRetryLater(`Environment ${id}'s pod did not start within ${waitMs / 1000} s (the Runtime limit diskPodWaitMs).`, "environment_unavailable");
|
|
138
|
+
await new Promise((resolve) => setTimeout(resolve, hostTuning().pods.enginePollMs));
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
/** A file's bytes and their hash, read in ranges. */
|
|
141
|
-
async function readFile(ctx, target, path) {
|
|
142
|
+
async function readFile(ctx, target, path, rangeBytes) {
|
|
142
143
|
const hash = createHash("sha256");
|
|
143
144
|
const chunks = [];
|
|
144
145
|
let offset = 0;
|
|
145
146
|
for (;;) {
|
|
146
|
-
const answer = await diskOp(ctx, target, { read: { path, offset, length:
|
|
147
|
+
const answer = await diskOp(ctx, target, { read: { path, offset, length: rangeBytes } });
|
|
147
148
|
if (answer.kind === "missing")
|
|
148
149
|
return undefined;
|
|
149
150
|
if (answer.kind !== "read")
|
|
@@ -163,25 +164,26 @@ async function readFile(ctx, target, path) {
|
|
|
163
164
|
export async function snapshotEnvironment(ctx, id, scope) {
|
|
164
165
|
assertReaches(scope, id);
|
|
165
166
|
const started = Date.now();
|
|
166
|
-
const
|
|
167
|
-
const
|
|
167
|
+
const limits = await copyLimits(ctx, await ctx.store.tx((t) => liveEnvironment(t, id)));
|
|
168
|
+
const environment = await lease(ctx, id, "snapshot", limits.leaseMs);
|
|
169
|
+
const beat = heartbeat(ctx, id, limits);
|
|
168
170
|
try {
|
|
169
171
|
if (environment.kind === "pod")
|
|
170
|
-
await awaitEngine(ctx, id);
|
|
172
|
+
await awaitEngine(ctx, id, limits.podWaitMs);
|
|
171
173
|
const target = await diskTarget(ctx, environment);
|
|
172
|
-
const listed = await diskOp(ctx, target, { list: { maxEntries:
|
|
174
|
+
const listed = await diskOp(ctx, target, { list: { maxEntries: limits.versionEntries } });
|
|
173
175
|
if (listed.kind !== "listed")
|
|
174
176
|
throw new Error("The disk was not listed");
|
|
175
177
|
if (listed.truncated)
|
|
176
|
-
fail(413, `The disk holds more than ${
|
|
178
|
+
fail(413, `The disk holds more than ${limits.versionEntries} entries (the limit diskVersionEntries).`, {
|
|
177
179
|
code: "limit_exceeded",
|
|
178
|
-
details: { limit: "files", max:
|
|
180
|
+
details: { limit: "files", max: limits.versionEntries },
|
|
179
181
|
});
|
|
180
182
|
const bytes = listed.entries.reduce((sum, entry) => sum + (entry.type === "file" ? entry.size : 0), 0);
|
|
181
|
-
if (bytes >
|
|
182
|
-
fail(413, `The disk holds ${bytes} bytes; a version holds at most ${
|
|
183
|
+
if (bytes > limits.versionBytes)
|
|
184
|
+
fail(413, `The disk holds ${bytes} bytes; a version holds at most ${limits.versionBytes} (the limit diskVersionBytes).`, {
|
|
183
185
|
code: "limit_exceeded",
|
|
184
|
-
details: { limit: "bytes", max:
|
|
186
|
+
details: { limit: "bytes", max: limits.versionBytes },
|
|
185
187
|
});
|
|
186
188
|
const epoch = await ctx.store.tx((t) => contentEpoch(t));
|
|
187
189
|
const entries = [];
|
|
@@ -198,7 +200,7 @@ export async function snapshotEnvironment(ctx, id, scope) {
|
|
|
198
200
|
continue;
|
|
199
201
|
}
|
|
200
202
|
const base = { path: entry.path, mode: entry.mode, mtimeMs: entry.mtimeMs };
|
|
201
|
-
const read = await readFile(ctx, target, entry.path);
|
|
203
|
+
const read = await readFile(ctx, target, entry.path, limits.chunkBytes);
|
|
202
204
|
if (!read)
|
|
203
205
|
continue; // removed while it was copied (a background process)
|
|
204
206
|
const key = contentKey(read.sha256);
|
|
@@ -262,9 +264,9 @@ async function commitDiskVersion(ctx, id, entries, epoch) {
|
|
|
262
264
|
const artifactId = prior?.id ?? newArtifactId();
|
|
263
265
|
const key = `artifacts/${artifactId}/${randomUUID()}`;
|
|
264
266
|
await ctx.blobs.put(key, manifest, { contentType: FOLDER_CONTENT_TYPE });
|
|
265
|
-
const totalBytes = (await ctx.store.tx((t) => readArtifactLimits(t))).totalBytes;
|
|
266
267
|
return ctx.store.tx(async (t) => {
|
|
267
268
|
(await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Environment not found");
|
|
269
|
+
const quota = await artifactQuota(t, id);
|
|
268
270
|
await t.lockArtifactQuota();
|
|
269
271
|
const shas = [...files.keys()];
|
|
270
272
|
const referenced = await t.referencedArtifactContent(shas);
|
|
@@ -273,13 +275,18 @@ async function commitDiskVersion(ctx, id, entries, epoch) {
|
|
|
273
275
|
for (const sha of fresh)
|
|
274
276
|
if (!(await ctx.blobs.head(contentKey(sha))))
|
|
275
277
|
fail(409, "A file's bytes were removed while the snapshot ran; try again.", { code: "environment_busy" });
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
for (const total of quota.totals) {
|
|
279
|
+
// A file adds bytes to a total unless an artifact it counts already holds the file.
|
|
280
|
+
const scope = total.environmentId === undefined ? undefined : { environmentId: total.environmentId };
|
|
281
|
+
const held = scope === undefined ? referenced : await t.referencedArtifactContent(shas, scope);
|
|
282
|
+
const added = shas.filter((sha) => !held.has(sha)).reduce((sum, sha) => sum + files.get(sha), 0);
|
|
283
|
+
const used = await t.artifactBytes(scope);
|
|
284
|
+
if (used + added > total.limitBytes)
|
|
285
|
+
fail(413, `${totalSubject(total)} may hold at most ${total.limitBytes} bytes; this version adds ${added} to ${used}.`, {
|
|
286
|
+
code: "limit_exceeded",
|
|
287
|
+
details: { limit: scope === undefined ? "tenant_total" : "environment_total", max: total.limitBytes },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
283
290
|
const createdAt = new Date().toISOString();
|
|
284
291
|
const current = await t.diskArtifact(id, { lock: true });
|
|
285
292
|
const version = {
|
|
@@ -291,6 +298,7 @@ async function commitDiskVersion(ctx, id, entries, epoch) {
|
|
|
291
298
|
contentType: FOLDER_CONTENT_TYPE,
|
|
292
299
|
source: "snapshot",
|
|
293
300
|
createdAt,
|
|
301
|
+
writeKey: null,
|
|
294
302
|
};
|
|
295
303
|
let artifact;
|
|
296
304
|
if (current) {
|
|
@@ -320,7 +328,7 @@ async function commitDiskVersion(ctx, id, entries, epoch) {
|
|
|
320
328
|
* Writes disk version `seed` into Environment `id`'s workspace: empties it (the engine's files
|
|
321
329
|
* stay), then directories, files (large ones in ranges) and links. Its caller holds the lease.
|
|
322
330
|
*/
|
|
323
|
-
async function writeVersion(ctx, environment, seed) {
|
|
331
|
+
async function writeVersion(ctx, environment, seed, rangeBytes) {
|
|
324
332
|
const version = await ctx.store.tx((t) => t.artifactVersion(seed.artifactId, seed.version));
|
|
325
333
|
if (!version)
|
|
326
334
|
throw new Error(`Disk version ${seed.version} of ${seed.artifactId} is gone`);
|
|
@@ -354,22 +362,22 @@ async function writeVersion(ctx, environment, seed) {
|
|
|
354
362
|
...(entry.mode === undefined ? {} : { mode: entry.mode }),
|
|
355
363
|
...(entry.mtimeMs === undefined ? {} : { mtimeMs: entry.mtimeMs }),
|
|
356
364
|
};
|
|
357
|
-
if (content.length <=
|
|
358
|
-
if (batchBytes + content.length >
|
|
365
|
+
if (content.length <= rangeBytes) {
|
|
366
|
+
if (batchBytes + content.length > rangeBytes)
|
|
359
367
|
await flush();
|
|
360
368
|
batch.push({ path: entry.path, type: "file", bytes: content, ...meta });
|
|
361
369
|
batchBytes += content.length;
|
|
362
370
|
continue;
|
|
363
371
|
}
|
|
364
372
|
await flush();
|
|
365
|
-
for (let offset = 0; offset < content.length; offset +=
|
|
366
|
-
const last = offset +
|
|
373
|
+
for (let offset = 0; offset < content.length; offset += rangeBytes) {
|
|
374
|
+
const last = offset + rangeBytes >= content.length;
|
|
367
375
|
await diskOp(ctx, target, {
|
|
368
376
|
put: [
|
|
369
377
|
{
|
|
370
378
|
path: entry.path,
|
|
371
379
|
type: "file",
|
|
372
|
-
bytes: content.subarray(offset, offset +
|
|
380
|
+
bytes: content.subarray(offset, offset + rangeBytes),
|
|
373
381
|
...(offset > 0 ? { append: true } : {}),
|
|
374
382
|
...(last ? meta : {}),
|
|
375
383
|
},
|
|
@@ -408,18 +416,16 @@ export async function restoreEnvironment(ctx, id, scope, version) {
|
|
|
408
416
|
const environment = (await liveEnvironment(t, id, { lock: true })) ?? fail(404, "Environment not found");
|
|
409
417
|
const busy = (await t.sessionsOnEnvironment(id)).some((session) => session.activeTurnId);
|
|
410
418
|
if (busy || diskHeld(environment) || environment.pod.retiring !== undefined)
|
|
411
|
-
|
|
412
|
-
code: "environment_busy",
|
|
413
|
-
details: { retryAfterSeconds: 5 },
|
|
414
|
-
}, { "retry-after": "5" });
|
|
419
|
+
failRetryLater(`Environment ${id} has a turn running, its disk is being copied or its last volume is still being deleted.`, "environment_busy");
|
|
415
420
|
return newVolume(ctx, t, environment, { seed, reason: "restore" });
|
|
416
421
|
});
|
|
417
422
|
return { environmentId: id, restored: seed.version, status: "pending", volumeGeneration };
|
|
418
423
|
}
|
|
419
|
-
const
|
|
420
|
-
const
|
|
424
|
+
const limits = await copyLimits(ctx, found);
|
|
425
|
+
const environment = await lease(ctx, id, "restore", limits.leaseMs, seed.version);
|
|
426
|
+
const beat = heartbeat(ctx, id, limits);
|
|
421
427
|
try {
|
|
422
|
-
await writeVersion(ctx, environment, seed);
|
|
428
|
+
await writeVersion(ctx, environment, seed, limits.chunkBytes);
|
|
423
429
|
}
|
|
424
430
|
finally {
|
|
425
431
|
await beat.stop();
|
|
@@ -444,20 +450,21 @@ export async function runPendingSeed(ctx, id) {
|
|
|
444
450
|
const seed = environment?.diskSeed;
|
|
445
451
|
if (!environment || !seed || seed.written)
|
|
446
452
|
return;
|
|
453
|
+
const limits = await copyLimits(ctx, environment);
|
|
447
454
|
if (environment.kind === "pod") {
|
|
448
|
-
const deadline = Date.now() +
|
|
455
|
+
const deadline = Date.now() + limits.podWaitMs;
|
|
449
456
|
// The engine on the new volume: the old one's connection may still be open.
|
|
450
457
|
while (!ctx.harness.hosting(id, environment.pod?.hostEpoch)) {
|
|
451
458
|
if (Date.now() > deadline)
|
|
452
459
|
return;
|
|
453
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
460
|
+
await new Promise((resolve) => setTimeout(resolve, hostTuning().pods.enginePollMs));
|
|
454
461
|
}
|
|
455
462
|
}
|
|
456
|
-
const beat = heartbeat(ctx, id,
|
|
463
|
+
const beat = heartbeat(ctx, id, limits, hostTuning().tenant.seedLeaseMs);
|
|
457
464
|
let done = false;
|
|
458
465
|
try {
|
|
459
466
|
const started = Date.now();
|
|
460
|
-
const written = await writeVersion(ctx, environment, seed);
|
|
467
|
+
const written = await writeVersion(ctx, environment, seed, limits.chunkBytes);
|
|
461
468
|
done = true;
|
|
462
469
|
ctx.config.logger.info("disk_seed", { environmentId: id, version: seed.version, ...written, durationMs: Date.now() - started });
|
|
463
470
|
}
|
|
@@ -538,10 +545,7 @@ export async function deleteEnvironmentVersion(ctx, id, scope, version) {
|
|
|
538
545
|
if (!artifact)
|
|
539
546
|
return undefined;
|
|
540
547
|
if (environment.diskSeed?.artifactId === artifact.id && environment.diskSeed.version === version)
|
|
541
|
-
|
|
542
|
-
code: "environment_busy",
|
|
543
|
-
details: { retryAfterSeconds: 5 },
|
|
544
|
-
}, { "retry-after": "5" });
|
|
548
|
+
failRetryLater(`A new volume of Environment ${id} is being seeded from version ${version}.`, "environment_busy");
|
|
545
549
|
await t.lockArtifactQuota();
|
|
546
550
|
const content = await t.artifactContentShas({ artifactId: artifact.id });
|
|
547
551
|
const key = await t.deleteArtifactVersion(artifact.id, version);
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { JsonValue } from "@nylorun/core/define";
|
|
10
10
|
import type { BlobStore } from "../blob/index.js";
|
|
11
|
-
import type { SessionStore } from "../store/types.js";
|
|
11
|
+
import type { ArtifactRow, SessionStore } from "../store/types.js";
|
|
12
|
+
import type { Session } from "../tenant/context.js";
|
|
12
13
|
/** A file a prompt names, with its bytes. */
|
|
13
14
|
export interface ResolvedFile {
|
|
14
15
|
readonly name: string;
|
|
@@ -22,15 +23,23 @@ export declare class FileUnavailableError extends Error {
|
|
|
22
23
|
}
|
|
23
24
|
/** Resolves a media part's reference to the file's bytes, or throws `FileUnavailableError`. */
|
|
24
25
|
export type FileResolver = (reference: JsonValue) => Promise<ResolvedFile>;
|
|
26
|
+
/**
|
|
27
|
+
* What a session may read of the Tenant's artifacts (the read rule): its own, and the Tenant-wide
|
|
28
|
+
* ones its messages named (`Session.fileArtifacts`); never another session's, and never an
|
|
29
|
+
* Environment's disk versions, which belong to no session. The Model Gate and the artifact seam
|
|
30
|
+
* (`artifact.read`) both read by it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sessionMayRead(session: Pick<Session, "id" | "fileArtifacts"> | undefined, artifact: Pick<ArtifactRow, "id" | "sessionId" | "environmentId">): boolean;
|
|
25
33
|
/**
|
|
26
34
|
* Files from the Tenant's artifacts: `store` for the version's row, `blobs` for its bytes, for
|
|
27
|
-
* the call of session `sessionId`.
|
|
28
|
-
*
|
|
35
|
+
* the call of session `sessionId`. It reads by the read rule (`sessionMayRead`): another
|
|
36
|
+
* session's artifact, or a Tenant-wide one the session never named, is refused, and without a
|
|
37
|
+
* session every file is: the check is never skipped. A file larger than the Tenant's Runtime
|
|
38
|
+
* limit `modelFileBytes`, read with the row, is refused too.
|
|
29
39
|
*/
|
|
30
40
|
export declare function artifactFiles(options: {
|
|
31
41
|
readonly store: SessionStore;
|
|
32
42
|
readonly blobs: BlobStore;
|
|
33
43
|
/** The session the call is for: the run token's, over HTTP. */
|
|
34
44
|
readonly sessionId: string | undefined;
|
|
35
|
-
readonly maxBytes?: number;
|
|
36
45
|
}): FileResolver;
|
package/dist/artifacts/files.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readRuntimeLimits } from "../tenant/runtime-limits.js";
|
|
2
2
|
import { fileReferenceOf } from "./parts.js";
|
|
3
3
|
/** Why a file cannot go to the model: the message is the outcome's, so it names no secret. */
|
|
4
4
|
export class FileUnavailableError extends Error {
|
|
@@ -7,13 +7,27 @@ export class FileUnavailableError extends Error {
|
|
|
7
7
|
this.name = "FileUnavailableError";
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* What a session may read of the Tenant's artifacts (the read rule): its own, and the Tenant-wide
|
|
12
|
+
* ones its messages named (`Session.fileArtifacts`); never another session's, and never an
|
|
13
|
+
* Environment's disk versions, which belong to no session. The Model Gate and the artifact seam
|
|
14
|
+
* (`artifact.read`) both read by it.
|
|
15
|
+
*/
|
|
16
|
+
export function sessionMayRead(session, artifact) {
|
|
17
|
+
if (!session)
|
|
18
|
+
return false;
|
|
19
|
+
if (artifact.sessionId !== null)
|
|
20
|
+
return artifact.sessionId === session.id;
|
|
21
|
+
return artifact.environmentId === null && session.fileArtifacts?.includes(artifact.id) === true;
|
|
22
|
+
}
|
|
10
23
|
/**
|
|
11
24
|
* Files from the Tenant's artifacts: `store` for the version's row, `blobs` for its bytes, for
|
|
12
|
-
* the call of session `sessionId`.
|
|
13
|
-
*
|
|
25
|
+
* the call of session `sessionId`. It reads by the read rule (`sessionMayRead`): another
|
|
26
|
+
* session's artifact, or a Tenant-wide one the session never named, is refused, and without a
|
|
27
|
+
* session every file is: the check is never skipped. A file larger than the Tenant's Runtime
|
|
28
|
+
* limit `modelFileBytes`, read with the row, is refused too.
|
|
14
29
|
*/
|
|
15
30
|
export function artifactFiles(options) {
|
|
16
|
-
const maxBytes = options.maxBytes ?? MODEL_FILE_MAX_BYTES;
|
|
17
31
|
return async (value) => {
|
|
18
32
|
const reference = fileReferenceOf(value);
|
|
19
33
|
if (!reference)
|
|
@@ -25,17 +39,21 @@ export function artifactFiles(options) {
|
|
|
25
39
|
const artifact = await t.artifact(reference.artifactId);
|
|
26
40
|
if (!artifact)
|
|
27
41
|
return undefined;
|
|
42
|
+
const session = artifact.sessionId === null
|
|
43
|
+
? await t.get("sessions", sessionId)
|
|
44
|
+
: { id: sessionId };
|
|
45
|
+
if (!sessionMayRead(session, artifact))
|
|
46
|
+
return undefined;
|
|
28
47
|
const version = await t.artifactVersion(artifact.id, reference.version);
|
|
29
|
-
return version ? { artifact, version } : undefined;
|
|
48
|
+
return version ? { artifact, version, maxBytes: (await readRuntimeLimits(t)).modelFileBytes } : undefined;
|
|
30
49
|
});
|
|
31
|
-
if (!found
|
|
32
|
-
(found.artifact.sessionId !== null && found.artifact.sessionId !== sessionId))
|
|
50
|
+
if (!found)
|
|
33
51
|
throw new FileUnavailableError(`Artifact ${reference.artifactId} version ${reference.version} is not available.`);
|
|
34
|
-
const { artifact, version } = found;
|
|
52
|
+
const { artifact, version, maxBytes } = found;
|
|
35
53
|
if (artifact.kind !== "file")
|
|
36
54
|
throw new FileUnavailableError(`${artifact.name} is a folder; a model reads files only.`);
|
|
37
55
|
if (version.size > maxBytes)
|
|
38
|
-
throw new FileUnavailableError(`${artifact.name} is ${version.size} bytes; a model reads files of at most ${maxBytes} bytes.`);
|
|
56
|
+
throw new FileUnavailableError(`${artifact.name} is ${version.size} bytes; a model reads files of at most ${maxBytes} bytes (the Runtime limit modelFileBytes).`);
|
|
39
57
|
const got = await options.blobs.get(version.blobKey);
|
|
40
58
|
if (!got)
|
|
41
59
|
throw new FileUnavailableError(`The bytes of ${artifact.name} are missing from the Object store.`);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type HarnessLimits } from "@nylorun/core/contracts";
|
|
2
|
+
import { type ParamsOf } from "@nylorun/core/harness-api";
|
|
3
|
+
import type { ArtifactRow, ArtifactVersionRow } from "../store/types.js";
|
|
4
|
+
import type { TenantContext } from "../tenant/context.js";
|
|
5
|
+
import type { ArtifactQuota } from "./quota.js";
|
|
6
|
+
/** What a folder upload writes: the session's folder, and what its version must fit. */
|
|
7
|
+
export interface FolderTarget {
|
|
8
|
+
readonly sessionId: string;
|
|
9
|
+
readonly turnId: string;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
/** The folder this upload adds a version to; absent: a new folder. */
|
|
12
|
+
readonly prior?: {
|
|
13
|
+
readonly row: ArtifactRow;
|
|
14
|
+
readonly latest?: ArtifactVersionRow;
|
|
15
|
+
};
|
|
16
|
+
readonly quota: ArtifactQuota;
|
|
17
|
+
/** The content epoch read at `begin`, before any `head` of this upload. */
|
|
18
|
+
readonly epoch: number;
|
|
19
|
+
readonly writeKey: string;
|
|
20
|
+
readonly limits: HarnessLimits;
|
|
21
|
+
}
|
|
22
|
+
/** How a folder upload's `commit` ended. */
|
|
23
|
+
export type FolderCommit = {
|
|
24
|
+
readonly kind: "written";
|
|
25
|
+
readonly row: ArtifactRow;
|
|
26
|
+
readonly version: ArtifactVersionRow;
|
|
27
|
+
readonly created: boolean;
|
|
28
|
+
readonly unchanged?: true;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: "refused";
|
|
31
|
+
readonly reason: "tenant_total" | "environment_total";
|
|
32
|
+
readonly message: string;
|
|
33
|
+
readonly limit: number;
|
|
34
|
+
};
|
|
35
|
+
/** A folder file's path: relative, `/`-separated, without empty, `.` or `..` segments. */
|
|
36
|
+
export declare function checkFolderPath(path: string): void;
|
|
37
|
+
export declare class FolderUpload {
|
|
38
|
+
private readonly ctx;
|
|
39
|
+
readonly target: FolderTarget;
|
|
40
|
+
readonly id: `${string}-${string}-${string}-${string}-${string}`;
|
|
41
|
+
/** The most bytes its files may hold together, and one file. */
|
|
42
|
+
readonly maxBytes: number;
|
|
43
|
+
readonly maxFileBytes: number;
|
|
44
|
+
readonly maxEntries: number;
|
|
45
|
+
private readonly entries;
|
|
46
|
+
private readonly paths;
|
|
47
|
+
/** Content taken whole and checked in this upload. */
|
|
48
|
+
private readonly taken;
|
|
49
|
+
/** Content this upload stored at its content key: collected again if nothing commits. */
|
|
50
|
+
private readonly stored;
|
|
51
|
+
private bytes;
|
|
52
|
+
private files;
|
|
53
|
+
private current?;
|
|
54
|
+
private ended;
|
|
55
|
+
constructor(ctx: TenantContext, target: FolderTarget);
|
|
56
|
+
/** Starts a file; its bytes come with it (`base64`) or in `chunk`s. Answers the bytes received. */
|
|
57
|
+
file(params: Extract<ParamsOf<"artifact.write">, {
|
|
58
|
+
op: "file";
|
|
59
|
+
}>, signal: AbortSignal): Promise<number>;
|
|
60
|
+
/** Bytes of the file in progress from `offset`. Answers the bytes received of it. */
|
|
61
|
+
chunk(params: Extract<ParamsOf<"artifact.write">, {
|
|
62
|
+
op: "chunk";
|
|
63
|
+
}>): Promise<number>;
|
|
64
|
+
/** Stores the version, unless it is the folder's latest; collects what it stored when not. */
|
|
65
|
+
commit(signal: AbortSignal): Promise<FolderCommit>;
|
|
66
|
+
/** Ends the upload: nothing commits, and what it stored is collected. Never throws. */
|
|
67
|
+
abort(reason: string): Promise<void>;
|
|
68
|
+
private live;
|
|
69
|
+
private take;
|
|
70
|
+
/** The file in progress is whole: check its hash, then keep its content once. */
|
|
71
|
+
private finish;
|
|
72
|
+
private dropCurrent;
|
|
73
|
+
/** Content this upload stored that no committed version names is removed again. */
|
|
74
|
+
private collect;
|
|
75
|
+
}
|