@aerol-ai/aerolvm-sdk 0.1.7 → 0.1.9

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.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Image is a fluent Dockerfile builder. The shape mirrors the Daytona SDK's
3
+ * Image API so examples port across by changing the import. The output is a
4
+ * Dockerfile string accessible via `.dockerfile` — the SDK ships it to
5
+ * `POST /v1/images/build` when an Image is passed to `MicroVM.create`.
6
+ *
7
+ * Caller-side context (COPY/ADD from local files) needs the
8
+ * SB_IMAGE_BUILD_CONTEXT_ENABLED operator flag on the daemon. Without that,
9
+ * use only `runCommands`, `env`, `workdir`, `entrypoint`, `cmd`, `user`, and
10
+ * `expose` — every step they emit is RUN/ENV/WORKDIR/etc. against the base
11
+ * image, no local files involved.
12
+ */
13
+ export declare class Image {
14
+ private _dockerfile;
15
+ private constructor();
16
+ get dockerfile(): string;
17
+ /** Start a new image from `FROM <image>`. */
18
+ static base(image: string): Image;
19
+ /**
20
+ * Build from a raw Dockerfile string. Use when you need full control
21
+ * (multi-stage builds, ARG, ONBUILD, HEALTHCHECK, etc.) that the fluent
22
+ * API doesn't expose. Caller is responsible for the Dockerfile validity.
23
+ */
24
+ static fromDockerfile(dockerfile: string): Image;
25
+ /**
26
+ * Append one or more RUN steps. Each argument becomes its own RUN line;
27
+ * an array argument is joined with `&&` so the commands share one layer.
28
+ */
29
+ runCommands(...commands: (string | string[])[]): Image;
30
+ /** Append ENV K=V pairs (one ENV line per call, all vars on it). */
31
+ env(envVars: Record<string, string>): Image;
32
+ workdir(dirPath: string): Image;
33
+ entrypoint(entrypointCommands: string[]): Image;
34
+ cmd(cmd: string[]): Image;
35
+ user(username: string): Image;
36
+ expose(port: number): Image;
37
+ }
package/dist/Image.js ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Image is a fluent Dockerfile builder. The shape mirrors the Daytona SDK's
3
+ * Image API so examples port across by changing the import. The output is a
4
+ * Dockerfile string accessible via `.dockerfile` — the SDK ships it to
5
+ * `POST /v1/images/build` when an Image is passed to `MicroVM.create`.
6
+ *
7
+ * Caller-side context (COPY/ADD from local files) needs the
8
+ * SB_IMAGE_BUILD_CONTEXT_ENABLED operator flag on the daemon. Without that,
9
+ * use only `runCommands`, `env`, `workdir`, `entrypoint`, `cmd`, `user`, and
10
+ * `expose` — every step they emit is RUN/ENV/WORKDIR/etc. against the base
11
+ * image, no local files involved.
12
+ */
13
+ export class Image {
14
+ _dockerfile = "";
15
+ constructor(dockerfile) {
16
+ this._dockerfile = dockerfile;
17
+ }
18
+ get dockerfile() {
19
+ return this._dockerfile;
20
+ }
21
+ /** Start a new image from `FROM <image>`. */
22
+ static base(image) {
23
+ if (typeof image !== "string" || image.trim() === "") {
24
+ throw new TypeError("Image.base requires a non-empty image string");
25
+ }
26
+ return new Image(`FROM ${image.trim()}\n`);
27
+ }
28
+ /**
29
+ * Build from a raw Dockerfile string. Use when you need full control
30
+ * (multi-stage builds, ARG, ONBUILD, HEALTHCHECK, etc.) that the fluent
31
+ * API doesn't expose. Caller is responsible for the Dockerfile validity.
32
+ */
33
+ static fromDockerfile(dockerfile) {
34
+ if (typeof dockerfile !== "string" || dockerfile.trim() === "") {
35
+ throw new TypeError("Image.fromDockerfile requires a non-empty Dockerfile string");
36
+ }
37
+ return new Image(dockerfile.endsWith("\n") ? dockerfile : dockerfile + "\n");
38
+ }
39
+ /**
40
+ * Append one or more RUN steps. Each argument becomes its own RUN line;
41
+ * an array argument is joined with `&&` so the commands share one layer.
42
+ */
43
+ runCommands(...commands) {
44
+ for (const entry of commands) {
45
+ if (Array.isArray(entry)) {
46
+ const joined = entry.map((c) => c.trim()).filter(Boolean).join(" && ");
47
+ if (joined !== "") {
48
+ this._dockerfile += `RUN ${joined}\n`;
49
+ }
50
+ continue;
51
+ }
52
+ const cmd = entry.trim();
53
+ if (cmd !== "") {
54
+ this._dockerfile += `RUN ${cmd}\n`;
55
+ }
56
+ }
57
+ return this;
58
+ }
59
+ /** Append ENV K=V pairs (one ENV line per call, all vars on it). */
60
+ env(envVars) {
61
+ const parts = Object.entries(envVars).map(([k, v]) => `${k}=${dockerQuote(v)}`);
62
+ if (parts.length > 0) {
63
+ this._dockerfile += `ENV ${parts.join(" ")}\n`;
64
+ }
65
+ return this;
66
+ }
67
+ workdir(dirPath) {
68
+ if (dirPath.trim() === "") {
69
+ throw new TypeError("Image.workdir requires a non-empty path");
70
+ }
71
+ this._dockerfile += `WORKDIR ${dirPath}\n`;
72
+ return this;
73
+ }
74
+ entrypoint(entrypointCommands) {
75
+ this._dockerfile += `ENTRYPOINT ${jsonExecForm(entrypointCommands)}\n`;
76
+ return this;
77
+ }
78
+ cmd(cmd) {
79
+ this._dockerfile += `CMD ${jsonExecForm(cmd)}\n`;
80
+ return this;
81
+ }
82
+ user(username) {
83
+ if (username.trim() === "") {
84
+ throw new TypeError("Image.user requires a non-empty username");
85
+ }
86
+ this._dockerfile += `USER ${username}\n`;
87
+ return this;
88
+ }
89
+ expose(port) {
90
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
91
+ throw new RangeError(`Image.expose: port ${port} is out of range`);
92
+ }
93
+ this._dockerfile += `EXPOSE ${port}\n`;
94
+ return this;
95
+ }
96
+ }
97
+ function dockerQuote(value) {
98
+ // Quote when the value contains whitespace or a special char; otherwise
99
+ // leave bare to match canonical Dockerfile style.
100
+ if (/^[A-Za-z0-9_\-./:@]+$/.test(value)) {
101
+ return value;
102
+ }
103
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
104
+ }
105
+ function jsonExecForm(parts) {
106
+ return JSON.stringify(parts);
107
+ }
package/dist/MicroVM.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type APIVersion } from "./internal/client.js";
2
2
  import { Sandbox } from "./Sandbox.js";
3
- import type { CreateOptions, CreateSessionOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
3
+ import { Image } from "./Image.js";
4
+ import type { BuildImageOptions, BuildImageResult, CreateOptions, CreateSessionOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
4
5
  type FetchLike = typeof fetch;
5
6
  export interface MicroVMConfig {
6
7
  patToken?: string;
@@ -24,10 +25,18 @@ export declare class MicroVM {
24
25
  get(id: string): Promise<Sandbox>;
25
26
  start(id: string): Promise<Sandbox>;
26
27
  stop(id: string): Promise<Sandbox>;
28
+ createSnapshot(id: string, name: string): Promise<SandboxSnapshot>;
27
29
  destroy(id: string): Promise<void>;
28
30
  resize(id: string, options: ResizeOptions): Promise<Sandbox>;
29
31
  updateLifecycle(id: string, lifecycle: Lifecycle): Promise<Sandbox>;
30
32
  reconcile(): Promise<void>;
33
+ /**
34
+ * Build an {@link Image} on the daemon and optionally push the result to a
35
+ * remote registry. Push credentials are forwarded per request and never
36
+ * persisted by the daemon. Returns the local content-addressed tag and,
37
+ * when push was requested, the pushed reference.
38
+ */
39
+ buildImage(image: Image, options?: BuildImageOptions): Promise<BuildImageResult>;
31
40
  health(): Promise<HealthStatus>;
32
41
  mounts(sandboxID: string): Promise<MountSpecRedacted[]>;
33
42
  execStream(sandboxID: string, options: ExecStreamOptions): ExecStreamHandle;
package/dist/MicroVM.js CHANGED
@@ -41,6 +41,9 @@ export class MicroVM {
41
41
  const sandbox = await this.client.stop(id);
42
42
  return this.wrap(sandbox.toJSON());
43
43
  }
44
+ async createSnapshot(id, name) {
45
+ return this.client.createSnapshot(id, name);
46
+ }
44
47
  async destroy(id) {
45
48
  await this.client.destroy(id);
46
49
  }
@@ -55,6 +58,15 @@ export class MicroVM {
55
58
  async reconcile() {
56
59
  await this.client.reconcile();
57
60
  }
61
+ /**
62
+ * Build an {@link Image} on the daemon and optionally push the result to a
63
+ * remote registry. Push credentials are forwarded per request and never
64
+ * persisted by the daemon. Returns the local content-addressed tag and,
65
+ * when push was requested, the pushed reference.
66
+ */
67
+ async buildImage(image, options) {
68
+ return this.client.buildImage(image, options);
69
+ }
58
70
  async health() {
59
71
  return this.client.health();
60
72
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { MicroVM } from "./MicroVM.js";
2
2
  export { Sandbox } from "./Sandbox.js";
3
+ export { Image } from "./Image.js";
3
4
  export type { MicroVMConfig } from "./MicroVM.js";
4
- export type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, ExposePortOptions, ExposeProtocol, ExposeResult, HealthStatus, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, UpdateLifecycleOptions, } from "./types.js";
5
+ export type { BinaryLike, BuildImageOptions, BuildImagePushOptions, BuildImageResult, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, ExposePortOptions, ExposeProtocol, ExposeResult, HealthStatus, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxSnapshot, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, UpdateLifecycleOptions, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { MicroVM } from "./MicroVM.js";
2
2
  export { Sandbox } from "./Sandbox.js";
3
+ export { Image } from "./Image.js";
@@ -1,7 +1,7 @@
1
1
  // v1 path-prefix constant. The TypeScript SDK uses this to build URLs against
2
- // the v1 surface of the sandbox daemon. When v2 lands, a sibling
3
- // internal/api/v2/paths.ts will export its own PATH_PREFIX, and the client
4
- // constructor's apiVersion option selects which one to use.
2
+ // the v1 surface of the sandbox daemon. When a new wire version lands, a
3
+ // sibling internal/api/vN/paths.ts will export its own PATH_PREFIX, and the
4
+ // client constructor's apiVersion option selects which one to use.
5
5
  //
6
6
  // Keep this in sync with pkg/api/v1/dto.go::PathPrefix on the server.
7
7
  export const PATH_PREFIX = "/v1";
@@ -1,10 +1,19 @@
1
- import type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, ExposePortOptions, ExposeResult, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Sandbox, Session, SessionAttachHandle, SessionAttachOptions } from "../types.js";
1
+ import { Image } from "../Image.js";
2
+ import type { BinaryLike, BuildImageOptions, BuildImageResult, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, ExposePortOptions, ExposeResult, HealthStatus, Lifecycle, MountSpecRedacted, NetworkUsage, SetNetworkLimitsOptions, ResizeOptions, Sandbox, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "../types.js";
2
3
  type FetchLike = typeof fetch;
3
4
  /**
4
- * Wire version of the sandbox daemon API this client speaks. The SDK and the
5
- * server version independently — bumping the SDK package does not move the
6
- * wire version. Today only "v1" exists; "v2" will be added when a wire-level
7
- * breaking change is needed on the server.
5
+ * Wire version of the v1-style sandbox endpoints (`/v1/sandboxes`, etc.) this
6
+ * client speaks. The SDK and the server version independently — bumping the
7
+ * SDK package does not move the wire version. Today only "v1" exists; future
8
+ * breaking changes to the sandbox endpoints will land in a new version
9
+ * prefix.
10
+ *
11
+ * The `/v1/images/build` endpoint used by {@link Image}-shaped image inputs
12
+ * is called unconditionally when an Image is supplied to
13
+ * {@link APIClient.create}, regardless of `apiVersion`. Daemons older than
14
+ * the image-build feature return 404; the SDK turns that into a clear
15
+ * "daemon does not support image builds" error so the user can switch to a
16
+ * string image.
8
17
  */
9
18
  export type APIVersion = "v1";
10
19
  export interface APIClientConfig {
@@ -28,15 +37,29 @@ export declare class APIClient {
28
37
  /**
29
38
  * Build a versioned API path. Pass the suffix beginning with "/" (e.g.
30
39
  * "/sandboxes") and the active version's prefix is prepended. Use this for
31
- * every versioned API call so v2 (when it lands) can be selected by the
40
+ * every versioned API call so a future wire version can be selected by the
32
41
  * apiVersion option without touching call sites.
33
42
  */
34
43
  private versioned;
35
44
  create(options: CreateOptions): Promise<SandboxResource>;
45
+ /**
46
+ * Compile a fluent {@link Image} into a content-addressed image tag by
47
+ * POSTing its Dockerfile to `/v1/images/build`. The daemon caches by
48
+ * content hash, so repeated calls with the same Dockerfile are a no-op.
49
+ * String images are passed through unchanged.
50
+ *
51
+ * Daemons predating the image-build feature do not register the route and
52
+ * return 404; this method translates that into a clear error telling the
53
+ * caller to pass a string image instead, rather than the generic "request
54
+ * failed with status 404" the JSON decoder would otherwise produce.
55
+ */
56
+ buildImage(image: Image, options?: BuildImageOptions): Promise<BuildImageResult>;
57
+ private resolveImage;
36
58
  list(): Promise<SandboxResource[]>;
37
59
  get(id: string): Promise<SandboxResource>;
38
60
  start(id: string): Promise<SandboxResource>;
39
61
  stop(id: string): Promise<SandboxResource>;
62
+ createSnapshot(id: string, name: string): Promise<SandboxSnapshot>;
40
63
  destroy(id: string): Promise<void>;
41
64
  resize(id: string, options: ResizeOptions): Promise<SandboxResource>;
42
65
  updateLifecycle(id: string, lifecycle: Lifecycle): Promise<SandboxResource>;
@@ -71,6 +94,8 @@ export declare class APIClient {
71
94
  reconcile(): Promise<void>;
72
95
  health(): Promise<HealthStatus>;
73
96
  mounts(id: string): Promise<MountSpecRedacted[]>;
97
+ getNetworkUsage(id: string): Promise<NetworkUsage>;
98
+ setNetworkLimits(id: string, options: SetNetworkLimitsOptions): Promise<NetworkUsage>;
74
99
  private wrap;
75
100
  private doJSON;
76
101
  private doBytes;
@@ -118,11 +143,14 @@ export declare class SandboxResource implements Sandbox {
118
143
  downloadFile(targetPath: string): Promise<Uint8Array>;
119
144
  exposePort(port: number, options?: ExposePortOptions): Promise<ExposeResult>;
120
145
  unexposePort(port: number): Promise<void>;
146
+ createSnapshot(name: string): Promise<SandboxSnapshot>;
121
147
  start(): Promise<this>;
122
148
  stop(): Promise<this>;
123
149
  destroy(): Promise<void>;
124
150
  resize(options: ResizeOptions): Promise<this>;
125
151
  updateLifecycle(lifecycle: Lifecycle): Promise<this>;
152
+ getNetworkUsage(): Promise<NetworkUsage>;
153
+ setNetworkLimits(options: SetNetworkLimitsOptions): Promise<NetworkUsage>;
126
154
  toJSON(): Sandbox;
127
155
  private apply;
128
156
  }
@@ -1,5 +1,6 @@
1
1
  import { basename } from "node:path";
2
2
  import { PATH_PREFIX as V1_PATH_PREFIX } from "./api/v1/paths.js";
3
+ import { Image } from "../Image.js";
3
4
  const DEFAULT_API_VERSION = "v1";
4
5
  const PATH_PREFIXES = {
5
6
  v1: V1_PATH_PREFIX,
@@ -20,16 +21,70 @@ export class APIClient {
20
21
  /**
21
22
  * Build a versioned API path. Pass the suffix beginning with "/" (e.g.
22
23
  * "/sandboxes") and the active version's prefix is prepended. Use this for
23
- * every versioned API call so v2 (when it lands) can be selected by the
24
+ * every versioned API call so a future wire version can be selected by the
24
25
  * apiVersion option without touching call sites.
25
26
  */
26
27
  versioned(suffix) {
27
28
  return `${this.versionPrefix}${suffix}`;
28
29
  }
29
30
  async create(options) {
30
- const response = await this.doJSON("POST", this.versioned("/sandboxes"), toApiCreateOptions(options));
31
+ const resolved = await this.resolveImage(options);
32
+ const response = await this.doJSON("POST", this.versioned("/sandboxes"), toApiCreateOptions(resolved));
31
33
  return new SandboxResource(this, fromApiCreateSandboxResponse(response));
32
34
  }
35
+ /**
36
+ * Compile a fluent {@link Image} into a content-addressed image tag by
37
+ * POSTing its Dockerfile to `/v1/images/build`. The daemon caches by
38
+ * content hash, so repeated calls with the same Dockerfile are a no-op.
39
+ * String images are passed through unchanged.
40
+ *
41
+ * Daemons predating the image-build feature do not register the route and
42
+ * return 404; this method translates that into a clear error telling the
43
+ * caller to pass a string image instead, rather than the generic "request
44
+ * failed with status 404" the JSON decoder would otherwise produce.
45
+ */
46
+ async buildImage(image, options) {
47
+ const body = { dockerfile_content: image.dockerfile };
48
+ if (options?.push) {
49
+ const p = options.push;
50
+ if (!p.registry) {
51
+ throw new Error("buildImage: push.registry is required when push is set");
52
+ }
53
+ if (!p.username || !p.password) {
54
+ throw new Error("buildImage: push.username and push.password are required when push is set");
55
+ }
56
+ body.push = {
57
+ registry: p.registry,
58
+ tag: p.tag,
59
+ server: p.server,
60
+ username: p.username,
61
+ password: p.password,
62
+ };
63
+ }
64
+ const response = await this.request("POST", "/v1/images/build", {
65
+ body: JSON.stringify(body),
66
+ headers: { "Content-Type": "application/json" },
67
+ });
68
+ if (response.status === 404) {
69
+ await response.text().catch(() => undefined);
70
+ throw new Error("this daemon does not support Image builds (POST /v1/images/build is not registered) — pass a string image reference (e.g. \"ubuntu:22.04\") instead, or upgrade the daemon");
71
+ }
72
+ if (!response.ok) {
73
+ throw await decodeError(response);
74
+ }
75
+ const payload = (await response.json());
76
+ return { image: payload.image, pushed: payload.pushed };
77
+ }
78
+ async resolveImage(options) {
79
+ if (typeof options.image === "string") {
80
+ return options;
81
+ }
82
+ if (!(options.image instanceof Image)) {
83
+ throw new TypeError("CreateOptions.image must be a string or Image");
84
+ }
85
+ const result = await this.buildImage(options.image);
86
+ return { ...options, image: result.image };
87
+ }
33
88
  async list() {
34
89
  const response = await this.doJSON("GET", this.versioned("/sandboxes"));
35
90
  return response.map((item) => this.wrap(item));
@@ -46,6 +101,10 @@ export class APIClient {
46
101
  const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/stop`);
47
102
  return this.wrap(response);
48
103
  }
104
+ async createSnapshot(id, name) {
105
+ const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/snapshot`, { name });
106
+ return fromApiSandboxSnapshot(response);
107
+ }
49
108
  async destroy(id) {
50
109
  await this.doJSON("DELETE", `${this.versionPrefix}/sandboxes/${id}`);
51
110
  }
@@ -142,6 +201,14 @@ export class APIClient {
142
201
  const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/mounts`);
143
202
  return response.mounts.map(fromApiMountSpecRedacted);
144
203
  }
204
+ async getNetworkUsage(id) {
205
+ const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/network/usage`);
206
+ return fromApiNetworkUsage(response);
207
+ }
208
+ async setNetworkLimits(id, options) {
209
+ const response = await this.doJSON("PATCH", `${this.versionPrefix}/sandboxes/${id}/network/limits`, toApiSetNetworkLimitsOptions(options));
210
+ return fromApiNetworkUsage(response);
211
+ }
145
212
  wrap(sandbox) {
146
213
  return new SandboxResource(this, fromApiSandbox(sandbox));
147
214
  }
@@ -237,6 +304,9 @@ export class SandboxResource {
237
304
  async unexposePort(port) {
238
305
  await this.client.unexposePort(this.id, port);
239
306
  }
307
+ async createSnapshot(name) {
308
+ return this.client.createSnapshot(this.id, name);
309
+ }
240
310
  async start() {
241
311
  const updated = await this.client.start(this.id);
242
312
  this.apply(updated.toJSON());
@@ -260,6 +330,12 @@ export class SandboxResource {
260
330
  this.apply(updated.toJSON());
261
331
  return this;
262
332
  }
333
+ async getNetworkUsage() {
334
+ return this.client.getNetworkUsage(this.id);
335
+ }
336
+ async setNetworkLimits(options) {
337
+ return this.client.setNetworkLimits(this.id, options);
338
+ }
263
339
  toJSON() {
264
340
  return cloneSandbox(this);
265
341
  }
@@ -276,6 +352,8 @@ function toApiCreateOptions(options) {
276
352
  env: options.env,
277
353
  os_user: options.osUser,
278
354
  network_block_all: options.networkBlockAll,
355
+ network_bytes_in_limit: options.networkBytesInLimit,
356
+ network_bytes_out_limit: options.networkBytesOutLimit,
279
357
  registry: options.registry,
280
358
  container_command: options.containerCommand,
281
359
  mounts: options.mounts?.map(toApiMountSpec),
@@ -359,6 +437,15 @@ function fromApiCreateSandboxResponse(response) {
359
437
  sshPrivateKey: response.ssh_private_key,
360
438
  };
361
439
  }
440
+ function fromApiSandboxSnapshot(snapshot) {
441
+ return {
442
+ name: snapshot.name,
443
+ image: snapshot.image,
444
+ imageID: snapshot.image_id,
445
+ sourceSandboxID: snapshot.source_sandbox_id,
446
+ createdAt: snapshot.created_at,
447
+ };
448
+ }
362
449
  function fromApiSession(session) {
363
450
  return {
364
451
  id: session.id,
@@ -438,6 +525,24 @@ function fromApiMountSpecRedacted(mount) {
438
525
  hasCredentials: mount.has_credentials,
439
526
  };
440
527
  }
528
+ function fromApiNetworkUsage(usage) {
529
+ return {
530
+ sandboxID: usage.sandbox_id,
531
+ bytesIn: usage.bytes_in,
532
+ bytesOut: usage.bytes_out,
533
+ bytesInLimit: usage.bytes_in_limit,
534
+ bytesOutLimit: usage.bytes_out_limit,
535
+ quotaExceeded: usage.quota_exceeded,
536
+ quotaExceededAt: usage.quota_exceeded_at ?? undefined,
537
+ lastSampledAt: usage.last_sampled_at ?? undefined,
538
+ };
539
+ }
540
+ function toApiSetNetworkLimitsOptions(options) {
541
+ return {
542
+ network_bytes_in_limit: options.networkBytesInLimit,
543
+ network_bytes_out_limit: options.networkBytesOutLimit,
544
+ };
545
+ }
441
546
  function fromApiLifecycle(lifecycle) {
442
547
  const result = {};
443
548
  if (lifecycle?.stop_if_idle_for !== undefined) {
package/dist/types.d.ts CHANGED
@@ -4,6 +4,30 @@ export interface RegistryAuth {
4
4
  username: string;
5
5
  password: string;
6
6
  }
7
+ /**
8
+ * Per-request push directive for `MicroVM.buildImage`. Credentials are
9
+ * forwarded to the daemon as a one-shot `X-Registry-Auth` header on the
10
+ * push call and are never persisted.
11
+ */
12
+ export interface BuildImagePushOptions {
13
+ /** Destination repository, e.g. "ghcr.io/my-org/my-image". */
14
+ registry: string;
15
+ /** Destination tag. Defaults to "latest" on the daemon when omitted. */
16
+ tag?: string;
17
+ /** Registry serveraddress, e.g. "ghcr.io". Sent inside X-Registry-Auth. */
18
+ server?: string;
19
+ username: string;
20
+ password: string;
21
+ }
22
+ export interface BuildImageOptions {
23
+ push?: BuildImagePushOptions;
24
+ }
25
+ export interface BuildImageResult {
26
+ /** Local content-addressed tag (always returned). */
27
+ image: string;
28
+ /** Pushed reference, e.g. "ghcr.io/my-org/my-image:v1.2.3". */
29
+ pushed?: string;
30
+ }
7
31
  export type MountType = "s3" | "nfs" | "sshfs" | "rclone";
8
32
  export interface MountSpec {
9
33
  type: MountType;
@@ -60,8 +84,17 @@ export interface GPUOptions {
60
84
  */
61
85
  deviceIDs?: string[];
62
86
  }
87
+ import type { Image } from "./Image.js";
63
88
  export interface CreateOptions {
64
- image: string;
89
+ /**
90
+ * The base image for this sandbox. A bare image reference (e.g.
91
+ * `"ubuntu:22.04"`) is pulled by the daemon as-is. An {@link Image} builder
92
+ * is compiled to a Dockerfile and sent to the daemon's
93
+ * `POST /v1/images/build` endpoint; the resulting content-addressed tag is
94
+ * used for the sandbox. The daemon must have an image builder configured
95
+ * (every official deployment does); otherwise the build call returns 503.
96
+ */
97
+ image: string | Image;
65
98
  /** Number of CPU cores to allocate. Fractional values are supported (e.g. 0.5 = half a core). */
66
99
  cpu?: number;
67
100
  memoryMB?: number;
@@ -69,6 +102,19 @@ export interface CreateOptions {
69
102
  env?: Record<string, string>;
70
103
  osUser?: string;
71
104
  networkBlockAll?: boolean;
105
+ /**
106
+ * Cap on bytes the sandbox may receive from outside the container before its
107
+ * ingress is dropped via per-IP iptables rule. `0` (default) is unlimited.
108
+ * Limits can be raised or lifted at runtime via `setNetworkLimits`.
109
+ */
110
+ networkBytesInLimit?: number;
111
+ /**
112
+ * Cap on bytes the sandbox may send to outside the container before its
113
+ * egress is dropped. `0` (default) is unlimited. The block reuses the same
114
+ * iptables row as `networkBlockAll`; clearing the quota does not lift an
115
+ * operator-set blanket egress block.
116
+ */
117
+ networkBytesOutLimit?: number;
72
118
  registry?: RegistryAuth;
73
119
  containerCommand?: string[];
74
120
  mounts?: MountSpec[];
@@ -140,6 +186,13 @@ export interface ExposePortOptions {
140
186
  /** Defaults to "http" when omitted. */
141
187
  protocol?: ExposeProtocol;
142
188
  }
189
+ export interface SandboxSnapshot {
190
+ name: string;
191
+ image: string;
192
+ imageID?: string;
193
+ sourceSandboxID: string;
194
+ createdAt: string;
195
+ }
143
196
  /**
144
197
  * Discriminated result from `exposePort`. Branch on `protocol` to access the
145
198
  * fields that are meaningful for the chosen wire protocol — only the raw-TCP
@@ -189,6 +242,29 @@ export interface Sandbox {
189
242
  /** GPU configuration this sandbox was created with. Absent means no GPU. */
190
243
  gpus?: GPUOptions;
191
244
  }
245
+ /**
246
+ * Per-sandbox network byte counters and the configured caps that drive the
247
+ * quota enforcer. `bytesIn` is traffic the container received (ingress);
248
+ * `bytesOut` is traffic the container sent (egress). A `*Limit` of `0` means
249
+ * unlimited. `quotaExceeded` flips true the first time the meter crosses
250
+ * either configured cap and stays true until the operator raises the limit.
251
+ */
252
+ export interface NetworkUsage {
253
+ sandboxID: string;
254
+ bytesIn: number;
255
+ bytesOut: number;
256
+ bytesInLimit: number;
257
+ bytesOutLimit: number;
258
+ quotaExceeded: boolean;
259
+ quotaExceededAt?: string;
260
+ /** Absent until the netstats poller has produced at least one sample. */
261
+ lastSampledAt?: string;
262
+ }
263
+ export interface SetNetworkLimitsOptions {
264
+ /** Omit (undefined) to leave unchanged. `0` means unlimited. */
265
+ networkBytesInLimit?: number;
266
+ networkBytesOutLimit?: number;
267
+ }
192
268
  export interface ExecRequest {
193
269
  command: string;
194
270
  workDir?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerol-ai/aerolvm-sdk",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,11 +13,12 @@
13
13
  },
14
14
  "scripts": {
15
15
  "build": "tsc -p tsconfig.json",
16
+ "example:browser-isolation:src": "npx tsx src/examples/browserIsolation.ts",
16
17
  "example:create": "npm run build && node dist/examples/createSandbox.js",
17
18
  "example:create:src": "npx tsx src/examples/createSandbox.ts",
18
19
  "example:delete": "npm run build && node dist/examples/deleteSandbox.js",
19
20
  "example:delete:src": "npx tsx src/examples/deleteSandbox.ts",
20
- "test": "tsc -p tsconfig.test.json --outDir dist-test && node --test dist-test/*.test.js"
21
+ "test": "tsc -p tsconfig.test.json --outDir dist-test && node --test dist-test/*.test.js dist-test/internal/*.test.js"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/node": "^25.6.2",