@claxedo/sandbox-manager 0.6.0 → 0.9.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.
Files changed (41) hide show
  1. package/README.md +60 -15
  2. package/dist/checkpoint-manager.d.ts +47 -0
  3. package/dist/checkpoint-manager.mjs +116 -0
  4. package/dist/daytona-allow-list.d.ts +11 -0
  5. package/dist/daytona-allow-list.mjs +5 -1
  6. package/dist/driver-catalog.d.ts +5 -52
  7. package/dist/driver-catalog.mjs +263 -121
  8. package/dist/drivers/box.mjs +309 -39
  9. package/dist/drivers/cloudflare.d.ts +18 -7
  10. package/dist/drivers/cloudflare.mjs +423 -34
  11. package/dist/drivers/daytona.d.ts +82 -13
  12. package/dist/drivers/daytona.mjs +495 -45
  13. package/dist/drivers/docker.mjs +284 -38
  14. package/dist/drivers/exe.d.ts +26 -0
  15. package/dist/drivers/exe.mjs +492 -0
  16. package/dist/drivers/fetch-bridge.mjs +29 -5
  17. package/dist/drivers/modal.d.ts +2 -5
  18. package/dist/drivers/modal.mjs +296 -46
  19. package/dist/drivers/transient-error.d.ts +11 -0
  20. package/dist/drivers/vercel.d.ts +27 -11
  21. package/dist/drivers/vercel.mjs +339 -59
  22. package/dist/hosted-network-policy.d.ts +87 -0
  23. package/dist/hosted-network-policy.mjs +61 -0
  24. package/dist/image-name.mjs +34 -0
  25. package/dist/image.d.ts +18 -3
  26. package/dist/image.mjs +47 -32
  27. package/dist/index.d.ts +281 -17
  28. package/dist/index.mjs +393 -26
  29. package/dist/json.d.ts +13 -0
  30. package/dist/lease-policy.d.ts +6 -3
  31. package/dist/lease-policy.mjs +213 -54
  32. package/dist/lease-types.d.ts +17 -3
  33. package/dist/runtime-env.d.ts +12 -1
  34. package/dist/runtime-env.mjs +14 -0
  35. package/dist/runtime-version.mjs +1 -9
  36. package/dist/service-url-exposure.d.ts +1 -2
  37. package/dist/service-url-exposure.mjs +1 -17
  38. package/dist/stores/memory.mjs +39 -4
  39. package/package.json +67 -51
  40. package/dist/drivers/cloudflare-egress.d.ts +0 -40
  41. package/dist/drivers/cloudflare-egress.mjs +0 -113
@@ -5,7 +5,31 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { promisify } from "node:util";
7
7
 
8
+ // ../claxedo-helpers/dist/string.mjs
9
+ function isString(value) {
10
+ return typeof value === "string";
11
+ }
12
+ function trimToUndefined(value) {
13
+ if (!isString(value))
14
+ return;
15
+ const trimmed = value.trim();
16
+ return trimmed.length > 0 ? trimmed : void 0;
17
+ }
18
+
8
19
  // src/runtime-env.ts
20
+ function workspaceRuntimeBootEnv(input) {
21
+ const env = {
22
+ ...workspaceRuntimeTargetEnv(input),
23
+ ...workspaceRuntimeSourceEnv(input),
24
+ ...input.env
25
+ };
26
+ if (input.runner) env.WORKSPACE_RUNTIME_RUNNER = input.runner;
27
+ if (input.controlEnv?.relayJwksUrl) env.WORKSPACE_RUNTIME_RELAY_JWKS_URL = input.controlEnv.relayJwksUrl;
28
+ if (input.controlEnv?.relayVerifyPem) env.WORKSPACE_RUNTIME_RELAY_HOST_VERIFY_PEM = input.controlEnv.relayVerifyPem;
29
+ if (input.controlEnv?.managementJwksUrl) env.WORKSPACE_RUNTIME_MANAGEMENT_JWKS_URL = input.controlEnv.managementJwksUrl;
30
+ if (input.controlEnv?.sessionAuthorityUrl) env.WORKSPACE_RUNTIME_SESSION_AUTHORITY_URL = input.controlEnv.sessionAuthorityUrl;
31
+ return env;
32
+ }
9
33
  function workspaceRuntimeTargetEnv(input) {
10
34
  return {
11
35
  WORKSPACE_RUNTIME_WORKSPACE_ID: input.workspaceId,
@@ -32,21 +56,10 @@ function shell(input) {
32
56
  // src/constants.ts
33
57
  var DEFAULT_WORKSPACE_RUNTIME_PORT = 2593;
34
58
 
35
- // src/image.ts
36
- import { Image, DaytonaNotFoundError } from "@daytona/sdk";
37
-
38
59
  // src/runtime-version.ts
39
60
  var DEFAULT_WORKSPACE_RUNTIME_VERSION = "0.5.2";
40
- var versionCache;
41
61
  function workspaceRuntimeVersion() {
42
- if (versionCache) return versionCache;
43
- const envVersion = process.env.WORKSPACE_RUNTIME_VERSION;
44
- if (envVersion) {
45
- versionCache = envVersion;
46
- return versionCache;
47
- }
48
- versionCache = DEFAULT_WORKSPACE_RUNTIME_VERSION;
49
- return versionCache;
62
+ return process.env.WORKSPACE_RUNTIME_VERSION || DEFAULT_WORKSPACE_RUNTIME_VERSION;
50
63
  }
51
64
 
52
65
  // src/image-name.ts
@@ -70,11 +83,250 @@ function defaultSnapshotName(version = workspaceRuntimeVersion(), buildId, env =
70
83
  return `claxedo-workspace-runtime-${snapshotVersion(version)}${buildIdSuffix(buildId, env)}-v${SNAPSHOT_SCHEMA_VERSION}`;
71
84
  }
72
85
 
86
+ // ../claxedo-helpers/dist/guards.mjs
87
+ function isRecord(value) {
88
+ return value !== null && typeof value === "object" && !Array.isArray(value);
89
+ }
90
+
91
+ // src/json.ts
92
+ function num(input) {
93
+ return typeof input === "number" ? input : void 0;
94
+ }
95
+
73
96
  // src/image.ts
74
97
  var SANDBOX_IMAGE_REPOSITORY = sandboxImageRepository();
75
98
  var SANDBOX_IMAGE = process.env.CLAXEDO_SANDBOX_IMAGE || defaultSandboxImage();
76
99
  var SNAPSHOT_NAME = process.env.CLAXEDO_SNAPSHOT_NAME || defaultSnapshotName();
77
100
 
101
+ // src/hosted-network-policy.ts
102
+ var SANDBOX_MODEL_PROVIDER_HOSTS = [
103
+ "api.anthropic.com",
104
+ "api.openai.com",
105
+ "generativelanguage.googleapis.com",
106
+ "openrouter.ai",
107
+ // Model catalog + engine config, fetched on runner startup.
108
+ "models.dev",
109
+ "opencode.ai"
110
+ ];
111
+ var SANDBOX_PACKAGE_REGISTRY_HOSTS = [
112
+ "registry.npmjs.org",
113
+ "nodejs.org",
114
+ "pypi.org",
115
+ "files.pythonhosted.org",
116
+ "crates.io",
117
+ "static.crates.io",
118
+ "index.crates.io",
119
+ "proxy.golang.org",
120
+ "sum.golang.org"
121
+ ];
122
+ var SANDBOX_BASELINE_EGRESS_HOSTS = [
123
+ ...SANDBOX_MODEL_PROVIDER_HOSTS,
124
+ ...SANDBOX_PACKAGE_REGISTRY_HOSTS
125
+ ];
126
+
127
+ // ../sandbox-contract/dist/index.mjs
128
+ var sandboxDriverCredentialFields = {
129
+ exe: [{ key: "api_token", label: "API Token", secret: true }],
130
+ daytona: [{ key: "api_key", label: "API Key", secret: true }],
131
+ modal: [
132
+ { key: "token_id", label: "Token ID", secret: true },
133
+ { key: "token_secret", label: "Token Secret", secret: true }
134
+ ],
135
+ vercel: [
136
+ { key: "access_token", label: "Access Token", secret: true },
137
+ { key: "team_id", label: "Team ID" },
138
+ { key: "project_id", label: "Project ID" }
139
+ ],
140
+ cloudflare: [
141
+ { key: "api_token", label: "API Token", secret: true },
142
+ { key: "worker_url", label: "Worker URL" }
143
+ ],
144
+ box: [{ key: "api_key", label: "API Key", secret: true }],
145
+ docker: [{ key: "image", label: "Image" }]
146
+ };
147
+ var sandboxDriverLabels = {
148
+ exe: "exe.dev",
149
+ daytona: "Daytona",
150
+ modal: "Modal",
151
+ vercel: "Vercel",
152
+ cloudflare: "Cloudflare",
153
+ box: "Box",
154
+ docker: "Docker"
155
+ };
156
+
157
+ // src/driver-catalog.ts
158
+ var sandboxDriverCatalog = {
159
+ exe: {
160
+ id: "exe",
161
+ label: sandboxDriverLabels.exe,
162
+ credentialFields: sandboxDriverCredentialFields.exe,
163
+ metadata: {
164
+ driverRunsIn: ["worker", "node"],
165
+ hostStopBehavior: "not-supported",
166
+ hostResumeBehavior: "same-host",
167
+ targetAccess: "relay",
168
+ secretBrokering: "none",
169
+ egressControl: "none",
170
+ persistence: {
171
+ resume: "same-sandbox",
172
+ capture: "none",
173
+ clone: true,
174
+ captureSource: "not-applicable",
175
+ retention: "provider-managed",
176
+ restoreMount: "same-resource"
177
+ }
178
+ }
179
+ },
180
+ daytona: {
181
+ id: "daytona",
182
+ label: sandboxDriverLabels.daytona,
183
+ credentialFields: sandboxDriverCredentialFields.daytona,
184
+ metadata: {
185
+ driverRunsIn: ["worker", "node"],
186
+ hostStopBehavior: "suspends-host",
187
+ hostResumeBehavior: "same-host",
188
+ targetAccess: "relay",
189
+ secretBrokering: "native",
190
+ // The driver translates names to domainAllowList, or addresses to networkAllowList.
191
+ egressControl: "hosts-and-cidrs",
192
+ persistence: {
193
+ resume: "same-sandbox",
194
+ capture: "filesystem",
195
+ clone: false,
196
+ captureSource: "preserved",
197
+ retention: "provider-managed",
198
+ restoreMount: "new-resource"
199
+ }
200
+ }
201
+ },
202
+ modal: {
203
+ id: "modal",
204
+ label: sandboxDriverLabels.modal,
205
+ credentialFields: sandboxDriverCredentialFields.modal,
206
+ metadata: {
207
+ driverRunsIn: ["node"],
208
+ hostStopBehavior: "terminates-host",
209
+ hostResumeBehavior: "replacement-host",
210
+ targetAccess: "relay",
211
+ secretBrokering: "none",
212
+ // The driver does not install Modal's domain allowlist or alpha sidecar.
213
+ egressControl: "none",
214
+ persistence: {
215
+ resume: "replacement-restore",
216
+ capture: "filesystem",
217
+ clone: false,
218
+ captureSource: "preserved",
219
+ retention: "provider-managed",
220
+ restoreMount: "new-resource"
221
+ }
222
+ }
223
+ },
224
+ vercel: {
225
+ id: "vercel",
226
+ label: sandboxDriverLabels.vercel,
227
+ credentialFields: sandboxDriverCredentialFields.vercel,
228
+ metadata: {
229
+ driverRunsIn: ["node"],
230
+ hostStopBehavior: "terminates-host",
231
+ hostResumeBehavior: "replacement-host",
232
+ targetAccess: "relay",
233
+ secretBrokering: "native",
234
+ // The driver translates hosts only; the SDK's subnet rules are not wired to net.cidrs.
235
+ egressControl: "hosts",
236
+ persistence: {
237
+ resume: "replacement-restore",
238
+ capture: "filesystem",
239
+ clone: false,
240
+ captureSource: "stopped",
241
+ retention: "explicit",
242
+ restoreMount: "new-resource"
243
+ }
244
+ }
245
+ },
246
+ cloudflare: {
247
+ id: "cloudflare",
248
+ label: sandboxDriverLabels.cloudflare,
249
+ credentialFields: sandboxDriverCredentialFields.cloudflare,
250
+ metadata: {
251
+ driverRunsIn: ["worker"],
252
+ hostStopBehavior: "not-supported",
253
+ hostResumeBehavior: "same-host",
254
+ targetAccess: "relay",
255
+ secretBrokering: "native",
256
+ // Credential handlers do not restrict unrelated destinations.
257
+ egressControl: "none",
258
+ persistence: {
259
+ resume: "replacement-restore",
260
+ capture: "directories",
261
+ clone: false,
262
+ captureSource: "preserved",
263
+ retention: "provider-managed",
264
+ restoreMount: "copy-on-write"
265
+ }
266
+ }
267
+ },
268
+ box: {
269
+ id: "box",
270
+ label: sandboxDriverLabels.box,
271
+ credentialFields: sandboxDriverCredentialFields.box,
272
+ metadata: {
273
+ driverRunsIn: ["node"],
274
+ hostStopBehavior: "suspends-host",
275
+ hostResumeBehavior: "same-host",
276
+ targetAccess: "relay",
277
+ secretBrokering: "none",
278
+ egressControl: "none",
279
+ persistence: {
280
+ resume: "same-sandbox",
281
+ capture: "none",
282
+ clone: false,
283
+ captureSource: "not-applicable",
284
+ retention: "provider-managed",
285
+ restoreMount: "same-resource"
286
+ }
287
+ }
288
+ },
289
+ docker: {
290
+ id: "docker",
291
+ label: sandboxDriverLabels.docker,
292
+ credentialFields: sandboxDriverCredentialFields.docker,
293
+ metadata: {
294
+ driverRunsIn: ["local"],
295
+ hostStopBehavior: "terminates-host",
296
+ hostResumeBehavior: "same-host",
297
+ targetAccess: "loopback",
298
+ secretBrokering: "none",
299
+ egressControl: "none",
300
+ persistence: {
301
+ resume: "replacement-restore",
302
+ capture: "same-resource",
303
+ clone: false,
304
+ captureSource: "preserved",
305
+ retention: "provider-managed",
306
+ restoreMount: "same-resource"
307
+ }
308
+ }
309
+ }
310
+ };
311
+
312
+ // src/drivers/transient-error.ts
313
+ function textOf(input) {
314
+ return typeof input === "string" ? input : "";
315
+ }
316
+ function driverErrorSignals(err) {
317
+ const shaped = isRecord(err) ? err : {};
318
+ const nested = isRecord(shaped.response) ? shaped.response : {};
319
+ return {
320
+ status: num(nested.status) ?? num(shaped.status),
321
+ text: `${textOf(shaped.code)} ${textOf(shaped.name)} ${textOf(shaped.message)}`.toLowerCase()
322
+ };
323
+ }
324
+ function isTransientDriverError(err, markers) {
325
+ const { status, text: text2 } = driverErrorSignals(err);
326
+ if (status !== void 0 && status >= 500) return true;
327
+ return markers.some((marker) => text2.includes(marker));
328
+ }
329
+
78
330
  // src/drivers/docker.ts
79
331
  var execFileAsync = promisify(execFile);
80
332
  var DEFAULT_OPERATION_TIMEOUT_MS = 6e4;
@@ -85,13 +337,13 @@ var DEFAULT_WORKSPACE_DIR = "/workspace";
85
337
  var DOCKER_LABEL = "claxedo";
86
338
  var AUTH_STAGE_DIR = "/tmp/claxedo-auth";
87
339
  function dockerSandboxSyncLocalAuth(env = process.env) {
88
- return !disabled(env.CLAXEDO_DOCKER_SANDBOX_SYNC_LOCAL_AUTH);
340
+ return enabledFlag(env.CLAXEDO_DOCKER_SANDBOX_SYNC_LOCAL_AUTH);
89
341
  }
90
342
  function dockerSandboxAuthHome(env = process.env) {
91
- return clean(env.CLAXEDO_DOCKER_SANDBOX_AUTH_HOME) ?? clean(env.HOME) ?? os.homedir();
343
+ return trimToUndefined(env.CLAXEDO_DOCKER_SANDBOX_AUTH_HOME) ?? trimToUndefined(env.HOME) ?? os.homedir();
92
344
  }
93
345
  function dockerSandboxImageFromEnv(env = process.env) {
94
- return clean(env.CLAXEDO_DOCKER_SANDBOX_IMAGE) ?? clean(env.CLAXEDO_SANDBOX_IMAGE) ?? SANDBOX_IMAGE;
346
+ return trimToUndefined(env.CLAXEDO_DOCKER_SANDBOX_IMAGE) ?? trimToUndefined(env.CLAXEDO_SANDBOX_IMAGE) ?? SANDBOX_IMAGE;
95
347
  }
96
348
  function parseDockerPort(input) {
97
349
  const line = input.trim().split(/\r?\n/)[0]?.trim();
@@ -122,12 +374,8 @@ function dockerLocalAuthCandidates(home) {
122
374
  }
123
375
  ];
124
376
  }
125
- function clean(input) {
126
- const txt = input?.trim();
127
- return txt ? txt : void 0;
128
- }
129
- function disabled(input) {
130
- return ["0", "false", "no", "off"].includes(input?.trim().toLowerCase() ?? "");
377
+ function enabledFlag(input) {
378
+ return ["1", "true", "yes", "on"].includes(input?.trim().toLowerCase() ?? "");
131
379
  }
132
380
  function nameFor(workspaceId) {
133
381
  const cleanId = workspaceId.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -190,10 +438,9 @@ function envArgs(input) {
190
438
  function labelArgs(input) {
191
439
  return Object.entries(input).flatMap(([key, value]) => ["--label", `${key}=${value}`]);
192
440
  }
441
+ var TRANSIENT_MARKERS = ["timeout", "temporarily unavailable", "connection refused"];
193
442
  function transientDriverError(err) {
194
- const shaped = err;
195
- const text = `${shaped.code ?? ""} ${shaped.message ?? ""}`.toLowerCase();
196
- return text.includes("timeout") || text.includes("temporarily unavailable") || text.includes("connection refused");
443
+ return isTransientDriverError(err, TRANSIENT_MARKERS);
197
444
  }
198
445
  function createDockerSandboxDriver(options) {
199
446
  const dockerCommand = options.docker ?? docker;
@@ -218,19 +465,16 @@ function createDockerSandboxDriver(options) {
218
465
  return input.bootSource?.kind === "image" ? input.bootSource.image : input.snapshot ?? options.image;
219
466
  }
220
467
  function bootEnv(input, hostId) {
221
- const env = {
222
- ...workspaceRuntimeTargetEnv({
223
- workspaceId: input.workspaceId,
224
- hostId,
225
- directory: workspaceDirectory(input),
226
- port: runtimePort(input),
227
- host: "0.0.0.0"
228
- }),
229
- ...workspaceRuntimeSourceEnv({ source: input.source }),
230
- ...input.env
231
- };
232
- if (options.runner) env.WORKSPACE_RUNTIME_RUNNER = options.runner;
233
- return env;
468
+ return workspaceRuntimeBootEnv({
469
+ workspaceId: input.workspaceId,
470
+ hostId,
471
+ directory: workspaceDirectory(input),
472
+ port: runtimePort(input),
473
+ host: "0.0.0.0",
474
+ source: input.source,
475
+ env: input.env,
476
+ runner: options.runner
477
+ });
234
478
  }
235
479
  async function readyTarget(input, sandboxId, hostId) {
236
480
  const out = await dockerCommand(["port", sandboxId, `${runtimePort(input)}/tcp`], timeoutMs);
@@ -326,7 +570,9 @@ function createDockerSandboxDriver(options) {
326
570
  hostStopBehavior: "terminates-host",
327
571
  hostResumeBehavior: "same-host",
328
572
  targetAccess: "loopback",
329
- secretBrokering: "none"
573
+ secretBrokering: "none",
574
+ egressControl: "none",
575
+ persistence: sandboxDriverCatalog.docker.metadata.persistence
330
576
  },
331
577
  ensureHost,
332
578
  resumeHost,
@@ -0,0 +1,26 @@
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 {};