langsmith 0.5.8 → 0.5.10
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/client.cjs +33 -10
- package/dist/client.js +33 -10
- package/dist/experimental/sandbox/client.cjs +73 -4
- package/dist/experimental/sandbox/client.d.ts +33 -1
- package/dist/experimental/sandbox/client.js +74 -5
- package/dist/experimental/sandbox/command_handle.cjs +325 -0
- package/dist/experimental/sandbox/command_handle.d.ts +98 -0
- package/dist/experimental/sandbox/command_handle.js +321 -0
- package/dist/experimental/sandbox/errors.cjs +67 -2
- package/dist/experimental/sandbox/errors.d.ts +30 -0
- package/dist/experimental/sandbox/errors.js +63 -1
- package/dist/experimental/sandbox/index.cjs +7 -1
- package/dist/experimental/sandbox/index.d.ts +3 -2
- package/dist/experimental/sandbox/index.js +5 -2
- package/dist/experimental/sandbox/sandbox.cjs +112 -28
- package/dist/experimental/sandbox/sandbox.d.ts +48 -19
- package/dist/experimental/sandbox/sandbox.js +113 -29
- package/dist/experimental/sandbox/types.d.ts +126 -0
- package/dist/experimental/sandbox/ws_execute.cjs +350 -0
- package/dist/experimental/sandbox/ws_execute.d.ts +70 -0
- package/dist/experimental/sandbox/ws_execute.js +341 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/run_trees.cjs +10 -3
- package/dist/run_trees.js +10 -3
- package/package.json +9 -3
package/dist/client.cjs
CHANGED
|
@@ -50,6 +50,26 @@ const index_js_2 = require("./utils/prompt_cache/index.cjs");
|
|
|
50
50
|
const fsUtils = __importStar(require("./utils/fs.cjs"));
|
|
51
51
|
const fetch_js_1 = require("./singletons/fetch.cjs");
|
|
52
52
|
const index_js_3 = require("./utils/fast-safe-stringify/index.cjs");
|
|
53
|
+
/**
|
|
54
|
+
* Catches timestamps without a timezone suffix.
|
|
55
|
+
*/
|
|
56
|
+
function _ensureUTCTimestamp(ts) {
|
|
57
|
+
if (typeof ts === "string" &&
|
|
58
|
+
ts.length > 0 &&
|
|
59
|
+
!ts.includes("Z") &&
|
|
60
|
+
!ts.includes("+") &&
|
|
61
|
+
!ts.includes("-", 10)) {
|
|
62
|
+
return ts + "Z";
|
|
63
|
+
}
|
|
64
|
+
return ts;
|
|
65
|
+
}
|
|
66
|
+
function _normalizeRunTimestamps(run) {
|
|
67
|
+
return {
|
|
68
|
+
...run,
|
|
69
|
+
start_time: _ensureUTCTimestamp(run.start_time),
|
|
70
|
+
end_time: _ensureUTCTimestamp(run.end_time),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
53
73
|
function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo) {
|
|
54
74
|
if (omitTracedRuntimeInfo) {
|
|
55
75
|
return run;
|
|
@@ -1542,7 +1562,7 @@ class Client {
|
|
|
1542
1562
|
}
|
|
1543
1563
|
async readRun(runId, { loadChildRuns } = { loadChildRuns: false }) {
|
|
1544
1564
|
(0, _uuid_js_1.assertUuid)(runId);
|
|
1545
|
-
let run = await this._get(`/runs/${runId}`);
|
|
1565
|
+
let run = _normalizeRunTimestamps(await this._get(`/runs/${runId}`));
|
|
1546
1566
|
if (loadChildRuns) {
|
|
1547
1567
|
run = await this._loadChildRuns(run);
|
|
1548
1568
|
}
|
|
@@ -1761,20 +1781,21 @@ class Client {
|
|
|
1761
1781
|
}
|
|
1762
1782
|
let runsYielded = 0;
|
|
1763
1783
|
for await (const runs of this._getCursorPaginatedList("/runs/query", body)) {
|
|
1784
|
+
const normalized = runs.map(_normalizeRunTimestamps);
|
|
1764
1785
|
if (limit) {
|
|
1765
1786
|
if (runsYielded >= limit) {
|
|
1766
1787
|
break;
|
|
1767
1788
|
}
|
|
1768
|
-
if (
|
|
1769
|
-
const newRuns =
|
|
1789
|
+
if (normalized.length + runsYielded > limit) {
|
|
1790
|
+
const newRuns = normalized.slice(0, limit - runsYielded);
|
|
1770
1791
|
yield* newRuns;
|
|
1771
1792
|
break;
|
|
1772
1793
|
}
|
|
1773
|
-
runsYielded +=
|
|
1774
|
-
yield*
|
|
1794
|
+
runsYielded += normalized.length;
|
|
1795
|
+
yield* normalized;
|
|
1775
1796
|
}
|
|
1776
1797
|
else {
|
|
1777
|
-
yield*
|
|
1798
|
+
yield* normalized;
|
|
1778
1799
|
}
|
|
1779
1800
|
}
|
|
1780
1801
|
}
|
|
@@ -1891,7 +1912,8 @@ class Client {
|
|
|
1891
1912
|
}
|
|
1892
1913
|
const threadsMap = new Map();
|
|
1893
1914
|
for await (const runs of this._getCursorPaginatedList("/runs/query", bodyQuery)) {
|
|
1894
|
-
for (const
|
|
1915
|
+
for (const raw of runs) {
|
|
1916
|
+
const run = _normalizeRunTimestamps(raw);
|
|
1895
1917
|
const tid = run.thread_id;
|
|
1896
1918
|
if (tid) {
|
|
1897
1919
|
const list = threadsMap.get(tid) ?? [];
|
|
@@ -2064,8 +2086,8 @@ class Client {
|
|
|
2064
2086
|
await (0, error_js_1.raiseForStatus)(res, "list shared runs");
|
|
2065
2087
|
return res;
|
|
2066
2088
|
});
|
|
2067
|
-
const runs = await response.json();
|
|
2068
|
-
return runs;
|
|
2089
|
+
const runs = (await response.json());
|
|
2090
|
+
return runs.map(_normalizeRunTimestamps);
|
|
2069
2091
|
}
|
|
2070
2092
|
async readDatasetSharedSchema(datasetId, datasetName) {
|
|
2071
2093
|
if (!datasetId && !datasetName) {
|
|
@@ -3714,7 +3736,8 @@ class Client {
|
|
|
3714
3736
|
await (0, error_js_1.raiseForStatus)(res, "get run from annotation queue");
|
|
3715
3737
|
return res;
|
|
3716
3738
|
});
|
|
3717
|
-
|
|
3739
|
+
const run = await response.json();
|
|
3740
|
+
return _normalizeRunTimestamps(run);
|
|
3718
3741
|
}
|
|
3719
3742
|
/**
|
|
3720
3743
|
* Delete a run from an an annotation queue.
|
package/dist/client.js
CHANGED
|
@@ -13,6 +13,26 @@ import { promptCacheSingleton, } from "./utils/prompt_cache/index.js";
|
|
|
13
13
|
import * as fsUtils from "./utils/fs.js";
|
|
14
14
|
import { _shouldStreamForGlobalFetchImplementation, _getFetchImplementation, } from "./singletons/fetch.js";
|
|
15
15
|
import { serialize as serializePayloadForTracing } from "./utils/fast-safe-stringify/index.js";
|
|
16
|
+
/**
|
|
17
|
+
* Catches timestamps without a timezone suffix.
|
|
18
|
+
*/
|
|
19
|
+
function _ensureUTCTimestamp(ts) {
|
|
20
|
+
if (typeof ts === "string" &&
|
|
21
|
+
ts.length > 0 &&
|
|
22
|
+
!ts.includes("Z") &&
|
|
23
|
+
!ts.includes("+") &&
|
|
24
|
+
!ts.includes("-", 10)) {
|
|
25
|
+
return ts + "Z";
|
|
26
|
+
}
|
|
27
|
+
return ts;
|
|
28
|
+
}
|
|
29
|
+
function _normalizeRunTimestamps(run) {
|
|
30
|
+
return {
|
|
31
|
+
...run,
|
|
32
|
+
start_time: _ensureUTCTimestamp(run.start_time),
|
|
33
|
+
end_time: _ensureUTCTimestamp(run.end_time),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
16
36
|
export function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo) {
|
|
17
37
|
if (omitTracedRuntimeInfo) {
|
|
18
38
|
return run;
|
|
@@ -1504,7 +1524,7 @@ export class Client {
|
|
|
1504
1524
|
}
|
|
1505
1525
|
async readRun(runId, { loadChildRuns } = { loadChildRuns: false }) {
|
|
1506
1526
|
assertUuid(runId);
|
|
1507
|
-
let run = await this._get(`/runs/${runId}`);
|
|
1527
|
+
let run = _normalizeRunTimestamps(await this._get(`/runs/${runId}`));
|
|
1508
1528
|
if (loadChildRuns) {
|
|
1509
1529
|
run = await this._loadChildRuns(run);
|
|
1510
1530
|
}
|
|
@@ -1723,20 +1743,21 @@ export class Client {
|
|
|
1723
1743
|
}
|
|
1724
1744
|
let runsYielded = 0;
|
|
1725
1745
|
for await (const runs of this._getCursorPaginatedList("/runs/query", body)) {
|
|
1746
|
+
const normalized = runs.map(_normalizeRunTimestamps);
|
|
1726
1747
|
if (limit) {
|
|
1727
1748
|
if (runsYielded >= limit) {
|
|
1728
1749
|
break;
|
|
1729
1750
|
}
|
|
1730
|
-
if (
|
|
1731
|
-
const newRuns =
|
|
1751
|
+
if (normalized.length + runsYielded > limit) {
|
|
1752
|
+
const newRuns = normalized.slice(0, limit - runsYielded);
|
|
1732
1753
|
yield* newRuns;
|
|
1733
1754
|
break;
|
|
1734
1755
|
}
|
|
1735
|
-
runsYielded +=
|
|
1736
|
-
yield*
|
|
1756
|
+
runsYielded += normalized.length;
|
|
1757
|
+
yield* normalized;
|
|
1737
1758
|
}
|
|
1738
1759
|
else {
|
|
1739
|
-
yield*
|
|
1760
|
+
yield* normalized;
|
|
1740
1761
|
}
|
|
1741
1762
|
}
|
|
1742
1763
|
}
|
|
@@ -1853,7 +1874,8 @@ export class Client {
|
|
|
1853
1874
|
}
|
|
1854
1875
|
const threadsMap = new Map();
|
|
1855
1876
|
for await (const runs of this._getCursorPaginatedList("/runs/query", bodyQuery)) {
|
|
1856
|
-
for (const
|
|
1877
|
+
for (const raw of runs) {
|
|
1878
|
+
const run = _normalizeRunTimestamps(raw);
|
|
1857
1879
|
const tid = run.thread_id;
|
|
1858
1880
|
if (tid) {
|
|
1859
1881
|
const list = threadsMap.get(tid) ?? [];
|
|
@@ -2026,8 +2048,8 @@ export class Client {
|
|
|
2026
2048
|
await raiseForStatus(res, "list shared runs");
|
|
2027
2049
|
return res;
|
|
2028
2050
|
});
|
|
2029
|
-
const runs = await response.json();
|
|
2030
|
-
return runs;
|
|
2051
|
+
const runs = (await response.json());
|
|
2052
|
+
return runs.map(_normalizeRunTimestamps);
|
|
2031
2053
|
}
|
|
2032
2054
|
async readDatasetSharedSchema(datasetId, datasetName) {
|
|
2033
2055
|
if (!datasetId && !datasetName) {
|
|
@@ -3676,7 +3698,8 @@ export class Client {
|
|
|
3676
3698
|
await raiseForStatus(res, "get run from annotation queue");
|
|
3677
3699
|
return res;
|
|
3678
3700
|
});
|
|
3679
|
-
|
|
3701
|
+
const run = await response.json();
|
|
3702
|
+
return _normalizeRunTimestamps(run);
|
|
3680
3703
|
}
|
|
3681
3704
|
/**
|
|
3682
3705
|
* Delete a run from an an annotation queue.
|
|
@@ -106,6 +106,13 @@ class SandboxClient {
|
|
|
106
106
|
headers,
|
|
107
107
|
}));
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the API key for WebSocket authentication.
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
getApiKey() {
|
|
114
|
+
return this._apiKey;
|
|
115
|
+
}
|
|
109
116
|
// =========================================================================
|
|
110
117
|
// Volume Operations
|
|
111
118
|
// =========================================================================
|
|
@@ -530,21 +537,24 @@ class SandboxClient {
|
|
|
530
537
|
* ```
|
|
531
538
|
*/
|
|
532
539
|
async createSandbox(templateName, options = {}) {
|
|
533
|
-
const { name, timeout = 30 } = options;
|
|
540
|
+
const { name, timeout = 30, waitForReady = true } = options;
|
|
534
541
|
const url = `${this._baseUrl}/boxes`;
|
|
535
542
|
const payload = {
|
|
536
543
|
template_name: templateName,
|
|
537
|
-
wait_for_ready:
|
|
538
|
-
timeout,
|
|
544
|
+
wait_for_ready: waitForReady,
|
|
539
545
|
};
|
|
546
|
+
if (waitForReady) {
|
|
547
|
+
payload.timeout = timeout;
|
|
548
|
+
}
|
|
540
549
|
if (name) {
|
|
541
550
|
payload.name = name;
|
|
542
551
|
}
|
|
552
|
+
const httpTimeout = waitForReady ? (timeout + 30) * 1000 : 30 * 1000;
|
|
543
553
|
const response = await this._fetch(url, {
|
|
544
554
|
method: "POST",
|
|
545
555
|
headers: { "Content-Type": "application/json" },
|
|
546
556
|
body: JSON.stringify(payload),
|
|
547
|
-
signal: AbortSignal.timeout(
|
|
557
|
+
signal: AbortSignal.timeout(httpTimeout),
|
|
548
558
|
});
|
|
549
559
|
if (!response.ok) {
|
|
550
560
|
await (0, helpers_js_1.handleSandboxCreationError)(response);
|
|
@@ -635,5 +645,64 @@ class SandboxClient {
|
|
|
635
645
|
await (0, helpers_js_1.handleClientHttpError)(response);
|
|
636
646
|
}
|
|
637
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Get the provisioning status of a sandbox.
|
|
650
|
+
*
|
|
651
|
+
* This is a lightweight endpoint designed for polling during async creation.
|
|
652
|
+
* Use this instead of getSandbox() when you only need the status.
|
|
653
|
+
*
|
|
654
|
+
* @param name - Sandbox name.
|
|
655
|
+
* @returns ResourceStatus with status and optional status_message.
|
|
656
|
+
* @throws LangSmithResourceNotFoundError if sandbox not found.
|
|
657
|
+
*/
|
|
658
|
+
async getSandboxStatus(name) {
|
|
659
|
+
const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/status`;
|
|
660
|
+
const response = await this._fetch(url);
|
|
661
|
+
if (!response.ok) {
|
|
662
|
+
if (response.status === 404) {
|
|
663
|
+
throw new errors_js_1.LangSmithResourceNotFoundError(`Sandbox '${name}' not found`, "sandbox");
|
|
664
|
+
}
|
|
665
|
+
await (0, helpers_js_1.handleClientHttpError)(response);
|
|
666
|
+
}
|
|
667
|
+
return (await response.json());
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Wait for a sandbox to become ready.
|
|
671
|
+
*
|
|
672
|
+
* Polls getSandboxStatus() until the sandbox reaches "ready" or "failed" status,
|
|
673
|
+
* then returns the full Sandbox object.
|
|
674
|
+
*
|
|
675
|
+
* @param name - Sandbox name.
|
|
676
|
+
* @param options - Polling options (timeout, pollInterval).
|
|
677
|
+
* @returns Ready Sandbox.
|
|
678
|
+
* @throws LangSmithResourceCreationError if sandbox status becomes "failed".
|
|
679
|
+
* @throws LangSmithResourceTimeoutError if timeout expires while still provisioning.
|
|
680
|
+
* @throws LangSmithResourceNotFoundError if sandbox not found.
|
|
681
|
+
*
|
|
682
|
+
* @example
|
|
683
|
+
* ```typescript
|
|
684
|
+
* const sandbox = await client.createSandbox("python-sandbox", { waitForReady: false });
|
|
685
|
+
* // ... do other work ...
|
|
686
|
+
* const readySandbox = await client.waitForSandbox(sandbox.name);
|
|
687
|
+
* ```
|
|
688
|
+
*/
|
|
689
|
+
async waitForSandbox(name, options = {}) {
|
|
690
|
+
const { timeout = 120, pollInterval = 1.0 } = options;
|
|
691
|
+
const deadline = Date.now() + timeout * 1000;
|
|
692
|
+
let lastStatus = "provisioning";
|
|
693
|
+
while (Date.now() < deadline) {
|
|
694
|
+
const statusResult = await this.getSandboxStatus(name);
|
|
695
|
+
lastStatus = statusResult.status;
|
|
696
|
+
if (statusResult.status === "ready") {
|
|
697
|
+
return this.getSandbox(name);
|
|
698
|
+
}
|
|
699
|
+
if (statusResult.status === "failed") {
|
|
700
|
+
throw new errors_js_1.LangSmithResourceCreationError(statusResult.status_message ?? `Sandbox '${name}' creation failed`, "sandbox");
|
|
701
|
+
}
|
|
702
|
+
// Wait before polling again
|
|
703
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
|
|
704
|
+
}
|
|
705
|
+
throw new errors_js_1.LangSmithResourceTimeoutError(`Sandbox '${name}' did not become ready within ${timeout}s`, "sandbox", lastStatus);
|
|
706
|
+
}
|
|
638
707
|
}
|
|
639
708
|
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, SandboxClientConfig, SandboxTemplate, UpdatePoolOptions, UpdateTemplateOptions, UpdateVolumeOptions, Volume } from "./types.js";
|
|
4
|
+
import type { CreatePoolOptions, CreateSandboxOptions, CreateTemplateOptions, CreateVolumeOptions, Pool, ResourceStatus, SandboxClientConfig, SandboxTemplate, UpdatePoolOptions, UpdateTemplateOptions, UpdateVolumeOptions, Volume, WaitForSandboxOptions } from "./types.js";
|
|
5
5
|
import { Sandbox } from "./sandbox.js";
|
|
6
6
|
/**
|
|
7
7
|
* Client for interacting with the Sandbox Server API.
|
|
@@ -237,4 +237,36 @@ export declare class SandboxClient {
|
|
|
237
237
|
* @throws LangSmithResourceNotFoundError if sandbox not found.
|
|
238
238
|
*/
|
|
239
239
|
deleteSandbox(name: string): Promise<void>;
|
|
240
|
+
/**
|
|
241
|
+
* Get the provisioning status of a sandbox.
|
|
242
|
+
*
|
|
243
|
+
* This is a lightweight endpoint designed for polling during async creation.
|
|
244
|
+
* Use this instead of getSandbox() when you only need the status.
|
|
245
|
+
*
|
|
246
|
+
* @param name - Sandbox name.
|
|
247
|
+
* @returns ResourceStatus with status and optional status_message.
|
|
248
|
+
* @throws LangSmithResourceNotFoundError if sandbox not found.
|
|
249
|
+
*/
|
|
250
|
+
getSandboxStatus(name: string): Promise<ResourceStatus>;
|
|
251
|
+
/**
|
|
252
|
+
* Wait for a sandbox to become ready.
|
|
253
|
+
*
|
|
254
|
+
* Polls getSandboxStatus() until the sandbox reaches "ready" or "failed" status,
|
|
255
|
+
* then returns the full Sandbox object.
|
|
256
|
+
*
|
|
257
|
+
* @param name - Sandbox name.
|
|
258
|
+
* @param options - Polling options (timeout, pollInterval).
|
|
259
|
+
* @returns Ready Sandbox.
|
|
260
|
+
* @throws LangSmithResourceCreationError if sandbox status becomes "failed".
|
|
261
|
+
* @throws LangSmithResourceTimeoutError if timeout expires while still provisioning.
|
|
262
|
+
* @throws LangSmithResourceNotFoundError if sandbox not found.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const sandbox = await client.createSandbox("python-sandbox", { waitForReady: false });
|
|
267
|
+
* // ... do other work ...
|
|
268
|
+
* const readySandbox = await client.waitForSandbox(sandbox.name);
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
waitForSandbox(name: string, options?: WaitForSandboxOptions): Promise<Sandbox>;
|
|
240
272
|
}
|
|
@@ -5,7 +5,7 @@ import { getLangSmithEnvironmentVariable } from "../../utils/env.js";
|
|
|
5
5
|
import { _getFetchImplementation } from "../../singletons/fetch.js";
|
|
6
6
|
import { AsyncCaller } from "../../utils/async_caller.js";
|
|
7
7
|
import { Sandbox } from "./sandbox.js";
|
|
8
|
-
import { LangSmithResourceNameConflictError, LangSmithResourceNotFoundError, LangSmithSandboxAPIError, } from "./errors.js";
|
|
8
|
+
import { LangSmithResourceCreationError, LangSmithResourceNameConflictError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithSandboxAPIError, } from "./errors.js";
|
|
9
9
|
import { handleClientHttpError, handleConflictError, handlePoolError, handleResourceInUseError, handleSandboxCreationError, handleVolumeCreationError, } from "./helpers.js";
|
|
10
10
|
/**
|
|
11
11
|
* Get the default sandbox API endpoint from environment.
|
|
@@ -103,6 +103,13 @@ export class SandboxClient {
|
|
|
103
103
|
headers,
|
|
104
104
|
}));
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Get the API key for WebSocket authentication.
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
getApiKey() {
|
|
111
|
+
return this._apiKey;
|
|
112
|
+
}
|
|
106
113
|
// =========================================================================
|
|
107
114
|
// Volume Operations
|
|
108
115
|
// =========================================================================
|
|
@@ -527,21 +534,24 @@ export class SandboxClient {
|
|
|
527
534
|
* ```
|
|
528
535
|
*/
|
|
529
536
|
async createSandbox(templateName, options = {}) {
|
|
530
|
-
const { name, timeout = 30 } = options;
|
|
537
|
+
const { name, timeout = 30, waitForReady = true } = options;
|
|
531
538
|
const url = `${this._baseUrl}/boxes`;
|
|
532
539
|
const payload = {
|
|
533
540
|
template_name: templateName,
|
|
534
|
-
wait_for_ready:
|
|
535
|
-
timeout,
|
|
541
|
+
wait_for_ready: waitForReady,
|
|
536
542
|
};
|
|
543
|
+
if (waitForReady) {
|
|
544
|
+
payload.timeout = timeout;
|
|
545
|
+
}
|
|
537
546
|
if (name) {
|
|
538
547
|
payload.name = name;
|
|
539
548
|
}
|
|
549
|
+
const httpTimeout = waitForReady ? (timeout + 30) * 1000 : 30 * 1000;
|
|
540
550
|
const response = await this._fetch(url, {
|
|
541
551
|
method: "POST",
|
|
542
552
|
headers: { "Content-Type": "application/json" },
|
|
543
553
|
body: JSON.stringify(payload),
|
|
544
|
-
signal: AbortSignal.timeout(
|
|
554
|
+
signal: AbortSignal.timeout(httpTimeout),
|
|
545
555
|
});
|
|
546
556
|
if (!response.ok) {
|
|
547
557
|
await handleSandboxCreationError(response);
|
|
@@ -632,4 +642,63 @@ export class SandboxClient {
|
|
|
632
642
|
await handleClientHttpError(response);
|
|
633
643
|
}
|
|
634
644
|
}
|
|
645
|
+
/**
|
|
646
|
+
* Get the provisioning status of a sandbox.
|
|
647
|
+
*
|
|
648
|
+
* This is a lightweight endpoint designed for polling during async creation.
|
|
649
|
+
* Use this instead of getSandbox() when you only need the status.
|
|
650
|
+
*
|
|
651
|
+
* @param name - Sandbox name.
|
|
652
|
+
* @returns ResourceStatus with status and optional status_message.
|
|
653
|
+
* @throws LangSmithResourceNotFoundError if sandbox not found.
|
|
654
|
+
*/
|
|
655
|
+
async getSandboxStatus(name) {
|
|
656
|
+
const url = `${this._baseUrl}/boxes/${encodeURIComponent(name)}/status`;
|
|
657
|
+
const response = await this._fetch(url);
|
|
658
|
+
if (!response.ok) {
|
|
659
|
+
if (response.status === 404) {
|
|
660
|
+
throw new LangSmithResourceNotFoundError(`Sandbox '${name}' not found`, "sandbox");
|
|
661
|
+
}
|
|
662
|
+
await handleClientHttpError(response);
|
|
663
|
+
}
|
|
664
|
+
return (await response.json());
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Wait for a sandbox to become ready.
|
|
668
|
+
*
|
|
669
|
+
* Polls getSandboxStatus() until the sandbox reaches "ready" or "failed" status,
|
|
670
|
+
* then returns the full Sandbox object.
|
|
671
|
+
*
|
|
672
|
+
* @param name - Sandbox name.
|
|
673
|
+
* @param options - Polling options (timeout, pollInterval).
|
|
674
|
+
* @returns Ready Sandbox.
|
|
675
|
+
* @throws LangSmithResourceCreationError if sandbox status becomes "failed".
|
|
676
|
+
* @throws LangSmithResourceTimeoutError if timeout expires while still provisioning.
|
|
677
|
+
* @throws LangSmithResourceNotFoundError if sandbox not found.
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```typescript
|
|
681
|
+
* const sandbox = await client.createSandbox("python-sandbox", { waitForReady: false });
|
|
682
|
+
* // ... do other work ...
|
|
683
|
+
* const readySandbox = await client.waitForSandbox(sandbox.name);
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
async waitForSandbox(name, options = {}) {
|
|
687
|
+
const { timeout = 120, pollInterval = 1.0 } = options;
|
|
688
|
+
const deadline = Date.now() + timeout * 1000;
|
|
689
|
+
let lastStatus = "provisioning";
|
|
690
|
+
while (Date.now() < deadline) {
|
|
691
|
+
const statusResult = await this.getSandboxStatus(name);
|
|
692
|
+
lastStatus = statusResult.status;
|
|
693
|
+
if (statusResult.status === "ready") {
|
|
694
|
+
return this.getSandbox(name);
|
|
695
|
+
}
|
|
696
|
+
if (statusResult.status === "failed") {
|
|
697
|
+
throw new LangSmithResourceCreationError(statusResult.status_message ?? `Sandbox '${name}' creation failed`, "sandbox");
|
|
698
|
+
}
|
|
699
|
+
// Wait before polling again
|
|
700
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
|
|
701
|
+
}
|
|
702
|
+
throw new LangSmithResourceTimeoutError(`Sandbox '${name}' did not become ready within ${timeout}s`, "sandbox", lastStatus);
|
|
703
|
+
}
|
|
635
704
|
}
|