braintrust 3.21.0 → 3.23.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 (54) hide show
  1. package/README.md +47 -0
  2. package/dev/dist/index.d.mts +1030 -1
  3. package/dev/dist/index.d.ts +1030 -1
  4. package/dev/dist/index.js +1429 -663
  5. package/dev/dist/index.mjs +811 -45
  6. package/dist/apply-auto-instrumentation.js +222 -186
  7. package/dist/apply-auto-instrumentation.mjs +37 -1
  8. package/dist/auto-instrumentations/bundler/esbuild.cjs +53 -1
  9. package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
  10. package/dist/auto-instrumentations/bundler/next.cjs +53 -1
  11. package/dist/auto-instrumentations/bundler/next.mjs +3 -3
  12. package/dist/auto-instrumentations/bundler/rollup.cjs +53 -1
  13. package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
  14. package/dist/auto-instrumentations/bundler/vite.cjs +53 -1
  15. package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
  16. package/dist/auto-instrumentations/bundler/webpack-loader.cjs +53 -1
  17. package/dist/auto-instrumentations/bundler/webpack.cjs +53 -1
  18. package/dist/auto-instrumentations/bundler/webpack.mjs +3 -3
  19. package/dist/auto-instrumentations/{chunk-TKRPRPGD.mjs → chunk-EXY7QCJD.mjs} +5 -2
  20. package/dist/auto-instrumentations/{chunk-T6J4C7LX.mjs → chunk-KIMLYPRW.mjs} +1 -1
  21. package/dist/auto-instrumentations/{chunk-BRQX23KL.mjs → chunk-YXLNSAMJ.mjs} +51 -0
  22. package/dist/auto-instrumentations/hook.mjs +224 -31
  23. package/dist/auto-instrumentations/index.cjs +52 -0
  24. package/dist/auto-instrumentations/index.d.mts +3 -1
  25. package/dist/auto-instrumentations/index.d.ts +3 -1
  26. package/dist/auto-instrumentations/index.mjs +3 -1
  27. package/dist/browser.d.mts +3273 -93
  28. package/dist/browser.d.ts +3273 -93
  29. package/dist/browser.js +2495 -63
  30. package/dist/browser.mjs +2495 -63
  31. package/dist/{chunk-KMGUTPB7.mjs → chunk-36IPYKMG.mjs} +20 -1
  32. package/dist/{chunk-BFGIH2ZJ.js → chunk-CDIKAHDZ.js} +21 -2
  33. package/dist/{chunk-FY7DAKA5.js → chunk-FZWPFCVE.js} +1590 -832
  34. package/dist/{chunk-O2P765XK.mjs → chunk-IXL4PMY4.mjs} +802 -44
  35. package/dist/cli.js +816 -47
  36. package/dist/edge-light.d.mts +1 -1
  37. package/dist/edge-light.d.ts +1 -1
  38. package/dist/edge-light.js +2495 -63
  39. package/dist/edge-light.mjs +2495 -63
  40. package/dist/index.d.mts +3273 -93
  41. package/dist/index.d.ts +3273 -93
  42. package/dist/index.js +2078 -416
  43. package/dist/index.mjs +1664 -2
  44. package/dist/instrumentation/index.d.mts +912 -2
  45. package/dist/instrumentation/index.d.ts +912 -2
  46. package/dist/instrumentation/index.js +2202 -42
  47. package/dist/instrumentation/index.mjs +2200 -42
  48. package/dist/vitest-evals-reporter.js +16 -16
  49. package/dist/vitest-evals-reporter.mjs +2 -2
  50. package/dist/workerd.d.mts +1 -1
  51. package/dist/workerd.d.ts +1 -1
  52. package/dist/workerd.js +2495 -63
  53. package/dist/workerd.mjs +2495 -63
  54. package/package.json +5 -2
package/dist/cli.js CHANGED
@@ -1232,7 +1232,7 @@ var require_package = __commonJS({
1232
1232
  "package.json"(exports2, module2) {
1233
1233
  module2.exports = {
1234
1234
  name: "braintrust",
1235
- version: "3.21.0",
1235
+ version: "3.23.0",
1236
1236
  description: "SDK for integrating Braintrust",
1237
1237
  repository: {
1238
1238
  type: "git",
@@ -1413,6 +1413,7 @@ var require_package = __commonJS({
1413
1413
  "@types/async": "^3.2.24",
1414
1414
  "@types/cli-progress": "^3.11.5",
1415
1415
  "@types/cors": "^2.8.17",
1416
+ "@types/esquery": "^1.5.4",
1416
1417
  "@types/express": "^5.0.0",
1417
1418
  "@types/http-errors": "^2.0.4",
1418
1419
  "@types/mustache": "^4.2.5",
@@ -1424,11 +1425,14 @@ var require_package = __commonJS({
1424
1425
  ai: "^6.0.0",
1425
1426
  async: "^3.2.5",
1426
1427
  "cross-env": "^7.0.3",
1428
+ "eslint-plugin-es-x": "9.7.0",
1429
+ "eslint-plugin-n": "^18.2.1",
1427
1430
  "eslint-plugin-node-import": "^1.0.5",
1428
1431
  openai: "6.25.0",
1429
1432
  rollup: "^4.60.3",
1430
1433
  tar: "^7.5.16",
1431
1434
  tinybench: "^4.0.1",
1435
+ "ts-declaration-location": "^1.0.7",
1432
1436
  tsup: "^8.5.1",
1433
1437
  tsx: "^4.21.0",
1434
1438
  typedoc: "^0.25.13",
@@ -1442,7 +1446,6 @@ var require_package = __commonJS({
1442
1446
  },
1443
1447
  dependencies: {
1444
1448
  "@next/env": "^14.2.3",
1445
- "@types/estree": "^1.0.8",
1446
1449
  "@vercel/functions": "^1.0.2",
1447
1450
  acorn: "^8.16.0",
1448
1451
  "acorn-import-attributes": "^1.9.5",
@@ -3724,19 +3727,30 @@ var CallEvent = import_v36.z.union([
3724
3727
  var ChatCompletionContentPartTextWithTitle = import_v36.z.object({
3725
3728
  text: import_v36.z.string().default(""),
3726
3729
  type: import_v36.z.literal("text"),
3727
- cache_control: import_v36.z.object({ type: import_v36.z.literal("ephemeral") }).optional()
3730
+ cache_control: import_v36.z.object({
3731
+ type: import_v36.z.literal("ephemeral"),
3732
+ ttl: import_v36.z.enum(["5m", "1h"]).optional()
3733
+ }).optional()
3728
3734
  });
3729
3735
  var ChatCompletionContentPartImageWithTitle = import_v36.z.object({
3730
3736
  image_url: import_v36.z.object({
3731
3737
  url: import_v36.z.string(),
3732
3738
  detail: import_v36.z.union([import_v36.z.literal("auto"), import_v36.z.literal("low"), import_v36.z.literal("high")]).optional()
3733
3739
  }),
3734
- type: import_v36.z.literal("image_url")
3740
+ type: import_v36.z.literal("image_url"),
3741
+ cache_control: import_v36.z.object({
3742
+ type: import_v36.z.literal("ephemeral"),
3743
+ ttl: import_v36.z.enum(["5m", "1h"]).optional()
3744
+ }).optional()
3735
3745
  });
3736
3746
  var ChatCompletionContentPartFileFile = import_v36.z.object({ file_data: import_v36.z.string(), filename: import_v36.z.string(), file_id: import_v36.z.string() }).partial();
3737
3747
  var ChatCompletionContentPartFileWithTitle = import_v36.z.object({
3738
3748
  file: ChatCompletionContentPartFileFile,
3739
- type: import_v36.z.literal("file")
3749
+ type: import_v36.z.literal("file"),
3750
+ cache_control: import_v36.z.object({
3751
+ type: import_v36.z.literal("ephemeral"),
3752
+ ttl: import_v36.z.enum(["5m", "1h"]).optional()
3753
+ }).optional()
3740
3754
  });
3741
3755
  var ChatCompletionContentPart = import_v36.z.union([
3742
3756
  ChatCompletionContentPartTextWithTitle,
@@ -3746,7 +3760,10 @@ var ChatCompletionContentPart = import_v36.z.union([
3746
3760
  var ChatCompletionContentPartText = import_v36.z.object({
3747
3761
  text: import_v36.z.string().default(""),
3748
3762
  type: import_v36.z.literal("text"),
3749
- cache_control: import_v36.z.object({ type: import_v36.z.literal("ephemeral") }).optional()
3763
+ cache_control: import_v36.z.object({
3764
+ type: import_v36.z.literal("ephemeral"),
3765
+ ttl: import_v36.z.enum(["5m", "1h"]).optional()
3766
+ }).optional()
3750
3767
  });
3751
3768
  var ChatCompletionMessageToolCall = import_v36.z.object({
3752
3769
  id: import_v36.z.string(),
@@ -5781,7 +5798,7 @@ var DiskCache = class {
5781
5798
  }
5782
5799
  };
5783
5800
 
5784
- // src/prompt-cache/lru-cache.ts
5801
+ // src/lru-cache.ts
5785
5802
  var LRUCache = class {
5786
5803
  cache;
5787
5804
  maxSize;
@@ -5797,13 +5814,23 @@ var LRUCache = class {
5797
5814
  * @returns The cached value if found, undefined otherwise.
5798
5815
  */
5799
5816
  get(key) {
5800
- const value = this.cache.get(key);
5801
- if (value === void 0) {
5817
+ const entry = this.cache.get(key);
5818
+ if (entry === void 0) {
5802
5819
  return void 0;
5803
5820
  }
5804
5821
  this.cache.delete(key);
5805
- this.cache.set(key, value);
5806
- return value;
5822
+ this.cache.set(key, entry);
5823
+ return entry.value;
5824
+ }
5825
+ /**
5826
+ * Checks whether a key exists and marks it as most recently used.
5827
+ */
5828
+ has(key) {
5829
+ if (!this.cache.has(key)) {
5830
+ return false;
5831
+ }
5832
+ this.get(key);
5833
+ return true;
5807
5834
  }
5808
5835
  /**
5809
5836
  * Stores a value in the cache.
@@ -5820,7 +5847,38 @@ var LRUCache = class {
5820
5847
  const first = this.cache.keys().next().value;
5821
5848
  this.cache.delete(first);
5822
5849
  }
5823
- this.cache.set(key, value);
5850
+ this.cache.set(key, { value });
5851
+ }
5852
+ /**
5853
+ * Removes an item from the cache.
5854
+ */
5855
+ delete(key) {
5856
+ return this.cache.delete(key);
5857
+ }
5858
+ /**
5859
+ * Iterates over cache entries from least to most recently used.
5860
+ */
5861
+ *entries() {
5862
+ for (const [key, entry] of this.cache) {
5863
+ yield [key, entry.value];
5864
+ }
5865
+ }
5866
+ /**
5867
+ * Iterates over cache keys from least to most recently used.
5868
+ */
5869
+ keys() {
5870
+ return this.cache.keys();
5871
+ }
5872
+ /**
5873
+ * Iterates over cache values from least to most recently used.
5874
+ */
5875
+ *values() {
5876
+ for (const entry of this.cache.values()) {
5877
+ yield entry.value;
5878
+ }
5879
+ }
5880
+ [Symbol.iterator]() {
5881
+ return this.entries();
5824
5882
  }
5825
5883
  /**
5826
5884
  * Removes all items from the cache.
@@ -6378,6 +6436,9 @@ function applyMaskingToField(maskingFunction, data, fieldName) {
6378
6436
  return `ERROR: Failed to mask field '${fieldName}' - ${errorType}`;
6379
6437
  }
6380
6438
  }
6439
+ var INITIAL_SPAN_WRITE_AS_MERGE = /* @__PURE__ */ Symbol(
6440
+ "braintrust.initial-span-write-as-merge"
6441
+ );
6381
6442
  var BRAINTRUST_CURRENT_SPAN_STORE = /* @__PURE__ */ Symbol.for(
6382
6443
  "braintrust.currentSpanStore"
6383
6444
  );
@@ -6947,25 +7008,46 @@ var HTTPConnection = class _HTTPConnection {
6947
7008
  })
6948
7009
  );
6949
7010
  }
6950
- async post(path8, params, config3) {
7011
+ async post(path8, params, config3, retries = 0) {
6951
7012
  const { headers, ...rest } = config3 || {};
6952
7013
  const this_fetch = this.fetch;
6953
7014
  const this_base_url = this.base_url;
6954
7015
  const this_headers = this.headers;
6955
- return await checkResponse(
6956
- await this_fetch(_urljoin(this_base_url, path8), {
6957
- method: "POST",
6958
- headers: {
6959
- Accept: "application/json",
6960
- "Content-Type": "application/json",
6961
- ...this_headers,
6962
- ...headers
6963
- },
6964
- body: typeof params === "string" ? params : params ? JSON.stringify(params) : void 0,
6965
- keepalive: true,
6966
- ...rest
6967
- })
6968
- );
7016
+ const tries = retries + 1;
7017
+ for (let i = 0; i < tries; i++) {
7018
+ try {
7019
+ return await checkResponse(
7020
+ await this_fetch(_urljoin(this_base_url, path8), {
7021
+ method: "POST",
7022
+ headers: {
7023
+ Accept: "application/json",
7024
+ "Content-Type": "application/json",
7025
+ ...this_headers,
7026
+ ...headers
7027
+ },
7028
+ body: typeof params === "string" ? params : params ? JSON.stringify(params) : void 0,
7029
+ keepalive: true,
7030
+ ...rest
7031
+ })
7032
+ );
7033
+ } catch (error2) {
7034
+ if (config3?.signal?.aborted) {
7035
+ throw getAbortReason(config3.signal);
7036
+ }
7037
+ if (i === tries - 1 || !isRetryableHTTPError(error2)) {
7038
+ throw error2;
7039
+ }
7040
+ debugLogger.debug(
7041
+ `Retrying API request ${path8} after ${formatHTTPError(error2)}`
7042
+ );
7043
+ const sleepTimeMs = HTTP_RETRY_BASE_SLEEP_TIME_S * 1e3 * 2 ** i + Math.random() * HTTP_RETRY_JITTER_MS;
7044
+ debugLogger.info(
7045
+ `Sleeping for ${sleepTimeMs}ms before retrying API request`
7046
+ );
7047
+ await waitForRetry(sleepTimeMs, config3?.signal);
7048
+ }
7049
+ }
7050
+ throw new Error("Unexpected retry state");
6969
7051
  }
6970
7052
  async get_json(object_type, args = void 0, retries = 0) {
6971
7053
  const tries = retries + 1;
@@ -7865,6 +7947,45 @@ function now() {
7865
7947
  var DEFAULT_FLUSH_BACKPRESSURE_BYTES = 10 * 1024 * 1024;
7866
7948
  var BACKGROUND_LOGGER_BASE_SLEEP_TIME_S = 1;
7867
7949
  var HTTP_RETRY_BASE_SLEEP_TIME_S = 1;
7950
+ var HTTP_RETRY_JITTER_MS = 200;
7951
+ var BTQL_HTTP_RETRIES = 3;
7952
+ var RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
7953
+ function isRetryableHTTPError(error2) {
7954
+ return !(error2 instanceof FailedHTTPResponse) || RETRYABLE_HTTP_STATUS_CODES.has(error2.status);
7955
+ }
7956
+ function formatHTTPError(error2) {
7957
+ if (error2 instanceof FailedHTTPResponse) {
7958
+ return `${error2.status} ${error2.text}`;
7959
+ }
7960
+ return error2 instanceof Error ? error2.message : String(error2);
7961
+ }
7962
+ function getAbortReason(signal) {
7963
+ return signal.reason ?? new Error("Request aborted");
7964
+ }
7965
+ async function waitForRetry(delayMs, signal) {
7966
+ if (!signal) {
7967
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
7968
+ return;
7969
+ }
7970
+ if (signal.aborted) {
7971
+ throw getAbortReason(signal);
7972
+ }
7973
+ await new Promise((resolve2, reject2) => {
7974
+ const onAbort = () => {
7975
+ clearTimeout(timeout);
7976
+ signal.removeEventListener("abort", onAbort);
7977
+ reject2(getAbortReason(signal));
7978
+ };
7979
+ const timeout = setTimeout(() => {
7980
+ signal.removeEventListener("abort", onAbort);
7981
+ resolve2();
7982
+ }, delayMs);
7983
+ signal.addEventListener("abort", onAbort, { once: true });
7984
+ if (signal.aborted) {
7985
+ onAbort();
7986
+ }
7987
+ });
7988
+ }
7868
7989
  var HTTPBackgroundLogger = class _HTTPBackgroundLogger {
7869
7990
  apiConn;
7870
7991
  queue;
@@ -9292,15 +9413,15 @@ function parentSpanIdsUsable(spanId, rootSpanId) {
9292
9413
  return Boolean(spanId) && Boolean(rootSpanId);
9293
9414
  }
9294
9415
  function logError(span, error2) {
9295
- let errorMessage = "<error>";
9416
+ let errorMessage2 = "<error>";
9296
9417
  let stackTrace = "";
9297
9418
  if (error2 instanceof Error) {
9298
- errorMessage = error2.message;
9419
+ errorMessage2 = error2.message;
9299
9420
  stackTrace = error2.stack || "";
9300
9421
  } else {
9301
- errorMessage = String(error2);
9422
+ errorMessage2 = String(error2);
9302
9423
  }
9303
- span.log({ error: `${errorMessage}
9424
+ span.log({ error: `${errorMessage2}
9304
9425
 
9305
9426
  ${stackTrace}` });
9306
9427
  }
@@ -9679,7 +9800,8 @@ var ObjectFetcher = class {
9679
9800
  version: this.pinnedVersion
9680
9801
  } : {}
9681
9802
  },
9682
- { headers: { "Accept-Encoding": "gzip" } }
9803
+ { headers: { "Accept-Encoding": "gzip" } },
9804
+ BTQL_HTTP_RETRIES
9683
9805
  );
9684
9806
  const respJson = await resp.json();
9685
9807
  const mutate = this.mutateRecord;
@@ -10196,7 +10318,7 @@ var SpanImpl = class _SpanImpl {
10196
10318
  this._spanId = resolvedIds.spanId;
10197
10319
  this._rootSpanId = resolvedIds.rootSpanId;
10198
10320
  this._spanParents = resolvedIds.spanParents;
10199
- this.isMerge = false;
10321
+ this.isMerge = args[INITIAL_SPAN_WRITE_AS_MERGE] === true;
10200
10322
  this.logInternal({ event, internalData });
10201
10323
  this.isMerge = true;
10202
10324
  }
@@ -32855,6 +32977,446 @@ function isBraintrustHandler(handler) {
32855
32977
  return Reflect.get(handler, "name") === BRAINTRUST_LANGCHAIN_CALLBACK_HANDLER_NAME;
32856
32978
  }
32857
32979
 
32980
+ // src/instrumentation/plugins/langsmith-channels.ts
32981
+ var langSmithChannels = defineChannels("langsmith", {
32982
+ createRun: channel({
32983
+ channelName: "Client.createRun",
32984
+ kind: "async"
32985
+ }),
32986
+ updateRun: channel({
32987
+ channelName: "Client.updateRun",
32988
+ kind: "async"
32989
+ }),
32990
+ batchIngestRuns: channel({
32991
+ channelName: "Client.batchIngestRuns",
32992
+ kind: "async"
32993
+ })
32994
+ });
32995
+
32996
+ // src/instrumentation/plugins/langsmith-plugin.ts
32997
+ var MAX_COMPLETED_RUNS = 1e4;
32998
+ var BLOCKED_METADATA_KEYS = /* @__PURE__ */ new Set([
32999
+ "__proto__",
33000
+ "constructor",
33001
+ "prototype",
33002
+ "usage_metadata"
33003
+ ]);
33004
+ var BLOCKED_METADATA_PREFIXES = ["__pregel_", "langgraph_", "lc_"];
33005
+ var LLM_SETTING_KEYS = [
33006
+ "temperature",
33007
+ "top_p",
33008
+ "max_tokens",
33009
+ "frequency_penalty",
33010
+ "presence_penalty",
33011
+ "stop",
33012
+ "response_format"
33013
+ ];
33014
+ var LangSmithPlugin = class extends BasePlugin {
33015
+ activeRuns = /* @__PURE__ */ new Map();
33016
+ completedRuns = new LRUCache({
33017
+ max: MAX_COMPLETED_RUNS
33018
+ });
33019
+ skipLangChainRuns;
33020
+ constructor(options = {}) {
33021
+ super();
33022
+ this.skipLangChainRuns = options.skipLangChainRuns ?? true;
33023
+ }
33024
+ onEnable() {
33025
+ const createChannel = langSmithChannels.createRun.tracingChannel();
33026
+ const createHandlers = {
33027
+ start: (event) => {
33028
+ this.containLifecycleFailure("createRun", () => {
33029
+ this.processCreate(event.arguments[0]);
33030
+ });
33031
+ }
33032
+ };
33033
+ createChannel.subscribe(createHandlers);
33034
+ this.unsubscribers.push(() => createChannel.unsubscribe(createHandlers));
33035
+ const updateChannel = langSmithChannels.updateRun.tracingChannel();
33036
+ const updateHandlers = {
33037
+ start: (event) => {
33038
+ this.containLifecycleFailure("updateRun", () => {
33039
+ this.processUpdate(event.arguments[0], event.arguments[1]);
33040
+ });
33041
+ }
33042
+ };
33043
+ updateChannel.subscribe(updateHandlers);
33044
+ this.unsubscribers.push(() => updateChannel.unsubscribe(updateHandlers));
33045
+ const batchChannel = langSmithChannels.batchIngestRuns.tracingChannel();
33046
+ const batchHandlers = {
33047
+ start: (event) => {
33048
+ this.containLifecycleFailure("batchIngestRuns", () => {
33049
+ this.processBatch(event.arguments[0]);
33050
+ });
33051
+ }
33052
+ };
33053
+ batchChannel.subscribe(batchHandlers);
33054
+ this.unsubscribers.push(() => batchChannel.unsubscribe(batchHandlers));
33055
+ }
33056
+ onDisable() {
33057
+ this.unsubscribers = unsubscribeAll(this.unsubscribers);
33058
+ for (const { span } of this.activeRuns.values()) {
33059
+ span.end();
33060
+ }
33061
+ this.activeRuns.clear();
33062
+ this.completedRuns.clear();
33063
+ }
33064
+ processBatch(batch) {
33065
+ if (!isRecord2(batch)) {
33066
+ return;
33067
+ }
33068
+ const creates = ownValue(batch, "runCreates");
33069
+ if (Array.isArray(creates)) {
33070
+ const parentFirst = [...creates].sort((left, right) => {
33071
+ const leftId = stringValue2(ownValue(left, "id"));
33072
+ const rightId = stringValue2(ownValue(right, "id"));
33073
+ const leftParent = stringValue2(ownValue(left, "parent_run_id"));
33074
+ const rightParent = stringValue2(ownValue(right, "parent_run_id"));
33075
+ if (leftParent && leftParent === rightId) {
33076
+ return 1;
33077
+ }
33078
+ if (rightParent && rightParent === leftId) {
33079
+ return -1;
33080
+ }
33081
+ return dottedOrderDepth(left) - dottedOrderDepth(right);
33082
+ });
33083
+ for (const run2 of parentFirst) {
33084
+ this.containLifecycleFailure("batchIngestRuns create", () => {
33085
+ this.processCreate(run2);
33086
+ });
33087
+ }
33088
+ }
33089
+ const updates = ownValue(batch, "runUpdates");
33090
+ if (Array.isArray(updates)) {
33091
+ for (const run2 of updates) {
33092
+ this.containLifecycleFailure("batchIngestRuns update", () => {
33093
+ this.processUpdate(stringValue2(ownValue(run2, "id")) ?? "", run2);
33094
+ });
33095
+ }
33096
+ }
33097
+ }
33098
+ processCreate(run2) {
33099
+ const id = stringValue2(ownValue(run2, "id"));
33100
+ if (!id || this.completedRuns.get(id)) {
33101
+ return;
33102
+ }
33103
+ if (this.shouldSkipLangChainRun(run2)) {
33104
+ this.completedRuns.set(id, true);
33105
+ return;
33106
+ }
33107
+ const active = this.activeRuns.get(id);
33108
+ if (active) {
33109
+ const previous = active.run;
33110
+ active.run = mergeRuns(previous, run2);
33111
+ this.logRun(
33112
+ active.span,
33113
+ active.run,
33114
+ previous,
33115
+ timestampSeconds(ownValue(active.run, "end_time")) !== void 0 || errorMessage(ownValue(active.run, "error")) !== void 0
33116
+ );
33117
+ this.endIfComplete(id, active, active.run);
33118
+ return;
33119
+ }
33120
+ const span = this.startRunSpan(id, run2);
33121
+ const activeRun = { run: run2, span };
33122
+ this.activeRuns.set(id, activeRun);
33123
+ this.logRun(
33124
+ span,
33125
+ run2,
33126
+ void 0,
33127
+ timestampSeconds(ownValue(run2, "end_time")) !== void 0 || errorMessage(ownValue(run2, "error")) !== void 0
33128
+ );
33129
+ this.endIfComplete(id, activeRun, run2);
33130
+ }
33131
+ processUpdate(explicitId, run2) {
33132
+ const id = stringValue2(explicitId) ?? stringValue2(ownValue(run2, "id"));
33133
+ if (!id || this.completedRuns.get(id)) {
33134
+ return;
33135
+ }
33136
+ if (this.shouldSkipLangChainRun(run2)) {
33137
+ const active2 = this.activeRuns.get(id);
33138
+ active2?.span.end();
33139
+ this.activeRuns.delete(id);
33140
+ this.completedRuns.set(id, true);
33141
+ return;
33142
+ }
33143
+ let active = this.activeRuns.get(id);
33144
+ if (!active) {
33145
+ const span = this.startRunSpan(id, run2);
33146
+ active = { run: run2, span };
33147
+ this.activeRuns.set(id, active);
33148
+ }
33149
+ const previous = active.run;
33150
+ active.run = mergeRuns(previous, run2);
33151
+ this.logRun(active.span, active.run, previous, true);
33152
+ this.endIfComplete(id, active, active.run);
33153
+ }
33154
+ startRunSpan(id, run2) {
33155
+ const traceId = stringValue2(ownValue(run2, "trace_id")) ?? id;
33156
+ const parentId = stringValue2(ownValue(run2, "parent_run_id")) ?? stringValue2(ownValue(ownValue(run2, "parent_run"), "id"));
33157
+ const startTime = timestampSeconds(ownValue(run2, "start_time"));
33158
+ return startSpan({
33159
+ name: stringValue2(ownValue(run2, "name")) ?? "LangSmith run",
33160
+ spanId: id,
33161
+ parentSpanIds: {
33162
+ parentSpanIds: parentId ? [parentId] : [],
33163
+ rootSpanId: traceId
33164
+ },
33165
+ spanAttributes: {
33166
+ type: mapRunType(ownValue(run2, "run_type"))
33167
+ },
33168
+ ...startTime === void 0 ? {} : { startTime },
33169
+ event: { id }
33170
+ });
33171
+ }
33172
+ logRun(span, run2, previous, includeOutput) {
33173
+ const inputs = preferOwnValue(run2, previous, "inputs");
33174
+ const outputs = preferOwnValue(run2, previous, "outputs");
33175
+ const error2 = errorMessage(preferOwnValue(run2, previous, "error"));
33176
+ const metadata = extractMetadata(run2, previous);
33177
+ const tags = extractTags(preferOwnValue(run2, previous, "tags"));
33178
+ const metrics = extractMetrics(run2, previous);
33179
+ span.log({
33180
+ ...inputs === void 0 ? {} : { input: sanitizeLoggedValue(inputs) },
33181
+ ...includeOutput && outputs !== void 0 ? { output: sanitizeLoggedValue(outputs) } : {},
33182
+ ...error2 === void 0 ? {} : { error: error2 },
33183
+ ...metadata === void 0 ? {} : { metadata },
33184
+ ...tags === void 0 ? {} : { tags },
33185
+ ...Object.keys(metrics).length === 0 ? {} : { metrics }
33186
+ });
33187
+ }
33188
+ endIfComplete(id, active, run2) {
33189
+ const endTime = timestampSeconds(ownValue(run2, "end_time"));
33190
+ if (endTime === void 0 && errorMessage(ownValue(run2, "error")) === void 0) {
33191
+ return;
33192
+ }
33193
+ active.span.end(endTime === void 0 ? void 0 : { endTime });
33194
+ this.activeRuns.delete(id);
33195
+ this.completedRuns.set(id, true);
33196
+ }
33197
+ shouldSkipLangChainRun(run2) {
33198
+ if (!this.skipLangChainRuns) {
33199
+ return false;
33200
+ }
33201
+ const serialized = ownValue(run2, "serialized");
33202
+ return isRecord2(serialized) && ownValue(serialized, "lc") === 1;
33203
+ }
33204
+ containLifecycleFailure(operation, fn) {
33205
+ try {
33206
+ fn();
33207
+ } catch (error2) {
33208
+ debugLogger.error(
33209
+ `Failed to process LangSmith ${operation} instrumentation:`,
33210
+ error2
33211
+ );
33212
+ }
33213
+ }
33214
+ };
33215
+ function ownValue(value, key) {
33216
+ if (!isRecord2(value)) {
33217
+ return void 0;
33218
+ }
33219
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
33220
+ return descriptor && "value" in descriptor ? descriptor.value : void 0;
33221
+ }
33222
+ function preferOwnValue(current, previous, key) {
33223
+ const currentDescriptor = isRecord2(current) ? Object.getOwnPropertyDescriptor(current, key) : void 0;
33224
+ if (currentDescriptor && "value" in currentDescriptor) {
33225
+ return currentDescriptor.value;
33226
+ }
33227
+ return ownValue(previous, key);
33228
+ }
33229
+ function mergeRuns(previous, current) {
33230
+ const entries = /* @__PURE__ */ new Map();
33231
+ for (const value of [previous, current]) {
33232
+ if (!isRecord2(value)) {
33233
+ continue;
33234
+ }
33235
+ for (const [key, descriptor] of Object.entries(
33236
+ Object.getOwnPropertyDescriptors(value)
33237
+ )) {
33238
+ if (descriptor.enumerable && "value" in descriptor) {
33239
+ entries.set(key, descriptor.value);
33240
+ }
33241
+ }
33242
+ }
33243
+ return Object.fromEntries(entries);
33244
+ }
33245
+ function isRecord2(value) {
33246
+ return typeof value === "object" && value !== null && !Array.isArray(value);
33247
+ }
33248
+ function stringValue2(value) {
33249
+ return typeof value === "string" && value.length > 0 ? value : void 0;
33250
+ }
33251
+ function timestampSeconds(value) {
33252
+ if (value instanceof Date) {
33253
+ const timestamp = value.getTime();
33254
+ return Number.isFinite(timestamp) ? timestamp / 1e3 : void 0;
33255
+ }
33256
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
33257
+ return value > 1e10 ? value / 1e3 : value;
33258
+ }
33259
+ if (typeof value === "string") {
33260
+ const timestamp = Date.parse(value);
33261
+ return Number.isFinite(timestamp) ? timestamp / 1e3 : void 0;
33262
+ }
33263
+ return void 0;
33264
+ }
33265
+ function dottedOrderDepth(run2) {
33266
+ const dottedOrder = stringValue2(ownValue(run2, "dotted_order"));
33267
+ return dottedOrder ? dottedOrder.split(".").length : Number.MAX_SAFE_INTEGER;
33268
+ }
33269
+ function mapRunType(runType) {
33270
+ switch (runType) {
33271
+ case "llm":
33272
+ case "embedding":
33273
+ return "llm" /* LLM */;
33274
+ case "tool":
33275
+ case "retriever":
33276
+ return "tool" /* TOOL */;
33277
+ default:
33278
+ return "task" /* TASK */;
33279
+ }
33280
+ }
33281
+ function extractMetadata(run2, previous) {
33282
+ const extra = preferOwnValue(run2, previous, "extra");
33283
+ const rawMetadata = ownValue(extra, "metadata");
33284
+ if (!isRecord2(rawMetadata)) {
33285
+ return void 0;
33286
+ }
33287
+ const metadata = {};
33288
+ for (const [key, descriptor] of Object.entries(
33289
+ Object.getOwnPropertyDescriptors(rawMetadata)
33290
+ )) {
33291
+ if (!("value" in descriptor) || !descriptor.enumerable || BLOCKED_METADATA_KEYS.has(key) || BLOCKED_METADATA_PREFIXES.some((prefix) => key.startsWith(prefix)) || key.startsWith("ls_") && key !== "ls_provider" && key !== "ls_model_name" && !LLM_SETTING_KEYS.some((setting) => key === `ls_${setting}`)) {
33292
+ continue;
33293
+ }
33294
+ const normalizedKey = key === "ls_provider" ? "provider" : key === "ls_model_name" ? "model" : key.startsWith("ls_") ? key.slice(3) : key;
33295
+ const sanitized = sanitizeLoggedValue(descriptor.value);
33296
+ if (sanitized !== void 0) {
33297
+ metadata[normalizedKey] = sanitized;
33298
+ }
33299
+ }
33300
+ return Object.keys(metadata).length === 0 ? void 0 : metadata;
33301
+ }
33302
+ function extractTags(value) {
33303
+ if (!Array.isArray(value)) {
33304
+ return void 0;
33305
+ }
33306
+ const tags = value.filter(
33307
+ (tag) => typeof tag === "string" && tag.length > 0
33308
+ );
33309
+ return tags.length > 0 ? tags : void 0;
33310
+ }
33311
+ function extractMetrics(run2, previous) {
33312
+ const outputs = preferOwnValue(run2, previous, "outputs");
33313
+ const extra = preferOwnValue(run2, previous, "extra");
33314
+ const metadata = ownValue(extra, "metadata");
33315
+ const usage = ownValue(outputs, "usage_metadata") ?? ownValue(metadata, "usage_metadata");
33316
+ const metrics = {};
33317
+ assignFirstMetric(metrics, "prompt_tokens", usage, [
33318
+ "input_tokens",
33319
+ "prompt_tokens"
33320
+ ]);
33321
+ assignFirstMetric(metrics, "completion_tokens", usage, [
33322
+ "output_tokens",
33323
+ "completion_tokens"
33324
+ ]);
33325
+ assignFirstMetric(metrics, "tokens", usage, ["total_tokens", "tokens"]);
33326
+ const inputTokenDetails = ownValue(usage, "input_token_details");
33327
+ assignFirstMetric(metrics, "prompt_cached_tokens", inputTokenDetails, [
33328
+ "cache_read",
33329
+ "cached_tokens"
33330
+ ]);
33331
+ if (metrics.prompt_cached_tokens === void 0) {
33332
+ assignFirstMetric(metrics, "prompt_cached_tokens", usage, [
33333
+ "cache_read_input_tokens",
33334
+ "prompt_cached_tokens"
33335
+ ]);
33336
+ }
33337
+ assignFirstMetric(
33338
+ metrics,
33339
+ "prompt_cache_creation_tokens",
33340
+ inputTokenDetails,
33341
+ ["cache_creation"]
33342
+ );
33343
+ if (metrics.prompt_cache_creation_tokens === void 0) {
33344
+ assignFirstMetric(metrics, "prompt_cache_creation_tokens", usage, [
33345
+ "cache_creation_input_tokens",
33346
+ "prompt_cache_creation_tokens"
33347
+ ]);
33348
+ }
33349
+ if (metrics.tokens === void 0 && (metrics.prompt_tokens !== void 0 || metrics.completion_tokens !== void 0)) {
33350
+ metrics.tokens = (metrics.prompt_tokens ?? 0) + (metrics.completion_tokens ?? 0);
33351
+ }
33352
+ const startTime = timestampSeconds(
33353
+ preferOwnValue(run2, previous, "start_time")
33354
+ );
33355
+ const events = preferOwnValue(run2, previous, "events");
33356
+ if (startTime !== void 0 && Array.isArray(events)) {
33357
+ for (const event of events) {
33358
+ if (ownValue(event, "name") !== "new_token") {
33359
+ continue;
33360
+ }
33361
+ const eventTime2 = timestampSeconds(ownValue(event, "time"));
33362
+ if (eventTime2 !== void 0 && eventTime2 >= startTime) {
33363
+ metrics.time_to_first_token = eventTime2 - startTime;
33364
+ }
33365
+ break;
33366
+ }
33367
+ }
33368
+ return metrics;
33369
+ }
33370
+ function assignFirstMetric(metrics, target, source, keys) {
33371
+ for (const key of keys) {
33372
+ const value = ownValue(source, key);
33373
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
33374
+ metrics[target] = value;
33375
+ return;
33376
+ }
33377
+ }
33378
+ }
33379
+ function errorMessage(value) {
33380
+ if (typeof value === "string") {
33381
+ return value;
33382
+ }
33383
+ if (value instanceof Error) {
33384
+ return value.message;
33385
+ }
33386
+ return void 0;
33387
+ }
33388
+ function sanitizeLoggedValue(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
33389
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
33390
+ return value;
33391
+ }
33392
+ if (typeof value === "number") {
33393
+ return Number.isFinite(value) ? value : void 0;
33394
+ }
33395
+ if (value instanceof Date) {
33396
+ return Number.isFinite(value.getTime()) ? value.toISOString() : void 0;
33397
+ }
33398
+ if (typeof value !== "object" || depth >= 20) {
33399
+ return void 0;
33400
+ }
33401
+ if (seen.has(value)) {
33402
+ return "[Circular]";
33403
+ }
33404
+ seen.add(value);
33405
+ if (Array.isArray(value)) {
33406
+ return value.map((item) => sanitizeLoggedValue(item, seen, depth + 1));
33407
+ }
33408
+ const entries = [];
33409
+ for (const [key, descriptor] of Object.entries(
33410
+ Object.getOwnPropertyDescriptors(value)
33411
+ )) {
33412
+ if (!descriptor.enumerable || !("value" in descriptor) || BLOCKED_METADATA_KEYS.has(key)) {
33413
+ continue;
33414
+ }
33415
+ entries.push([key, sanitizeLoggedValue(descriptor.value, seen, depth + 1)]);
33416
+ }
33417
+ return Object.fromEntries(entries);
33418
+ }
33419
+
32858
33420
  // src/instrumentation/plugins/pi-coding-agent-channels.ts
32859
33421
  var piCodingAgentChannels = defineChannels(
32860
33422
  "@earendil-works/pi-coding-agent",
@@ -33234,11 +33796,11 @@ function patchAssistantMessageStream(stream, promptState, llmState) {
33234
33796
  function recordPiAssistantMessageEvent(promptState, llmState, event) {
33235
33797
  recordFirstTokenMetric(llmState, event);
33236
33798
  const message = "message" in event ? event.message : void 0;
33237
- const errorMessage = "error" in event ? event.error : void 0;
33799
+ const errorMessage2 = "error" in event ? event.error : void 0;
33238
33800
  if (event.type === "done" && isPiAssistantMessage(message)) {
33239
33801
  finishPiLlmSpan(promptState, llmState, message);
33240
- } else if (event.type === "error" && isPiAssistantMessage(errorMessage)) {
33241
- finishPiLlmSpan(promptState, llmState, errorMessage);
33802
+ } else if (event.type === "error" && isPiAssistantMessage(errorMessage2)) {
33803
+ finishPiLlmSpan(promptState, llmState, errorMessage2);
33242
33804
  }
33243
33805
  }
33244
33806
  function recordFirstTokenMetric(state, event) {
@@ -33756,6 +34318,7 @@ var strandsAgentSDKChannels = defineChannels("@strands-agents/sdk", {
33756
34318
  });
33757
34319
 
33758
34320
  // src/instrumentation/plugins/strands-agent-sdk-plugin.ts
34321
+ var MAX_STRANDS_STRING_ATTACHMENT_CACHE_ENTRIES = 32;
33759
34322
  var StrandsAgentSDKPlugin = class extends BasePlugin {
33760
34323
  activeChildParents = /* @__PURE__ */ new WeakMap();
33761
34324
  onEnable() {
@@ -33900,11 +34463,16 @@ function startAgentStream(event, activeChildParents) {
33900
34463
  ...event.moduleVersion ? { "strands_agent_sdk.version": event.moduleVersion } : {}
33901
34464
  };
33902
34465
  const parentSpan = agent ? getOnlyChildParent(activeChildParents, agent) : void 0;
34466
+ const attachmentCache = createStrandsAttachmentCache();
34467
+ const input = processStrandsInputAttachments(
34468
+ event.arguments[0],
34469
+ attachmentCache
34470
+ );
33903
34471
  const span = parentSpan ? withCurrent(
33904
34472
  parentSpan,
33905
34473
  () => startSpan({
33906
34474
  event: {
33907
- input: event.arguments[0],
34475
+ input,
33908
34476
  metadata
33909
34477
  },
33910
34478
  name: formatAgentSpanName(agent),
@@ -33912,7 +34480,7 @@ function startAgentStream(event, activeChildParents) {
33912
34480
  })
33913
34481
  ) : startSpan({
33914
34482
  event: {
33915
- input: event.arguments[0],
34483
+ input,
33916
34484
  metadata
33917
34485
  },
33918
34486
  name: formatAgentSpanName(agent),
@@ -33920,6 +34488,7 @@ function startAgentStream(event, activeChildParents) {
33920
34488
  });
33921
34489
  return {
33922
34490
  activeTools: /* @__PURE__ */ new Map(),
34491
+ attachmentCache,
33923
34492
  finalized: false,
33924
34493
  metadata,
33925
34494
  span,
@@ -33935,11 +34504,12 @@ function startMultiAgentStream(event, operation, activeChildParents) {
33935
34504
  ...event.moduleVersion ? { "strands_agent_sdk.version": event.moduleVersion } : {}
33936
34505
  };
33937
34506
  const parentSpan = orchestrator ? getOnlyChildParent(activeChildParents, orchestrator) : void 0;
34507
+ const input = processStrandsInputAttachments(event.arguments[0]);
33938
34508
  const span = parentSpan ? withCurrent(
33939
34509
  parentSpan,
33940
34510
  () => startSpan({
33941
34511
  event: {
33942
- input: event.arguments[0],
34512
+ input,
33943
34513
  metadata
33944
34514
  },
33945
34515
  name: operation === "Graph.stream" ? "Strands Graph" : "Strands Swarm",
@@ -33947,7 +34517,7 @@ function startMultiAgentStream(event, operation, activeChildParents) {
33947
34517
  })
33948
34518
  ) : startSpan({
33949
34519
  event: {
33950
- input: event.arguments[0],
34520
+ input,
33951
34521
  metadata
33952
34522
  },
33953
34523
  name: operation === "Graph.stream" ? "Strands Graph" : "Strands Swarm",
@@ -34056,7 +34626,10 @@ function startModelSpan(state, event) {
34056
34626
  state.span,
34057
34627
  () => startSpan({
34058
34628
  event: {
34059
- input: Array.isArray(event.agent?.messages) ? event.agent.messages : void 0,
34629
+ input: Array.isArray(event.agent?.messages) ? processStrandsInputAttachments(
34630
+ event.agent.messages,
34631
+ state.attachmentCache
34632
+ ) : void 0,
34060
34633
  metadata
34061
34634
  },
34062
34635
  name: formatModelSpanName(model),
@@ -34270,6 +34843,7 @@ function finalizeAgentStream(state, error2, output) {
34270
34843
  ...output !== void 0 ? { output } : {}
34271
34844
  });
34272
34845
  state.span.end();
34846
+ state.attachmentCache.strings.clear();
34273
34847
  }
34274
34848
  function finalizeMultiAgentStream(state, activeChildParents, error2, output) {
34275
34849
  if (state.finalized) {
@@ -34416,6 +34990,166 @@ function extractNodeResultOutput(result) {
34416
34990
  }
34417
34991
  return result;
34418
34992
  }
34993
+ var STRANDS_MEDIA_TYPES = {
34994
+ png: "image/png",
34995
+ jpg: "image/jpeg",
34996
+ jpeg: "image/jpeg",
34997
+ gif: "image/gif",
34998
+ webp: "image/webp",
34999
+ mkv: "video/x-matroska",
35000
+ mov: "video/quicktime",
35001
+ mp4: "video/mp4",
35002
+ webm: "video/webm",
35003
+ flv: "video/x-flv",
35004
+ mpeg: "video/mpeg",
35005
+ mpg: "video/mpeg",
35006
+ wmv: "video/x-ms-wmv",
35007
+ "3gp": "video/3gpp",
35008
+ pdf: "application/pdf",
35009
+ csv: "text/csv",
35010
+ doc: "application/msword",
35011
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
35012
+ xls: "application/vnd.ms-excel",
35013
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
35014
+ html: "text/html",
35015
+ txt: "text/plain",
35016
+ md: "text/markdown",
35017
+ json: "application/json",
35018
+ xml: "application/xml"
35019
+ };
35020
+ function createStrandsAttachmentCache() {
35021
+ return {
35022
+ objects: /* @__PURE__ */ new WeakMap(),
35023
+ strings: new LRUCache({
35024
+ max: MAX_STRANDS_STRING_ATTACHMENT_CACHE_ENTRIES
35025
+ })
35026
+ };
35027
+ }
35028
+ function processStrandsInputAttachments(input, cache = createStrandsAttachmentCache()) {
35029
+ try {
35030
+ return processStrandsInputNode(input, cache);
35031
+ } catch (error2) {
35032
+ logInstrumentationError5("Strands Agent SDK input attachments", error2);
35033
+ return input;
35034
+ }
35035
+ }
35036
+ function processStrandsInputNode(value, cache) {
35037
+ if (value instanceof BaseAttachment) {
35038
+ return value;
35039
+ }
35040
+ if (Array.isArray(value)) {
35041
+ return value.map((child) => processStrandsInputNode(child, cache));
35042
+ }
35043
+ if (!isObject(value)) {
35044
+ return value;
35045
+ }
35046
+ const directMedia = processDirectStrandsMediaBlock(value, cache);
35047
+ if (directMedia !== void 0) {
35048
+ return directMedia;
35049
+ }
35050
+ const wrappedMedia = processWrappedStrandsMediaBlock(value, cache);
35051
+ if (wrappedMedia !== void 0) {
35052
+ return wrappedMedia;
35053
+ }
35054
+ if (value.type === "message" && Array.isArray(value.content)) {
35055
+ return {
35056
+ role: value.role,
35057
+ content: value.content.map(
35058
+ (child) => processStrandsInputNode(child, cache)
35059
+ ),
35060
+ ...value.metadata !== void 0 ? { metadata: value.metadata } : {}
35061
+ };
35062
+ }
35063
+ if (typeof value.toJSON === "function") {
35064
+ return processStrandsInputNode(value.toJSON(), cache);
35065
+ }
35066
+ return Object.fromEntries(
35067
+ Object.entries(value).map(([key, child]) => [
35068
+ key,
35069
+ processStrandsInputNode(child, cache)
35070
+ ])
35071
+ );
35072
+ }
35073
+ function processDirectStrandsMediaBlock(block, cache) {
35074
+ if (!isStrandsMediaBlock(block)) {
35075
+ return void 0;
35076
+ }
35077
+ const mediaKey = block.type === "imageBlock" ? "image" : block.type === "videoBlock" ? "video" : "document";
35078
+ return createStrandsMediaAttachment(mediaKey, block, cache);
35079
+ }
35080
+ function isStrandsMediaBlock(block) {
35081
+ return (block.type === "imageBlock" || block.type === "videoBlock" || block.type === "documentBlock") && typeof block.format === "string" && isObject(block.source);
35082
+ }
35083
+ function processWrappedStrandsMediaBlock(block, cache) {
35084
+ for (const mediaKey of ["image", "video", "document"]) {
35085
+ const media = block[mediaKey];
35086
+ if (!Object.hasOwn(block, mediaKey) || !isObject(media)) {
35087
+ continue;
35088
+ }
35089
+ const processed = createStrandsMediaAttachment(mediaKey, media, cache);
35090
+ if (processed !== void 0) {
35091
+ return processed;
35092
+ }
35093
+ }
35094
+ return void 0;
35095
+ }
35096
+ function createStrandsMediaAttachment(mediaKey, media, cache) {
35097
+ const format = media.format;
35098
+ const source = media.source;
35099
+ if (typeof format !== "string" || !isObject(source) || !Object.hasOwn(source, "bytes")) {
35100
+ return void 0;
35101
+ }
35102
+ const contentType = STRANDS_MEDIA_TYPES[format.toLowerCase()];
35103
+ if (!contentType) {
35104
+ return void 0;
35105
+ }
35106
+ const filename = mediaKey === "document" && typeof media.name === "string" && media.name.length > 0 ? media.name : `${mediaKey}.${format.toLowerCase()}`;
35107
+ const attachment = getOrCreateStrandsAttachment(
35108
+ source.bytes,
35109
+ filename,
35110
+ contentType,
35111
+ cache
35112
+ );
35113
+ if (!attachment) {
35114
+ return void 0;
35115
+ }
35116
+ const { type: _type, ...serializedMedia } = media;
35117
+ const { type: _sourceType, ...serializedSource } = source;
35118
+ return {
35119
+ [mediaKey]: {
35120
+ ...serializedMedia,
35121
+ source: {
35122
+ ...serializedSource,
35123
+ bytes: attachment
35124
+ }
35125
+ }
35126
+ };
35127
+ }
35128
+ function getOrCreateStrandsAttachment(data, filename, contentType, cache) {
35129
+ const key = `${contentType}\0${filename}`;
35130
+ const attachments = typeof data === "string" ? cache.strings.get(data) : isObject(data) ? cache.objects.get(data) : void 0;
35131
+ const cached = attachments?.get(key);
35132
+ if (cached) {
35133
+ return cached;
35134
+ }
35135
+ const blob = convertDataToBlob(data, contentType);
35136
+ if (!blob) {
35137
+ return void 0;
35138
+ }
35139
+ const attachment = new Attachment({
35140
+ data: blob,
35141
+ filename,
35142
+ contentType
35143
+ });
35144
+ const updatedAttachments = attachments ?? /* @__PURE__ */ new Map();
35145
+ updatedAttachments.set(key, attachment);
35146
+ if (typeof data === "string") {
35147
+ cache.strings.set(data, updatedAttachments);
35148
+ } else if (isObject(data)) {
35149
+ cache.objects.set(data, updatedAttachments);
35150
+ }
35151
+ return attachment;
35152
+ }
34419
35153
  function normalizeContentBlocks(blocks) {
34420
35154
  const text = blocks.map((block) => typeof block.text === "string" ? block.text : void 0).filter((part) => Boolean(part)).join("");
34421
35155
  return text.length > 0 ? text : blocks;
@@ -34545,6 +35279,7 @@ var BraintrustPlugin = class extends BasePlugin {
34545
35279
  gitHubCopilotPlugin = null;
34546
35280
  fluePlugin = null;
34547
35281
  langChainPlugin = null;
35282
+ langSmithPlugin = null;
34548
35283
  piCodingAgentPlugin = null;
34549
35284
  strandsAgentSDKPlugin = null;
34550
35285
  constructor(config3 = {}) {
@@ -34641,6 +35376,12 @@ var BraintrustPlugin = class extends BasePlugin {
34641
35376
  this.langChainPlugin = new LangChainPlugin();
34642
35377
  this.langChainPlugin.enable();
34643
35378
  }
35379
+ if (integrations.langsmith !== false) {
35380
+ this.langSmithPlugin = new LangSmithPlugin({
35381
+ skipLangChainRuns: integrations.langchain !== false
35382
+ });
35383
+ this.langSmithPlugin.enable();
35384
+ }
34644
35385
  }
34645
35386
  onDisable() {
34646
35387
  if (this.openaiPlugin) {
@@ -34731,6 +35472,10 @@ var BraintrustPlugin = class extends BasePlugin {
34731
35472
  this.langChainPlugin.disable();
34732
35473
  this.langChainPlugin = null;
34733
35474
  }
35475
+ if (this.langSmithPlugin) {
35476
+ this.langSmithPlugin.disable();
35477
+ this.langSmithPlugin = null;
35478
+ }
34734
35479
  }
34735
35480
  };
34736
35481
 
@@ -34795,7 +35540,8 @@ var envIntegrationAliases = {
34795
35540
  langchain: "langchain",
34796
35541
  "langchain-js": "langchain",
34797
35542
  "@langchain": "langchain",
34798
- langgraph: "langgraph"
35543
+ langgraph: "langgraph",
35544
+ langsmith: "langsmith"
34799
35545
  };
34800
35546
  function getDefaultInstrumentationIntegrations() {
34801
35547
  return {
@@ -34826,6 +35572,7 @@ function getDefaultInstrumentationIntegrations() {
34826
35572
  gitHubCopilot: true,
34827
35573
  langchain: true,
34828
35574
  langgraph: true,
35575
+ langsmith: true,
34829
35576
  piCodingAgent: true,
34830
35577
  strandsAgentSDK: true
34831
35578
  };
@@ -35066,6 +35813,15 @@ function modelMetrics(attributes) {
35066
35813
  }
35067
35814
  return Object.keys(out).length > 0 ? out : void 0;
35068
35815
  }
35816
+ function timeToFirstTokenSeconds(attributes, spanStartSeconds) {
35817
+ if (!isObject(attributes)) return void 0;
35818
+ if (spanStartSeconds === void 0) return void 0;
35819
+ const raw = attributes.completionStartTime;
35820
+ const completionStart = raw instanceof Date || typeof raw === "string" || typeof raw === "number" ? epochSeconds(raw) : void 0;
35821
+ if (completionStart === void 0) return void 0;
35822
+ const ttft = completionStart - spanStartSeconds;
35823
+ return Number.isFinite(ttft) && ttft >= 0 ? ttft : void 0;
35824
+ }
35069
35825
  function buildMetadata(exported) {
35070
35826
  const out = {};
35071
35827
  if (exported.entityId !== void 0) out.entity_id = exported.entityId;
@@ -35135,7 +35891,13 @@ var BraintrustObservabilityExporter = class {
35135
35891
  const args = {
35136
35892
  name: exported.name,
35137
35893
  spanAttributes: { type: spanTypeFor(exported.type) },
35138
- startTime: epochSeconds(exported.startTime)
35894
+ startTime: epochSeconds(exported.startTime),
35895
+ // Use the Mastra span id as the Braintrust row id so that
35896
+ // `logFeedback({ id: <mastra span id> })` (and Mastra's score events)
35897
+ // attach to the right row. Without this, `SpanImpl` auto-generates a
35898
+ // row id (`this._id = eventId ?? idGenerator.getSpanId()`) that no
35899
+ // external caller could know.
35900
+ event: { id: exported.id }
35139
35901
  };
35140
35902
  const parentRecord = exported.parentSpanId ? this.spans.get(exported.parentSpanId) : void 0;
35141
35903
  if (!this.capturedParent) {
@@ -35184,8 +35946,15 @@ var BraintrustObservabilityExporter = class {
35184
35946
  event.metadata = metadata;
35185
35947
  }
35186
35948
  const metrics = modelMetrics(exported.attributes);
35187
- if (metrics) {
35188
- event.metrics = metrics;
35949
+ const ttft = timeToFirstTokenSeconds(
35950
+ exported.attributes,
35951
+ epochSeconds(exported.startTime)
35952
+ );
35953
+ if (metrics || ttft !== void 0) {
35954
+ event.metrics = {
35955
+ ...metrics ?? {},
35956
+ ...ttft !== void 0 ? { time_to_first_token: ttft } : {}
35957
+ };
35189
35958
  }
35190
35959
  if (Object.keys(event).length > 0) {
35191
35960
  record.span.log(event);
@@ -36884,8 +37653,8 @@ function makeCheckAuthorized(allowedOrgName) {
36884
37653
  return next((0, import_http_errors.default)(400, "Missing x-bt-org-name header"));
36885
37654
  }
36886
37655
  if (allowedOrgName && allowedOrgName !== orgName) {
36887
- const errorMessage = `Org '${orgName}' is not allowed. Only org '${allowedOrgName}' is allowed.`;
36888
- return next((0, import_http_errors.default)(403, errorMessage));
37656
+ const errorMessage2 = `Org '${orgName}' is not allowed. Only org '${allowedOrgName}' is allowed.`;
37657
+ return next((0, import_http_errors.default)(403, errorMessage2));
36889
37658
  }
36890
37659
  const state = await cachedLogin({
36891
37660
  apiKey: req.ctx?.token,