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.cjs CHANGED
@@ -35,7 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.Client = exports.AutoBatchQueue = exports.DEFAULT_MAX_SIZE_BYTES = exports.DEFAULT_UNCOMPRESSED_BATCH_SIZE_LIMIT_BYTES = void 0;
37
37
  exports.mergeRuntimeEnvIntoRun = mergeRuntimeEnvIntoRun;
38
- const uuid = __importStar(require("uuid"));
38
+ const uuid = __importStar(require("./utils/uuid/src/index.cjs"));
39
39
  const translator_js_1 = require("./experimental/otel/translator.cjs");
40
40
  const otel_js_1 = require("./singletons/otel.cjs");
41
41
  const async_caller_js_1 = require("./utils/async_caller.cjs");
@@ -50,6 +50,7 @@ const index_js_2 = require("./utils/prompt_cache/index.cjs");
50
50
  const fsUtils = __importStar(require("./utils/fs.cjs"));
51
51
  const fetch_js_1 = require("./singletons/fetch.cjs");
52
52
  const index_js_3 = require("./utils/fast-safe-stringify/index.cjs");
53
+ const serialize_worker_js_1 = require("./utils/serialize_worker.cjs");
53
54
  /**
54
55
  * Catches timestamps without a timezone suffix.
55
56
  */
@@ -189,7 +190,19 @@ class AutoBatchQueue {
189
190
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
190
191
  itemPromiseResolve = resolve;
191
192
  });
192
- const size = (0, index_js_3.serialize)(item.item, `Serializing run with id: ${item.item.id}`).length;
193
+ // By default we compute the exact serialized size here by stringifying
194
+ // the payload. This is expensive: JSON.stringify on large payloads
195
+ // blocks the event loop on the user's hot path.
196
+ //
197
+ // Opting into LANGSMITH_PERF_OPTIMIZATION=true switches to a cheap
198
+ // structural estimate instead. The estimate is only used for soft
199
+ // memory accounting (queue size limit and downstream async caller
200
+ // memory tracking), never for anything correctness-critical -- the
201
+ // real serialization still happens later, off the hot path, when the
202
+ // batch is assembled for sending.
203
+ const size = (0, env_js_1.getLangSmithEnvironmentVariable)("PERF_OPTIMIZATION") === "true"
204
+ ? (0, index_js_3.estimateSerializedSize)(item.item).size
205
+ : (0, index_js_3.serialize)(item.item, `Serializing run with id: ${item.item.id}`).length;
193
206
  // Check if adding this item would exceed the size limit
194
207
  // Allow the run if the queue is empty (to support large single traces)
195
208
  if (this.sizeBytes + size > this.maxSizeBytes && this.items.length > 0) {
@@ -253,9 +266,67 @@ class AutoBatchQueue {
253
266
  }
254
267
  exports.AutoBatchQueue = AutoBatchQueue;
255
268
  class Client {
269
+ get tracingMode() {
270
+ return this._tracingMode;
271
+ }
256
272
  get _fetch() {
257
273
  return this.fetchImplementation || (0, fetch_js_1._getFetchImplementation)(this.debug);
258
274
  }
275
+ /**
276
+ * Serialize a payload for tracing, optionally offloading the work to a
277
+ * Node worker thread when LANGSMITH_PERF_OPTIMIZATION=true and the runtime
278
+ * supports worker_threads.
279
+ *
280
+ * Falls back to synchronous serialization when:
281
+ * - the perf flag is off
282
+ * - manualFlushMode is enabled (serverless: worker boot cost > benefit)
283
+ * - worker_threads is unavailable (non-Node runtimes)
284
+ * - the payload contains values that can't be structured-cloned across
285
+ * threads (functions, non-cloneable class instances, streams, etc.)
286
+ * - the worker throws for any other reason
287
+ *
288
+ * In all fallback cases the returned bytes are identical to the sync path.
289
+ */
290
+ _trackDrain(promise) {
291
+ this._pendingDrains.add(promise);
292
+ promise.finally(() => {
293
+ this._pendingDrains.delete(promise);
294
+ });
295
+ }
296
+ async _serializeBody(payload, errorContext) {
297
+ const perfOptIn = (0, env_js_1.getLangSmithEnvironmentVariable)("PERF_OPTIMIZATION") === "true";
298
+ if (!perfOptIn || this.manualFlushMode) {
299
+ return (0, index_js_3.serialize)(payload, errorContext);
300
+ }
301
+ // Shape-aware gate: worker offload pays for itself only when the
302
+ // payload is dominated by one or more large strings (V8 can refcount
303
+ // those across isolates instead of copying). For structure-heavy
304
+ // payloads -- many keys, deep nesting, lots of small strings -- the
305
+ // structuredClone walk plus thread-hop cost exceeds the JSON.stringify
306
+ // cost we would pay inline, so we fall through to sync serialize.
307
+ if (!(0, serialize_worker_js_1.hasLargeString)(payload)) {
308
+ return (0, index_js_3.serialize)(payload, errorContext);
309
+ }
310
+ if (this._serializeWorker === undefined) {
311
+ this._serializeWorker = (0, serialize_worker_js_1.getSharedSerializeWorker)();
312
+ }
313
+ if (this._serializeWorker === null) {
314
+ return (0, index_js_3.serialize)(payload, errorContext);
315
+ }
316
+ try {
317
+ const bytes = await this._serializeWorker.serialize(payload);
318
+ if (bytes === null) {
319
+ // Worker subsystem unavailable; cache the null to skip re-entry.
320
+ this._serializeWorker = null;
321
+ return (0, index_js_3.serialize)(payload, errorContext);
322
+ }
323
+ return bytes;
324
+ }
325
+ catch {
326
+ // DataCloneError, worker crash, etc. Fall back silently.
327
+ return (0, index_js_3.serialize)(payload, errorContext);
328
+ }
329
+ }
259
330
  constructor(config = {}) {
260
331
  Object.defineProperty(this, "apiKey", {
261
332
  enumerable: true,
@@ -420,12 +491,35 @@ class Client {
420
491
  writable: true,
421
492
  value: false
422
493
  });
494
+ Object.defineProperty(this, "_serializeWorker", {
495
+ enumerable: true,
496
+ configurable: true,
497
+ writable: true,
498
+ value: void 0
499
+ });
500
+ /**
501
+ * Tracks in-flight drainAutoBatchQueue promises so awaitPendingTraceBatches
502
+ * can wait on them even if the flush involves async work (worker-thread
503
+ * serialize) that hasn't yet registered with batchIngestCaller.queue.
504
+ */
505
+ Object.defineProperty(this, "_pendingDrains", {
506
+ enumerable: true,
507
+ configurable: true,
508
+ writable: true,
509
+ value: new Set()
510
+ });
423
511
  Object.defineProperty(this, "langSmithToOTELTranslator", {
424
512
  enumerable: true,
425
513
  configurable: true,
426
514
  writable: true,
427
515
  value: void 0
428
516
  });
517
+ Object.defineProperty(this, "_tracingMode", {
518
+ enumerable: true,
519
+ configurable: true,
520
+ writable: true,
521
+ value: "langsmith"
522
+ });
429
523
  Object.defineProperty(this, "fetchImplementation", {
430
524
  enumerable: true,
431
525
  configurable: true,
@@ -545,7 +639,8 @@ class Client {
545
639
  this.batchSizeLimit = config.batchSizeLimit;
546
640
  this.fetchOptions = config.fetchOptions || {};
547
641
  this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
548
- if ((0, env_js_1.getOtelEnabled)()) {
642
+ this._tracingMode = (0, env_js_1.resolveTracingMode)(config.tracingMode);
643
+ if (this._tracingMode === "otel") {
549
644
  this.langSmithToOTELTranslator = new translator_js_1.LangSmithToOTELTranslator();
550
645
  }
551
646
  // Cache metadata env vars once during construction to avoid repeatedly scanning process.env
@@ -1084,18 +1179,18 @@ class Client {
1084
1179
  const sizeLimit = await this._getBatchSizeLimit();
1085
1180
  if (this.autoBatchQueue.sizeBytes > sizeLimitBytes ||
1086
1181
  this.autoBatchQueue.items.length > sizeLimit) {
1087
- void this.drainAutoBatchQueue({
1182
+ this._trackDrain(this.drainAutoBatchQueue({
1088
1183
  batchSizeLimitBytes: sizeLimitBytes,
1089
1184
  batchSizeLimit: sizeLimit,
1090
- });
1185
+ }));
1091
1186
  }
1092
1187
  if (this.autoBatchQueue.items.length > 0) {
1093
1188
  this.autoBatchTimeout = setTimeout(() => {
1094
1189
  this.autoBatchTimeout = undefined;
1095
- void this.drainAutoBatchQueue({
1190
+ this._trackDrain(this.drainAutoBatchQueue({
1096
1191
  batchSizeLimitBytes: sizeLimitBytes,
1097
1192
  batchSizeLimit: sizeLimit,
1098
- });
1193
+ }));
1099
1194
  }, this.autoBatchAggregationDelayMs);
1100
1195
  }
1101
1196
  return itemPromise;
@@ -1275,7 +1370,7 @@ class Client {
1275
1370
  .map((item) => item.id)
1276
1371
  .concat(batchChunks.patch.map((item) => item.id))
1277
1372
  .join(",");
1278
- await this._postBatchIngestRuns((0, index_js_3.serialize)(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1373
+ await this._postBatchIngestRuns(await this._serializeBody(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1279
1374
  }
1280
1375
  }
1281
1376
  async _postBatchIngestRuns(body, options) {
@@ -1376,7 +1471,7 @@ class Client {
1376
1471
  const { inputs, outputs, events, extra, error, serialized, attachments, ...payload } = originalPayload;
1377
1472
  const fields = { inputs, outputs, events, extra, error, serialized };
1378
1473
  // encode the main run payload
1379
- const stringifiedPayload = (0, index_js_3.serialize)(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1474
+ const stringifiedPayload = await this._serializeBody(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1380
1475
  accumulatedParts.push({
1381
1476
  name: `${method}.${payload.id}`,
1382
1477
  payload: new Blob([stringifiedPayload], {
@@ -1388,7 +1483,7 @@ class Client {
1388
1483
  if (value === undefined) {
1389
1484
  continue;
1390
1485
  }
1391
- const stringifiedValue = (0, index_js_3.serialize)(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1486
+ const stringifiedValue = await this._serializeBody(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1392
1487
  accumulatedParts.push({
1393
1488
  name: `${method}.${payload.id}.${key}`,
1394
1489
  payload: new Blob([stringifiedValue], {
@@ -3821,7 +3916,7 @@ class Client {
3821
3916
  return json.commits[0].commit_hash;
3822
3917
  }
3823
3918
  async _likeOrUnlikePrompt(promptIdentifier, like) {
3824
- const [owner, promptName, _] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
3919
+ const [owner, promptName, _] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
3825
3920
  const body = JSON.stringify({ like: like });
3826
3921
  const response = await this.caller.call(async () => {
3827
3922
  const res = await this._fetch(`${this.apiUrl}/likes/${owner}/${promptName}`, {
@@ -3840,7 +3935,7 @@ class Client {
3840
3935
  return response.json();
3841
3936
  }
3842
3937
  async _getPromptUrl(promptIdentifier) {
3843
- const [owner, promptName, commitHash] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
3938
+ const [owner, promptName, commitHash] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
3844
3939
  if (!(await this._currentTenantIsOwner(owner))) {
3845
3940
  if (commitHash !== "latest") {
3846
3941
  return `${this.getHostUrl()}/hub/${owner}/${promptName}/${commitHash.substring(0, 8)}`;
@@ -3932,7 +4027,7 @@ class Client {
3932
4027
  * ```
3933
4028
  */
3934
4029
  async *listCommits(promptIdentifier) {
3935
- const [owner, promptName, _] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
4030
+ const [owner, promptName, _] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
3936
4031
  for await (const commits of this._getPaginated(`/commits/${owner}/${promptName}/`, new URLSearchParams(), (res) => res.commits)) {
3937
4032
  yield* commits;
3938
4033
  }
@@ -3995,7 +4090,7 @@ class Client {
3995
4090
  * ```
3996
4091
  */
3997
4092
  async getPrompt(promptIdentifier) {
3998
- const [owner, promptName, _] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
4093
+ const [owner, promptName, _] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
3999
4094
  const response = await this.caller.call(async () => {
4000
4095
  const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${promptName}`, {
4001
4096
  method: "GET",
@@ -4052,7 +4147,7 @@ class Client {
4052
4147
  You can add a handle by creating a public prompt at:\n
4053
4148
  https://smith.langchain.com/prompts`);
4054
4149
  }
4055
- const [owner, promptName, _] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
4150
+ const [owner, promptName, _] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
4056
4151
  if (!(await this._currentTenantIsOwner(owner))) {
4057
4152
  throw await this._ownerConflictError("create a prompt", owner);
4058
4153
  }
@@ -4111,7 +4206,7 @@ class Client {
4111
4206
  if (!(await this.promptExists(promptIdentifier))) {
4112
4207
  throw new Error("Prompt does not exist, you must create it first.");
4113
4208
  }
4114
- const [owner, promptName, _] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
4209
+ const [owner, promptName, _] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
4115
4210
  const resolvedParentCommitHash = options?.parentCommitHash === "latest" || !options?.parentCommitHash
4116
4211
  ? await this._getLatestCommitHash(`${owner}/${promptName}`)
4117
4212
  : options?.parentCommitHash;
@@ -4309,7 +4404,7 @@ class Client {
4309
4404
  if (!(await this.promptExists(promptIdentifier))) {
4310
4405
  throw new Error("Prompt does not exist, you must create it first.");
4311
4406
  }
4312
- const [owner, promptName] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
4407
+ const [owner, promptName] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
4313
4408
  if (!(await this._currentTenantIsOwner(owner))) {
4314
4409
  throw await this._ownerConflictError("update a prompt", owner);
4315
4410
  }
@@ -4349,7 +4444,7 @@ class Client {
4349
4444
  if (!(await this.promptExists(promptIdentifier))) {
4350
4445
  throw new Error("Prompt does not exist, you must create it first.");
4351
4446
  }
4352
- const [owner, promptName, _] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
4447
+ const [owner, promptName, _] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
4353
4448
  if (!(await this._currentTenantIsOwner(owner))) {
4354
4449
  throw await this._ownerConflictError("delete a prompt", owner);
4355
4450
  }
@@ -4377,7 +4472,7 @@ class Client {
4377
4472
  * Fetch a prompt commit directly from the API (bypassing cache).
4378
4473
  */
4379
4474
  async _fetchPromptFromApi(promptIdentifier, options) {
4380
- const [owner, promptName, commitHash] = (0, prompts_js_1.parsePromptIdentifier)(promptIdentifier);
4475
+ const [owner, promptName, commitHash] = (0, prompts_js_1.parseHubIdentifier)(promptIdentifier);
4381
4476
  const response = await this.caller.call(async () => {
4382
4477
  const res = await this._fetch(`${this.apiUrl}/commits/${owner}/${promptName}/${commitHash}${options?.includeModel ? "?include_model=true" : ""}`, {
4383
4478
  method: "GET",
@@ -4457,6 +4552,251 @@ class Client {
4457
4552
  });
4458
4553
  return url;
4459
4554
  }
4555
+ /**
4556
+ * Check if an agent repo exists.
4557
+ */
4558
+ async agentExists(identifier) {
4559
+ const [owner, name] = (0, prompts_js_1.parseHubIdentifier)(identifier);
4560
+ return this._repoExists(owner, name);
4561
+ }
4562
+ /**
4563
+ * Check if a skill repo exists.
4564
+ */
4565
+ async skillExists(identifier) {
4566
+ const [owner, name] = (0, prompts_js_1.parseHubIdentifier)(identifier);
4567
+ return this._repoExists(owner, name);
4568
+ }
4569
+ /**
4570
+ * Pull an agent directory from Hub.
4571
+ * @param identifier The identifier (owner/name[:version]).
4572
+ * @param options.version Commit hash or tag; overrides identifier's version.
4573
+ */
4574
+ async pullAgent(identifier, options) {
4575
+ return (await this._pullDirectory(identifier, "agent", options?.version));
4576
+ }
4577
+ /**
4578
+ * Pull a skill directory from Hub.
4579
+ */
4580
+ async pullSkill(identifier, options) {
4581
+ return (await this._pullDirectory(identifier, "skill", options?.version));
4582
+ }
4583
+ /**
4584
+ * Push an agent to Hub. Creates the repo if missing, patches metadata if
4585
+ * provided, then commits the given files.
4586
+ * @returns The URL of the resulting commit.
4587
+ */
4588
+ async pushAgent(identifier, options) {
4589
+ return this._pushDirectory(identifier, "agent", options);
4590
+ }
4591
+ /**
4592
+ * Push a skill to Hub.
4593
+ */
4594
+ async pushSkill(identifier, options) {
4595
+ return this._pushDirectory(identifier, "skill", options);
4596
+ }
4597
+ /**
4598
+ * Delete an agent and all its owned child file repos.
4599
+ */
4600
+ async deleteAgent(identifier) {
4601
+ return this._deleteDirectory(identifier);
4602
+ }
4603
+ /**
4604
+ * Delete a skill and all its owned child file repos.
4605
+ */
4606
+ async deleteSkill(identifier) {
4607
+ return this._deleteDirectory(identifier);
4608
+ }
4609
+ /**
4610
+ * List agent repos. Yields one at a time, auto-paginating.
4611
+ */
4612
+ async *listAgents(options) {
4613
+ yield* this._listReposByType("agent", options);
4614
+ }
4615
+ /**
4616
+ * List skill repos. Yields one at a time, auto-paginating.
4617
+ */
4618
+ async *listSkills(options) {
4619
+ yield* this._listReposByType("skill", options);
4620
+ }
4621
+ async *_listReposByType(repoType, options) {
4622
+ const params = new URLSearchParams();
4623
+ params.append("repo_type", repoType);
4624
+ params.append("is_archived", (!!options?.isArchived).toString());
4625
+ if (options?.isPublic !== undefined) {
4626
+ params.append("is_public", options.isPublic.toString());
4627
+ }
4628
+ if (options?.query) {
4629
+ params.append("query", options.query);
4630
+ }
4631
+ for await (const repos of this._getPaginated("/repos", params, (res) => res.repos)) {
4632
+ yield* repos;
4633
+ }
4634
+ }
4635
+ async _pullDirectory(identifier, repoType, version) {
4636
+ const [owner, name, parsedVersion] = (0, prompts_js_1.parseHubIdentifier)(identifier);
4637
+ const resolvedVersion = version ?? (parsedVersion !== "latest" ? parsedVersion : undefined);
4638
+ const url = new URL(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories`);
4639
+ url.searchParams.set("repo_type", repoType);
4640
+ if (resolvedVersion) {
4641
+ url.searchParams.set("commit", resolvedVersion);
4642
+ }
4643
+ const response = await this.caller.call(async () => {
4644
+ const res = await this._fetch(url.toString(), {
4645
+ method: "GET",
4646
+ headers: this._mergedHeaders,
4647
+ signal: AbortSignal.timeout(this.timeout_ms),
4648
+ ...this.fetchOptions,
4649
+ });
4650
+ await (0, error_js_1.raiseForStatus)(res, "pull directory");
4651
+ return res;
4652
+ });
4653
+ return (await response.json());
4654
+ }
4655
+ async _pushDirectory(identifier, repoType, options) {
4656
+ if (options.parentCommit !== undefined &&
4657
+ (options.parentCommit.length < 8 || options.parentCommit.length > 64)) {
4658
+ throw new Error("parent_commit must be 8-64 characters");
4659
+ }
4660
+ const [owner, name] = (0, prompts_js_1.parseHubIdentifier)(identifier);
4661
+ if (!(await this._currentTenantIsOwner(owner))) {
4662
+ throw await this._ownerConflictError(`push ${repoType}`, owner);
4663
+ }
4664
+ if (await this._repoExists(owner, name)) {
4665
+ if (options.description !== undefined ||
4666
+ options.readme !== undefined ||
4667
+ options.tags !== undefined ||
4668
+ options.isPublic !== undefined) {
4669
+ await this._updateRepoMetadata(owner, name, options);
4670
+ }
4671
+ }
4672
+ else {
4673
+ const REPO_HANDLE_PATTERN = /^[a-z][a-z0-9-_]*$/;
4674
+ if (!REPO_HANDLE_PATTERN.test(name)) {
4675
+ throw new Error(`Invalid repo_handle ${JSON.stringify(name)}: must match ${REPO_HANDLE_PATTERN}`);
4676
+ }
4677
+ await this._createRepo(name, repoType, options);
4678
+ }
4679
+ const body = { files: options.files };
4680
+ if (options.parentCommit) {
4681
+ body.parent_commit = options.parentCommit;
4682
+ }
4683
+ const response = await this.caller.call(async () => {
4684
+ const res = await this._fetch(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories/commits`, {
4685
+ method: "POST",
4686
+ headers: {
4687
+ ...this._mergedHeaders,
4688
+ "Content-Type": "application/json",
4689
+ },
4690
+ signal: AbortSignal.timeout(this.timeout_ms),
4691
+ ...this.fetchOptions,
4692
+ body: JSON.stringify(body),
4693
+ });
4694
+ await (0, error_js_1.raiseForStatus)(res, `push ${repoType}`);
4695
+ return res;
4696
+ });
4697
+ const data = (await response.json());
4698
+ const commitHash = data.commit.commit_hash;
4699
+ return `${this.getHostUrl()}/hub/${owner}/${name}:${commitHash.slice(0, 8)}`;
4700
+ }
4701
+ async _deleteDirectory(identifier) {
4702
+ const [owner, name] = (0, prompts_js_1.parseHubIdentifier)(identifier);
4703
+ if (!(await this._currentTenantIsOwner(owner))) {
4704
+ throw await this._ownerConflictError("delete", owner);
4705
+ }
4706
+ await this.caller.call(async () => {
4707
+ const res = await this._fetch(`${this.apiUrl}/v1/platform/hub/repos/${owner}/${name}/directories`, {
4708
+ method: "DELETE",
4709
+ headers: this._mergedHeaders,
4710
+ signal: AbortSignal.timeout(this.timeout_ms),
4711
+ ...this.fetchOptions,
4712
+ });
4713
+ await (0, error_js_1.raiseForStatus)(res, "delete directory");
4714
+ return res;
4715
+ });
4716
+ }
4717
+ async _repoExists(owner, name) {
4718
+ try {
4719
+ await this.caller.call(async () => {
4720
+ const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${name}`, {
4721
+ method: "GET",
4722
+ headers: this._mergedHeaders,
4723
+ signal: AbortSignal.timeout(this.timeout_ms),
4724
+ ...this.fetchOptions,
4725
+ });
4726
+ await (0, error_js_1.raiseForStatus)(res, "check repo exists");
4727
+ return res;
4728
+ });
4729
+ return true;
4730
+ }
4731
+ catch (e) {
4732
+ if ((0, error_js_1.isLangSmithNotFoundError)(e)) {
4733
+ return false;
4734
+ }
4735
+ throw e;
4736
+ }
4737
+ }
4738
+ async _createRepo(name, repoType, options) {
4739
+ const body = {
4740
+ repo_handle: name,
4741
+ repo_type: repoType,
4742
+ is_public: !!options.isPublic,
4743
+ };
4744
+ if (options.description !== undefined)
4745
+ body.description = options.description;
4746
+ if (options.readme !== undefined)
4747
+ body.readme = options.readme;
4748
+ if (options.tags !== undefined)
4749
+ body.tags = options.tags;
4750
+ try {
4751
+ await this.caller.call(async () => {
4752
+ const res = await this._fetch(`${this.apiUrl}/repos/`, {
4753
+ method: "POST",
4754
+ headers: {
4755
+ ...this._mergedHeaders,
4756
+ "Content-Type": "application/json",
4757
+ },
4758
+ signal: AbortSignal.timeout(this.timeout_ms),
4759
+ ...this.fetchOptions,
4760
+ body: JSON.stringify(body),
4761
+ });
4762
+ await (0, error_js_1.raiseForStatus)(res, `create ${repoType}`);
4763
+ return res;
4764
+ });
4765
+ }
4766
+ catch (e) {
4767
+ if ((0, error_js_1.isLangSmithConflictError)(e)) {
4768
+ return;
4769
+ }
4770
+ throw e;
4771
+ }
4772
+ }
4773
+ async _updateRepoMetadata(owner, name, options) {
4774
+ const body = {};
4775
+ if (options.description !== undefined)
4776
+ body.description = options.description;
4777
+ if (options.readme !== undefined)
4778
+ body.readme = options.readme;
4779
+ if (options.tags !== undefined)
4780
+ body.tags = options.tags;
4781
+ if (options.isPublic !== undefined)
4782
+ body.is_public = options.isPublic;
4783
+ if (Object.keys(body).length === 0)
4784
+ return;
4785
+ await this.caller.call(async () => {
4786
+ const res = await this._fetch(`${this.apiUrl}/repos/${owner}/${name}`, {
4787
+ method: "PATCH",
4788
+ headers: {
4789
+ ...this._mergedHeaders,
4790
+ "Content-Type": "application/json",
4791
+ },
4792
+ signal: AbortSignal.timeout(this.timeout_ms),
4793
+ ...this.fetchOptions,
4794
+ body: JSON.stringify(body),
4795
+ });
4796
+ await (0, error_js_1.raiseForStatus)(res, "update repo metadata");
4797
+ return res;
4798
+ });
4799
+ }
4460
4800
  /**
4461
4801
  * Clone a public dataset to your own langsmith tenant.
4462
4802
  * This operation is idempotent. If you already have a dataset with the given name,
@@ -4588,6 +4928,12 @@ class Client {
4588
4928
  * ```
4589
4929
  */
4590
4930
  await new Promise((resolve) => setTimeout(resolve, 1));
4931
+ // Wait for any in-flight drains (whose serialize work may still be
4932
+ // in-progress on a worker thread and not yet registered with the
4933
+ // batchIngestCaller queue) before checking queue idleness.
4934
+ while (this._pendingDrains.size > 0) {
4935
+ await Promise.all([...this._pendingDrains]);
4936
+ }
4591
4937
  await Promise.all([
4592
4938
  ...this.autoBatchQueue.items.map(({ itemPromise }) => itemPromise),
4593
4939
  this.batchIngestCaller.queue.onIdle(),