langsmith 0.10.2 → 0.10.4

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/README.md CHANGED
@@ -341,7 +341,7 @@ import { wrapOpenAI } from "langsmith/wrappers";
341
341
  const openai = wrapOpenAI(new OpenAI());
342
342
 
343
343
  await openai.chat.completions.create({
344
- model: "gpt-3.5-turbo",
344
+ model: "gpt-5.4",
345
345
  messages: [{ content: "Hi there!", role: "user" }],
346
346
  });
347
347
  ```
@@ -359,7 +359,7 @@ const createCompletion = traceable(
359
359
  );
360
360
 
361
361
  await createCompletion({
362
- model: "gpt-3.5-turbo",
362
+ model: "gpt-5.4",
363
363
  messages: [{ content: "Hi there!", role: "user" }],
364
364
  });
365
365
  ```
@@ -375,7 +375,7 @@ within other functions wrapped with `traceable`.
375
375
  ```ts
376
376
  const nestedTrace = traceable(async (text: string) => {
377
377
  const completion = await openai.chat.completions.create({
378
- model: "gpt-3.5-turbo",
378
+ model: "gpt-5.4",
379
379
  messages: [{ content: text, role: "user" }],
380
380
  });
381
381
  return completion;
@@ -389,7 +389,7 @@ await nestedTrace("Why is the sky blue?");
389
389
  "id": "chatcmpl-8sPToJQLLVepJvyeTfzZMOMVIKjMo",
390
390
  "object": "chat.completion",
391
391
  "created": 1707978348,
392
- "model": "gpt-3.5-turbo-0613",
392
+ "model": "gpt-5.4",
393
393
  "choices": [
394
394
  {
395
395
  "index": 0,
@@ -435,14 +435,14 @@ const handler = traceable(
435
435
  const openai = wrapOpenAI(new OpenAI());
436
436
 
437
437
  const completion = await openai.chat.completions.create({
438
- model: "gpt-3.5-turbo",
438
+ model: "gpt-5.4",
439
439
  messages: [{ content: "Why is the sky blue?", role: "user" }],
440
440
  });
441
441
 
442
442
  const response1 = completion.choices[0].message.content;
443
443
 
444
444
  const completion2 = await openai.chat.completions.create({
445
- model: "gpt-3.5-turbo",
445
+ model: "gpt-5.4",
446
446
  messages: [
447
447
  { content: "Why is the sky blue?", role: "user" },
448
448
  { content: response1, role: "assistant" },
package/dist/client.cjs CHANGED
@@ -82,7 +82,7 @@ function _normalizeRunTimestamps(run) {
82
82
  end_time: _ensureUTCTimestamp(run.end_time),
83
83
  };
84
84
  }
85
- function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo) {
85
+ function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo, tracingSampleRate) {
86
86
  if (omitTracedRuntimeInfo) {
87
87
  return run;
88
88
  }
@@ -105,6 +105,9 @@ function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo) {
105
105
  }
106
106
  : {}),
107
107
  ...metadata,
108
+ ...(tracingSampleRate !== undefined
109
+ ? { ls_tracing_sample_rate: tracingSampleRate }
110
+ : {}),
108
111
  },
109
112
  };
110
113
  return run;
@@ -1617,7 +1620,7 @@ class Client {
1617
1620
  };
1618
1621
  }
1619
1622
  async _mergeRuntimeEnvAndMaskMetadata(run) {
1620
- const merged = mergeRuntimeEnvIntoRun(run, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo);
1623
+ const merged = mergeRuntimeEnvIntoRun(run, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo, this.tracingSampleRate);
1621
1624
  if (this.omitTracedRuntimeInfo) {
1622
1625
  return merged;
1623
1626
  }
@@ -1626,7 +1629,7 @@ class Client {
1626
1629
  async processRunOperation(item) {
1627
1630
  clearTimeout(this.autoBatchTimeout);
1628
1631
  this.autoBatchTimeout = undefined;
1629
- item.item = mergeRuntimeEnvIntoRun(item.item, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo);
1632
+ item.item = mergeRuntimeEnvIntoRun(item.item, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo, this.tracingSampleRate);
1630
1633
  const itemPromise = this.autoBatchQueue.push(item);
1631
1634
  if (this.manualFlushMode) {
1632
1635
  // Rely on manual flushing in serverless environments
@@ -2227,7 +2230,9 @@ class Client {
2227
2230
  if (options?.workspaceId !== undefined) {
2228
2231
  headers["x-tenant-id"] = options.workspaceId;
2229
2232
  }
2230
- const body = (0, index_js_4.serialize)(run, `Serializing payload to update run with id: ${runId}`);
2233
+ const body = (0, index_js_4.serialize)(run.extra
2234
+ ? mergeRuntimeEnvIntoRun(run, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo, this.tracingSampleRate)
2235
+ : run, `Serializing payload to update run with id: ${runId}`);
2231
2236
  await this.caller.call(async () => {
2232
2237
  const res = await this._fetch(`${options?.apiUrl ?? this.apiUrl}/runs/${runId}`, {
2233
2238
  method: "PATCH",
package/dist/client.d.ts CHANGED
@@ -445,7 +445,7 @@ type Thread = {
445
445
  export interface ListThreadsItem extends Thread {
446
446
  runs: Run[];
447
447
  }
448
- export declare function mergeRuntimeEnvIntoRun<T extends RunCreate | RunUpdate>(run: T, cachedEnvVars?: Record<string, string>, omitTracedRuntimeInfo?: boolean): T;
448
+ export declare function mergeRuntimeEnvIntoRun<T extends RunCreate | RunUpdate>(run: T, cachedEnvVars?: Record<string, string>, omitTracedRuntimeInfo?: boolean, tracingSampleRate?: number): T;
449
449
  export declare function _checkBackendVersion(backendVersion: string, minVersion: string): void;
450
450
  export declare const DEFAULT_UNCOMPRESSED_BATCH_SIZE_LIMIT_BYTES: number;
451
451
  /** Default maximum memory (1GB) for queue size limits. */
package/dist/client.js CHANGED
@@ -44,7 +44,7 @@ function _normalizeRunTimestamps(run) {
44
44
  end_time: _ensureUTCTimestamp(run.end_time),
45
45
  };
46
46
  }
47
- export function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo) {
47
+ export function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo, tracingSampleRate) {
48
48
  if (omitTracedRuntimeInfo) {
49
49
  return run;
50
50
  }
@@ -67,6 +67,9 @@ export function mergeRuntimeEnvIntoRun(run, cachedEnvVars, omitTracedRuntimeInfo
67
67
  }
68
68
  : {}),
69
69
  ...metadata,
70
+ ...(tracingSampleRate !== undefined
71
+ ? { ls_tracing_sample_rate: tracingSampleRate }
72
+ : {}),
70
73
  },
71
74
  };
72
75
  return run;
@@ -1578,7 +1581,7 @@ export class Client {
1578
1581
  };
1579
1582
  }
1580
1583
  async _mergeRuntimeEnvAndMaskMetadata(run) {
1581
- const merged = mergeRuntimeEnvIntoRun(run, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo);
1584
+ const merged = mergeRuntimeEnvIntoRun(run, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo, this.tracingSampleRate);
1582
1585
  if (this.omitTracedRuntimeInfo) {
1583
1586
  return merged;
1584
1587
  }
@@ -1587,7 +1590,7 @@ export class Client {
1587
1590
  async processRunOperation(item) {
1588
1591
  clearTimeout(this.autoBatchTimeout);
1589
1592
  this.autoBatchTimeout = undefined;
1590
- item.item = mergeRuntimeEnvIntoRun(item.item, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo);
1593
+ item.item = mergeRuntimeEnvIntoRun(item.item, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo, this.tracingSampleRate);
1591
1594
  const itemPromise = this.autoBatchQueue.push(item);
1592
1595
  if (this.manualFlushMode) {
1593
1596
  // Rely on manual flushing in serverless environments
@@ -2188,7 +2191,9 @@ export class Client {
2188
2191
  if (options?.workspaceId !== undefined) {
2189
2192
  headers["x-tenant-id"] = options.workspaceId;
2190
2193
  }
2191
- const body = serializePayloadForTracing(run, `Serializing payload to update run with id: ${runId}`);
2194
+ const body = serializePayloadForTracing(run.extra
2195
+ ? mergeRuntimeEnvIntoRun(run, this.cachedLSEnvVarsForMetadata, this.omitTracedRuntimeInfo, this.tracingSampleRate)
2196
+ : run, `Serializing payload to update run with id: ${runId}`);
2192
2197
  await this.caller.call(async () => {
2193
2198
  const res = await this._fetch(`${options?.apiUrl ?? this.apiUrl}/runs/${runId}`, {
2194
2199
  method: "PATCH",
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.10.2";
38
+ exports.__version__ = "0.10.4";
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.10.2";
10
+ export declare const __version__ = "0.10.4";
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.10.2";
10
+ export const __version__ = "0.10.4";
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";
@@ -523,12 +523,13 @@ class SandboxClient {
523
523
  const options = typeof newNameOrOptions === "string"
524
524
  ? { newName: newNameOrOptions }
525
525
  : newNameOrOptions;
526
- const { newName, idleTtlSeconds, deleteAfterStopSeconds } = options;
526
+ const { newName, idleTtlSeconds, deleteAfterStopSeconds, proxyConfig } = options;
527
527
  (0, helpers_js_1.validateTtl)(idleTtlSeconds, "idleTtlSeconds");
528
528
  (0, helpers_js_1.validateTtl)(deleteAfterStopSeconds, "deleteAfterStopSeconds");
529
529
  if (newName === undefined &&
530
530
  idleTtlSeconds === undefined &&
531
- deleteAfterStopSeconds === undefined) {
531
+ deleteAfterStopSeconds === undefined &&
532
+ proxyConfig === undefined) {
532
533
  return this.getSandbox(name);
533
534
  }
534
535
  const url = this._boxUrl(name);
@@ -542,6 +543,9 @@ class SandboxClient {
542
543
  if (deleteAfterStopSeconds !== undefined) {
543
544
  payload.delete_after_stop_seconds = deleteAfterStopSeconds;
544
545
  }
546
+ if (proxyConfig !== undefined) {
547
+ payload.proxy_config = proxyConfig;
548
+ }
545
549
  const response = await this._fetch(url, {
546
550
  method: "PATCH",
547
551
  headers: { "Content-Type": "application/json" },
@@ -556,6 +560,9 @@ class SandboxClient {
556
560
  ? `Sandbox name '${newName}' already in use`
557
561
  : "Sandbox update conflict (name may already be in use)", "sandbox");
558
562
  }
563
+ if (proxyConfig !== undefined) {
564
+ await (0, helpers_js_1.throwIfNotReady)(response, name);
565
+ }
559
566
  await (0, helpers_js_1.handleClientHttpError)(response);
560
567
  }
561
568
  const data = (await response.json());
@@ -122,8 +122,8 @@ export declare class SandboxClient {
122
122
  */
123
123
  updateSandbox(name: string, newName: string): Promise<Sandbox>;
124
124
  /**
125
- * Update a sandbox's name and/or retention settings (idle stop and
126
- * delete-after-stop).
125
+ * Update a sandbox's name, retention settings (idle stop and
126
+ * delete-after-stop), and/or proxy config.
127
127
  *
128
128
  * @param name - Current sandbox name.
129
129
  * @param options - Fields to update. Omit a field to leave it unchanged.
@@ -131,6 +131,8 @@ export declare class SandboxClient {
131
131
  * @throws LangSmithResourceNotFoundError if sandbox not found.
132
132
  * @throws LangSmithResourceNameConflictError if newName is already in use.
133
133
  * @throws LangSmithValidationError if retention values are invalid.
134
+ * @throws LangSmithSandboxNotReadyError if proxyConfig was given and the
135
+ * sandbox is not `ready`.
134
136
  */
135
137
  updateSandbox(name: string, options: UpdateSandboxOptions): Promise<Sandbox>;
136
138
  /**
@@ -7,7 +7,7 @@ import { AsyncCaller } from "../utils/async_caller.js";
7
7
  import { Langsmith as OpenAPILangsmith } from "../_openapi_client/index.js";
8
8
  import { Sandbox } from "./sandbox.js";
9
9
  import { LangSmithResourceCreationError, LangSmithResourceNameConflictError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithSandboxAPIError, LangSmithValidationError, } from "./errors.js";
10
- import { handleClientHttpError, handleSandboxCreationError, validateTtl, } from "./helpers.js";
10
+ import { handleClientHttpError, handleSandboxCreationError, throwIfNotReady, validateTtl, } from "./helpers.js";
11
11
  import { validateMountConfigProxyConfig } from "./mounts.js";
12
12
  import { v4 as uuidv4 } from "../utils/uuid/src/index.js";
13
13
  /**
@@ -520,12 +520,13 @@ export class SandboxClient {
520
520
  const options = typeof newNameOrOptions === "string"
521
521
  ? { newName: newNameOrOptions }
522
522
  : newNameOrOptions;
523
- const { newName, idleTtlSeconds, deleteAfterStopSeconds } = options;
523
+ const { newName, idleTtlSeconds, deleteAfterStopSeconds, proxyConfig } = options;
524
524
  validateTtl(idleTtlSeconds, "idleTtlSeconds");
525
525
  validateTtl(deleteAfterStopSeconds, "deleteAfterStopSeconds");
526
526
  if (newName === undefined &&
527
527
  idleTtlSeconds === undefined &&
528
- deleteAfterStopSeconds === undefined) {
528
+ deleteAfterStopSeconds === undefined &&
529
+ proxyConfig === undefined) {
529
530
  return this.getSandbox(name);
530
531
  }
531
532
  const url = this._boxUrl(name);
@@ -539,6 +540,9 @@ export class SandboxClient {
539
540
  if (deleteAfterStopSeconds !== undefined) {
540
541
  payload.delete_after_stop_seconds = deleteAfterStopSeconds;
541
542
  }
543
+ if (proxyConfig !== undefined) {
544
+ payload.proxy_config = proxyConfig;
545
+ }
542
546
  const response = await this._fetch(url, {
543
547
  method: "PATCH",
544
548
  headers: { "Content-Type": "application/json" },
@@ -553,6 +557,9 @@ export class SandboxClient {
553
557
  ? `Sandbox name '${newName}' already in use`
554
558
  : "Sandbox update conflict (name may already be in use)", "sandbox");
555
559
  }
560
+ if (proxyConfig !== undefined) {
561
+ await throwIfNotReady(response, name);
562
+ }
556
563
  await handleClientHttpError(response);
557
564
  }
558
565
  const data = (await response.json());
@@ -11,6 +11,7 @@ exports.parseErrorResponse = parseErrorResponse;
11
11
  exports.parseValidationError = parseValidationError;
12
12
  exports.extractQuotaType = extractQuotaType;
13
13
  exports.handleSandboxCreationError = handleSandboxCreationError;
14
+ exports.throwIfNotReady = throwIfNotReady;
14
15
  exports.handleClientHttpError = handleClientHttpError;
15
16
  exports.handleSandboxHttpError = handleSandboxHttpError;
16
17
  const errors_js_1 = require("./errors.cjs");
@@ -46,9 +47,12 @@ async function parseErrorResponse(response) {
46
47
  const detail = data?.detail;
47
48
  // Standardized format: {"detail": {"error": "...", "message": "..."}}
48
49
  if (detail && typeof detail === "object" && !Array.isArray(detail)) {
50
+ const message = detail.message || `HTTP ${response.status}: ${response.statusText}`;
49
51
  return {
50
52
  errorType: detail.error,
51
- message: detail.message || `HTTP ${response.status}: ${response.statusText}`,
53
+ message: typeof detail.error_id === "string" && detail.error_id
54
+ ? `${message} (error_id=${detail.error_id})`
55
+ : message,
52
56
  };
53
57
  }
54
58
  // Pydantic validation error format: {"detail": [{"loc": [...], "msg": "..."}]}
@@ -162,6 +166,25 @@ async function handleSandboxCreationError(response) {
162
166
  // Fall through to generic handling — pass clone since body is already consumed
163
167
  return handleClientHttpError(clonedResponse);
164
168
  }
169
+ /**
170
+ * Throw `LangSmithSandboxNotReadyError` when the API rejected a proxy-config
171
+ * update because the sandbox is not `ready`.
172
+ *
173
+ * That status check is the sole source of `InvalidRequest` on the update
174
+ * endpoint for the fields this client sends, so a typed error lets callers
175
+ * start the sandbox and retry instead of matching on status codes. Reads a
176
+ * clone so the caller's own error handling can still consume the body.
177
+ */
178
+ async function throwIfNotReady(response, name) {
179
+ if (response.status !== 400) {
180
+ return;
181
+ }
182
+ const data = await parseErrorResponse(response.clone());
183
+ if (data.errorType !== "InvalidRequest") {
184
+ return;
185
+ }
186
+ throw new errors_js_1.LangSmithSandboxNotReadyError(data.message || `Sandbox '${name}' is not ready`);
187
+ }
165
188
  /**
166
189
  * Handle HTTP errors and raise appropriate exceptions (for client operations).
167
190
  */
@@ -49,6 +49,16 @@ export declare function extractQuotaType(message: string): string | undefined;
49
49
  * - Other: Falls through to generic error handling
50
50
  */
51
51
  export declare function handleSandboxCreationError(response: Response): Promise<never>;
52
+ /**
53
+ * Throw `LangSmithSandboxNotReadyError` when the API rejected a proxy-config
54
+ * update because the sandbox is not `ready`.
55
+ *
56
+ * That status check is the sole source of `InvalidRequest` on the update
57
+ * endpoint for the fields this client sends, so a typed error lets callers
58
+ * start the sandbox and retry instead of matching on status codes. Reads a
59
+ * clone so the caller's own error handling can still consume the body.
60
+ */
61
+ export declare function throwIfNotReady(response: Response, name: string): Promise<void>;
52
62
  /**
53
63
  * Handle HTTP errors and raise appropriate exceptions (for client operations).
54
64
  */
@@ -37,9 +37,12 @@ export async function parseErrorResponse(response) {
37
37
  const detail = data?.detail;
38
38
  // Standardized format: {"detail": {"error": "...", "message": "..."}}
39
39
  if (detail && typeof detail === "object" && !Array.isArray(detail)) {
40
+ const message = detail.message || `HTTP ${response.status}: ${response.statusText}`;
40
41
  return {
41
42
  errorType: detail.error,
42
- message: detail.message || `HTTP ${response.status}: ${response.statusText}`,
43
+ message: typeof detail.error_id === "string" && detail.error_id
44
+ ? `${message} (error_id=${detail.error_id})`
45
+ : message,
43
46
  };
44
47
  }
45
48
  // Pydantic validation error format: {"detail": [{"loc": [...], "msg": "..."}]}
@@ -153,6 +156,25 @@ export async function handleSandboxCreationError(response) {
153
156
  // Fall through to generic handling — pass clone since body is already consumed
154
157
  return handleClientHttpError(clonedResponse);
155
158
  }
159
+ /**
160
+ * Throw `LangSmithSandboxNotReadyError` when the API rejected a proxy-config
161
+ * update because the sandbox is not `ready`.
162
+ *
163
+ * That status check is the sole source of `InvalidRequest` on the update
164
+ * endpoint for the fields this client sends, so a typed error lets callers
165
+ * start the sandbox and retry instead of matching on status codes. Reads a
166
+ * clone so the caller's own error handling can still consume the body.
167
+ */
168
+ export async function throwIfNotReady(response, name) {
169
+ if (response.status !== 400) {
170
+ return;
171
+ }
172
+ const data = await parseErrorResponse(response.clone());
173
+ if (data.errorType !== "InvalidRequest") {
174
+ return;
175
+ }
176
+ throw new LangSmithSandboxNotReadyError(data.message || `Sandbox '${name}' is not ready`);
177
+ }
156
178
  /**
157
179
  * Handle HTTP errors and raise appropriate exceptions (for client operations).
158
180
  */
@@ -643,7 +643,7 @@ export interface StartSandboxOptions {
643
643
  signal?: AbortSignal;
644
644
  }
645
645
  /**
646
- * Options for updating a sandbox (name and/or retention settings).
646
+ * Options for updating a sandbox (name, retention settings, proxy config).
647
647
  */
648
648
  export interface UpdateSandboxOptions {
649
649
  /** New display name. */
@@ -660,6 +660,15 @@ export interface UpdateSandboxOptions {
660
660
  * `undefined`) to leave the existing value unchanged.
661
661
  */
662
662
  deleteAfterStopSeconds?: number;
663
+ /**
664
+ * Replacement proxy configuration, sent to the server as-is (same shape as
665
+ * `createSandbox`). Rules replace the existing set rather than merging into
666
+ * it, so include every rule the sandbox should keep. Opaque header values
667
+ * carry over from the current config, so rotating one credential does not
668
+ * mean re-supplying secrets that can no longer be read. The sandbox must be
669
+ * `ready`; start a stopped one first.
670
+ */
671
+ proxyConfig?: SandboxProxyConfig;
663
672
  }
664
673
  /**
665
674
  * Options for waiting for a sandbox to become ready.
@@ -6,15 +6,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const reporters_1 = require("vitest/reporters");
7
7
  const reporter_js_1 = require("./utils/reporter.cjs");
8
8
  class LangSmithEvalReporter extends reporters_1.DefaultReporter {
9
+ constructor() {
10
+ super(...arguments);
11
+ Object.defineProperty(this, "skipOnFinished", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: false
16
+ });
17
+ }
9
18
  async onFinished(files, errors) {
10
- super.onFinished(files, errors);
19
+ const onFinished = reporters_1.DefaultReporter.prototype.onFinished;
20
+ onFinished?.call(this, files, errors);
21
+ if (this.skipOnFinished)
22
+ return;
11
23
  await (0, reporter_js_1.printVitestReporterTable)(files, this.ctx);
12
24
  }
13
- // @ts-expect-error Vitest 4.x introduces a new `onTestRunEnd` method
14
- async onTestRunEnd(testModules, unhandledErrors, reason) {
15
- // @ts-expect-error Vitest 4.x introduces a new `onTestRunEnd` method
16
- super.onTestRunEnd(testModules, unhandledErrors, reason);
17
- await (0, reporter_js_1.printVitestTestModulesReporterTable)(testModules);
25
+ async onTestRunEnd(...args) {
26
+ super.onTestRunEnd(...args);
27
+ this.skipOnFinished = true;
28
+ await (0, reporter_js_1.printVitestTestModulesReporterTable)(args[0]);
18
29
  }
19
30
  }
20
31
  exports.default = LangSmithEvalReporter;
@@ -1,8 +1,8 @@
1
1
  import { DefaultReporter } from "vitest/reporters";
2
2
  import { RunnerTestFile } from "vitest";
3
- import { type VitestTestModule } from "./utils/reporter.js";
4
3
  declare class LangSmithEvalReporter extends DefaultReporter {
4
+ private skipOnFinished;
5
5
  onFinished(files: RunnerTestFile[], errors: unknown[]): Promise<void>;
6
- onTestRunEnd(testModules: VitestTestModule[], unhandledErrors: unknown[], reason: "passed" | "interrupted" | "failed"): Promise<void>;
6
+ onTestRunEnd(...args: Parameters<DefaultReporter["onTestRunEnd"]>): Promise<void>;
7
7
  }
8
8
  export default LangSmithEvalReporter;
@@ -4,15 +4,26 @@
4
4
  import { DefaultReporter } from "vitest/reporters";
5
5
  import { printVitestReporterTable, printVitestTestModulesReporterTable, } from "./utils/reporter.js";
6
6
  class LangSmithEvalReporter extends DefaultReporter {
7
+ constructor() {
8
+ super(...arguments);
9
+ Object.defineProperty(this, "skipOnFinished", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: false
14
+ });
15
+ }
7
16
  async onFinished(files, errors) {
8
- super.onFinished(files, errors);
17
+ const onFinished = DefaultReporter.prototype.onFinished;
18
+ onFinished?.call(this, files, errors);
19
+ if (this.skipOnFinished)
20
+ return;
9
21
  await printVitestReporterTable(files, this.ctx);
10
22
  }
11
- // @ts-expect-error Vitest 4.x introduces a new `onTestRunEnd` method
12
- async onTestRunEnd(testModules, unhandledErrors, reason) {
13
- // @ts-expect-error Vitest 4.x introduces a new `onTestRunEnd` method
14
- super.onTestRunEnd(testModules, unhandledErrors, reason);
15
- await printVitestTestModulesReporterTable(testModules);
23
+ async onTestRunEnd(...args) {
24
+ super.onTestRunEnd(...args);
25
+ this.skipOnFinished = true;
26
+ await printVitestTestModulesReporterTable(args[0]);
16
27
  }
17
28
  }
18
29
  export default LangSmithEvalReporter;
@@ -32,7 +32,8 @@ const printVitestTestModulesReporterTable = async (testModules) => {
32
32
  duration: Math.round(test.diagnostic()?.duration ?? 0),
33
33
  };
34
34
  });
35
- await (0, reporter_js_1.printReporterTable)(testModule.relativeModuleId, tests, testModule.state());
35
+ const state = testModule.state();
36
+ await (0, reporter_js_1.printReporterTable)(testModule.relativeModuleId, tests, state === "pending" || state === "queued" ? "skipped" : state);
36
37
  }
37
38
  };
38
39
  exports.printVitestTestModulesReporterTable = printVitestTestModulesReporterTable;
@@ -1,31 +1,17 @@
1
1
  export declare const printVitestReporterTable: (files: any, ctx: any) => Promise<void>;
2
2
  export interface VitestTestModule {
3
3
  children: {
4
- allTests: () => {
4
+ allTests: () => Iterable<{
5
5
  name: string;
6
6
  result: () => {
7
7
  state: "pending" | "passed" | "failed" | "skipped";
8
8
  };
9
9
  diagnostic: () => {
10
10
  duration: number;
11
- };
12
- }[];
11
+ } | undefined;
12
+ }>;
13
13
  };
14
- state: () => "skipped" | "passed" | "failed";
14
+ state: () => "pending" | "queued" | "skipped" | "passed" | "failed";
15
15
  relativeModuleId: string;
16
16
  }
17
- export declare const printVitestTestModulesReporterTable: (testModules: {
18
- children: {
19
- allTests: () => {
20
- name: string;
21
- result: () => {
22
- state: "pending" | "passed" | "failed" | "skipped";
23
- };
24
- diagnostic: () => {
25
- duration: number;
26
- };
27
- }[];
28
- };
29
- state: () => "skipped" | "passed" | "failed";
30
- relativeModuleId: string;
31
- }[]) => Promise<void>;
17
+ export declare const printVitestTestModulesReporterTable: (testModules: readonly VitestTestModule[]) => Promise<void>;
@@ -28,6 +28,7 @@ export const printVitestTestModulesReporterTable = async (testModules) => {
28
28
  duration: Math.round(test.diagnostic()?.duration ?? 0),
29
29
  };
30
30
  });
31
- await printReporterTable(testModule.relativeModuleId, tests, testModule.state());
31
+ const state = testModule.state();
32
+ await printReporterTable(testModule.relativeModuleId, tests, state === "pending" || state === "queued" ? "skipped" : state);
32
33
  }
33
34
  };
@@ -369,6 +369,33 @@ function createResponsesUsageMetadata(usage) {
369
369
  }
370
370
  return result;
371
371
  }
372
+ /**
373
+ * Return an ls_agent_type for the span, or undefined to leave it alone.
374
+ *
375
+ * If the run already has middleware/subagent/compaction, keep it. Otherwise
376
+ * guardrails become middleware and agents under any tool become subagents.
377
+ *
378
+ * Openai-agents structural counterpart to resolveVercelLsAgentType.
379
+ */
380
+ function _resolveOpenAIAgentsLsAgentType(spanData, parentRun, existingTag) {
381
+ if (typeof existingTag === "string" &&
382
+ ls_agent_type_js_1.NON_ROOT_LS_AGENT_TYPES.has(existingTag)) {
383
+ return undefined;
384
+ }
385
+ if (spanData.type === "guardrail")
386
+ return "middleware";
387
+ // Walk the full parent chain, not just the direct parent: asTool inserts
388
+ // a chain run between the tool and the inner agent span.
389
+ if (spanData.type === "agent") {
390
+ let cursor = parentRun;
391
+ while (cursor !== undefined) {
392
+ if (cursor.run_type === "tool")
393
+ return "subagent";
394
+ cursor = cursor.parent_run;
395
+ }
396
+ }
397
+ return undefined;
398
+ }
372
399
  /**
373
400
  * Tracing processor for the [OpenAI Agents SDK](https://openai.github.io/openai-agents-js/).
374
401
  *
@@ -463,12 +490,6 @@ class OpenAIAgentsTracingProcessor {
463
490
  writable: true,
464
491
  value: new Map()
465
492
  });
466
- Object.defineProperty(this, "_spanDataTypes", {
467
- enumerable: true,
468
- configurable: true,
469
- writable: true,
470
- value: new Map()
471
- });
472
493
  Object.defineProperty(this, "_unpostedTraces", {
473
494
  enumerable: true,
474
495
  configurable: true,
@@ -684,28 +705,17 @@ class OpenAIAgentsTracingProcessor {
684
705
  console.error("Error creating span run:", e);
685
706
  return;
686
707
  }
687
- // Add ls_agent_type metadata for agent spans that are children of
688
- // function spans (i.e., agents used as tools).
689
- // Handoff agents are not considered subagents.
690
- if (spanData.type === "agent") {
691
- const parentSpanType = parentId
692
- ? this._spanDataTypes.get(parentId)
693
- : undefined;
694
- if (parentSpanType === "function") {
695
- if (!childRun.extra) {
696
- childRun.extra = {};
697
- }
698
- if (!childRun.extra.metadata) {
699
- childRun.extra.metadata = {};
700
- }
701
- const meta = childRun.extra.metadata;
702
- if (!ls_agent_type_js_1.NON_ROOT_LS_AGENT_TYPES.has(meta.ls_agent_type)) {
703
- meta.ls_agent_type = "subagent";
704
- }
705
- }
708
+ // Handoffs replace the caller rather than run as a tool, so a handoff
709
+ // agent has no tool ancestor and correctly stays untagged here.
710
+ if (!childRun.extra)
711
+ childRun.extra = {};
712
+ if (!childRun.extra.metadata)
713
+ childRun.extra.metadata = {};
714
+ const meta = childRun.extra.metadata;
715
+ const structuralTag = _resolveOpenAIAgentsLsAgentType(spanData, parentRun, meta.ls_agent_type);
716
+ if (structuralTag !== undefined) {
717
+ meta.ls_agent_type = structuralTag;
706
718
  }
707
- // Track span data type for parent lookups
708
- this._spanDataTypes.set(span.spanId, spanData.type);
709
719
  this._runs.set(span.spanId, childRun);
710
720
  // Enter AsyncLocalStorage context synchronously so nested traceable()
711
721
  // calls inside the span's body nest under this run. Remember the
@@ -741,7 +751,6 @@ class OpenAIAgentsTracingProcessor {
741
751
  enterRunTreeContext(previousStore);
742
752
  }
743
753
  const run = this._runs.get(span.spanId);
744
- this._spanDataTypes.delete(span.spanId);
745
754
  if (!run) {
746
755
  return;
747
756
  }
@@ -69,7 +69,6 @@ export declare class OpenAIAgentsTracingProcessor implements TracingProcessor {
69
69
  private _firstResponseInputs;
70
70
  private _lastResponseOutputs;
71
71
  private _runs;
72
- private _spanDataTypes;
73
72
  private _unpostedTraces;
74
73
  private _unpostedSpans;
75
74
  private _previousStoreByTrace;
@@ -366,6 +366,33 @@ function createResponsesUsageMetadata(usage) {
366
366
  }
367
367
  return result;
368
368
  }
369
+ /**
370
+ * Return an ls_agent_type for the span, or undefined to leave it alone.
371
+ *
372
+ * If the run already has middleware/subagent/compaction, keep it. Otherwise
373
+ * guardrails become middleware and agents under any tool become subagents.
374
+ *
375
+ * Openai-agents structural counterpart to resolveVercelLsAgentType.
376
+ */
377
+ function _resolveOpenAIAgentsLsAgentType(spanData, parentRun, existingTag) {
378
+ if (typeof existingTag === "string" &&
379
+ NON_ROOT_LS_AGENT_TYPES.has(existingTag)) {
380
+ return undefined;
381
+ }
382
+ if (spanData.type === "guardrail")
383
+ return "middleware";
384
+ // Walk the full parent chain, not just the direct parent: asTool inserts
385
+ // a chain run between the tool and the inner agent span.
386
+ if (spanData.type === "agent") {
387
+ let cursor = parentRun;
388
+ while (cursor !== undefined) {
389
+ if (cursor.run_type === "tool")
390
+ return "subagent";
391
+ cursor = cursor.parent_run;
392
+ }
393
+ }
394
+ return undefined;
395
+ }
369
396
  /**
370
397
  * Tracing processor for the [OpenAI Agents SDK](https://openai.github.io/openai-agents-js/).
371
398
  *
@@ -460,12 +487,6 @@ export class OpenAIAgentsTracingProcessor {
460
487
  writable: true,
461
488
  value: new Map()
462
489
  });
463
- Object.defineProperty(this, "_spanDataTypes", {
464
- enumerable: true,
465
- configurable: true,
466
- writable: true,
467
- value: new Map()
468
- });
469
490
  Object.defineProperty(this, "_unpostedTraces", {
470
491
  enumerable: true,
471
492
  configurable: true,
@@ -681,28 +702,17 @@ export class OpenAIAgentsTracingProcessor {
681
702
  console.error("Error creating span run:", e);
682
703
  return;
683
704
  }
684
- // Add ls_agent_type metadata for agent spans that are children of
685
- // function spans (i.e., agents used as tools).
686
- // Handoff agents are not considered subagents.
687
- if (spanData.type === "agent") {
688
- const parentSpanType = parentId
689
- ? this._spanDataTypes.get(parentId)
690
- : undefined;
691
- if (parentSpanType === "function") {
692
- if (!childRun.extra) {
693
- childRun.extra = {};
694
- }
695
- if (!childRun.extra.metadata) {
696
- childRun.extra.metadata = {};
697
- }
698
- const meta = childRun.extra.metadata;
699
- if (!NON_ROOT_LS_AGENT_TYPES.has(meta.ls_agent_type)) {
700
- meta.ls_agent_type = "subagent";
701
- }
702
- }
705
+ // Handoffs replace the caller rather than run as a tool, so a handoff
706
+ // agent has no tool ancestor and correctly stays untagged here.
707
+ if (!childRun.extra)
708
+ childRun.extra = {};
709
+ if (!childRun.extra.metadata)
710
+ childRun.extra.metadata = {};
711
+ const meta = childRun.extra.metadata;
712
+ const structuralTag = _resolveOpenAIAgentsLsAgentType(spanData, parentRun, meta.ls_agent_type);
713
+ if (structuralTag !== undefined) {
714
+ meta.ls_agent_type = structuralTag;
703
715
  }
704
- // Track span data type for parent lookups
705
- this._spanDataTypes.set(span.spanId, spanData.type);
706
716
  this._runs.set(span.spanId, childRun);
707
717
  // Enter AsyncLocalStorage context synchronously so nested traceable()
708
718
  // calls inside the span's body nest under this run. Remember the
@@ -738,7 +748,6 @@ export class OpenAIAgentsTracingProcessor {
738
748
  enterRunTreeContext(previousStore);
739
749
  }
740
750
  const run = this._runs.get(span.spanId);
741
- this._spanDataTypes.delete(span.spanId);
742
751
  if (!run) {
743
752
  return;
744
753
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
5
5
  "packageManager": "pnpm@10.33.0",
6
6
  "files": [
@@ -209,7 +209,7 @@
209
209
  "typedoc": "^0.28.16",
210
210
  "typedoc-plugin-expand-object-like-types": "^0.1.2",
211
211
  "typescript": "^6.0.3",
212
- "vitest": "^3.1.3",
212
+ "vitest": "^4.1.11",
213
213
  "ws": "^8.19.0",
214
214
  "zod": "^4.3.6"
215
215
  },
@@ -488,8 +488,8 @@
488
488
  "brace-expansion@>=3.0.0 <5.0.7": "5.0.7",
489
489
  "fast-uri": "3.1.6",
490
490
  "postcss": "8.5.23",
491
- "js-yaml": "4.3.1",
492
- "hono": "4.12.34",
491
+ "js-yaml": "4.3.2",
492
+ "hono": "4.13.5",
493
493
  "ip-address": "10.3.1",
494
494
  "undici@>=7.0.0 <8.0.0": "7.29.0",
495
495
  "undici@>=8.0.0 <9.0.0": "8.9.0",