@aerol-ai/aerolvm-sdk 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/MicroVM.d.ts CHANGED
@@ -19,6 +19,7 @@ export declare class MicroVM {
19
19
  destroy(id: string): Promise<void>;
20
20
  resize(id: string, options: ResizeOptions): Promise<Sandbox>;
21
21
  updateLifecycle(id: string, lifecycle: Lifecycle): Promise<Sandbox>;
22
+ reconcile(): Promise<void>;
22
23
  health(): Promise<HealthStatus>;
23
24
  mounts(sandboxID: string): Promise<MountSpecRedacted[]>;
24
25
  execStream(sandboxID: string, options: ExecStreamOptions): ExecStreamHandle;
package/dist/MicroVM.js CHANGED
@@ -51,6 +51,9 @@ export class MicroVM {
51
51
  const sandbox = await this.client.updateLifecycle(id, lifecycle);
52
52
  return this.wrap(sandbox.toJSON());
53
53
  }
54
+ async reconcile() {
55
+ await this.client.reconcile();
56
+ }
54
57
  async health() {
55
58
  return this.client.health();
56
59
  }
@@ -33,6 +33,7 @@ export declare class APIClient {
33
33
  downloadFile(id: string, targetPath: string): Promise<Uint8Array>;
34
34
  exposePort(id: string, port: number): Promise<string>;
35
35
  unexposePort(id: string, port: number): Promise<void>;
36
+ reconcile(): Promise<void>;
36
37
  health(): Promise<HealthStatus>;
37
38
  mounts(id: string): Promise<MountSpecRedacted[]>;
38
39
  private wrap;
@@ -99,6 +99,9 @@ export class APIClient {
99
99
  async unexposePort(id, port) {
100
100
  await this.doJSON("DELETE", `/v1/sandboxes/${id}/ports/${port}`);
101
101
  }
102
+ async reconcile() {
103
+ await this.doJSON("POST", "/v1/admin/reconcile");
104
+ }
102
105
  async health() {
103
106
  const response = await this.doJSON("GET", "/health");
104
107
  return fromApiHealthStatus(response);
@@ -439,6 +442,35 @@ async function decodeError(response) {
439
442
  }
440
443
  const STREAM_PREFIX_STDOUT = 0x01;
441
444
  const STREAM_PREFIX_STDERR = 0x02;
445
+ // Pull whatever diagnostic detail the runtime gave us off a WebSocket "error"
446
+ // event. Node 22's native WebSocket fires an ErrorEvent with `.error` /
447
+ // `.message`; browsers' base WebSocket fires a bare Event. Returns "" when
448
+ // nothing useful is there so callers can fall back to a generic label.
449
+ function describeWSError(event) {
450
+ if (!event || typeof event !== "object")
451
+ return "";
452
+ const e = event;
453
+ if (e.error instanceof Error && e.error.message)
454
+ return e.error.message;
455
+ if (typeof e.error === "string" && e.error !== "")
456
+ return e.error;
457
+ if (typeof e.message === "string" && e.message !== "")
458
+ return e.message;
459
+ return "";
460
+ }
461
+ function describeWSClose(event) {
462
+ if (!event || typeof event !== "object")
463
+ return "";
464
+ const e = event;
465
+ const parts = [];
466
+ if (typeof e.code === "number")
467
+ parts.push(`code=${e.code}`);
468
+ if (typeof e.reason === "string" && e.reason !== "")
469
+ parts.push(`reason=${JSON.stringify(e.reason)}`);
470
+ if (typeof e.wasClean === "boolean")
471
+ parts.push(`wasClean=${e.wasClean}`);
472
+ return parts.join(" ");
473
+ }
442
474
  function openExecStream(baseURL, patToken, sandboxID, options) {
443
475
  const wsURL = baseURL.replace(/^http/, "ws") + `/v1/sandboxes/${encodeURIComponent(sandboxID)}/toolbox/process/exec/stream`;
444
476
  const WS = globalThis.WebSocket;
@@ -450,6 +482,10 @@ function openExecStream(baseURL, patToken, sandboxID, options) {
450
482
  // `sandbox.bearer, <token>`.
451
483
  const ws = new WS(wsURL, ["sandbox.bearer", patToken]);
452
484
  ws.binaryType = "arraybuffer";
485
+ // Captured by the "error" listener and consumed by "close" so we report the
486
+ // root cause (e.g. "Unexpected server response: 502") rather than the
487
+ // generic "stream closed before exit" that would otherwise win the race.
488
+ let lastErrorDetail = "";
453
489
  let exitResolve;
454
490
  let exitReject;
455
491
  const done = new Promise((resolve, reject) => {
@@ -510,11 +546,19 @@ function openExecStream(baseURL, patToken, sandboxID, options) {
510
546
  options.onStderr?.(payload);
511
547
  }
512
548
  });
513
- ws.addEventListener("close", () => {
514
- finishWith({ code: 0 });
549
+ ws.addEventListener("close", (event) => {
550
+ const closeDetail = describeWSClose(event);
551
+ const parts = [lastErrorDetail, closeDetail].filter((s) => s !== "");
552
+ const suffix = parts.length > 0 ? ` (${parts.join("; ")})` : "";
553
+ failWith(`stream closed before exit${suffix}`);
515
554
  });
516
- ws.addEventListener("error", () => {
517
- failWith("websocket error");
555
+ ws.addEventListener("error", (event) => {
556
+ const detail = describeWSError(event);
557
+ if (detail !== "") {
558
+ lastErrorDetail = detail;
559
+ }
560
+ options.onError?.(detail !== "" ? detail : "websocket error");
561
+ failWith(detail !== "" ? `websocket error: ${detail}` : "websocket error");
518
562
  });
519
563
  const sendBinary = (data) => {
520
564
  const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
@@ -530,7 +574,6 @@ function openExecStream(baseURL, patToken, sandboxID, options) {
530
574
  },
531
575
  close() {
532
576
  ws.send(JSON.stringify({ type: "close" }));
533
- ws.close();
534
577
  },
535
578
  done,
536
579
  };
@@ -550,6 +593,7 @@ function openSessionAttach(baseURL, patToken, sandboxID, sessionID, options) {
550
593
  exitReject = reject;
551
594
  });
552
595
  let settled = false;
596
+ let lastErrorDetail = "";
553
597
  const finishWith = (info) => {
554
598
  if (settled)
555
599
  return;
@@ -600,13 +644,21 @@ function openSessionAttach(baseURL, patToken, sandboxID, sessionID, options) {
600
644
  options.onStderr?.(payload);
601
645
  }
602
646
  });
603
- ws.addEventListener("close", () => {
647
+ ws.addEventListener("close", (event) => {
604
648
  if (!settled) {
605
- failWith("session stream closed before exit");
649
+ const closeDetail = describeWSClose(event);
650
+ const parts = [lastErrorDetail, closeDetail].filter((s) => s !== "");
651
+ const suffix = parts.length > 0 ? ` (${parts.join("; ")})` : "";
652
+ failWith(`session stream closed before exit${suffix}`);
606
653
  }
607
654
  });
608
- ws.addEventListener("error", () => {
609
- failWith("websocket error");
655
+ ws.addEventListener("error", (event) => {
656
+ const detail = describeWSError(event);
657
+ if (detail !== "") {
658
+ lastErrorDetail = detail;
659
+ }
660
+ options.onError?.(detail !== "" ? detail : "websocket error");
661
+ failWith(detail !== "" ? `websocket error: ${detail}` : "websocket error");
610
662
  });
611
663
  const sendBinary = (data) => {
612
664
  const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerol-ai/aerolvm-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,6 +15,8 @@
15
15
  "build": "tsc -p tsconfig.json",
16
16
  "example:create": "npm run build && node dist/examples/createSandbox.js",
17
17
  "example:create:src": "npx tsx src/examples/createSandbox.ts",
18
+ "example:delete": "npm run build && node dist/examples/deleteSandbox.js",
19
+ "example:delete:src": "npx tsx src/examples/deleteSandbox.ts",
18
20
  "test": "tsc -p tsconfig.test.json --outDir dist-test && node --test dist-test/*.test.js"
19
21
  },
20
22
  "devDependencies": {