@aerol-ai/aerolvm-sdk 0.8.1 → 0.8.2

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,14 @@
1
+ /** One archive member. Names use "/" and are relative to the folder. */
2
+ export interface TarEntry {
3
+ name: string;
4
+ type: "file" | "dir" | "symlink";
5
+ mode: number;
6
+ /** Seconds since the epoch. */
7
+ mtime: number;
8
+ data?: Uint8Array;
9
+ linkname?: string;
10
+ }
11
+ /** A PAX record: "<length> <key>=<value>\n", the length counting itself. */
12
+ export declare function paxRecord(key: string, value: string): Uint8Array;
13
+ /** The entries as one uncompressed tar archive. */
14
+ export declare function tarArchive(entries: TarEntry[]): Uint8Array;
@@ -0,0 +1,79 @@
1
+ // A minimal tar writer for uploadDir (plans/agent-sandbox-feature-parity.md,
2
+ // F3). Node has no tar module, and the SDK takes no dependencies. It writes
3
+ // ustar headers, with a PAX record for a name or link target longer than
4
+ // ustar's 100 bytes, which Go's archive/tar (toolboxd) reads.
5
+ const BLOCK = 512;
6
+ const encoder = new TextEncoder();
7
+ function putString(block, offset, length, value) {
8
+ const bytes = typeof value === "string" ? encoder.encode(value) : value;
9
+ block.set(bytes.subarray(0, length), offset);
10
+ }
11
+ function putOctal(block, offset, length, value) {
12
+ putString(block, offset, length - 1, Math.floor(value).toString(8).padStart(length - 1, "0"));
13
+ }
14
+ function header(name, typeflag, mode, size, mtime, linkname) {
15
+ const block = new Uint8Array(BLOCK);
16
+ putString(block, 0, 100, name);
17
+ putOctal(block, 100, 8, mode & 0o7777);
18
+ putOctal(block, 108, 8, 0);
19
+ putOctal(block, 116, 8, 0);
20
+ putOctal(block, 124, 12, size);
21
+ putOctal(block, 136, 12, mtime);
22
+ block.fill(0x20, 148, 156);
23
+ putString(block, 156, 1, typeflag);
24
+ putString(block, 157, 100, linkname);
25
+ putString(block, 257, 6, "ustar\0");
26
+ putString(block, 263, 2, "00");
27
+ let sum = 0;
28
+ for (const byte of block)
29
+ sum += byte;
30
+ putString(block, 148, 8, `${sum.toString(8).padStart(6, "0")}\0 `);
31
+ return block;
32
+ }
33
+ /** A PAX record: "<length> <key>=<value>\n", the length counting itself. */
34
+ export function paxRecord(key, value) {
35
+ const body = encoder.encode(` ${key}=${value}\n`);
36
+ let length = body.length + String(body.length).length;
37
+ if (String(length).length !== String(body.length).length) {
38
+ // Adding the digits made the number one digit longer (98 -> 100).
39
+ length = body.length + String(length).length;
40
+ }
41
+ return encoder.encode(`${length} ${key}=${value}\n`);
42
+ }
43
+ function padded(data) {
44
+ const rest = data.length % BLOCK;
45
+ return rest === 0 ? [data] : [data, new Uint8Array(BLOCK - rest)];
46
+ }
47
+ /** The entries as one uncompressed tar archive. */
48
+ export function tarArchive(entries) {
49
+ const chunks = [];
50
+ for (const entry of entries) {
51
+ const name = encoder.encode(entry.type === "dir" && !entry.name.endsWith("/") ? `${entry.name}/` : entry.name);
52
+ const linkname = encoder.encode(entry.linkname ?? "");
53
+ const records = [];
54
+ if (name.length > 100)
55
+ records.push(paxRecord("path", new TextDecoder().decode(name)));
56
+ if (linkname.length > 100)
57
+ records.push(paxRecord("linkpath", entry.linkname ?? ""));
58
+ if (records.length > 0) {
59
+ const pax = concat(records);
60
+ chunks.push(header(encoder.encode("PaxHeader"), "x", 0o644, pax.length, entry.mtime, new Uint8Array()), ...padded(pax));
61
+ }
62
+ const data = entry.type === "file" ? entry.data ?? new Uint8Array() : new Uint8Array();
63
+ const typeflag = entry.type === "file" ? "0" : entry.type === "dir" ? "5" : "2";
64
+ chunks.push(header(name, typeflag, entry.mode, data.length, entry.mtime, linkname));
65
+ if (data.length > 0)
66
+ chunks.push(...padded(data));
67
+ }
68
+ chunks.push(new Uint8Array(BLOCK * 2));
69
+ return concat(chunks);
70
+ }
71
+ function concat(chunks) {
72
+ const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
73
+ let offset = 0;
74
+ for (const chunk of chunks) {
75
+ out.set(chunk, offset);
76
+ offset += chunk.length;
77
+ }
78
+ return out;
79
+ }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,11 @@
1
1
  export type SandboxStatus = "creating" | "started" | "stopped" | "destroyed" | "error" | "passivated" | "awaiting_runtime";
2
+ /**
3
+ * Why a sandbox is in its current status. A stopped sandbox says what
4
+ * stopped it; a started one may say `"program_exited"` (its main program
5
+ * ended and `lifecycle.destroyAfterExitFor` is counting), `"failover"`
6
+ * (recreated on another node) or `"restarted"` (by `restartOnFailure`).
7
+ */
8
+ export type StatusReason = "user_stop" | "idle" | "max_age" | "out_of_memory" | "disk_limit" | "crashed" | "reconcile" | "program_exited" | "failover" | "restarted";
2
9
  export type Durability = "ephemeral" | "passivatable" | "durable";
3
10
  export interface RegistryAuth {
4
11
  server: string;
@@ -88,6 +95,13 @@ export interface PlatformVolumeMount {
88
95
  path: string;
89
96
  /** Mount read-only. Defaults to read-write. */
90
97
  readOnly?: boolean;
98
+ /**
99
+ * Delete the volume, its files and its record, once the last sandbox using
100
+ * it is deleted. Set when the volume is first made; a later sandbox that
101
+ * names the same volume must pass the same value, or the create fails with
102
+ * 409. Defaults to false: the volume outlives its sandboxes.
103
+ */
104
+ deleteWithSandbox?: boolean;
91
105
  }
92
106
  export interface Lifecycle {
93
107
  /** Duration in integer nanoseconds. */
@@ -105,6 +119,27 @@ export interface Lifecycle {
105
119
  * without an idle window. TCP and TLS exposures stay always-on.
106
120
  */
107
121
  serverless?: boolean;
122
+ /**
123
+ * Start the sandbox again when it crashes or runs out of memory, waiting
124
+ * 5 s, then 10 s, 20 s and so on up to 5 minutes between tries. The wait
125
+ * goes back to 5 s once it has run for 10 minutes. Off by default.
126
+ * Docker, gVisor and containerd sandboxes only: Firecracker, WebAssembly
127
+ * and V8-isolate sandboxes refuse it with a 400.
128
+ */
129
+ restartOnFailure?: boolean;
130
+ /**
131
+ * Delete the sandbox this long (integer nanoseconds) after its main
132
+ * program ends. The server checks about once a minute. Not for
133
+ * WebAssembly or V8-isolate sandboxes, which have no main program (400).
134
+ */
135
+ destroyAfterExitFor?: number;
136
+ /**
137
+ * Delete the sandbox at this clock time: a `Date`, or an RFC 3339 string
138
+ * such as `"2026-11-06T18:00:00Z"`. The server turns it into
139
+ * `destroyAtAge`, so the sandbox you get back shows `destroyAtAge`, not
140
+ * this. A time in the past is refused with a 400, as is setting both.
141
+ */
142
+ destroyAt?: Date | string;
108
143
  }
109
144
  export type UpdateLifecycleOptions = Lifecycle;
110
145
  export type FailoverPolicy = "none" | "recreate";
@@ -115,6 +150,17 @@ export interface Failover {
115
150
  */
116
151
  policy: FailoverPolicy;
117
152
  }
153
+ /**
154
+ * Where a sandbox may run in a cluster. Every field you set must match the
155
+ * node's labels exactly. If no node matches, create fails with 409
156
+ * `placement_unsatisfiable`; it never falls back to another node.
157
+ */
158
+ export interface PlacementRule {
159
+ /** Run only on nodes in this zone (the node's `SB_NODE_ZONE`). */
160
+ zone?: string;
161
+ /** Run only on nodes that have all of these labels (`SB_NODE_LABELS`). */
162
+ labels?: Record<string, string>;
163
+ }
118
164
  export type GPUVendor = "nvidia" | "amd" | "apple";
119
165
  export interface GPUOptions {
120
166
  /**
@@ -158,6 +204,12 @@ export interface CreateOptions {
158
204
  * the whole call safe too, such as after your process restarts.
159
205
  */
160
206
  idempotencyKey?: string;
207
+ /**
208
+ * Client-side only, never sent. When true, the returned sandbox deletes
209
+ * itself when an `await using` block around it ends. Off by default, so
210
+ * leaving the block keeps the sandbox.
211
+ */
212
+ destroyOnExit?: boolean;
161
213
  /**
162
214
  * Optional name, unique among the caller's sandboxes (another account may
163
215
  * use the same name). A repeat create with a name the caller already holds
@@ -255,6 +307,11 @@ export interface CreateOptions {
255
307
  platformVolumes?: PlatformVolumeMount[];
256
308
  lifecycle?: Lifecycle;
257
309
  failover?: Failover;
310
+ /**
311
+ * Limit which nodes may run the sandbox, by zone and node labels. Kept on
312
+ * failover. No matching node gives a 409 `placement_unsatisfiable` error.
313
+ */
314
+ placement?: PlacementRule;
258
315
  /**
259
316
  * Container runtime to use for this sandbox. Omit to inherit the host
260
317
  * default (`SB_CONTAINER_RUNTIME`). Use `"gvisor"` for runsc-backed
@@ -300,6 +357,65 @@ export interface CreateOptions {
300
357
  * rootfs instead of building one from `image` on every create.
301
358
  */
302
359
  templateId?: string;
360
+ /**
361
+ * A short-lived cloud identity token for the sandbox (AWS, GCP, Azure).
362
+ * The role tag must be on your team's allow list (`putIdentityRole`).
363
+ */
364
+ identity?: IdentityOptions;
365
+ }
366
+ /**
367
+ * A cloud identity for a sandbox. The server signs a one-hour token whose
368
+ * subject is your team and `roleTag`, writes it to
369
+ * `/run/aerolvm/identity/token` inside the sandbox and refreshes it.
370
+ */
371
+ export interface IdentityOptions {
372
+ roleTag: string;
373
+ /** The token's `aud`; default `"sts.amazonaws.com"`. */
374
+ audience?: string;
375
+ /** Also sets `AWS_ROLE_ARN` and `AWS_WEB_IDENTITY_TOKEN_FILE` for the AWS SDKs. */
376
+ awsRoleArn?: string;
377
+ /** Set both to give the Azure SDKs `AZURE_CLIENT_ID`, `AZURE_TENANT_ID` and `AZURE_FEDERATED_TOKEN_FILE`. */
378
+ azureClientId?: string;
379
+ azureTenantId?: string;
380
+ }
381
+ /** A sandbox's cloud identity as `get` shows it. Never the token. */
382
+ export interface SandboxIdentity {
383
+ roleTag: string;
384
+ audience: string;
385
+ /** The token's `sub`: what your cloud's trust policy matches. */
386
+ subject: string;
387
+ awsRoleArn?: string;
388
+ azureClientId?: string;
389
+ azureTenantId?: string;
390
+ tokenFile: string;
391
+ /** When the token now in the sandbox expires; absent until the first one. */
392
+ tokenExpiresAt?: string;
393
+ lastError?: string;
394
+ }
395
+ /** One role tag on a team's identity allow list. */
396
+ export interface IdentityRole {
397
+ tag: string;
398
+ /** The least caller role that may create a sandbox with this tag: `"runner"` or `"admin"`. */
399
+ minRole: string;
400
+ team?: string;
401
+ createdAt: string;
402
+ updatedAt: string;
403
+ }
404
+ /** The body of `putIdentityRole`. */
405
+ export interface IdentityRoleOptions {
406
+ minRole: "runner" | "admin";
407
+ /** The team whose list to change; omit for sandboxes the operator token creates. */
408
+ team?: string;
409
+ }
410
+ export interface ListIdentityRolesOptions {
411
+ team?: string;
412
+ cursor?: string;
413
+ limit?: number;
414
+ }
415
+ export interface IdentityRoleList {
416
+ roles: IdentityRole[];
417
+ /** Pass back as `cursor` for the next page; absent on the last one. */
418
+ nextCursor?: string;
303
419
  }
304
420
  export interface ResizeOptions {
305
421
  /** Number of CPU cores. Fractional values supported (e.g. 0.5). */
@@ -307,6 +423,30 @@ export interface ResizeOptions {
307
423
  memoryMB?: number;
308
424
  diskGB?: number;
309
425
  }
426
+ /**
427
+ * The last, optional argument of every `MicroVM` and `Sandbox` call that
428
+ * talks to the server.
429
+ */
430
+ export interface CallOptions {
431
+ /**
432
+ * Cancels this call: aborting it ends the request in flight and any retry
433
+ * wait, and the call rejects with the signal's reason. Pass
434
+ * `AbortSignal.timeout(ms)` to give one call a time limit. It works with
435
+ * the client-wide `signal`; either one aborting ends the call. Calls that
436
+ * open a stream (`execStream`, `attachSession`, `watchFiles`) take no
437
+ * `CallOptions`, as the client-wide signal does not cover streams either.
438
+ */
439
+ signal?: AbortSignal;
440
+ }
441
+ /** Options for {@link MicroVM.destroyAll}. */
442
+ export interface DestroyAllOptions {
443
+ /**
444
+ * Delete only sandboxes whose `tags` hold every key/value pair given here.
445
+ * Required and must not be empty: with no filter the call would delete
446
+ * every sandbox you own.
447
+ */
448
+ tags: Record<string, string>;
449
+ }
310
450
  export interface ListOptions {
311
451
  /**
312
452
  * Filter the result to only sandboxes whose `tags` map contains every
@@ -367,6 +507,8 @@ export interface ExposedPort {
367
507
  port: number;
368
508
  publicURL: string;
369
509
  createdAt: string;
510
+ /** `"token"` when the URL needs a sandbox token; absent for a public port. */
511
+ visibility?: ExposeVisibility;
370
512
  }
371
513
  /**
372
514
  * Lifecycle states for a per-sandbox custom hostname.
@@ -459,9 +601,22 @@ export interface CustomDomainDNSRecords {
459
601
  * - "tls": caddy-l4 TLS-SNI route on the shared :443 listener.
460
602
  */
461
603
  export type ExposeProtocol = "http" | "tcp" | "tls";
604
+ /**
605
+ * Who can open a port's URL:
606
+ * - "public": anyone with the URL.
607
+ * - "token": only a visitor with a sandbox token for this port (see
608
+ * `sandbox.createToken({ port })`). HTTP ports on a server
609
+ * with a domain only.
610
+ */
611
+ export type ExposeVisibility = "public" | "token";
462
612
  export interface ExposePortOptions {
463
613
  /** Defaults to "http" when omitted. */
464
614
  protocol?: ExposeProtocol;
615
+ /**
616
+ * Left out, a new port is public and an exposed port keeps what it has.
617
+ * Pass it to change it.
618
+ */
619
+ visibility?: ExposeVisibility;
465
620
  }
466
621
  export interface SandboxSnapshot {
467
622
  name: string;
@@ -489,6 +644,52 @@ export interface SnapshotList {
489
644
  /** True when more snapshots remain after this page. */
490
645
  hasMore: boolean;
491
646
  }
647
+ /**
648
+ * Options for `sandbox.createToken`: a signed token for this one sandbox that
649
+ * you can hand to a browser or an end user. Every field but `expiresIn`
650
+ * narrows it; leave one out and that part is not limited.
651
+ */
652
+ export interface CreateSandboxTokenOptions {
653
+ /** Required lifetime: `"15m"`, `"2h"` or `"1d"`. The server caps it (24 hours by default). */
654
+ expiresIn: string;
655
+ /**
656
+ * A port token opens that port's public URL (a port exposed with
657
+ * `visibility: "token"`). Without a port the token is for the sandbox's
658
+ * toolbox: commands, files and terminal sessions.
659
+ */
660
+ port?: number;
661
+ /** Only paths under this one, for example `"/files"` (toolbox) or `"/app"` (public URL). */
662
+ pathPrefix?: string;
663
+ /** Only these HTTP methods, for example `["GET"]`. */
664
+ methods?: string[];
665
+ }
666
+ /**
667
+ * A sandbox token from `sandbox.createToken`. The server keeps no copy, so
668
+ * `token` is shown only here. Every call makes a new token. It stops working
669
+ * when it expires, when its sandbox is deleted, or when
670
+ * `sandbox.revokeTokens()` is called.
671
+ */
672
+ export interface SandboxToken {
673
+ /** The token (`avmst_…`). Send it as the bearer credential. */
674
+ token: string;
675
+ /** Public id (`stk_…`): what audit records name. Safe to log. */
676
+ tokenID: string;
677
+ sandboxID: string;
678
+ port?: number;
679
+ pathPrefix?: string;
680
+ methods?: string[];
681
+ /** RFC 3339 time the token stops working. */
682
+ expiresAt: string;
683
+ }
684
+ /**
685
+ * The result of `sandbox.revokeTokens()`. Every token issued for the sandbox
686
+ * before `tokensNotBefore` no longer works; a token made after the call works.
687
+ */
688
+ export interface RevokeSandboxTokensResult {
689
+ sandboxID: string;
690
+ /** RFC 3339 time. Tokens issued before it are refused. */
691
+ tokensNotBefore: string;
692
+ }
492
693
  /**
493
694
  * Discriminated result from `exposePort`. Branch on `protocol` to access the
494
695
  * fields that are meaningful for the chosen wire protocol — only the raw-TCP
@@ -498,6 +699,7 @@ export interface SnapshotList {
498
699
  export type ExposeResult = {
499
700
  protocol: "http";
500
701
  url: string;
702
+ visibility?: "token";
501
703
  } | {
502
704
  protocol: "tcp";
503
705
  url: string;
@@ -550,6 +752,10 @@ export interface Sandbox {
550
752
  updatedAt: string;
551
753
  lastActiveAt: string;
552
754
  lastError?: string;
755
+ /** Why the sandbox is in its status; absent when a call simply created or started it. */
756
+ statusReason?: StatusReason;
757
+ /** Exit code behind `out_of_memory`, `crashed`, `program_exited` and `restarted`. */
758
+ exitCode?: number;
553
759
  containerCommand?: string[];
554
760
  lifecycle: Lifecycle;
555
761
  failover?: Failover;
@@ -568,6 +774,8 @@ export interface Sandbox {
568
774
  tenantId?: string;
569
775
  /** GPU configuration this sandbox was created with. Absent means no GPU. */
570
776
  gpus?: GPUOptions;
777
+ /** The sandbox's cloud identity, when it was created with one. */
778
+ identity?: SandboxIdentity;
571
779
  }
572
780
  /**
573
781
  * Per-sandbox network byte counters and the configured caps that drive the
@@ -623,6 +831,12 @@ export interface AuditOptions {
623
831
  limit?: number;
624
832
  cursor?: string;
625
833
  incarnationID?: string;
834
+ /**
835
+ * Only records by this caller: an id such as `"system:lifecycle"`, or
836
+ * `"system:*"` for everything the server did on its own (automatic
837
+ * stops, deletes, restarts and recreates).
838
+ */
839
+ caller?: string;
626
840
  }
627
841
  /**
628
842
  * Would a sandbox created with these egress fields reach `destination`?
@@ -756,6 +970,32 @@ export interface EgressProfile {
756
970
  createdAt: string;
757
971
  updatedAt: string;
758
972
  }
973
+ /**
974
+ * A team quota's limits, the body of `putTeamQuota`. Omit a limit (or set
975
+ * 0) for no limit on it. At least one must be set.
976
+ */
977
+ export interface TeamQuotaOptions {
978
+ /** Most sandboxes the team may hold, running or stopped. */
979
+ maxSandboxes?: number;
980
+ /** Most CPU the team's sandboxes may ask for in total. */
981
+ maxCPU?: number;
982
+ /** Most memory, in MB, the team's sandboxes may ask for in total. */
983
+ maxMemoryMB?: number;
984
+ }
985
+ /** What a team holds now: every sandbox not deleted, at the size it asked for. */
986
+ export interface TeamQuotaUsage {
987
+ sandboxes: number;
988
+ cpu: number;
989
+ memoryMB: number;
990
+ }
991
+ /** A team's quota, and its usage when read back. */
992
+ export interface TeamQuota extends TeamQuotaOptions {
993
+ team: string;
994
+ generation: number;
995
+ createdAt: string;
996
+ updatedAt: string;
997
+ usage?: TeamQuotaUsage;
998
+ }
759
999
  /** The body of `putEgressProfile`: a full replace. */
760
1000
  export interface EgressProfileOptions {
761
1001
  allowOut: string[];
@@ -770,6 +1010,66 @@ export interface EgressProfileList {
770
1010
  /** Pass back as `cursor` for the next page; absent on the last one. */
771
1011
  nextCursor?: string;
772
1012
  }
1013
+ /**
1014
+ * A warm-pool entry: how many ready sandboxes every server with a warm pool
1015
+ * keeps for one image. The server's `SB_DOCKER_POOL_*` / `SB_CONTAINERD_POOL_*`
1016
+ * settings stay the default for every other image. Admin only.
1017
+ */
1018
+ export interface WarmPool {
1019
+ /** The image, exactly as creates name it (`python:3.12`, `ghcr.io/acme/agent:1`). */
1020
+ image: string;
1021
+ /** Warm slots each server keeps for this image. 0 turns warming off for it. */
1022
+ depth: number;
1023
+ /** With `maxDepth`, each server sets its own target from recent demand, never below this. */
1024
+ minDepth?: number;
1025
+ /** Set to follow demand: each server's target moves with its recent creates, up to this. */
1026
+ maxDepth?: number;
1027
+ /** What happens to warm sandboxes when the image changes. */
1028
+ onImageChange: WarmPoolOnImageChange;
1029
+ /** Goes up by one on every change; the same `putWarmPool` twice leaves it alone. */
1030
+ generation: number;
1031
+ createdAt: string;
1032
+ updatedAt: string;
1033
+ /** How full the pools are. Set by `getWarmPool` and `listWarmPools`. */
1034
+ status?: WarmPoolStatus;
1035
+ }
1036
+ /**
1037
+ * The body of `putWarmPool`: a full replace. Set `depth` for a fixed size, or
1038
+ * `minDepth` and `maxDepth` to follow demand, not both.
1039
+ */
1040
+ export interface WarmPoolOptions {
1041
+ /** Warm slots each server keeps for this image (0 to 128). */
1042
+ depth?: number;
1043
+ /** The fewest warm slots a server keeps when demand is low (0 lets it reach zero). */
1044
+ minDepth?: number;
1045
+ /** The most warm slots a server keeps when demand is high (1 to 128). */
1046
+ maxDepth?: number;
1047
+ /** What happens to warm sandboxes when the image changes. Default `"replace_now"`. */
1048
+ onImageChange?: WarmPoolOnImageChange;
1049
+ }
1050
+ /**
1051
+ * What a warm pool does when the image its name points to changes on a
1052
+ * server. `"replace_now"` swaps every waiting sandbox for one on the new
1053
+ * image at once; `"replace_as_used"` keeps handing out the old ones and
1054
+ * replaces each as it is used.
1055
+ */
1056
+ export type WarmPoolOnImageChange = "replace_now" | "replace_as_used";
1057
+ /** Ready and target slots in total, and on each server that runs a warm pool. */
1058
+ export interface WarmPoolStatus {
1059
+ ready: number;
1060
+ target: number;
1061
+ /** Ready sandboxes still on an older image (a `"replace_as_used"` change in progress). */
1062
+ outdated: number;
1063
+ servers: WarmPoolServerStatus[];
1064
+ }
1065
+ export interface WarmPoolServerStatus {
1066
+ nodeId: string;
1067
+ ready: number;
1068
+ target: number;
1069
+ outdated: number;
1070
+ /** The server's last report is too old to trust; the numbers are its last ones. */
1071
+ stale?: boolean;
1072
+ }
773
1073
  /** Who the caller is, as `microvm.whoami()` reports it. */
774
1074
  export interface WhoAmIPrincipal {
775
1075
  /** `"operator"`, `"token"`, `"oidc"`, `"controlplane"`, ... */
@@ -857,19 +1157,130 @@ export interface SetNetworkLimitsOptions {
857
1157
  networkBytesOutLimit?: number;
858
1158
  }
859
1159
  export interface ExecRequest {
860
- command: string;
1160
+ /** A shell command. Set `command` or `argv`, not both. */
1161
+ command?: string;
1162
+ /**
1163
+ * Run a program with these arguments instead of a shell command: spaces,
1164
+ * quotes, `$` and `;` reach it as written. A sandbox whose agent predates
1165
+ * argv throws MicroVMUnsupportedFeatureError before anything runs.
1166
+ */
1167
+ argv?: string[];
861
1168
  workDir?: string;
862
1169
  env?: Record<string, string>;
863
1170
  timeoutSeconds?: number;
1171
+ /**
1172
+ * Keep only the first N bytes of stdout. The rest is dropped and the command
1173
+ * is not slowed down. A sandbox whose agent predates the caps throws
1174
+ * MicroVMUnsupportedFeatureError instead of returning everything.
1175
+ */
1176
+ maxStdoutBytes?: number;
1177
+ /** Keep only the first N bytes of stderr. Same rules as maxStdoutBytes. */
1178
+ maxStderrBytes?: number;
864
1179
  }
865
1180
  export interface ExecResult {
866
1181
  stdout: string;
867
1182
  stderr: string;
868
1183
  exitCode: number;
869
1184
  durationMS: number;
1185
+ /** True when the command ran past its timeout and the sandbox killed its whole process group. Older sandbox agents never set it. */
1186
+ timedOut: boolean;
1187
+ /** True when stdout was longer than its cap (maxStdoutBytes, or the server's own limit) and was cut. */
1188
+ stdoutTruncated: boolean;
1189
+ /** True when stderr was longer than its cap and was cut. */
1190
+ stderrTruncated: boolean;
1191
+ /**
1192
+ * The signal that ended the command, such as "SIGKILL"; undefined when it
1193
+ * exited on its own. exitCode is -1 then; a shell would say 128 + the
1194
+ * signal's number (137 for SIGKILL).
1195
+ */
1196
+ signal?: string;
1197
+ }
1198
+ /**
1199
+ * How an upload writes its file. With none set, missing folders are made and
1200
+ * an existing file is replaced in place.
1201
+ */
1202
+ export interface UploadFileOptions {
1203
+ /** Add to the end of the file instead of replacing it. */
1204
+ append?: boolean;
1205
+ /** Write only if the file does not exist yet (HTTP 412 otherwise). Of two uploads racing for one new path, exactly one wins. */
1206
+ createOnly?: boolean;
1207
+ /** Write a temp file and rename it into place, so a reader never sees half a file. Cannot be combined with `append`. */
1208
+ atomic?: boolean;
1209
+ /** Permissions such as `0o600`. Without it a new file is 0644 and an existing one keeps its own. */
1210
+ mode?: number;
1211
+ }
1212
+ export interface DownloadFileOptions {
1213
+ /**
1214
+ * Refuse a file larger than this many bytes with `MicroVMFileTooLargeError`:
1215
+ * before reading when the server states the size, otherwise as soon as more
1216
+ * arrives. A guard against a file larger than expected filling memory.
1217
+ */
1218
+ maxBytes?: number;
1219
+ }
1220
+ /** One file, folder or symlink in a sandbox. */
1221
+ export interface FileInfo {
1222
+ name: string;
1223
+ /** The full path in the sandbox. */
1224
+ path: string;
1225
+ isDir: boolean;
1226
+ /** Bytes. */
1227
+ size: number;
1228
+ /** Type and permission string, such as `-rw-r--r--`. */
1229
+ mode: string;
1230
+ /** Octal permission bits, such as `0644`. */
1231
+ permissions: string;
1232
+ owner: string;
1233
+ group: string;
1234
+ /** Last change time, RFC 3339 in UTC. */
1235
+ modTime: string;
1236
+ /**
1237
+ * Where a symlink points. Only on `listFiles` entries: `getFileInfo`
1238
+ * follows a symlink and describes what it names.
1239
+ */
1240
+ symlinkTarget?: string;
1241
+ }
1242
+ /** The result of `listFiles`. `truncated` means the listing reached its limit and more entries exist. */
1243
+ export interface FileList {
1244
+ entries: FileInfo[];
1245
+ truncated: boolean;
1246
+ }
1247
+ export interface ListFilesOptions {
1248
+ /** Levels to go down; 1 (the default) is the folder's own entries. Symlinked folders are listed but never entered. */
1249
+ depth?: number;
1250
+ /** The most entries to return. Defaults to 10,000. */
1251
+ limit?: number;
1252
+ }
1253
+ export interface DeleteFileOptions {
1254
+ /** Delete a folder and everything in it. Without it a folder is refused (HTTP 409). */
1255
+ recursive?: boolean;
1256
+ }
1257
+ export interface MakeDirOptions {
1258
+ /** Also make missing parent folders, and accept an existing folder (`mkdir -p`). */
1259
+ parents?: boolean;
1260
+ /** Permissions such as `0o750`. Defaults to 0755. */
1261
+ mode?: number;
1262
+ }
1263
+ export interface MoveFileOptions {
1264
+ /** Replace an existing destination. Without it an existing destination is refused (HTTP 409). */
1265
+ overwrite?: boolean;
1266
+ }
1267
+ export interface CopyFileOptions {
1268
+ /** Replace an existing destination. Without it an existing destination is refused (HTTP 409). */
1269
+ overwrite?: boolean;
1270
+ /** Copy a folder and everything in it, keeping modes and symlinks. Without it a folder is refused (HTTP 409). */
1271
+ recursive?: boolean;
1272
+ }
1273
+ /** One line `findInFiles` found. */
1274
+ export interface FileMatch {
1275
+ file: string;
1276
+ line: number;
1277
+ content: string;
870
1278
  }
871
1279
  export interface ExecStreamOptions {
872
- command: string;
1280
+ /** A shell command. Set `command` or `argv`, not both. */
1281
+ command?: string;
1282
+ /** Same as ExecRequest.argv. */
1283
+ argv?: string[];
873
1284
  workdir?: string;
874
1285
  env?: Record<string, string>;
875
1286
  tty?: boolean;
@@ -915,6 +1326,41 @@ export interface SessionAttachHandle {
915
1326
  /** Resolves when the session exits, or rejects if the attach stream drops first. */
916
1327
  done: Promise<ExecExitInfo>;
917
1328
  }
1329
+ /**
1330
+ * The kinds of change a file watch reports. `overflow` means some events were
1331
+ * lost (the kernel's queue filled, too many changes in one batch, or a folder
1332
+ * could not be watched): list the folder again to catch up. A rename is
1333
+ * reported as `rename` for the old name and `create` for the new one.
1334
+ */
1335
+ export type FileEventType = "create" | "write" | "remove" | "rename" | "chmod" | "overflow";
1336
+ /** One change reported by {@link FileWatchHandle}'s watch. */
1337
+ export interface FileEvent {
1338
+ /**
1339
+ * One of {@link FileEventType}. A newer sandbox agent may add kinds; they
1340
+ * are passed through as they come rather than dropped.
1341
+ */
1342
+ type: FileEventType | (string & {});
1343
+ /** The entry's absolute path in the sandbox. Empty on most overflow events. */
1344
+ path: string;
1345
+ /** True when the entry is a folder. */
1346
+ isDir: boolean;
1347
+ /** Overflow events only: `kernel_queue`, `event_buffer` or `watch_limit`. */
1348
+ reason?: string;
1349
+ }
1350
+ export interface WatchFilesOptions {
1351
+ /** Also watch every subfolder, including ones made after the watch starts. */
1352
+ recursive?: boolean;
1353
+ }
1354
+ export interface FileWatchHandle {
1355
+ /** Stop watching. Safe to call more than once; `wait()` then resolves. */
1356
+ stop(): void;
1357
+ /**
1358
+ * Resolves after `stop()`. Rejects when the sandbox ends the watch (the
1359
+ * watched folder was removed or moved; the error message says which), when
1360
+ * the connection drops, or with the error `onEvent` threw.
1361
+ */
1362
+ wait(): Promise<void>;
1363
+ }
918
1364
  export interface HealthStatus {
919
1365
  status: string;
920
1366
  sandboxes: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerol-ai/aerolvm-sdk",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",