langsmith 0.5.19 → 0.6.0

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.
Files changed (161) hide show
  1. package/dist/client.cjs +460 -22
  2. package/dist/client.d.ts +165 -1
  3. package/dist/client.js +464 -26
  4. package/dist/evaluation/_runner.cjs +4 -7
  5. package/dist/evaluation/_runner.js +2 -5
  6. package/dist/evaluation/evaluate_comparative.cjs +10 -10
  7. package/dist/evaluation/evaluate_comparative.js +1 -1
  8. package/dist/evaluation/evaluator.cjs +2 -2
  9. package/dist/evaluation/evaluator.js +1 -1
  10. package/dist/experimental/anthropic/context.cjs +419 -49
  11. package/dist/experimental/anthropic/context.js +420 -50
  12. package/dist/experimental/anthropic/index.cjs +78 -10
  13. package/dist/experimental/anthropic/index.js +80 -12
  14. package/dist/experimental/anthropic/messages.cjs +53 -0
  15. package/dist/experimental/anthropic/messages.d.ts +6 -0
  16. package/dist/experimental/anthropic/messages.js +52 -0
  17. package/dist/experimental/anthropic/transcripts.cjs +144 -0
  18. package/dist/experimental/anthropic/transcripts.d.ts +22 -0
  19. package/dist/experimental/anthropic/transcripts.js +141 -0
  20. package/dist/experimental/anthropic/types.d.ts +1 -0
  21. package/dist/experimental/anthropic/usage.cjs +19 -20
  22. package/dist/experimental/anthropic/usage.d.ts +2 -1
  23. package/dist/experimental/anthropic/usage.js +18 -20
  24. package/dist/experimental/opencode/index.cjs +36 -0
  25. package/dist/experimental/opencode/index.d.ts +3 -0
  26. package/dist/experimental/opencode/index.js +32 -0
  27. package/dist/experimental/opencode/tracer.cjs +389 -0
  28. package/dist/experimental/opencode/tracer.d.ts +30 -0
  29. package/dist/experimental/opencode/tracer.js +385 -0
  30. package/dist/experimental/otel/setup.cjs +1 -1
  31. package/dist/experimental/otel/setup.js +1 -1
  32. package/dist/experimental/sandbox/client.cjs +308 -409
  33. package/dist/experimental/sandbox/client.d.ts +136 -157
  34. package/dist/experimental/sandbox/client.js +310 -411
  35. package/dist/experimental/sandbox/errors.cjs +1 -2
  36. package/dist/experimental/sandbox/errors.d.ts +1 -2
  37. package/dist/experimental/sandbox/errors.js +1 -2
  38. package/dist/experimental/sandbox/helpers.cjs +8 -98
  39. package/dist/experimental/sandbox/helpers.d.ts +0 -29
  40. package/dist/experimental/sandbox/helpers.js +9 -95
  41. package/dist/experimental/sandbox/index.cjs +6 -1
  42. package/dist/experimental/sandbox/index.d.ts +7 -2
  43. package/dist/experimental/sandbox/index.js +6 -1
  44. package/dist/experimental/sandbox/sandbox.cjs +71 -13
  45. package/dist/experimental/sandbox/sandbox.d.ts +39 -9
  46. package/dist/experimental/sandbox/sandbox.js +71 -13
  47. package/dist/experimental/sandbox/types.d.ts +147 -137
  48. package/dist/experimental/vercel/index.cjs +1 -1
  49. package/dist/experimental/vercel/index.js +1 -1
  50. package/dist/experimental/vercel/middleware.cjs +2 -1
  51. package/dist/experimental/vercel/middleware.js +2 -1
  52. package/dist/index.cjs +1 -1
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/run_trees.cjs +8 -7
  56. package/dist/run_trees.d.ts +7 -0
  57. package/dist/run_trees.js +7 -6
  58. package/dist/schemas.d.ts +58 -0
  59. package/dist/singletons/otel.cjs +3 -2
  60. package/dist/singletons/otel.js +4 -3
  61. package/dist/singletons/traceable.cjs +1 -3
  62. package/dist/singletons/traceable.js +1 -3
  63. package/dist/traceable.cjs +2 -5
  64. package/dist/traceable.js +2 -5
  65. package/dist/utils/_git.cjs +2 -2
  66. package/dist/utils/_git.js +2 -2
  67. package/dist/utils/_uuid.cjs +2 -2
  68. package/dist/utils/_uuid.js +1 -1
  69. package/dist/utils/env.cjs +35 -2
  70. package/dist/utils/env.d.ts +9 -0
  71. package/dist/utils/env.js +34 -2
  72. package/dist/utils/error.cjs +9 -2
  73. package/dist/utils/error.d.ts +1 -0
  74. package/dist/utils/error.js +8 -2
  75. package/dist/utils/fast-safe-stringify/index.cjs +203 -0
  76. package/dist/utils/fast-safe-stringify/index.d.ts +46 -0
  77. package/dist/utils/fast-safe-stringify/index.js +202 -0
  78. package/dist/utils/jestlike/index.cjs +5 -5
  79. package/dist/utils/jestlike/index.js +1 -1
  80. package/dist/utils/jestlike/reporter.cjs +1 -1
  81. package/dist/utils/jestlike/reporter.js +1 -1
  82. package/dist/utils/jestlike/vendor/chain.cjs +2 -3
  83. package/dist/utils/jestlike/vendor/chain.js +2 -3
  84. package/dist/utils/jestlike/vendor/evaluatedBy.cjs +3 -3
  85. package/dist/utils/jestlike/vendor/evaluatedBy.js +1 -1
  86. package/dist/utils/prompts.cjs +7 -2
  87. package/dist/utils/prompts.d.ts +6 -1
  88. package/dist/utils/prompts.js +6 -1
  89. package/dist/utils/serialize_worker.cjs +388 -0
  90. package/dist/utils/serialize_worker.d.ts +66 -0
  91. package/dist/utils/serialize_worker.js +382 -0
  92. package/dist/utils/uuid/src/index.cjs +24 -0
  93. package/dist/utils/uuid/src/index.d.ts +10 -0
  94. package/dist/utils/uuid/src/index.js +9 -0
  95. package/dist/utils/uuid/src/max.cjs +3 -0
  96. package/dist/utils/uuid/src/max.d.ts +2 -0
  97. package/dist/utils/uuid/src/max.js +1 -0
  98. package/dist/utils/uuid/src/nil.cjs +3 -0
  99. package/dist/utils/uuid/src/nil.d.ts +2 -0
  100. package/dist/utils/uuid/src/nil.js +1 -0
  101. package/dist/utils/uuid/src/parse.cjs +23 -0
  102. package/dist/utils/uuid/src/parse.d.ts +3 -0
  103. package/dist/utils/uuid/src/parse.js +18 -0
  104. package/dist/utils/uuid/src/regex.cjs +3 -0
  105. package/dist/utils/uuid/src/regex.d.ts +2 -0
  106. package/dist/utils/uuid/src/regex.js +1 -0
  107. package/dist/utils/uuid/src/rng.cjs +10 -0
  108. package/dist/utils/uuid/src/rng.d.ts +1 -0
  109. package/dist/utils/uuid/src/rng.js +7 -0
  110. package/dist/utils/uuid/src/sha1.cjs +75 -0
  111. package/dist/utils/uuid/src/sha1.d.ts +2 -0
  112. package/dist/utils/uuid/src/sha1.js +73 -0
  113. package/dist/utils/uuid/src/stringify.cjs +55 -0
  114. package/dist/utils/uuid/src/stringify.d.ts +3 -0
  115. package/dist/utils/uuid/src/stringify.js +49 -0
  116. package/dist/utils/uuid/src/types.cjs +2 -0
  117. package/dist/utils/uuid/src/types.d.ts +22 -0
  118. package/dist/utils/uuid/src/types.js +1 -0
  119. package/dist/utils/uuid/src/v35.cjs +52 -0
  120. package/dist/utils/uuid/src/v35.d.ts +7 -0
  121. package/dist/utils/uuid/src/v35.js +44 -0
  122. package/dist/utils/uuid/src/v4.cjs +40 -0
  123. package/dist/utils/uuid/src/v4.d.ts +4 -0
  124. package/dist/utils/uuid/src/v4.js +35 -0
  125. package/dist/utils/uuid/src/v5.cjs +50 -0
  126. package/dist/utils/uuid/src/v5.d.ts +9 -0
  127. package/dist/utils/uuid/src/v5.js +9 -0
  128. package/dist/utils/uuid/src/v7.cjs +88 -0
  129. package/dist/utils/uuid/src/v7.d.ts +9 -0
  130. package/dist/utils/uuid/src/v7.js +82 -0
  131. package/dist/utils/uuid/src/validate.cjs +10 -0
  132. package/dist/utils/uuid/src/validate.d.ts +2 -0
  133. package/dist/utils/uuid/src/validate.js +5 -0
  134. package/dist/utils/uuid/src/version.cjs +13 -0
  135. package/dist/utils/uuid/src/version.d.ts +2 -0
  136. package/dist/utils/uuid/src/version.js +8 -0
  137. package/dist/utils/worker_threads.browser.cjs +16 -0
  138. package/dist/utils/worker_threads.browser.d.ts +14 -0
  139. package/dist/utils/worker_threads.browser.js +13 -0
  140. package/dist/utils/worker_threads.cjs +16 -0
  141. package/dist/utils/worker_threads.d.ts +13 -0
  142. package/dist/utils/worker_threads.js +13 -0
  143. package/dist/uuid.cjs +2 -2
  144. package/dist/uuid.js +1 -1
  145. package/dist/vitest/utils/esm.mjs +4 -4
  146. package/dist/wrappers/gemini.cjs +1 -1
  147. package/dist/wrappers/gemini.js +1 -1
  148. package/dist/wrappers/openai.cjs +2 -6
  149. package/dist/wrappers/openai.js +2 -6
  150. package/dist/wrappers/openai_agents.cjs +849 -0
  151. package/dist/wrappers/openai_agents.d.ts +92 -0
  152. package/dist/wrappers/openai_agents.js +845 -0
  153. package/experimental/opencode.cjs +1 -0
  154. package/experimental/opencode.d.cts +1 -0
  155. package/experimental/opencode.d.ts +1 -0
  156. package/experimental/opencode.js +1 -0
  157. package/package.json +47 -25
  158. package/wrappers/openai_agents.cjs +1 -0
  159. package/wrappers/openai_agents.d.cts +1 -0
  160. package/wrappers/openai_agents.d.ts +1 -0
  161. package/wrappers/openai_agents.js +1 -0
package/dist/client.js CHANGED
@@ -1,18 +1,25 @@
1
- import * as uuid from "uuid";
1
+ import * as uuid from "./utils/uuid/src/index.js";
2
2
  import { LangSmithToOTELTranslator, } from "./experimental/otel/translator.js";
3
3
  import { getDefaultOTLPTracerComponents, getOTELTrace, getOTELContext, } from "./singletons/otel.js";
4
4
  import { AsyncCaller } from "./utils/async_caller.js";
5
5
  import { convertLangChainMessageToExample, isLangChainMessage, } from "./utils/messages.js";
6
- import { getEnvironmentVariable, getLangSmithEnvVarsMetadata, getLangSmithEnvironmentVariable, getRuntimeEnvironment, getOtelEnabled, getEnv, } from "./utils/env.js";
6
+ import { getEnvironmentVariable, getLangSmithEnvVarsMetadata, getLangSmithEnvironmentVariable, getRuntimeEnvironment, getEnv, resolveTracingMode, } from "./utils/env.js";
7
7
  import { __version__ } from "./index.js";
8
8
  import { assertUuid } from "./utils/_uuid.js";
9
9
  import { warnOnce } from "./utils/warn.js";
10
- import { parsePromptIdentifier } from "./utils/prompts.js";
11
- import { raiseForStatus, isLangSmithNotFoundError } from "./utils/error.js";
10
+ import { parseHubIdentifier } from "./utils/prompts.js";
11
+ import { raiseForStatus, isLangSmithNotFoundError, isLangSmithConflictError, } from "./utils/error.js";
12
12
  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
- import { serialize as serializePayloadForTracing } from "./utils/fast-safe-stringify/index.js";
15
+ import { serialize as serializePayloadForTracing, estimateSerializedSize, } from "./utils/fast-safe-stringify/index.js";
16
+ import { getSharedSerializeWorker, hasLargeString, } from "./utils/serialize_worker.js";
17
+ function assertPullPublicPromptAllowed(promptIdentifier, dangerouslyPullPublicPrompt) {
18
+ const [owner] = parseHubIdentifier(promptIdentifier);
19
+ if (owner !== "-" && !dangerouslyPullPublicPrompt) {
20
+ throw new Error("Pulling a public prompt by owner/name is disabled by default because prompts may contain untrusted serialized LangChain objects. If you trust this prompt, set `dangerouslyPullPublicPrompt: true` to acknowledge the risk.");
21
+ }
22
+ }
16
23
  /**
17
24
  * Catches timestamps without a timezone suffix.
18
25
  */
@@ -152,7 +159,11 @@ export class AutoBatchQueue {
152
159
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
153
160
  itemPromiseResolve = resolve;
154
161
  });
155
- const size = serializePayloadForTracing(item.item, `Serializing run with id: ${item.item.id}`).length;
162
+ // Use a cheap structural estimate for soft memory accounting (queue size
163
+ // limit and downstream async caller memory tracking). The exact
164
+ // serialization still happens later, off the hot path, when the batch is
165
+ // assembled for sending.
166
+ const size = estimateSerializedSize(item.item).size;
156
167
  // Check if adding this item would exceed the size limit
157
168
  // Allow the run if the queue is empty (to support large single traces)
158
169
  if (this.sizeBytes + size > this.maxSizeBytes && this.items.length > 0) {
@@ -215,9 +226,64 @@ export class AutoBatchQueue {
215
226
  }
216
227
  }
217
228
  export class Client {
229
+ get tracingMode() {
230
+ return this._tracingMode;
231
+ }
218
232
  get _fetch() {
219
233
  return this.fetchImplementation || _getFetchImplementation(this.debug);
220
234
  }
235
+ /**
236
+ * Serialize a payload for tracing, optionally offloading the work to a
237
+ * Node worker thread when the runtime supports worker_threads.
238
+ *
239
+ * Falls back to synchronous serialization when:
240
+ * - manualFlushMode is enabled (serverless: worker boot cost > benefit)
241
+ * - worker_threads is unavailable (non-Node runtimes)
242
+ * - the payload contains values that can't be structured-cloned across
243
+ * threads (functions, non-cloneable class instances, streams, etc.)
244
+ * - the worker throws for any other reason
245
+ *
246
+ * In all fallback cases the returned bytes are identical to the sync path.
247
+ */
248
+ _trackDrain(promise) {
249
+ this._pendingDrains.add(promise);
250
+ promise.finally(() => {
251
+ this._pendingDrains.delete(promise);
252
+ });
253
+ }
254
+ async _serializeBody(payload, errorContext) {
255
+ if (this.manualFlushMode) {
256
+ return serializePayloadForTracing(payload, errorContext);
257
+ }
258
+ // Shape-aware gate: worker offload pays for itself only when the
259
+ // payload is dominated by one or more large strings (V8 can refcount
260
+ // those across isolates instead of copying). For structure-heavy
261
+ // payloads -- many keys, deep nesting, lots of small strings -- the
262
+ // structuredClone walk plus thread-hop cost exceeds the JSON.stringify
263
+ // cost we would pay inline, so we fall through to sync serialize.
264
+ if (!hasLargeString(payload)) {
265
+ return serializePayloadForTracing(payload, errorContext);
266
+ }
267
+ if (this._serializeWorker === undefined) {
268
+ this._serializeWorker = getSharedSerializeWorker();
269
+ }
270
+ if (this._serializeWorker === null) {
271
+ return serializePayloadForTracing(payload, errorContext);
272
+ }
273
+ try {
274
+ const bytes = await this._serializeWorker.serialize(payload);
275
+ if (bytes === null) {
276
+ // Worker subsystem unavailable; cache the null to skip re-entry.
277
+ this._serializeWorker = null;
278
+ return serializePayloadForTracing(payload, errorContext);
279
+ }
280
+ return bytes;
281
+ }
282
+ catch {
283
+ // DataCloneError, worker crash, etc. Fall back silently.
284
+ return serializePayloadForTracing(payload, errorContext);
285
+ }
286
+ }
221
287
  constructor(config = {}) {
222
288
  Object.defineProperty(this, "apiKey", {
223
289
  enumerable: true,
@@ -279,6 +345,12 @@ export class Client {
279
345
  writable: true,
280
346
  value: void 0
281
347
  });
348
+ Object.defineProperty(this, "hideMetadata", {
349
+ enumerable: true,
350
+ configurable: true,
351
+ writable: true,
352
+ value: void 0
353
+ });
282
354
  Object.defineProperty(this, "omitTracedRuntimeInfo", {
283
355
  enumerable: true,
284
356
  configurable: true,
@@ -376,12 +448,35 @@ export class Client {
376
448
  writable: true,
377
449
  value: false
378
450
  });
451
+ Object.defineProperty(this, "_serializeWorker", {
452
+ enumerable: true,
453
+ configurable: true,
454
+ writable: true,
455
+ value: void 0
456
+ });
457
+ /**
458
+ * Tracks in-flight drainAutoBatchQueue promises so awaitPendingTraceBatches
459
+ * can wait on them even if the flush involves async work (worker-thread
460
+ * serialize) that hasn't yet registered with batchIngestCaller.queue.
461
+ */
462
+ Object.defineProperty(this, "_pendingDrains", {
463
+ enumerable: true,
464
+ configurable: true,
465
+ writable: true,
466
+ value: new Set()
467
+ });
379
468
  Object.defineProperty(this, "langSmithToOTELTranslator", {
380
469
  enumerable: true,
381
470
  configurable: true,
382
471
  writable: true,
383
472
  value: void 0
384
473
  });
474
+ Object.defineProperty(this, "_tracingMode", {
475
+ enumerable: true,
476
+ configurable: true,
477
+ writable: true,
478
+ value: "langsmith"
479
+ });
385
480
  Object.defineProperty(this, "fetchImplementation", {
386
481
  enumerable: true,
387
482
  configurable: true,
@@ -491,6 +586,7 @@ export class Client {
491
586
  config.hideInputs ?? config.anonymizer ?? defaultConfig.hideInputs;
492
587
  this.hideOutputs =
493
588
  config.hideOutputs ?? config.anonymizer ?? defaultConfig.hideOutputs;
589
+ this.hideMetadata = config.hideMetadata ?? defaultConfig.hideMetadata;
494
590
  this.omitTracedRuntimeInfo = config.omitTracedRuntimeInfo ?? false;
495
591
  this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
496
592
  this.autoBatchQueue = new AutoBatchQueue(maxMemory);
@@ -500,7 +596,8 @@ export class Client {
500
596
  this.batchSizeLimit = config.batchSizeLimit;
501
597
  this.fetchOptions = config.fetchOptions || {};
502
598
  this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
503
- if (getOtelEnabled()) {
599
+ this._tracingMode = resolveTracingMode(config.tracingMode);
600
+ if (this._tracingMode === "otel") {
504
601
  this.langSmithToOTELTranslator = new LangSmithToOTELTranslator();
505
602
  }
506
603
  // Cache metadata env vars once during construction to avoid repeatedly scanning process.env
@@ -540,12 +637,14 @@ export class Client {
540
637
  const apiUrl = getLangSmithEnvironmentVariable("ENDPOINT") ?? DEFAULT_API_URL;
541
638
  const hideInputs = getLangSmithEnvironmentVariable("HIDE_INPUTS") === "true";
542
639
  const hideOutputs = getLangSmithEnvironmentVariable("HIDE_OUTPUTS") === "true";
640
+ const hideMetadata = getLangSmithEnvironmentVariable("HIDE_METADATA") === "true";
543
641
  return {
544
642
  apiUrl: apiUrl,
545
643
  apiKey: apiKey,
546
644
  webUrl: undefined,
547
645
  hideInputs: hideInputs,
548
646
  hideOutputs: hideOutputs,
647
+ hideMetadata: hideMetadata,
549
648
  };
550
649
  }
551
650
  getHostUrl() {
@@ -577,6 +676,10 @@ export class Client {
577
676
  this.webUrl = "https://aws.smith.langchain.com";
578
677
  return this.webUrl;
579
678
  }
679
+ else if (this.apiUrl.split(".", 1)[0].includes("apac")) {
680
+ this.webUrl = "https://apac.smith.langchain.com";
681
+ return this.webUrl;
682
+ }
580
683
  else if (this.apiUrl.split(".", 1)[0].includes("beta")) {
581
684
  this.webUrl = "https://beta.smith.langchain.com";
582
685
  return this.webUrl;
@@ -641,6 +744,18 @@ export class Client {
641
744
  }
642
745
  return outputs;
643
746
  }
747
+ async processMetadata(metadata) {
748
+ if (this.hideMetadata === false) {
749
+ return metadata;
750
+ }
751
+ if (this.hideMetadata === true) {
752
+ return {};
753
+ }
754
+ if (typeof this.hideMetadata === "function") {
755
+ return this.hideMetadata(metadata);
756
+ }
757
+ return metadata;
758
+ }
644
759
  /**
645
760
  * Filter content from new_token events to prevent streaming LLM output
646
761
  * from being uploaded via events.
@@ -667,6 +782,12 @@ export class Client {
667
782
  if (runParams.outputs !== undefined) {
668
783
  runParams.outputs = await this.processOutputs(runParams.outputs);
669
784
  }
785
+ if (runParams.extra != null && "metadata" in runParams.extra) {
786
+ runParams.extra = {
787
+ ...runParams.extra,
788
+ metadata: await this.processMetadata(runParams.extra.metadata),
789
+ };
790
+ }
670
791
  if (runParams.events !== undefined) {
671
792
  runParams.events = this._filterNewTokenEvents(runParams.events);
672
793
  }
@@ -1019,18 +1140,18 @@ export class Client {
1019
1140
  const sizeLimit = await this._getBatchSizeLimit();
1020
1141
  if (this.autoBatchQueue.sizeBytes > sizeLimitBytes ||
1021
1142
  this.autoBatchQueue.items.length > sizeLimit) {
1022
- void this.drainAutoBatchQueue({
1143
+ this._trackDrain(this.drainAutoBatchQueue({
1023
1144
  batchSizeLimitBytes: sizeLimitBytes,
1024
1145
  batchSizeLimit: sizeLimit,
1025
- });
1146
+ }));
1026
1147
  }
1027
1148
  if (this.autoBatchQueue.items.length > 0) {
1028
1149
  this.autoBatchTimeout = setTimeout(() => {
1029
1150
  this.autoBatchTimeout = undefined;
1030
- void this.drainAutoBatchQueue({
1151
+ this._trackDrain(this.drainAutoBatchQueue({
1031
1152
  batchSizeLimitBytes: sizeLimitBytes,
1032
1153
  batchSizeLimit: sizeLimit,
1033
- });
1154
+ }));
1034
1155
  }, this.autoBatchAggregationDelayMs);
1035
1156
  }
1036
1157
  return itemPromise;
@@ -1039,7 +1160,7 @@ export class Client {
1039
1160
  const response = await this.caller.call(async () => {
1040
1161
  const res = await this._fetch(`${this.apiUrl}/info`, {
1041
1162
  method: "GET",
1042
- headers: { Accept: "application/json" },
1163
+ headers: { ...this._mergedHeaders, Accept: "application/json" },
1043
1164
  signal: AbortSignal.timeout(SERVER_INFO_REQUEST_TIMEOUT_MS),
1044
1165
  ...this.fetchOptions,
1045
1166
  });
@@ -1210,7 +1331,7 @@ export class Client {
1210
1331
  .map((item) => item.id)
1211
1332
  .concat(batchChunks.patch.map((item) => item.id))
1212
1333
  .join(",");
1213
- await this._postBatchIngestRuns(serializePayloadForTracing(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1334
+ await this._postBatchIngestRuns(await this._serializeBody(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1214
1335
  }
1215
1336
  }
1216
1337
  async _postBatchIngestRuns(body, options) {
@@ -1311,7 +1432,7 @@ export class Client {
1311
1432
  const { inputs, outputs, events, extra, error, serialized, attachments, ...payload } = originalPayload;
1312
1433
  const fields = { inputs, outputs, events, extra, error, serialized };
1313
1434
  // encode the main run payload
1314
- const stringifiedPayload = serializePayloadForTracing(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1435
+ const stringifiedPayload = await this._serializeBody(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1315
1436
  accumulatedParts.push({
1316
1437
  name: `${method}.${payload.id}`,
1317
1438
  payload: new Blob([stringifiedPayload], {
@@ -1323,7 +1444,7 @@ export class Client {
1323
1444
  if (value === undefined) {
1324
1445
  continue;
1325
1446
  }
1326
- const stringifiedValue = serializePayloadForTracing(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1447
+ const stringifiedValue = await this._serializeBody(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1327
1448
  accumulatedParts.push({
1328
1449
  name: `${method}.${payload.id}.${key}`,
1329
1450
  payload: new Blob([stringifiedValue], {
@@ -1514,6 +1635,12 @@ export class Client {
1514
1635
  if (run.outputs) {
1515
1636
  run.outputs = await this.processOutputs(run.outputs);
1516
1637
  }
1638
+ if (run.extra != null && "metadata" in run.extra) {
1639
+ run.extra = {
1640
+ ...run.extra,
1641
+ metadata: await this.processMetadata(run.extra.metadata),
1642
+ };
1643
+ }
1517
1644
  if (run.events) {
1518
1645
  run.events = this._filterNewTokenEvents(run.events);
1519
1646
  }
@@ -2329,7 +2456,7 @@ export class Client {
2329
2456
  // projectId querying
2330
2457
  return true;
2331
2458
  }
2332
- catch (e) {
2459
+ catch (_e) {
2333
2460
  return false;
2334
2461
  }
2335
2462
  }
@@ -3750,7 +3877,7 @@ export class Client {
3750
3877
  return json.commits[0].commit_hash;
3751
3878
  }
3752
3879
  async _likeOrUnlikePrompt(promptIdentifier, like) {
3753
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
3880
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
3754
3881
  const body = JSON.stringify({ like: like });
3755
3882
  const response = await this.caller.call(async () => {
3756
3883
  const res = await this._fetch(`${this.apiUrl}/likes/${owner}/${promptName}`, {
@@ -3769,7 +3896,7 @@ export class Client {
3769
3896
  return response.json();
3770
3897
  }
3771
3898
  async _getPromptUrl(promptIdentifier) {
3772
- const [owner, promptName, commitHash] = parsePromptIdentifier(promptIdentifier);
3899
+ const [owner, promptName, commitHash] = parseHubIdentifier(promptIdentifier);
3773
3900
  if (!(await this._currentTenantIsOwner(owner))) {
3774
3901
  if (commitHash !== "latest") {
3775
3902
  return `${this.getHostUrl()}/hub/${owner}/${promptName}/${commitHash.substring(0, 8)}`;
@@ -3861,7 +3988,7 @@ export class Client {
3861
3988
  * ```
3862
3989
  */
3863
3990
  async *listCommits(promptIdentifier) {
3864
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
3991
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
3865
3992
  for await (const commits of this._getPaginated(`/commits/${owner}/${promptName}/`, new URLSearchParams(), (res) => res.commits)) {
3866
3993
  yield* commits;
3867
3994
  }
@@ -3924,7 +4051,7 @@ export class Client {
3924
4051
  * ```
3925
4052
  */
3926
4053
  async getPrompt(promptIdentifier) {
3927
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
4054
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
3928
4055
  const response = await this.caller.call(async () => {
3929
4056
  const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${promptName}`, {
3930
4057
  method: "GET",
@@ -3981,7 +4108,7 @@ export class Client {
3981
4108
  You can add a handle by creating a public prompt at:\n
3982
4109
  https://smith.langchain.com/prompts`);
3983
4110
  }
3984
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
4111
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
3985
4112
  if (!(await this._currentTenantIsOwner(owner))) {
3986
4113
  throw await this._ownerConflictError("create a prompt", owner);
3987
4114
  }
@@ -4040,7 +4167,7 @@ export class Client {
4040
4167
  if (!(await this.promptExists(promptIdentifier))) {
4041
4168
  throw new Error("Prompt does not exist, you must create it first.");
4042
4169
  }
4043
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
4170
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
4044
4171
  const resolvedParentCommitHash = options?.parentCommitHash === "latest" || !options?.parentCommitHash
4045
4172
  ? await this._getLatestCommitHash(`${owner}/${promptName}`)
4046
4173
  : options?.parentCommitHash;
@@ -4238,7 +4365,7 @@ export class Client {
4238
4365
  if (!(await this.promptExists(promptIdentifier))) {
4239
4366
  throw new Error("Prompt does not exist, you must create it first.");
4240
4367
  }
4241
- const [owner, promptName] = parsePromptIdentifier(promptIdentifier);
4368
+ const [owner, promptName] = parseHubIdentifier(promptIdentifier);
4242
4369
  if (!(await this._currentTenantIsOwner(owner))) {
4243
4370
  throw await this._ownerConflictError("update a prompt", owner);
4244
4371
  }
@@ -4278,7 +4405,7 @@ export class Client {
4278
4405
  if (!(await this.promptExists(promptIdentifier))) {
4279
4406
  throw new Error("Prompt does not exist, you must create it first.");
4280
4407
  }
4281
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
4408
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
4282
4409
  if (!(await this._currentTenantIsOwner(owner))) {
4283
4410
  throw await this._ownerConflictError("delete a prompt", owner);
4284
4411
  }
@@ -4306,7 +4433,7 @@ export class Client {
4306
4433
  * Fetch a prompt commit directly from the API (bypassing cache).
4307
4434
  */
4308
4435
  async _fetchPromptFromApi(promptIdentifier, options) {
4309
- const [owner, promptName, commitHash] = parsePromptIdentifier(promptIdentifier);
4436
+ const [owner, promptName, commitHash] = parseHubIdentifier(promptIdentifier);
4310
4437
  const response = await this.caller.call(async () => {
4311
4438
  const res = await this._fetch(`${this.apiUrl}/commits/${owner}/${promptName}/${commitHash}${options?.includeModel ? "?include_model=true" : ""}`, {
4312
4439
  method: "GET",
@@ -4324,9 +4451,28 @@ export class Client {
4324
4451
  commit_hash: result.commit_hash,
4325
4452
  manifest: result.manifest,
4326
4453
  examples: result.examples,
4454
+ hub_model_config: result.model_config,
4455
+ hub_model_provider: result.model_provider,
4327
4456
  };
4328
4457
  }
4458
+ /**
4459
+ * Pull a prompt commit from the LangSmith API.
4460
+ *
4461
+ * Public prompts referenced by owner/name cross a trust boundary because the
4462
+ * prompt manifest may contain serialized LangChain objects and configuration
4463
+ * that affect runtime behavior. For example, a prompt can intentionally
4464
+ * configure a model with a custom base URL, headers, model name, or other
4465
+ * constructor arguments. These are supported features, but they also mean the
4466
+ * prompt contents should be treated as executable configuration rather than
4467
+ * plain text.
4468
+ *
4469
+ * Set `dangerouslyPullPublicPrompt: true` only after reviewing and trusting
4470
+ * the prompt contents, not merely the publishing account. Prompts from your
4471
+ * own or your organization's account can still be unsafe if that account or
4472
+ * prompt was compromised.
4473
+ */
4329
4474
  async pullPromptCommit(promptIdentifier, options) {
4475
+ assertPullPublicPromptAllowed(promptIdentifier, options?.dangerouslyPullPublicPrompt);
4330
4476
  // Check cache first if not skipped
4331
4477
  const refreshFunc = this._fetchPromptFromApi.bind(this, promptIdentifier, options);
4332
4478
  if (!options?.skipCache && this._promptCache) {
@@ -4346,12 +4492,26 @@ export class Client {
4346
4492
  /**
4347
4493
  * This method should not be used directly, use `import { pull } from "langchain/hub"` instead.
4348
4494
  * Using this method directly returns the JSON string of the prompt rather than a LangChain object.
4495
+ *
4496
+ * Public prompts referenced by owner/name cross a trust boundary because the
4497
+ * prompt manifest may contain serialized LangChain objects and configuration
4498
+ * that affect runtime behavior. For example, a prompt can intentionally
4499
+ * configure a model with a custom base URL, headers, model name, or other
4500
+ * constructor arguments. These are supported features, but they also mean the
4501
+ * prompt contents should be treated as executable configuration rather than
4502
+ * plain text.
4503
+ *
4504
+ * Set `dangerouslyPullPublicPrompt: true` only after reviewing and trusting
4505
+ * the prompt contents, not merely the publishing account. Prompts from your
4506
+ * own or your organization's account can still be unsafe if that account or
4507
+ * prompt was compromised.
4349
4508
  * @private
4350
4509
  */
4351
4510
  async _pullPrompt(promptIdentifier, options) {
4352
4511
  const promptObject = await this.pullPromptCommit(promptIdentifier, {
4353
4512
  includeModel: options?.includeModel,
4354
4513
  skipCache: options?.skipCache,
4514
+ dangerouslyPullPublicPrompt: options?.dangerouslyPullPublicPrompt,
4355
4515
  });
4356
4516
  const prompt = JSON.stringify(promptObject.manifest);
4357
4517
  return prompt;
@@ -4386,6 +4546,251 @@ export class Client {
4386
4546
  });
4387
4547
  return url;
4388
4548
  }
4549
+ /**
4550
+ * Check if an agent repo exists.
4551
+ */
4552
+ async agentExists(identifier) {
4553
+ const [owner, name] = parseHubIdentifier(identifier);
4554
+ return this._repoExists(owner, name);
4555
+ }
4556
+ /**
4557
+ * Check if a skill repo exists.
4558
+ */
4559
+ async skillExists(identifier) {
4560
+ const [owner, name] = parseHubIdentifier(identifier);
4561
+ return this._repoExists(owner, name);
4562
+ }
4563
+ /**
4564
+ * Pull an agent directory from Hub.
4565
+ * @param identifier The identifier (owner/name[:version]).
4566
+ * @param options.version Commit hash or tag; overrides identifier's version.
4567
+ */
4568
+ async pullAgent(identifier, options) {
4569
+ return (await this._pullDirectory(identifier, "agent", options?.version));
4570
+ }
4571
+ /**
4572
+ * Pull a skill directory from Hub.
4573
+ */
4574
+ async pullSkill(identifier, options) {
4575
+ return (await this._pullDirectory(identifier, "skill", options?.version));
4576
+ }
4577
+ /**
4578
+ * Push an agent to Hub. Creates the repo if missing, patches metadata if
4579
+ * provided, then commits the given files.
4580
+ * @returns The URL of the resulting commit.
4581
+ */
4582
+ async pushAgent(identifier, options) {
4583
+ return this._pushDirectory(identifier, "agent", options);
4584
+ }
4585
+ /**
4586
+ * Push a skill to Hub.
4587
+ */
4588
+ async pushSkill(identifier, options) {
4589
+ return this._pushDirectory(identifier, "skill", options);
4590
+ }
4591
+ /**
4592
+ * Delete an agent and all its owned child file repos.
4593
+ */
4594
+ async deleteAgent(identifier) {
4595
+ return this._deleteDirectory(identifier);
4596
+ }
4597
+ /**
4598
+ * Delete a skill and all its owned child file repos.
4599
+ */
4600
+ async deleteSkill(identifier) {
4601
+ return this._deleteDirectory(identifier);
4602
+ }
4603
+ /**
4604
+ * List agent repos. Yields one at a time, auto-paginating.
4605
+ */
4606
+ async *listAgents(options) {
4607
+ yield* this._listReposByType("agent", options);
4608
+ }
4609
+ /**
4610
+ * List skill repos. Yields one at a time, auto-paginating.
4611
+ */
4612
+ async *listSkills(options) {
4613
+ yield* this._listReposByType("skill", options);
4614
+ }
4615
+ async *_listReposByType(repoType, options) {
4616
+ const params = new URLSearchParams();
4617
+ params.append("repo_type", repoType);
4618
+ params.append("is_archived", (!!options?.isArchived).toString());
4619
+ if (options?.isPublic !== undefined) {
4620
+ params.append("is_public", options.isPublic.toString());
4621
+ }
4622
+ if (options?.query) {
4623
+ params.append("query", options.query);
4624
+ }
4625
+ for await (const repos of this._getPaginated("/repos", params, (res) => res.repos)) {
4626
+ yield* repos;
4627
+ }
4628
+ }
4629
+ async _pullDirectory(identifier, repoType, version) {
4630
+ const [owner, name, parsedVersion] = parseHubIdentifier(identifier);
4631
+ const resolvedVersion = version ?? (parsedVersion !== "latest" ? parsedVersion : undefined);
4632
+ const url = new URL(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories`);
4633
+ url.searchParams.set("repo_type", repoType);
4634
+ if (resolvedVersion) {
4635
+ url.searchParams.set("commit", resolvedVersion);
4636
+ }
4637
+ const response = await this.caller.call(async () => {
4638
+ const res = await this._fetch(url.toString(), {
4639
+ method: "GET",
4640
+ headers: this._mergedHeaders,
4641
+ signal: AbortSignal.timeout(this.timeout_ms),
4642
+ ...this.fetchOptions,
4643
+ });
4644
+ await raiseForStatus(res, "pull directory");
4645
+ return res;
4646
+ });
4647
+ return (await response.json());
4648
+ }
4649
+ async _pushDirectory(identifier, repoType, options) {
4650
+ if (options.parentCommit !== undefined &&
4651
+ (options.parentCommit.length < 8 || options.parentCommit.length > 64)) {
4652
+ throw new Error("parent_commit must be 8-64 characters");
4653
+ }
4654
+ const [owner, name] = parseHubIdentifier(identifier);
4655
+ if (!(await this._currentTenantIsOwner(owner))) {
4656
+ throw await this._ownerConflictError(`push ${repoType}`, owner);
4657
+ }
4658
+ if (await this._repoExists(owner, name)) {
4659
+ if (options.description !== undefined ||
4660
+ options.readme !== undefined ||
4661
+ options.tags !== undefined ||
4662
+ options.isPublic !== undefined) {
4663
+ await this._updateRepoMetadata(owner, name, options);
4664
+ }
4665
+ }
4666
+ else {
4667
+ const REPO_HANDLE_PATTERN = /^[a-z][a-z0-9-_]*$/;
4668
+ if (!REPO_HANDLE_PATTERN.test(name)) {
4669
+ throw new Error(`Invalid repo_handle ${JSON.stringify(name)}: must match ${REPO_HANDLE_PATTERN}`);
4670
+ }
4671
+ await this._createRepo(name, repoType, options);
4672
+ }
4673
+ const body = { files: options.files };
4674
+ if (options.parentCommit) {
4675
+ body.parent_commit = options.parentCommit;
4676
+ }
4677
+ const response = await this.caller.call(async () => {
4678
+ const res = await this._fetch(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories/commits`, {
4679
+ method: "POST",
4680
+ headers: {
4681
+ ...this._mergedHeaders,
4682
+ "Content-Type": "application/json",
4683
+ },
4684
+ signal: AbortSignal.timeout(this.timeout_ms),
4685
+ ...this.fetchOptions,
4686
+ body: JSON.stringify(body),
4687
+ });
4688
+ await raiseForStatus(res, `push ${repoType}`);
4689
+ return res;
4690
+ });
4691
+ const data = (await response.json());
4692
+ const commitHash = data.commit.commit_hash;
4693
+ return `${this.getHostUrl()}/hub/${owner}/${name}:${commitHash.slice(0, 8)}`;
4694
+ }
4695
+ async _deleteDirectory(identifier) {
4696
+ const [owner, name] = parseHubIdentifier(identifier);
4697
+ if (!(await this._currentTenantIsOwner(owner))) {
4698
+ throw await this._ownerConflictError("delete", owner);
4699
+ }
4700
+ await this.caller.call(async () => {
4701
+ const res = await this._fetch(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories`, {
4702
+ method: "DELETE",
4703
+ headers: this._mergedHeaders,
4704
+ signal: AbortSignal.timeout(this.timeout_ms),
4705
+ ...this.fetchOptions,
4706
+ });
4707
+ await raiseForStatus(res, "delete directory");
4708
+ return res;
4709
+ });
4710
+ }
4711
+ async _repoExists(owner, name) {
4712
+ try {
4713
+ await this.caller.call(async () => {
4714
+ const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${name}`, {
4715
+ method: "GET",
4716
+ headers: this._mergedHeaders,
4717
+ signal: AbortSignal.timeout(this.timeout_ms),
4718
+ ...this.fetchOptions,
4719
+ });
4720
+ await raiseForStatus(res, "check repo exists");
4721
+ return res;
4722
+ });
4723
+ return true;
4724
+ }
4725
+ catch (e) {
4726
+ if (isLangSmithNotFoundError(e)) {
4727
+ return false;
4728
+ }
4729
+ throw e;
4730
+ }
4731
+ }
4732
+ async _createRepo(name, repoType, options) {
4733
+ const body = {
4734
+ repo_handle: name,
4735
+ repo_type: repoType,
4736
+ is_public: !!options.isPublic,
4737
+ };
4738
+ if (options.description !== undefined)
4739
+ body.description = options.description;
4740
+ if (options.readme !== undefined)
4741
+ body.readme = options.readme;
4742
+ if (options.tags !== undefined)
4743
+ body.tags = options.tags;
4744
+ try {
4745
+ await this.caller.call(async () => {
4746
+ const res = await this._fetch(`${this.apiUrl}/repos/`, {
4747
+ method: "POST",
4748
+ headers: {
4749
+ ...this._mergedHeaders,
4750
+ "Content-Type": "application/json",
4751
+ },
4752
+ signal: AbortSignal.timeout(this.timeout_ms),
4753
+ ...this.fetchOptions,
4754
+ body: JSON.stringify(body),
4755
+ });
4756
+ await raiseForStatus(res, `create ${repoType}`);
4757
+ return res;
4758
+ });
4759
+ }
4760
+ catch (e) {
4761
+ if (isLangSmithConflictError(e)) {
4762
+ return;
4763
+ }
4764
+ throw e;
4765
+ }
4766
+ }
4767
+ async _updateRepoMetadata(owner, name, options) {
4768
+ const body = {};
4769
+ if (options.description !== undefined)
4770
+ body.description = options.description;
4771
+ if (options.readme !== undefined)
4772
+ body.readme = options.readme;
4773
+ if (options.tags !== undefined)
4774
+ body.tags = options.tags;
4775
+ if (options.isPublic !== undefined)
4776
+ body.is_public = options.isPublic;
4777
+ if (Object.keys(body).length === 0)
4778
+ return;
4779
+ await this.caller.call(async () => {
4780
+ const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${name}`, {
4781
+ method: "PATCH",
4782
+ headers: {
4783
+ ...this._mergedHeaders,
4784
+ "Content-Type": "application/json",
4785
+ },
4786
+ signal: AbortSignal.timeout(this.timeout_ms),
4787
+ ...this.fetchOptions,
4788
+ body: JSON.stringify(body),
4789
+ });
4790
+ await raiseForStatus(res, "update repo metadata");
4791
+ return res;
4792
+ });
4793
+ }
4389
4794
  /**
4390
4795
  * Clone a public dataset to your own langsmith tenant.
4391
4796
  * This operation is idempotent. If you already have a dataset with the given name,
@@ -4463,7 +4868,7 @@ export class Client {
4463
4868
  throw new Error(`Invalid public ${kind} URL: ${urlOrToken}`);
4464
4869
  }
4465
4870
  }
4466
- catch (error) {
4871
+ catch (_error) {
4467
4872
  throw new Error(`Invalid public ${kind} URL or token: ${urlOrToken}`);
4468
4873
  }
4469
4874
  }
@@ -4517,6 +4922,12 @@ export class Client {
4517
4922
  * ```
4518
4923
  */
4519
4924
  await new Promise((resolve) => setTimeout(resolve, 1));
4925
+ // Wait for any in-flight drains (whose serialize work may still be
4926
+ // in-progress on a worker thread and not yet registered with the
4927
+ // batchIngestCaller queue) before checking queue idleness.
4928
+ while (this._pendingDrains.size > 0) {
4929
+ await Promise.all([...this._pendingDrains]);
4930
+ }
4520
4931
  await Promise.all([
4521
4932
  ...this.autoBatchQueue.items.map(({ itemPromise }) => itemPromise),
4522
4933
  this.batchIngestCaller.queue.onIdle(),
@@ -4525,6 +4936,33 @@ export class Client {
4525
4936
  await getDefaultOTLPTracerComponents()?.DEFAULT_LANGSMITH_SPAN_PROCESSOR?.forceFlush();
4526
4937
  }
4527
4938
  }
4939
+ /**
4940
+ * Returns a string representation of the Client instance.
4941
+ * This method is called when the object is converted to a string
4942
+ * or logged, ensuring sensitive information like API keys is not exposed.
4943
+ *
4944
+ * @returns A string representation of the Client.
4945
+ */
4946
+ toString() {
4947
+ const params = [`apiUrl=${JSON.stringify(this.apiUrl)}`];
4948
+ if (this.webUrl !== undefined) {
4949
+ params.push(`webUrl=${JSON.stringify(this.webUrl)}`);
4950
+ }
4951
+ if (this.workspaceId !== undefined) {
4952
+ params.push(`workspaceId=${JSON.stringify(this.workspaceId)}`);
4953
+ }
4954
+ return `[LangSmithClient ${params.join(" ")}]`;
4955
+ }
4956
+ /**
4957
+ * Custom inspect method for Node.js.
4958
+ * This method is called when the object is inspected in the Node.js REPL
4959
+ * or with console.log, ensuring sensitive information like API keys is not exposed.
4960
+ *
4961
+ * @returns A string representation of the Client for inspection.
4962
+ */
4963
+ [Symbol.for("nodejs.util.inspect.custom")]() {
4964
+ return this.toString();
4965
+ }
4528
4966
  }
4529
4967
  Object.defineProperty(Client, "_fallbackDirsCreated", {
4530
4968
  enumerable: true,