@sandboxrouter/sdk 0.2.0 → 0.3.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 (91) hide show
  1. package/README.md +224 -48
  2. package/dist/bench.js +4 -4
  3. package/dist/catalog.d.ts +159 -0
  4. package/dist/catalog.js +448 -15
  5. package/dist/check.d.ts +28 -0
  6. package/dist/check.js +185 -0
  7. package/dist/cli.js +148 -11
  8. package/dist/computers.d.ts +55 -13
  9. package/dist/computers.js +132 -6
  10. package/dist/conformance.d.ts +46 -0
  11. package/dist/conformance.js +65 -0
  12. package/dist/cost.d.ts +67 -0
  13. package/dist/cost.js +153 -0
  14. package/dist/gc.d.ts +29 -0
  15. package/dist/gc.js +54 -0
  16. package/dist/index.d.ts +28 -2
  17. package/dist/index.js +28 -2
  18. package/dist/leaderboard.d.ts +6 -0
  19. package/dist/leaderboard.js +5 -4
  20. package/dist/ledger.d.ts +38 -0
  21. package/dist/ledger.js +89 -0
  22. package/dist/providers/agent37.d.ts +40 -0
  23. package/dist/providers/agent37.js +224 -0
  24. package/dist/providers/agentcore.d.ts +71 -0
  25. package/dist/providers/agentcore.js +446 -0
  26. package/dist/providers/alibaba.d.ts +18 -0
  27. package/dist/providers/alibaba.js +106 -0
  28. package/dist/providers/azure.d.ts +43 -0
  29. package/dist/providers/azure.js +330 -0
  30. package/dist/providers/beam.d.ts +11 -0
  31. package/dist/providers/beam.js +179 -0
  32. package/dist/providers/blaxel.d.ts +3 -1
  33. package/dist/providers/blaxel.js +32 -22
  34. package/dist/providers/boat.js +8 -3
  35. package/dist/providers/cloudflare.js +8 -3
  36. package/dist/providers/codesandbox.d.ts +59 -0
  37. package/dist/providers/codesandbox.js +287 -0
  38. package/dist/providers/daytona.d.ts +25 -4
  39. package/dist/providers/daytona.js +449 -28
  40. package/dist/providers/dedalus.d.ts +12 -0
  41. package/dist/providers/dedalus.js +244 -0
  42. package/dist/providers/deno.d.ts +50 -0
  43. package/dist/providers/deno.js +170 -0
  44. package/dist/providers/docker.js +11 -5
  45. package/dist/providers/e2b.d.ts +70 -3
  46. package/dist/providers/e2b.js +376 -8
  47. package/dist/providers/exedev.js +17 -5
  48. package/dist/providers/freestyle.d.ts +49 -0
  49. package/dist/providers/freestyle.js +261 -0
  50. package/dist/providers/hopx.js +27 -18
  51. package/dist/providers/instabox.d.ts +76 -0
  52. package/dist/providers/instabox.js +360 -0
  53. package/dist/providers/koyeb.d.ts +71 -0
  54. package/dist/providers/koyeb.js +379 -0
  55. package/dist/providers/lambda.d.ts +51 -0
  56. package/dist/providers/lambda.js +467 -0
  57. package/dist/providers/langsmith.d.ts +16 -0
  58. package/dist/providers/langsmith.js +149 -0
  59. package/dist/providers/limrun.d.ts +77 -0
  60. package/dist/providers/limrun.js +626 -0
  61. package/dist/providers/maritime.d.ts +5 -1
  62. package/dist/providers/maritime.js +39 -8
  63. package/dist/providers/mobilerun.d.ts +44 -0
  64. package/dist/providers/mobilerun.js +417 -0
  65. package/dist/providers/modal.js +12 -4
  66. package/dist/providers/morph.js +12 -4
  67. package/dist/providers/northflank.js +11 -13
  68. package/dist/providers/novita.d.ts +12 -0
  69. package/dist/providers/novita.js +81 -0
  70. package/dist/providers/openscale.d.ts +17 -0
  71. package/dist/providers/openscale.js +87 -0
  72. package/dist/providers/orgo.d.ts +45 -0
  73. package/dist/providers/orgo.js +414 -0
  74. package/dist/providers/sail.d.ts +32 -0
  75. package/dist/providers/sail.js +372 -0
  76. package/dist/providers/superserve.d.ts +25 -0
  77. package/dist/providers/superserve.js +337 -0
  78. package/dist/providers/tensorlake.js +17 -14
  79. package/dist/providers/upstash.d.ts +30 -0
  80. package/dist/providers/upstash.js +258 -0
  81. package/dist/providers/vercel.js +14 -3
  82. package/dist/providers/workspaces.d.ts +60 -0
  83. package/dist/providers/workspaces.js +662 -0
  84. package/dist/router.d.ts +36 -5
  85. package/dist/router.js +149 -19
  86. package/dist/server.d.ts +1 -1
  87. package/dist/server.js +22 -3
  88. package/dist/types.d.ts +13 -5
  89. package/dist/util.d.ts +28 -1
  90. package/dist/util.js +56 -7
  91. package/package.json +80 -5
@@ -0,0 +1,379 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { ProviderError } from "../types.js";
3
+ import { readSSE } from "../sse.js";
4
+ import { shq, toBytes } from "../util.js";
5
+ /**
6
+ * Koyeb Sandboxes over plain REST (no SDK needed). Koyeb Sandboxes are in public preview and need
7
+ * a paid plan (Pro or Scale).
8
+ *
9
+ * A sandbox is a Koyeb service of type SANDBOX in its own app. The platform runs a command executor
10
+ * next to the image (any public Docker image works; default `koyeb/sandbox`, Ubuntu 22.04 with
11
+ * common runtimes), reachable over HTTPS with a per-sandbox secret that we mint and pass as the
12
+ * SANDBOX_SECRET env var. This is the wire protocol of the official SDKs (@koyeb/sandbox-sdk and
13
+ * koyeb-python-sdk's koyeb.sandbox), which is the only place Koyeb documents it.
14
+ *
15
+ * Control plane, https://app.koyeb.com, Authorization: Bearer <KOYEB_API_TOKEN>:
16
+ * POST /v1/apps {name, life_cycle:{delete_when_empty}} -> {app}
17
+ * POST /v1/services {app_id, definition:{type:"SANDBOX",docker,instance_types,
18
+ * regions,ports,routes,env,scalings}, life_cycle:{delete_after_create}}
19
+ * -> {service}
20
+ * GET /v1/services/:id -> {service:{app_id, active_deployment_id, latest_deployment_id}}
21
+ * GET /v1/deployments/:id -> {deployment:{status, definition.env, metadata.sandbox:{public_url, routing_key}}}
22
+ * GET /v1/apps/:id -> {app:{domains:[{name}]}} (executor address on older deployments)
23
+ * DELETE /v1/apps/:id, DELETE /v1/services/:id
24
+ * Errors are {status, code, message, fields}.
25
+ * Executor, <public_url>/koyeb-sandbox, Authorization: Bearer <secret>, X-Routing-Key: <routing_key>:
26
+ * GET /health 200 once the executor answers
27
+ * POST /run_streaming {cmd, cwd, env} SSE: `output` {stream, data, eol?} one line per event,
28
+ * `complete` {code, error}, `error` {error} (failed start)
29
+ * PUT /files?path=P&parents=1 raw bytes in, {size, sha256} out (executor v0.0.24+)
30
+ * GET /files?path=P raw bytes out, 404 when missing
31
+ * Sizes are fixed instance types (KOYEB_INSTANCES); the adapter picks the smallest that fits.
32
+ * No pause: Koyeb's sleep scales the instance to zero and loses its disk, so scale-to-zero is off.
33
+ * Docs: https://www.koyeb.com/docs/sandboxes, https://www.koyeb.com/docs/sandboxes/sandbox-lifecycle,
34
+ * https://www.koyeb.com/docs/sandboxes/sandbox-run-commands,
35
+ * https://www.koyeb.com/docs/sandboxes/sandbox-file-operations,
36
+ * https://www.koyeb.com/docs/sandboxes/sandbox-default-image,
37
+ * https://www.koyeb.com/docs/reference/instances, https://www.koyeb.com/docs/api,
38
+ * https://github.com/koyeb/koyeb-sandbox-sdk-js (src/definition.ts, src/sandbox.ts,
39
+ * src/command-runner.ts, src/executor-gateway.ts),
40
+ * https://github.com/koyeb/koyeb-python-sdk/blob/main/docs/sandbox.md
41
+ */
42
+ const ID = "koyeb";
43
+ const DEFAULT_IMAGE = "koyeb/sandbox";
44
+ const READY_TIMEOUT_MS = 5 * 60_000;
45
+ /** life_cycle.delete_after_create bounds: 60 s to 24 h. */
46
+ const MIN_LIFETIME_S = 60;
47
+ const MAX_LIFETIME_S = 86_400;
48
+ const READY = new Set(["HEALTHY", "DEGRADED"]);
49
+ const STARTING = new Set(["PENDING", "PROVISIONING", "SCHEDULED", "ALLOCATING", "STARTING"]);
50
+ /**
51
+ * Koyeb Standard instance types, from https://www.koyeb.com/docs/reference/instances
52
+ * (checked 2026-11-04). Billed per second for the whole instance.
53
+ */
54
+ export const KOYEB_INSTANCES = [
55
+ { type: "nano", vcpu: 0.25, memoryGb: 0.25, usdHour: 0.0036 },
56
+ { type: "micro", vcpu: 0.5, memoryGb: 0.5, usdHour: 0.0072 },
57
+ { type: "small", vcpu: 1, memoryGb: 1, usdHour: 0.0144 },
58
+ { type: "medium", vcpu: 2, memoryGb: 2, usdHour: 0.0288 },
59
+ { type: "large", vcpu: 4, memoryGb: 4, usdHour: 0.0576 },
60
+ { type: "xlarge", vcpu: 8, memoryGb: 8, usdHour: 0.1152 },
61
+ { type: "2xlarge", vcpu: 16, memoryGb: 16, usdHour: 0.2304 },
62
+ { type: "3xlarge", vcpu: 24, memoryGb: 32, usdHour: 0.4608 },
63
+ { type: "4xlarge", vcpu: 32, memoryGb: 64, usdHour: 0.9216 },
64
+ { type: "5xlarge", vcpu: 40, memoryGb: 128, usdHour: 1.8432 },
65
+ ];
66
+ /** Smallest Standard instance with at least the requested vCPU and memory, or undefined. */
67
+ export function koyebInstance(vcpu, memoryGb) {
68
+ return KOYEB_INSTANCES.find((t) => t.vcpu >= vcpu && t.memoryGb >= memoryGb);
69
+ }
70
+ export class KoyebProvider {
71
+ token;
72
+ baseUrl;
73
+ region;
74
+ id = ID;
75
+ constructor(token = process.env.KOYEB_API_TOKEN, baseUrl = process.env.KOYEB_API_HOST ?? "https://app.koyeb.com", region = process.env.KOYEB_REGION ?? "na") {
76
+ this.token = token;
77
+ this.baseUrl = baseUrl;
78
+ this.region = region;
79
+ }
80
+ configured() {
81
+ return !!this.token;
82
+ }
83
+ /** @internal Control-plane JSON call. */
84
+ async api(method, path, json) {
85
+ if (!this.token)
86
+ throw new ProviderError(ID, "KOYEB_API_TOKEN is not set");
87
+ const res = await fetch(`${this.baseUrl}${path}`, {
88
+ method,
89
+ headers: { Authorization: `Bearer ${this.token}`, ...(json !== undefined ? { "Content-Type": "application/json" } : {}) },
90
+ body: json !== undefined ? JSON.stringify(json) : undefined,
91
+ });
92
+ const text = await res.text();
93
+ if (!res.ok) {
94
+ let detail = text.slice(0, 300);
95
+ try {
96
+ const j = JSON.parse(text);
97
+ if (j?.message)
98
+ detail = `${j.code ? `${j.code}: ` : ""}${j.message}${j.fields?.length ? ` (${j.fields.map((f) => `${f.field}: ${f.description}`).join("; ")})` : ""}`;
99
+ }
100
+ catch {
101
+ /* plain-text body */
102
+ }
103
+ throw new ProviderError(ID, `${method} ${path.split("?")[0]} -> ${res.status} ${detail}`, res.status);
104
+ }
105
+ return (text ? JSON.parse(text) : {});
106
+ }
107
+ async create(spec) {
108
+ const vcpu = spec.vcpu ?? 2;
109
+ const memoryGb = spec.memoryGb ?? 4;
110
+ const instance = koyebInstance(vcpu, memoryGb);
111
+ if (!instance)
112
+ throw new ProviderError(ID, `no Koyeb instance has ${vcpu} vCPU / ${memoryGb} GB (max 5xlarge: 40 vCPU / 128 GB)`);
113
+ const suffix = randomBytes(5).toString("hex");
114
+ const secret = randomBytes(32).toString("base64url");
115
+ const env = [{ key: "SANDBOX_SECRET", value: secret }];
116
+ for (const [key, value] of Object.entries(spec.env ?? {})) {
117
+ if (key === "SANDBOX_SECRET")
118
+ throw new ProviderError(ID, "SANDBOX_SECRET is reserved for the executor");
119
+ env.push({ key, value });
120
+ }
121
+ const lifetime = Math.round((spec.timeoutMinutes ?? 30) * 60);
122
+ const { app } = await this.api("POST", "/v1/apps", {
123
+ name: `sbr-${suffix}`,
124
+ life_cycle: { delete_when_empty: true },
125
+ });
126
+ if (!app?.id)
127
+ throw new ProviderError(ID, "create app returned no id");
128
+ let service;
129
+ try {
130
+ ({ service } = await this.api("POST", "/v1/services", {
131
+ app_id: app.id,
132
+ definition: {
133
+ name: `sbr-${suffix}`,
134
+ type: "SANDBOX",
135
+ docker: { image: spec.image?.koyeb ?? DEFAULT_IMAGE },
136
+ instance_types: [{ type: instance.type }],
137
+ regions: [this.region],
138
+ // The executor listens on 3030 under /koyeb-sandbox/; 3031 is the port the sandbox may expose.
139
+ ports: [
140
+ { port: 3030, protocol: "http" },
141
+ { port: 3031, protocol: "http" },
142
+ ],
143
+ routes: [
144
+ { port: 3030, path: "/koyeb-sandbox/" },
145
+ { port: 3031, path: "/" },
146
+ ],
147
+ env,
148
+ mesh: "DEPLOYMENT_MESH_AUTO",
149
+ // Always one instance: scale-to-zero would drop the sandbox's disk.
150
+ scalings: [{ min: 1, max: 1 }],
151
+ },
152
+ life_cycle: { delete_after_create: Math.min(MAX_LIFETIME_S, Math.max(MIN_LIFETIME_S, lifetime)) },
153
+ }));
154
+ if (!service?.id)
155
+ throw new ProviderError(ID, "create service returned no id");
156
+ }
157
+ catch (err) {
158
+ await this.api("DELETE", `/v1/apps/${app.id}`).catch(() => { });
159
+ throw err;
160
+ }
161
+ const box = new KoyebBox(this, service.id, app.id, secret, true);
162
+ try {
163
+ await box.waitReady(service.latest_deployment_id);
164
+ }
165
+ catch (err) {
166
+ await box.kill().catch(() => { });
167
+ throw err;
168
+ }
169
+ return box;
170
+ }
171
+ /** Reattach by service id. The executor secret is read back from the deployment's env. */
172
+ async connect(nativeId) {
173
+ const { service } = await this.api("GET", `/v1/services/${encodeURIComponent(nativeId)}`);
174
+ const deploymentId = service?.active_deployment_id || service?.latest_deployment_id;
175
+ if (!service?.app_id || !deploymentId)
176
+ throw new ProviderError(ID, `service ${nativeId} has no deployment`, 404);
177
+ const { deployment } = await this.api("GET", `/v1/deployments/${deploymentId}`);
178
+ const secret = deployment?.definition?.env?.find((e) => e.key === "SANDBOX_SECRET")?.value;
179
+ if (!secret)
180
+ throw new ProviderError(ID, `service ${nativeId} has no SANDBOX_SECRET; it is not a Koyeb sandbox`);
181
+ const box = new KoyebBox(this, nativeId, service.app_id, secret, false);
182
+ await box.waitReady(deploymentId);
183
+ return box;
184
+ }
185
+ }
186
+ class KoyebBox {
187
+ p;
188
+ nativeId;
189
+ appId;
190
+ secret;
191
+ ownsApp;
192
+ base;
193
+ routingKey;
194
+ constructor(p, nativeId, appId, secret,
195
+ /** Created by this adapter in its own app: kill deletes the app, not just the service. */
196
+ ownsApp) {
197
+ this.p = p;
198
+ this.nativeId = nativeId;
199
+ this.appId = appId;
200
+ this.secret = secret;
201
+ this.ownsApp = ownsApp;
202
+ }
203
+ /** Poll the deployment until HEALTHY, learn the executor address, then poll /health. */
204
+ async waitReady(deploymentId) {
205
+ const deadline = Date.now() + READY_TIMEOUT_MS;
206
+ let id = deploymentId;
207
+ for (;;) {
208
+ if (!id) {
209
+ const { service } = await this.p.api("GET", `/v1/services/${this.nativeId}`);
210
+ id = service?.active_deployment_id || service?.latest_deployment_id;
211
+ }
212
+ if (id) {
213
+ const { deployment } = await this.p.api("GET", `/v1/deployments/${id}`);
214
+ const status = deployment?.status ?? "";
215
+ if (READY.has(status)) {
216
+ await this.resolveExecutor(deployment);
217
+ break;
218
+ }
219
+ if (!STARTING.has(status)) {
220
+ const why = deployment?.messages?.join("; ");
221
+ throw new ProviderError(ID, `sandbox ${this.nativeId} deployment is ${status || "unknown"}${why ? `: ${why}` : ""}`);
222
+ }
223
+ }
224
+ if (Date.now() > deadline)
225
+ throw new ProviderError(ID, `sandbox ${this.nativeId} not ready after ${READY_TIMEOUT_MS / 1000}s`);
226
+ await sleep(500);
227
+ }
228
+ for (;;) {
229
+ try {
230
+ const res = await this.executor("GET", "/health", undefined, AbortSignal.timeout(5_000));
231
+ await res.body?.cancel().catch(() => { });
232
+ if (res.ok)
233
+ return;
234
+ if (res.status === 401 || res.status === 403)
235
+ throw new ProviderError(ID, `executor rejected the sandbox secret (${res.status})`, res.status);
236
+ }
237
+ catch (err) {
238
+ if (err instanceof ProviderError)
239
+ throw err;
240
+ }
241
+ if (Date.now() > deadline)
242
+ throw new ProviderError(ID, `sandbox ${this.nativeId} executor not answering after ${READY_TIMEOUT_MS / 1000}s`);
243
+ await sleep(500);
244
+ }
245
+ }
246
+ /** The executor lives at <public_url>/koyeb-sandbox; older deployments only expose the app domain. */
247
+ async resolveExecutor(deployment) {
248
+ const sb = deployment.metadata?.sandbox;
249
+ if (sb?.public_url) {
250
+ const url = sb.public_url.includes("://") ? sb.public_url : `https://${sb.public_url}`;
251
+ this.base = `${url.replace(/\/+$/, "")}/koyeb-sandbox`;
252
+ this.routingKey = sb.routing_key;
253
+ return;
254
+ }
255
+ const { app } = await this.p.api("GET", `/v1/apps/${this.appId}`);
256
+ const domain = app?.domains?.[0]?.name;
257
+ if (!domain)
258
+ throw new ProviderError(ID, `sandbox ${this.nativeId} has no public URL yet`);
259
+ this.base = `https://${domain}/koyeb-sandbox`;
260
+ }
261
+ executor(method, path, body, signal) {
262
+ if (!this.base)
263
+ throw new ProviderError(ID, `sandbox ${this.nativeId} executor address unknown`);
264
+ const headers = { Authorization: `Bearer ${this.secret}` };
265
+ if (this.routingKey)
266
+ headers["X-Routing-Key"] = this.routingKey;
267
+ let payload;
268
+ if (body?.json !== undefined) {
269
+ headers["Content-Type"] = "application/json";
270
+ payload = JSON.stringify(body.json);
271
+ }
272
+ else if (body?.bytes) {
273
+ headers["Content-Type"] = "application/octet-stream";
274
+ payload = body.bytes;
275
+ }
276
+ return fetch(`${this.base}${path}`, { method, headers, body: payload, signal });
277
+ }
278
+ async fail(res, what) {
279
+ const text = await res.text().catch(() => "");
280
+ let detail = text.slice(0, 300);
281
+ try {
282
+ const j = JSON.parse(text);
283
+ detail = j?.error ?? j?.message ?? detail;
284
+ }
285
+ catch {
286
+ /* plain-text body */
287
+ }
288
+ throw new ProviderError(ID, `${what} -> ${res.status} ${detail}`.trim(), res.status);
289
+ }
290
+ /**
291
+ * POST /run_streaming. The executor has no timeout field, so the command runs under `timeout`
292
+ * when the image has it, and the request is aborted 10 s after the deadline either way.
293
+ */
294
+ async exec(cmd, opts = {}) {
295
+ const t0 = Date.now();
296
+ const secs = opts.timeoutSeconds ?? 60;
297
+ const guarded = `if command -v timeout >/dev/null 2>&1; then exec timeout -k 5 ${secs} /bin/sh -c ${shq(cmd)}; else exec /bin/sh -c ${shq(cmd)}; fi`;
298
+ const ctl = new AbortController();
299
+ const timer = setTimeout(() => ctl.abort(), secs * 1000 + 10_000);
300
+ timer.unref();
301
+ let stdout = "";
302
+ let stderr = "";
303
+ let exitCode;
304
+ try {
305
+ const res = await this.executor("POST", "/run_streaming", { json: { cmd: guarded, cwd: opts.cwd, env: opts.env } }, ctl.signal);
306
+ if (!res.ok)
307
+ await this.fail(res, "run_streaming");
308
+ for await (const ev of readSSE(res)) {
309
+ let data;
310
+ try {
311
+ data = JSON.parse(ev.data);
312
+ }
313
+ catch {
314
+ throw new ProviderError(ID, `unparseable ${ev.event} event: ${ev.data.slice(0, 200)}`);
315
+ }
316
+ if (ev.event === "output") {
317
+ // One line per event without its line ending; `eol` carries it when the executor sends one.
318
+ const chunk = `${data.data ?? ""}${typeof data.eol === "string" ? data.eol : "\n"}`;
319
+ if (data.stream === "stderr") {
320
+ stderr += chunk;
321
+ opts.onStderr?.(chunk);
322
+ }
323
+ else {
324
+ stdout += chunk;
325
+ opts.onStdout?.(chunk);
326
+ }
327
+ }
328
+ else if (ev.event === "complete") {
329
+ if (typeof data.code === "number")
330
+ exitCode = data.code;
331
+ else if (typeof data.error === "string")
332
+ throw new ProviderError(ID, `command failed to start: ${data.error}`);
333
+ break;
334
+ }
335
+ else if (ev.event === "error") {
336
+ throw new ProviderError(ID, `command failed to start: ${data.error ?? ev.data}`);
337
+ }
338
+ }
339
+ }
340
+ catch (err) {
341
+ if (ctl.signal.aborted)
342
+ throw new ProviderError(ID, `command timed out after ${secs}s`);
343
+ if (err instanceof ProviderError)
344
+ throw err;
345
+ throw new ProviderError(ID, `run_streaming: ${err.message ?? err}`);
346
+ }
347
+ finally {
348
+ clearTimeout(timer);
349
+ }
350
+ if (exitCode === undefined)
351
+ throw new ProviderError(ID, "command stream ended without an exit code");
352
+ const durationMs = Date.now() - t0;
353
+ if (durationMs >= secs * 1000 && [124, 137, 143].includes(exitCode))
354
+ throw new ProviderError(ID, `command timed out after ${secs}s`);
355
+ return { exitCode, stdout, stderr, durationMs };
356
+ }
357
+ async writeFile(path, content) {
358
+ const q = new URLSearchParams({ path, parents: "1" });
359
+ const res = await this.executor("PUT", `/files?${q}`, { bytes: toBytes(content) });
360
+ if (!res.ok)
361
+ await this.fail(res, `write ${path}`);
362
+ await res.body?.cancel().catch(() => { });
363
+ }
364
+ async readFile(path) {
365
+ const res = await this.executor("GET", `/files?${new URLSearchParams({ path })}`);
366
+ if (!res.ok)
367
+ await this.fail(res, `read ${path}`);
368
+ return new Uint8Array(await res.arrayBuffer());
369
+ }
370
+ async kill() {
371
+ if (this.ownsApp)
372
+ await this.p.api("DELETE", `/v1/apps/${this.appId}`);
373
+ else
374
+ await this.p.api("DELETE", `/v1/services/${this.nativeId}`);
375
+ }
376
+ }
377
+ function sleep(ms) {
378
+ return new Promise((r) => setTimeout(r, ms));
379
+ }
@@ -0,0 +1,51 @@
1
+ import { type Provider, type ProviderSandbox, type SandboxSpec } from "../types.js";
2
+ import { AwsClient, type AwsCredentials } from "./agentcore.js";
3
+ export interface LambdaMicrovmOptions {
4
+ region?: string;
5
+ /** MicroVM image ARN or ID. */
6
+ image?: string;
7
+ imageVersion?: string;
8
+ /** Baseline memory of the image in GB (0.5, 1, 2, 4, 8). Specs above 4x this are refused. */
9
+ memoryGb?: number;
10
+ executionRoleArn?: string;
11
+ /** Egress connector ARNs; default the Lambda-provided INTERNET_EGRESS. Empty array: no egress. */
12
+ egressConnectors?: string[];
13
+ /** Override the control-plane base URL (tests). Default https://lambda.{region}.amazonaws.com. */
14
+ endpoint?: string;
15
+ credentials?: AwsCredentials | (() => Promise<AwsCredentials>);
16
+ /** Seconds without endpoint traffic before Lambda suspends the VM (min 60, default 900). */
17
+ idleSeconds?: number;
18
+ pollMs?: number;
19
+ readyTimeoutMs?: number;
20
+ }
21
+ interface Microvm {
22
+ microvmId: string;
23
+ endpoint?: string;
24
+ state?: string;
25
+ stateReason?: string;
26
+ }
27
+ export declare class LambdaMicrovmProvider implements Provider {
28
+ readonly id: "lambda";
29
+ readonly region?: string;
30
+ readonly image?: string;
31
+ /** @internal */ readonly opts: LambdaMicrovmOptions;
32
+ private client?;
33
+ constructor(opts?: LambdaMicrovmOptions);
34
+ configured(): boolean;
35
+ /** @internal */
36
+ aws(): AwsClient;
37
+ /** @internal */
38
+ url(path?: string): URL;
39
+ /** @internal */
40
+ get pollMs(): number;
41
+ /** @internal GetMicrovm until it settles in RUNNING (or fails). */
42
+ waitRunning(id: string): Promise<Microvm>;
43
+ create(spec: SandboxSpec): Promise<ProviderSandbox>;
44
+ connect(nativeId: string): Promise<ProviderSandbox>;
45
+ }
46
+ /** The in-VM server the MicroVM image must run (Node.js >= 18, built-ins only). */
47
+ export declare const LAMBDA_MICROVM_SERVER: string;
48
+ export declare const LAMBDA_MICROVM_DOCKERFILE = "FROM public.ecr.aws/docker/library/node:22-bookworm-slim\nRUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl git python3 && rm -rf /var/lib/apt/lists/*\nCOPY server.mjs /opt/sbr/server.mjs\nEXPOSE 8080\nCMD [\"node\", \"/opt/sbr/server.mjs\"]\n";
49
+ /** Writes server.mjs + Dockerfile into dir, ready to zip and upload for create-microvm-image. */
50
+ export declare function writeLambdaMicrovmBundle(dir: string): Promise<void>;
51
+ export {};