langsmith 0.8.5 → 0.8.7

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/index.cjs CHANGED
@@ -35,6 +35,6 @@ Object.defineProperty(exports, "UnprocessableEntityError", { enumerable: true, g
35
35
  Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return error_js_1.RateLimitError; } });
36
36
  Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return error_js_1.InternalServerError; } });
37
37
  // Update using pnpm bump-version
38
- exports.__version__ = "0.8.5";
38
+ exports.__version__ = "0.8.7";
39
39
  // Metadata key to hide a traced run from LangSmith's Messages View.
40
40
  exports.LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
package/dist/index.d.ts CHANGED
@@ -7,5 +7,5 @@ export { computeRunIdForSecondaryReplica, uuid7, uuid7FromTime, } from "./uuid.j
7
7
  export { isTracingEnabled } from "./utils/guard.js";
8
8
  export { Cache, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
9
9
  export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
10
- export declare const __version__ = "0.8.5";
10
+ export declare const __version__ = "0.8.7";
11
11
  export declare const LS_MESSAGE_VIEW_EXCLUDE: "ls_message_view_exclude";
package/dist/index.js CHANGED
@@ -7,6 +7,6 @@ export { isTracingEnabled } from "./utils/guard.js";
7
7
  export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
8
8
  export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
9
9
  // Update using pnpm bump-version
10
- export const __version__ = "0.8.5";
10
+ export const __version__ = "0.8.7";
11
11
  // Metadata key to hide a traced run from LangSmith's Messages View.
12
12
  export const LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
@@ -147,7 +147,7 @@ class CommandHandle {
147
147
  return;
148
148
  const firstResult = await this._stream.next();
149
149
  if (firstResult.done) {
150
- throw new errors_js_1.LangSmithSandboxOperationError("Command stream ended before 'started' message", "command");
150
+ throw new errors_js_1.LangSmithStreamEndedBeforeStartedError("Command stream ended before 'started' message");
151
151
  }
152
152
  const firstMsg = firstResult.value;
153
153
  if (firstMsg.type !== "started") {
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Port of Python's AsyncCommandHandle to TypeScript.
6
6
  */
7
- import { LangSmithSandboxConnectionError, LangSmithSandboxOperationError, } from "./errors.js";
7
+ import { LangSmithSandboxConnectionError, LangSmithSandboxOperationError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
8
8
  /**
9
9
  * Async handle to a running command with streaming output and auto-reconnect.
10
10
  *
@@ -144,7 +144,7 @@ export class CommandHandle {
144
144
  return;
145
145
  const firstResult = await this._stream.next();
146
146
  if (firstResult.done) {
147
- throw new LangSmithSandboxOperationError("Command stream ended before 'started' message", "command");
147
+ throw new LangSmithStreamEndedBeforeStartedError("Command stream ended before 'started' message");
148
148
  }
149
149
  const firstMsg = firstResult.value;
150
150
  if (firstMsg.type !== "started") {
@@ -7,7 +7,7 @@
7
7
  * properties for specific handling when needed.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.LangSmithSandboxServerReloadError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = void 0;
10
+ exports.LangSmithSandboxServerReloadError = exports.LangSmithStreamEndedBeforeStartedError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = void 0;
11
11
  /**
12
12
  * Base exception for sandbox client errors.
13
13
  */
@@ -357,6 +357,21 @@ class LangSmithCommandTimeoutError extends LangSmithSandboxOperationError {
357
357
  }
358
358
  }
359
359
  exports.LangSmithCommandTimeoutError = LangSmithCommandTimeoutError;
360
+ /**
361
+ * Internal: a command WebSocket closed before the guest sent its 'started'
362
+ * frame (the proxied tunnel was torn down gracefully mid-handshake).
363
+ *
364
+ * Marks the idempotently-retryable early close, as distinct from a
365
+ * command-level failure. Subclasses the operation error so callers that catch
366
+ * the public type are unaffected.
367
+ */
368
+ class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
369
+ constructor(message) {
370
+ super(message, "command");
371
+ this.name = "LangSmithStreamEndedBeforeStartedError";
372
+ }
373
+ }
374
+ exports.LangSmithStreamEndedBeforeStartedError = LangSmithStreamEndedBeforeStartedError;
360
375
  /**
361
376
  * Raised when the sandbox server is reloading (close code 1001).
362
377
  *
@@ -154,6 +154,17 @@ export declare class LangSmithCommandTimeoutError extends LangSmithSandboxOperat
154
154
  timeout?: number;
155
155
  constructor(message: string, timeout?: number);
156
156
  }
157
+ /**
158
+ * Internal: a command WebSocket closed before the guest sent its 'started'
159
+ * frame (the proxied tunnel was torn down gracefully mid-handshake).
160
+ *
161
+ * Marks the idempotently-retryable early close, as distinct from a
162
+ * command-level failure. Subclasses the operation error so callers that catch
163
+ * the public type are unaffected.
164
+ */
165
+ export declare class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
166
+ constructor(message: string);
167
+ }
157
168
  /**
158
169
  * Raised when the sandbox server is reloading (close code 1001).
159
170
  *
@@ -337,6 +337,20 @@ export class LangSmithCommandTimeoutError extends LangSmithSandboxOperationError
337
337
  this.timeout = timeout;
338
338
  }
339
339
  }
340
+ /**
341
+ * Internal: a command WebSocket closed before the guest sent its 'started'
342
+ * frame (the proxied tunnel was torn down gracefully mid-handshake).
343
+ *
344
+ * Marks the idempotently-retryable early close, as distinct from a
345
+ * command-level failure. Subclasses the operation error so callers that catch
346
+ * the public type are unaffected.
347
+ */
348
+ export class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
349
+ constructor(message) {
350
+ super(message, "command");
351
+ this.name = "LangSmithStreamEndedBeforeStartedError";
352
+ }
353
+ }
340
354
  /**
341
355
  * Raised when the sandbox server is reloading (close code 1001).
342
356
  *
@@ -4,6 +4,7 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Sandbox = void 0;
7
+ const uuid_js_1 = require("../uuid.cjs");
7
8
  const errors_js_1 = require("./errors.cjs");
8
9
  const helpers_js_1 = require("./helpers.cjs");
9
10
  const command_handle_js_1 = require("./command_handle.cjs");
@@ -198,8 +199,9 @@ class Sandbox {
198
199
  // wait=true with callbacks: drain stream and return result
199
200
  return handle.result;
200
201
  }
201
- // wait=true, no callbacks: try WS, fall back to HTTP
202
- try {
202
+ // wait=true, no callbacks: use WebSocket when the 'ws' package is
203
+ // available, otherwise the blocking HTTP endpoint. WS errors propagate.
204
+ if (await this._wsAvailable()) {
203
205
  const handle = await this._runWs(command, {
204
206
  ...restOptions,
205
207
  idleTimeout,
@@ -209,17 +211,14 @@ class Sandbox {
209
211
  });
210
212
  return await handle.result;
211
213
  }
212
- catch (e) {
213
- // Fall back to HTTP on connection errors or missing ws package
214
- const name = e != null && typeof e === "object" ? e.name : "";
215
- const message = e != null && typeof e === "object" ? (e.message ?? "") : "";
216
- if (name === "LangSmithSandboxConnectionError" ||
217
- name === "LangSmithSandboxServerReloadError" ||
218
- message.includes("'ws' package")) {
219
- return this._runHttp(command, restOptions);
220
- }
221
- throw e;
222
- }
214
+ return this._runHttp(command, restOptions);
215
+ }
216
+ /**
217
+ * Whether the optional `ws` package is importable (resolved once).
218
+ * @internal
219
+ */
220
+ _wsAvailable() {
221
+ return (0, ws_execute_js_1.isWsAvailable)();
223
222
  }
224
223
  /**
225
224
  * Execute a command via WebSocket streaming.
@@ -229,26 +228,49 @@ class Sandbox {
229
228
  const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
230
229
  const dataplaneUrl = this.requireDataplaneUrl();
231
230
  const clientHeaders = this._client.getDefaultHeaders();
232
- const [stream, control] = await (0, ws_execute_js_1.runWsStream)(dataplaneUrl, this._client.getApiKey(), command, {
233
- timeout,
234
- env,
235
- cwd,
236
- shell,
237
- commandId,
238
- idleTimeout,
239
- killOnDisconnect,
240
- ttlSeconds,
241
- pty,
242
- ...(Object.keys(clientHeaders).length > 0
243
- ? { headers: clientHeaders }
244
- : {}),
245
- });
246
- const handle = new command_handle_js_1.CommandHandle(stream, control, this, {
247
- onStdout,
248
- onStderr,
249
- });
250
- await handle._ensureStarted();
251
- return handle;
231
+ // A client-supplied command_id makes execute idempotent: the daemon does
232
+ // get-or-create keyed on it, so if the tunnel closes before "started" we
233
+ // can re-issue the same id and reattach to the existing command instead of
234
+ // spawning a second one.
235
+ const execCommandId = commandId ?? (0, uuid_js_1.uuid7)();
236
+ let attempt = 0;
237
+ // eslint-disable-next-line no-constant-condition
238
+ while (true) {
239
+ const [stream, control] = await (0, ws_execute_js_1.runWsStream)(dataplaneUrl, this._client.getApiKey(), command, {
240
+ timeout,
241
+ env,
242
+ cwd,
243
+ shell,
244
+ commandId: execCommandId,
245
+ idleTimeout,
246
+ killOnDisconnect,
247
+ ttlSeconds,
248
+ pty,
249
+ ...(Object.keys(clientHeaders).length > 0
250
+ ? { headers: clientHeaders }
251
+ : {}),
252
+ });
253
+ const handle = new command_handle_js_1.CommandHandle(stream, control, this, {
254
+ onStdout,
255
+ onStderr,
256
+ });
257
+ try {
258
+ await handle._ensureStarted();
259
+ return handle;
260
+ }
261
+ catch (e) {
262
+ // Idempotent re-issue (same command_id) after an early close.
263
+ if (!(e instanceof errors_js_1.LangSmithStreamEndedBeforeStartedError)) {
264
+ throw e;
265
+ }
266
+ attempt++;
267
+ if (attempt > command_handle_js_1.CommandHandle.MAX_AUTO_RECONNECTS) {
268
+ throw e;
269
+ }
270
+ const delay = Math.min(command_handle_js_1.CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), command_handle_js_1.CommandHandle.BACKOFF_MAX);
271
+ await new Promise((r) => setTimeout(r, delay * 1000));
272
+ }
273
+ }
252
274
  }
253
275
  /**
254
276
  * Execute a command via HTTP POST (blocking).
@@ -78,7 +78,8 @@ export declare class Sandbox {
78
78
  * Execute a command in the sandbox.
79
79
  *
80
80
  * When `wait` is true (default) and no streaming callbacks are provided,
81
- * tries WebSocket first and falls back to HTTP POST.
81
+ * uses WebSocket, falling back to HTTP POST only when the optional `ws`
82
+ * package isn't installed.
82
83
  *
83
84
  * When `wait` is false or streaming callbacks are provided, uses WebSocket
84
85
  * (required). Returns a CommandHandle for streaming output.
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Sandbox class for interacting with a specific sandbox instance.
3
3
  */
4
- import { LangSmithDataplaneNotConfiguredError } from "./errors.js";
4
+ import { uuid7 } from "../uuid.js";
5
+ import { LangSmithDataplaneNotConfiguredError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
5
6
  import { handleSandboxHttpError } from "./helpers.js";
6
7
  import { CommandHandle } from "./command_handle.js";
7
- import { reconnectWsStream, runWsStream } from "./ws_execute.js";
8
+ import { isWsAvailable, reconnectWsStream, runWsStream } from "./ws_execute.js";
8
9
  /**
9
10
  * Represents an active sandbox for running commands and file operations.
10
11
  *
@@ -195,8 +196,9 @@ export class Sandbox {
195
196
  // wait=true with callbacks: drain stream and return result
196
197
  return handle.result;
197
198
  }
198
- // wait=true, no callbacks: try WS, fall back to HTTP
199
- try {
199
+ // wait=true, no callbacks: use WebSocket when the 'ws' package is
200
+ // available, otherwise the blocking HTTP endpoint. WS errors propagate.
201
+ if (await this._wsAvailable()) {
200
202
  const handle = await this._runWs(command, {
201
203
  ...restOptions,
202
204
  idleTimeout,
@@ -206,17 +208,14 @@ export class Sandbox {
206
208
  });
207
209
  return await handle.result;
208
210
  }
209
- catch (e) {
210
- // Fall back to HTTP on connection errors or missing ws package
211
- const name = e != null && typeof e === "object" ? e.name : "";
212
- const message = e != null && typeof e === "object" ? (e.message ?? "") : "";
213
- if (name === "LangSmithSandboxConnectionError" ||
214
- name === "LangSmithSandboxServerReloadError" ||
215
- message.includes("'ws' package")) {
216
- return this._runHttp(command, restOptions);
217
- }
218
- throw e;
219
- }
211
+ return this._runHttp(command, restOptions);
212
+ }
213
+ /**
214
+ * Whether the optional `ws` package is importable (resolved once).
215
+ * @internal
216
+ */
217
+ _wsAvailable() {
218
+ return isWsAvailable();
220
219
  }
221
220
  /**
222
221
  * Execute a command via WebSocket streaming.
@@ -226,26 +225,49 @@ export class Sandbox {
226
225
  const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
227
226
  const dataplaneUrl = this.requireDataplaneUrl();
228
227
  const clientHeaders = this._client.getDefaultHeaders();
229
- const [stream, control] = await runWsStream(dataplaneUrl, this._client.getApiKey(), command, {
230
- timeout,
231
- env,
232
- cwd,
233
- shell,
234
- commandId,
235
- idleTimeout,
236
- killOnDisconnect,
237
- ttlSeconds,
238
- pty,
239
- ...(Object.keys(clientHeaders).length > 0
240
- ? { headers: clientHeaders }
241
- : {}),
242
- });
243
- const handle = new CommandHandle(stream, control, this, {
244
- onStdout,
245
- onStderr,
246
- });
247
- await handle._ensureStarted();
248
- return handle;
228
+ // A client-supplied command_id makes execute idempotent: the daemon does
229
+ // get-or-create keyed on it, so if the tunnel closes before "started" we
230
+ // can re-issue the same id and reattach to the existing command instead of
231
+ // spawning a second one.
232
+ const execCommandId = commandId ?? uuid7();
233
+ let attempt = 0;
234
+ // eslint-disable-next-line no-constant-condition
235
+ while (true) {
236
+ const [stream, control] = await runWsStream(dataplaneUrl, this._client.getApiKey(), command, {
237
+ timeout,
238
+ env,
239
+ cwd,
240
+ shell,
241
+ commandId: execCommandId,
242
+ idleTimeout,
243
+ killOnDisconnect,
244
+ ttlSeconds,
245
+ pty,
246
+ ...(Object.keys(clientHeaders).length > 0
247
+ ? { headers: clientHeaders }
248
+ : {}),
249
+ });
250
+ const handle = new CommandHandle(stream, control, this, {
251
+ onStdout,
252
+ onStderr,
253
+ });
254
+ try {
255
+ await handle._ensureStarted();
256
+ return handle;
257
+ }
258
+ catch (e) {
259
+ // Idempotent re-issue (same command_id) after an early close.
260
+ if (!(e instanceof LangSmithStreamEndedBeforeStartedError)) {
261
+ throw e;
262
+ }
263
+ attempt++;
264
+ if (attempt > CommandHandle.MAX_AUTO_RECONNECTS) {
265
+ throw e;
266
+ }
267
+ const delay = Math.min(CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), CommandHandle.BACKOFF_MAX);
268
+ await new Promise((r) => setTimeout(r, delay * 1000));
269
+ }
270
+ }
249
271
  }
250
272
  /**
251
273
  * Execute a command via HTTP POST (blocking).
@@ -7,21 +7,28 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.WSStreamControl = void 0;
10
+ exports.isWsAvailable = isWsAvailable;
10
11
  exports.buildWsUrl = buildWsUrl;
11
12
  exports.buildAuthHeaders = buildAuthHeaders;
12
13
  exports.raiseForWsError = raiseForWsError;
13
14
  exports.runWsStream = runWsStream;
14
15
  exports.reconnectWsStream = reconnectWsStream;
15
16
  const errors_js_1 = require("./errors.cjs");
17
+ // Load the optional 'ws' package once, at module load, instead of importing on
18
+ // every call. Resolves to the WebSocket constructor, or null if 'ws' isn't
19
+ // installed. run() reads isWsAvailable() to choose WS vs the HTTP fallback.
20
+ const wsWebSocketPromise = import("ws").then((ws) => ws.default || ws.WebSocket || ws, () => null);
21
+ /** Whether the optional `ws` package could be loaded (resolved once). */
22
+ async function isWsAvailable() {
23
+ return (await wsWebSocketPromise) !== null;
24
+ }
16
25
  async function ensureWs() {
17
- try {
18
- const ws = await import("ws");
19
- return { WebSocket: ws.default || ws.WebSocket || ws };
20
- }
21
- catch {
26
+ const WebSocket = await wsWebSocketPromise;
27
+ if (!WebSocket) {
22
28
  throw new Error("WebSocket-based execution requires the 'ws' package. " +
23
29
  "Install it with: npm install ws");
24
30
  }
31
+ return { WebSocket };
25
32
  }
26
33
  // =============================================================================
27
34
  // URL and Auth Helpers
@@ -6,6 +6,8 @@
6
6
  */
7
7
  import type { WsMessage, WsRunOptions } from "./types.js";
8
8
  type WsWebSocket = any;
9
+ /** Whether the optional `ws` package could be loaded (resolved once). */
10
+ export declare function isWsAvailable(): Promise<boolean>;
9
11
  /**
10
12
  * Convert a dataplane HTTP URL to a WebSocket URL for /execute/ws.
11
13
  */
@@ -5,15 +5,21 @@
5
5
  * Install with: npm install ws
6
6
  */
7
7
  import { LangSmithCommandTimeoutError, LangSmithSandboxConnectionError, LangSmithSandboxOperationError, LangSmithSandboxServerReloadError, } from "./errors.js";
8
+ // Load the optional 'ws' package once, at module load, instead of importing on
9
+ // every call. Resolves to the WebSocket constructor, or null if 'ws' isn't
10
+ // installed. run() reads isWsAvailable() to choose WS vs the HTTP fallback.
11
+ const wsWebSocketPromise = import("ws").then((ws) => ws.default || ws.WebSocket || ws, () => null);
12
+ /** Whether the optional `ws` package could be loaded (resolved once). */
13
+ export async function isWsAvailable() {
14
+ return (await wsWebSocketPromise) !== null;
15
+ }
8
16
  async function ensureWs() {
9
- try {
10
- const ws = await import("ws");
11
- return { WebSocket: ws.default || ws.WebSocket || ws };
12
- }
13
- catch {
17
+ const WebSocket = await wsWebSocketPromise;
18
+ if (!WebSocket) {
14
19
  throw new Error("WebSocket-based execution requires the 'ws' package. " +
15
20
  "Install it with: npm install ws");
16
21
  }
22
+ return { WebSocket };
17
23
  }
18
24
  // =============================================================================
19
25
  // URL and Auth Helpers
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.syncExamplePromises = exports.evaluatorLogFeedbackPromises = exports.testWrapperAsyncLocalStorageInstance = exports.DEFAULT_TEST_CLIENT = void 0;
3
+ exports.evaluatorLogFeedbackPromises = exports.testWrapperAsyncLocalStorageInstance = exports.DEFAULT_TEST_CLIENT = void 0;
4
4
  exports.trackingEnabled = trackingEnabled;
5
5
  exports._logTestFeedback = _logTestFeedback;
6
6
  const node_async_hooks_1 = require("node:async_hooks");
@@ -19,7 +19,6 @@ function trackingEnabled(context) {
19
19
  return (0, env_js_2.isEnvTracingEnabled)();
20
20
  }
21
21
  exports.evaluatorLogFeedbackPromises = new Set();
22
- exports.syncExamplePromises = new Map();
23
22
  function _logTestFeedback(params) {
24
23
  const { exampleId, feedback, context, runTree, client, sourceRunId } = params;
25
24
  if (trackingEnabled(context)) {
@@ -33,7 +32,7 @@ function _logTestFeedback(params) {
33
32
  if (context.project == null) {
34
33
  throw new Error("Could not log feedback to LangSmith: missing project information. Please contact us for help.");
35
34
  }
36
- await exports.syncExamplePromises.get(exampleId);
35
+ await context.syncExamplePromises?.get(exampleId);
37
36
  await client?.logEvaluationFeedback({
38
37
  evaluatorResponse: feedback,
39
38
  run: runTree,
@@ -20,11 +20,11 @@ export type TestWrapperAsyncLocalStorageData = {
20
20
  suiteName: string;
21
21
  testRootRunTree?: RunTree;
22
22
  setupPromise?: Promise<void>;
23
+ syncExamplePromises?: Map<string, Promise<Example>>;
23
24
  };
24
25
  export declare const testWrapperAsyncLocalStorageInstance: AsyncLocalStorage<TestWrapperAsyncLocalStorageData>;
25
26
  export declare function trackingEnabled(context: TestWrapperAsyncLocalStorageData): boolean;
26
27
  export declare const evaluatorLogFeedbackPromises: Set<unknown>;
27
- export declare const syncExamplePromises: Map<any, any>;
28
28
  export declare function _logTestFeedback(params: {
29
29
  exampleId?: string;
30
30
  feedback: SimpleEvaluationResult;
@@ -14,7 +14,6 @@ export function trackingEnabled(context) {
14
14
  return isEnvTracingEnabled();
15
15
  }
16
16
  export const evaluatorLogFeedbackPromises = new Set();
17
- export const syncExamplePromises = new Map();
18
17
  export function _logTestFeedback(params) {
19
18
  const { exampleId, feedback, context, runTree, client, sourceRunId } = params;
20
19
  if (trackingEnabled(context)) {
@@ -28,7 +27,7 @@ export function _logTestFeedback(params) {
28
27
  if (context.project == null) {
29
28
  throw new Error("Could not log feedback to LangSmith: missing project information. Please contact us for help.");
30
29
  }
31
- await syncExamplePromises.get(exampleId);
30
+ await context.syncExamplePromises?.get(exampleId);
32
31
  await client?.logEvaluationFeedback({
33
32
  evaluatorResponse: feedback,
34
33
  run: runTree,
@@ -300,6 +300,7 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
300
300
  },
301
301
  enableTestTracking: experimentConfig?.enableTestTracking,
302
302
  setupPromise,
303
+ syncExamplePromises: new Map(),
303
304
  };
304
305
  beforeAll(async () => {
305
306
  const storageValue = await runDatasetSetup(context);
@@ -307,15 +308,14 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
307
308
  setupPromiseResolver();
308
309
  });
309
310
  afterAll(async () => {
311
+ const examples = await Promise.all(context.syncExamplePromises.values());
310
312
  await Promise.all([
311
313
  client.awaitPendingTraceBatches(),
312
- ...globals_js_1.syncExamplePromises.values(),
313
314
  ...globals_js_1.evaluatorLogFeedbackPromises.values(),
314
315
  ]);
315
316
  if (!(0, globals_js_1.trackingEnabled)(context)) {
316
317
  return;
317
318
  }
318
- const examples = [...globals_js_1.syncExamplePromises.values()];
319
319
  if (examples.length === 0) {
320
320
  return;
321
321
  }
@@ -364,6 +364,7 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
364
364
  await client.updateProject(datasetInfo.project.id, {
365
365
  metadata: {
366
366
  ...suiteMetadata,
367
+ dataset_version: finalModifiedAt,
367
368
  commit,
368
369
  branch,
369
370
  dirty,
@@ -546,8 +547,11 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
546
547
  // Currently run end time has to be after example modified time
547
548
  // for examples to render properly, so we must modify the example
548
549
  // first before running the test.
549
- if (globals_js_1.syncExamplePromises.get(exampleId) === undefined) {
550
- globals_js_1.syncExamplePromises.set(exampleId, await syncExample({
550
+ if (context.syncExamplePromises === undefined) {
551
+ throw new Error("Internal error: syncExamplePromises not initialized. This is a bug in the LangSmith SDK — please open an issue at https://github.com/langchain-ai/langsmith-sdk/issues.");
552
+ }
553
+ if (!context.syncExamplePromises.has(exampleId)) {
554
+ const syncPromise = syncExample({
551
555
  client,
552
556
  exampleId,
553
557
  datasetId: dataset.id,
@@ -556,8 +560,10 @@ function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
556
560
  metadata,
557
561
  split,
558
562
  createdAt,
559
- }));
563
+ });
564
+ context.syncExamplePromises.set(exampleId, syncPromise);
560
565
  }
566
+ await context.syncExamplePromises.get(exampleId);
561
567
  const traceableOptions = {
562
568
  reference_example_id: exampleId,
563
569
  project_name: project.name,
@@ -9,7 +9,7 @@ import { execSync } from "child_process";
9
9
  import { getCurrentRunTree, traceable } from "../../traceable.js";
10
10
  import { randomName } from "../../evaluation/_random_name.js";
11
11
  import { toBeRelativeCloseTo, toBeAbsoluteCloseTo, toBeSemanticCloseTo, } from "./matchers.js";
12
- import { evaluatorLogFeedbackPromises, testWrapperAsyncLocalStorageInstance, _logTestFeedback, syncExamplePromises, trackingEnabled, DEFAULT_TEST_CLIENT, } from "./globals.js";
12
+ import { evaluatorLogFeedbackPromises, testWrapperAsyncLocalStorageInstance, _logTestFeedback, trackingEnabled, DEFAULT_TEST_CLIENT, } from "./globals.js";
13
13
  import { wrapExpect } from "./vendor/chain.js";
14
14
  import { getEnvironmentVariable, isJsDom } from "../env.js";
15
15
  import { STRIP_ANSI_REGEX, TEST_ID_DELIMITER, UUID5_NAMESPACE, } from "./constants.js";
@@ -253,6 +253,7 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
253
253
  },
254
254
  enableTestTracking: experimentConfig?.enableTestTracking,
255
255
  setupPromise,
256
+ syncExamplePromises: new Map(),
256
257
  };
257
258
  beforeAll(async () => {
258
259
  const storageValue = await runDatasetSetup(context);
@@ -260,15 +261,14 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
260
261
  setupPromiseResolver();
261
262
  });
262
263
  afterAll(async () => {
264
+ const examples = await Promise.all(context.syncExamplePromises.values());
263
265
  await Promise.all([
264
266
  client.awaitPendingTraceBatches(),
265
- ...syncExamplePromises.values(),
266
267
  ...evaluatorLogFeedbackPromises.values(),
267
268
  ]);
268
269
  if (!trackingEnabled(context)) {
269
270
  return;
270
271
  }
271
- const examples = [...syncExamplePromises.values()];
272
272
  if (examples.length === 0) {
273
273
  return;
274
274
  }
@@ -317,6 +317,7 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
317
317
  await client.updateProject(datasetInfo.project.id, {
318
318
  metadata: {
319
319
  ...suiteMetadata,
320
+ dataset_version: finalModifiedAt,
320
321
  commit,
321
322
  branch,
322
323
  dirty,
@@ -499,8 +500,11 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
499
500
  // Currently run end time has to be after example modified time
500
501
  // for examples to render properly, so we must modify the example
501
502
  // first before running the test.
502
- if (syncExamplePromises.get(exampleId) === undefined) {
503
- syncExamplePromises.set(exampleId, await syncExample({
503
+ if (context.syncExamplePromises === undefined) {
504
+ throw new Error("Internal error: syncExamplePromises not initialized. This is a bug in the LangSmith SDK — please open an issue at https://github.com/langchain-ai/langsmith-sdk/issues.");
505
+ }
506
+ if (!context.syncExamplePromises.has(exampleId)) {
507
+ const syncPromise = syncExample({
504
508
  client,
505
509
  exampleId,
506
510
  datasetId: dataset.id,
@@ -509,8 +513,10 @@ export function generateWrapperFromJestlikeMethods(methods, testRunnerName) {
509
513
  metadata,
510
514
  split,
511
515
  createdAt,
512
- }));
516
+ });
517
+ context.syncExamplePromises.set(exampleId, syncPromise);
513
518
  }
519
+ await context.syncExamplePromises.get(exampleId);
514
520
  const traceableOptions = {
515
521
  reference_example_id: exampleId,
516
522
  project_name: project.name,