langsmith 0.5.22 → 0.5.24

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 (95) hide show
  1. package/dist/client.cjs +365 -19
  2. package/dist/client.d.ts +116 -1
  3. package/dist/client.js +369 -23
  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/index.cjs +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/run_trees.cjs +8 -7
  14. package/dist/run_trees.d.ts +7 -0
  15. package/dist/run_trees.js +7 -6
  16. package/dist/schemas.d.ts +54 -0
  17. package/dist/singletons/otel.cjs +3 -2
  18. package/dist/singletons/otel.js +4 -3
  19. package/dist/traceable.cjs +1 -2
  20. package/dist/traceable.js +1 -2
  21. package/dist/utils/_uuid.cjs +2 -2
  22. package/dist/utils/_uuid.js +1 -1
  23. package/dist/utils/env.cjs +33 -0
  24. package/dist/utils/env.d.ts +9 -0
  25. package/dist/utils/env.js +32 -0
  26. package/dist/utils/error.cjs +7 -0
  27. package/dist/utils/error.d.ts +1 -0
  28. package/dist/utils/error.js +6 -0
  29. package/dist/utils/fast-safe-stringify/index.cjs +203 -0
  30. package/dist/utils/fast-safe-stringify/index.d.ts +46 -0
  31. package/dist/utils/fast-safe-stringify/index.js +202 -0
  32. package/dist/utils/jestlike/index.cjs +5 -5
  33. package/dist/utils/jestlike/index.js +1 -1
  34. package/dist/utils/jestlike/vendor/evaluatedBy.cjs +3 -3
  35. package/dist/utils/jestlike/vendor/evaluatedBy.js +1 -1
  36. package/dist/utils/prompts.cjs +7 -2
  37. package/dist/utils/prompts.d.ts +6 -1
  38. package/dist/utils/prompts.js +6 -1
  39. package/dist/utils/serialize_worker.cjs +389 -0
  40. package/dist/utils/serialize_worker.d.ts +67 -0
  41. package/dist/utils/serialize_worker.js +383 -0
  42. package/dist/utils/uuid/src/index.cjs +24 -0
  43. package/dist/utils/uuid/src/index.d.ts +10 -0
  44. package/dist/utils/uuid/src/index.js +9 -0
  45. package/dist/utils/uuid/src/max.cjs +3 -0
  46. package/dist/utils/uuid/src/max.d.ts +2 -0
  47. package/dist/utils/uuid/src/max.js +1 -0
  48. package/dist/utils/uuid/src/nil.cjs +3 -0
  49. package/dist/utils/uuid/src/nil.d.ts +2 -0
  50. package/dist/utils/uuid/src/nil.js +1 -0
  51. package/dist/utils/uuid/src/parse.cjs +23 -0
  52. package/dist/utils/uuid/src/parse.d.ts +3 -0
  53. package/dist/utils/uuid/src/parse.js +18 -0
  54. package/dist/utils/uuid/src/regex.cjs +3 -0
  55. package/dist/utils/uuid/src/regex.d.ts +2 -0
  56. package/dist/utils/uuid/src/regex.js +1 -0
  57. package/dist/utils/uuid/src/rng.cjs +10 -0
  58. package/dist/utils/uuid/src/rng.d.ts +1 -0
  59. package/dist/utils/uuid/src/rng.js +7 -0
  60. package/dist/utils/uuid/src/sha1.cjs +75 -0
  61. package/dist/utils/uuid/src/sha1.d.ts +2 -0
  62. package/dist/utils/uuid/src/sha1.js +73 -0
  63. package/dist/utils/uuid/src/stringify.cjs +55 -0
  64. package/dist/utils/uuid/src/stringify.d.ts +3 -0
  65. package/dist/utils/uuid/src/stringify.js +49 -0
  66. package/dist/utils/uuid/src/types.cjs +2 -0
  67. package/dist/utils/uuid/src/types.d.ts +22 -0
  68. package/dist/utils/uuid/src/types.js +1 -0
  69. package/dist/utils/uuid/src/v35.cjs +52 -0
  70. package/dist/utils/uuid/src/v35.d.ts +7 -0
  71. package/dist/utils/uuid/src/v35.js +44 -0
  72. package/dist/utils/uuid/src/v4.cjs +40 -0
  73. package/dist/utils/uuid/src/v4.d.ts +4 -0
  74. package/dist/utils/uuid/src/v4.js +35 -0
  75. package/dist/utils/uuid/src/v5.cjs +50 -0
  76. package/dist/utils/uuid/src/v5.d.ts +9 -0
  77. package/dist/utils/uuid/src/v5.js +9 -0
  78. package/dist/utils/uuid/src/v7.cjs +88 -0
  79. package/dist/utils/uuid/src/v7.d.ts +9 -0
  80. package/dist/utils/uuid/src/v7.js +82 -0
  81. package/dist/utils/uuid/src/validate.cjs +10 -0
  82. package/dist/utils/uuid/src/validate.d.ts +2 -0
  83. package/dist/utils/uuid/src/validate.js +5 -0
  84. package/dist/utils/uuid/src/version.cjs +13 -0
  85. package/dist/utils/uuid/src/version.d.ts +2 -0
  86. package/dist/utils/uuid/src/version.js +8 -0
  87. package/dist/utils/worker_threads.browser.cjs +16 -0
  88. package/dist/utils/worker_threads.browser.d.ts +14 -0
  89. package/dist/utils/worker_threads.browser.js +13 -0
  90. package/dist/utils/worker_threads.cjs +16 -0
  91. package/dist/utils/worker_threads.d.ts +13 -0
  92. package/dist/utils/worker_threads.js +13 -0
  93. package/dist/uuid.cjs +2 -2
  94. package/dist/uuid.js +1 -1
  95. package/package.json +7 -5
package/dist/client.js CHANGED
@@ -1,18 +1,19 @@
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";
16
17
  /**
17
18
  * Catches timestamps without a timezone suffix.
18
19
  */
@@ -152,7 +153,19 @@ export class AutoBatchQueue {
152
153
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
153
154
  itemPromiseResolve = resolve;
154
155
  });
155
- const size = serializePayloadForTracing(item.item, `Serializing run with id: ${item.item.id}`).length;
156
+ // By default we compute the exact serialized size here by stringifying
157
+ // the payload. This is expensive: JSON.stringify on large payloads
158
+ // blocks the event loop on the user's hot path.
159
+ //
160
+ // Opting into LANGSMITH_PERF_OPTIMIZATION=true switches to a cheap
161
+ // structural estimate instead. The estimate is only used for soft
162
+ // memory accounting (queue size limit and downstream async caller
163
+ // memory tracking), never for anything correctness-critical -- the
164
+ // real serialization still happens later, off the hot path, when the
165
+ // batch is assembled for sending.
166
+ const size = getLangSmithEnvironmentVariable("PERF_OPTIMIZATION") === "true"
167
+ ? estimateSerializedSize(item.item).size
168
+ : serializePayloadForTracing(item.item, `Serializing run with id: ${item.item.id}`).length;
156
169
  // Check if adding this item would exceed the size limit
157
170
  // Allow the run if the queue is empty (to support large single traces)
158
171
  if (this.sizeBytes + size > this.maxSizeBytes && this.items.length > 0) {
@@ -215,9 +228,67 @@ export class AutoBatchQueue {
215
228
  }
216
229
  }
217
230
  export class Client {
231
+ get tracingMode() {
232
+ return this._tracingMode;
233
+ }
218
234
  get _fetch() {
219
235
  return this.fetchImplementation || _getFetchImplementation(this.debug);
220
236
  }
237
+ /**
238
+ * Serialize a payload for tracing, optionally offloading the work to a
239
+ * Node worker thread when LANGSMITH_PERF_OPTIMIZATION=true and the runtime
240
+ * supports worker_threads.
241
+ *
242
+ * Falls back to synchronous serialization when:
243
+ * - the perf flag is off
244
+ * - manualFlushMode is enabled (serverless: worker boot cost > benefit)
245
+ * - worker_threads is unavailable (non-Node runtimes)
246
+ * - the payload contains values that can't be structured-cloned across
247
+ * threads (functions, non-cloneable class instances, streams, etc.)
248
+ * - the worker throws for any other reason
249
+ *
250
+ * In all fallback cases the returned bytes are identical to the sync path.
251
+ */
252
+ _trackDrain(promise) {
253
+ this._pendingDrains.add(promise);
254
+ promise.finally(() => {
255
+ this._pendingDrains.delete(promise);
256
+ });
257
+ }
258
+ async _serializeBody(payload, errorContext) {
259
+ const perfOptIn = getLangSmithEnvironmentVariable("PERF_OPTIMIZATION") === "true";
260
+ if (!perfOptIn || this.manualFlushMode) {
261
+ return serializePayloadForTracing(payload, errorContext);
262
+ }
263
+ // Shape-aware gate: worker offload pays for itself only when the
264
+ // payload is dominated by one or more large strings (V8 can refcount
265
+ // those across isolates instead of copying). For structure-heavy
266
+ // payloads -- many keys, deep nesting, lots of small strings -- the
267
+ // structuredClone walk plus thread-hop cost exceeds the JSON.stringify
268
+ // cost we would pay inline, so we fall through to sync serialize.
269
+ if (!hasLargeString(payload)) {
270
+ return serializePayloadForTracing(payload, errorContext);
271
+ }
272
+ if (this._serializeWorker === undefined) {
273
+ this._serializeWorker = getSharedSerializeWorker();
274
+ }
275
+ if (this._serializeWorker === null) {
276
+ return serializePayloadForTracing(payload, errorContext);
277
+ }
278
+ try {
279
+ const bytes = await this._serializeWorker.serialize(payload);
280
+ if (bytes === null) {
281
+ // Worker subsystem unavailable; cache the null to skip re-entry.
282
+ this._serializeWorker = null;
283
+ return serializePayloadForTracing(payload, errorContext);
284
+ }
285
+ return bytes;
286
+ }
287
+ catch {
288
+ // DataCloneError, worker crash, etc. Fall back silently.
289
+ return serializePayloadForTracing(payload, errorContext);
290
+ }
291
+ }
221
292
  constructor(config = {}) {
222
293
  Object.defineProperty(this, "apiKey", {
223
294
  enumerable: true,
@@ -382,12 +453,35 @@ export class Client {
382
453
  writable: true,
383
454
  value: false
384
455
  });
456
+ Object.defineProperty(this, "_serializeWorker", {
457
+ enumerable: true,
458
+ configurable: true,
459
+ writable: true,
460
+ value: void 0
461
+ });
462
+ /**
463
+ * Tracks in-flight drainAutoBatchQueue promises so awaitPendingTraceBatches
464
+ * can wait on them even if the flush involves async work (worker-thread
465
+ * serialize) that hasn't yet registered with batchIngestCaller.queue.
466
+ */
467
+ Object.defineProperty(this, "_pendingDrains", {
468
+ enumerable: true,
469
+ configurable: true,
470
+ writable: true,
471
+ value: new Set()
472
+ });
385
473
  Object.defineProperty(this, "langSmithToOTELTranslator", {
386
474
  enumerable: true,
387
475
  configurable: true,
388
476
  writable: true,
389
477
  value: void 0
390
478
  });
479
+ Object.defineProperty(this, "_tracingMode", {
480
+ enumerable: true,
481
+ configurable: true,
482
+ writable: true,
483
+ value: "langsmith"
484
+ });
391
485
  Object.defineProperty(this, "fetchImplementation", {
392
486
  enumerable: true,
393
487
  configurable: true,
@@ -507,7 +601,8 @@ export class Client {
507
601
  this.batchSizeLimit = config.batchSizeLimit;
508
602
  this.fetchOptions = config.fetchOptions || {};
509
603
  this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
510
- if (getOtelEnabled()) {
604
+ this._tracingMode = resolveTracingMode(config.tracingMode);
605
+ if (this._tracingMode === "otel") {
511
606
  this.langSmithToOTELTranslator = new LangSmithToOTELTranslator();
512
607
  }
513
608
  // Cache metadata env vars once during construction to avoid repeatedly scanning process.env
@@ -1046,18 +1141,18 @@ export class Client {
1046
1141
  const sizeLimit = await this._getBatchSizeLimit();
1047
1142
  if (this.autoBatchQueue.sizeBytes > sizeLimitBytes ||
1048
1143
  this.autoBatchQueue.items.length > sizeLimit) {
1049
- void this.drainAutoBatchQueue({
1144
+ this._trackDrain(this.drainAutoBatchQueue({
1050
1145
  batchSizeLimitBytes: sizeLimitBytes,
1051
1146
  batchSizeLimit: sizeLimit,
1052
- });
1147
+ }));
1053
1148
  }
1054
1149
  if (this.autoBatchQueue.items.length > 0) {
1055
1150
  this.autoBatchTimeout = setTimeout(() => {
1056
1151
  this.autoBatchTimeout = undefined;
1057
- void this.drainAutoBatchQueue({
1152
+ this._trackDrain(this.drainAutoBatchQueue({
1058
1153
  batchSizeLimitBytes: sizeLimitBytes,
1059
1154
  batchSizeLimit: sizeLimit,
1060
- });
1155
+ }));
1061
1156
  }, this.autoBatchAggregationDelayMs);
1062
1157
  }
1063
1158
  return itemPromise;
@@ -1237,7 +1332,7 @@ export class Client {
1237
1332
  .map((item) => item.id)
1238
1333
  .concat(batchChunks.patch.map((item) => item.id))
1239
1334
  .join(",");
1240
- await this._postBatchIngestRuns(serializePayloadForTracing(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1335
+ await this._postBatchIngestRuns(await this._serializeBody(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1241
1336
  }
1242
1337
  }
1243
1338
  async _postBatchIngestRuns(body, options) {
@@ -1338,7 +1433,7 @@ export class Client {
1338
1433
  const { inputs, outputs, events, extra, error, serialized, attachments, ...payload } = originalPayload;
1339
1434
  const fields = { inputs, outputs, events, extra, error, serialized };
1340
1435
  // encode the main run payload
1341
- const stringifiedPayload = serializePayloadForTracing(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1436
+ const stringifiedPayload = await this._serializeBody(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1342
1437
  accumulatedParts.push({
1343
1438
  name: `${method}.${payload.id}`,
1344
1439
  payload: new Blob([stringifiedPayload], {
@@ -1350,7 +1445,7 @@ export class Client {
1350
1445
  if (value === undefined) {
1351
1446
  continue;
1352
1447
  }
1353
- const stringifiedValue = serializePayloadForTracing(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1448
+ const stringifiedValue = await this._serializeBody(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1354
1449
  accumulatedParts.push({
1355
1450
  name: `${method}.${payload.id}.${key}`,
1356
1451
  payload: new Blob([stringifiedValue], {
@@ -3783,7 +3878,7 @@ export class Client {
3783
3878
  return json.commits[0].commit_hash;
3784
3879
  }
3785
3880
  async _likeOrUnlikePrompt(promptIdentifier, like) {
3786
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
3881
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
3787
3882
  const body = JSON.stringify({ like: like });
3788
3883
  const response = await this.caller.call(async () => {
3789
3884
  const res = await this._fetch(`${this.apiUrl}/likes/${owner}/${promptName}`, {
@@ -3802,7 +3897,7 @@ export class Client {
3802
3897
  return response.json();
3803
3898
  }
3804
3899
  async _getPromptUrl(promptIdentifier) {
3805
- const [owner, promptName, commitHash] = parsePromptIdentifier(promptIdentifier);
3900
+ const [owner, promptName, commitHash] = parseHubIdentifier(promptIdentifier);
3806
3901
  if (!(await this._currentTenantIsOwner(owner))) {
3807
3902
  if (commitHash !== "latest") {
3808
3903
  return `${this.getHostUrl()}/hub/${owner}/${promptName}/${commitHash.substring(0, 8)}`;
@@ -3894,7 +3989,7 @@ export class Client {
3894
3989
  * ```
3895
3990
  */
3896
3991
  async *listCommits(promptIdentifier) {
3897
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
3992
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
3898
3993
  for await (const commits of this._getPaginated(`/commits/${owner}/${promptName}/`, new URLSearchParams(), (res) => res.commits)) {
3899
3994
  yield* commits;
3900
3995
  }
@@ -3957,7 +4052,7 @@ export class Client {
3957
4052
  * ```
3958
4053
  */
3959
4054
  async getPrompt(promptIdentifier) {
3960
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
4055
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
3961
4056
  const response = await this.caller.call(async () => {
3962
4057
  const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${promptName}`, {
3963
4058
  method: "GET",
@@ -4014,7 +4109,7 @@ export class Client {
4014
4109
  You can add a handle by creating a public prompt at:\n
4015
4110
  https://smith.langchain.com/prompts`);
4016
4111
  }
4017
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
4112
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
4018
4113
  if (!(await this._currentTenantIsOwner(owner))) {
4019
4114
  throw await this._ownerConflictError("create a prompt", owner);
4020
4115
  }
@@ -4073,7 +4168,7 @@ export class Client {
4073
4168
  if (!(await this.promptExists(promptIdentifier))) {
4074
4169
  throw new Error("Prompt does not exist, you must create it first.");
4075
4170
  }
4076
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
4171
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
4077
4172
  const resolvedParentCommitHash = options?.parentCommitHash === "latest" || !options?.parentCommitHash
4078
4173
  ? await this._getLatestCommitHash(`${owner}/${promptName}`)
4079
4174
  : options?.parentCommitHash;
@@ -4271,7 +4366,7 @@ export class Client {
4271
4366
  if (!(await this.promptExists(promptIdentifier))) {
4272
4367
  throw new Error("Prompt does not exist, you must create it first.");
4273
4368
  }
4274
- const [owner, promptName] = parsePromptIdentifier(promptIdentifier);
4369
+ const [owner, promptName] = parseHubIdentifier(promptIdentifier);
4275
4370
  if (!(await this._currentTenantIsOwner(owner))) {
4276
4371
  throw await this._ownerConflictError("update a prompt", owner);
4277
4372
  }
@@ -4311,7 +4406,7 @@ export class Client {
4311
4406
  if (!(await this.promptExists(promptIdentifier))) {
4312
4407
  throw new Error("Prompt does not exist, you must create it first.");
4313
4408
  }
4314
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
4409
+ const [owner, promptName, _] = parseHubIdentifier(promptIdentifier);
4315
4410
  if (!(await this._currentTenantIsOwner(owner))) {
4316
4411
  throw await this._ownerConflictError("delete a prompt", owner);
4317
4412
  }
@@ -4339,7 +4434,7 @@ export class Client {
4339
4434
  * Fetch a prompt commit directly from the API (bypassing cache).
4340
4435
  */
4341
4436
  async _fetchPromptFromApi(promptIdentifier, options) {
4342
- const [owner, promptName, commitHash] = parsePromptIdentifier(promptIdentifier);
4437
+ const [owner, promptName, commitHash] = parseHubIdentifier(promptIdentifier);
4343
4438
  const response = await this.caller.call(async () => {
4344
4439
  const res = await this._fetch(`${this.apiUrl}/commits/${owner}/${promptName}/${commitHash}${options?.includeModel ? "?include_model=true" : ""}`, {
4345
4440
  method: "GET",
@@ -4419,6 +4514,251 @@ export class Client {
4419
4514
  });
4420
4515
  return url;
4421
4516
  }
4517
+ /**
4518
+ * Check if an agent repo exists.
4519
+ */
4520
+ async agentExists(identifier) {
4521
+ const [owner, name] = parseHubIdentifier(identifier);
4522
+ return this._repoExists(owner, name);
4523
+ }
4524
+ /**
4525
+ * Check if a skill repo exists.
4526
+ */
4527
+ async skillExists(identifier) {
4528
+ const [owner, name] = parseHubIdentifier(identifier);
4529
+ return this._repoExists(owner, name);
4530
+ }
4531
+ /**
4532
+ * Pull an agent directory from Hub.
4533
+ * @param identifier The identifier (owner/name[:version]).
4534
+ * @param options.version Commit hash or tag; overrides identifier's version.
4535
+ */
4536
+ async pullAgent(identifier, options) {
4537
+ return (await this._pullDirectory(identifier, "agent", options?.version));
4538
+ }
4539
+ /**
4540
+ * Pull a skill directory from Hub.
4541
+ */
4542
+ async pullSkill(identifier, options) {
4543
+ return (await this._pullDirectory(identifier, "skill", options?.version));
4544
+ }
4545
+ /**
4546
+ * Push an agent to Hub. Creates the repo if missing, patches metadata if
4547
+ * provided, then commits the given files.
4548
+ * @returns The URL of the resulting commit.
4549
+ */
4550
+ async pushAgent(identifier, options) {
4551
+ return this._pushDirectory(identifier, "agent", options);
4552
+ }
4553
+ /**
4554
+ * Push a skill to Hub.
4555
+ */
4556
+ async pushSkill(identifier, options) {
4557
+ return this._pushDirectory(identifier, "skill", options);
4558
+ }
4559
+ /**
4560
+ * Delete an agent and all its owned child file repos.
4561
+ */
4562
+ async deleteAgent(identifier) {
4563
+ return this._deleteDirectory(identifier);
4564
+ }
4565
+ /**
4566
+ * Delete a skill and all its owned child file repos.
4567
+ */
4568
+ async deleteSkill(identifier) {
4569
+ return this._deleteDirectory(identifier);
4570
+ }
4571
+ /**
4572
+ * List agent repos. Yields one at a time, auto-paginating.
4573
+ */
4574
+ async *listAgents(options) {
4575
+ yield* this._listReposByType("agent", options);
4576
+ }
4577
+ /**
4578
+ * List skill repos. Yields one at a time, auto-paginating.
4579
+ */
4580
+ async *listSkills(options) {
4581
+ yield* this._listReposByType("skill", options);
4582
+ }
4583
+ async *_listReposByType(repoType, options) {
4584
+ const params = new URLSearchParams();
4585
+ params.append("repo_type", repoType);
4586
+ params.append("is_archived", (!!options?.isArchived).toString());
4587
+ if (options?.isPublic !== undefined) {
4588
+ params.append("is_public", options.isPublic.toString());
4589
+ }
4590
+ if (options?.query) {
4591
+ params.append("query", options.query);
4592
+ }
4593
+ for await (const repos of this._getPaginated("/repos", params, (res) => res.repos)) {
4594
+ yield* repos;
4595
+ }
4596
+ }
4597
+ async _pullDirectory(identifier, repoType, version) {
4598
+ const [owner, name, parsedVersion] = parseHubIdentifier(identifier);
4599
+ const resolvedVersion = version ?? (parsedVersion !== "latest" ? parsedVersion : undefined);
4600
+ const url = new URL(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories`);
4601
+ url.searchParams.set("repo_type", repoType);
4602
+ if (resolvedVersion) {
4603
+ url.searchParams.set("commit", resolvedVersion);
4604
+ }
4605
+ const response = await this.caller.call(async () => {
4606
+ const res = await this._fetch(url.toString(), {
4607
+ method: "GET",
4608
+ headers: this._mergedHeaders,
4609
+ signal: AbortSignal.timeout(this.timeout_ms),
4610
+ ...this.fetchOptions,
4611
+ });
4612
+ await raiseForStatus(res, "pull directory");
4613
+ return res;
4614
+ });
4615
+ return (await response.json());
4616
+ }
4617
+ async _pushDirectory(identifier, repoType, options) {
4618
+ if (options.parentCommit !== undefined &&
4619
+ (options.parentCommit.length < 8 || options.parentCommit.length > 64)) {
4620
+ throw new Error("parent_commit must be 8-64 characters");
4621
+ }
4622
+ const [owner, name] = parseHubIdentifier(identifier);
4623
+ if (!(await this._currentTenantIsOwner(owner))) {
4624
+ throw await this._ownerConflictError(`push ${repoType}`, owner);
4625
+ }
4626
+ if (await this._repoExists(owner, name)) {
4627
+ if (options.description !== undefined ||
4628
+ options.readme !== undefined ||
4629
+ options.tags !== undefined ||
4630
+ options.isPublic !== undefined) {
4631
+ await this._updateRepoMetadata(owner, name, options);
4632
+ }
4633
+ }
4634
+ else {
4635
+ const REPO_HANDLE_PATTERN = /^[a-z][a-z0-9-_]*$/;
4636
+ if (!REPO_HANDLE_PATTERN.test(name)) {
4637
+ throw new Error(`Invalid repo_handle ${JSON.stringify(name)}: must match ${REPO_HANDLE_PATTERN}`);
4638
+ }
4639
+ await this._createRepo(name, repoType, options);
4640
+ }
4641
+ const body = { files: options.files };
4642
+ if (options.parentCommit) {
4643
+ body.parent_commit = options.parentCommit;
4644
+ }
4645
+ const response = await this.caller.call(async () => {
4646
+ const res = await this._fetch(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories/commits`, {
4647
+ method: "POST",
4648
+ headers: {
4649
+ ...this._mergedHeaders,
4650
+ "Content-Type": "application/json",
4651
+ },
4652
+ signal: AbortSignal.timeout(this.timeout_ms),
4653
+ ...this.fetchOptions,
4654
+ body: JSON.stringify(body),
4655
+ });
4656
+ await raiseForStatus(res, `push ${repoType}`);
4657
+ return res;
4658
+ });
4659
+ const data = (await response.json());
4660
+ const commitHash = data.commit.commit_hash;
4661
+ return `${this.getHostUrl()}/hub/${owner}/${name}:${commitHash.slice(0, 8)}`;
4662
+ }
4663
+ async _deleteDirectory(identifier) {
4664
+ const [owner, name] = parseHubIdentifier(identifier);
4665
+ if (!(await this._currentTenantIsOwner(owner))) {
4666
+ throw await this._ownerConflictError("delete", owner);
4667
+ }
4668
+ await this.caller.call(async () => {
4669
+ const res = await this._fetch(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories`, {
4670
+ method: "DELETE",
4671
+ headers: this._mergedHeaders,
4672
+ signal: AbortSignal.timeout(this.timeout_ms),
4673
+ ...this.fetchOptions,
4674
+ });
4675
+ await raiseForStatus(res, "delete directory");
4676
+ return res;
4677
+ });
4678
+ }
4679
+ async _repoExists(owner, name) {
4680
+ try {
4681
+ await this.caller.call(async () => {
4682
+ const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${name}`, {
4683
+ method: "GET",
4684
+ headers: this._mergedHeaders,
4685
+ signal: AbortSignal.timeout(this.timeout_ms),
4686
+ ...this.fetchOptions,
4687
+ });
4688
+ await raiseForStatus(res, "check repo exists");
4689
+ return res;
4690
+ });
4691
+ return true;
4692
+ }
4693
+ catch (e) {
4694
+ if (isLangSmithNotFoundError(e)) {
4695
+ return false;
4696
+ }
4697
+ throw e;
4698
+ }
4699
+ }
4700
+ async _createRepo(name, repoType, options) {
4701
+ const body = {
4702
+ repo_handle: name,
4703
+ repo_type: repoType,
4704
+ is_public: !!options.isPublic,
4705
+ };
4706
+ if (options.description !== undefined)
4707
+ body.description = options.description;
4708
+ if (options.readme !== undefined)
4709
+ body.readme = options.readme;
4710
+ if (options.tags !== undefined)
4711
+ body.tags = options.tags;
4712
+ try {
4713
+ await this.caller.call(async () => {
4714
+ const res = await this._fetch(`${this.apiUrl}/repos/`, {
4715
+ method: "POST",
4716
+ headers: {
4717
+ ...this._mergedHeaders,
4718
+ "Content-Type": "application/json",
4719
+ },
4720
+ signal: AbortSignal.timeout(this.timeout_ms),
4721
+ ...this.fetchOptions,
4722
+ body: JSON.stringify(body),
4723
+ });
4724
+ await raiseForStatus(res, `create ${repoType}`);
4725
+ return res;
4726
+ });
4727
+ }
4728
+ catch (e) {
4729
+ if (isLangSmithConflictError(e)) {
4730
+ return;
4731
+ }
4732
+ throw e;
4733
+ }
4734
+ }
4735
+ async _updateRepoMetadata(owner, name, options) {
4736
+ const body = {};
4737
+ if (options.description !== undefined)
4738
+ body.description = options.description;
4739
+ if (options.readme !== undefined)
4740
+ body.readme = options.readme;
4741
+ if (options.tags !== undefined)
4742
+ body.tags = options.tags;
4743
+ if (options.isPublic !== undefined)
4744
+ body.is_public = options.isPublic;
4745
+ if (Object.keys(body).length === 0)
4746
+ return;
4747
+ await this.caller.call(async () => {
4748
+ const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${name}`, {
4749
+ method: "PATCH",
4750
+ headers: {
4751
+ ...this._mergedHeaders,
4752
+ "Content-Type": "application/json",
4753
+ },
4754
+ signal: AbortSignal.timeout(this.timeout_ms),
4755
+ ...this.fetchOptions,
4756
+ body: JSON.stringify(body),
4757
+ });
4758
+ await raiseForStatus(res, "update repo metadata");
4759
+ return res;
4760
+ });
4761
+ }
4422
4762
  /**
4423
4763
  * Clone a public dataset to your own langsmith tenant.
4424
4764
  * This operation is idempotent. If you already have a dataset with the given name,
@@ -4550,6 +4890,12 @@ export class Client {
4550
4890
  * ```
4551
4891
  */
4552
4892
  await new Promise((resolve) => setTimeout(resolve, 1));
4893
+ // Wait for any in-flight drains (whose serialize work may still be
4894
+ // in-progress on a worker thread and not yet registered with the
4895
+ // batchIngestCaller queue) before checking queue idleness.
4896
+ while (this._pendingDrains.size > 0) {
4897
+ await Promise.all([...this._pendingDrains]);
4898
+ }
4553
4899
  await Promise.all([
4554
4900
  ...this.autoBatchQueue.items.map(({ itemPromise }) => itemPromise),
4555
4901
  this.batchIngestCaller.queue.onIdle(),
@@ -13,7 +13,7 @@ const env_js_1 = require("../utils/env.cjs");
13
13
  const error_js_1 = require("../utils/error.cjs");
14
14
  const _random_name_js_1 = require("./_random_name.cjs");
15
15
  const evaluator_js_1 = require("./evaluator.cjs");
16
- const uuid_1 = require("uuid");
16
+ const index_js_2 = require("../utils/uuid/src/index.cjs");
17
17
  const evaluate_comparative_js_1 = require("./evaluate_comparative.cjs");
18
18
  const p_queue_js_1 = require("../utils/p-queue.cjs");
19
19
  // Implementation signature
@@ -202,7 +202,7 @@ class _ExperimentManager {
202
202
  this._experimentName = (0, _random_name_js_1.randomName)();
203
203
  }
204
204
  else if (typeof args.experiment === "string") {
205
- this._experimentName = `${args.experiment}-${(0, uuid_1.v4)().slice(0, 8)}`;
205
+ this._experimentName = `${args.experiment}-${(0, index_js_2.v4)().slice(0, 8)}`;
206
206
  }
207
207
  else {
208
208
  if (!args.experiment.name) {
@@ -276,7 +276,7 @@ class _ExperimentManager {
276
276
  catch (e) {
277
277
  // Naming collision
278
278
  if (e?.name === "LangSmithConflictError") {
279
- const ent = (0, uuid_1.v4)().slice(0, 6);
279
+ const ent = (0, index_js_2.v4)().slice(0, 6);
280
280
  this._experimentName = `${originalExperimentName}-${ent}`;
281
281
  }
282
282
  else {
@@ -726,10 +726,7 @@ async function _evaluate(target, fields) {
726
726
  const standardFields = fields;
727
727
  const [experiment_, newRuns] = await _resolveExperiment(fields.experiment ?? null, runs, client);
728
728
  let manager = await new _ExperimentManager({
729
- data: Array.isArray(standardFields.data) ? undefined : standardFields.data,
730
- examples: Array.isArray(standardFields.data)
731
- ? standardFields.data
732
- : undefined,
729
+ data: standardFields.data,
733
730
  client,
734
731
  metadata: fields.metadata,
735
732
  experiment: experiment_ ?? fields.experimentPrefix,
@@ -7,7 +7,7 @@ import { getLangSmithEnvVarsMetadata } from "../utils/env.js";
7
7
  import { printErrorStackTrace } from "../utils/error.js";
8
8
  import { randomName } from "./_random_name.js";
9
9
  import { runEvaluator, } from "./evaluator.js";
10
- import { v4 as uuidv4 } from "uuid";
10
+ import { v4 as uuidv4 } from "../utils/uuid/src/index.js";
11
11
  import { evaluateComparative, } from "./evaluate_comparative.js";
12
12
  import { PQueue } from "../utils/p-queue.js";
13
13
  // Implementation signature
@@ -719,10 +719,7 @@ async function _evaluate(target, fields) {
719
719
  const standardFields = fields;
720
720
  const [experiment_, newRuns] = await _resolveExperiment(fields.experiment ?? null, runs, client);
721
721
  let manager = await new _ExperimentManager({
722
- data: Array.isArray(standardFields.data) ? undefined : standardFields.data,
723
- examples: Array.isArray(standardFields.data)
724
- ? standardFields.data
725
- : undefined,
722
+ data: standardFields.data,
726
723
  client,
727
724
  metadata: fields.metadata,
728
725
  experiment: experiment_ ?? fields.experimentPrefix,
@@ -4,22 +4,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.evaluateComparative = evaluateComparative;
7
- const uuid_1 = require("uuid");
8
- const index_js_1 = require("../index.cjs");
7
+ const index_js_1 = require("../utils/uuid/src/index.cjs");
8
+ const index_js_2 = require("../index.cjs");
9
9
  const shuffle_js_1 = require("../utils/shuffle.cjs");
10
10
  const async_caller_js_1 = require("../utils/async_caller.cjs");
11
- const index_js_2 = __importDefault(require("../utils/p-retry/index.cjs"));
11
+ const index_js_3 = __importDefault(require("../utils/p-retry/index.cjs"));
12
12
  const traceable_js_1 = require("../traceable.cjs");
13
13
  function isExperimentResultsList(value) {
14
14
  return value.some((x) => typeof x !== "string");
15
15
  }
16
16
  async function loadExperiment(client, experiment) {
17
17
  const value = typeof experiment === "string" ? experiment : experiment.experimentName;
18
- return client.readProject((0, uuid_1.validate)(value) ? { projectId: value } : { projectName: value });
18
+ return client.readProject((0, index_js_1.validate)(value) ? { projectId: value } : { projectName: value });
19
19
  }
20
20
  async function loadTraces(client, experiment, options) {
21
21
  const executionOrder = options.loadNested ? undefined : 1;
22
- const runs = await client.listRuns((0, uuid_1.validate)(experiment)
22
+ const runs = await client.listRuns((0, index_js_1.validate)(experiment)
23
23
  ? { projectId: experiment, executionOrder }
24
24
  : { projectName: experiment, executionOrder });
25
25
  const treeMap = {};
@@ -56,7 +56,7 @@ async function evaluateComparative(experiments, options) {
56
56
  if (options.maxConcurrency && options.maxConcurrency < 0) {
57
57
  throw new Error("maxConcurrency must be a positive number.");
58
58
  }
59
- const client = options.client ?? new index_js_1.Client();
59
+ const client = options.client ?? new index_js_2.Client();
60
60
  const resolvedExperiments = await Promise.all(experiments);
61
61
  const projects = await (() => {
62
62
  if (!isExperimentResultsList(resolvedExperiments)) {
@@ -64,7 +64,7 @@ async function evaluateComparative(experiments, options) {
64
64
  }
65
65
  // if we know the number of runs beforehand, check if the
66
66
  // number of runs in the project matches the expected number of runs
67
- return Promise.all(resolvedExperiments.map((experiment) => (0, index_js_2.default)(async () => {
67
+ return Promise.all(resolvedExperiments.map((experiment) => (0, index_js_3.default)(async () => {
68
68
  const project = await loadExperiment(client, experiment);
69
69
  if (project.run_count !== experiment?.results.length) {
70
70
  throw new Error("Experiment is missing runs. Retrying.");
@@ -83,16 +83,16 @@ async function evaluateComparative(experiments, options) {
83
83
  console.warn("Detected multiple dataset versions used by experiments, which may lead to inaccurate results.");
84
84
  }
85
85
  const datasetVersion = projects.at(0)?.extra?.metadata?.dataset_version;
86
- const id = (0, uuid_1.v4)();
86
+ const id = (0, index_js_1.v4)();
87
87
  const experimentName = (() => {
88
88
  if (!options.experimentPrefix) {
89
89
  const names = projects
90
90
  .map((p) => p.name)
91
91
  .filter(Boolean)
92
92
  .join(" vs. ");
93
- return `${names}-${(0, uuid_1.v4)().slice(0, 4)}`;
93
+ return `${names}-${(0, index_js_1.v4)().slice(0, 4)}`;
94
94
  }
95
- return `${options.experimentPrefix}-${(0, uuid_1.v4)().slice(0, 4)}`;
95
+ return `${options.experimentPrefix}-${(0, index_js_1.v4)().slice(0, 4)}`;
96
96
  })();
97
97
  // TODO: add URL to the comparative experiment
98
98
  console.log(`Starting pairwise evaluation of: ${experimentName}`);