langsmith 0.5.19 → 0.5.21

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.
@@ -10,6 +10,27 @@ const async_caller_js_1 = require("../../utils/async_caller.cjs");
10
10
  const sandbox_js_1 = require("./sandbox.cjs");
11
11
  const errors_js_1 = require("./errors.cjs");
12
12
  const helpers_js_1 = require("./helpers.cjs");
13
+ /**
14
+ * Sleep that can be interrupted by an AbortSignal.
15
+ * Resolves after `ms` milliseconds or rejects immediately if the signal fires.
16
+ */
17
+ function sleepWithSignal(ms, signal) {
18
+ if (!signal) {
19
+ return new Promise((resolve) => setTimeout(resolve, ms));
20
+ }
21
+ signal.throwIfAborted();
22
+ return new Promise((resolve, reject) => {
23
+ const timer = setTimeout(() => {
24
+ signal.removeEventListener("abort", onAbort);
25
+ resolve();
26
+ }, ms);
27
+ function onAbort() {
28
+ clearTimeout(timer);
29
+ reject(signal.reason);
30
+ }
31
+ signal.addEventListener("abort", onAbort, { once: true });
32
+ });
33
+ }
13
34
  /**
14
35
  * Get the default sandbox API endpoint from environment.
15
36
  *
@@ -115,6 +136,25 @@ class SandboxClient {
115
136
  getApiKey() {
116
137
  return this._apiKey;
117
138
  }
139
+ /**
140
+ * JSON POST helper. Sends JSON body, checks response status,
141
+ * and returns the Response for further processing.
142
+ * Throws on non-ok responses via handleClientHttpError.
143
+ * Callers can add specific status checks (e.g. 404) before calling this.
144
+ * @internal
145
+ */
146
+ async _postJson(url, body, options) {
147
+ const response = await this._fetch(url, {
148
+ method: "POST",
149
+ headers: { "Content-Type": "application/json" },
150
+ body: JSON.stringify(body),
151
+ signal: options?.signal,
152
+ });
153
+ if (!response.ok) {
154
+ await (0, helpers_js_1.handleClientHttpError)(response);
155
+ }
156
+ return response;
157
+ }
118
158
  // =========================================================================
119
159
  // Volume Operations
120
160
  // =========================================================================
@@ -540,14 +580,25 @@ class SandboxClient {
540
580
  * ```
541
581
  */
542
582
  async createSandbox(templateName, options = {}) {
543
- const { name, timeout = 30, waitForReady = true, ttlSeconds, idleTtlSeconds, } = options;
583
+ const { snapshotId, name, timeout = 30, waitForReady = true, ttlSeconds, idleTtlSeconds, vCpus, memBytes, fsCapacityBytes, proxyConfig, } = options;
584
+ if (!templateName && !snapshotId) {
585
+ throw new Error("Either templateName or snapshotId is required");
586
+ }
587
+ if (templateName && snapshotId) {
588
+ throw new Error("Cannot specify both templateName and snapshotId");
589
+ }
544
590
  (0, helpers_js_1.validateTtl)(ttlSeconds, "ttlSeconds");
545
591
  (0, helpers_js_1.validateTtl)(idleTtlSeconds, "idleTtlSeconds");
546
592
  const url = `${this._baseUrl}/boxes`;
547
593
  const payload = {
548
- template_name: templateName,
549
594
  wait_for_ready: waitForReady,
550
595
  };
596
+ if (templateName) {
597
+ payload.template_name = templateName;
598
+ }
599
+ if (snapshotId) {
600
+ payload.snapshot_id = snapshotId;
601
+ }
551
602
  if (waitForReady) {
552
603
  payload.timeout = timeout;
553
604
  }
@@ -560,6 +611,18 @@ class SandboxClient {
560
611
  if (idleTtlSeconds !== undefined) {
561
612
  payload.idle_ttl_seconds = idleTtlSeconds;
562
613
  }
614
+ if (vCpus !== undefined) {
615
+ payload.vcpus = vCpus;
616
+ }
617
+ if (memBytes !== undefined) {
618
+ payload.mem_bytes = memBytes;
619
+ }
620
+ if (fsCapacityBytes !== undefined) {
621
+ payload.fs_capacity_bytes = fsCapacityBytes;
622
+ }
623
+ if (proxyConfig !== undefined) {
624
+ payload.proxy_config = proxyConfig;
625
+ }
563
626
  const httpTimeout = waitForReady ? (timeout + 30) * 1000 : 30 * 1000;
564
627
  const response = await this._fetch(url, {
565
628
  method: "POST",
@@ -582,9 +645,9 @@ class SandboxClient {
582
645
  * @returns Sandbox.
583
646
  * @throws LangSmithResourceNotFoundError if sandbox not found.
584
647
  */
585
- async getSandbox(name) {
648
+ async getSandbox(name, options) {
586
649
  const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}`;
587
- const response = await this._fetch(url);
650
+ const response = await this._fetch(url, { signal: options?.signal });
588
651
  if (!response.ok) {
589
652
  if (response.status === 404) {
590
653
  throw new errors_js_1.LangSmithResourceNotFoundError(`Sandbox '${name}' not found`, "sandbox");
@@ -679,9 +742,9 @@ class SandboxClient {
679
742
  * @returns ResourceStatus with status and optional status_message.
680
743
  * @throws LangSmithResourceNotFoundError if sandbox not found.
681
744
  */
682
- async getSandboxStatus(name) {
745
+ async getSandboxStatus(name, options) {
683
746
  const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/status`;
684
- const response = await this._fetch(url);
747
+ const response = await this._fetch(url, { signal: options?.signal });
685
748
  if (!response.ok) {
686
749
  if (response.status === 404) {
687
750
  throw new errors_js_1.LangSmithResourceNotFoundError(`Sandbox '${name}' not found`, "sandbox");
@@ -711,22 +774,183 @@ class SandboxClient {
711
774
  * ```
712
775
  */
713
776
  async waitForSandbox(name, options = {}) {
714
- const { timeout = 120, pollInterval = 1.0 } = options;
777
+ const { timeout = 120, pollInterval = 1.0, signal } = options;
715
778
  const deadline = Date.now() + timeout * 1000;
716
779
  let lastStatus = "provisioning";
717
780
  while (Date.now() < deadline) {
718
- const statusResult = await this.getSandboxStatus(name);
781
+ signal?.throwIfAborted();
782
+ const statusResult = await this.getSandboxStatus(name, { signal });
719
783
  lastStatus = statusResult.status;
720
784
  if (statusResult.status === "ready") {
721
- return this.getSandbox(name);
785
+ return this.getSandbox(name, { signal });
722
786
  }
723
787
  if (statusResult.status === "failed") {
724
788
  throw new errors_js_1.LangSmithResourceCreationError(statusResult.status_message ?? `Sandbox '${name}' creation failed`, "sandbox");
725
789
  }
726
- // Wait before polling again
727
- await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
790
+ // Wait before polling again, capped to remaining time + jitter
791
+ const remaining = deadline - Date.now();
792
+ const jitter = pollInterval * 200 * (Math.random() - 0.5); // ±10%
793
+ const delay = Math.min(pollInterval * 1000 + jitter, remaining);
794
+ if (delay > 0) {
795
+ await sleepWithSignal(delay, signal);
796
+ }
728
797
  }
729
798
  throw new errors_js_1.LangSmithResourceTimeoutError(`Sandbox '${name}' did not become ready within ${timeout}s`, "sandbox", lastStatus);
730
799
  }
800
+ /**
801
+ * Start a stopped sandbox and wait until ready.
802
+ *
803
+ * @param name - Sandbox name.
804
+ * @param options - Options with timeout.
805
+ * @returns Sandbox in "ready" status.
806
+ */
807
+ async startSandbox(name, options = {}) {
808
+ const { timeout = 120, signal } = options;
809
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/start`;
810
+ await this._postJson(url, {}, { signal });
811
+ return this.waitForSandbox(name, { timeout, signal });
812
+ }
813
+ /**
814
+ * Stop a running sandbox (preserves sandbox files for later restart).
815
+ *
816
+ * @param name - Sandbox name.
817
+ */
818
+ async stopSandbox(name) {
819
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/stop`;
820
+ await this._postJson(url, {});
821
+ }
822
+ // =========================================================================
823
+ // Snapshot Operations
824
+ // =========================================================================
825
+ /**
826
+ * Build a snapshot from a Docker image.
827
+ *
828
+ * Blocks until the snapshot is ready (polls with 2s interval).
829
+ *
830
+ * @param name - Snapshot name.
831
+ * @param dockerImage - Docker image to build from (e.g., "python:3.12-slim").
832
+ * @param fsCapacityBytes - Filesystem capacity in bytes.
833
+ * @param options - Additional options (registry credentials, timeout).
834
+ * @returns Snapshot in "ready" status.
835
+ */
836
+ async createSnapshot(name, dockerImage, fsCapacityBytes, options = {}) {
837
+ const { registryId, registryUrl, registryUsername, registryPassword, timeout = 60, signal, } = options;
838
+ const url = `${this._baseUrl}/snapshots`;
839
+ const payload = {
840
+ name,
841
+ docker_image: dockerImage,
842
+ fs_capacity_bytes: fsCapacityBytes,
843
+ };
844
+ if (registryId !== undefined) {
845
+ payload.registry_id = registryId;
846
+ }
847
+ if (registryUrl !== undefined) {
848
+ payload.registry_url = registryUrl;
849
+ }
850
+ if (registryUsername !== undefined) {
851
+ payload.registry_username = registryUsername;
852
+ }
853
+ if (registryPassword !== undefined) {
854
+ payload.registry_password = registryPassword;
855
+ }
856
+ const response = await this._postJson(url, payload, { signal });
857
+ const snapshot = (await response.json());
858
+ return this.waitForSnapshot(snapshot.id, { timeout, signal });
859
+ }
860
+ /**
861
+ * Capture a snapshot from a running sandbox.
862
+ *
863
+ * Blocks until the snapshot is ready (polls with 2s interval).
864
+ *
865
+ * @param sandboxName - Name of the sandbox to capture from.
866
+ * @param name - Snapshot name.
867
+ * @param options - Capture options (checkpoint, timeout).
868
+ * @returns Snapshot in "ready" status.
869
+ */
870
+ async captureSnapshot(sandboxName, name, options = {}) {
871
+ const { checkpoint, timeout = 60, signal } = options;
872
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(sandboxName)}/snapshot`;
873
+ const payload = { name };
874
+ if (checkpoint !== undefined) {
875
+ payload.checkpoint = checkpoint;
876
+ }
877
+ const response = await this._postJson(url, payload, { signal });
878
+ const snapshot = (await response.json());
879
+ return this.waitForSnapshot(snapshot.id, { timeout, signal });
880
+ }
881
+ /**
882
+ * Get a snapshot by ID.
883
+ *
884
+ * @param snapshotId - Snapshot UUID.
885
+ * @returns Snapshot.
886
+ */
887
+ async getSnapshot(snapshotId, options) {
888
+ const url = `${this._baseUrl}/snapshots/${encodeURIComponent(snapshotId)}`;
889
+ const response = await this._fetch(url, { signal: options?.signal });
890
+ if (!response.ok) {
891
+ if (response.status === 404) {
892
+ throw new errors_js_1.LangSmithResourceNotFoundError(`Snapshot '${snapshotId}' not found`, "snapshot");
893
+ }
894
+ await (0, helpers_js_1.handleClientHttpError)(response);
895
+ }
896
+ return (await response.json());
897
+ }
898
+ /**
899
+ * List all snapshots.
900
+ *
901
+ * @returns List of Snapshots.
902
+ */
903
+ async listSnapshots() {
904
+ const url = `${this._baseUrl}/snapshots`;
905
+ const response = await this._fetch(url);
906
+ if (!response.ok) {
907
+ await (0, helpers_js_1.handleClientHttpError)(response);
908
+ }
909
+ const data = await response.json();
910
+ return (data.snapshots ?? []);
911
+ }
912
+ /**
913
+ * Delete a snapshot.
914
+ *
915
+ * @param snapshotId - Snapshot UUID.
916
+ */
917
+ async deleteSnapshot(snapshotId) {
918
+ const url = `${this._baseUrl}/snapshots/${encodeURIComponent(snapshotId)}`;
919
+ const response = await this._fetch(url, { method: "DELETE" });
920
+ if (!response.ok) {
921
+ await (0, helpers_js_1.handleClientHttpError)(response);
922
+ }
923
+ }
924
+ /**
925
+ * Poll until a snapshot reaches "ready" or "failed" status.
926
+ *
927
+ * @param snapshotId - Snapshot UUID.
928
+ * @param options - Polling options (timeout, pollInterval).
929
+ * @returns Snapshot in "ready" status.
930
+ */
931
+ async waitForSnapshot(snapshotId, options = {}) {
932
+ const { timeout = 300, pollInterval = 2.0, signal } = options;
933
+ const deadline = Date.now() + timeout * 1000;
934
+ let lastStatus = "building";
935
+ while (Date.now() < deadline) {
936
+ signal?.throwIfAborted();
937
+ const snapshot = await this.getSnapshot(snapshotId, { signal });
938
+ lastStatus = snapshot.status;
939
+ if (snapshot.status === "ready") {
940
+ return snapshot;
941
+ }
942
+ if (snapshot.status === "failed") {
943
+ throw new errors_js_1.LangSmithResourceCreationError(snapshot.status_message ?? `Snapshot '${snapshotId}' build failed`, "snapshot");
944
+ }
945
+ // Cap sleep to remaining time + jitter
946
+ const remaining = deadline - Date.now();
947
+ const jitter = pollInterval * 200 * (Math.random() - 0.5); // ±10%
948
+ const delay = Math.min(pollInterval * 1000 + jitter, remaining);
949
+ if (delay > 0) {
950
+ await sleepWithSignal(delay, signal);
951
+ }
952
+ }
953
+ throw new errors_js_1.LangSmithResourceTimeoutError(`Snapshot '${snapshotId}' did not become ready within ${timeout}s`, "snapshot", lastStatus);
954
+ }
731
955
  }
732
956
  exports.SandboxClient = SandboxClient;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Main SandboxClient class for interacting with the sandbox server API.
3
3
  */
4
- import type { CreatePoolOptions, CreateSandboxOptions, CreateTemplateOptions, CreateVolumeOptions, Pool, ResourceStatus, SandboxClientConfig, SandboxTemplate, UpdatePoolOptions, UpdateSandboxOptions, UpdateTemplateOptions, UpdateVolumeOptions, Volume, WaitForSandboxOptions } from "./types.js";
4
+ import type { CaptureSnapshotOptions, CreatePoolOptions, CreateSandboxOptions, CreateSnapshotOptions, CreateTemplateOptions, CreateVolumeOptions, Pool, ResourceStatus, SandboxClientConfig, SandboxTemplate, Snapshot, StartSandboxOptions, UpdatePoolOptions, UpdateSandboxOptions, UpdateTemplateOptions, UpdateVolumeOptions, Volume, WaitForSandboxOptions, WaitForSnapshotOptions } from "./types.js";
5
5
  import { Sandbox } from "./sandbox.js";
6
6
  /**
7
7
  * Client for interacting with the Sandbox Server API.
@@ -206,7 +206,7 @@ export declare class SandboxClient {
206
206
  * }
207
207
  * ```
208
208
  */
209
- createSandbox(templateName: string, options?: CreateSandboxOptions): Promise<Sandbox>;
209
+ createSandbox(templateName?: string, options?: CreateSandboxOptions): Promise<Sandbox>;
210
210
  /**
211
211
  * Get a Sandbox by name.
212
212
  *
@@ -216,7 +216,9 @@ export declare class SandboxClient {
216
216
  * @returns Sandbox.
217
217
  * @throws LangSmithResourceNotFoundError if sandbox not found.
218
218
  */
219
- getSandbox(name: string): Promise<Sandbox>;
219
+ getSandbox(name: string, options?: {
220
+ signal?: AbortSignal;
221
+ }): Promise<Sandbox>;
220
222
  /**
221
223
  * List all Sandboxes.
222
224
  *
@@ -258,7 +260,9 @@ export declare class SandboxClient {
258
260
  * @returns ResourceStatus with status and optional status_message.
259
261
  * @throws LangSmithResourceNotFoundError if sandbox not found.
260
262
  */
261
- getSandboxStatus(name: string): Promise<ResourceStatus>;
263
+ getSandboxStatus(name: string, options?: {
264
+ signal?: AbortSignal;
265
+ }): Promise<ResourceStatus>;
262
266
  /**
263
267
  * Wait for a sandbox to become ready.
264
268
  *
@@ -280,4 +284,70 @@ export declare class SandboxClient {
280
284
  * ```
281
285
  */
282
286
  waitForSandbox(name: string, options?: WaitForSandboxOptions): Promise<Sandbox>;
287
+ /**
288
+ * Start a stopped sandbox and wait until ready.
289
+ *
290
+ * @param name - Sandbox name.
291
+ * @param options - Options with timeout.
292
+ * @returns Sandbox in "ready" status.
293
+ */
294
+ startSandbox(name: string, options?: StartSandboxOptions): Promise<Sandbox>;
295
+ /**
296
+ * Stop a running sandbox (preserves sandbox files for later restart).
297
+ *
298
+ * @param name - Sandbox name.
299
+ */
300
+ stopSandbox(name: string): Promise<void>;
301
+ /**
302
+ * Build a snapshot from a Docker image.
303
+ *
304
+ * Blocks until the snapshot is ready (polls with 2s interval).
305
+ *
306
+ * @param name - Snapshot name.
307
+ * @param dockerImage - Docker image to build from (e.g., "python:3.12-slim").
308
+ * @param fsCapacityBytes - Filesystem capacity in bytes.
309
+ * @param options - Additional options (registry credentials, timeout).
310
+ * @returns Snapshot in "ready" status.
311
+ */
312
+ createSnapshot(name: string, dockerImage: string, fsCapacityBytes: number, options?: CreateSnapshotOptions): Promise<Snapshot>;
313
+ /**
314
+ * Capture a snapshot from a running sandbox.
315
+ *
316
+ * Blocks until the snapshot is ready (polls with 2s interval).
317
+ *
318
+ * @param sandboxName - Name of the sandbox to capture from.
319
+ * @param name - Snapshot name.
320
+ * @param options - Capture options (checkpoint, timeout).
321
+ * @returns Snapshot in "ready" status.
322
+ */
323
+ captureSnapshot(sandboxName: string, name: string, options?: CaptureSnapshotOptions): Promise<Snapshot>;
324
+ /**
325
+ * Get a snapshot by ID.
326
+ *
327
+ * @param snapshotId - Snapshot UUID.
328
+ * @returns Snapshot.
329
+ */
330
+ getSnapshot(snapshotId: string, options?: {
331
+ signal?: AbortSignal;
332
+ }): Promise<Snapshot>;
333
+ /**
334
+ * List all snapshots.
335
+ *
336
+ * @returns List of Snapshots.
337
+ */
338
+ listSnapshots(): Promise<Snapshot[]>;
339
+ /**
340
+ * Delete a snapshot.
341
+ *
342
+ * @param snapshotId - Snapshot UUID.
343
+ */
344
+ deleteSnapshot(snapshotId: string): Promise<void>;
345
+ /**
346
+ * Poll until a snapshot reaches "ready" or "failed" status.
347
+ *
348
+ * @param snapshotId - Snapshot UUID.
349
+ * @param options - Polling options (timeout, pollInterval).
350
+ * @returns Snapshot in "ready" status.
351
+ */
352
+ waitForSnapshot(snapshotId: string, options?: WaitForSnapshotOptions): Promise<Snapshot>;
283
353
  }
@@ -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
  *
@@ -112,6 +133,25 @@ export class SandboxClient {
112
133
  getApiKey() {
113
134
  return this._apiKey;
114
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
+ }
115
155
  // =========================================================================
116
156
  // Volume Operations
117
157
  // =========================================================================
@@ -537,14 +577,25 @@ export class SandboxClient {
537
577
  * ```
538
578
  */
539
579
  async createSandbox(templateName, options = {}) {
540
- const { name, timeout = 30, waitForReady = true, ttlSeconds, idleTtlSeconds, } = options;
580
+ const { snapshotId, name, timeout = 30, waitForReady = true, ttlSeconds, idleTtlSeconds, vCpus, memBytes, fsCapacityBytes, proxyConfig, } = 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
+ }
541
587
  validateTtl(ttlSeconds, "ttlSeconds");
542
588
  validateTtl(idleTtlSeconds, "idleTtlSeconds");
543
589
  const url = `${this._baseUrl}/boxes`;
544
590
  const payload = {
545
- template_name: templateName,
546
591
  wait_for_ready: waitForReady,
547
592
  };
593
+ if (templateName) {
594
+ payload.template_name = templateName;
595
+ }
596
+ if (snapshotId) {
597
+ payload.snapshot_id = snapshotId;
598
+ }
548
599
  if (waitForReady) {
549
600
  payload.timeout = timeout;
550
601
  }
@@ -557,6 +608,18 @@ export class SandboxClient {
557
608
  if (idleTtlSeconds !== undefined) {
558
609
  payload.idle_ttl_seconds = idleTtlSeconds;
559
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
+ }
620
+ if (proxyConfig !== undefined) {
621
+ payload.proxy_config = proxyConfig;
622
+ }
560
623
  const httpTimeout = waitForReady ? (timeout + 30) * 1000 : 30 * 1000;
561
624
  const response = await this._fetch(url, {
562
625
  method: "POST",
@@ -579,9 +642,9 @@ export class SandboxClient {
579
642
  * @returns Sandbox.
580
643
  * @throws LangSmithResourceNotFoundError if sandbox not found.
581
644
  */
582
- async getSandbox(name) {
645
+ async getSandbox(name, options) {
583
646
  const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}`;
584
- const response = await this._fetch(url);
647
+ const response = await this._fetch(url, { signal: options?.signal });
585
648
  if (!response.ok) {
586
649
  if (response.status === 404) {
587
650
  throw new LangSmithResourceNotFoundError(`Sandbox '${name}' not found`, "sandbox");
@@ -676,9 +739,9 @@ export class SandboxClient {
676
739
  * @returns ResourceStatus with status and optional status_message.
677
740
  * @throws LangSmithResourceNotFoundError if sandbox not found.
678
741
  */
679
- async getSandboxStatus(name) {
742
+ async getSandboxStatus(name, options) {
680
743
  const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/status`;
681
- const response = await this._fetch(url);
744
+ const response = await this._fetch(url, { signal: options?.signal });
682
745
  if (!response.ok) {
683
746
  if (response.status === 404) {
684
747
  throw new LangSmithResourceNotFoundError(`Sandbox '${name}' not found`, "sandbox");
@@ -708,21 +771,182 @@ export class SandboxClient {
708
771
  * ```
709
772
  */
710
773
  async waitForSandbox(name, options = {}) {
711
- const { timeout = 120, pollInterval = 1.0 } = options;
774
+ const { timeout = 120, pollInterval = 1.0, signal } = options;
712
775
  const deadline = Date.now() + timeout * 1000;
713
776
  let lastStatus = "provisioning";
714
777
  while (Date.now() < deadline) {
715
- const statusResult = await this.getSandboxStatus(name);
778
+ signal?.throwIfAborted();
779
+ const statusResult = await this.getSandboxStatus(name, { signal });
716
780
  lastStatus = statusResult.status;
717
781
  if (statusResult.status === "ready") {
718
- return this.getSandbox(name);
782
+ return this.getSandbox(name, { signal });
719
783
  }
720
784
  if (statusResult.status === "failed") {
721
785
  throw new LangSmithResourceCreationError(statusResult.status_message ?? `Sandbox '${name}' creation failed`, "sandbox");
722
786
  }
723
- // Wait before polling again
724
- await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
787
+ // Wait before polling again, capped to remaining time + jitter
788
+ const remaining = deadline - Date.now();
789
+ const jitter = pollInterval * 200 * (Math.random() - 0.5); // ±10%
790
+ const delay = Math.min(pollInterval * 1000 + jitter, remaining);
791
+ if (delay > 0) {
792
+ await sleepWithSignal(delay, signal);
793
+ }
725
794
  }
726
795
  throw new LangSmithResourceTimeoutError(`Sandbox '${name}' did not become ready within ${timeout}s`, "sandbox", lastStatus);
727
796
  }
797
+ /**
798
+ * Start a stopped sandbox and wait until ready.
799
+ *
800
+ * @param name - Sandbox name.
801
+ * @param options - Options with timeout.
802
+ * @returns Sandbox in "ready" status.
803
+ */
804
+ async startSandbox(name, options = {}) {
805
+ const { timeout = 120, signal } = options;
806
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/start`;
807
+ await this._postJson(url, {}, { signal });
808
+ return this.waitForSandbox(name, { timeout, signal });
809
+ }
810
+ /**
811
+ * Stop a running sandbox (preserves sandbox files for later restart).
812
+ *
813
+ * @param name - Sandbox name.
814
+ */
815
+ async stopSandbox(name) {
816
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/stop`;
817
+ await this._postJson(url, {});
818
+ }
819
+ // =========================================================================
820
+ // Snapshot Operations
821
+ // =========================================================================
822
+ /**
823
+ * Build a snapshot from a Docker image.
824
+ *
825
+ * Blocks until the snapshot is ready (polls with 2s interval).
826
+ *
827
+ * @param name - Snapshot name.
828
+ * @param dockerImage - Docker image to build from (e.g., "python:3.12-slim").
829
+ * @param fsCapacityBytes - Filesystem capacity in bytes.
830
+ * @param options - Additional options (registry credentials, timeout).
831
+ * @returns Snapshot in "ready" status.
832
+ */
833
+ async createSnapshot(name, dockerImage, fsCapacityBytes, options = {}) {
834
+ const { registryId, registryUrl, registryUsername, registryPassword, timeout = 60, signal, } = options;
835
+ const url = `${this._baseUrl}/snapshots`;
836
+ const payload = {
837
+ name,
838
+ docker_image: dockerImage,
839
+ fs_capacity_bytes: fsCapacityBytes,
840
+ };
841
+ if (registryId !== undefined) {
842
+ payload.registry_id = registryId;
843
+ }
844
+ if (registryUrl !== undefined) {
845
+ payload.registry_url = registryUrl;
846
+ }
847
+ if (registryUsername !== undefined) {
848
+ payload.registry_username = registryUsername;
849
+ }
850
+ if (registryPassword !== undefined) {
851
+ payload.registry_password = registryPassword;
852
+ }
853
+ const response = await this._postJson(url, payload, { signal });
854
+ const snapshot = (await response.json());
855
+ return this.waitForSnapshot(snapshot.id, { timeout, signal });
856
+ }
857
+ /**
858
+ * Capture a snapshot from a running sandbox.
859
+ *
860
+ * Blocks until the snapshot is ready (polls with 2s interval).
861
+ *
862
+ * @param sandboxName - Name of the sandbox to capture from.
863
+ * @param name - Snapshot name.
864
+ * @param options - Capture options (checkpoint, timeout).
865
+ * @returns Snapshot in "ready" status.
866
+ */
867
+ async captureSnapshot(sandboxName, name, options = {}) {
868
+ const { checkpoint, timeout = 60, signal } = options;
869
+ const url = `${this._baseUrl}/boxes/${encodeURIComponent(sandboxName)}/snapshot`;
870
+ const payload = { name };
871
+ if (checkpoint !== undefined) {
872
+ payload.checkpoint = checkpoint;
873
+ }
874
+ const response = await this._postJson(url, payload, { signal });
875
+ const snapshot = (await response.json());
876
+ return this.waitForSnapshot(snapshot.id, { timeout, signal });
877
+ }
878
+ /**
879
+ * Get a snapshot by ID.
880
+ *
881
+ * @param snapshotId - Snapshot UUID.
882
+ * @returns Snapshot.
883
+ */
884
+ async getSnapshot(snapshotId, options) {
885
+ const url = `${this._baseUrl}/snapshots/${encodeURIComponent(snapshotId)}`;
886
+ const response = await this._fetch(url, { signal: options?.signal });
887
+ if (!response.ok) {
888
+ if (response.status === 404) {
889
+ throw new LangSmithResourceNotFoundError(`Snapshot '${snapshotId}' not found`, "snapshot");
890
+ }
891
+ await handleClientHttpError(response);
892
+ }
893
+ return (await response.json());
894
+ }
895
+ /**
896
+ * List all snapshots.
897
+ *
898
+ * @returns List of Snapshots.
899
+ */
900
+ async listSnapshots() {
901
+ const url = `${this._baseUrl}/snapshots`;
902
+ const response = await this._fetch(url);
903
+ if (!response.ok) {
904
+ await handleClientHttpError(response);
905
+ }
906
+ const data = await response.json();
907
+ return (data.snapshots ?? []);
908
+ }
909
+ /**
910
+ * Delete a snapshot.
911
+ *
912
+ * @param snapshotId - Snapshot UUID.
913
+ */
914
+ async deleteSnapshot(snapshotId) {
915
+ const url = `${this._baseUrl}/snapshots/${encodeURIComponent(snapshotId)}`;
916
+ const response = await this._fetch(url, { method: "DELETE" });
917
+ if (!response.ok) {
918
+ await handleClientHttpError(response);
919
+ }
920
+ }
921
+ /**
922
+ * Poll until a snapshot reaches "ready" or "failed" status.
923
+ *
924
+ * @param snapshotId - Snapshot UUID.
925
+ * @param options - Polling options (timeout, pollInterval).
926
+ * @returns Snapshot in "ready" status.
927
+ */
928
+ async waitForSnapshot(snapshotId, options = {}) {
929
+ const { timeout = 300, pollInterval = 2.0, signal } = options;
930
+ const deadline = Date.now() + timeout * 1000;
931
+ let lastStatus = "building";
932
+ while (Date.now() < deadline) {
933
+ signal?.throwIfAborted();
934
+ const snapshot = await this.getSnapshot(snapshotId, { signal });
935
+ lastStatus = snapshot.status;
936
+ if (snapshot.status === "ready") {
937
+ return snapshot;
938
+ }
939
+ if (snapshot.status === "failed") {
940
+ throw new LangSmithResourceCreationError(snapshot.status_message ?? `Snapshot '${snapshotId}' build failed`, "snapshot");
941
+ }
942
+ // Cap sleep to remaining time + jitter
943
+ const remaining = deadline - Date.now();
944
+ const jitter = pollInterval * 200 * (Math.random() - 0.5); // ±10%
945
+ const delay = Math.min(pollInterval * 1000 + jitter, remaining);
946
+ if (delay > 0) {
947
+ await sleepWithSignal(delay, signal);
948
+ }
949
+ }
950
+ throw new LangSmithResourceTimeoutError(`Snapshot '${snapshotId}' did not become ready within ${timeout}s`, "snapshot", lastStatus);
951
+ }
728
952
  }
@@ -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, SandboxAccessControl, SandboxProxyConfig, 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";
@@ -52,7 +52,7 @@ class Sandbox {
52
52
  writable: true,
53
53
  value: void 0
54
54
  });
55
- /** Provisioning status ("provisioning", "ready", "failed"). */
55
+ /** Provisioning status ("provisioning", "ready", "failed", "stopped"). */
56
56
  Object.defineProperty(this, "status", {
57
57
  enumerable: true,
58
58
  configurable: true,
@@ -108,6 +108,34 @@ class Sandbox {
108
108
  writable: true,
109
109
  value: void 0
110
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
+ });
111
139
  Object.defineProperty(this, "_client", {
112
140
  enumerable: true,
113
141
  configurable: true,
@@ -125,6 +153,10 @@ class Sandbox {
125
153
  this.ttl_seconds = data.ttl_seconds;
126
154
  this.idle_ttl_seconds = data.idle_ttl_seconds;
127
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;
128
160
  this._client = client;
129
161
  }
130
162
  /**
@@ -339,5 +371,35 @@ class Sandbox {
339
371
  async delete() {
340
372
  await this._client.deleteSandbox(this.name);
341
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
+ }
342
404
  }
343
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.
@@ -27,11 +27,11 @@ export declare class Sandbox {
27
27
  /** Display name (can be updated). */
28
28
  readonly name: string;
29
29
  /** Name of the template used to create this sandbox. */
30
- readonly template_name: string;
30
+ readonly template_name?: string;
31
31
  /** URL for data plane operations (file I/O, command execution). */
32
- readonly dataplane_url?: string;
33
- /** Provisioning status ("provisioning", "ready", "failed"). */
34
- readonly status?: string;
32
+ dataplane_url?: string;
33
+ /** Provisioning status ("provisioning", "ready", "failed", "stopped"). */
34
+ status?: string;
35
35
  /** Human-readable status message (e.g., error details when failed). */
36
36
  readonly status_message?: string;
37
37
  /** Unique identifier (UUID). Remains constant even if name changes. */
@@ -46,6 +46,14 @@ export declare class Sandbox {
46
46
  readonly idle_ttl_seconds?: number;
47
47
  /** Computed expiration timestamp when a TTL is active. */
48
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;
49
57
  private _client;
50
58
  /**
51
59
  * Validate and return the dataplane URL.
@@ -147,4 +155,24 @@ export declare class Sandbox {
147
155
  * ```
148
156
  */
149
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>;
150
178
  }
@@ -49,7 +49,7 @@ export class Sandbox {
49
49
  writable: true,
50
50
  value: void 0
51
51
  });
52
- /** Provisioning status ("provisioning", "ready", "failed"). */
52
+ /** Provisioning status ("provisioning", "ready", "failed", "stopped"). */
53
53
  Object.defineProperty(this, "status", {
54
54
  enumerable: true,
55
55
  configurable: true,
@@ -105,6 +105,34 @@ export class Sandbox {
105
105
  writable: true,
106
106
  value: void 0
107
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
+ });
108
136
  Object.defineProperty(this, "_client", {
109
137
  enumerable: true,
110
138
  configurable: true,
@@ -122,6 +150,10 @@ export class Sandbox {
122
150
  this.ttl_seconds = data.ttl_seconds;
123
151
  this.idle_ttl_seconds = data.idle_ttl_seconds;
124
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;
125
157
  this._client = client;
126
158
  }
127
159
  /**
@@ -336,4 +368,34 @@ export class Sandbox {
336
368
  async delete() {
337
369
  await this._client.deleteSandbox(this.name);
338
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
+ }
339
401
  }
@@ -73,13 +73,35 @@ export interface ResourceStatus {
73
73
  /** Human-readable details when failed. */
74
74
  status_message?: string;
75
75
  }
76
+ /**
77
+ * Represents a sandbox snapshot.
78
+ *
79
+ * Snapshots are built from Docker images or captured from running sandboxes.
80
+ * They are used to create new sandboxes.
81
+ */
82
+ export interface Snapshot {
83
+ id: string;
84
+ name: string;
85
+ /** One of "building", "ready", "failed". */
86
+ status: string;
87
+ fs_capacity_bytes: number;
88
+ docker_image?: string;
89
+ image_digest?: string;
90
+ source_sandbox_id?: string;
91
+ status_message?: string;
92
+ fs_used_bytes?: number;
93
+ created_by?: string;
94
+ registry_id?: string;
95
+ created_at?: string;
96
+ updated_at?: string;
97
+ }
76
98
  /**
77
99
  * Data representing a sandbox instance from the API.
78
100
  */
79
101
  export interface SandboxData {
80
102
  id?: string;
81
103
  name: string;
82
- template_name: string;
104
+ template_name?: string;
83
105
  dataplane_url?: string;
84
106
  status?: string;
85
107
  status_message?: string;
@@ -91,6 +113,14 @@ export interface SandboxData {
91
113
  idle_ttl_seconds?: number;
92
114
  /** Computed expiration timestamp when a TTL is active, else omitted/`undefined`. */
93
115
  expires_at?: string;
116
+ /** Snapshot ID used to create this sandbox. */
117
+ snapshot_id?: string;
118
+ /** Number of vCPUs allocated. */
119
+ vcpus?: number;
120
+ /** Memory allocation in bytes. */
121
+ mem_bytes?: number;
122
+ /** Root filesystem capacity in bytes. */
123
+ fs_capacity_bytes?: number;
94
124
  }
95
125
  /**
96
126
  * Configuration options for the SandboxClient.
@@ -235,10 +265,42 @@ export interface RunOptions {
235
265
  */
236
266
  pty?: boolean;
237
267
  }
268
+ /**
269
+ * Network access-control rules for a sandbox's proxy sidecar.
270
+ *
271
+ * Supported pattern types: exact domains, globs (e.g. `*.example.com`),
272
+ * IPs, CIDR ranges (e.g. `10.0.0.0/8`), and regex (`~pattern`).
273
+ *
274
+ * Only one of `allow_list` and `deny_list` may be populated.
275
+ */
276
+ export interface SandboxAccessControl {
277
+ /** Hosts the sandbox is allowed to reach. */
278
+ allow_list?: string[];
279
+ /** Hosts the sandbox is blocked from reaching. */
280
+ deny_list?: string[];
281
+ }
282
+ /**
283
+ * Full proxy configuration forwarded to the sandbox server as-is (snake_case
284
+ * so it's wire-compatible with the backend). Mirrors the server's
285
+ * `ProxyConfig` type.
286
+ */
287
+ export interface SandboxProxyConfig {
288
+ /** Header-injection rules keyed by host pattern. */
289
+ rules?: unknown[];
290
+ /** Hosts that bypass the proxy entirely. */
291
+ no_proxy?: string[];
292
+ /** Allow/deny list enforced at the proxy sidecar. */
293
+ access_control?: SandboxAccessControl;
294
+ }
238
295
  /**
239
296
  * Options for creating a sandbox.
240
297
  */
241
298
  export interface CreateSandboxOptions {
299
+ /**
300
+ * Snapshot ID to boot from.
301
+ * Mutually exclusive with the `templateName` positional arg.
302
+ */
303
+ snapshotId?: string;
242
304
  /**
243
305
  * Optional sandbox name (auto-generated if not provided).
244
306
  */
@@ -264,6 +326,67 @@ export interface CreateSandboxOptions {
264
326
  * Must be a multiple of 60, or `0`/`undefined` to disable or omit.
265
327
  */
266
328
  idleTtlSeconds?: number;
329
+ /** Number of vCPUs. */
330
+ vCpus?: number;
331
+ /** Memory in bytes. */
332
+ memBytes?: number;
333
+ /** Root filesystem capacity in bytes. */
334
+ fsCapacityBytes?: number;
335
+ /**
336
+ * Per-sandbox proxy configuration. Use
337
+ * `{ access_control: { allow_list: ["github.com", "*.example.com"] } }`
338
+ * to restrict outbound HTTPS to a set of host patterns. Forwarded to the
339
+ * server as-is on the wire.
340
+ */
341
+ proxyConfig?: SandboxProxyConfig;
342
+ }
343
+ /**
344
+ * Options for creating a snapshot from a Docker image.
345
+ */
346
+ export interface CreateSnapshotOptions {
347
+ /** Private registry ID (alternative to URL/credentials). */
348
+ registryId?: string;
349
+ /** Registry URL for private images. */
350
+ registryUrl?: string;
351
+ /** Registry username. */
352
+ registryUsername?: string;
353
+ /** Registry password. */
354
+ registryPassword?: string;
355
+ /** Timeout in seconds when waiting for ready. Default: 60. */
356
+ timeout?: number;
357
+ /** AbortSignal for cancellation. */
358
+ signal?: AbortSignal;
359
+ }
360
+ /**
361
+ * Options for capturing a snapshot from a running sandbox.
362
+ */
363
+ export interface CaptureSnapshotOptions {
364
+ /** Checkpoint timestamp to use. If omitted, creates a fresh checkpoint. */
365
+ checkpoint?: string;
366
+ /** Timeout in seconds when waiting for ready. Default: 60. */
367
+ timeout?: number;
368
+ /** AbortSignal for cancellation. */
369
+ signal?: AbortSignal;
370
+ }
371
+ /**
372
+ * Options for waiting for a snapshot to become ready.
373
+ */
374
+ export interface WaitForSnapshotOptions {
375
+ /** Maximum time in seconds to wait. Default: 300. */
376
+ timeout?: number;
377
+ /** Time in seconds between status polls. Default: 2.0. */
378
+ pollInterval?: number;
379
+ /** AbortSignal for cancellation. */
380
+ signal?: AbortSignal;
381
+ }
382
+ /**
383
+ * Options for starting a stopped sandbox.
384
+ */
385
+ export interface StartSandboxOptions {
386
+ /** Timeout in seconds when waiting for ready. Default: 120. */
387
+ timeout?: number;
388
+ /** AbortSignal for cancellation. */
389
+ signal?: AbortSignal;
267
390
  }
268
391
  /**
269
392
  * Options for updating a sandbox (name and/or TTL).
@@ -295,6 +418,8 @@ export interface WaitForSandboxOptions {
295
418
  * Default: 1.0.
296
419
  */
297
420
  pollInterval?: number;
421
+ /** AbortSignal for cancellation. */
422
+ signal?: AbortSignal;
298
423
  }
299
424
  /**
300
425
  * Options for creating a volume.
package/dist/index.cjs CHANGED
@@ -18,4 +18,4 @@ Object.defineProperty(exports, "PromptCache", { enumerable: true, get: function
18
18
  Object.defineProperty(exports, "configureGlobalPromptCache", { enumerable: true, get: function () { return index_js_1.configureGlobalPromptCache; } });
19
19
  Object.defineProperty(exports, "promptCacheSingleton", { enumerable: true, get: function () { return index_js_1.promptCacheSingleton; } });
20
20
  // Update using pnpm bump-version
21
- exports.__version__ = "0.5.19";
21
+ exports.__version__ = "0.5.21";
package/dist/index.d.ts CHANGED
@@ -5,4 +5,4 @@ export { overrideFetchImplementation } from "./singletons/fetch.js";
5
5
  export { getDefaultProjectName } from "./utils/project.js";
6
6
  export { uuid7, uuid7FromTime } from "./uuid.js";
7
7
  export { Cache, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
8
- export declare const __version__ = "0.5.19";
8
+ export declare const __version__ = "0.5.21";
package/dist/index.js CHANGED
@@ -5,4 +5,4 @@ export { getDefaultProjectName } from "./utils/project.js";
5
5
  export { uuid7, uuid7FromTime } from "./uuid.js";
6
6
  export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
7
7
  // Update using pnpm bump-version
8
- export const __version__ = "0.5.19";
8
+ export const __version__ = "0.5.21";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.5.19",
3
+ "version": "0.5.21",
4
4
  "description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
5
5
  "packageManager": "pnpm@10.33.0",
6
6
  "files": [
@@ -153,7 +153,7 @@
153
153
  "@ai-sdk/openai": "^3.0.0",
154
154
  "@ai-sdk/provider": "^3.0.0",
155
155
  "@anthropic-ai/claude-agent-sdk": "^0.2.83",
156
- "@anthropic-ai/sdk": "^0.88.0",
156
+ "@anthropic-ai/sdk": "^0.89.0",
157
157
  "@babel/preset-env": "^7.22.4",
158
158
  "@faker-js/faker": "^8.4.1",
159
159
  "@google/genai": "^1.29.0",