@claxedo/sandbox-manager 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -26
- package/dist/checkpoint-manager.d.ts +29 -4
- package/dist/checkpoint-manager.mjs +133 -24
- package/dist/command.d.ts +9 -2
- package/dist/command.mjs +8 -8
- package/dist/contract.d.ts +687 -0
- package/dist/driver-billing.d.ts +29 -0
- package/dist/driver-catalog.d.ts +42 -7
- package/dist/driver-catalog.mjs +115 -161
- package/dist/drivers/boat-client.d.ts +95 -0
- package/dist/drivers/boat-named-snapshots.d.ts +31 -0
- package/dist/drivers/boat-template.d.ts +49 -0
- package/dist/drivers/boat-template.mjs +1196 -0
- package/dist/drivers/boat.d.ts +64 -0
- package/dist/drivers/boat.mjs +990 -0
- package/dist/drivers/cloudflare.d.ts +3 -3
- package/dist/drivers/cloudflare.mjs +208 -147
- package/dist/drivers/docker.d.ts +4 -3
- package/dist/drivers/docker.mjs +154 -126
- package/dist/drivers/local-brokering.d.ts +14 -0
- package/dist/drivers/local-brokering.mjs +1029 -0
- package/dist/drivers/modal.d.ts +2 -2
- package/dist/drivers/modal.mjs +121 -111
- package/dist/drivers/transient-error.d.ts +2 -0
- package/dist/drivers/vercel.d.ts +3 -2
- package/dist/drivers/vercel.mjs +151 -115
- package/dist/egress-policy.d.ts +134 -0
- package/dist/egress-warnings.d.ts +22 -0
- package/dist/garbage-collect.d.ts +14 -0
- package/dist/hosted-network-policy.d.ts +9 -2
- package/dist/hosted-network-policy.mjs +218 -1
- package/dist/image-name.d.ts +12 -2
- package/dist/image-name.mjs +14 -5
- package/dist/image.d.ts +1 -20
- package/dist/image.mjs +11 -88
- package/dist/index.d.ts +8 -578
- package/dist/index.mjs +1379 -320
- package/dist/lease-failure.d.ts +173 -0
- package/dist/lease-lifecycle.d.ts +65 -0
- package/dist/lease-retry-wait.d.ts +8 -0
- package/dist/lease-start.d.ts +21 -0
- package/dist/lease-types.d.ts +4 -3
- package/dist/manager.d.ts +2 -0
- package/dist/runtime-env.d.ts +4 -2
- package/dist/runtime-env.mjs +37 -2
- package/dist/runtime-snapshot.d.ts +49 -0
- package/dist/runtime-version.d.ts +9 -0
- package/dist/runtime-version.mjs +2 -0
- package/dist/service-url-exposure.d.ts +0 -1
- package/dist/service-url-exposure.mjs +0 -9
- package/dist/start-fresh.d.ts +17 -0
- package/dist/start-telemetry.d.ts +37 -0
- package/dist/stores/memory.d.ts +1 -1
- package/dist/stores/memory.mjs +63 -52
- package/package.json +26 -42
- package/dist/daytona-allow-list.d.ts +0 -16
- package/dist/daytona-allow-list.mjs +0 -11
- package/dist/drivers/box.d.ts +0 -52
- package/dist/drivers/box.mjs +0 -574
- package/dist/drivers/daytona.d.ts +0 -163
- package/dist/drivers/daytona.mjs +0 -683
- package/dist/drivers/exe.d.ts +0 -26
- package/dist/drivers/exe.mjs +0 -492
- package/dist/drivers/fetch-bridge.d.ts +0 -10
- package/dist/drivers/fetch-bridge.mjs +0 -126
- package/dist/lease-policy.d.ts +0 -52
- package/dist/lease-policy.mjs +0 -290
package/dist/drivers/exe.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { SandboxDriver, SandboxDriverEnsureInput } from "..";
|
|
2
|
-
type ExeVm = {
|
|
3
|
-
vm_name: string;
|
|
4
|
-
https_url?: string;
|
|
5
|
-
ssh_dest?: string;
|
|
6
|
-
status?: string;
|
|
7
|
-
/** Provider-side tags, written at create — the durable half of our labels. */
|
|
8
|
-
tags?: string[];
|
|
9
|
-
};
|
|
10
|
-
export type ExeSandboxDriverOptions = {
|
|
11
|
-
apiToken: string;
|
|
12
|
-
endpoint?: string;
|
|
13
|
-
image?: string;
|
|
14
|
-
runtimeCommand?: string;
|
|
15
|
-
workspaceDir?: string;
|
|
16
|
-
runtimePort?: number;
|
|
17
|
-
runner?: string;
|
|
18
|
-
operationTimeoutMs?: number;
|
|
19
|
-
healthTimeoutMs?: number;
|
|
20
|
-
healthIntervalMs?: number;
|
|
21
|
-
fetchImpl?: typeof fetch;
|
|
22
|
-
env?: (input: SandboxDriverEnsureInput, vm: ExeVm) => Record<string, string> | Promise<Record<string, string>>;
|
|
23
|
-
};
|
|
24
|
-
export declare function exeWorkspaceName(workspaceId: string, epoch: number): string;
|
|
25
|
-
export declare function createExeSandboxDriver(options: ExeSandboxDriverOptions): SandboxDriver;
|
|
26
|
-
export {};
|
package/dist/drivers/exe.mjs
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
// src/command.ts
|
|
2
|
-
function shell(input) {
|
|
3
|
-
return `'${input.replace(/'/g, `'"'"'`)}'`;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
// src/constants.ts
|
|
7
|
-
var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
|
|
8
|
-
|
|
9
|
-
// src/hosted-network-policy.ts
|
|
10
|
-
var SANDBOX_MODEL_PROVIDER_HOSTS = [
|
|
11
|
-
"api.anthropic.com",
|
|
12
|
-
"api.openai.com",
|
|
13
|
-
"generativelanguage.googleapis.com",
|
|
14
|
-
"openrouter.ai",
|
|
15
|
-
// Model catalog + engine config, fetched on runner startup.
|
|
16
|
-
"models.dev",
|
|
17
|
-
"opencode.ai"
|
|
18
|
-
];
|
|
19
|
-
var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
|
|
20
|
-
"registry.npmjs.org",
|
|
21
|
-
"nodejs.org",
|
|
22
|
-
"pypi.org",
|
|
23
|
-
"files.pythonhosted.org",
|
|
24
|
-
"crates.io",
|
|
25
|
-
"static.crates.io",
|
|
26
|
-
"index.crates.io",
|
|
27
|
-
"proxy.golang.org",
|
|
28
|
-
"sum.golang.org"
|
|
29
|
-
];
|
|
30
|
-
var SANDBOX_BASELINE_EGRESS_HOSTS = [
|
|
31
|
-
...SANDBOX_MODEL_PROVIDER_HOSTS,
|
|
32
|
-
...SANDBOX_PACKAGE_REGISTRY_HOSTS
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
// src/runtime-version.ts
|
|
36
|
-
var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
|
|
37
|
-
function workspaceRuntimeVersion() {
|
|
38
|
-
return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// src/image-name.ts
|
|
42
|
-
var SNAPSHOT_SCHEMA_VERSION = 8;
|
|
43
|
-
var DEFAULT_SANDBOX_IMAGE_REPOSITORY = "ghcr.io/kyashrathore/claxedo-sandbox";
|
|
44
|
-
function snapshotVersion(value) {
|
|
45
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
46
|
-
}
|
|
47
|
-
function sandboxImageRepository(env = process.env) {
|
|
48
|
-
const repository = env.CLAXEDO_SANDBOX_IMAGE_REPOSITORY?.trim();
|
|
49
|
-
return repository ? repository : DEFAULT_SANDBOX_IMAGE_REPOSITORY;
|
|
50
|
-
}
|
|
51
|
-
function buildIdSuffix(buildId, env) {
|
|
52
|
-
const id = (buildId ?? env.CLAXEDO_SANDBOX_BUILD_ID)?.trim();
|
|
53
|
-
return id ? `-${snapshotVersion(id)}` : "";
|
|
54
|
-
}
|
|
55
|
-
function defaultSandboxImage(version = workspaceRuntimeVersion(), buildId, env = process.env) {
|
|
56
|
-
return `${sandboxImageRepository(env)}:workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
|
|
57
|
-
}
|
|
58
|
-
function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env = process.env) {
|
|
59
|
-
return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ../sandbox-contract/dist/index.mjs
|
|
63
|
-
var sandboxDriverCredentialFields = {
|
|
64
|
-
exe: [{ key: "api_token", label: "API Token", secret: true }],
|
|
65
|
-
daytona: [{ key: "api_key", label: "API Key", secret: true }],
|
|
66
|
-
modal: [
|
|
67
|
-
{ key: "token_id", label: "Token ID", secret: true },
|
|
68
|
-
{ key: "token_secret", label: "Token Secret", secret: true }
|
|
69
|
-
],
|
|
70
|
-
vercel: [
|
|
71
|
-
{ key: "access_token", label: "Access Token", secret: true },
|
|
72
|
-
{ key: "team_id", label: "Team ID" },
|
|
73
|
-
{ key: "project_id", label: "Project ID" }
|
|
74
|
-
],
|
|
75
|
-
cloudflare: [
|
|
76
|
-
{ key: "api_token", label: "API Token", secret: true },
|
|
77
|
-
{ key: "worker_url", label: "Worker URL" }
|
|
78
|
-
],
|
|
79
|
-
box: [{ key: "api_key", label: "API Key", secret: true }],
|
|
80
|
-
docker: [{ key: "image", label: "Image" }]
|
|
81
|
-
};
|
|
82
|
-
var sandboxDriverLabels = {
|
|
83
|
-
exe: "exe.dev",
|
|
84
|
-
daytona: "Daytona",
|
|
85
|
-
modal: "Modal",
|
|
86
|
-
vercel: "Vercel",
|
|
87
|
-
cloudflare: "Cloudflare",
|
|
88
|
-
box: "Box",
|
|
89
|
-
docker: "Docker"
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// src/driver-catalog.ts
|
|
93
|
-
var sandboxDriverCatalog = {
|
|
94
|
-
exe: {
|
|
95
|
-
id: "exe",
|
|
96
|
-
label: sandboxDriverLabels.exe,
|
|
97
|
-
credentialFields: sandboxDriverCredentialFields.exe,
|
|
98
|
-
metadata: {
|
|
99
|
-
driverRunsIn: ["worker", "node"],
|
|
100
|
-
hostStopBehavior: "not-supported",
|
|
101
|
-
hostResumeBehavior: "same-host",
|
|
102
|
-
targetAccess: "relay",
|
|
103
|
-
secretBrokering: "none",
|
|
104
|
-
egressControl: "none",
|
|
105
|
-
persistence: {
|
|
106
|
-
resume: "same-sandbox",
|
|
107
|
-
capture: "none",
|
|
108
|
-
clone: true,
|
|
109
|
-
captureSource: "not-applicable",
|
|
110
|
-
retention: "provider-managed",
|
|
111
|
-
restoreMount: "same-resource"
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
daytona: {
|
|
116
|
-
id: "daytona",
|
|
117
|
-
label: sandboxDriverLabels.daytona,
|
|
118
|
-
credentialFields: sandboxDriverCredentialFields.daytona,
|
|
119
|
-
metadata: {
|
|
120
|
-
driverRunsIn: ["worker", "node"],
|
|
121
|
-
hostStopBehavior: "suspends-host",
|
|
122
|
-
hostResumeBehavior: "same-host",
|
|
123
|
-
targetAccess: "relay",
|
|
124
|
-
secretBrokering: "native",
|
|
125
|
-
// The driver translates names to domainAllowList, or addresses to networkAllowList.
|
|
126
|
-
egressControl: "hosts-and-cidrs",
|
|
127
|
-
persistence: {
|
|
128
|
-
resume: "same-sandbox",
|
|
129
|
-
capture: "filesystem",
|
|
130
|
-
clone: false,
|
|
131
|
-
captureSource: "preserved",
|
|
132
|
-
retention: "provider-managed",
|
|
133
|
-
restoreMount: "new-resource"
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
modal: {
|
|
138
|
-
id: "modal",
|
|
139
|
-
label: sandboxDriverLabels.modal,
|
|
140
|
-
credentialFields: sandboxDriverCredentialFields.modal,
|
|
141
|
-
metadata: {
|
|
142
|
-
driverRunsIn: ["node"],
|
|
143
|
-
hostStopBehavior: "terminates-host",
|
|
144
|
-
hostResumeBehavior: "replacement-host",
|
|
145
|
-
targetAccess: "relay",
|
|
146
|
-
secretBrokering: "none",
|
|
147
|
-
// The driver does not install Modal's domain allowlist or alpha sidecar.
|
|
148
|
-
egressControl: "none",
|
|
149
|
-
persistence: {
|
|
150
|
-
resume: "replacement-restore",
|
|
151
|
-
capture: "filesystem",
|
|
152
|
-
clone: false,
|
|
153
|
-
captureSource: "preserved",
|
|
154
|
-
retention: "provider-managed",
|
|
155
|
-
restoreMount: "new-resource"
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
vercel: {
|
|
160
|
-
id: "vercel",
|
|
161
|
-
label: sandboxDriverLabels.vercel,
|
|
162
|
-
credentialFields: sandboxDriverCredentialFields.vercel,
|
|
163
|
-
metadata: {
|
|
164
|
-
driverRunsIn: ["node"],
|
|
165
|
-
hostStopBehavior: "terminates-host",
|
|
166
|
-
hostResumeBehavior: "replacement-host",
|
|
167
|
-
targetAccess: "relay",
|
|
168
|
-
secretBrokering: "native",
|
|
169
|
-
// The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
|
|
170
|
-
egressControl: "hosts",
|
|
171
|
-
persistence: {
|
|
172
|
-
resume: "replacement-restore",
|
|
173
|
-
capture: "filesystem",
|
|
174
|
-
clone: false,
|
|
175
|
-
captureSource: "stopped",
|
|
176
|
-
retention: "explicit",
|
|
177
|
-
restoreMount: "new-resource"
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
cloudflare: {
|
|
182
|
-
id: "cloudflare",
|
|
183
|
-
label: sandboxDriverLabels.cloudflare,
|
|
184
|
-
credentialFields: sandboxDriverCredentialFields.cloudflare,
|
|
185
|
-
metadata: {
|
|
186
|
-
driverRunsIn: ["worker"],
|
|
187
|
-
hostStopBehavior: "not-supported",
|
|
188
|
-
hostResumeBehavior: "same-host",
|
|
189
|
-
targetAccess: "relay",
|
|
190
|
-
secretBrokering: "native",
|
|
191
|
-
// Credential handlers do not restrict unrelated destinations.
|
|
192
|
-
egressControl: "none",
|
|
193
|
-
persistence: {
|
|
194
|
-
resume: "replacement-restore",
|
|
195
|
-
capture: "directories",
|
|
196
|
-
clone: false,
|
|
197
|
-
captureSource: "preserved",
|
|
198
|
-
retention: "provider-managed",
|
|
199
|
-
restoreMount: "copy-on-write"
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
box: {
|
|
204
|
-
id: "box",
|
|
205
|
-
label: sandboxDriverLabels.box,
|
|
206
|
-
credentialFields: sandboxDriverCredentialFields.box,
|
|
207
|
-
metadata: {
|
|
208
|
-
driverRunsIn: ["node"],
|
|
209
|
-
hostStopBehavior: "suspends-host",
|
|
210
|
-
hostResumeBehavior: "same-host",
|
|
211
|
-
targetAccess: "relay",
|
|
212
|
-
secretBrokering: "none",
|
|
213
|
-
egressControl: "none",
|
|
214
|
-
persistence: {
|
|
215
|
-
resume: "same-sandbox",
|
|
216
|
-
capture: "none",
|
|
217
|
-
clone: false,
|
|
218
|
-
captureSource: "not-applicable",
|
|
219
|
-
retention: "provider-managed",
|
|
220
|
-
restoreMount: "same-resource"
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
docker: {
|
|
225
|
-
id: "docker",
|
|
226
|
-
label: sandboxDriverLabels.docker,
|
|
227
|
-
credentialFields: sandboxDriverCredentialFields.docker,
|
|
228
|
-
metadata: {
|
|
229
|
-
driverRunsIn: ["local"],
|
|
230
|
-
hostStopBehavior: "terminates-host",
|
|
231
|
-
hostResumeBehavior: "same-host",
|
|
232
|
-
targetAccess: "loopback",
|
|
233
|
-
secretBrokering: "none",
|
|
234
|
-
egressControl: "none",
|
|
235
|
-
persistence: {
|
|
236
|
-
resume: "replacement-restore",
|
|
237
|
-
capture: "same-resource",
|
|
238
|
-
clone: false,
|
|
239
|
-
captureSource: "preserved",
|
|
240
|
-
retention: "provider-managed",
|
|
241
|
-
restoreMount: "same-resource"
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// ../claxedo-helpers/dist/guards.mjs
|
|
248
|
-
function isRecord(value) {
|
|
249
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
250
|
-
}
|
|
251
|
-
function asRecord(value) {
|
|
252
|
-
return isRecord(value) ? value : void 0;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// src/json.ts
|
|
256
|
-
function text(input) {
|
|
257
|
-
return typeof input === "string" && input.length > 0 ? input : void 0;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// src/image.ts
|
|
261
|
-
var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
|
|
262
|
-
var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
|
|
263
|
-
var SNAPSHOT_NAME = process.env.CLAXEDO_SNAPSHOT_NAME || defaultSnapshotName();
|
|
264
|
-
|
|
265
|
-
// src/runtime-env.ts
|
|
266
|
-
function workspaceRuntimeTargetEnv(input) {
|
|
267
|
-
return {
|
|
268
|
-
WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
|
|
269
|
-
...input.hostId ? { WORKSPACE_RUNTIME_HOST_ID: input.hostId } : {},
|
|
270
|
-
WORKSPACE_RUNTIME_DIRECTORY: input.directory,
|
|
271
|
-
WORKSPACE_RUNTIME_PORT: String(input.port),
|
|
272
|
-
...input.host ? { WORKSPACE_RUNTIME_HOST: input.host } : {}
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
function workspaceRuntimeSourceEnv(input) {
|
|
276
|
-
if (!input.source || input.source.kind === "empty") return { WORKSPACE_RUNTIME_SOURCE_KIND: "empty" };
|
|
277
|
-
return {
|
|
278
|
-
WORKSPACE_RUNTIME_SOURCE_KIND: "git",
|
|
279
|
-
WORKSPACE_RUNTIME_GIT_REPO_URL: input.source.repoUrl,
|
|
280
|
-
...input.source.branch ? { WORKSPACE_RUNTIME_GIT_BRANCH: input.source.branch } : {}
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// src/drivers/exe.ts
|
|
285
|
-
var DEFAULT_ENDPOINT = "https://exe.dev/exec";
|
|
286
|
-
var DEFAULT_RUNTIME_COMMAND = "workspace-runtime";
|
|
287
|
-
var DEFAULT_WORKSPACE_DIR = "/workspace";
|
|
288
|
-
var DEFAULT_OPERATION_TIMEOUT_MS = 12e4;
|
|
289
|
-
var DEFAULT_EXE_APP_LABEL = "claxedo";
|
|
290
|
-
var DEFAULT_HEALTH_TIMEOUT_MS = 6e4;
|
|
291
|
-
var DEFAULT_HEALTH_INTERVAL_MS = 1e3;
|
|
292
|
-
function exeWorkspaceName(workspaceId, epoch) {
|
|
293
|
-
const slug = workspaceId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30) || "workspace";
|
|
294
|
-
let hash = 2166136261;
|
|
295
|
-
for (const char of workspaceId) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619);
|
|
296
|
-
return `claxedo-ws-${slug}-${(hash >>> 0).toString(36).slice(0, 8)}-g${epoch}`.slice(0, 63).replace(/-+$/g, "");
|
|
297
|
-
}
|
|
298
|
-
function createExeSandboxDriver(options) {
|
|
299
|
-
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
|
|
300
|
-
const image = options.image ?? SANDBOX_IMAGE;
|
|
301
|
-
const runtimeCommand = options.runtimeCommand ?? DEFAULT_RUNTIME_COMMAND;
|
|
302
|
-
const workspaceDir = options.workspaceDir ?? DEFAULT_WORKSPACE_DIR;
|
|
303
|
-
const operationTimeoutMs = options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;
|
|
304
|
-
const healthTimeoutMs = options.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS;
|
|
305
|
-
const healthIntervalMs = options.healthIntervalMs ?? DEFAULT_HEALTH_INTERVAL_MS;
|
|
306
|
-
const fetchImpl = options.fetchImpl ?? fetch;
|
|
307
|
-
const labels = /* @__PURE__ */ new Map();
|
|
308
|
-
async function api(command) {
|
|
309
|
-
const response = await fetchImpl(endpoint, {
|
|
310
|
-
method: "POST",
|
|
311
|
-
headers: {
|
|
312
|
-
Authorization: `Bearer ${options.apiToken}`,
|
|
313
|
-
"Content-Type": "text/plain; charset=utf-8"
|
|
314
|
-
},
|
|
315
|
-
body: command,
|
|
316
|
-
signal: AbortSignal.timeout(operationTimeoutMs)
|
|
317
|
-
});
|
|
318
|
-
const payload = await response.text();
|
|
319
|
-
const body = (payload ? asRecord(JSON.parse(payload)) : void 0) ?? {};
|
|
320
|
-
if (!response.ok || body.ok === false || typeof body.error === "string") {
|
|
321
|
-
const detail = text(body.error) ?? text(body.message) ?? response.statusText;
|
|
322
|
-
throw new Error(`exe.dev command failed (${response.status}): ${detail}`);
|
|
323
|
-
}
|
|
324
|
-
return body;
|
|
325
|
-
}
|
|
326
|
-
function vm(input) {
|
|
327
|
-
const value = asRecord(input);
|
|
328
|
-
if (!value || typeof value.vm_name !== "string") return void 0;
|
|
329
|
-
return {
|
|
330
|
-
vm_name: value.vm_name,
|
|
331
|
-
...typeof value.https_url === "string" ? { https_url: value.https_url } : {},
|
|
332
|
-
...typeof value.ssh_dest === "string" ? { ssh_dest: value.ssh_dest } : {},
|
|
333
|
-
...typeof value.status === "string" ? { status: value.status } : {},
|
|
334
|
-
...Array.isArray(value.tags) ? { tags: value.tags.filter((tag) => typeof tag === "string") } : {}
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function durableLabels(item) {
|
|
338
|
-
const cached = labels.get(item.vm_name);
|
|
339
|
-
if (cached) return cached;
|
|
340
|
-
const tags = item.tags ?? [];
|
|
341
|
-
if (!tags.includes("claxedo")) return void 0;
|
|
342
|
-
const workspaceId = tags.find((tag) => tag.startsWith("workspace-"))?.slice("workspace-".length);
|
|
343
|
-
if (!workspaceId) return void 0;
|
|
344
|
-
const epoch = item.vm_name.match(/-g(\d+)$/)?.[1];
|
|
345
|
-
if (!epoch) return void 0;
|
|
346
|
-
return { app: DEFAULT_EXE_APP_LABEL, workspaceId, epoch };
|
|
347
|
-
}
|
|
348
|
-
async function inspect(name) {
|
|
349
|
-
const result = await api(`ls ${shell(name)}`);
|
|
350
|
-
return (Array.isArray(result.vms) ? result.vms : []).map(vm).find((candidate) => candidate?.vm_name === name);
|
|
351
|
-
}
|
|
352
|
-
function target(input) {
|
|
353
|
-
const url = input.vm.https_url ?? `https://${input.vm.vm_name}.exe.xyz`;
|
|
354
|
-
return {
|
|
355
|
-
...input.workspaceId ? { workspaceId: input.workspaceId } : {},
|
|
356
|
-
sandboxId: input.vm.vm_name,
|
|
357
|
-
url,
|
|
358
|
-
hostId: input.hostId ?? input.vm.vm_name,
|
|
359
|
-
driverResourceId: input.vm.vm_name,
|
|
360
|
-
labels: input.labels ?? labels.get(input.vm.vm_name),
|
|
361
|
-
driver: { id: "exe", resourceId: input.vm.vm_name }
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
async function execute(name, command) {
|
|
365
|
-
const body = await api(`ssh ${shell(name)} ${shell(command)}`);
|
|
366
|
-
const result = asRecord(body.result) ?? body;
|
|
367
|
-
return {
|
|
368
|
-
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
369
|
-
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
370
|
-
exitCode: typeof result.exit_code === "number" ? result.exit_code : typeof result.exitCode === "number" ? result.exitCode : 0
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
async function executeOrThrow(name, command, label) {
|
|
374
|
-
const result = await execute(name, command);
|
|
375
|
-
if (result.exitCode !== 0) {
|
|
376
|
-
throw new Error(`exe.dev ${name} ${label} failed (${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
377
|
-
}
|
|
378
|
-
return result;
|
|
379
|
-
}
|
|
380
|
-
function runtimePort(input) {
|
|
381
|
-
return input.workspaceRuntimePort ?? options.runtimePort ?? DEFAULT_WORKSPACE_RUNTIME_PORT;
|
|
382
|
-
}
|
|
383
|
-
function directory(input) {
|
|
384
|
-
return input.workspaceRoot ?? workspaceDir;
|
|
385
|
-
}
|
|
386
|
-
async function boot(vm2, input, hostId) {
|
|
387
|
-
const env = {
|
|
388
|
-
...workspaceRuntimeTargetEnv({
|
|
389
|
-
workspaceId: input.workspaceId,
|
|
390
|
-
hostId,
|
|
391
|
-
directory: directory(input),
|
|
392
|
-
port: runtimePort(input),
|
|
393
|
-
host: "0.0.0.0"
|
|
394
|
-
}),
|
|
395
|
-
...workspaceRuntimeSourceEnv({ source: input.source }),
|
|
396
|
-
...input.env,
|
|
397
|
-
...await options.env?.(input, vm2)
|
|
398
|
-
};
|
|
399
|
-
if (options.runner) env.WORKSPACE_RUNTIME_RUNNER = options.runner;
|
|
400
|
-
const envArgs = Object.entries(env).map(([key, value]) => `${key}=${shell(value)}`).join(" ");
|
|
401
|
-
await executeOrThrow(
|
|
402
|
-
vm2.vm_name,
|
|
403
|
-
`mkdir -p ${shell(directory(input))} && (pkill -f ${shell(runtimeCommand)} || true) && cd ${shell(directory(input))} && nohup env ${envArgs} ${runtimeCommand} > .claxedo-workspace-runtime.log 2>&1 < /dev/null &`,
|
|
404
|
-
"runtime start"
|
|
405
|
-
);
|
|
406
|
-
const until = Date.now() + healthTimeoutMs;
|
|
407
|
-
while (Date.now() <= until) {
|
|
408
|
-
const health = await execute(
|
|
409
|
-
vm2.vm_name,
|
|
410
|
-
`curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:${runtimePort(input)}/global/health || true`
|
|
411
|
-
);
|
|
412
|
-
if (health.stdout.trim() === "200") {
|
|
413
|
-
labels.set(vm2.vm_name, input.labels);
|
|
414
|
-
return target({ vm: vm2, workspaceId: input.workspaceId, hostId, labels: input.labels });
|
|
415
|
-
}
|
|
416
|
-
await new Promise((resolve) => setTimeout(resolve, healthIntervalMs));
|
|
417
|
-
}
|
|
418
|
-
throw new Error(`exe.dev ${vm2.vm_name} workspace runtime did not become healthy`);
|
|
419
|
-
}
|
|
420
|
-
function assertNetwork(input) {
|
|
421
|
-
if (input.net && input.net.mode !== "allow-all") {
|
|
422
|
-
throw new Error("exe.dev SandboxDriver cannot enforce host-based network policy");
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
async function ensureHost(input) {
|
|
426
|
-
assertNetwork(input);
|
|
427
|
-
const name = exeWorkspaceName(input.workspaceId, input.epoch);
|
|
428
|
-
const existing = await inspect(name);
|
|
429
|
-
const created = existing ?? vm(await api(
|
|
430
|
-
`new --name=${shell(name)} --image=${shell(input.bootSource?.kind === "image" ? input.bootSource.image : image)} --tag=claxedo --tag=${shell(`workspace-${input.workspaceId}`)} ${input.hostId ? `--tag=${shell(`host-${input.hostId}`)} ` : ""}--no-email`
|
|
431
|
-
).then((result) => result.vm ?? result));
|
|
432
|
-
if (!created) throw new Error(`exe.dev did not return VM metadata for ${name}`);
|
|
433
|
-
return boot(created, input, input.hostId ?? name);
|
|
434
|
-
}
|
|
435
|
-
async function resumeHost(input) {
|
|
436
|
-
assertNetwork(input.ensure);
|
|
437
|
-
const name = input.lease.sandboxId ?? exeWorkspaceName(input.ensure.workspaceId, input.lease.epoch);
|
|
438
|
-
const existing = await inspect(name);
|
|
439
|
-
if (!existing) return ensureHost(input.ensure);
|
|
440
|
-
return boot(existing, input.ensure, input.ensure.hostId ?? input.lease.hostId ?? name);
|
|
441
|
-
}
|
|
442
|
-
return {
|
|
443
|
-
id: "exe",
|
|
444
|
-
metadata: sandboxDriverCatalog.exe.metadata,
|
|
445
|
-
ensureHost,
|
|
446
|
-
resumeHost,
|
|
447
|
-
// Provider-state enumeration for `garbageCollect()`. Labels come from
|
|
448
|
-
// DURABLE provider state (tags + the VM name's epoch suffix), not the
|
|
449
|
-
// in-process cache — see `durableLabels`. A VM whose labels cannot be
|
|
450
|
-
// recovered is returned WITHOUT them so GC skips it as unlabeled rather
|
|
451
|
-
// than acting on a guess; it is never destroyed on partial identity.
|
|
452
|
-
async list() {
|
|
453
|
-
const result = await api("ls claxedo-ws-*");
|
|
454
|
-
return (Array.isArray(result.vms) ? result.vms : []).map(vm).filter((item) => !!item && item.vm_name.startsWith("claxedo-ws-")).map((item) => {
|
|
455
|
-
const recovered = durableLabels(item);
|
|
456
|
-
const hostId = item.tags?.find((tag) => tag.startsWith("host-"))?.slice("host-".length);
|
|
457
|
-
return target({
|
|
458
|
-
vm: item,
|
|
459
|
-
...recovered?.workspaceId ? { workspaceId: recovered.workspaceId } : {},
|
|
460
|
-
...recovered ? { labels: recovered } : {},
|
|
461
|
-
...hostId ? { hostId } : {}
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
},
|
|
465
|
-
async inspect(input) {
|
|
466
|
-
const current = await inspect(input.sandboxId);
|
|
467
|
-
return current ? target({ vm: current, workspaceId: input.workspaceId, hostId: input.hostId }) : void 0;
|
|
468
|
-
},
|
|
469
|
-
exec: (input, command) => execute(input.sandboxId, command),
|
|
470
|
-
async stop(input) {
|
|
471
|
-
await executeOrThrow(input.sandboxId, `pkill -f ${shell(runtimeCommand)} || true`, "runtime stop");
|
|
472
|
-
},
|
|
473
|
-
async destroy(input) {
|
|
474
|
-
await api(`rm ${shell(input.sandboxId)}`);
|
|
475
|
-
labels.delete(input.sandboxId);
|
|
476
|
-
},
|
|
477
|
-
async clone(input, clone) {
|
|
478
|
-
const name = exeWorkspaceName(clone.name, 1);
|
|
479
|
-
const copied = vm(await api(`cp ${shell(input.sandboxId)} ${shell(name)} --copy-tags`).then(
|
|
480
|
-
(result) => result.vm ?? result
|
|
481
|
-
));
|
|
482
|
-
if (!copied) throw new Error(`exe.dev did not return cloned VM metadata for ${name}`);
|
|
483
|
-
return target({ vm: copied });
|
|
484
|
-
},
|
|
485
|
-
async touch() {
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
export {
|
|
490
|
-
createExeSandboxDriver,
|
|
491
|
-
exeWorkspaceName
|
|
492
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { SandboxDriver } from "..";
|
|
2
|
-
export type FetchBridgeSandboxDriverOptions = {
|
|
3
|
-
id: string;
|
|
4
|
-
baseUrl: string;
|
|
5
|
-
token?: string;
|
|
6
|
-
fetch?: typeof fetch;
|
|
7
|
-
autoStopMs: number;
|
|
8
|
-
autoDeleteMs: number;
|
|
9
|
-
};
|
|
10
|
-
export declare function createFetchBridgeSandboxDriver(options: FetchBridgeSandboxDriverOptions): SandboxDriver;
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// ../claxedo-helpers/dist/guards.mjs
|
|
2
|
-
function isRecord(value) {
|
|
3
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
|
-
}
|
|
5
|
-
function asRecord(value) {
|
|
6
|
-
return isRecord(value) ? value : void 0;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// src/drivers/fetch-bridge.ts
|
|
10
|
-
function cleanUrl(input) {
|
|
11
|
-
return input.replace(/\/+$/, "");
|
|
12
|
-
}
|
|
13
|
-
function assertLifecycleSettings(options) {
|
|
14
|
-
if (!Number.isFinite(options.autoStopMs) || options.autoStopMs <= 0) {
|
|
15
|
-
throw new Error("Fetch bridge sandbox driver requires a finite auto-stop/sleep policy");
|
|
16
|
-
}
|
|
17
|
-
if (!Number.isFinite(options.autoDeleteMs) || options.autoDeleteMs <= 0) {
|
|
18
|
-
throw new Error("Fetch bridge sandbox driver requires a finite auto-delete policy");
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function stringMap(input) {
|
|
22
|
-
const row = asRecord(input);
|
|
23
|
-
if (!row) return void 0;
|
|
24
|
-
const entries = Object.entries(row).filter((entry) => typeof entry[1] === "string");
|
|
25
|
-
return Object.fromEntries(entries);
|
|
26
|
-
}
|
|
27
|
-
function headers(token) {
|
|
28
|
-
return {
|
|
29
|
-
"content-type": "application/json",
|
|
30
|
-
...token ? { authorization: `Bearer ${token}` } : {}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
function target(input) {
|
|
34
|
-
const row = asRecord(input);
|
|
35
|
-
if (!row) throw new Error("Fetch bridge sandbox driver returned an invalid response");
|
|
36
|
-
if (row.status === "provisioning") {
|
|
37
|
-
return {
|
|
38
|
-
provisioning: true,
|
|
39
|
-
retryAfterMs: typeof row.retryAfterMs === "number" ? row.retryAfterMs : 2e3
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
if (typeof row.sandboxId !== "string" || typeof row.url !== "string" || typeof row.hostId !== "string") {
|
|
43
|
-
throw new Error("Fetch bridge sandbox driver returned an incomplete target");
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
workspaceId: typeof row.workspaceId === "string" ? row.workspaceId : void 0,
|
|
47
|
-
sandboxId: row.sandboxId,
|
|
48
|
-
url: row.url,
|
|
49
|
-
hostId: row.hostId,
|
|
50
|
-
driverResourceId: typeof row.driverResourceId === "string" ? row.driverResourceId : void 0,
|
|
51
|
-
labels: stringMap(row.labels)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function bridgeTarget(input) {
|
|
55
|
-
const result = target(input);
|
|
56
|
-
if ("provisioning" in result) throw new Error("Fetch bridge sandbox driver returned provisioning status in a target list");
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
|
-
function targetList(input) {
|
|
60
|
-
const targets = asRecord(input)?.targets;
|
|
61
|
-
const rows = Array.isArray(input) ? input : Array.isArray(targets) ? targets : void 0;
|
|
62
|
-
if (!rows) throw new Error("Fetch bridge sandbox driver returned an invalid target list");
|
|
63
|
-
return rows.map(bridgeTarget);
|
|
64
|
-
}
|
|
65
|
-
async function post(options, path, body) {
|
|
66
|
-
const res = await (options.fetch ?? fetch)(`${cleanUrl(options.baseUrl)}${path}`, {
|
|
67
|
-
method: "POST",
|
|
68
|
-
headers: headers(options.token),
|
|
69
|
-
body: JSON.stringify(body)
|
|
70
|
-
});
|
|
71
|
-
if (!res.ok) throw new Error(`Fetch bridge sandbox driver ${path} failed with ${res.status}`);
|
|
72
|
-
return res.json();
|
|
73
|
-
}
|
|
74
|
-
function createFetchBridgeSandboxDriver(options) {
|
|
75
|
-
assertLifecycleSettings(options);
|
|
76
|
-
async function ensureHost(input) {
|
|
77
|
-
return target(await post(options, "/runtime/ensure", {
|
|
78
|
-
workspaceId: input.workspaceId,
|
|
79
|
-
homeRegion: input.homeRegion,
|
|
80
|
-
epoch: input.epoch,
|
|
81
|
-
labels: input.labels,
|
|
82
|
-
hostControl: {
|
|
83
|
-
autoStopMs: options.autoStopMs,
|
|
84
|
-
autoDeleteMs: options.autoDeleteMs
|
|
85
|
-
}
|
|
86
|
-
}));
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
id: options.id,
|
|
90
|
-
metadata: {
|
|
91
|
-
driverRunsIn: ["worker", "node"],
|
|
92
|
-
hostStopBehavior: "suspends-host",
|
|
93
|
-
hostResumeBehavior: "same-host",
|
|
94
|
-
targetAccess: "relay",
|
|
95
|
-
secretBrokering: "none",
|
|
96
|
-
egressControl: "none",
|
|
97
|
-
persistence: {
|
|
98
|
-
resume: "same-sandbox",
|
|
99
|
-
capture: "none",
|
|
100
|
-
clone: false,
|
|
101
|
-
captureSource: "not-applicable",
|
|
102
|
-
retention: "not-applicable",
|
|
103
|
-
restoreMount: "not-applicable"
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
ensureHost,
|
|
107
|
-
async list() {
|
|
108
|
-
return targetList(await post(options, "/runtime/list", {}));
|
|
109
|
-
},
|
|
110
|
-
async touch(input) {
|
|
111
|
-
await post(options, "/runtime/touch", input);
|
|
112
|
-
},
|
|
113
|
-
async stop(input) {
|
|
114
|
-
await post(options, "/runtime/stop", input);
|
|
115
|
-
},
|
|
116
|
-
async suspend(input) {
|
|
117
|
-
await post(options, "/runtime/stop", input);
|
|
118
|
-
},
|
|
119
|
-
async destroy(input) {
|
|
120
|
-
await post(options, "/runtime/destroy", input);
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
export {
|
|
125
|
-
createFetchBridgeSandboxDriver
|
|
126
|
-
};
|