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.
- package/dist/experimental/sandbox/client.cjs +235 -11
- package/dist/experimental/sandbox/client.d.ts +74 -4
- package/dist/experimental/sandbox/client.js +235 -11
- package/dist/experimental/sandbox/index.d.ts +1 -1
- package/dist/experimental/sandbox/sandbox.cjs +63 -1
- package/dist/experimental/sandbox/sandbox.d.ts +33 -5
- package/dist/experimental/sandbox/sandbox.js +63 -1
- package/dist/experimental/sandbox/types.d.ts +126 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
30
|
+
readonly template_name?: string;
|
|
31
31
|
/** URL for data plane operations (file I/O, command execution). */
|
|
32
|
-
|
|
33
|
-
/** Provisioning status ("provisioning", "ready", "failed"). */
|
|
34
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|