langsmith 0.5.18 → 0.5.20

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.
@@ -7,6 +7,27 @@ import { AsyncCaller } from "../../utils/async_caller.js";
7
7
  import { Sandbox } from "./sandbox.js";
8
8
  import { LangSmithResourceCreationError, LangSmithResourceNameConflictError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithSandboxAPIError, } from "./errors.js";
9
9
  import { handleClientHttpError, handleConflictError, handlePoolError, handleResourceInUseError, handleSandboxCreationError, handleVolumeCreationError, validateTtl, } from "./helpers.js";
10
+ /**
11
+ * Sleep that can be interrupted by an AbortSignal.
12
+ * Resolves after `ms` milliseconds or rejects immediately if the signal fires.
13
+ */
14
+ function sleepWithSignal(ms, signal) {
15
+ if (!signal) {
16
+ return new Promise((resolve) => setTimeout(resolve, ms));
17
+ }
18
+ signal.throwIfAborted();
19
+ return new Promise((resolve, reject) => {
20
+ const timer = setTimeout(() => {
21
+ signal.removeEventListener("abort", onAbort);
22
+ resolve();
23
+ }, ms);
24
+ function onAbort() {
25
+ clearTimeout(timer);
26
+ reject(signal.reason);
27
+ }
28
+ signal.addEventListener("abort", onAbort, { once: true });
29
+ });
30
+ }
10
31
  /**
11
32
  * Get the default sandbox API endpoint from environment.
12
33
  *
@@ -50,6 +71,8 @@ function getDefaultApiKey() {
50
71
  * await sandbox.delete();
51
72
  * }
52
73
  * ```
74
+ *
75
+ * @experimental This feature is experimental, and breaking changes are expected.
53
76
  */
54
77
  export class SandboxClient {
55
78
  constructor(config = {}) {
@@ -110,6 +133,25 @@ export class SandboxClient {
110
133
  getApiKey() {
111
134
  return this._apiKey;
112
135
  }
136
+ /**
137
+ * JSON POST helper. Sends JSON body, checks response status,
138
+ * and returns the Response for further processing.
139
+ * Throws on non-ok responses via handleClientHttpError.
140
+ * Callers can add specific status checks (e.g. 404) before calling this.
141
+ * @internal
142
+ */
143
+ async _postJson(url, body, options) {
144
+ const response = await this._fetch(url, {
145
+ method: "POST",
146
+ headers: { "Content-Type": "application/json" },
147
+ body: JSON.stringify(body),
148
+ signal: options?.signal,
149
+ });
150
+ if (!response.ok) {
151
+ await handleClientHttpError(response);
152
+ }
153
+ return response;
154
+ }
113
155
  // =========================================================================
114
156
  // Volume Operations
115
157
  // =========================================================================
@@ -535,14 +577,25 @@ export class SandboxClient {
535
577
  * ```
536
578
  */
537
579
  async createSandbox(templateName, options = {}) {
538
- const { name, timeout = 30, waitForReady = true, ttlSeconds, idleTtlSeconds, } = options;
580
+ const { snapshotId, name, timeout = 30, waitForReady = true, ttlSeconds, idleTtlSeconds, vCpus, memBytes, fsCapacityBytes, } = options;
581
+ if (!templateName && !snapshotId) {
582
+ throw new Error("Either templateName or snapshotId is required");
583
+ }
584
+ if (templateName && snapshotId) {
585
+ throw new Error("Cannot specify both templateName and snapshotId");
586
+ }
539
587
  validateTtl(ttlSeconds, "ttlSeconds");
540
588
  validateTtl(idleTtlSeconds, "idleTtlSeconds");
541
589
  const url = `${this._baseUrl}/boxes`;
542
590
  const payload = {
543
- template_name: templateName,
544
591
  wait_for_ready: waitForReady,
545
592
  };
593
+ if (templateName) {
594
+ payload.template_name = templateName;
595
+ }
596
+ if (snapshotId) {
597
+ payload.snapshot_id = snapshotId;
598
+ }
546
599
  if (waitForReady) {
547
600
  payload.timeout = timeout;
548
601
  }
@@ -555,6 +608,15 @@ export class SandboxClient {
555
608
  if (idleTtlSeconds !== undefined) {
556
609
  payload.idle_ttl_seconds = idleTtlSeconds;
557
610
  }
611
+ if (vCpus !== undefined) {
612
+ payload.vcpus = vCpus;
613
+ }
614
+ if (memBytes !== undefined) {
615
+ payload.mem_bytes = memBytes;
616
+ }
617
+ if (fsCapacityBytes !== undefined) {
618
+ payload.fs_capacity_bytes = fsCapacityBytes;
619
+ }
558
620
  const httpTimeout = waitForReady ? (timeout + 30) * 1000 : 30 * 1000;
559
621
  const response = await this._fetch(url, {
560
622
  method: "POST",
@@ -577,9 +639,9 @@ export class SandboxClient {
577
639
  * @returns Sandbox.
578
640
  * @throws LangSmithResourceNotFoundError if sandbox not found.
579
641
  */
580
- async getSandbox(name) {
642
+ async getSandbox(name, options) {
581
643
  const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}`;
582
- const response = await this._fetch(url);
644
+ const response = await this._fetch(url, { signal: options?.signal });
583
645
  if (!response.ok) {
584
646
  if (response.status === 404) {
585
647
  throw new LangSmithResourceNotFoundError(`Sandbox '${name}' not found`, "sandbox");
@@ -674,9 +736,9 @@ export class SandboxClient {
674
736
  * @returns ResourceStatus with status and optional status_message.
675
737
  * @throws LangSmithResourceNotFoundError if sandbox not found.
676
738
  */
677
- async getSandboxStatus(name) {
739
+ async getSandboxStatus(name, options) {
678
740
  const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/status`;
679
- const response = await this._fetch(url);
741
+ const response = await this._fetch(url, { signal: options?.signal });
680
742
  if (!response.ok) {
681
743
  if (response.status === 404) {
682
744
  throw new LangSmithResourceNotFoundError(`Sandbox '${name}' not found`, "sandbox");
@@ -706,21 +768,182 @@ export class SandboxClient {
706
768
  * ```
707
769
  */
708
770
  async waitForSandbox(name, options = {}) {
709
- const { timeout = 120, pollInterval = 1.0 } = options;
771
+ const { timeout = 120, pollInterval = 1.0, signal } = options;
710
772
  const deadline = Date.now() + timeout * 1000;
711
773
  let lastStatus = "provisioning";
712
774
  while (Date.now() < deadline) {
713
- const statusResult = await this.getSandboxStatus(name);
775
+ signal?.throwIfAborted();
776
+ const statusResult = await this.getSandboxStatus(name, { signal });
714
777
  lastStatus = statusResult.status;
715
778
  if (statusResult.status === "ready") {
716
- return this.getSandbox(name);
779
+ return this.getSandbox(name, { signal });
717
780
  }
718
781
  if (statusResult.status === "failed") {
719
782
  throw new LangSmithResourceCreationError(statusResult.status_message ?? `Sandbox '${name}' creation failed`, "sandbox");
720
783
  }
721
- // Wait before polling again
722
- await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
784
+ // Wait before polling again, capped to remaining time + jitter
785
+ const remaining = deadline - Date.now();
786
+ const jitter = pollInterval * 200 * (Math.random() - 0.5); // ±10%
787
+ const delay = Math.min(pollInterval * 1000 + jitter, remaining);
788
+ if (delay > 0) {
789
+ await sleepWithSignal(delay, signal);
790
+ }
723
791
  }
724
792
  throw new LangSmithResourceTimeoutError(`Sandbox '${name}' did not become ready within ${timeout}s`, "sandbox", lastStatus);
725
793
  }
794
+ /**
795
+ * Start a stopped sandbox and wait until ready.
796
+ *
797
+ * @param name - Sandbox name.
798
+ * @param options - Options with timeout.
799
+ * @returns Sandbox in "ready" status.
800
+ */
801
+ async startSandbox(name, options = {}) {
802
+ const { timeout = 120, signal } = options;
803
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/start`;
804
+ await this._postJson(url, {}, { signal });
805
+ return this.waitForSandbox(name, { timeout, signal });
806
+ }
807
+ /**
808
+ * Stop a running sandbox (preserves sandbox files for later restart).
809
+ *
810
+ * @param name - Sandbox name.
811
+ */
812
+ async stopSandbox(name) {
813
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/stop`;
814
+ await this._postJson(url, {});
815
+ }
816
+ // =========================================================================
817
+ // Snapshot Operations
818
+ // =========================================================================
819
+ /**
820
+ * Build a snapshot from a Docker image.
821
+ *
822
+ * Blocks until the snapshot is ready (polls with 2s interval).
823
+ *
824
+ * @param name - Snapshot name.
825
+ * @param dockerImage - Docker image to build from (e.g., "python:3.12-slim").
826
+ * @param fsCapacityBytes - Filesystem capacity in bytes.
827
+ * @param options - Additional options (registry credentials, timeout).
828
+ * @returns Snapshot in "ready" status.
829
+ */
830
+ async createSnapshot(name, dockerImage, fsCapacityBytes, options = {}) {
831
+ const { registryId, registryUrl, registryUsername, registryPassword, timeout = 60, signal, } = options;
832
+ const url = `${this._baseUrl}/snapshots`;
833
+ const payload = {
834
+ name,
835
+ docker_image: dockerImage,
836
+ fs_capacity_bytes: fsCapacityBytes,
837
+ };
838
+ if (registryId !== undefined) {
839
+ payload.registry_id = registryId;
840
+ }
841
+ if (registryUrl !== undefined) {
842
+ payload.registry_url = registryUrl;
843
+ }
844
+ if (registryUsername !== undefined) {
845
+ payload.registry_username = registryUsername;
846
+ }
847
+ if (registryPassword !== undefined) {
848
+ payload.registry_password = registryPassword;
849
+ }
850
+ const response = await this._postJson(url, payload, { signal });
851
+ const snapshot = (await response.json());
852
+ return this.waitForSnapshot(snapshot.id, { timeout, signal });
853
+ }
854
+ /**
855
+ * Capture a snapshot from a running sandbox.
856
+ *
857
+ * Blocks until the snapshot is ready (polls with 2s interval).
858
+ *
859
+ * @param sandboxName - Name of the sandbox to capture from.
860
+ * @param name - Snapshot name.
861
+ * @param options - Capture options (checkpoint, timeout).
862
+ * @returns Snapshot in "ready" status.
863
+ */
864
+ async captureSnapshot(sandboxName, name, options = {}) {
865
+ const { checkpoint, timeout = 60, signal } = options;
866
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(sandboxName)}/snapshot`;
867
+ const payload = { name };
868
+ if (checkpoint !== undefined) {
869
+ payload.checkpoint = checkpoint;
870
+ }
871
+ const response = await this._postJson(url, payload, { signal });
872
+ const snapshot = (await response.json());
873
+ return this.waitForSnapshot(snapshot.id, { timeout, signal });
874
+ }
875
+ /**
876
+ * Get a snapshot by ID.
877
+ *
878
+ * @param snapshotId - Snapshot UUID.
879
+ * @returns Snapshot.
880
+ */
881
+ async getSnapshot(snapshotId, options) {
882
+ const url = `${this._baseUrl}/snapshots/${encodeURIComponent(snapshotId)}`;
883
+ const response = await this._fetch(url, { signal: options?.signal });
884
+ if (!response.ok) {
885
+ if (response.status === 404) {
886
+ throw new LangSmithResourceNotFoundError(`Snapshot '${snapshotId}' not found`, "snapshot");
887
+ }
888
+ await handleClientHttpError(response);
889
+ }
890
+ return (await response.json());
891
+ }
892
+ /**
893
+ * List all snapshots.
894
+ *
895
+ * @returns List of Snapshots.
896
+ */
897
+ async listSnapshots() {
898
+ const url = `${this._baseUrl}/snapshots`;
899
+ const response = await this._fetch(url);
900
+ if (!response.ok) {
901
+ await handleClientHttpError(response);
902
+ }
903
+ const data = await response.json();
904
+ return (data.snapshots ?? []);
905
+ }
906
+ /**
907
+ * Delete a snapshot.
908
+ *
909
+ * @param snapshotId - Snapshot UUID.
910
+ */
911
+ async deleteSnapshot(snapshotId) {
912
+ const url = `${this._baseUrl}/snapshots/${encodeURIComponent(snapshotId)}`;
913
+ const response = await this._fetch(url, { method: "DELETE" });
914
+ if (!response.ok) {
915
+ await handleClientHttpError(response);
916
+ }
917
+ }
918
+ /**
919
+ * Poll until a snapshot reaches "ready" or "failed" status.
920
+ *
921
+ * @param snapshotId - Snapshot UUID.
922
+ * @param options - Polling options (timeout, pollInterval).
923
+ * @returns Snapshot in "ready" status.
924
+ */
925
+ async waitForSnapshot(snapshotId, options = {}) {
926
+ const { timeout = 300, pollInterval = 2.0, signal } = options;
927
+ const deadline = Date.now() + timeout * 1000;
928
+ let lastStatus = "building";
929
+ while (Date.now() < deadline) {
930
+ signal?.throwIfAborted();
931
+ const snapshot = await this.getSnapshot(snapshotId, { signal });
932
+ lastStatus = snapshot.status;
933
+ if (snapshot.status === "ready") {
934
+ return snapshot;
935
+ }
936
+ if (snapshot.status === "failed") {
937
+ throw new LangSmithResourceCreationError(snapshot.status_message ?? `Snapshot '${snapshotId}' build failed`, "snapshot");
938
+ }
939
+ // Cap sleep to remaining time + jitter
940
+ const remaining = deadline - Date.now();
941
+ const jitter = pollInterval * 200 * (Math.random() - 0.5); // ±10%
942
+ const delay = Math.min(pollInterval * 1000 + jitter, remaining);
943
+ if (delay > 0) {
944
+ await sleepWithSignal(delay, signal);
945
+ }
946
+ }
947
+ throw new LangSmithResourceTimeoutError(`Snapshot '${snapshotId}' did not become ready within ${timeout}s`, "snapshot", lastStatus);
948
+ }
726
949
  }
@@ -25,9 +25,6 @@
25
25
  */
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = exports.CommandHandle = exports.Sandbox = exports.SandboxClient = void 0;
28
- // Emit warning on import (alpha feature)
29
- console.warn("langsmith/experimental/sandbox is in alpha. " +
30
- "This feature is experimental, and breaking changes are expected.");
31
28
  // Main classes
32
29
  var client_js_1 = require("./client.cjs");
33
30
  Object.defineProperty(exports, "SandboxClient", { enumerable: true, get: function () { return client_js_1.SandboxClient; } });
@@ -25,5 +25,5 @@
25
25
  export { SandboxClient } from "./client.js";
26
26
  export { Sandbox } from "./sandbox.js";
27
27
  export { CommandHandle } from "./command_handle.js";
28
- export type { ExecutionResult, OutputChunk, WsMessage, WsRunOptions, ResourceSpec, ResourceStatus, VolumeMountSpec, Volume, SandboxTemplate, Pool, SandboxData, SandboxClientConfig, RunOptions, CreateSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, CreateVolumeOptions, CreateTemplateOptions, UpdateTemplateOptions, CreatePoolOptions, UpdateVolumeOptions, UpdatePoolOptions, } from "./types.js";
28
+ export type { ExecutionResult, OutputChunk, WsMessage, WsRunOptions, ResourceSpec, ResourceStatus, Snapshot, VolumeMountSpec, Volume, SandboxTemplate, Pool, SandboxData, SandboxClientConfig, RunOptions, CreateSandboxOptions, CreateSnapshotOptions, CaptureSnapshotOptions, WaitForSnapshotOptions, StartSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, CreateVolumeOptions, CreateTemplateOptions, UpdateTemplateOptions, CreatePoolOptions, UpdateVolumeOptions, UpdatePoolOptions, } from "./types.js";
29
29
  export { LangSmithSandboxError, LangSmithSandboxAPIError, LangSmithSandboxAuthenticationError, LangSmithSandboxConnectionError, LangSmithSandboxServerReloadError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithResourceInUseError, LangSmithResourceAlreadyExistsError, LangSmithResourceNameConflictError, LangSmithValidationError, LangSmithQuotaExceededError, LangSmithResourceCreationError, LangSmithSandboxCreationError, LangSmithSandboxNotReadyError, LangSmithSandboxOperationError, LangSmithCommandTimeoutError, LangSmithDataplaneNotConfiguredError, } from "./errors.js";
@@ -22,9 +22,6 @@
22
22
  *
23
23
  * @packageDocumentation
24
24
  */
25
- // Emit warning on import (alpha feature)
26
- console.warn("langsmith/experimental/sandbox is in alpha. " +
27
- "This feature is experimental, and breaking changes are expected.");
28
25
  // Main classes
29
26
  export { SandboxClient } from "./client.js";
30
27
  export { Sandbox } from "./sandbox.js";
@@ -25,6 +25,8 @@ const ws_execute_js_1 = require("./ws_execute.cjs");
25
25
  * await sandbox.delete();
26
26
  * }
27
27
  * ```
28
+ *
29
+ * @experimental This feature is experimental, and breaking changes are expected.
28
30
  */
29
31
  class Sandbox {
30
32
  /** @internal */
@@ -50,7 +52,7 @@ class Sandbox {
50
52
  writable: true,
51
53
  value: void 0
52
54
  });
53
- /** Provisioning status ("provisioning", "ready", "failed"). */
55
+ /** Provisioning status ("provisioning", "ready", "failed", "stopped"). */
54
56
  Object.defineProperty(this, "status", {
55
57
  enumerable: true,
56
58
  configurable: true,
@@ -106,6 +108,34 @@ class Sandbox {
106
108
  writable: true,
107
109
  value: void 0
108
110
  });
111
+ /** Snapshot ID used to create this sandbox. */
112
+ Object.defineProperty(this, "snapshot_id", {
113
+ enumerable: true,
114
+ configurable: true,
115
+ writable: true,
116
+ value: void 0
117
+ });
118
+ /** Number of vCPUs allocated. */
119
+ Object.defineProperty(this, "vCpus", {
120
+ enumerable: true,
121
+ configurable: true,
122
+ writable: true,
123
+ value: void 0
124
+ });
125
+ /** Memory allocation in bytes. */
126
+ Object.defineProperty(this, "mem_bytes", {
127
+ enumerable: true,
128
+ configurable: true,
129
+ writable: true,
130
+ value: void 0
131
+ });
132
+ /** Root filesystem capacity in bytes. */
133
+ Object.defineProperty(this, "fs_capacity_bytes", {
134
+ enumerable: true,
135
+ configurable: true,
136
+ writable: true,
137
+ value: void 0
138
+ });
109
139
  Object.defineProperty(this, "_client", {
110
140
  enumerable: true,
111
141
  configurable: true,
@@ -123,6 +153,10 @@ class Sandbox {
123
153
  this.ttl_seconds = data.ttl_seconds;
124
154
  this.idle_ttl_seconds = data.idle_ttl_seconds;
125
155
  this.expires_at = data.expires_at;
156
+ this.snapshot_id = data.snapshot_id;
157
+ this.vCpus = data.vcpus;
158
+ this.mem_bytes = data.mem_bytes;
159
+ this.fs_capacity_bytes = data.fs_capacity_bytes;
126
160
  this._client = client;
127
161
  }
128
162
  /**
@@ -337,5 +371,35 @@ class Sandbox {
337
371
  async delete() {
338
372
  await this._client.deleteSandbox(this.name);
339
373
  }
374
+ /**
375
+ * Start a stopped sandbox and wait until ready.
376
+ *
377
+ * Updates this sandbox's status and dataplane_url in place.
378
+ *
379
+ * @param timeout - Timeout in seconds when waiting for ready. Default: 120.
380
+ */
381
+ async start(options = {}) {
382
+ const refreshed = await this._client.startSandbox(this.name, options);
383
+ this.status = refreshed.status;
384
+ this.dataplane_url = refreshed.dataplane_url;
385
+ }
386
+ /**
387
+ * Stop a running sandbox (preserves sandbox files for later restart).
388
+ */
389
+ async stop() {
390
+ await this._client.stopSandbox(this.name);
391
+ this.status = "stopped";
392
+ this.dataplane_url = undefined;
393
+ }
394
+ /**
395
+ * Capture a snapshot from this sandbox.
396
+ *
397
+ * @param name - Snapshot name.
398
+ * @param options - Capture options (checkpoint, timeout).
399
+ * @returns Snapshot in "ready" status.
400
+ */
401
+ async captureSnapshot(name, options = {}) {
402
+ return this._client.captureSnapshot(this.name, name, options);
403
+ }
340
404
  }
341
405
  exports.Sandbox = Sandbox;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Sandbox class for interacting with a specific sandbox instance.
3
3
  */
4
- import type { ExecutionResult, RunOptions } from "./types.js";
4
+ import type { CaptureSnapshotOptions, ExecutionResult, RunOptions, Snapshot, StartSandboxOptions } from "./types.js";
5
5
  import { CommandHandle } from "./command_handle.js";
6
6
  /**
7
7
  * Represents an active sandbox for running commands and file operations.
@@ -20,16 +20,18 @@ import { CommandHandle } from "./command_handle.js";
20
20
  * await sandbox.delete();
21
21
  * }
22
22
  * ```
23
+ *
24
+ * @experimental This feature is experimental, and breaking changes are expected.
23
25
  */
24
26
  export declare class Sandbox {
25
27
  /** Display name (can be updated). */
26
28
  readonly name: string;
27
29
  /** Name of the template used to create this sandbox. */
28
- readonly template_name: string;
30
+ readonly template_name?: string;
29
31
  /** URL for data plane operations (file I/O, command execution). */
30
- readonly dataplane_url?: string;
31
- /** Provisioning status ("provisioning", "ready", "failed"). */
32
- readonly status?: string;
32
+ dataplane_url?: string;
33
+ /** Provisioning status ("provisioning", "ready", "failed", "stopped"). */
34
+ status?: string;
33
35
  /** Human-readable status message (e.g., error details when failed). */
34
36
  readonly status_message?: string;
35
37
  /** Unique identifier (UUID). Remains constant even if name changes. */
@@ -44,6 +46,14 @@ export declare class Sandbox {
44
46
  readonly idle_ttl_seconds?: number;
45
47
  /** Computed expiration timestamp when a TTL is active. */
46
48
  readonly expires_at?: string;
49
+ /** Snapshot ID used to create this sandbox. */
50
+ readonly snapshot_id?: string;
51
+ /** Number of vCPUs allocated. */
52
+ readonly vCpus?: number;
53
+ /** Memory allocation in bytes. */
54
+ readonly mem_bytes?: number;
55
+ /** Root filesystem capacity in bytes. */
56
+ readonly fs_capacity_bytes?: number;
47
57
  private _client;
48
58
  /**
49
59
  * Validate and return the dataplane URL.
@@ -145,4 +155,24 @@ export declare class Sandbox {
145
155
  * ```
146
156
  */
147
157
  delete(): Promise<void>;
158
+ /**
159
+ * Start a stopped sandbox and wait until ready.
160
+ *
161
+ * Updates this sandbox's status and dataplane_url in place.
162
+ *
163
+ * @param timeout - Timeout in seconds when waiting for ready. Default: 120.
164
+ */
165
+ start(options?: StartSandboxOptions): Promise<void>;
166
+ /**
167
+ * Stop a running sandbox (preserves sandbox files for later restart).
168
+ */
169
+ stop(): Promise<void>;
170
+ /**
171
+ * Capture a snapshot from this sandbox.
172
+ *
173
+ * @param name - Snapshot name.
174
+ * @param options - Capture options (checkpoint, timeout).
175
+ * @returns Snapshot in "ready" status.
176
+ */
177
+ captureSnapshot(name: string, options?: CaptureSnapshotOptions): Promise<Snapshot>;
148
178
  }
@@ -22,6 +22,8 @@ import { reconnectWsStream, runWsStream } from "./ws_execute.js";
22
22
  * await sandbox.delete();
23
23
  * }
24
24
  * ```
25
+ *
26
+ * @experimental This feature is experimental, and breaking changes are expected.
25
27
  */
26
28
  export class Sandbox {
27
29
  /** @internal */
@@ -47,7 +49,7 @@ export class Sandbox {
47
49
  writable: true,
48
50
  value: void 0
49
51
  });
50
- /** Provisioning status ("provisioning", "ready", "failed"). */
52
+ /** Provisioning status ("provisioning", "ready", "failed", "stopped"). */
51
53
  Object.defineProperty(this, "status", {
52
54
  enumerable: true,
53
55
  configurable: true,
@@ -103,6 +105,34 @@ export class Sandbox {
103
105
  writable: true,
104
106
  value: void 0
105
107
  });
108
+ /** Snapshot ID used to create this sandbox. */
109
+ Object.defineProperty(this, "snapshot_id", {
110
+ enumerable: true,
111
+ configurable: true,
112
+ writable: true,
113
+ value: void 0
114
+ });
115
+ /** Number of vCPUs allocated. */
116
+ Object.defineProperty(this, "vCpus", {
117
+ enumerable: true,
118
+ configurable: true,
119
+ writable: true,
120
+ value: void 0
121
+ });
122
+ /** Memory allocation in bytes. */
123
+ Object.defineProperty(this, "mem_bytes", {
124
+ enumerable: true,
125
+ configurable: true,
126
+ writable: true,
127
+ value: void 0
128
+ });
129
+ /** Root filesystem capacity in bytes. */
130
+ Object.defineProperty(this, "fs_capacity_bytes", {
131
+ enumerable: true,
132
+ configurable: true,
133
+ writable: true,
134
+ value: void 0
135
+ });
106
136
  Object.defineProperty(this, "_client", {
107
137
  enumerable: true,
108
138
  configurable: true,
@@ -120,6 +150,10 @@ export class Sandbox {
120
150
  this.ttl_seconds = data.ttl_seconds;
121
151
  this.idle_ttl_seconds = data.idle_ttl_seconds;
122
152
  this.expires_at = data.expires_at;
153
+ this.snapshot_id = data.snapshot_id;
154
+ this.vCpus = data.vcpus;
155
+ this.mem_bytes = data.mem_bytes;
156
+ this.fs_capacity_bytes = data.fs_capacity_bytes;
123
157
  this._client = client;
124
158
  }
125
159
  /**
@@ -334,4 +368,34 @@ export class Sandbox {
334
368
  async delete() {
335
369
  await this._client.deleteSandbox(this.name);
336
370
  }
371
+ /**
372
+ * Start a stopped sandbox and wait until ready.
373
+ *
374
+ * Updates this sandbox's status and dataplane_url in place.
375
+ *
376
+ * @param timeout - Timeout in seconds when waiting for ready. Default: 120.
377
+ */
378
+ async start(options = {}) {
379
+ const refreshed = await this._client.startSandbox(this.name, options);
380
+ this.status = refreshed.status;
381
+ this.dataplane_url = refreshed.dataplane_url;
382
+ }
383
+ /**
384
+ * Stop a running sandbox (preserves sandbox files for later restart).
385
+ */
386
+ async stop() {
387
+ await this._client.stopSandbox(this.name);
388
+ this.status = "stopped";
389
+ this.dataplane_url = undefined;
390
+ }
391
+ /**
392
+ * Capture a snapshot from this sandbox.
393
+ *
394
+ * @param name - Snapshot name.
395
+ * @param options - Capture options (checkpoint, timeout).
396
+ * @returns Snapshot in "ready" status.
397
+ */
398
+ async captureSnapshot(name, options = {}) {
399
+ return this._client.captureSnapshot(this.name, name, options);
400
+ }
337
401
  }