@tangle-network/agent-provider-cli-bridge 2.0.6 → 2.0.7
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/dist/sandbox/checkpoint.d.ts +26 -0
- package/dist/sandbox/checkpoint.js +82 -0
- package/dist/sandbox/image.d.ts +4 -2
- package/dist/sandbox/image.js +5 -3
- package/dist/sandbox/index.d.ts +2 -1
- package/dist/sandbox/index.js +1 -0
- package/dist/sandbox/provider.js +18 -2
- package/dist/sandbox/vendor.d.ts +25 -0
- package/package.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type CheckpointRef, type CheckpointRequest, type CreateAgentEnvironmentInput, type ForkRequest } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
+
import type { BridgeSandboxVendor, BridgeVendorSnapshots } from "./vendor.js";
|
|
3
|
+
/**
|
|
4
|
+
* Vendor-snapshot checkpointing: the portable seam between the optional
|
|
5
|
+
* `AgentEnvironment.checkpoint`/`fork` surface and a vendor's snapshot
|
|
6
|
+
* lifecycle. A vendor without {@link BridgeVendorSnapshots} refuses both,
|
|
7
|
+
* exactly as it refuses every capability it cannot honor.
|
|
8
|
+
*/
|
|
9
|
+
/** The vendor snapshot lifecycle, or a refusal naming the vendor. */
|
|
10
|
+
export declare function vendorSnapshots(vendor: BridgeSandboxVendor): BridgeVendorSnapshots;
|
|
11
|
+
/** A checkpoint name that cannot collide with a caller-chosen vendor snapshot name. */
|
|
12
|
+
export declare function checkpointSnapshotName(name: string | undefined): string;
|
|
13
|
+
/** Snapshot one running sandbox and return the portable checkpoint reference. */
|
|
14
|
+
export declare function createVendorSnapshot(vendor: BridgeSandboxVendor, sandboxId: string, options?: CheckpointRequest & {
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}): Promise<CheckpointRef>;
|
|
17
|
+
/** A fork must restore this vendor's snapshot; a foreign checkpoint reference is refused. */
|
|
18
|
+
export declare function assertForkableCheckpoint(ref: CheckpointRef, vendorName: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* The create input a fork runs with: the source environment's input, minus
|
|
21
|
+
* everything the snapshot already carries. A repository clone would overwrite
|
|
22
|
+
* the snapshot's workspace, and a caller-chosen environment would boot a
|
|
23
|
+
* different image than the checkpoint captured; both are dropped, while the
|
|
24
|
+
* requested working directory keeps its placement.
|
|
25
|
+
*/
|
|
26
|
+
export declare function forkCreateInput(source: CreateAgentEnvironmentInput, request?: ForkRequest): CreateAgentEnvironmentInput;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { EnvironmentProviderError, } from "@tangle-network/agent-interface/environment-provider";
|
|
3
|
+
/**
|
|
4
|
+
* Vendor-snapshot checkpointing: the portable seam between the optional
|
|
5
|
+
* `AgentEnvironment.checkpoint`/`fork` surface and a vendor's snapshot
|
|
6
|
+
* lifecycle. A vendor without {@link BridgeVendorSnapshots} refuses both,
|
|
7
|
+
* exactly as it refuses every capability it cannot honor.
|
|
8
|
+
*/
|
|
9
|
+
/** The vendor snapshot lifecycle, or a refusal naming the vendor. */
|
|
10
|
+
export function vendorSnapshots(vendor) {
|
|
11
|
+
if (vendor.snapshots === undefined) {
|
|
12
|
+
throw new EnvironmentProviderError("refused", `vendor ${vendor.name} exposes no snapshot lifecycle`, { provider: vendor.name });
|
|
13
|
+
}
|
|
14
|
+
return vendor.snapshots;
|
|
15
|
+
}
|
|
16
|
+
/** Snapshot metadata must be string-valued, exactly like vendor sandbox tags. */
|
|
17
|
+
function snapshotMetadata(metadata, provider) {
|
|
18
|
+
if (metadata === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
22
|
+
if (typeof value !== "string") {
|
|
23
|
+
throw new EnvironmentProviderError("refused", `snapshot metadata holds string values; ${key} is not a string`, { path: `metadata.${key}`, provider });
|
|
24
|
+
}
|
|
25
|
+
out[key] = value;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
/** A checkpoint name that cannot collide with a caller-chosen vendor snapshot name. */
|
|
30
|
+
export function checkpointSnapshotName(name) {
|
|
31
|
+
return name ?? `tangle-ckpt-${randomUUID()}`;
|
|
32
|
+
}
|
|
33
|
+
/** Snapshot one running sandbox and return the portable checkpoint reference. */
|
|
34
|
+
export async function createVendorSnapshot(vendor, sandboxId, options) {
|
|
35
|
+
const snapshots = vendorSnapshots(vendor);
|
|
36
|
+
const request = options === undefined
|
|
37
|
+
? {}
|
|
38
|
+
: {
|
|
39
|
+
...(options.name === undefined ? {} : { name: options.name }),
|
|
40
|
+
...(options.metadata === undefined ? {} : { metadata: snapshotMetadata(options.metadata, vendor.name) }),
|
|
41
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
42
|
+
};
|
|
43
|
+
const record = await snapshots.create(sandboxId, Object.keys(request).length === 0 ? undefined : request);
|
|
44
|
+
if (typeof record.id !== "string" || record.id.length === 0) {
|
|
45
|
+
throw new EnvironmentProviderError("provision_failed", `vendor ${vendor.name} returned no snapshot id for ${sandboxId}`, { provider: vendor.name });
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
id: record.id,
|
|
49
|
+
provider: vendor.name,
|
|
50
|
+
...(record.createdAt === undefined ? {} : { metadata: { createdAt: record.createdAt } }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** A fork must restore this vendor's snapshot; a foreign checkpoint reference is refused. */
|
|
54
|
+
export function assertForkableCheckpoint(ref, vendorName) {
|
|
55
|
+
if (typeof ref.id !== "string" || ref.id.length === 0) {
|
|
56
|
+
throw new EnvironmentProviderError("invalid_input", "a checkpoint reference carries a non-empty id", { path: "checkpoint.id", provider: vendorName });
|
|
57
|
+
}
|
|
58
|
+
if (ref.provider !== undefined && ref.provider !== vendorName) {
|
|
59
|
+
throw new EnvironmentProviderError("refused", `checkpoint from provider ${ref.provider} cannot restore on ${vendorName}`, { path: "checkpoint.provider", provider: vendorName });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The create input a fork runs with: the source environment's input, minus
|
|
64
|
+
* everything the snapshot already carries. A repository clone would overwrite
|
|
65
|
+
* the snapshot's workspace, and a caller-chosen environment would boot a
|
|
66
|
+
* different image than the checkpoint captured; both are dropped, while the
|
|
67
|
+
* requested working directory keeps its placement.
|
|
68
|
+
*/
|
|
69
|
+
export function forkCreateInput(source, request) {
|
|
70
|
+
const { workspace, name, metadata, signal, ...rest } = source;
|
|
71
|
+
const cwd = workspace?.cwd;
|
|
72
|
+
return {
|
|
73
|
+
...rest,
|
|
74
|
+
...(request?.name === undefined
|
|
75
|
+
? (name === undefined ? {} : { name })
|
|
76
|
+
: { name: request.name }),
|
|
77
|
+
...(metadata === undefined && request?.metadata === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { metadata: { ...metadata, ...request?.metadata } }),
|
|
80
|
+
...(cwd === undefined ? {} : { workspace: { cwd } }),
|
|
81
|
+
};
|
|
82
|
+
}
|
package/dist/sandbox/image.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { HarnessType } from "@tangle-network/agent-interface";
|
|
|
4
4
|
*
|
|
5
5
|
* Every cloud builds from this text: E2B and Daytona build their template and
|
|
6
6
|
* image from it, and a local Docker build of it is the same box. Harness
|
|
7
|
-
* versions come from the registry that owns the pins.
|
|
7
|
+
* versions come from the registry that owns the pins. `baseImage` overrides
|
|
8
|
+
* the Node base for a box that carries more than the pins; the install layer
|
|
9
|
+
* is unchanged, so every box keeps the same Bridge and harness versions.
|
|
8
10
|
*/
|
|
9
|
-
export declare function renderBoxDockerfile(harnesses?: readonly HarnessType[]): string;
|
|
11
|
+
export declare function renderBoxDockerfile(harnesses?: readonly HarnessType[], baseImage?: string): string;
|
package/dist/sandbox/image.js
CHANGED
|
@@ -4,9 +4,11 @@ import { BOX_HARNESSES, BOX_HOME, BOX_NODE_IMAGE, BOX_PINS, BOX_WORKSPACE_DIR, b
|
|
|
4
4
|
*
|
|
5
5
|
* Every cloud builds from this text: E2B and Daytona build their template and
|
|
6
6
|
* image from it, and a local Docker build of it is the same box. Harness
|
|
7
|
-
* versions come from the registry that owns the pins.
|
|
7
|
+
* versions come from the registry that owns the pins. `baseImage` overrides
|
|
8
|
+
* the Node base for a box that carries more than the pins; the install layer
|
|
9
|
+
* is unchanged, so every box keeps the same Bridge and harness versions.
|
|
8
10
|
*/
|
|
9
|
-
export function renderBoxDockerfile(harnesses = Object.keys(BOX_HARNESSES)) {
|
|
11
|
+
export function renderBoxDockerfile(harnesses = Object.keys(BOX_HARNESSES), baseImage = BOX_NODE_IMAGE) {
|
|
10
12
|
const installs = harnesses.map((name) => {
|
|
11
13
|
const entry = BOX_HARNESSES[name];
|
|
12
14
|
if (entry === undefined)
|
|
@@ -17,7 +19,7 @@ export function renderBoxDockerfile(harnesses = Object.keys(BOX_HARNESSES)) {
|
|
|
17
19
|
const packages = installs.map(({ entry, version }) => `${entry.npmPackage}@${version}`);
|
|
18
20
|
const probes = installs.map(({ entry, version }) => `test "$(${entry.bin} --version | cut -d' ' -f1)" = "${version}"`);
|
|
19
21
|
return [
|
|
20
|
-
`FROM ${
|
|
22
|
+
`FROM ${baseImage}`,
|
|
21
23
|
"RUN apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates python3 make g++ ripgrep procps \\",
|
|
22
24
|
" && rm -rf /var/lib/apt/lists/*",
|
|
23
25
|
`RUN npm install -g ${[...packages, bridge].join(" ")} \\`,
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createBridgeSandboxProvider, BRIDGE_RUN_TAG } from "./provider.js";
|
|
2
2
|
export type { BridgeSandboxProviderOptions } from "./provider.js";
|
|
3
|
-
export type { BridgeSandbox, BridgeSandboxCreateRequest, BridgeSandboxSummary, BridgeSandboxVendor, BridgeVendorSupport, } from "./vendor.js";
|
|
3
|
+
export type { BridgeSandbox, BridgeSandboxCreateRequest, BridgeSandboxSummary, BridgeSandboxVendor, BridgeVendorSnapshot, BridgeVendorSnapshots, BridgeVendorSupport, } from "./vendor.js";
|
|
4
|
+
export { checkpointSnapshotName } from "./checkpoint.js";
|
|
4
5
|
export { validateBoxProfile } from "./refusal.js";
|
|
5
6
|
export { envFileLaunch, markedExec, untilAborted } from "./shell.js";
|
|
6
7
|
export { BOX_HARNESSES, BOX_HOME, BOX_PINS, BOX_TEMPLATE_NAME, BOX_WORKSPACE_DIR, BRIDGE_PORT, boxHarnessVersion, } from "./box.js";
|
package/dist/sandbox/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createBridgeSandboxProvider, BRIDGE_RUN_TAG } from "./provider.js";
|
|
2
|
+
export { checkpointSnapshotName } from "./checkpoint.js";
|
|
2
3
|
export { validateBoxProfile } from "./refusal.js";
|
|
3
4
|
export { envFileLaunch, markedExec, untilAborted } from "./shell.js";
|
|
4
5
|
export { BOX_HARNESSES, BOX_HOME, BOX_PINS, BOX_TEMPLATE_NAME, BOX_WORKSPACE_DIR, BRIDGE_PORT, boxHarnessVersion, } from "./box.js";
|
package/dist/sandbox/provider.js
CHANGED
|
@@ -8,6 +8,7 @@ import { harnessModelSupportFor } from "@tangle-network/cli-agent-registry";
|
|
|
8
8
|
import { createCliBridgeProvider, defaultCliBridgeCapabilities } from "../index.js";
|
|
9
9
|
import { BOX_WORKSPACE_DIR, BRIDGE_PORT, BRIDGE_START_COMMAND, bridgeProcessEnv, boxHarness, boxHarnessVersion, } from "./box.js";
|
|
10
10
|
import { assertProfileValid, declarableHarnesses, plannerLowers, refuseUnsupportedCreateInput, resolveBoxHarness, validateBoxProfile } from "./refusal.js";
|
|
11
|
+
import { assertForkableCheckpoint, createVendorSnapshot, forkCreateInput, } from "./checkpoint.js";
|
|
11
12
|
export const BRIDGE_RUN_TAG = "tangle-bridge-run";
|
|
12
13
|
const DEFAULT_LIFETIME_MS = 2 * 60 * 60 * 1000;
|
|
13
14
|
const DEFAULT_READY_TIMEOUT_MS = 120_000;
|
|
@@ -36,9 +37,12 @@ export function createBridgeSandboxProvider(options) {
|
|
|
36
37
|
const lifetimeMs = options.lifetimeMs ?? DEFAULT_LIFETIME_MS;
|
|
37
38
|
const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
|
|
38
39
|
const providerCapabilities = (harness) => environmentCapabilities(defaultCliBridgeCapabilities(harness), vendor, harness === undefined ? false : plannerLowers(harness, "hooks"));
|
|
39
|
-
async function create(input) {
|
|
40
|
+
async function create(input, restore) {
|
|
40
41
|
input.signal?.throwIfAborted();
|
|
41
42
|
refuseUnsupportedCreateInput(input, vendor.support);
|
|
43
|
+
if (restore !== undefined && input.workspace?.environment !== undefined) {
|
|
44
|
+
throw new EnvironmentProviderError("refused", "a fork restores its checkpoint; workspace.environment names a different one", { path: "workspace.environment" });
|
|
45
|
+
}
|
|
42
46
|
const profile = input.profile;
|
|
43
47
|
const validation = validateBoxProfile(profile, input.backend, declared);
|
|
44
48
|
assertProfileValid(validation);
|
|
@@ -58,6 +62,7 @@ export function createBridgeSandboxProvider(options) {
|
|
|
58
62
|
env: input.env ?? {},
|
|
59
63
|
lifetimeMs,
|
|
60
64
|
...(input.workspace?.environment === undefined ? {} : { environment: input.workspace.environment }),
|
|
65
|
+
...(restore === undefined ? {} : { snapshot: restore.snapshot }),
|
|
61
66
|
...(input.workspace?.image === undefined ? {} : { image: input.workspace.image }),
|
|
62
67
|
...(input.resources === undefined ? {} : { resources: input.resources }),
|
|
63
68
|
...(input.egress === undefined ? {} : { egress: egressFor(input, routerBaseUrl) }),
|
|
@@ -89,7 +94,8 @@ export function createBridgeSandboxProvider(options) {
|
|
|
89
94
|
workspace: { cwd: { base: "host", path: workspaceDir } },
|
|
90
95
|
});
|
|
91
96
|
input.signal?.throwIfAborted();
|
|
92
|
-
|
|
97
|
+
const forkFromCheckpoint = vendor.snapshots === undefined ? undefined : (checkpoint, request, signal) => create({ ...forkCreateInput(input, request), ...(signal === undefined ? {} : { signal }) }, { snapshot: checkpoint.id });
|
|
98
|
+
return wrapEnvironment({ inner, sandbox, vendor, harness, profile, workspaceDir, dispatcher, capabilities: environmentCapabilities(inner.capabilities ?? providerCapabilities(harness), vendor, plannerLowers(harness, "hooks")), input, ...(forkFromCheckpoint === undefined ? {} : { forkFromCheckpoint }) });
|
|
93
99
|
}
|
|
94
100
|
catch (error) {
|
|
95
101
|
await dispatcher?.close().catch(() => undefined);
|
|
@@ -264,10 +270,12 @@ function environmentCapabilities(bridge, vendor, hooks) {
|
|
|
264
270
|
...base,
|
|
265
271
|
profile: { ...base.profile, validation: true, hooks: hooks === true },
|
|
266
272
|
workspace: { read: true, write: true, exec: true, git: false, upload: false, download: false, cwdBases: { repository: true, host: false } },
|
|
273
|
+
branching: { checkpoint: vendor.snapshots !== undefined, fork: vendor.snapshots !== undefined },
|
|
267
274
|
placement: true,
|
|
268
275
|
create: {
|
|
269
276
|
durableIdempotency: false,
|
|
270
277
|
...(vendor.support.egress.length === 0 ? {} : { egress: [...vendor.support.egress] }),
|
|
278
|
+
...(vendor.snapshots === undefined ? {} : { workspaceSnapshot: false }),
|
|
271
279
|
},
|
|
272
280
|
};
|
|
273
281
|
}
|
|
@@ -348,6 +356,14 @@ function wrapEnvironment(args) {
|
|
|
348
356
|
status: operations.status,
|
|
349
357
|
stream: exclusive,
|
|
350
358
|
...(inner.session === undefined ? {} : { session: (id, options) => inner.session(id, options) }),
|
|
359
|
+
...(vendor.snapshots === undefined ? {} : {
|
|
360
|
+
checkpoint: (options) => createVendorSnapshot(vendor, sandbox.id, options),
|
|
361
|
+
fork: async (checkpoint, options) => {
|
|
362
|
+
assertForkableCheckpoint(checkpoint, vendor.name);
|
|
363
|
+
const { signal, ...request } = options ?? {};
|
|
364
|
+
return args.forkFromCheckpoint(checkpoint, Object.keys(request).length === 0 ? undefined : request, signal);
|
|
365
|
+
},
|
|
366
|
+
}),
|
|
351
367
|
...(inner.dispatch === undefined
|
|
352
368
|
? {}
|
|
353
369
|
: {
|
package/dist/sandbox/vendor.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ export interface BridgeSandboxCreateRequest {
|
|
|
61
61
|
lifetimeMs: number;
|
|
62
62
|
/** Vendor template, snapshot or image the caller chose; absent selects the published box image. */
|
|
63
63
|
environment?: string;
|
|
64
|
+
/** Snapshot id {@link BridgeVendorSnapshots.create} returned; restores that exact state. */
|
|
65
|
+
snapshot?: string;
|
|
64
66
|
image?: string;
|
|
65
67
|
resources?: ResourceRequest;
|
|
66
68
|
egress?: {
|
|
@@ -86,10 +88,33 @@ export interface BridgeVendorSupport {
|
|
|
86
88
|
/** Egress modes the vendor enforces; absent modes are refused. */
|
|
87
89
|
egress: readonly AgentEnvironmentEgressMode[];
|
|
88
90
|
}
|
|
91
|
+
/** A vendor snapshot of one sandbox's state, restorable through `create`. */
|
|
92
|
+
export interface BridgeVendorSnapshot {
|
|
93
|
+
/** Vendor snapshot identifier; restores via `BridgeSandboxCreateRequest.snapshot`. */
|
|
94
|
+
readonly id: string;
|
|
95
|
+
/** Vendor-reported creation time, when the vendor reports one. */
|
|
96
|
+
readonly createdAt?: string;
|
|
97
|
+
}
|
|
98
|
+
/** Snapshot lifecycle a vendor may expose beyond create, exec and destroy. */
|
|
99
|
+
export interface BridgeVendorSnapshots {
|
|
100
|
+
/**
|
|
101
|
+
* Snapshot a running sandbox. The returned id restores via
|
|
102
|
+
* `create({ snapshot: id })`, producing a sandbox that starts from the
|
|
103
|
+
* source's captured state. What a snapshot carries (filesystem only, or
|
|
104
|
+
* memory too) is vendor truth, reported nowhere else.
|
|
105
|
+
*/
|
|
106
|
+
create(sandboxId: string, options?: {
|
|
107
|
+
name?: string;
|
|
108
|
+
metadata?: Record<string, string>;
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
}): Promise<BridgeVendorSnapshot>;
|
|
111
|
+
}
|
|
89
112
|
export interface BridgeSandboxVendor {
|
|
90
113
|
/** Provider name reported on every environment, for example `e2b`. */
|
|
91
114
|
readonly name: string;
|
|
92
115
|
readonly support: BridgeVendorSupport;
|
|
116
|
+
/** Present only when the vendor snapshots running sandboxes. */
|
|
117
|
+
readonly snapshots?: BridgeVendorSnapshots;
|
|
93
118
|
create(request: BridgeSandboxCreateRequest): Promise<BridgeSandbox>;
|
|
94
119
|
/** `null` only when the vendor definitively reports no such sandbox; transport failures throw. */
|
|
95
120
|
connect(id: string, options?: {
|
package/package.json
CHANGED