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 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 (runs.length + runsYielded > limit) {
1769
- const newRuns = runs.slice(0, limit - runsYielded);
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 += runs.length;
1774
- yield* runs;
1794
+ runsYielded += normalized.length;
1795
+ yield* normalized;
1775
1796
  }
1776
1797
  else {
1777
- yield* runs;
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 run of runs) {
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
- return response.json();
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 (runs.length + runsYielded > limit) {
1731
- const newRuns = runs.slice(0, limit - runsYielded);
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 += runs.length;
1736
- yield* runs;
1756
+ runsYielded += normalized.length;
1757
+ yield* normalized;
1737
1758
  }
1738
1759
  else {
1739
- yield* runs;
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 run of runs) {
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
- return response.json();
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: true,
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((timeout + 30) * 1000),
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: true,
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((timeout + 30) * 1000),
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
  }