braintrust 3.28.0 → 3.30.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 +1 -1
  2. package/dev/dist/index.d.mts +356 -7
  3. package/dev/dist/index.d.ts +356 -7
  4. package/dev/dist/index.js +2679 -1828
  5. package/dev/dist/index.mjs +1862 -1011
  6. package/dist/apply-auto-instrumentation.js +298 -265
  7. package/dist/apply-auto-instrumentation.mjs +85 -52
  8. package/dist/auto-instrumentations/bundler/esbuild.cjs +56 -6
  9. package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
  10. package/dist/auto-instrumentations/bundler/next.cjs +56 -6
  11. package/dist/auto-instrumentations/bundler/next.mjs +3 -3
  12. package/dist/auto-instrumentations/bundler/rollup.cjs +56 -6
  13. package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
  14. package/dist/auto-instrumentations/bundler/vite.cjs +56 -6
  15. package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
  16. package/dist/auto-instrumentations/bundler/webpack-loader.cjs +56 -6
  17. package/dist/auto-instrumentations/bundler/webpack.cjs +56 -6
  18. package/dist/auto-instrumentations/bundler/webpack.mjs +3 -3
  19. package/dist/auto-instrumentations/{chunk-26PKVUKB.mjs → chunk-LW4HDHXT.mjs} +12 -4
  20. package/dist/auto-instrumentations/{chunk-HD35AM3M.mjs → chunk-OXINGIEZ.mjs} +1 -1
  21. package/dist/auto-instrumentations/{chunk-NP7V4XB2.mjs → chunk-U64OYU4Q.mjs} +45 -3
  22. package/dist/auto-instrumentations/hook.mjs +967 -136
  23. package/dist/auto-instrumentations/index.cjs +45 -3
  24. package/dist/auto-instrumentations/index.mjs +1 -1
  25. package/dist/browser.d.mts +893 -24
  26. package/dist/browser.d.ts +893 -24
  27. package/dist/browser.js +2795 -1136
  28. package/dist/browser.mjs +2795 -1136
  29. package/dist/{chunk-BBE7SNRV.js → chunk-2XDW3UCG.js} +155 -23
  30. package/dist/{chunk-UPFNQCGB.mjs → chunk-BU6SM54N.mjs} +1830 -1045
  31. package/dist/{chunk-ZHUHZWFY.mjs → chunk-N3JKQ3D3.mjs} +152 -20
  32. package/dist/{chunk-OBBWQW6K.js → chunk-TWCDSYJN.js} +2890 -2105
  33. package/dist/cli.js +5976 -1348
  34. package/dist/edge-light.d.mts +1 -1
  35. package/dist/edge-light.d.ts +1 -1
  36. package/dist/edge-light.js +2795 -1136
  37. package/dist/edge-light.mjs +2795 -1136
  38. package/dist/index.d.mts +893 -24
  39. package/dist/index.d.ts +893 -24
  40. package/dist/index.js +1448 -699
  41. package/dist/index.mjs +848 -99
  42. package/dist/instrumentation/index.d.mts +563 -9
  43. package/dist/instrumentation/index.d.ts +563 -9
  44. package/dist/instrumentation/index.js +2794 -989
  45. package/dist/instrumentation/index.mjs +2794 -989
  46. package/dist/vitest-evals-reporter.js +16 -16
  47. package/dist/vitest-evals-reporter.mjs +2 -2
  48. package/dist/workerd.d.mts +1 -1
  49. package/dist/workerd.d.ts +1 -1
  50. package/dist/workerd.js +2795 -1136
  51. package/dist/workerd.mjs +2795 -1136
  52. package/package.json +1 -1
  53. package/util/dist/index.d.mts +42 -0
  54. package/util/dist/index.d.ts +42 -0
@@ -653,8 +653,6 @@ function newGlobalTracingChannel(nameOrChannels) {
653
653
  var DefaultAsyncLocalStorage = class {
654
654
  constructor() {
655
655
  }
656
- enterWith(_) {
657
- }
658
656
  run(_, callback) {
659
657
  return callback();
660
658
  }
@@ -2576,7 +2574,9 @@ var AclObjectType = z6.union([
2576
2574
  "project_log",
2577
2575
  "org_project",
2578
2576
  "org_audit_logs",
2579
- "project_group"
2577
+ "project_group",
2578
+ "ai_secret",
2579
+ "org_ai_secret"
2580
2580
  ]),
2581
2581
  z6.null()
2582
2582
  ]);
@@ -3409,7 +3409,7 @@ var PromptParserNullish = z6.union([
3409
3409
  }),
3410
3410
  z6.null()
3411
3411
  ]);
3412
- var PreprocessorSavedFunctionId = z6.union([
3412
+ var PreprocessorId = z6.union([
3413
3413
  z6.object({
3414
3414
  type: z6.literal("function"),
3415
3415
  id: z6.string(),
@@ -3420,6 +3420,7 @@ var PreprocessorSavedFunctionId = z6.union([
3420
3420
  name: z6.string(),
3421
3421
  function_type: z6.literal("preprocessor").optional().default("preprocessor")
3422
3422
  }),
3423
+ z6.object({ type: z6.literal("inline"), code: z6.string().min(1) }),
3423
3424
  z6.null()
3424
3425
  ]);
3425
3426
  var PromptDataNullish = z6.union([
@@ -3427,7 +3428,7 @@ var PromptDataNullish = z6.union([
3427
3428
  prompt: PromptBlockDataNullish,
3428
3429
  options: PromptOptionsNullish,
3429
3430
  parser: PromptParserNullish,
3430
- preprocessor: PreprocessorSavedFunctionId,
3431
+ preprocessor: PreprocessorId,
3431
3432
  tool_functions: z6.union([z6.array(SavedFunctionId), z6.null()]),
3432
3433
  template_format: z6.union([
3433
3434
  z6.enum(["mustache", "nunjucks", "none"]),
@@ -3629,7 +3630,7 @@ var PromptData = z6.object({
3629
3630
  prompt: PromptBlockDataNullish,
3630
3631
  options: PromptOptionsNullish,
3631
3632
  parser: PromptParserNullish,
3632
- preprocessor: PreprocessorSavedFunctionId,
3633
+ preprocessor: PreprocessorId,
3633
3634
  tool_functions: z6.union([z6.array(SavedFunctionId), z6.null()]),
3634
3635
  template_format: z6.union([
3635
3636
  z6.enum(["mustache", "nunjucks", "none"]),
@@ -4573,6 +4574,7 @@ var View = z6.object({
4573
4574
  ]),
4574
4575
  name: z6.string(),
4575
4576
  description: z6.union([z6.string(), z6.null()]).optional(),
4577
+ starred: z6.boolean().optional(),
4576
4578
  created: z6.union([z6.string(), z6.null()]).optional(),
4577
4579
  updated_at: z6.union([z6.string(), z6.null()]).optional(),
4578
4580
  view_data: ViewData.optional(),
@@ -4662,6 +4664,34 @@ var registerTemplatePlugin = templateRegistry.register.bind(templateRegistry);
4662
4664
  var getTemplateRenderer = templateRegistry.get.bind(templateRegistry);
4663
4665
  registerTemplatePlugin(mustachePlugin);
4664
4666
 
4667
+ // src/template/renderer.ts
4668
+ function isTemplateFormat(v) {
4669
+ return v === "mustache" || v === "nunjucks" || v === "none";
4670
+ }
4671
+ function parseTemplateFormat(value, defaultFormat = "mustache") {
4672
+ return isTemplateFormat(value) ? value : defaultFormat;
4673
+ }
4674
+ function renderTemplateContent(template, variables, escape, options) {
4675
+ const strict = !!options.strict;
4676
+ const templateFormat = parseTemplateFormat(options.templateFormat);
4677
+ if (templateFormat === "none") {
4678
+ return template;
4679
+ }
4680
+ const renderer = getTemplateRenderer(templateFormat);
4681
+ if (!renderer) {
4682
+ if (templateFormat === "nunjucks") {
4683
+ throw new Error(
4684
+ "Nunjucks templating requires @braintrust/template-nunjucks. Install and import it to enable templateFormat: 'nunjucks'."
4685
+ );
4686
+ }
4687
+ throw new Error(`No template renderer registered for ${templateFormat}`);
4688
+ }
4689
+ if (strict && renderer.lint) {
4690
+ renderer.lint(template, variables);
4691
+ }
4692
+ return renderer.render(template, variables, escape, strict);
4693
+ }
4694
+
4665
4695
  // src/logger.ts
4666
4696
  import { z as z8, ZodError } from "zod/v3";
4667
4697
 
@@ -5248,44 +5278,72 @@ function createCacheLayers({
5248
5278
  }
5249
5279
 
5250
5280
  // src/prompt-cache/prompt-cache.ts
5251
- function createCacheKey(key) {
5281
+ function createCacheKey(key, namespace) {
5282
+ let cacheKey;
5252
5283
  if (key.id) {
5253
- return `id:${key.id}`;
5254
- }
5255
- const prefix = key.projectId ?? key.projectName;
5256
- if (!prefix) {
5257
- throw new Error("Either projectId or projectName must be provided");
5258
- }
5259
- if (!key.slug) {
5260
- throw new Error("Slug must be provided when not using ID");
5284
+ cacheKey = `id:${key.id}`;
5285
+ } else {
5286
+ const prefix = key.projectId ?? key.projectName;
5287
+ if (!prefix) {
5288
+ throw new Error("Either projectId or projectName must be provided");
5289
+ }
5290
+ if (!key.slug) {
5291
+ throw new Error("Slug must be provided when not using ID");
5292
+ }
5293
+ cacheKey = `${prefix}:${key.slug}:${key.version ?? "latest"}`;
5261
5294
  }
5262
- return `${prefix}:${key.slug}:${key.version ?? "latest"}`;
5295
+ return namespace === void 0 ? cacheKey : `${namespace.length}:${namespace}:${cacheKey}`;
5263
5296
  }
5264
- var PromptCache = class {
5297
+ var PromptCache = class _PromptCache {
5265
5298
  memoryCache;
5266
5299
  diskCache;
5300
+ namespace;
5301
+ expectedResolvedOrgIdentity;
5267
5302
  constructor(options) {
5268
5303
  this.memoryCache = options.memoryCache;
5269
5304
  this.diskCache = options.diskCache;
5305
+ this.namespace = options.namespace;
5306
+ this.expectedResolvedOrgIdentity = options.expectedResolvedOrgIdentity;
5307
+ }
5308
+ /**
5309
+ * Returns a cache view that shares the same storage layers but isolates all
5310
+ * entries under the provided namespace.
5311
+ */
5312
+ withNamespace(namespace, expectedResolvedOrgIdentity) {
5313
+ return new _PromptCache({
5314
+ memoryCache: this.memoryCache,
5315
+ diskCache: this.diskCache,
5316
+ namespace,
5317
+ expectedResolvedOrgIdentity
5318
+ });
5270
5319
  }
5271
5320
  /**
5272
5321
  * Retrieves a prompt from the cache.
5273
5322
  * First checks the in-memory LRU cache, then falls back to checking the disk cache if available.
5274
5323
  */
5275
5324
  async get(key) {
5276
- const cacheKey = createCacheKey(key);
5325
+ const cacheKey = createCacheKey(key, this.namespace);
5277
5326
  if (this.memoryCache) {
5278
- const memoryPrompt = this.memoryCache.get(cacheKey);
5279
- if (memoryPrompt !== void 0) {
5280
- return memoryPrompt;
5327
+ const memoryEntry = this.memoryCache.get(cacheKey);
5328
+ if (memoryEntry !== void 0 && (this.expectedResolvedOrgIdentity === void 0 || memoryEntry.resolvedOrgIdentity === this.expectedResolvedOrgIdentity)) {
5329
+ return memoryEntry.value;
5281
5330
  }
5282
5331
  }
5283
5332
  if (this.diskCache) {
5284
- const diskPrompt = await this.diskCache.get(cacheKey);
5285
- if (!diskPrompt) {
5333
+ const diskEntry = await this.diskCache.get(cacheKey);
5334
+ if (!diskEntry || this.expectedResolvedOrgIdentity !== void 0 && diskEntry.resolvedOrgIdentity !== this.expectedResolvedOrgIdentity) {
5286
5335
  return void 0;
5287
5336
  }
5288
- this.memoryCache?.set(cacheKey, diskPrompt);
5337
+ const serializedPrompt = diskEntry.value;
5338
+ const diskPrompt = new Prompt2(
5339
+ serializedPrompt.metadata,
5340
+ serializedPrompt.defaults,
5341
+ serializedPrompt.noTrace
5342
+ );
5343
+ this.memoryCache?.set(cacheKey, {
5344
+ value: diskPrompt,
5345
+ resolvedOrgIdentity: diskEntry.resolvedOrgIdentity
5346
+ });
5289
5347
  return diskPrompt;
5290
5348
  }
5291
5349
  return void 0;
@@ -5299,58 +5357,91 @@ var PromptCache = class {
5299
5357
  * @throws If there is an error writing to the disk cache.
5300
5358
  */
5301
5359
  async set(key, value) {
5302
- const cacheKey = createCacheKey(key);
5303
- this.memoryCache?.set(cacheKey, value);
5360
+ const cacheKey = createCacheKey(key, this.namespace);
5361
+ const memoryEntry = {
5362
+ value,
5363
+ resolvedOrgIdentity: this.expectedResolvedOrgIdentity
5364
+ };
5365
+ this.memoryCache?.set(cacheKey, memoryEntry);
5304
5366
  if (this.diskCache) {
5305
- await this.diskCache.set(cacheKey, value);
5367
+ await this.diskCache.set(cacheKey, {
5368
+ value: value._internalSerializeForCache(),
5369
+ resolvedOrgIdentity: this.expectedResolvedOrgIdentity
5370
+ });
5306
5371
  }
5307
5372
  }
5308
5373
  };
5309
5374
 
5310
5375
  // src/prompt-cache/parameters-cache.ts
5311
- function createCacheKey2(key) {
5376
+ function createCacheKey2(key, namespace) {
5377
+ let cacheKey;
5312
5378
  if (key.id) {
5313
- return `parameters:id:${key.id}`;
5314
- }
5315
- const prefix = key.projectId ?? key.projectName;
5316
- if (!prefix) {
5317
- throw new Error("Either projectId or projectName must be provided");
5318
- }
5319
- if (!key.slug) {
5320
- throw new Error("Slug must be provided when not using ID");
5379
+ cacheKey = `parameters:id:${key.id}`;
5380
+ } else {
5381
+ const prefix = key.projectId ?? key.projectName;
5382
+ if (!prefix) {
5383
+ throw new Error("Either projectId or projectName must be provided");
5384
+ }
5385
+ if (!key.slug) {
5386
+ throw new Error("Slug must be provided when not using ID");
5387
+ }
5388
+ cacheKey = `parameters:${prefix}:${key.slug}:${key.version ?? "latest"}`;
5321
5389
  }
5322
- return `parameters:${prefix}:${key.slug}:${key.version ?? "latest"}`;
5390
+ return namespace === void 0 ? cacheKey : `${namespace.length}:${namespace}:${cacheKey}`;
5323
5391
  }
5324
- var ParametersCache = class {
5392
+ var ParametersCache = class _ParametersCache {
5325
5393
  memoryCache;
5326
5394
  diskCache;
5395
+ namespace;
5396
+ expectedResolvedOrgIdentity;
5327
5397
  constructor(options) {
5328
5398
  this.memoryCache = options.memoryCache;
5329
5399
  this.diskCache = options.diskCache;
5400
+ this.namespace = options.namespace;
5401
+ this.expectedResolvedOrgIdentity = options.expectedResolvedOrgIdentity;
5402
+ }
5403
+ withNamespace(namespace, expectedResolvedOrgIdentity) {
5404
+ return new _ParametersCache({
5405
+ memoryCache: this.memoryCache,
5406
+ diskCache: this.diskCache,
5407
+ namespace,
5408
+ expectedResolvedOrgIdentity
5409
+ });
5330
5410
  }
5331
5411
  async get(key) {
5332
- const cacheKey = createCacheKey2(key);
5412
+ const cacheKey = createCacheKey2(key, this.namespace);
5333
5413
  if (this.memoryCache) {
5334
- const memoryParams = this.memoryCache.get(cacheKey);
5335
- if (memoryParams !== void 0) {
5336
- return memoryParams;
5414
+ const memoryEntry = this.memoryCache.get(cacheKey);
5415
+ if (memoryEntry !== void 0 && (this.expectedResolvedOrgIdentity === void 0 || memoryEntry.resolvedOrgIdentity === this.expectedResolvedOrgIdentity)) {
5416
+ return memoryEntry.value;
5337
5417
  }
5338
5418
  }
5339
5419
  if (this.diskCache) {
5340
- const diskParams = await this.diskCache.get(cacheKey);
5341
- if (!diskParams) {
5420
+ const diskEntry = await this.diskCache.get(cacheKey);
5421
+ if (!diskEntry || this.expectedResolvedOrgIdentity !== void 0 && diskEntry.resolvedOrgIdentity !== this.expectedResolvedOrgIdentity) {
5342
5422
  return void 0;
5343
5423
  }
5344
- this.memoryCache?.set(cacheKey, diskParams);
5345
- return diskParams;
5424
+ const diskParameters = new RemoteEvalParameters(diskEntry.value.metadata);
5425
+ this.memoryCache?.set(cacheKey, {
5426
+ value: diskParameters,
5427
+ resolvedOrgIdentity: diskEntry.resolvedOrgIdentity
5428
+ });
5429
+ return diskParameters;
5346
5430
  }
5347
5431
  return void 0;
5348
5432
  }
5349
5433
  async set(key, value) {
5350
- const cacheKey = createCacheKey2(key);
5351
- this.memoryCache?.set(cacheKey, value);
5434
+ const cacheKey = createCacheKey2(key, this.namespace);
5435
+ const memoryEntry = {
5436
+ value,
5437
+ resolvedOrgIdentity: this.expectedResolvedOrgIdentity
5438
+ };
5439
+ this.memoryCache?.set(cacheKey, memoryEntry);
5352
5440
  if (this.diskCache) {
5353
- await this.diskCache.set(cacheKey, value);
5441
+ await this.diskCache.set(cacheKey, {
5442
+ value: value._internalSerializeForCache(),
5443
+ resolvedOrgIdentity: this.expectedResolvedOrgIdentity
5444
+ });
5354
5445
  }
5355
5446
  }
5356
5447
  };
@@ -5654,6 +5745,7 @@ var INSTRUMENTATION_NAMES = {
5654
5745
  CLOUDFLARE_THINK: "cloudflare-think",
5655
5746
  COHERE: "cohere",
5656
5747
  CURSOR_SDK: "cursor-sdk",
5748
+ DEEPSEEK_HARNESS: "deepseek-harness",
5657
5749
  EVE: "eve",
5658
5750
  FLUE: "flue",
5659
5751
  GENKIT: "genkit",
@@ -5679,7 +5771,7 @@ var INSTRUMENTATION_NAMES = {
5679
5771
  var INTERNAL_SPAN_INSTRUMENTATION_NAME = /* @__PURE__ */ Symbol.for(
5680
5772
  "braintrust.spanInstrumentationName"
5681
5773
  );
5682
- var SDK_VERSION = true ? "3.28.0" : "0.0.0";
5774
+ var SDK_VERSION = true ? "3.30.0" : "0.0.0";
5683
5775
  function withSpanInstrumentationName(args, instrumentationName) {
5684
5776
  return {
5685
5777
  ...args,
@@ -5798,6 +5890,16 @@ var datasetSnapshotRegisterResponseSchema = z8.object({
5798
5890
  dataset_snapshot: DatasetSnapshot,
5799
5891
  found_existing: z8.boolean().optional()
5800
5892
  });
5893
+ var datasetObjectInfoSchema = z8.object({
5894
+ object_id: z8.string(),
5895
+ object_name: z8.string(),
5896
+ parent_cols: z8.object({
5897
+ project: z8.object({
5898
+ id: z8.string(),
5899
+ name: z8.string()
5900
+ })
5901
+ })
5902
+ });
5801
5903
  var datasetRestorePreviewResultSchema = z8.object({
5802
5904
  rows_to_restore: z8.number(),
5803
5905
  rows_to_delete: z8.number()
@@ -6011,12 +6113,53 @@ var loginSchema = z8.strictObject({
6011
6113
  });
6012
6114
  var stateNonce = 0;
6013
6115
  var V1_PROXY_SUFFIX = "/v1/proxy";
6116
+ var LOADER_LOGIN_CACHE_MAX = 16;
6014
6117
  function normalizeProxyConnUrl(proxyUrl) {
6015
6118
  return proxyUrl.endsWith(V1_PROXY_SUFFIX) ? proxyUrl.slice(0, proxyUrl.length - V1_PROXY_SUFFIX.length) : proxyUrl;
6016
6119
  }
6017
6120
  var BraintrustState = class _BraintrustState {
6121
+ id;
6122
+ currentExperiment;
6123
+ // Note: the value of IsAsyncFlush doesn't really matter here, since we
6124
+ // (safely) dynamically cast it whenever retrieving the logger.
6125
+ currentLogger;
6126
+ currentParent;
6127
+ currentSpan;
6128
+ // Any time we re-log in, we directly update the apiConn inside the logger.
6129
+ // This is preferable to replacing the whole logger, which would create the
6130
+ // possibility of multiple loggers floating around, which may not log in a
6131
+ // deterministic order.
6132
+ _bgLogger;
6133
+ _overrideBgLogger = null;
6134
+ appUrl = null;
6135
+ appPublicUrl = null;
6136
+ loginToken = null;
6137
+ orgId = null;
6138
+ orgName = null;
6139
+ apiUrl = null;
6140
+ proxyUrl = null;
6141
+ loggedIn = false;
6142
+ gitMetadataSettings;
6143
+ debugLogLevel;
6144
+ debugLogLevelConfigured = false;
6145
+ fetch = globalThis.fetch;
6146
+ _appConn = null;
6147
+ _apiConn = null;
6148
+ _proxyConn = null;
6149
+ promptCache;
6150
+ parametersCache;
6151
+ spanCache;
6152
+ _idGenerator = null;
6153
+ _contextManager = null;
6154
+ _otelFlushCallback = null;
6155
+ spanOriginEnvironment;
6156
+ traceContextSigningSecret;
6157
+ loaderLoginCache = /* @__PURE__ */ new WeakMap();
6158
+ loginParams;
6159
+ activeLoginOrgNameSelector;
6018
6160
  constructor(loginParams) {
6019
- this.loginParams = loginParams;
6161
+ this.loginParams = { ...loginParams };
6162
+ this.activeLoginOrgNameSelector = loginParams.orgName ?? isomorph_default.getEnv("BRAINTRUST_ORG_NAME");
6020
6163
  this.id = `${(/* @__PURE__ */ new Date()).toLocaleString()}-${stateNonce++}`;
6021
6164
  this.currentExperiment = void 0;
6022
6165
  this.currentLogger = void 0;
@@ -6053,12 +6196,14 @@ var BraintrustState = class _BraintrustState {
6053
6196
  const {
6054
6197
  memoryCache: parametersMemoryCache,
6055
6198
  diskCache: parametersDiskCache
6056
- } = createCacheLayers({
6057
- memoryMaxEnvVar: "BRAINTRUST_PARAMETERS_CACHE_MEMORY_MAX",
6058
- diskCacheDirEnvVar: "BRAINTRUST_PARAMETERS_CACHE_DIR",
6059
- diskMaxEnvVar: "BRAINTRUST_PARAMETERS_CACHE_DISK_MAX",
6060
- getDefaultDiskCacheDir: () => `${isomorph_default.getEnv("HOME") ?? isomorph_default.homedir()}/.braintrust/parameters_cache`
6061
- });
6199
+ } = createCacheLayers(
6200
+ {
6201
+ memoryMaxEnvVar: "BRAINTRUST_PARAMETERS_CACHE_MEMORY_MAX",
6202
+ diskCacheDirEnvVar: "BRAINTRUST_PARAMETERS_CACHE_DIR",
6203
+ diskMaxEnvVar: "BRAINTRUST_PARAMETERS_CACHE_DISK_MAX",
6204
+ getDefaultDiskCacheDir: () => `${isomorph_default.getEnv("HOME") ?? isomorph_default.homedir()}/.braintrust/parameters_cache`
6205
+ }
6206
+ );
6062
6207
  this.parametersCache = new ParametersCache({
6063
6208
  memoryCache: parametersMemoryCache,
6064
6209
  diskCache: parametersDiskCache
@@ -6067,43 +6212,6 @@ var BraintrustState = class _BraintrustState {
6067
6212
  this.spanOriginEnvironment = detectSpanOriginEnvironment();
6068
6213
  this._internalSetTraceContextSigningSecret(loginParams.apiKey);
6069
6214
  }
6070
- loginParams;
6071
- id;
6072
- currentExperiment;
6073
- // Note: the value of IsAsyncFlush doesn't really matter here, since we
6074
- // (safely) dynamically cast it whenever retrieving the logger.
6075
- currentLogger;
6076
- currentParent;
6077
- currentSpan;
6078
- // Any time we re-log in, we directly update the apiConn inside the logger.
6079
- // This is preferable to replacing the whole logger, which would create the
6080
- // possibility of multiple loggers floating around, which may not log in a
6081
- // deterministic order.
6082
- _bgLogger;
6083
- _overrideBgLogger = null;
6084
- appUrl = null;
6085
- appPublicUrl = null;
6086
- loginToken = null;
6087
- orgId = null;
6088
- orgName = null;
6089
- apiUrl = null;
6090
- proxyUrl = null;
6091
- loggedIn = false;
6092
- gitMetadataSettings;
6093
- debugLogLevel;
6094
- debugLogLevelConfigured = false;
6095
- fetch = globalThis.fetch;
6096
- _appConn = null;
6097
- _apiConn = null;
6098
- _proxyConn = null;
6099
- promptCache;
6100
- parametersCache;
6101
- spanCache;
6102
- _idGenerator = null;
6103
- _contextManager = null;
6104
- _otelFlushCallback = null;
6105
- spanOriginEnvironment;
6106
- traceContextSigningSecret;
6107
6215
  /** @internal */
6108
6216
  _internalSetTraceContextSigningSecret(secret) {
6109
6217
  const normalizedSecret = secret?.trim();
@@ -6128,6 +6236,101 @@ var BraintrustState = class _BraintrustState {
6128
6236
  this._appConn = null;
6129
6237
  this._apiConn = null;
6130
6238
  this._proxyConn = null;
6239
+ this.loaderLoginCache = /* @__PURE__ */ new WeakMap();
6240
+ }
6241
+ /** @internal */
6242
+ async _internalResolveLoaderLoginOptions({
6243
+ apiKey,
6244
+ appUrl,
6245
+ orgName,
6246
+ fetch: fetch2,
6247
+ forceLogin
6248
+ }) {
6249
+ const resolvedAppUrl = appUrl ?? (this.loggedIn ? this.appUrl ?? void 0 : void 0) ?? this.loginParams.appUrl ?? isomorph_default.getEnv("BRAINTRUST_APP_URL") ?? "https://www.braintrust.dev";
6250
+ const resolvedApiKey = apiKey ?? (this.loggedIn ? this.loginToken ?? void 0 : void 0) ?? this.loginParams.apiKey ?? await isomorph_default.getBraintrustApiKey();
6251
+ if (!resolvedApiKey) {
6252
+ throw new Error(
6253
+ "Please specify an api key (e.g. by setting BRAINTRUST_API_KEY)."
6254
+ );
6255
+ }
6256
+ const normalizedApiKey = HTTPConnection.sanitize_token(resolvedApiKey);
6257
+ const usesActiveCredential = this.loggedIn && normalizedApiKey === this.loginToken;
6258
+ const requestedOrgName = orgName ?? (usesActiveCredential ? this.activeLoginOrgNameSelector : void 0) ?? this.loginParams.orgName ?? isomorph_default.getEnv("BRAINTRUST_ORG_NAME");
6259
+ const resolvedOrgName = orgName ?? (usesActiveCredential ? this.orgName ?? void 0 : void 0) ?? requestedOrgName;
6260
+ const resolvedFetch = fetch2 ?? (this.loggedIn ? this.fetch : void 0) ?? this.loginParams.fetch ?? globalThis.fetch;
6261
+ const credentialCacheNamespace = JSON.stringify([
6262
+ "loader-credential",
6263
+ resolvedAppUrl,
6264
+ requestedOrgName,
6265
+ normalizedApiKey
6266
+ ]);
6267
+ return {
6268
+ apiKey: normalizedApiKey,
6269
+ appUrl: resolvedAppUrl,
6270
+ orgName: resolvedOrgName,
6271
+ fetch: resolvedFetch,
6272
+ forceLogin,
6273
+ credentialCacheNamespace,
6274
+ existingState: !forceLogin && usesActiveCredential && resolvedAppUrl === this.appUrl && resolvedOrgName === this.orgName && resolvedFetch === this.fetch ? this : void 0
6275
+ };
6276
+ }
6277
+ /** @internal */
6278
+ _internalGetLoaderCacheViews(loginOptions, requestState) {
6279
+ const expectedResolvedOrgIdentity = requestState?.orgId && requestState.appUrl ? JSON.stringify([
6280
+ "loader-org",
6281
+ requestState.appUrl,
6282
+ requestState.orgId
6283
+ ]) : void 0;
6284
+ return {
6285
+ promptCache: this.promptCache.withNamespace(
6286
+ loginOptions.credentialCacheNamespace,
6287
+ expectedResolvedOrgIdentity
6288
+ ),
6289
+ parametersCache: this.parametersCache.withNamespace(
6290
+ loginOptions.credentialCacheNamespace,
6291
+ expectedResolvedOrgIdentity
6292
+ )
6293
+ };
6294
+ }
6295
+ /** @internal */
6296
+ async _internalGetLoaderState({
6297
+ apiKey,
6298
+ appUrl,
6299
+ orgName,
6300
+ fetch: fetch2,
6301
+ forceLogin,
6302
+ existingState
6303
+ }) {
6304
+ if (existingState) {
6305
+ return existingState;
6306
+ }
6307
+ let cache = this.loaderLoginCache.get(fetch2);
6308
+ if (!cache) {
6309
+ cache = new LRUCache({ max: LOADER_LOGIN_CACHE_MAX });
6310
+ this.loaderLoginCache.set(fetch2, cache);
6311
+ }
6312
+ const cacheKey = JSON.stringify([appUrl, orgName, apiKey]);
6313
+ if (!forceLogin) {
6314
+ const cachedState = cache.get(cacheKey);
6315
+ if (cachedState) {
6316
+ return cachedState;
6317
+ }
6318
+ }
6319
+ const statePromise = loginToLoaderRequestState({
6320
+ orgName,
6321
+ apiKey,
6322
+ appUrl,
6323
+ fetch: fetch2
6324
+ });
6325
+ cache.set(cacheKey, statePromise);
6326
+ try {
6327
+ return await statePromise;
6328
+ } catch (error) {
6329
+ if (cache.get(cacheKey) === statePromise) {
6330
+ cache.delete(cacheKey);
6331
+ }
6332
+ throw error;
6333
+ }
6131
6334
  }
6132
6335
  resetIdGenState() {
6133
6336
  this._idGenerator = null;
@@ -6176,6 +6379,8 @@ var BraintrustState = class _BraintrustState {
6176
6379
  this.debugLogLevel = other.debugLogLevel;
6177
6380
  this.debugLogLevelConfigured = other.debugLogLevelConfigured;
6178
6381
  this.traceContextSigningSecret = other.traceContextSigningSecret;
6382
+ this.fetch = other.fetch;
6383
+ this.activeLoginOrgNameSelector = other.activeLoginOrgNameSelector;
6179
6384
  setGlobalDebugLogLevel(
6180
6385
  this.debugLogLevelConfigured ? this.debugLogLevel ?? false : void 0
6181
6386
  );
@@ -6370,36 +6575,76 @@ var FailedHTTPResponse = class extends Error {
6370
6575
  status;
6371
6576
  text;
6372
6577
  data;
6373
- constructor(status, text, data) {
6578
+ cause;
6579
+ constructor(status, text, data, cause) {
6374
6580
  super(`${status}: ${text} (${data})`);
6375
6581
  this.status = status;
6376
6582
  this.text = text;
6377
6583
  this.data = data;
6584
+ this.cause = cause;
6378
6585
  }
6379
6586
  };
6587
+ var HTTPTransportError = class extends Error {
6588
+ cause;
6589
+ constructor(cause) {
6590
+ super(cause instanceof Error ? cause.message : String(cause));
6591
+ this.name = "HTTPTransportError";
6592
+ this.cause = cause;
6593
+ }
6594
+ };
6595
+ var httpTransportErrorCauses = /* @__PURE__ */ new WeakSet();
6596
+ function recordHTTPTransportError(error) {
6597
+ if (typeof error === "object" && error !== null || typeof error === "function") {
6598
+ httpTransportErrorCauses.add(error);
6599
+ }
6600
+ }
6601
+ function rethrowHTTPTransportError(error, classifyTransportErrors) {
6602
+ if (classifyTransportErrors) {
6603
+ throw new HTTPTransportError(error);
6604
+ }
6605
+ recordHTTPTransportError(error);
6606
+ throw error;
6607
+ }
6608
+ async function readJSONResponse(response, classifyTransportErrors = false) {
6609
+ let data;
6610
+ try {
6611
+ data = await response.text();
6612
+ } catch (error) {
6613
+ rethrowHTTPTransportError(error, classifyTransportErrors);
6614
+ }
6615
+ return JSON.parse(data);
6616
+ }
6380
6617
  async function checkResponse(resp) {
6381
6618
  if (resp.ok) {
6382
6619
  return resp;
6383
- } else {
6620
+ }
6621
+ let data;
6622
+ try {
6623
+ data = await resp.text();
6624
+ } catch (error) {
6384
6625
  throw new FailedHTTPResponse(
6385
6626
  resp.status,
6386
6627
  resp.statusText,
6387
- await resp.text()
6628
+ "Unable to read response body",
6629
+ error
6388
6630
  );
6389
6631
  }
6632
+ throw new FailedHTTPResponse(resp.status, resp.statusText, data);
6390
6633
  }
6391
6634
  var HTTPConnection = class _HTTPConnection {
6392
- base_url;
6393
- token;
6394
- headers;
6395
- fetch;
6396
- constructor(base_url, fetch2) {
6635
+ constructor(base_url, fetch2, classifyTransportErrors = false) {
6636
+ this.classifyTransportErrors = classifyTransportErrors;
6397
6637
  this.base_url = base_url;
6398
6638
  this.token = null;
6399
6639
  this.headers = {};
6400
6640
  this._reset();
6401
6641
  this.fetch = fetch2;
6402
6642
  }
6643
+ classifyTransportErrors;
6644
+ base_url;
6645
+ token;
6646
+ headers;
6647
+ fetch;
6403
6648
  setFetch(fetch2) {
6404
6649
  this.fetch = fetch2;
6405
6650
  }
@@ -6439,9 +6684,9 @@ var HTTPConnection = class _HTTPConnection {
6439
6684
  ).toString();
6440
6685
  const this_fetch = this.fetch;
6441
6686
  const this_headers = this.headers;
6442
- return await checkResponse(
6443
- // Using toString() here makes it work with isomorphic fetch
6444
- await this_fetch(url.toString(), {
6687
+ let response;
6688
+ try {
6689
+ response = await this_fetch(url.toString(), {
6445
6690
  headers: {
6446
6691
  Accept: "application/json",
6447
6692
  ...this_headers,
@@ -6449,8 +6694,14 @@ var HTTPConnection = class _HTTPConnection {
6449
6694
  },
6450
6695
  keepalive: true,
6451
6696
  ...rest
6452
- })
6453
- );
6697
+ });
6698
+ } catch (error) {
6699
+ if (config?.signal?.aborted) {
6700
+ throw getAbortReason(config.signal);
6701
+ }
6702
+ rethrowHTTPTransportError(error, this.classifyTransportErrors);
6703
+ }
6704
+ return await checkResponse(response);
6454
6705
  }
6455
6706
  async post(path, params, config, retries = 0) {
6456
6707
  const { headers, ...rest } = config || {};
@@ -6460,8 +6711,9 @@ var HTTPConnection = class _HTTPConnection {
6460
6711
  const tries = retries + 1;
6461
6712
  for (let i = 0; i < tries; i++) {
6462
6713
  try {
6463
- return await checkResponse(
6464
- await this_fetch(_urljoin(this_base_url, path), {
6714
+ let response;
6715
+ try {
6716
+ response = await this_fetch(_urljoin(this_base_url, path), {
6465
6717
  method: "POST",
6466
6718
  headers: {
6467
6719
  Accept: "application/json",
@@ -6472,8 +6724,14 @@ var HTTPConnection = class _HTTPConnection {
6472
6724
  body: typeof params === "string" ? params : params ? JSON.stringify(params) : void 0,
6473
6725
  keepalive: true,
6474
6726
  ...rest
6475
- })
6476
- );
6727
+ });
6728
+ } catch (error) {
6729
+ if (config?.signal?.aborted) {
6730
+ throw getAbortReason(config.signal);
6731
+ }
6732
+ rethrowHTTPTransportError(error, this.classifyTransportErrors);
6733
+ }
6734
+ return await checkResponse(response);
6477
6735
  } catch (error) {
6478
6736
  if (config?.signal?.aborted) {
6479
6737
  throw getAbortReason(config.signal);
@@ -6498,7 +6756,7 @@ var HTTPConnection = class _HTTPConnection {
6498
6756
  for (let i = 0; i < tries; i++) {
6499
6757
  try {
6500
6758
  const resp = await this.get(`${object_type}`, args);
6501
- return await resp.json();
6759
+ return await readJSONResponse(resp, this.classifyTransportErrors);
6502
6760
  } catch (e) {
6503
6761
  if (i < tries - 1) {
6504
6762
  debugLogger.debug(
@@ -6521,7 +6779,7 @@ var HTTPConnection = class _HTTPConnection {
6521
6779
  const resp = await this.post(`${object_type}`, args, {
6522
6780
  headers: { "Content-Type": "application/json" }
6523
6781
  });
6524
- return await resp.json();
6782
+ return await readJSONResponse(resp, this.classifyTransportErrors);
6525
6783
  }
6526
6784
  // Custom inspect for Node.js console.log
6527
6785
  [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
@@ -8076,6 +8334,52 @@ function initLogger(options = {}) {
8076
8334
  }
8077
8335
  return ret;
8078
8336
  }
8337
+ async function loginToLoaderRequestState({
8338
+ appUrl,
8339
+ apiKey,
8340
+ orgName,
8341
+ fetch: fetch2
8342
+ }) {
8343
+ let orgId;
8344
+ let apiUrl;
8345
+ if (apiKey === TEST_API_KEY) {
8346
+ orgId = "test-org-id";
8347
+ apiUrl = "https://braintrust.dev/fake-api-url";
8348
+ } else {
8349
+ let loginResponse;
8350
+ try {
8351
+ loginResponse = await fetch2(_urljoin(appUrl, `/api/apikey/login`), {
8352
+ method: "POST",
8353
+ headers: {
8354
+ "Content-Type": "application/json",
8355
+ Authorization: `Bearer ${apiKey}`
8356
+ }
8357
+ });
8358
+ } catch (error) {
8359
+ throw new HTTPTransportError(error);
8360
+ }
8361
+ const info = await readJSONResponse(
8362
+ await checkResponse(loginResponse),
8363
+ true
8364
+ );
8365
+ const org = selectLoginOrg(info.org_info, orgName);
8366
+ orgId = org.id;
8367
+ apiUrl = isomorph_default.getEnv("BRAINTRUST_API_URL") ?? org.api_url;
8368
+ if (!apiUrl) {
8369
+ throw new Error(
8370
+ orgName ? `Unable to log into organization '${orgName}'. Are you sure this credential is scoped to the organization?` : "Unable to log into any organization with the provided credential."
8371
+ );
8372
+ }
8373
+ }
8374
+ const apiConnection = new HTTPConnection(apiUrl, fetch2, true);
8375
+ apiConnection.set_token(apiKey);
8376
+ apiConnection.make_long_lived();
8377
+ return {
8378
+ appUrl,
8379
+ orgId,
8380
+ apiConn: () => apiConnection
8381
+ };
8382
+ }
8079
8383
  async function loginToState(options = {}) {
8080
8384
  const {
8081
8385
  appUrl = isomorph_default.getEnv("BRAINTRUST_APP_URL") || "https://www.braintrust.dev",
@@ -8107,16 +8411,18 @@ async function loginToState(options = {}) {
8107
8411
  _saveOrgInfo(state, testOrgInfo, testOrgInfo[0].name);
8108
8412
  return state;
8109
8413
  } else {
8110
- const resp = await checkResponse(
8111
- await fetch2(_urljoin(state.appUrl, `/api/apikey/login`), {
8414
+ const loginResponse = await fetch2(
8415
+ _urljoin(state.appUrl, `/api/apikey/login`),
8416
+ {
8112
8417
  method: "POST",
8113
8418
  headers: {
8114
8419
  "Content-Type": "application/json",
8115
8420
  Authorization: `Bearer ${apiKey}`
8116
8421
  }
8117
- })
8422
+ }
8118
8423
  );
8119
- const info = await resp.json();
8424
+ const resp = await checkResponse(loginResponse);
8425
+ const info = await readJSONResponse(resp);
8120
8426
  _saveOrgInfo(state, info.org_info, orgName);
8121
8427
  if (!state.apiUrl) {
8122
8428
  if (orgName) {
@@ -8181,6 +8487,9 @@ function getSpanParentObjectAndPropagatedState(options) {
8181
8487
  }
8182
8488
  return { parentObject: NOOP_SPAN, propagatedState: void 0 };
8183
8489
  }
8490
+ function getSpanParentObject(options) {
8491
+ return getSpanParentObjectAndPropagatedState(options).parentObject;
8492
+ }
8184
8493
  function currentBraintrustParent(state) {
8185
8494
  const resolvedState = state ?? _globalState;
8186
8495
  const experiment = currentExperiment({ state: resolvedState });
@@ -8427,6 +8736,15 @@ function _internalStartSpanWithInitialMerge(args) {
8427
8736
  [INITIAL_SPAN_WRITE_AS_MERGE]: true
8428
8737
  }).span;
8429
8738
  }
8739
+ function _internalStartSpanWithInitialMergeAndParentSpanIds(args) {
8740
+ return startSpanAndIsLogger(
8741
+ {
8742
+ ...args,
8743
+ [INITIAL_SPAN_WRITE_AS_MERGE]: true
8744
+ },
8745
+ { useParentSpanIdsForObjectParent: true }
8746
+ ).span;
8747
+ }
8430
8748
  function _internalStartSpanWithContext(args, context) {
8431
8749
  return startSpanAndIsLogger({
8432
8750
  ...args,
@@ -8437,7 +8755,7 @@ async function flush(options) {
8437
8755
  const state = options?.state ?? _globalState;
8438
8756
  return await state.bgLogger().flush();
8439
8757
  }
8440
- function startSpanAndIsLogger(args) {
8758
+ function startSpanAndIsLogger(args, internalOptions) {
8441
8759
  const state = args?.state ?? _globalState;
8442
8760
  const { parentObject, propagatedState } = getSpanParentObjectAndPropagatedState({
8443
8761
  asyncFlush: args?.asyncFlush,
@@ -8451,7 +8769,7 @@ function startSpanAndIsLogger(args) {
8451
8769
  ) ? {
8452
8770
  spanId: parentObject.data.span_id,
8453
8771
  rootSpanId: parentObject.data.root_span_id
8454
- } : void 0;
8772
+ } : internalOptions?.useParentSpanIdsForObjectParent ? args?.parentSpanIds : void 0;
8455
8773
  const { parent: _ignoredParent, ...spanArgs } = args ?? {};
8456
8774
  const span = new SpanImpl({
8457
8775
  state,
@@ -8484,27 +8802,28 @@ function withCurrent(span, callback, state = void 0) {
8484
8802
  const currentState = state ?? _globalState;
8485
8803
  return currentState.contextManager.runInContext(span, () => callback(span));
8486
8804
  }
8487
- function _saveOrgInfo(state, org_info, org_name) {
8488
- if (org_info.length === 0) {
8805
+ function _saveOrgInfo(state, orgInfo, orgName) {
8806
+ const org = selectLoginOrg(orgInfo, orgName);
8807
+ state.orgId = org.id;
8808
+ state.orgName = org.name;
8809
+ state.apiUrl = isomorph_default.getEnv("BRAINTRUST_API_URL") ?? org.api_url;
8810
+ state.proxyUrl = isomorph_default.getEnv("BRAINTRUST_PROXY_URL") ?? org.proxy_url;
8811
+ state.gitMetadataSettings = org.git_metadata || void 0;
8812
+ }
8813
+ function selectLoginOrg(orgInfo, orgName) {
8814
+ if (orgInfo.length === 0) {
8489
8815
  throw new LoginInvalidOrgError(
8490
8816
  "This user is not part of any organizations."
8491
8817
  );
8492
8818
  }
8493
- for (const org of org_info) {
8494
- if (org_name === void 0 || org.name === org_name) {
8495
- state.orgId = org.id;
8496
- state.orgName = org.name;
8497
- state.apiUrl = isomorph_default.getEnv("BRAINTRUST_API_URL") ?? org.api_url;
8498
- state.proxyUrl = isomorph_default.getEnv("BRAINTRUST_PROXY_URL") ?? org.proxy_url;
8499
- state.gitMetadataSettings = org.git_metadata || void 0;
8500
- break;
8819
+ for (const org of orgInfo) {
8820
+ if (orgName === void 0 || org.name === orgName) {
8821
+ return org;
8501
8822
  }
8502
8823
  }
8503
- if (state.orgId === void 0) {
8504
- throw new LoginInvalidOrgError(
8505
- `Organization ${org_name} not found. Must be one of ${org_info.map((x) => x.name).join(", ")}`
8506
- );
8507
- }
8824
+ throw new LoginInvalidOrgError(
8825
+ `Organization ${orgName} not found. Must be one of ${orgInfo.map((org) => org.name).join(", ")}`
8826
+ );
8508
8827
  }
8509
8828
  function validateTags(tags) {
8510
8829
  const seen = /* @__PURE__ */ new Set();
@@ -8669,6 +8988,19 @@ function enrichAttachments(event, state) {
8669
8988
  }
8670
8989
  return event;
8671
8990
  }
8991
+ async function resolveAttachmentsToBase64(event, state) {
8992
+ for (const [key, value] of Object.entries(event)) {
8993
+ if (value instanceof ReadonlyAttachment) {
8994
+ event[key] = await value.asBase64Url();
8995
+ continue;
8996
+ }
8997
+ if (!(value instanceof Object)) {
8998
+ continue;
8999
+ }
9000
+ await resolveAttachmentsToBase64(value, state);
9001
+ }
9002
+ return event;
9003
+ }
8672
9004
  function validateAndSanitizeExperimentLogFullArgs(event, hasDataset) {
8673
9005
  if ("input" in event && !isEmpty2(event.input) && "inputs" in event && !isEmpty2(event.inputs) || !("input" in event) && !("inputs" in event)) {
8674
9006
  throw new Error(
@@ -9958,6 +10290,473 @@ var Dataset2 = class extends ObjectFetcher {
9958
10290
  return typeof data === "object" && data !== null && "__braintrust_dataset_marker" in data;
9959
10291
  }
9960
10292
  };
10293
+ function isAttachmentObject(value) {
10294
+ return BraintrustAttachmentReference.safeParse(value).success || InlineAttachmentReferenceSchema.safeParse(value).success || ExternalAttachmentReference.safeParse(value).success;
10295
+ }
10296
+ function isURL(url) {
10297
+ try {
10298
+ const parsedUrl = new URL(url.trim());
10299
+ return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
10300
+ } catch {
10301
+ return false;
10302
+ }
10303
+ }
10304
+ function expandAttachmentArrayPreTemplate(content, variables) {
10305
+ if (typeof content !== "string") return null;
10306
+ const match = content.match(/^\{\{\s*([\w.]+)\s*\}\}$/);
10307
+ if (!match) return null;
10308
+ const varPath = match[1];
10309
+ const value = varPath.includes(".") ? getObjValueByPath(variables, varPath.split(".")) : variables[varPath];
10310
+ if (!Array.isArray(value)) return null;
10311
+ const allValid = value.every(
10312
+ (v) => isAttachmentObject(v) || typeof v === "string" && isURL(v)
10313
+ );
10314
+ if (!allValid) return null;
10315
+ return value.map((item) => ({
10316
+ type: "image_url",
10317
+ image_url: { url: item }
10318
+ }));
10319
+ }
10320
+ function renderMessageImpl(render, message, variables) {
10321
+ return {
10322
+ ...message,
10323
+ ..."content" in message ? {
10324
+ content: isEmpty2(message.content) ? void 0 : typeof message.content === "string" ? render(message.content) : message.content.flatMap((c) => {
10325
+ switch (c.type) {
10326
+ case "text":
10327
+ return [{ ...c, text: render(c.text) }];
10328
+ case "image_url":
10329
+ if (isObject(c.image_url.url)) {
10330
+ throw new Error(
10331
+ "Attachments must be replaced with URLs before calling `build()`"
10332
+ );
10333
+ }
10334
+ if (variables) {
10335
+ const expanded = expandAttachmentArrayPreTemplate(
10336
+ c.image_url.url,
10337
+ variables
10338
+ );
10339
+ if (expanded) {
10340
+ return expanded;
10341
+ }
10342
+ }
10343
+ return [
10344
+ {
10345
+ ...c,
10346
+ image_url: {
10347
+ ...c.image_url,
10348
+ url: render(c.image_url.url)
10349
+ }
10350
+ }
10351
+ ];
10352
+ case "file":
10353
+ return [
10354
+ {
10355
+ ...c,
10356
+ file: {
10357
+ ...c.file.file_data && {
10358
+ file_data: render(c.file.file_data)
10359
+ },
10360
+ ...c.file.file_id && {
10361
+ file_id: render(c.file.file_id)
10362
+ },
10363
+ ...c.file.filename && {
10364
+ filename: render(c.file.filename)
10365
+ }
10366
+ }
10367
+ }
10368
+ ];
10369
+ default:
10370
+ const _exhaustiveCheck = c;
10371
+ return _exhaustiveCheck;
10372
+ }
10373
+ })
10374
+ } : {},
10375
+ ..."tool_calls" in message ? {
10376
+ tool_calls: isEmpty2(message.tool_calls) ? void 0 : message.tool_calls.map((t) => {
10377
+ return {
10378
+ type: t.type,
10379
+ id: render(t.id),
10380
+ function: {
10381
+ name: render(t.function.name),
10382
+ arguments: render(t.function.arguments)
10383
+ }
10384
+ };
10385
+ })
10386
+ } : {},
10387
+ ..."tool_call_id" in message ? {
10388
+ tool_call_id: render(message.tool_call_id)
10389
+ } : {}
10390
+ };
10391
+ }
10392
+ function deserializePlainStringAsJSON(s) {
10393
+ if (s.trim() === "") {
10394
+ return { value: null, error: void 0 };
10395
+ }
10396
+ try {
10397
+ return { value: JSON.parse(s), error: void 0 };
10398
+ } catch (e) {
10399
+ return { value: s, error: e };
10400
+ }
10401
+ }
10402
+ function renderTemplatedObject(obj, args, options) {
10403
+ if (typeof obj === "string") {
10404
+ return renderTemplateContent(
10405
+ obj,
10406
+ args,
10407
+ (value) => typeof value === "string" ? value : JSON.stringify(value),
10408
+ {
10409
+ strict: options.strict,
10410
+ templateFormat: options.templateFormat
10411
+ }
10412
+ );
10413
+ } else if (isArray(obj)) {
10414
+ return obj.map((item) => renderTemplatedObject(item, args, options));
10415
+ } else if (isObject(obj)) {
10416
+ return Object.fromEntries(
10417
+ Object.entries(obj).map(([key, value]) => [
10418
+ key,
10419
+ renderTemplatedObject(value, args, options)
10420
+ ])
10421
+ );
10422
+ }
10423
+ return obj;
10424
+ }
10425
+ function renderPromptParams(params, args, options = {}) {
10426
+ const templateFormat = parseTemplateFormat(options.templateFormat);
10427
+ const strict = !!options.strict;
10428
+ const schemaParsed = z8.object({
10429
+ response_format: z8.object({
10430
+ type: z8.literal("json_schema"),
10431
+ json_schema: ResponseFormatJsonSchema.omit({ schema: true }).extend({
10432
+ schema: z8.unknown()
10433
+ })
10434
+ })
10435
+ }).safeParse(params);
10436
+ if (schemaParsed.success) {
10437
+ const rawSchema = schemaParsed.data.response_format.json_schema.schema;
10438
+ const templatedSchema = renderTemplatedObject(rawSchema, args, {
10439
+ strict,
10440
+ templateFormat
10441
+ });
10442
+ const parsedSchema = typeof templatedSchema === "string" ? deserializePlainStringAsJSON(templatedSchema).value : templatedSchema;
10443
+ return {
10444
+ ...params,
10445
+ response_format: {
10446
+ ...schemaParsed.data.response_format,
10447
+ json_schema: {
10448
+ ...schemaParsed.data.response_format.json_schema,
10449
+ schema: parsedSchema
10450
+ }
10451
+ }
10452
+ };
10453
+ }
10454
+ return params;
10455
+ }
10456
+ var Prompt2 = class _Prompt {
10457
+ constructor(metadata, defaults, noTrace) {
10458
+ this.metadata = metadata;
10459
+ this.defaults = defaults;
10460
+ this.noTrace = noTrace;
10461
+ void this.__braintrust_prompt_marker;
10462
+ }
10463
+ metadata;
10464
+ defaults;
10465
+ noTrace;
10466
+ parsedPromptData;
10467
+ hasParsedPromptData = false;
10468
+ __braintrust_prompt_marker = true;
10469
+ get id() {
10470
+ return this.metadata.id;
10471
+ }
10472
+ get projectId() {
10473
+ return this.metadata.project_id;
10474
+ }
10475
+ get name() {
10476
+ return "name" in this.metadata ? this.metadata.name : `Playground function ${this.metadata.id}`;
10477
+ }
10478
+ get slug() {
10479
+ return "slug" in this.metadata ? this.metadata.slug : this.metadata.id;
10480
+ }
10481
+ get prompt() {
10482
+ return this.getParsedPromptData()?.prompt;
10483
+ }
10484
+ get version() {
10485
+ return this.metadata[TRANSACTION_ID_FIELD];
10486
+ }
10487
+ get options() {
10488
+ return this.getParsedPromptData()?.options || {};
10489
+ }
10490
+ get templateFormat() {
10491
+ return this.getParsedPromptData()?.template_format;
10492
+ }
10493
+ get promptData() {
10494
+ return this.getParsedPromptData();
10495
+ }
10496
+ /**
10497
+ * Build the prompt with the given formatting options. The args you pass in will
10498
+ * be forwarded to the mustache template that defines the prompt and rendered with
10499
+ * the `mustache-js` library.
10500
+ *
10501
+ * @param buildArgs Args to forward along to the prompt template.
10502
+ */
10503
+ build(buildArgs, options = {}) {
10504
+ return this.runBuild(buildArgs, {
10505
+ flavor: options.flavor ?? "chat",
10506
+ messages: options.messages,
10507
+ strict: options.strict,
10508
+ templateFormat: options.templateFormat
10509
+ });
10510
+ }
10511
+ /**
10512
+ * This is a special build method that first resolves attachment references, and then
10513
+ * calls the regular build method. You should use this if you are building prompts from
10514
+ * dataset rows that contain attachments.
10515
+ *
10516
+ * @param buildArgs Args to forward along to the prompt template.
10517
+ */
10518
+ async buildWithAttachments(buildArgs, options = {}) {
10519
+ const hydrated = buildArgs instanceof Object ? await resolveAttachmentsToBase64(buildArgs, options.state) : buildArgs;
10520
+ return this.runBuild(hydrated, {
10521
+ flavor: options.flavor ?? "chat",
10522
+ messages: options.messages,
10523
+ strict: options.strict,
10524
+ templateFormat: options.templateFormat
10525
+ });
10526
+ }
10527
+ runBuild(buildArgs, options) {
10528
+ const { flavor } = options;
10529
+ const params = Object.fromEntries(
10530
+ Object.entries({
10531
+ ...this.defaults,
10532
+ ...Object.fromEntries(
10533
+ Object.entries(this.options.params || {}).filter(
10534
+ ([k, _v]) => !BRAINTRUST_PARAMS.includes(k)
10535
+ )
10536
+ ),
10537
+ ...!isEmpty2(this.options.model) ? {
10538
+ model: this.options.model
10539
+ } : {}
10540
+ }).filter(([key, value]) => key !== "response_format" || value !== null)
10541
+ );
10542
+ if (!("model" in params) || isEmpty2(params.model)) {
10543
+ throw new Error(
10544
+ "No model specified. Either specify it in the prompt or as a default"
10545
+ );
10546
+ }
10547
+ const spanInfo = this.noTrace ? {} : {
10548
+ span_info: {
10549
+ metadata: {
10550
+ prompt: this.id ? {
10551
+ variables: buildArgs,
10552
+ id: this.id,
10553
+ project_id: this.projectId,
10554
+ version: this.version,
10555
+ ..."prompt_session_id" in this.metadata ? { prompt_session_id: this.metadata.prompt_session_id } : {}
10556
+ } : void 0
10557
+ }
10558
+ }
10559
+ };
10560
+ const prompt = this.prompt;
10561
+ if (!prompt) {
10562
+ throw new Error("Empty prompt");
10563
+ }
10564
+ const dictArgParsed = z8.record(z8.unknown()).safeParse(buildArgs);
10565
+ const variables = {
10566
+ input: buildArgs,
10567
+ ...dictArgParsed.success ? dictArgParsed.data : {}
10568
+ };
10569
+ const promptDataTemplateFormat = this.templateFormat;
10570
+ const resolvedTemplateFormat = parseTemplateFormat(
10571
+ options.templateFormat ?? promptDataTemplateFormat
10572
+ );
10573
+ const renderedPrompt = _Prompt.renderPrompt({
10574
+ prompt,
10575
+ buildArgs,
10576
+ options: { ...options, templateFormat: resolvedTemplateFormat }
10577
+ });
10578
+ if (flavor === "chat") {
10579
+ if (renderedPrompt.type !== "chat") {
10580
+ throw new Error(
10581
+ "Prompt is a completion prompt. Use buildCompletion() instead"
10582
+ );
10583
+ }
10584
+ return {
10585
+ ...renderPromptParams(params, variables, {
10586
+ strict: options.strict,
10587
+ templateFormat: resolvedTemplateFormat
10588
+ }),
10589
+ ...spanInfo,
10590
+ messages: renderedPrompt.messages,
10591
+ ...renderedPrompt.tools ? {
10592
+ tools: ChatCompletionTool.array().parse(JSON.parse(renderedPrompt.tools))
10593
+ } : void 0
10594
+ };
10595
+ } else if (flavor === "completion") {
10596
+ if (renderedPrompt.type !== "completion") {
10597
+ throw new Error(`Prompt is a chat prompt. Use flavor: 'chat' instead`);
10598
+ }
10599
+ return {
10600
+ ...renderPromptParams(params, variables, {
10601
+ strict: options.strict,
10602
+ templateFormat: resolvedTemplateFormat
10603
+ }),
10604
+ ...spanInfo,
10605
+ prompt: renderedPrompt.content
10606
+ };
10607
+ } else {
10608
+ throw new Error("never!");
10609
+ }
10610
+ }
10611
+ static renderPrompt({
10612
+ prompt,
10613
+ buildArgs,
10614
+ options
10615
+ }) {
10616
+ const escape = (v) => {
10617
+ if (v === void 0) {
10618
+ throw new Error("Missing!");
10619
+ } else if (typeof v === "string") {
10620
+ return v;
10621
+ } else if (v instanceof ReadonlyAttachment) {
10622
+ throw new Error(
10623
+ "Use buildWithAttachments() to build prompts with attachments"
10624
+ );
10625
+ } else {
10626
+ return JSON.stringify(v);
10627
+ }
10628
+ };
10629
+ const dictArgParsed = z8.record(z8.unknown()).safeParse(buildArgs);
10630
+ const variables = {
10631
+ input: buildArgs,
10632
+ ...dictArgParsed.success ? dictArgParsed.data : {}
10633
+ };
10634
+ const templateFormat = parseTemplateFormat(options.templateFormat);
10635
+ if (prompt.type === "chat") {
10636
+ const render = (template) => renderTemplateContent(template, variables, escape, {
10637
+ strict: options.strict,
10638
+ templateFormat
10639
+ });
10640
+ const baseMessages = (prompt.messages || []).map(
10641
+ (m) => renderMessageImpl(render, m, variables)
10642
+ );
10643
+ const hasSystemPrompt = baseMessages.some((m) => m.role === "system");
10644
+ const messages = [
10645
+ ...baseMessages,
10646
+ ...(options.messages ?? []).filter(
10647
+ (m) => !(hasSystemPrompt && m.role === "system")
10648
+ )
10649
+ ];
10650
+ return {
10651
+ type: "chat",
10652
+ messages,
10653
+ ...prompt.tools?.trim() ? {
10654
+ tools: render(prompt.tools)
10655
+ } : void 0
10656
+ };
10657
+ } else if (prompt.type === "completion") {
10658
+ if (options.messages) {
10659
+ throw new Error(
10660
+ "extra messages are not supported for completion prompts"
10661
+ );
10662
+ }
10663
+ const content = renderTemplateContent(prompt.content, variables, escape, {
10664
+ strict: options.strict,
10665
+ templateFormat
10666
+ });
10667
+ return {
10668
+ type: "completion",
10669
+ content
10670
+ };
10671
+ } else {
10672
+ const _ = prompt;
10673
+ throw new Error(`Invalid prompt type: ${_}`);
10674
+ }
10675
+ }
10676
+ getParsedPromptData() {
10677
+ if (!this.hasParsedPromptData) {
10678
+ this.parsedPromptData = PromptData.parse(this.metadata.prompt_data);
10679
+ this.hasParsedPromptData = true;
10680
+ }
10681
+ return this.parsedPromptData;
10682
+ }
10683
+ static isPrompt(data) {
10684
+ return typeof data === "object" && data !== null && "__braintrust_prompt_marker" in data;
10685
+ }
10686
+ /** @internal */
10687
+ _internalSerializeForCache() {
10688
+ return {
10689
+ metadata: this.metadata,
10690
+ defaults: this.defaults,
10691
+ noTrace: this.noTrace
10692
+ };
10693
+ }
10694
+ static fromPromptData(name, promptData) {
10695
+ return new _Prompt(
10696
+ {
10697
+ name,
10698
+ slug: name,
10699
+ prompt_data: promptData
10700
+ },
10701
+ {},
10702
+ false
10703
+ );
10704
+ }
10705
+ };
10706
+ var RemoteEvalParameters = class {
10707
+ constructor(metadata) {
10708
+ this.metadata = metadata;
10709
+ void this.__braintrust_parameters_marker;
10710
+ }
10711
+ metadata;
10712
+ __braintrust_parameters_marker = true;
10713
+ get id() {
10714
+ return this.metadata.id;
10715
+ }
10716
+ get projectId() {
10717
+ return this.metadata.project_id;
10718
+ }
10719
+ get name() {
10720
+ return this.metadata.name;
10721
+ }
10722
+ get slug() {
10723
+ return this.metadata.slug;
10724
+ }
10725
+ get version() {
10726
+ return this.metadata[TRANSACTION_ID_FIELD];
10727
+ }
10728
+ get schema() {
10729
+ return this.metadata.function_data.__schema;
10730
+ }
10731
+ get data() {
10732
+ return this.metadata.function_data.data ?? {};
10733
+ }
10734
+ /** @internal */
10735
+ _internalSerializeForCache() {
10736
+ return { metadata: this.metadata };
10737
+ }
10738
+ validate(data) {
10739
+ if (typeof data !== "object" || data === null) {
10740
+ return false;
10741
+ }
10742
+ const schemaProps = this.schema.properties;
10743
+ if (typeof schemaProps !== "object" || schemaProps === null) {
10744
+ return true;
10745
+ }
10746
+ for (const key of Object.keys(schemaProps)) {
10747
+ if (!(key in data)) {
10748
+ const required = Array.isArray(this.schema.required) ? this.schema.required : [];
10749
+ if (required.includes(key)) {
10750
+ return false;
10751
+ }
10752
+ }
10753
+ }
10754
+ return true;
10755
+ }
10756
+ static isParameters(x) {
10757
+ return typeof x === "object" && x !== null && "__braintrust_parameters_marker" in x && x.__braintrust_parameters_marker === true;
10758
+ }
10759
+ };
9961
10760
  var TEST_API_KEY = "___TEST_API_KEY__THIS_IS_NOT_REAL___";
9962
10761
 
9963
10762
  // src/instrumentation/core/channel-tracing-utils.ts
@@ -10403,56 +11202,14 @@ function suppressionStore() {
10403
11202
  autoInstrumentationSuppressionStore ??= isomorph_default.newAsyncLocalStorage();
10404
11203
  return autoInstrumentationSuppressionStore;
10405
11204
  }
10406
- function currentFrames() {
10407
- return suppressionStore().getStore()?.frames ?? [];
10408
- }
10409
11205
  function isAutoInstrumentationSuppressed() {
10410
- const frames = currentFrames();
10411
- return frames[frames.length - 1]?.mode === "suppress";
11206
+ return suppressionStore().getStore() === true;
10412
11207
  }
10413
11208
  function runWithAutoInstrumentationSuppressed(callback) {
10414
- const frame = {
10415
- id: /* @__PURE__ */ Symbol("braintrust.auto-instrumentation-suppress"),
10416
- mode: "suppress"
10417
- };
10418
- return suppressionStore().run(
10419
- { frames: [...currentFrames(), frame] },
10420
- callback
10421
- );
10422
- }
10423
- function bindAutoInstrumentationSuppressionToStart(tracingChannel) {
10424
- const startChannel = tracingChannel.start;
10425
- if (!startChannel) {
10426
- return void 0;
10427
- }
10428
- const store = suppressionStore();
10429
- startChannel.bindStore(store, () => ({
10430
- frames: [
10431
- ...currentFrames(),
10432
- {
10433
- id: /* @__PURE__ */ Symbol("braintrust.auto-instrumentation-suppress"),
10434
- mode: "suppress"
10435
- }
10436
- ]
10437
- }));
10438
- return () => {
10439
- startChannel.unbindStore(store);
10440
- };
11209
+ return suppressionStore().run(true, callback);
10441
11210
  }
10442
- function enterAutoInstrumentationAllowed() {
10443
- const frame = {
10444
- id: /* @__PURE__ */ Symbol("braintrust.auto-instrumentation-allow"),
10445
- mode: "allow"
10446
- };
10447
- suppressionStore().enterWith({
10448
- frames: [...currentFrames(), frame]
10449
- });
10450
- return () => {
10451
- const frames = currentFrames().filter(
10452
- (candidate) => candidate.id !== frame.id
10453
- );
10454
- suppressionStore().enterWith(frames.length > 0 ? { frames } : void 0);
10455
- };
11211
+ function runWithAutoInstrumentationAllowed(callback) {
11212
+ return suppressionStore().run(void 0, callback);
10456
11213
  }
10457
11214
 
10458
11215
  // src/instrumentation/core/channel-tracing.ts
@@ -11042,6 +11799,131 @@ function unsubscribeAll(unsubscribers) {
11042
11799
  return [];
11043
11800
  }
11044
11801
 
11802
+ // src/instrumentation/core/channel-definitions.ts
11803
+ function channel(spec) {
11804
+ return spec;
11805
+ }
11806
+ function defineChannels(pkg, channels, options) {
11807
+ const { instrumentationName } = options;
11808
+ return Object.fromEntries(
11809
+ Object.entries(channels).map(([key, spec]) => {
11810
+ const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
11811
+ if (spec.kind === "async") {
11812
+ const asyncSpec = spec;
11813
+ const tracingChannel2 = () => isomorph_default.newTracingChannel(
11814
+ fullChannelName
11815
+ );
11816
+ const intercept2 = (interceptor) => {
11817
+ const hook = tracingChannel2();
11818
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11819
+ };
11820
+ };
11821
+ return [
11822
+ key,
11823
+ {
11824
+ ...asyncSpec,
11825
+ instrumentationName,
11826
+ intercept: intercept2,
11827
+ invoke: (target, thisArg, args, additional) => {
11828
+ const hook = tracingChannel2();
11829
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11830
+ },
11831
+ tracingChannel: tracingChannel2,
11832
+ tracePromise: (fn, context) => tracingChannel2().tracePromise(
11833
+ fn,
11834
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11835
+ context
11836
+ )
11837
+ }
11838
+ ];
11839
+ }
11840
+ const syncSpec = spec;
11841
+ const tracingChannel = () => isomorph_default.newTracingChannel(
11842
+ fullChannelName
11843
+ );
11844
+ const intercept = (interceptor) => {
11845
+ const hook = tracingChannel();
11846
+ return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11847
+ };
11848
+ };
11849
+ return [
11850
+ key,
11851
+ {
11852
+ ...syncSpec,
11853
+ instrumentationName,
11854
+ intercept,
11855
+ invoke: (target, thisArg, args, additional) => {
11856
+ const hook = tracingChannel();
11857
+ return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11858
+ },
11859
+ tracingChannel,
11860
+ traceSync: (fn, context) => tracingChannel().traceSync(
11861
+ fn,
11862
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11863
+ context
11864
+ )
11865
+ }
11866
+ ];
11867
+ })
11868
+ );
11869
+ }
11870
+
11871
+ // src/instrumentation/plugins/openai-channels.ts
11872
+ var openAIChannels = defineChannels(
11873
+ "openai",
11874
+ {
11875
+ filesCreateTraced: channel({
11876
+ channelName: "files.create-traced",
11877
+ kind: "async"
11878
+ }),
11879
+ batchesRetrieveTraced: channel({
11880
+ channelName: "batches.retrieve-traced",
11881
+ kind: "async"
11882
+ }),
11883
+ batchesCompleteTrace: channel({
11884
+ channelName: "batches.complete-trace",
11885
+ kind: "async"
11886
+ }),
11887
+ chatCompletionsCreate: channel({
11888
+ channelName: "chat.completions.create",
11889
+ kind: "async"
11890
+ }),
11891
+ embeddingsCreate: channel({
11892
+ channelName: "embeddings.create",
11893
+ kind: "async"
11894
+ }),
11895
+ betaChatCompletionsParse: channel({
11896
+ channelName: "beta.chat.completions.parse",
11897
+ kind: "async"
11898
+ }),
11899
+ betaChatCompletionsStream: channel({
11900
+ channelName: "beta.chat.completions.stream",
11901
+ kind: "sync-stream"
11902
+ }),
11903
+ moderationsCreate: channel({
11904
+ channelName: "moderations.create",
11905
+ kind: "async"
11906
+ }),
11907
+ responsesCreate: channel({
11908
+ channelName: "responses.create",
11909
+ kind: "async"
11910
+ }),
11911
+ responsesStream: channel({
11912
+ channelName: "responses.stream",
11913
+ kind: "sync-stream"
11914
+ }),
11915
+ responsesParse: channel({
11916
+ channelName: "responses.parse",
11917
+ kind: "async"
11918
+ }),
11919
+ responsesCompact: channel({
11920
+ channelName: "responses.compact",
11921
+ kind: "async"
11922
+ })
11923
+ },
11924
+ { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
11925
+ );
11926
+
11045
11927
  // src/wrappers/attachment-utils.ts
11046
11928
  function getExtensionFromMediaType(mediaType) {
11047
11929
  const extensionMap = {
@@ -11217,118 +12099,91 @@ function processInputAttachments(input) {
11217
12099
  return processNode(input);
11218
12100
  }
11219
12101
 
11220
- // src/instrumentation/core/channel-definitions.ts
11221
- function channel(spec) {
11222
- return spec;
11223
- }
11224
- function defineChannels(pkg, channels, options) {
11225
- const { instrumentationName } = options;
11226
- return Object.fromEntries(
11227
- Object.entries(channels).map(([key, spec]) => {
11228
- const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
11229
- if (spec.kind === "async") {
11230
- const asyncSpec = spec;
11231
- const tracingChannel2 = () => isomorph_default.newTracingChannel(
11232
- fullChannelName
11233
- );
11234
- const intercept2 = (interceptor) => {
11235
- const hook = tracingChannel2();
11236
- return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11237
- };
11238
- };
11239
- return [
11240
- key,
11241
- {
11242
- ...asyncSpec,
11243
- instrumentationName,
11244
- intercept: intercept2,
11245
- invoke: (target, thisArg, args, additional) => {
11246
- const hook = tracingChannel2();
11247
- return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11248
- },
11249
- tracingChannel: tracingChannel2,
11250
- tracePromise: (fn, context) => tracingChannel2().tracePromise(
11251
- fn,
11252
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11253
- context
11254
- )
11255
- }
11256
- ];
12102
+ // src/instrumentation/plugins/openai-span-data.ts
12103
+ var OPENAI_METADATA_KEYS = [
12104
+ "model",
12105
+ "temperature",
12106
+ "top_p",
12107
+ "max_tokens",
12108
+ "frequency_penalty",
12109
+ "presence_penalty",
12110
+ "stop",
12111
+ "response_format",
12112
+ "tools",
12113
+ "tool_choice",
12114
+ "parallel_tool_calls",
12115
+ "max_tool_calls"
12116
+ ];
12117
+ function batchMetadata(params) {
12118
+ const metadata = { provider: "openai" };
12119
+ for (const key of OPENAI_METADATA_KEYS) {
12120
+ try {
12121
+ const value = Reflect.get(params, key);
12122
+ if (value !== void 0) {
12123
+ metadata[key] = value;
11257
12124
  }
11258
- const syncSpec = spec;
11259
- const tracingChannel = () => isomorph_default.newTracingChannel(
11260
- fullChannelName
11261
- );
11262
- const intercept = (interceptor) => {
11263
- const hook = tracingChannel();
11264
- return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
11265
- };
11266
- };
11267
- return [
11268
- key,
11269
- {
11270
- ...syncSpec,
11271
- instrumentationName,
11272
- intercept,
11273
- invoke: (target, thisArg, args, additional) => {
11274
- const hook = tracingChannel();
11275
- return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
11276
- },
11277
- tracingChannel,
11278
- traceSync: (fn, context) => tracingChannel().traceSync(
11279
- fn,
11280
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
11281
- context
11282
- )
11283
- }
11284
- ];
11285
- })
11286
- );
12125
+ } catch {
12126
+ }
12127
+ }
12128
+ return metadata;
12129
+ }
12130
+ function extractOpenAIBatchInput(endpoint, params) {
12131
+ const input = endpoint === "/v1/chat/completions" ? params.messages : params.input;
12132
+ return {
12133
+ input: processInputAttachments(input),
12134
+ metadata: batchMetadata(params)
12135
+ };
12136
+ }
12137
+ function extractOpenAIChatInput(params) {
12138
+ const { messages, ...metadata } = params;
12139
+ return {
12140
+ input: processInputAttachments(messages),
12141
+ metadata: { ...metadata, provider: "openai" }
12142
+ };
12143
+ }
12144
+ function extractOpenAIResponsesInput(params) {
12145
+ const { input, ...metadata } = params;
12146
+ return {
12147
+ input: processInputAttachments(input),
12148
+ metadata: { ...metadata, provider: "openai" }
12149
+ };
12150
+ }
12151
+ function extractOpenAIResponsesMetadata(result) {
12152
+ if (!result) {
12153
+ return void 0;
12154
+ }
12155
+ const { output: _output, usage: _usage, ...metadata } = result;
12156
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
12157
+ }
12158
+ function processImagesInOutput(output) {
12159
+ if (Array.isArray(output)) {
12160
+ return output.map(processImagesInOutput);
12161
+ }
12162
+ if (isObject(output) && output.type === "image_generation_call" && typeof output.result === "string" && output.result) {
12163
+ const fileExtension = output.output_format || "png";
12164
+ const contentType = `image/${fileExtension}`;
12165
+ const baseFilename = typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
12166
+ let binaryString;
12167
+ try {
12168
+ binaryString = atob(output.result);
12169
+ } catch {
12170
+ return output;
12171
+ }
12172
+ const bytes = new Uint8Array(binaryString.length);
12173
+ for (let i = 0; i < binaryString.length; i++) {
12174
+ bytes[i] = binaryString.charCodeAt(i);
12175
+ }
12176
+ return {
12177
+ ...output,
12178
+ result: new Attachment({
12179
+ data: new Blob([bytes], { type: contentType }),
12180
+ filename: `${baseFilename}.${fileExtension}`,
12181
+ contentType
12182
+ })
12183
+ };
12184
+ }
12185
+ return output;
11287
12186
  }
11288
-
11289
- // src/instrumentation/plugins/openai-channels.ts
11290
- var openAIChannels = defineChannels(
11291
- "openai",
11292
- {
11293
- chatCompletionsCreate: channel({
11294
- channelName: "chat.completions.create",
11295
- kind: "async"
11296
- }),
11297
- embeddingsCreate: channel({
11298
- channelName: "embeddings.create",
11299
- kind: "async"
11300
- }),
11301
- betaChatCompletionsParse: channel({
11302
- channelName: "beta.chat.completions.parse",
11303
- kind: "async"
11304
- }),
11305
- betaChatCompletionsStream: channel({
11306
- channelName: "beta.chat.completions.stream",
11307
- kind: "sync-stream"
11308
- }),
11309
- moderationsCreate: channel({
11310
- channelName: "moderations.create",
11311
- kind: "async"
11312
- }),
11313
- responsesCreate: channel({
11314
- channelName: "responses.create",
11315
- kind: "async"
11316
- }),
11317
- responsesStream: channel({
11318
- channelName: "responses.stream",
11319
- kind: "sync-stream"
11320
- }),
11321
- responsesParse: channel({
11322
- channelName: "responses.parse",
11323
- kind: "async"
11324
- }),
11325
- responsesCompact: channel({
11326
- channelName: "responses.compact",
11327
- kind: "async"
11328
- })
11329
- },
11330
- { instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
11331
- );
11332
12187
 
11333
12188
  // src/openai-utils.ts
11334
12189
  var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
@@ -11385,23 +12240,573 @@ function getCachedMetricFromHeaders(headers) {
11385
12240
  return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
11386
12241
  }
11387
12242
 
12243
+ // src/instrumentation/plugins/openai-batch-instrumentation.ts
12244
+ var SUPPORTED_ENDPOINTS = /* @__PURE__ */ new Set(["/v1/chat/completions", "/v1/responses"]);
12245
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
12246
+ "completed",
12247
+ "failed",
12248
+ "expired",
12249
+ "cancelled"
12250
+ ]);
12251
+ var pendingBatchTraces = /* @__PURE__ */ new Map();
12252
+ function read(value, key) {
12253
+ if (!isObject(value)) {
12254
+ return void 0;
12255
+ }
12256
+ try {
12257
+ return Reflect.get(value, key);
12258
+ } catch {
12259
+ return void 0;
12260
+ }
12261
+ }
12262
+ function isBatchRecordIterable(value) {
12263
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
12264
+ return false;
12265
+ }
12266
+ return typeof read(value, Symbol.iterator) === "function" || typeof read(value, Symbol.asyncIterator) === "function";
12267
+ }
12268
+ function validCustomId(value) {
12269
+ return typeof value === "string" && value.length > 0 && value.length <= 64;
12270
+ }
12271
+ function logBatchInstrumentationError(context, error) {
12272
+ debugLogger.debug(`OpenAI Batch instrumentation ${context}:`, error);
12273
+ }
12274
+ async function exportParent(parent) {
12275
+ if ("toStr" in parent && typeof parent.toStr === "function") {
12276
+ return parent.toStr();
12277
+ }
12278
+ if ("export" in parent && typeof parent.export === "function") {
12279
+ return await parent.export();
12280
+ }
12281
+ return void 0;
12282
+ }
12283
+ async function deterministicDigest(namespace, ...parts) {
12284
+ const encoded = new TextEncoder().encode(
12285
+ [namespace, ...parts].map((part) => `${part.length}:${part}`).join("\0")
12286
+ );
12287
+ return new Uint8Array(
12288
+ await globalThis.crypto.subtle.digest("SHA-256", encoded)
12289
+ );
12290
+ }
12291
+ function digestHex(bytes, length) {
12292
+ return Array.from(bytes.slice(0, length)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
12293
+ }
12294
+ function digestUuid(bytes) {
12295
+ const hex = digestHex(bytes, 16);
12296
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
12297
+ }
12298
+ async function batchSpanIds(inputFileId) {
12299
+ const [row, span, root] = await Promise.all([
12300
+ deterministicDigest("openai:batch:row", inputFileId),
12301
+ deterministicDigest("openai:batch:span", inputFileId),
12302
+ deterministicDigest("openai:batch:root", inputFileId)
12303
+ ]);
12304
+ return {
12305
+ rowId: digestUuid(row),
12306
+ spanId: digestHex(span, 8),
12307
+ rootSpanId: digestHex(root, 16)
12308
+ };
12309
+ }
12310
+ async function childSpanIds(inputFileId, customId) {
12311
+ const [row, span] = await Promise.all([
12312
+ deterministicDigest("openai:batch:child:row", inputFileId, customId),
12313
+ deterministicDigest("openai:batch:child:span", inputFileId, customId)
12314
+ ]);
12315
+ return { rowId: digestUuid(row), spanId: digestHex(span, 8) };
12316
+ }
12317
+ async function startBatchSpan(context) {
12318
+ const ids = await batchSpanIds(context.inputFileId);
12319
+ const parent = SpanComponentsV4.fromStr(context.parent);
12320
+ const hasParentSpan = Boolean(
12321
+ parent.data.row_id && parent.data.span_id && parent.data.root_span_id
12322
+ );
12323
+ return withCurrent(
12324
+ NOOP_SPAN,
12325
+ () => _internalStartSpanWithInitialMergeAndParentSpanIds(
12326
+ withSpanInstrumentationName(
12327
+ {
12328
+ name: "openai.batch",
12329
+ type: "task" /* TASK */,
12330
+ parent: context.parent,
12331
+ ...!hasParentSpan ? {
12332
+ parentSpanIds: {
12333
+ parentSpanIds: [],
12334
+ rootSpanId: ids.rootSpanId
12335
+ }
12336
+ } : {},
12337
+ spanId: ids.spanId,
12338
+ startTime: context.taskStartTime,
12339
+ event: {
12340
+ id: ids.rowId,
12341
+ metadata: {
12342
+ endpoint: context.endpoint,
12343
+ input_file_id: context.inputFileId,
12344
+ provider: "openai"
12345
+ }
12346
+ }
12347
+ },
12348
+ INSTRUMENTATION_NAMES.OPENAI
12349
+ )
12350
+ )
12351
+ );
12352
+ }
12353
+ async function startBatchChild(context, taskParent, input) {
12354
+ const ids = await childSpanIds(context.inputFileId, input.customId);
12355
+ return withCurrent(
12356
+ NOOP_SPAN,
12357
+ () => _internalStartSpanWithInitialMerge(
12358
+ withSpanInstrumentationName(
12359
+ {
12360
+ name: context.endpoint === "/v1/chat/completions" ? "Chat Completion" : "openai.responses.create",
12361
+ type: "llm" /* LLM */,
12362
+ parent: taskParent,
12363
+ spanId: ids.spanId,
12364
+ startTime: context.childStartTime,
12365
+ event: {
12366
+ id: ids.rowId,
12367
+ ...input.spanData?.input !== void 0 ? { input: input.spanData.input } : {},
12368
+ metadata: {
12369
+ ...input.spanData?.metadata,
12370
+ custom_id: input.customId,
12371
+ provider: "openai"
12372
+ }
12373
+ }
12374
+ },
12375
+ INSTRUMENTATION_NAMES.OPENAI
12376
+ )
12377
+ )
12378
+ );
12379
+ }
12380
+ async function* jsonlRecords(file, onIssue = () => {
12381
+ }) {
12382
+ const resolvedFile = await file;
12383
+ if (typeof resolvedFile === "string") {
12384
+ for (const line of resolvedFile.split("\n")) {
12385
+ if (!line.trim()) {
12386
+ continue;
12387
+ }
12388
+ try {
12389
+ yield JSON.parse(line.replace(/\r$/, ""));
12390
+ } catch (error) {
12391
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12392
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12393
+ }
12394
+ }
12395
+ return;
12396
+ }
12397
+ const body = read(resolvedFile, "body");
12398
+ const getReader = read(body, "getReader");
12399
+ if (isObject(body) && typeof getReader === "function") {
12400
+ let reader;
12401
+ try {
12402
+ reader = Reflect.apply(getReader, body, []);
12403
+ const decoder = new TextDecoder();
12404
+ let pending = "";
12405
+ while (true) {
12406
+ const readChunk = read(reader, "read");
12407
+ if (typeof readChunk !== "function") {
12408
+ throw new Error("Response body stream has no read method");
12409
+ }
12410
+ const chunk = await Reflect.apply(readChunk, reader, []);
12411
+ if (!isObject(chunk)) {
12412
+ throw new Error("Response body stream returned an invalid chunk");
12413
+ }
12414
+ if (chunk.done === true) {
12415
+ pending += decoder.decode();
12416
+ break;
12417
+ }
12418
+ if (!(chunk.value instanceof Uint8Array)) {
12419
+ throw new Error("Response body stream returned a non-byte chunk");
12420
+ }
12421
+ pending += decoder.decode(chunk.value, { stream: true });
12422
+ let newline = pending.indexOf("\n");
12423
+ while (newline !== -1) {
12424
+ const line = pending.slice(0, newline).replace(/\r$/, "");
12425
+ pending = pending.slice(newline + 1);
12426
+ if (line.trim()) {
12427
+ try {
12428
+ yield JSON.parse(line);
12429
+ } catch (error) {
12430
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12431
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12432
+ }
12433
+ }
12434
+ newline = pending.indexOf("\n");
12435
+ }
12436
+ }
12437
+ if (pending.trim()) {
12438
+ try {
12439
+ yield JSON.parse(pending.replace(/\r$/, ""));
12440
+ } catch (error) {
12441
+ logBatchInstrumentationError("skipped malformed JSONL", error);
12442
+ onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
12443
+ }
12444
+ }
12445
+ } catch (error) {
12446
+ logBatchInstrumentationError("could not read JSONL response body", error);
12447
+ onIssue(new Error("OpenAI Batch response body could not be read"));
12448
+ } finally {
12449
+ const releaseLock = read(reader, "releaseLock");
12450
+ if (typeof releaseLock === "function") {
12451
+ try {
12452
+ Reflect.apply(releaseLock, reader, []);
12453
+ } catch (error) {
12454
+ logBatchInstrumentationError(
12455
+ "could not release stream reader",
12456
+ error
12457
+ );
12458
+ }
12459
+ }
12460
+ }
12461
+ return;
12462
+ }
12463
+ if (isBatchRecordIterable(resolvedFile)) {
12464
+ for await (const record of resolvedFile) {
12465
+ yield record;
12466
+ }
12467
+ return;
12468
+ }
12469
+ logBatchInstrumentationError("skipped invalid JSONL source", resolvedFile);
12470
+ onIssue(new Error("OpenAI Batch file source is invalid"));
12471
+ }
12472
+ async function readBatchInputs(file) {
12473
+ const inputs = /* @__PURE__ */ new Map();
12474
+ const issues = [];
12475
+ let endpoint;
12476
+ try {
12477
+ for await (const value of jsonlRecords(
12478
+ file,
12479
+ (issue) => issues.push(issue)
12480
+ )) {
12481
+ const customId = read(value, "custom_id");
12482
+ const url = read(value, "url");
12483
+ const body = read(value, "body");
12484
+ if (!validCustomId(customId) || inputs.has(customId) || read(value, "method") !== "POST" || typeof url !== "string" || !SUPPORTED_ENDPOINTS.has(url) || endpoint !== void 0 && endpoint !== url || !isObject(body)) {
12485
+ issues.push(new Error("OpenAI Batch input contains an invalid record"));
12486
+ continue;
12487
+ }
12488
+ endpoint = url;
12489
+ let spanData;
12490
+ try {
12491
+ spanData = extractOpenAIBatchInput(url, body);
12492
+ } catch (error) {
12493
+ logBatchInstrumentationError("could not extract batch input", error);
12494
+ }
12495
+ inputs.set(customId, { customId, spanData });
12496
+ }
12497
+ } catch (error) {
12498
+ logBatchInstrumentationError("could not process input file", error);
12499
+ issues.push(new Error("OpenAI Batch input file could not be processed"));
12500
+ }
12501
+ if (!endpoint || inputs.size === 0) {
12502
+ issues.push(new Error("OpenAI Batch input contains no supported records"));
12503
+ }
12504
+ return { endpoint, inputs, issues };
12505
+ }
12506
+ async function writePendingSpans(trace, endTime) {
12507
+ const task = await startBatchSpan(trace.context);
12508
+ const taskParent = await task.export();
12509
+ for (const input of trace.inputs.values()) {
12510
+ try {
12511
+ const child = await startBatchChild(trace.context, taskParent, input);
12512
+ if (endTime !== void 0) {
12513
+ child.end({ endTime });
12514
+ }
12515
+ } catch (error) {
12516
+ logBatchInstrumentationError("could not write batch request span", error);
12517
+ }
12518
+ }
12519
+ if (endTime !== void 0) {
12520
+ if (trace.status && trace.status !== "completed") {
12521
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12522
+ }
12523
+ task.end({ endTime });
12524
+ }
12525
+ }
12526
+ var interceptOpenAIFilesCreateTraced = async (target, thisArg, args) => {
12527
+ const startTime = getCurrentUnixTimestamp();
12528
+ const inputPromise = readBatchInputs(args[0].inputFileContent);
12529
+ const parentPromise = exportParent(args[0].parent);
12530
+ const file = await Reflect.apply(target, thisArg, args);
12531
+ try {
12532
+ const inputFileId = read(file, "id");
12533
+ const [inputData, exportedParent2] = await Promise.all([
12534
+ inputPromise,
12535
+ parentPromise
12536
+ ]);
12537
+ if (typeof inputFileId !== "string" || !exportedParent2 || !inputData.endpoint || inputData.issues.length > 0) {
12538
+ if (inputData.issues[0]) {
12539
+ logBatchInstrumentationError(
12540
+ "skipped invalid input file",
12541
+ inputData.issues[0]
12542
+ );
12543
+ }
12544
+ return file;
12545
+ }
12546
+ const trace = {
12547
+ context: {
12548
+ inputFileId,
12549
+ endpoint: inputData.endpoint,
12550
+ parent: exportedParent2,
12551
+ taskStartTime: startTime,
12552
+ childStartTime: startTime
12553
+ },
12554
+ inputs: inputData.inputs
12555
+ };
12556
+ pendingBatchTraces.set(inputFileId, trace);
12557
+ await writePendingSpans(trace);
12558
+ } catch (error) {
12559
+ logBatchInstrumentationError("could not start batch spans", error);
12560
+ }
12561
+ return file;
12562
+ };
12563
+ function validTimestamp(value) {
12564
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
12565
+ }
12566
+ function terminalEndTime(batch, startTime) {
12567
+ let timestamp;
12568
+ switch (batch.status) {
12569
+ case "completed":
12570
+ timestamp = batch.completed_at;
12571
+ break;
12572
+ case "failed":
12573
+ timestamp = batch.failed_at;
12574
+ break;
12575
+ case "expired":
12576
+ timestamp = batch.expired_at;
12577
+ break;
12578
+ default:
12579
+ timestamp = batch.cancelled_at;
12580
+ }
12581
+ return validTimestamp(timestamp) ? Math.max(timestamp, startTime) : Math.max(getCurrentUnixTimestamp(), startTime);
12582
+ }
12583
+ async function updateBatchTimestamps(batch) {
12584
+ const trace = pendingBatchTraces.get(batch.input_file_id);
12585
+ if (!trace || trace.context.endpoint !== batch.endpoint) {
12586
+ return;
12587
+ }
12588
+ if (validTimestamp(batch.created_at)) {
12589
+ trace.context.taskStartTime = batch.created_at;
12590
+ }
12591
+ trace.context.childStartTime = validTimestamp(batch.in_progress_at) ? Math.max(batch.in_progress_at, trace.context.taskStartTime) : trace.context.taskStartTime;
12592
+ trace.status = batch.status;
12593
+ if (TERMINAL_STATUSES.has(batch.status)) {
12594
+ trace.endTime = terminalEndTime(batch, trace.context.childStartTime);
12595
+ }
12596
+ await writePendingSpans(trace, trace.endTime);
12597
+ }
12598
+ var interceptOpenAIBatchesRetrieveTraced = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (batch) => {
12599
+ try {
12600
+ await updateBatchTimestamps(batch);
12601
+ } catch (error) {
12602
+ logBatchInstrumentationError("could not update batch timestamps", error);
12603
+ }
12604
+ return batch;
12605
+ });
12606
+ function errorFromResult(result) {
12607
+ const error = read(result.value, "error");
12608
+ if (isObject(error)) {
12609
+ const message = read(error, "message");
12610
+ return new Error(
12611
+ typeof message === "string" ? message : "OpenAI Batch request failed"
12612
+ );
12613
+ }
12614
+ const response = read(result.value, "response");
12615
+ const statusCode = read(response, "status_code");
12616
+ if (result.source === "error" || typeof statusCode === "number" && (statusCode < 200 || statusCode >= 300)) {
12617
+ const message = read(read(read(response, "body"), "error"), "message");
12618
+ return new Error(
12619
+ typeof message === "string" ? message : "OpenAI Batch request failed"
12620
+ );
12621
+ }
12622
+ return void 0;
12623
+ }
12624
+ async function completeBatchResult(context, taskParent, endTime, input, result) {
12625
+ const child = await startBatchChild(context, taskParent, input);
12626
+ try {
12627
+ const resultError = errorFromResult(result);
12628
+ const responseBody = read(read(result.value, "response"), "body");
12629
+ if (resultError) {
12630
+ child.log({ error: resultError });
12631
+ } else if (isObject(responseBody)) {
12632
+ const model = read(responseBody, "model");
12633
+ child.log({
12634
+ output: context.endpoint === "/v1/chat/completions" ? read(responseBody, "choices") : processImagesInOutput(read(responseBody, "output")),
12635
+ ...typeof model === "string" ? { metadata: { model } } : {},
12636
+ metrics: parseMetricsFromUsage(read(responseBody, "usage"))
12637
+ });
12638
+ } else {
12639
+ child.log({ error: new Error("OpenAI Batch response body is missing") });
12640
+ }
12641
+ } catch (error) {
12642
+ child.log({ error });
12643
+ } finally {
12644
+ child.end({ endTime });
12645
+ }
12646
+ }
12647
+ async function completeResultFile({
12648
+ context,
12649
+ endTime,
12650
+ file,
12651
+ inputs,
12652
+ issues,
12653
+ seen,
12654
+ source,
12655
+ taskParent
12656
+ }) {
12657
+ if (file === void 0) {
12658
+ return;
12659
+ }
12660
+ for await (const value of jsonlRecords(file, (issue) => issues.push(issue))) {
12661
+ const customId = read(value, "custom_id");
12662
+ if (!validCustomId(customId) || !isObject(value)) {
12663
+ issues.push(
12664
+ new Error("OpenAI Batch result is missing a valid custom_id")
12665
+ );
12666
+ continue;
12667
+ }
12668
+ if (seen.has(customId)) {
12669
+ issues.push(new Error("OpenAI Batch result contains a duplicate"));
12670
+ continue;
12671
+ }
12672
+ const input = inputs.get(customId);
12673
+ if (!input) {
12674
+ issues.push(
12675
+ new Error("OpenAI Batch result does not match an input record")
12676
+ );
12677
+ continue;
12678
+ }
12679
+ seen.add(customId);
12680
+ await completeBatchResult(context, taskParent, endTime, input, {
12681
+ value,
12682
+ source
12683
+ });
12684
+ }
12685
+ }
12686
+ function sameInputs(first, second) {
12687
+ return first.size === second.size && [...first.keys()].every((customId) => second.has(customId));
12688
+ }
12689
+ async function contextForCompletion(inputFileId, inputData) {
12690
+ if (!inputData.endpoint || inputData.issues.length > 0) {
12691
+ return void 0;
12692
+ }
12693
+ const existing = pendingBatchTraces.get(inputFileId);
12694
+ if (existing) {
12695
+ if (existing.context.endpoint !== inputData.endpoint || !sameInputs(existing.inputs, inputData.inputs)) {
12696
+ return void 0;
12697
+ }
12698
+ return { ...existing, inputs: inputData.inputs };
12699
+ }
12700
+ const parent = await exportParent(getSpanParentObject());
12701
+ if (!parent) {
12702
+ return void 0;
12703
+ }
12704
+ const startTime = getCurrentUnixTimestamp();
12705
+ return {
12706
+ context: {
12707
+ inputFileId,
12708
+ endpoint: inputData.endpoint,
12709
+ parent,
12710
+ taskStartTime: startTime,
12711
+ childStartTime: startTime
12712
+ },
12713
+ inputs: inputData.inputs
12714
+ };
12715
+ }
12716
+ async function completeBatch(args) {
12717
+ const inputData = await readBatchInputs(args.inputFileContent);
12718
+ const trace = await contextForCompletion(args.inputFileId, inputData);
12719
+ if (!trace) {
12720
+ logBatchInstrumentationError(
12721
+ "left batch spans pending",
12722
+ inputData.issues[0] ?? new Error("OpenAI Batch input does not match")
12723
+ );
12724
+ return;
12725
+ }
12726
+ const endTime = trace.endTime ?? getCurrentUnixTimestamp();
12727
+ const task = await startBatchSpan(trace.context);
12728
+ const taskParent = await task.export();
12729
+ const issues = [];
12730
+ const seen = /* @__PURE__ */ new Set();
12731
+ await Promise.all([
12732
+ completeResultFile({
12733
+ context: trace.context,
12734
+ endTime,
12735
+ file: args.outputFileContent,
12736
+ inputs: trace.inputs,
12737
+ issues,
12738
+ seen,
12739
+ source: "output",
12740
+ taskParent
12741
+ }),
12742
+ completeResultFile({
12743
+ context: trace.context,
12744
+ endTime,
12745
+ file: args.errorFileContent,
12746
+ inputs: trace.inputs,
12747
+ issues,
12748
+ seen,
12749
+ source: "error",
12750
+ taskParent
12751
+ })
12752
+ ]);
12753
+ if (issues.length > 0) {
12754
+ logBatchInstrumentationError("left batch spans pending", issues[0]);
12755
+ return;
12756
+ }
12757
+ const missing = [...trace.inputs.values()].filter(
12758
+ ({ customId }) => !seen.has(customId)
12759
+ );
12760
+ if (!trace.status || trace.status === "completed") {
12761
+ if (missing.length > 0) {
12762
+ logBatchInstrumentationError(
12763
+ "left batch spans pending",
12764
+ new Error("OpenAI Batch result files are incomplete")
12765
+ );
12766
+ return;
12767
+ }
12768
+ } else {
12769
+ for (const input of missing) {
12770
+ const child = await startBatchChild(trace.context, taskParent, input);
12771
+ child.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12772
+ child.end({ endTime });
12773
+ }
12774
+ task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
12775
+ }
12776
+ task.end({ endTime });
12777
+ pendingBatchTraces.delete(args.inputFileId);
12778
+ }
12779
+ var interceptOpenAIBatchTraceComplete = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (result) => {
12780
+ try {
12781
+ await completeBatch(args[0]);
12782
+ } catch (error) {
12783
+ logBatchInstrumentationError("could not complete batch", error);
12784
+ }
12785
+ return result;
12786
+ });
12787
+
11388
12788
  // src/instrumentation/plugins/openai-plugin.ts
11389
12789
  var OpenAIPlugin = class extends BasePlugin {
11390
12790
  constructor() {
11391
12791
  super();
11392
12792
  }
11393
12793
  onEnable() {
12794
+ this.unsubscribers.push(
12795
+ openAIChannels.filesCreateTraced.intercept(
12796
+ interceptOpenAIFilesCreateTraced
12797
+ ),
12798
+ openAIChannels.batchesRetrieveTraced.intercept(
12799
+ interceptOpenAIBatchesRetrieveTraced
12800
+ ),
12801
+ openAIChannels.batchesCompleteTrace.intercept(
12802
+ interceptOpenAIBatchTraceComplete
12803
+ )
12804
+ );
11394
12805
  this.unsubscribers.push(
11395
12806
  traceStreamingChannel(openAIChannels.chatCompletionsCreate, {
11396
12807
  name: "Chat Completion",
11397
12808
  type: "llm" /* LLM */,
11398
- extractInput: ([params]) => {
11399
- const { messages, ...metadata } = params;
11400
- return {
11401
- input: processInputAttachments(messages),
11402
- metadata: { ...metadata, provider: "openai" }
11403
- };
11404
- },
12809
+ extractInput: ([params]) => extractOpenAIChatInput(params),
11405
12810
  extractOutput: (result) => {
11406
12811
  return result?.choices;
11407
12812
  },
@@ -11447,13 +12852,7 @@ var OpenAIPlugin = class extends BasePlugin {
11447
12852
  traceStreamingChannel(openAIChannels.betaChatCompletionsParse, {
11448
12853
  name: "Chat Completion",
11449
12854
  type: "llm" /* LLM */,
11450
- extractInput: ([params]) => {
11451
- const { messages, ...metadata } = params;
11452
- return {
11453
- input: processInputAttachments(messages),
11454
- metadata: { ...metadata, provider: "openai" }
11455
- };
11456
- },
12855
+ extractInput: ([params]) => extractOpenAIChatInput(params),
11457
12856
  extractOutput: (result) => {
11458
12857
  return result?.choices;
11459
12858
  },
@@ -11475,13 +12874,7 @@ var OpenAIPlugin = class extends BasePlugin {
11475
12874
  traceSyncStreamChannel(openAIChannels.betaChatCompletionsStream, {
11476
12875
  name: "Chat Completion",
11477
12876
  type: "llm" /* LLM */,
11478
- extractInput: ([params]) => {
11479
- const { messages, ...metadata } = params;
11480
- return {
11481
- input: processInputAttachments(messages),
11482
- metadata: { ...metadata, provider: "openai" }
11483
- };
11484
- }
12877
+ extractInput: ([params]) => extractOpenAIChatInput(params)
11485
12878
  })
11486
12879
  );
11487
12880
  this.unsubscribers.push(
@@ -11511,23 +12904,11 @@ var OpenAIPlugin = class extends BasePlugin {
11511
12904
  traceStreamingChannel(openAIChannels.responsesCreate, {
11512
12905
  name: "openai.responses.create",
11513
12906
  type: "llm" /* LLM */,
11514
- extractInput: ([params]) => {
11515
- const { input, ...metadata } = params;
11516
- return {
11517
- input: processInputAttachments(input),
11518
- metadata: { ...metadata, provider: "openai" }
11519
- };
11520
- },
12907
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
11521
12908
  extractOutput: (result) => {
11522
12909
  return processImagesInOutput(result?.output);
11523
12910
  },
11524
- extractMetadata: (result) => {
11525
- if (!result) {
11526
- return void 0;
11527
- }
11528
- const { output: _output, usage: _usage, ...metadata } = result;
11529
- return Object.keys(metadata).length > 0 ? metadata : void 0;
11530
- },
12911
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
11531
12912
  extractMetrics: (result, startTime, endEvent) => {
11532
12913
  const metrics = withCachedMetric(
11533
12914
  parseMetricsFromUsage(result?.usage),
@@ -11546,13 +12927,7 @@ var OpenAIPlugin = class extends BasePlugin {
11546
12927
  traceSyncStreamChannel(openAIChannels.responsesStream, {
11547
12928
  name: "openai.responses.create",
11548
12929
  type: "llm" /* LLM */,
11549
- extractInput: ([params]) => {
11550
- const { input, ...metadata } = params;
11551
- return {
11552
- input: processInputAttachments(input),
11553
- metadata: { ...metadata, provider: "openai" }
11554
- };
11555
- },
12930
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
11556
12931
  extractFromEvent: (event) => {
11557
12932
  if (event.type !== "response.completed" || !event.response) {
11558
12933
  return {};
@@ -11575,23 +12950,11 @@ var OpenAIPlugin = class extends BasePlugin {
11575
12950
  traceStreamingChannel(openAIChannels.responsesParse, {
11576
12951
  name: "openai.responses.parse",
11577
12952
  type: "llm" /* LLM */,
11578
- extractInput: ([params]) => {
11579
- const { input, ...metadata } = params;
11580
- return {
11581
- input: processInputAttachments(input),
11582
- metadata: { ...metadata, provider: "openai" }
11583
- };
11584
- },
12953
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
11585
12954
  extractOutput: (result) => {
11586
12955
  return processImagesInOutput(result?.output);
11587
12956
  },
11588
- extractMetadata: (result) => {
11589
- if (!result) {
11590
- return void 0;
11591
- }
11592
- const { output: _output, usage: _usage, ...metadata } = result;
11593
- return Object.keys(metadata).length > 0 ? metadata : void 0;
11594
- },
12957
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
11595
12958
  extractMetrics: (result, startTime, endEvent) => {
11596
12959
  const metrics = withCachedMetric(
11597
12960
  parseMetricsFromUsage(result?.usage),
@@ -11610,23 +12973,11 @@ var OpenAIPlugin = class extends BasePlugin {
11610
12973
  traceAsyncChannel(openAIChannels.responsesCompact, {
11611
12974
  name: "openai.responses.compact",
11612
12975
  type: "llm" /* LLM */,
11613
- extractInput: ([params]) => {
11614
- const { input, ...metadata } = params;
11615
- return {
11616
- input: processInputAttachments(input),
11617
- metadata: { ...metadata, provider: "openai" }
11618
- };
11619
- },
12976
+ extractInput: ([params]) => extractOpenAIResponsesInput(params),
11620
12977
  extractOutput: (result) => {
11621
12978
  return processImagesInOutput(result?.output);
11622
12979
  },
11623
- extractMetadata: (result) => {
11624
- if (!result) {
11625
- return void 0;
11626
- }
11627
- const { output: _output, usage: _usage, ...metadata } = result;
11628
- return Object.keys(metadata).length > 0 ? metadata : void 0;
11629
- },
12980
+ extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
11630
12981
  extractMetrics: (result, startTime, endEvent) => {
11631
12982
  const metrics = withCachedMetric(
11632
12983
  parseMetricsFromUsage(result?.usage),
@@ -11682,35 +13033,6 @@ function withCachedMetric(metrics, result, endEvent) {
11682
13033
  cached
11683
13034
  };
11684
13035
  }
11685
- function processImagesInOutput(output) {
11686
- if (Array.isArray(output)) {
11687
- return output.map(processImagesInOutput);
11688
- }
11689
- if (isObject(output)) {
11690
- if (output.type === "image_generation_call" && output.result && typeof output.result === "string") {
11691
- const fileExtension = output.output_format || "png";
11692
- const contentType = `image/${fileExtension}`;
11693
- const baseFilename = output.revised_prompt && typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
11694
- const filename = `${baseFilename}.${fileExtension}`;
11695
- const binaryString = atob(output.result);
11696
- const bytes = new Uint8Array(binaryString.length);
11697
- for (let i = 0; i < binaryString.length; i++) {
11698
- bytes[i] = binaryString.charCodeAt(i);
11699
- }
11700
- const blob = new Blob([bytes], { type: contentType });
11701
- const attachment = new Attachment({
11702
- data: blob,
11703
- filename,
11704
- contentType
11705
- });
11706
- return {
11707
- ...output,
11708
- result: attachment
11709
- };
11710
- }
11711
- }
11712
- return output;
11713
- }
11714
13036
  function mergeLogprobTokens(existing, incoming) {
11715
13037
  if (incoming === void 0) {
11716
13038
  return existing;
@@ -11741,13 +13063,33 @@ function aggregateChatLogprobs(existing, incoming) {
11741
13063
  }
11742
13064
  return aggregated;
11743
13065
  }
13066
+ function createAggregatedChatChoice(index) {
13067
+ return {
13068
+ index,
13069
+ role: void 0,
13070
+ content: void 0,
13071
+ refusal: void 0,
13072
+ toolCallsByIndex: /* @__PURE__ */ new Map(),
13073
+ logprobs: void 0,
13074
+ finish_reason: void 0
13075
+ };
13076
+ }
13077
+ function toChatChoice(choice) {
13078
+ const toolCalls = Array.from(choice.toolCallsByIndex.entries()).sort(([left], [right]) => left - right).map(([, toolCall]) => toolCall);
13079
+ return {
13080
+ index: choice.index,
13081
+ message: {
13082
+ role: choice.role,
13083
+ content: choice.content,
13084
+ ...choice.refusal !== void 0 ? { refusal: choice.refusal } : {},
13085
+ tool_calls: toolCalls.length > 0 ? toolCalls : void 0
13086
+ },
13087
+ logprobs: choice.logprobs ?? null,
13088
+ finish_reason: choice.finish_reason
13089
+ };
13090
+ }
11744
13091
  function aggregateChatCompletionChunks(chunks, streamResult, endEvent) {
11745
- let role = void 0;
11746
- let content = void 0;
11747
- let refusal = void 0;
11748
- let tool_calls = void 0;
11749
- let logprobs = void 0;
11750
- let finish_reason = void 0;
13092
+ const choicesByIndex = /* @__PURE__ */ new Map();
11751
13093
  let metrics = {};
11752
13094
  for (const chunk of chunks) {
11753
13095
  if (chunk.usage) {
@@ -11756,62 +13098,75 @@ function aggregateChatCompletionChunks(chunks, streamResult, endEvent) {
11756
13098
  ...parseMetricsFromUsage(chunk.usage)
11757
13099
  };
11758
13100
  }
11759
- const choice = chunk.choices?.[0];
11760
- if (!choice) {
11761
- continue;
11762
- }
11763
- if (choice.finish_reason) {
11764
- finish_reason = choice.finish_reason;
11765
- }
11766
- logprobs = aggregateChatLogprobs(logprobs, choice.logprobs);
11767
- const delta = choice.delta;
11768
- if (!delta) {
13101
+ const choices = chunk.choices;
13102
+ if (!choices?.length) {
11769
13103
  continue;
11770
13104
  }
11771
- if (delta.finish_reason) {
11772
- finish_reason = delta.finish_reason;
11773
- }
11774
- if (!role && delta.role) {
11775
- role = delta.role;
11776
- }
11777
- if (delta.content) {
11778
- content = (content || "") + delta.content;
11779
- }
11780
- if (delta.refusal) {
11781
- refusal = (refusal || "") + delta.refusal;
11782
- }
11783
- if (delta.tool_calls) {
11784
- const toolDelta = delta.tool_calls[0];
11785
- if (!tool_calls || toolDelta.id && tool_calls[tool_calls.length - 1].id !== toolDelta.id) {
11786
- tool_calls = [
11787
- ...tool_calls || [],
11788
- {
11789
- id: toolDelta.id,
11790
- type: toolDelta.type,
11791
- function: toolDelta.function
13105
+ for (const choice of choices) {
13106
+ const choiceIndex = choice.index;
13107
+ let aggregatedChoice = choicesByIndex.get(choiceIndex);
13108
+ if (!aggregatedChoice) {
13109
+ aggregatedChoice = createAggregatedChatChoice(choiceIndex);
13110
+ choicesByIndex.set(choiceIndex, aggregatedChoice);
13111
+ }
13112
+ if (choice.finish_reason) {
13113
+ aggregatedChoice.finish_reason = choice.finish_reason;
13114
+ }
13115
+ aggregatedChoice.logprobs = aggregateChatLogprobs(
13116
+ aggregatedChoice.logprobs,
13117
+ choice.logprobs
13118
+ );
13119
+ const delta = choice.delta;
13120
+ if (!delta) {
13121
+ continue;
13122
+ }
13123
+ if (delta.finish_reason) {
13124
+ aggregatedChoice.finish_reason = delta.finish_reason;
13125
+ }
13126
+ if (!aggregatedChoice.role && delta.role) {
13127
+ aggregatedChoice.role = delta.role;
13128
+ }
13129
+ if (delta.content) {
13130
+ aggregatedChoice.content = (aggregatedChoice.content || "") + delta.content;
13131
+ }
13132
+ if (delta.refusal) {
13133
+ aggregatedChoice.refusal = (aggregatedChoice.refusal || "") + delta.refusal;
13134
+ }
13135
+ if (delta.tool_calls) {
13136
+ for (const toolDelta of delta.tool_calls) {
13137
+ let aggregatedToolCall = aggregatedChoice.toolCallsByIndex.get(
13138
+ toolDelta.index
13139
+ );
13140
+ if (!aggregatedToolCall) {
13141
+ aggregatedToolCall = {
13142
+ function: { arguments: "" }
13143
+ };
13144
+ aggregatedChoice.toolCallsByIndex.set(
13145
+ toolDelta.index,
13146
+ aggregatedToolCall
13147
+ );
11792
13148
  }
11793
- ];
11794
- } else {
11795
- tool_calls[tool_calls.length - 1].function.arguments += toolDelta.function.arguments;
13149
+ if (toolDelta.id !== void 0) {
13150
+ aggregatedToolCall.id = toolDelta.id;
13151
+ }
13152
+ if (toolDelta.type !== void 0) {
13153
+ aggregatedToolCall.type = toolDelta.type;
13154
+ }
13155
+ if (toolDelta.function?.name !== void 0) {
13156
+ aggregatedToolCall.function.name = toolDelta.function.name;
13157
+ }
13158
+ if (toolDelta.function?.arguments !== void 0) {
13159
+ aggregatedToolCall.function.arguments += toolDelta.function.arguments;
13160
+ }
13161
+ }
11796
13162
  }
11797
13163
  }
11798
13164
  }
11799
13165
  metrics = withCachedMetric(metrics, streamResult, endEvent);
13166
+ const output = Array.from(choicesByIndex.values()).sort((left, right) => left.index - right.index).map(toChatChoice);
11800
13167
  return {
11801
13168
  metrics,
11802
- output: [
11803
- {
11804
- index: 0,
11805
- message: {
11806
- role,
11807
- content,
11808
- ...refusal !== void 0 ? { refusal } : {},
11809
- tool_calls
11810
- },
11811
- logprobs: logprobs ?? null,
11812
- finish_reason
11813
- }
11814
- ]
13169
+ output: output.length > 0 ? output : [toChatChoice(createAggregatedChatChoice(0))]
11815
13170
  };
11816
13171
  }
11817
13172
  function aggregateResponseStreamEvents(chunks, _streamResult, endEvent) {
@@ -13134,6 +14489,12 @@ function parseMetricsFromUsage2(usage) {
13134
14489
  }
13135
14490
  }
13136
14491
  }
14492
+ if (isObject(usage.output_tokens_details)) {
14493
+ const thinkingTokens = usage.output_tokens_details.thinking_tokens;
14494
+ if (typeof thinkingTokens === "number") {
14495
+ metrics.completion_reasoning_tokens = thinkingTokens;
14496
+ }
14497
+ }
13137
14498
  if (isObject(usage.server_tool_use)) {
13138
14499
  for (const [name, value] of Object.entries(usage.server_tool_use)) {
13139
14500
  if (typeof value === "number") {
@@ -14090,7 +15451,6 @@ function endHarnessTurn(parent) {
14090
15451
  function braintrustAISDKTelemetry() {
14091
15452
  const operations = /* @__PURE__ */ new Map();
14092
15453
  const operationKeysByCallId = /* @__PURE__ */ new Map();
14093
- const workflowOperationKeyStore = isomorph_default.newAsyncLocalStorage();
14094
15454
  const modelSpans = /* @__PURE__ */ new Map();
14095
15455
  const objectSpans = /* @__PURE__ */ new Map();
14096
15456
  const embedSpans = /* @__PURE__ */ new Map();
@@ -14133,9 +15493,6 @@ function braintrustAISDKTelemetry() {
14133
15493
  return;
14134
15494
  }
14135
15495
  operations.delete(operationKey);
14136
- if (workflowOperationKeyStore.getStore() === operationKey) {
14137
- workflowOperationKeyStore.enterWith(void 0);
14138
- }
14139
15496
  const keys = operationKeysByCallId.get(state.callId);
14140
15497
  if (!keys) {
14141
15498
  return;
@@ -14183,14 +15540,7 @@ function braintrustAISDKTelemetry() {
14183
15540
  return key;
14184
15541
  }
14185
15542
  }
14186
- const workflowOperationKey = workflowOperationKeyStore.getStore();
14187
- if (workflowOperationKey && keys.includes(workflowOperationKey)) {
14188
- return workflowOperationKey;
14189
- }
14190
- if (callId === "workflow-agent") {
14191
- return void 0;
14192
- }
14193
- return mode === "finish" ? keys[0] : keys[keys.length - 1];
15543
+ return callId === "workflow-agent" || mode === "active" ? keys[keys.length - 1] : keys[0];
14194
15544
  };
14195
15545
  const operationKeyFromEvent = (event, mode = "active") => {
14196
15546
  const explicit = explicitOperationKey(event);
@@ -14204,17 +15554,13 @@ function braintrustAISDKTelemetry() {
14204
15554
  if (operationKey) {
14205
15555
  return operationKey;
14206
15556
  }
14207
- const workflowOperationKey2 = workflowOperationKeyStore.getStore();
14208
- if (workflowOperationKey2 && operations.has(workflowOperationKey2)) {
14209
- return workflowOperationKey2;
15557
+ const workflowAgentKeys2 = operationKeysByCallId.get("workflow-agent");
15558
+ if (workflowAgentKeys2?.length) {
15559
+ return workflowAgentKeys2[workflowAgentKeys2.length - 1];
14210
15560
  }
14211
15561
  return callId === "workflow-agent" ? void 0 : callId;
14212
15562
  }
14213
15563
  }
14214
- const workflowOperationKey = workflowOperationKeyStore.getStore();
14215
- if (workflowOperationKey && operations.has(workflowOperationKey)) {
14216
- return workflowOperationKey;
14217
- }
14218
15564
  const wrapperSpan = currentWorkflowAgentWrapperSpan();
14219
15565
  if (wrapperSpan?.spanId) {
14220
15566
  for (const [operationKey, state] of operations) {
@@ -14224,8 +15570,8 @@ function braintrustAISDKTelemetry() {
14224
15570
  }
14225
15571
  }
14226
15572
  const workflowAgentKeys = operationKeysByCallId.get("workflow-agent");
14227
- if (workflowAgentKeys?.length === 1) {
14228
- return workflowAgentKeys[0];
15573
+ if (workflowAgentKeys?.length) {
15574
+ return workflowAgentKeys[workflowAgentKeys.length - 1];
14229
15575
  }
14230
15576
  if (operations.size === 1) {
14231
15577
  return operations.keys().next().value;
@@ -14428,9 +15774,6 @@ function braintrustAISDKTelemetry() {
14428
15774
  if (!ownsSpan) {
14429
15775
  return;
14430
15776
  }
14431
- if (workflowAgent) {
14432
- workflowOperationKeyStore.enterWith(operationKey);
14433
- }
14434
15777
  let metadata = metadataFromEvent(event);
14435
15778
  const logPayload = { metadata };
14436
15779
  const workflowAgentCallInput = workflowAgent ? operationInput(event, operationName) : void 0;
@@ -14902,6 +16245,10 @@ var aiSDKChannels = defineChannels(
14902
16245
  channelName: "generateText",
14903
16246
  kind: "async"
14904
16247
  }),
16248
+ generateImage: channel({
16249
+ channelName: "generateImage",
16250
+ kind: "async"
16251
+ }),
14905
16252
  streamText: channel({
14906
16253
  channelName: "streamText",
14907
16254
  kind: "async"
@@ -15089,7 +16436,7 @@ var AISDKPlugin = class extends BasePlugin {
15089
16436
  }
15090
16437
  subscribeToAISDK() {
15091
16438
  const denyOutputPaths = this.config.denyOutputPaths || DEFAULT_DENY_OUTPUT_PATHS;
15092
- this.unsubscribers.push(subscribeToAISDKV7TelemetryDispatcher());
16439
+ this.unsubscribers.push(interceptAISDKV7TelemetryDispatcher());
15093
16440
  this.unsubscribers.push(subscribeToHarnessAgentCreateSession());
15094
16441
  this.unsubscribers.push(
15095
16442
  subscribeToHarnessContinuation(
@@ -15117,6 +16464,18 @@ var AISDKPlugin = class extends BasePlugin {
15117
16464
  aggregateChunks: aggregateAISDKChunks
15118
16465
  })
15119
16466
  );
16467
+ this.unsubscribers.push(
16468
+ traceAsyncChannel(aiSDKChannels.generateImage, {
16469
+ name: "generateImage",
16470
+ type: "llm" /* LLM */,
16471
+ extractInput: ([params], event) => prepareAISDKGenerateImageInput(params, event.self),
16472
+ extractOutput: (result, endEvent) => processAISDKGenerateImageOutput(
16473
+ result,
16474
+ resolveDenyOutputPaths(endEvent, denyOutputPaths)
16475
+ ),
16476
+ extractMetrics: (result) => extractTokenMetrics(result)
16477
+ })
16478
+ );
15120
16479
  this.unsubscribers.push(
15121
16480
  traceStreamingChannel(aiSDKChannels.streamText, {
15122
16481
  name: "streamText",
@@ -15606,26 +16965,29 @@ function subscribeToHarnessContinuation(continuationChannel, defaultDenyOutputPa
15606
16965
  channel2.unsubscribe(handlers);
15607
16966
  };
15608
16967
  }
15609
- function subscribeToAISDKV7TelemetryDispatcher() {
15610
- const channel2 = aiSDKChannels.v7CreateTelemetryDispatcher.tracingChannel();
16968
+ function interceptAISDKV7TelemetryDispatcher() {
15611
16969
  const telemetry = braintrustAISDKTelemetry();
15612
- const handlers = {
15613
- end: (event) => {
15614
- const telemetryOptions = event.arguments?.[0]?.telemetry;
15615
- if (telemetryOptions?.isEnabled === false) {
15616
- return;
16970
+ return aiSDKChannels.v7CreateTelemetryDispatcher.intercept(
16971
+ (target, thisArg, args) => {
16972
+ const dispatcher = Reflect.apply(target, thisArg, args);
16973
+ const telemetryOptions = args[0]?.telemetry;
16974
+ if (telemetryOptions?.isEnabled !== false) {
16975
+ try {
16976
+ patchAISDKV7TelemetryDispatcher(
16977
+ dispatcher,
16978
+ telemetry,
16979
+ telemetryOptions
16980
+ );
16981
+ } catch (error) {
16982
+ debugLogger.error(
16983
+ "Error instrumenting AI SDK v7 telemetry dispatcher:",
16984
+ error
16985
+ );
16986
+ }
15617
16987
  }
15618
- patchAISDKV7TelemetryDispatcher(
15619
- event.result,
15620
- telemetry,
15621
- telemetryOptions
15622
- );
16988
+ return dispatcher;
15623
16989
  }
15624
- };
15625
- channel2.subscribe(handlers);
15626
- return () => {
15627
- channel2.unsubscribe(handlers);
15628
- };
16990
+ );
15629
16991
  }
15630
16992
  function patchAISDKV7TelemetryDispatcher(dispatcher, telemetry, telemetryOptions) {
15631
16993
  if (!isObject(dispatcher)) {
@@ -15950,16 +17312,10 @@ var convertImageToAttachment = (image, explicitMimeType) => {
15950
17312
  }
15951
17313
  }
15952
17314
  if (explicitMimeType) {
15953
- if (image instanceof Uint8Array) {
17315
+ const blob = convertDataToBlob(image, explicitMimeType);
17316
+ if (blob) {
15954
17317
  return new Attachment({
15955
- data: new Blob([image], { type: explicitMimeType }),
15956
- filename: `image.${getExtensionFromMediaType(explicitMimeType)}`,
15957
- contentType: explicitMimeType
15958
- });
15959
- }
15960
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(image)) {
15961
- return new Attachment({
15962
- data: new Blob([image], { type: explicitMimeType }),
17318
+ data: blob,
15963
17319
  filename: `image.${getExtensionFromMediaType(explicitMimeType)}`,
15964
17320
  contentType: explicitMimeType
15965
17321
  });
@@ -16013,6 +17369,25 @@ var convertDataToAttachment = (data, mimeType, filename) => {
16013
17369
  function processAISDKCallInput(params) {
16014
17370
  return processInputAttachmentsSync(params);
16015
17371
  }
17372
+ function processAISDKGenerateImageInput(params) {
17373
+ const prompt = params.prompt;
17374
+ if (!isObject(prompt) || Array.isArray(prompt)) {
17375
+ return processAISDKCallInput(params);
17376
+ }
17377
+ const processedPrompt = { ...prompt };
17378
+ if (Array.isArray(prompt.images)) {
17379
+ processedPrompt.images = prompt.images.map(
17380
+ (image) => convertImageToAttachment(image, "image/png") ?? image
17381
+ );
17382
+ }
17383
+ if (prompt.mask !== void 0) {
17384
+ processedPrompt.mask = convertImageToAttachment(prompt.mask, "image/png") ?? prompt.mask;
17385
+ }
17386
+ return processAISDKCallInput({
17387
+ ...params,
17388
+ prompt: processedPrompt
17389
+ });
17390
+ }
16016
17391
  function processAISDKWorkflowAgentCallInput(params) {
16017
17392
  const processed = processAISDKCallInput(params);
16018
17393
  return {
@@ -16151,6 +17526,12 @@ function prepareAISDKEmbedInput(params, self) {
16151
17526
  metadata: extractMetadataFromEmbedParams(params, self)
16152
17527
  };
16153
17528
  }
17529
+ function prepareAISDKGenerateImageInput(params, self) {
17530
+ return {
17531
+ input: processAISDKGenerateImageInput(params).input,
17532
+ metadata: extractMetadataFromCallParams(params, self)
17533
+ };
17534
+ }
16154
17535
  function prepareAISDKRerankInput(params, self) {
16155
17536
  const { documents, query } = params;
16156
17537
  return {
@@ -17526,6 +18907,57 @@ function processAISDKOutput(output, denyOutputPaths) {
17526
18907
  }
17527
18908
  return normalizeAISDKLoggedOutput(sanitized);
17528
18909
  }
18910
+ function processAISDKGenerateImageOutput(output, denyOutputPaths) {
18911
+ if (!output || typeof output !== "object") {
18912
+ return output;
18913
+ }
18914
+ const summarized = {};
18915
+ for (const field of [
18916
+ "usage",
18917
+ "warnings",
18918
+ "providerMetadata",
18919
+ "experimental_providerMetadata",
18920
+ "responses"
18921
+ ]) {
18922
+ const value = safeSerializableFieldRead(output, field);
18923
+ if (value !== void 0 && isSerializableOutputValue(value)) {
18924
+ summarized[field] = value;
18925
+ }
18926
+ }
18927
+ const images = safeSerializableFieldRead(output, "images");
18928
+ const image = safeSerializableFieldRead(output, "image");
18929
+ const generatedFiles = Array.isArray(images) && images.length > 0 ? images : image !== void 0 ? [image] : [];
18930
+ const loggedOutput = normalizeAISDKLoggedOutput(
18931
+ omit(summarized, denyOutputPaths)
18932
+ );
18933
+ if (generatedFiles.length > 0) {
18934
+ loggedOutput.images = generatedFiles.map(
18935
+ (file, index) => convertAISDKGeneratedFileToAttachment(file, index)
18936
+ );
18937
+ }
18938
+ return loggedOutput;
18939
+ }
18940
+ function convertAISDKGeneratedFileToAttachment(file, index) {
18941
+ if (!file || typeof file !== "object") {
18942
+ return file;
18943
+ }
18944
+ const generatedFile = file;
18945
+ const generatedMediaType = safeSerializableFieldRead(
18946
+ generatedFile,
18947
+ "mediaType"
18948
+ );
18949
+ const mediaType = typeof generatedMediaType === "string" ? generatedMediaType : "application/octet-stream";
18950
+ const data = safeSerializableFieldRead(generatedFile, "base64") ?? safeSerializableFieldRead(generatedFile, "uint8Array");
18951
+ const blob = convertDataToBlob(data, mediaType);
18952
+ if (blob) {
18953
+ return new Attachment({
18954
+ data: blob,
18955
+ filename: `generated_image_${index}.${getExtensionFromMediaType(mediaType)}`,
18956
+ contentType: mediaType
18957
+ });
18958
+ }
18959
+ return file;
18960
+ }
17529
18961
  function processAISDKEmbeddingOutput(output, denyOutputPaths) {
17530
18962
  if (!output || typeof output !== "object") {
17531
18963
  return output;
@@ -18026,118 +19458,22 @@ var claudeAgentSDKChannels = defineChannels(
18026
19458
  var CLAUDE_AGENT_SDK_SKIP_LOCAL_TOOL_HOOKS_OPTION = "__braintrust_skip_local_tool_hooks";
18027
19459
 
18028
19460
  // src/instrumentation/plugins/claude-agent-sdk-local-tool-context.ts
18029
- var LOCAL_TOOL_CONTEXT_ASYNC_ITERATOR_PATCHED = /* @__PURE__ */ Symbol.for(
18030
- "braintrust.claude_agent_sdk.local_tool_context_async_iterator_patched"
18031
- );
18032
- function createLocalToolContextStore() {
18033
- const maybeIsoWithAsyncLocalStorage = isomorph_default;
18034
- if (typeof maybeIsoWithAsyncLocalStorage.newAsyncLocalStorage === "function") {
18035
- return maybeIsoWithAsyncLocalStorage.newAsyncLocalStorage();
18036
- }
18037
- let currentStore;
18038
- return {
18039
- enterWith(store) {
18040
- currentStore = store;
18041
- },
18042
- getStore() {
18043
- return currentStore;
18044
- },
18045
- run(store, callback) {
18046
- const previousStore = currentStore;
18047
- currentStore = store;
18048
- try {
18049
- return callback();
18050
- } finally {
18051
- currentStore = previousStore;
18052
- }
18053
- }
18054
- };
18055
- }
18056
- var localToolContextStore = createLocalToolContextStore();
18057
- var fallbackLocalToolParentResolver;
18058
- function createClaudeLocalToolContext() {
18059
- return {};
18060
- }
18061
- function runWithClaudeLocalToolContext(callback, context) {
18062
- return localToolContextStore.run(
18063
- context ?? createClaudeLocalToolContext(),
18064
- callback
18065
- );
19461
+ var localToolContextStore = isomorph_default.newAsyncLocalStorage();
19462
+ var localToolParentResolversByToolUseId = /* @__PURE__ */ new Map();
19463
+ function runWithClaudeLocalToolContext(callback, resolver) {
19464
+ return localToolContextStore.run(resolver, callback);
18066
19465
  }
18067
- function ensureClaudeLocalToolContext() {
18068
- const existing = localToolContextStore.getStore();
18069
- if (existing) {
18070
- return existing;
18071
- }
18072
- const created = {};
18073
- localToolContextStore.enterWith(created);
18074
- return created;
18075
- }
18076
- function setClaudeLocalToolParentResolver(resolver) {
18077
- fallbackLocalToolParentResolver = resolver;
18078
- const context = ensureClaudeLocalToolContext();
18079
- if (!context) {
18080
- return;
18081
- }
18082
- context.resolveLocalToolParent = resolver;
18083
- }
18084
- function getClaudeLocalToolParentResolver() {
18085
- return localToolContextStore.getStore()?.resolveLocalToolParent ?? fallbackLocalToolParentResolver;
18086
- }
18087
- function isAsyncIterable3(value) {
18088
- return value !== null && typeof value === "object" && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
19466
+ function registerClaudeLocalToolParentResolver(toolUseId, resolver) {
19467
+ localToolParentResolversByToolUseId.set(toolUseId, resolver);
18089
19468
  }
18090
- function bindClaudeLocalToolContextToAsyncIterable(result, localToolContext) {
18091
- if (!isAsyncIterable3(result) || Object.isFrozen(result) || Object.isSealed(result)) {
18092
- return result;
18093
- }
18094
- const stream = result;
18095
- const originalAsyncIterator = stream[Symbol.asyncIterator];
18096
- if (originalAsyncIterator[LOCAL_TOOL_CONTEXT_ASYNC_ITERATOR_PATCHED]) {
18097
- return result;
19469
+ function getClaudeLocalToolParentResolver(toolUseId) {
19470
+ const currentResolver = localToolContextStore.getStore();
19471
+ if (!toolUseId) {
19472
+ return currentResolver;
18098
19473
  }
18099
- const patchedAsyncIterator = function() {
18100
- return runWithClaudeLocalToolContext(() => {
18101
- const iterator = Reflect.apply(originalAsyncIterator, this, []);
18102
- if (!iterator || typeof iterator !== "object") {
18103
- return iterator;
18104
- }
18105
- const patchMethod = (methodName) => {
18106
- const originalMethod = Reflect.get(iterator, methodName);
18107
- if (typeof originalMethod !== "function") {
18108
- return;
18109
- }
18110
- Reflect.set(
18111
- iterator,
18112
- methodName,
18113
- (...args) => runWithClaudeLocalToolContext(
18114
- () => Reflect.apply(
18115
- originalMethod,
18116
- iterator,
18117
- args
18118
- ),
18119
- localToolContext
18120
- )
18121
- );
18122
- };
18123
- patchMethod("next");
18124
- patchMethod("return");
18125
- patchMethod("throw");
18126
- return iterator;
18127
- }, localToolContext);
18128
- };
18129
- Object.defineProperty(
18130
- patchedAsyncIterator,
18131
- LOCAL_TOOL_CONTEXT_ASYNC_ITERATOR_PATCHED,
18132
- {
18133
- configurable: false,
18134
- enumerable: false,
18135
- value: true,
18136
- writable: false
18137
- }
18138
- );
18139
- Reflect.set(stream, Symbol.asyncIterator, patchedAsyncIterator);
18140
- return result;
19474
+ const registeredResolver = localToolParentResolversByToolUseId.get(toolUseId);
19475
+ localToolParentResolversByToolUseId.delete(toolUseId);
19476
+ return currentResolver ?? registeredResolver;
18141
19477
  }
18142
19478
 
18143
19479
  // src/instrumentation/plugins/claude-agent-sdk-local-tool-spans.ts
@@ -18163,7 +19499,7 @@ function wrapLocalClaudeToolHandler(handler, getMetadata) {
18163
19499
  const metadata = getMetadata();
18164
19500
  const rawToolName = metadata.serverName ? `mcp__${metadata.serverName}__${metadata.toolName}` : metadata.toolName;
18165
19501
  const toolUseId = getToolUseIdFromExtra(handlerArgs[1]);
18166
- const localToolParentResolver = getClaudeLocalToolParentResolver();
19502
+ const localToolParentResolver = getClaudeLocalToolParentResolver(toolUseId);
18167
19503
  const spanName = metadata.serverName ? `tool: ${metadata.serverName}/${metadata.toolName}` : `tool: ${metadata.toolName}`;
18168
19504
  const runWithResolvedParent = async () => {
18169
19505
  const parent = toolUseId && localToolParentResolver ? await localToolParentResolver(toolUseId).catch(() => void 0) : void 0;
@@ -18690,6 +20026,7 @@ function createToolTracingHooks(resolveParentSpan, taskIdToToolUseId, toolUseToP
18690
20026
  }
18691
20027
  }
18692
20028
  if (skipLocalToolHooks && (isLocalToolUse(input.tool_name, mcpServers) || localToolHookNames.has(input.tool_name))) {
20029
+ registerClaudeLocalToolParentResolver(toolUseID, resolveParentSpan);
18693
20030
  return {};
18694
20031
  }
18695
20032
  const parsed = parseToolName(input.tool_name);
@@ -19400,7 +20737,7 @@ async function finalizeQuerySpan(state) {
19400
20737
  }
19401
20738
  var ClaudeAgentSDKPlugin = class extends BasePlugin {
19402
20739
  onEnable() {
19403
- this.subscribeToQuery();
20740
+ this.interceptQuery();
19404
20741
  }
19405
20742
  onDisable() {
19406
20743
  for (const unsubscribe of this.unsubscribers) {
@@ -19408,211 +20745,218 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
19408
20745
  }
19409
20746
  this.unsubscribers = [];
19410
20747
  }
19411
- subscribeToQuery() {
19412
- const channel2 = claudeAgentSDKChannels.query.tracingChannel();
19413
- const spans = /* @__PURE__ */ new WeakMap();
19414
- const handlers = {
19415
- start: (event) => {
19416
- const params = event.arguments[0] ?? {};
19417
- const originalPrompt = params.prompt;
19418
- const options = params.options ?? {};
19419
- const promptIsAsyncIterable = isAsyncIterable(originalPrompt);
19420
- let promptStarted = false;
19421
- let capturedPromptMessages;
19422
- let resolvePromptDone;
19423
- const promptDone = new Promise((resolve) => {
19424
- resolvePromptDone = resolve;
19425
- });
19426
- if (promptIsAsyncIterable) {
19427
- capturedPromptMessages = [];
19428
- const promptStream = originalPrompt;
19429
- params.prompt = (async function* () {
19430
- promptStarted = true;
19431
- try {
19432
- for await (const message of promptStream) {
19433
- capturedPromptMessages.push(message);
19434
- yield message;
19435
- }
19436
- } finally {
19437
- resolvePromptDone?.();
20748
+ interceptQuery() {
20749
+ const startQuery = (params) => {
20750
+ const originalPrompt = params.prompt;
20751
+ const options = params.options ?? {};
20752
+ const promptIsAsyncIterable = isAsyncIterable(originalPrompt);
20753
+ let promptStarted = false;
20754
+ let capturedPromptMessages;
20755
+ let resolvePromptDone;
20756
+ const promptDone = new Promise((resolve) => {
20757
+ resolvePromptDone = resolve;
20758
+ });
20759
+ if (promptIsAsyncIterable) {
20760
+ capturedPromptMessages = [];
20761
+ const promptStream = originalPrompt;
20762
+ params.prompt = (async function* () {
20763
+ promptStarted = true;
20764
+ try {
20765
+ for await (const message of promptStream) {
20766
+ capturedPromptMessages.push(message);
20767
+ yield message;
19438
20768
  }
19439
- })();
19440
- }
19441
- const span = startSpan(
19442
- withSpanInstrumentationName(
19443
- {
19444
- name: "Claude Agent",
19445
- spanAttributes: {
19446
- type: "task" /* TASK */
19447
- }
19448
- },
19449
- INSTRUMENTATION_NAMES.CLAUDE_AGENT_SDK
19450
- )
19451
- );
19452
- const startTime = getCurrentUnixTimestamp();
19453
- try {
19454
- span.log({
19455
- input: typeof originalPrompt === "string" ? originalPrompt : promptIsAsyncIterable ? void 0 : originalPrompt !== void 0 ? String(originalPrompt) : void 0,
19456
- metadata: filterSerializableOptions(options)
19457
- });
19458
- } catch (error) {
19459
- console.error("Error extracting input for Claude Agent SDK:", error);
19460
- }
19461
- const activeToolSpans = /* @__PURE__ */ new Map();
19462
- const activeLlmSpansByParentToolUse = /* @__PURE__ */ new Map();
19463
- const conversationHistoryByParentKey = /* @__PURE__ */ new Map();
19464
- const subAgentSpans = /* @__PURE__ */ new Map();
19465
- const endedSubAgentSpans = /* @__PURE__ */ new Set();
19466
- const toolUseToParent = /* @__PURE__ */ new Map();
19467
- const latestLlmParentBySubAgentToolUse = /* @__PURE__ */ new Map();
19468
- const latestRootLlmParentRef = {
19469
- value: void 0
19470
- };
19471
- const subAgentDetailsByToolUseId = /* @__PURE__ */ new Map();
19472
- const taskIdToToolUseId = /* @__PURE__ */ new Map();
19473
- const promptMessagesByParentKey = /* @__PURE__ */ new Map();
19474
- const promptSourcePriorityByParentKey = /* @__PURE__ */ new Map();
19475
- const localToolContext = createClaudeLocalToolContext();
19476
- const { hasLocalToolHandlers, localToolHookNames } = prepareLocalToolHandlersInMcpServers(options.mcpServers);
19477
- const skipLocalToolHooks = options[CLAUDE_AGENT_SDK_SKIP_LOCAL_TOOL_HOOKS_OPTION] === true || hasLocalToolHandlers;
19478
- const resolveToolUseParentSpan = async (toolUseID, context) => {
19479
- const trackedParentToolUseId = toolUseToParent.get(toolUseID);
19480
- const parentToolUseId = trackedParentToolUseId ?? (context?.agentId ? taskIdToToolUseId.get(context.agentId) ?? null : null);
19481
- const parentKey = llmParentKey(parentToolUseId);
19482
- const activeLlmSpan = activeLlmSpansByParentToolUse.get(parentKey);
19483
- const latestLlmParent = parentToolUseId ? latestLlmParentBySubAgentToolUse.get(parentToolUseId) : latestRootLlmParentRef.value;
19484
- if (!activeLlmSpan && !latestLlmParent) {
19485
- await ensureActiveLlmSpanForParentToolUse(
19486
- span,
19487
- activeLlmSpansByParentToolUse,
19488
- subAgentDetailsByToolUseId,
19489
- activeToolSpans,
19490
- subAgentSpans,
19491
- parentToolUseId,
19492
- getCurrentUnixTimestamp()
19493
- );
19494
- }
19495
- if (parentToolUseId) {
19496
- const subAgentSpan = await ensureSubAgentSpan(
19497
- subAgentDetailsByToolUseId,
19498
- span,
19499
- activeToolSpans,
19500
- subAgentSpans,
19501
- parentToolUseId
19502
- );
19503
- return subAgentSpan.export();
20769
+ } finally {
20770
+ resolvePromptDone?.();
19504
20771
  }
19505
- return span.export();
19506
- };
19507
- localToolContext.resolveLocalToolParent = resolveToolUseParentSpan;
19508
- setClaudeLocalToolParentResolver(resolveToolUseParentSpan);
19509
- const optionsWithHooks = injectTracingHooks(
19510
- options,
19511
- resolveToolUseParentSpan,
19512
- taskIdToToolUseId,
19513
- toolUseToParent,
19514
- activeToolSpans,
19515
- localToolHookNames,
19516
- skipLocalToolHooks,
19517
- subAgentDetailsByToolUseId,
19518
- subAgentSpans,
19519
- endedSubAgentSpans
19520
- );
19521
- params.options = optionsWithHooks;
19522
- event.arguments[0] = params;
19523
- spans.set(event, {
19524
- activeLlmSpansByParentToolUse,
19525
- activePartialMessageIdByParentKey: /* @__PURE__ */ new Map(),
19526
- activeToolSpans,
19527
- conversationHistoryByParentKey,
19528
- capturedPromptMessages,
19529
- currentMessageId: void 0,
19530
- currentMessageStartTime: startTime,
19531
- currentMessages: [],
19532
- endedSubAgentSpans,
19533
- finalOutputUsageMessageIds: /* @__PURE__ */ new Set(),
19534
- finalResults: [],
19535
- options: optionsWithHooks,
19536
- originalPrompt,
19537
- processing: Promise.resolve(),
19538
- promptDone,
19539
- promptMessagesByParentKey,
19540
- promptStarted: () => promptStarted,
19541
- promptSourcePriorityByParentKey,
19542
- span,
19543
- subAgentDetailsByToolUseId,
19544
- subAgentSpans,
19545
- taskIdToToolUseId,
19546
- latestLlmParentBySubAgentToolUse,
19547
- latestRootLlmParentRef,
19548
- toolUseToParent,
19549
- usageByMessageId: /* @__PURE__ */ new Map(),
19550
- localToolContext
20772
+ })();
20773
+ }
20774
+ const span = startSpan(
20775
+ withSpanInstrumentationName(
20776
+ {
20777
+ name: "Claude Agent",
20778
+ spanAttributes: {
20779
+ type: "task" /* TASK */
20780
+ }
20781
+ },
20782
+ INSTRUMENTATION_NAMES.CLAUDE_AGENT_SDK
20783
+ )
20784
+ );
20785
+ const startTime = getCurrentUnixTimestamp();
20786
+ try {
20787
+ span.log({
20788
+ input: typeof originalPrompt === "string" ? originalPrompt : promptIsAsyncIterable ? void 0 : originalPrompt !== void 0 ? String(originalPrompt) : void 0,
20789
+ metadata: filterSerializableOptions(options)
19551
20790
  });
19552
- },
19553
- end: (event) => {
19554
- const state = spans.get(event);
19555
- if (!state) {
19556
- return;
19557
- }
19558
- const eventResult = bindClaudeLocalToolContextToAsyncIterable(
19559
- event.result,
19560
- state.localToolContext
19561
- );
19562
- if (eventResult === void 0) {
19563
- state.span.end();
19564
- spans.delete(event);
19565
- return;
19566
- }
19567
- if (isAsyncIterable(eventResult)) {
19568
- patchStreamIfNeeded(eventResult, {
19569
- onChunk: (message) => {
19570
- maybeTrackToolUseContext(state, message);
19571
- state.processing = state.processing.then(() => handleStreamMessage(state, message)).catch((error) => {
19572
- console.error(
19573
- "Error processing Claude Agent SDK stream chunk:",
19574
- error
19575
- );
19576
- });
19577
- },
19578
- onComplete: () => state.processing.then(() => finalizeQuerySpan(state)).finally(() => {
19579
- spans.delete(event);
19580
- }),
19581
- onError: (error) => state.processing.then(() => {
19582
- state.span.log({
19583
- error: error.message
19584
- });
19585
- }).then(() => finalizeQuerySpan(state)).finally(() => {
19586
- spans.delete(event);
19587
- })
19588
- });
19589
- return;
19590
- }
19591
- try {
19592
- state.span.log({ output: eventResult });
19593
- } catch (error) {
19594
- console.error("Error extracting output for Claude Agent SDK:", error);
19595
- } finally {
19596
- state.span.end();
19597
- spans.delete(event);
20791
+ } catch (error) {
20792
+ console.error("Error extracting input for Claude Agent SDK:", error);
20793
+ }
20794
+ const activeToolSpans = /* @__PURE__ */ new Map();
20795
+ const activeLlmSpansByParentToolUse = /* @__PURE__ */ new Map();
20796
+ const conversationHistoryByParentKey = /* @__PURE__ */ new Map();
20797
+ const subAgentSpans = /* @__PURE__ */ new Map();
20798
+ const endedSubAgentSpans = /* @__PURE__ */ new Set();
20799
+ const toolUseToParent = /* @__PURE__ */ new Map();
20800
+ const latestLlmParentBySubAgentToolUse = /* @__PURE__ */ new Map();
20801
+ const latestRootLlmParentRef = {
20802
+ value: void 0
20803
+ };
20804
+ const subAgentDetailsByToolUseId = /* @__PURE__ */ new Map();
20805
+ const taskIdToToolUseId = /* @__PURE__ */ new Map();
20806
+ const promptMessagesByParentKey = /* @__PURE__ */ new Map();
20807
+ const promptSourcePriorityByParentKey = /* @__PURE__ */ new Map();
20808
+ const { hasLocalToolHandlers, localToolHookNames } = prepareLocalToolHandlersInMcpServers(options.mcpServers);
20809
+ const skipLocalToolHooks = options[CLAUDE_AGENT_SDK_SKIP_LOCAL_TOOL_HOOKS_OPTION] === true || hasLocalToolHandlers;
20810
+ const resolveToolUseParentSpan = async (toolUseID, context) => {
20811
+ const trackedParentToolUseId = toolUseToParent.get(toolUseID);
20812
+ const parentToolUseId = trackedParentToolUseId ?? (context?.agentId ? taskIdToToolUseId.get(context.agentId) ?? null : null);
20813
+ const parentKey = llmParentKey(parentToolUseId);
20814
+ const activeLlmSpan = activeLlmSpansByParentToolUse.get(parentKey);
20815
+ const latestLlmParent = parentToolUseId ? latestLlmParentBySubAgentToolUse.get(parentToolUseId) : latestRootLlmParentRef.value;
20816
+ if (!activeLlmSpan && !latestLlmParent) {
20817
+ await ensureActiveLlmSpanForParentToolUse(
20818
+ span,
20819
+ activeLlmSpansByParentToolUse,
20820
+ subAgentDetailsByToolUseId,
20821
+ activeToolSpans,
20822
+ subAgentSpans,
20823
+ parentToolUseId,
20824
+ getCurrentUnixTimestamp()
20825
+ );
19598
20826
  }
19599
- },
19600
- error: (event) => {
19601
- const state = spans.get(event);
19602
- if (!state || !event.error) {
19603
- return;
20827
+ if (parentToolUseId) {
20828
+ const subAgentSpan = await ensureSubAgentSpan(
20829
+ subAgentDetailsByToolUseId,
20830
+ span,
20831
+ activeToolSpans,
20832
+ subAgentSpans,
20833
+ parentToolUseId
20834
+ );
20835
+ return subAgentSpan.export();
19604
20836
  }
19605
- state.span.log({
19606
- error: event.error.message
20837
+ return span.export();
20838
+ };
20839
+ const optionsWithHooks = injectTracingHooks(
20840
+ options,
20841
+ resolveToolUseParentSpan,
20842
+ taskIdToToolUseId,
20843
+ toolUseToParent,
20844
+ activeToolSpans,
20845
+ localToolHookNames,
20846
+ skipLocalToolHooks,
20847
+ subAgentDetailsByToolUseId,
20848
+ subAgentSpans,
20849
+ endedSubAgentSpans
20850
+ );
20851
+ params.options = optionsWithHooks;
20852
+ return {
20853
+ activeLlmSpansByParentToolUse,
20854
+ activePartialMessageIdByParentKey: /* @__PURE__ */ new Map(),
20855
+ activeToolSpans,
20856
+ conversationHistoryByParentKey,
20857
+ capturedPromptMessages,
20858
+ currentMessageId: void 0,
20859
+ currentMessageStartTime: startTime,
20860
+ currentMessages: [],
20861
+ endedSubAgentSpans,
20862
+ finalOutputUsageMessageIds: /* @__PURE__ */ new Set(),
20863
+ finalResults: [],
20864
+ options: optionsWithHooks,
20865
+ originalPrompt,
20866
+ processing: Promise.resolve(),
20867
+ promptDone,
20868
+ promptMessagesByParentKey,
20869
+ promptStarted: () => promptStarted,
20870
+ promptSourcePriorityByParentKey,
20871
+ span,
20872
+ subAgentDetailsByToolUseId,
20873
+ subAgentSpans,
20874
+ taskIdToToolUseId,
20875
+ latestLlmParentBySubAgentToolUse,
20876
+ latestRootLlmParentRef,
20877
+ toolUseToParent,
20878
+ usageByMessageId: /* @__PURE__ */ new Map(),
20879
+ localToolParentResolver: resolveToolUseParentSpan
20880
+ };
20881
+ };
20882
+ const finishQuery = (state, result) => {
20883
+ if (isAsyncIterable(result)) {
20884
+ patchStreamIfNeeded(result, {
20885
+ aroundNext: (callback) => runWithClaudeLocalToolContext(
20886
+ callback,
20887
+ state.localToolParentResolver
20888
+ ),
20889
+ onChunk: (message) => {
20890
+ maybeTrackToolUseContext(state, message);
20891
+ state.processing = state.processing.then(() => handleStreamMessage(state, message)).catch((error) => {
20892
+ console.error(
20893
+ "Error processing Claude Agent SDK stream chunk:",
20894
+ error
20895
+ );
20896
+ });
20897
+ },
20898
+ onComplete: () => state.processing.then(() => finalizeQuerySpan(state)),
20899
+ onError: (error) => state.processing.then(() => {
20900
+ state.span.log({ error: error.message });
20901
+ }).then(() => finalizeQuerySpan(state))
19607
20902
  });
20903
+ return;
20904
+ }
20905
+ try {
20906
+ state.span.log({ output: result });
20907
+ } catch (error) {
20908
+ console.error("Error extracting output for Claude Agent SDK:", error);
20909
+ } finally {
19608
20910
  state.span.end();
19609
- spans.delete(event);
19610
20911
  }
19611
20912
  };
19612
- channel2.subscribe(handlers);
19613
- this.unsubscribers.push(() => {
19614
- channel2.unsubscribe(handlers);
19615
- });
20913
+ this.unsubscribers.push(
20914
+ claudeAgentSDKChannels.query.intercept((target, thisArg, args) => {
20915
+ let state;
20916
+ try {
20917
+ args[0] ??= {};
20918
+ state = startQuery(args[0]);
20919
+ } catch (error) {
20920
+ debugLogger.error(
20921
+ "Error starting Claude Agent SDK instrumentation:",
20922
+ error
20923
+ );
20924
+ }
20925
+ const invokeTarget = () => Reflect.apply(target, thisArg, args);
20926
+ try {
20927
+ const result = state ? runWithClaudeLocalToolContext(
20928
+ invokeTarget,
20929
+ state.localToolParentResolver
20930
+ ) : invokeTarget();
20931
+ if (state) {
20932
+ try {
20933
+ finishQuery(state, result);
20934
+ } catch (error) {
20935
+ debugLogger.error(
20936
+ "Error finalizing Claude Agent SDK instrumentation:",
20937
+ error
20938
+ );
20939
+ }
20940
+ }
20941
+ return result;
20942
+ } catch (error) {
20943
+ if (state) {
20944
+ try {
20945
+ state.span.log({
20946
+ error: error instanceof Error ? error.message : String(error)
20947
+ });
20948
+ state.span.end();
20949
+ } catch (instrumentationError) {
20950
+ debugLogger.error(
20951
+ "Error handling Claude Agent SDK instrumentation failure:",
20952
+ instrumentationError
20953
+ );
20954
+ }
20955
+ }
20956
+ throw error;
20957
+ }
20958
+ })
20959
+ );
19616
20960
  }
19617
20961
  };
19618
20962
 
@@ -23726,7 +25070,7 @@ function patchOpenRouterCallModelResult(args) {
23726
25070
  span,
23727
25071
  () => originalMethod.apply(resultLike, args2)
23728
25072
  );
23729
- if (!isAsyncIterable4(stream)) {
25073
+ if (!isAsyncIterable3(stream)) {
23730
25074
  return stream;
23731
25075
  }
23732
25076
  return wrapAsyncIterableWithSpan({
@@ -23921,7 +25265,7 @@ function wrapAsyncIterableWithSpan(args) {
23921
25265
  }
23922
25266
  };
23923
25267
  }
23924
- function isAsyncIterable4(value) {
25268
+ function isAsyncIterable3(value) {
23925
25269
  return !!value && (typeof value === "object" || typeof value === "function") && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
23926
25270
  }
23927
25271
  function normalizeError(error) {
@@ -24799,7 +26143,7 @@ function patchOpenRouterCallModelResult2(args) {
24799
26143
  span,
24800
26144
  () => originalMethod.apply(resultLike, args2)
24801
26145
  );
24802
- if (!isAsyncIterable5(stream)) {
26146
+ if (!isAsyncIterable4(stream)) {
24803
26147
  return stream;
24804
26148
  }
24805
26149
  return wrapAsyncIterableWithSpan2({
@@ -24994,7 +26338,7 @@ function wrapAsyncIterableWithSpan2(args) {
24994
26338
  }
24995
26339
  };
24996
26340
  }
24997
- function isAsyncIterable5(value) {
26341
+ function isAsyncIterable4(value) {
24998
26342
  return !!value && (typeof value === "object" || typeof value === "function") && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
24999
26343
  }
25000
26344
  function normalizeError2(error) {
@@ -30986,12 +32330,14 @@ function getMetricsFromResponse(response) {
30986
32330
  continue;
30987
32331
  }
30988
32332
  const inputTokenDetails = usageMetadata.input_token_details;
32333
+ const outputTokenDetails = usageMetadata.output_token_details;
30989
32334
  return normalizeTokenMetrics({
30990
32335
  total_tokens: usageMetadata.total_tokens,
30991
32336
  prompt_tokens: usageMetadata.input_tokens,
30992
32337
  completion_tokens: usageMetadata.output_tokens,
30993
32338
  prompt_cache_creation_tokens: isRecord(inputTokenDetails) ? inputTokenDetails.cache_creation : void 0,
30994
- prompt_cached_tokens: isRecord(inputTokenDetails) ? inputTokenDetails.cache_read : void 0
32339
+ prompt_cached_tokens: isRecord(inputTokenDetails) ? inputTokenDetails.cache_read : void 0,
32340
+ completion_reasoning_tokens: isRecord(outputTokenDetails) ? outputTokenDetails.reasoning : void 0
30995
32341
  });
30996
32342
  }
30997
32343
  const llmOutput = response.llmOutput || {};
@@ -31578,6 +32924,9 @@ var piCodingAgentChannels = defineChannels(
31578
32924
  // src/instrumentation/plugins/pi-coding-agent-plugin.ts
31579
32925
  var piAgentPatchStates = /* @__PURE__ */ new WeakMap();
31580
32926
  var piAgentEventSubscriptions = /* @__PURE__ */ new WeakSet();
32927
+ var PI_TOOL_EXECUTE_WRAPPED = /* @__PURE__ */ Symbol.for(
32928
+ "braintrust.pi_coding_agent.tool_execute_wrapped"
32929
+ );
31581
32930
  var piPromptContextStore;
31582
32931
  var PiCodingAgentPlugin = class extends BasePlugin {
31583
32932
  activePromptStates = /* @__PURE__ */ new Set();
@@ -31658,6 +33007,7 @@ function startPiPromptRun(event, onFinalize) {
31658
33007
  return void 0;
31659
33008
  }
31660
33009
  installPiAgentInstrumentation(agent);
33010
+ wrapPiToolExecutors(agent.state?.tools);
31661
33011
  const metadata = {
31662
33012
  ...extractSessionMetadata(session),
31663
33013
  ...extractPromptOptionsMetadata(event.arguments[1]),
@@ -31702,7 +33052,7 @@ function extractSession(event) {
31702
33052
  return isObject(candidate) && typeof candidate.prompt === "function" ? candidate : void 0;
31703
33053
  }
31704
33054
  function isPiAgent(value) {
31705
- return isObject(value) && typeof value.streamFn === "function" && typeof value.subscribe === "function";
33055
+ return isObject(value) && (typeof value.streamFunction === "function" || typeof value.streamFn === "function") && typeof value.subscribe === "function";
31706
33056
  }
31707
33057
  function promptContextStore() {
31708
33058
  piPromptContextStore ??= isomorph_default.newAsyncLocalStorage();
@@ -31712,17 +33062,21 @@ function currentPiPromptState() {
31712
33062
  return promptContextStore().getStore();
31713
33063
  }
31714
33064
  function installPiAgentInstrumentation(agent) {
33065
+ const property = typeof agent.streamFunction === "function" ? "streamFunction" : "streamFn";
33066
+ const streamFunction = agent[property];
31715
33067
  const existing = piAgentPatchStates.get(agent);
31716
- if (!existing || agent.streamFn !== existing.wrappedStreamFn) {
33068
+ if (streamFunction && (!existing || existing.property !== property || streamFunction !== existing.wrappedStreamFunction)) {
31717
33069
  const patchState = {
31718
- originalStreamFn: agent.streamFn,
31719
- wrappedStreamFn: agent.streamFn
33070
+ originalStreamFunction: streamFunction,
33071
+ property,
33072
+ wrappedStreamFunction: streamFunction
31720
33073
  };
31721
- patchState.wrappedStreamFn = makeInstrumentedStreamFn(
33074
+ patchState.wrappedStreamFunction = makeInstrumentedStreamFunction(
31722
33075
  agent,
31723
- patchState.originalStreamFn
33076
+ patchState.originalStreamFunction,
33077
+ property
31724
33078
  );
31725
- agent.streamFn = patchState.wrappedStreamFn;
33079
+ agent[property] = patchState.wrappedStreamFunction;
31726
33080
  piAgentPatchStates.set(agent, patchState);
31727
33081
  }
31728
33082
  if (piAgentEventSubscriptions.has(agent)) {
@@ -31747,14 +33101,21 @@ function installPiAgentInstrumentation(agent) {
31747
33101
  logInstrumentationError4("Pi Coding Agent event subscription", error);
31748
33102
  }
31749
33103
  }
31750
- function makeInstrumentedStreamFn(agent, originalStreamFn) {
31751
- return async function instrumentedPiStreamFn(model, context, options) {
31752
- const invokeOriginal = () => Reflect.apply(originalStreamFn, this, [model, context, options]);
33104
+ function makeInstrumentedStreamFunction(agent, originalStreamFunction, property) {
33105
+ return async function instrumentedPiStreamFunction(model, context, options) {
33106
+ const invokeOriginal = () => Reflect.apply(originalStreamFunction, this, [model, context, options]);
31753
33107
  const state = currentPiPromptState();
31754
33108
  if (!state || state.agent !== agent || state.finalized) {
31755
33109
  return invokeOriginal();
31756
33110
  }
31757
- const llmState = await startPiLlmSpan(state, model, context, options);
33111
+ wrapPiToolExecutors(context.tools);
33112
+ const llmState = await startPiLlmSpan(
33113
+ state,
33114
+ model,
33115
+ context,
33116
+ property,
33117
+ options
33118
+ );
31758
33119
  try {
31759
33120
  const stream = await runWithAutoInstrumentationSuppressed(invokeOriginal);
31760
33121
  return patchAssistantMessageStream(stream, state, llmState);
@@ -31764,12 +33125,39 @@ function makeInstrumentedStreamFn(agent, originalStreamFn) {
31764
33125
  }
31765
33126
  };
31766
33127
  }
31767
- async function startPiLlmSpan(state, model, context, options) {
33128
+ function wrapPiToolExecutors(tools) {
33129
+ if (!tools) {
33130
+ return;
33131
+ }
33132
+ for (const tool of tools) {
33133
+ try {
33134
+ const execute = tool.execute;
33135
+ if (typeof execute !== "function" || execute[PI_TOOL_EXECUTE_WRAPPED]) {
33136
+ continue;
33137
+ }
33138
+ const wrappedExecute = function(...args) {
33139
+ return runWithAutoInstrumentationAllowed(
33140
+ () => Reflect.apply(execute, this, args)
33141
+ );
33142
+ };
33143
+ Object.defineProperty(wrappedExecute, PI_TOOL_EXECUTE_WRAPPED, {
33144
+ configurable: false,
33145
+ enumerable: false,
33146
+ value: true,
33147
+ writable: false
33148
+ });
33149
+ tool.execute = wrappedExecute;
33150
+ } catch (error) {
33151
+ logInstrumentationError4("Pi Coding Agent tool wrapping", error);
33152
+ }
33153
+ }
33154
+ }
33155
+ async function startPiLlmSpan(state, model, context, property, options) {
31768
33156
  const metadata = {
31769
33157
  ...extractModelMetadata2(model),
31770
33158
  ...extractStreamOptionsMetadata(options),
31771
33159
  ...extractToolMetadata(context.tools),
31772
- "pi_coding_agent.operation": "agent.streamFn"
33160
+ "pi_coding_agent.operation": `agent.${property}`
31773
33161
  };
31774
33162
  const span = startSpan(
31775
33163
  withSpanInstrumentationName(
@@ -31935,35 +33323,26 @@ async function startPiToolSpan(state, event) {
31935
33323
  if (!event.toolCallId || state.activeToolSpans.has(event.toolCallId)) {
31936
33324
  return;
31937
33325
  }
31938
- const restoreAutoInstrumentation = enterAutoInstrumentationAllowed();
31939
33326
  const metadata = {
31940
33327
  "gen_ai.tool.call.id": event.toolCallId,
31941
33328
  "gen_ai.tool.name": event.toolName,
31942
33329
  "pi_coding_agent.tool.name": event.toolName
31943
33330
  };
31944
- try {
31945
- const span = startSpan(
31946
- withSpanInstrumentationName(
31947
- {
31948
- event: {
31949
- input: event.args,
31950
- metadata
31951
- },
31952
- name: event.toolName || "tool",
31953
- parent: await state.span.export(),
31954
- spanAttributes: { type: "tool" /* TOOL */ }
33331
+ const span = startSpan(
33332
+ withSpanInstrumentationName(
33333
+ {
33334
+ event: {
33335
+ input: event.args,
33336
+ metadata
31955
33337
  },
31956
- INSTRUMENTATION_NAMES.PI_CODING_AGENT
31957
- )
31958
- );
31959
- state.activeToolSpans.set(event.toolCallId, {
31960
- restoreAutoInstrumentation,
31961
- span
31962
- });
31963
- } catch (error) {
31964
- restoreAutoInstrumentation();
31965
- throw error;
31966
- }
33338
+ name: event.toolName || "tool",
33339
+ parent: await state.span.export(),
33340
+ spanAttributes: { type: "tool" /* TOOL */ }
33341
+ },
33342
+ INSTRUMENTATION_NAMES.PI_CODING_AGENT
33343
+ )
33344
+ );
33345
+ state.activeToolSpans.set(event.toolCallId, { span });
31967
33346
  }
31968
33347
  function finishPiToolSpan(state, event) {
31969
33348
  const toolState = state.activeToolSpans.get(event.toolCallId);
@@ -31984,11 +33363,7 @@ function finishPiToolSpan(state, event) {
31984
33363
  output: event.result
31985
33364
  });
31986
33365
  } finally {
31987
- try {
31988
- toolState.span.end();
31989
- } finally {
31990
- toolState.restoreAutoInstrumentation?.();
31991
- }
33366
+ toolState.span.end();
31992
33367
  }
31993
33368
  }
31994
33369
  function finishPiPromptRun(state, error) {
@@ -32038,10 +33413,10 @@ function finishPiLlmSpan(promptState, llmState, message, error) {
32038
33413
  ...cleanMetrics5(llmState.metrics),
32039
33414
  ...buildDurationMetrics3(llmState.startTime)
32040
33415
  };
32041
- const usageMetrics = extractUsageMetrics2(message?.usage);
32042
- if (Object.keys(usageMetrics).length > 0) {
33416
+ const usageMetrics2 = extractUsageMetrics2(message?.usage);
33417
+ if (Object.keys(usageMetrics2).length > 0) {
32043
33418
  promptState.collectedLlmUsageMetrics = true;
32044
- addMetrics(promptState.metrics, usageMetrics);
33419
+ addMetrics(promptState.metrics, usageMetrics2);
32045
33420
  }
32046
33421
  try {
32047
33422
  safeLog4(llmState.span, {
@@ -32059,14 +33434,10 @@ function finishPiLlmSpan(promptState, llmState, message, error) {
32059
33434
  }
32060
33435
  function finishOpenToolSpans(state, error) {
32061
33436
  for (const [, toolState] of state.activeToolSpans) {
32062
- try {
32063
- safeLog4(toolState.span, {
32064
- error: error ? toLoggedError(error) : "Pi tool did not complete"
32065
- });
32066
- toolState.span.end();
32067
- } finally {
32068
- toolState.restoreAutoInstrumentation?.();
32069
- }
33437
+ safeLog4(toolState.span, {
33438
+ error: error ? toLoggedError(error) : "Pi tool did not complete"
33439
+ });
33440
+ toolState.span.end();
32070
33441
  }
32071
33442
  state.activeToolSpans.clear();
32072
33443
  }
@@ -32385,12 +33756,12 @@ var MAX_STRANDS_STRING_ATTACHMENT_CACHE_ENTRIES = 32;
32385
33756
  var StrandsAgentSDKPlugin = class extends BasePlugin {
32386
33757
  activeChildParents = /* @__PURE__ */ new WeakMap();
32387
33758
  onEnable() {
32388
- this.subscribeToAgentStream();
32389
- this.subscribeToMultiAgentStream(
33759
+ this.interceptAgentStream();
33760
+ this.interceptMultiAgentStream(
32390
33761
  strandsAgentSDKChannels.graphStream,
32391
33762
  "Graph.stream"
32392
33763
  );
32393
- this.subscribeToMultiAgentStream(
33764
+ this.interceptMultiAgentStream(
32394
33765
  strandsAgentSDKChannels.swarmStream,
32395
33766
  "Swarm.stream"
32396
33767
  );
@@ -32401,122 +33772,92 @@ var StrandsAgentSDKPlugin = class extends BasePlugin {
32401
33772
  }
32402
33773
  this.unsubscribers = [];
32403
33774
  }
32404
- subscribeToAgentStream() {
32405
- const channel2 = strandsAgentSDKChannels.agentStream.tracingChannel();
32406
- const states = /* @__PURE__ */ new WeakMap();
32407
- const unbindAutoInstrumentationSuppression = bindAutoInstrumentationSuppressionToStart(channel2);
32408
- const handlers = {
32409
- start: (event) => {
32410
- const state = startAgentStream(event, this.activeChildParents);
32411
- if (state) {
32412
- states.set(event, state);
32413
- }
32414
- },
32415
- end: (event) => {
32416
- const state = states.get(event);
32417
- if (!state) {
32418
- return;
32419
- }
32420
- const result = event.result;
32421
- if (isAsyncIterable(result)) {
32422
- patchStreamIfNeeded(result, {
32423
- aroundNext: (callback) => runWithAutoInstrumentationSuppressed(callback),
32424
- onChunk: (chunk) => handleAgentStreamEvent(state, chunk),
32425
- onComplete: () => {
32426
- finalizeAgentStream(state);
32427
- states.delete(event);
32428
- },
32429
- onError: (error) => {
32430
- finalizeAgentStream(state, error);
32431
- states.delete(event);
32432
- }
32433
- });
32434
- return;
32435
- }
32436
- finalizeAgentStream(state, void 0, result);
32437
- states.delete(event);
32438
- },
32439
- error: (event) => {
32440
- const state = states.get(event);
32441
- if (!state || !event.error) {
32442
- return;
32443
- }
32444
- finalizeAgentStream(state, event.error);
32445
- states.delete(event);
32446
- }
32447
- };
32448
- channel2.subscribe(handlers);
32449
- this.unsubscribers.push(() => {
32450
- unbindAutoInstrumentationSuppression?.();
32451
- channel2.unsubscribe(handlers);
32452
- });
33775
+ interceptAgentStream() {
33776
+ this.unsubscribers.push(
33777
+ strandsAgentSDKChannels.agentStream.intercept(
33778
+ (target, thisArg, args, additional) => instrumentStrandsStreamInvocation({
33779
+ finalize: finalizeAgentStream,
33780
+ handleChunk: handleAgentStreamEvent,
33781
+ invoke: () => Reflect.apply(target, thisArg, args),
33782
+ name: "Strands Agent SDK",
33783
+ start: () => startAgentStream(
33784
+ args[0],
33785
+ extractAgent(additional.agent, thisArg),
33786
+ this.activeChildParents
33787
+ )
33788
+ })
33789
+ )
33790
+ );
32453
33791
  }
32454
- subscribeToMultiAgentStream(channel2, operation) {
32455
- const tracingChannel = channel2.tracingChannel();
32456
- const states = /* @__PURE__ */ new WeakMap();
32457
- const unbindAutoInstrumentationSuppression = bindAutoInstrumentationSuppressionToStart(tracingChannel);
32458
- const handlers = {
32459
- start: (event) => {
32460
- const state = startMultiAgentStream(
32461
- event,
32462
- operation,
32463
- this.activeChildParents
32464
- );
32465
- if (state) {
32466
- states.set(event, state);
32467
- }
32468
- },
32469
- end: (event) => {
32470
- const state = states.get(event);
32471
- if (!state) {
32472
- return;
32473
- }
32474
- const result = event.result;
32475
- if (isAsyncIterable(result)) {
32476
- patchStreamIfNeeded(result, {
32477
- aroundNext: (callback) => runWithAutoInstrumentationSuppressed(callback),
32478
- onChunk: (chunk) => handleMultiAgentStreamEvent(
32479
- state,
32480
- chunk,
32481
- this.activeChildParents
32482
- ),
32483
- onComplete: () => {
32484
- finalizeMultiAgentStream(state, this.activeChildParents);
32485
- states.delete(event);
32486
- },
32487
- onError: (error) => {
32488
- finalizeMultiAgentStream(state, this.activeChildParents, error);
32489
- states.delete(event);
32490
- }
32491
- });
32492
- return;
32493
- }
32494
- finalizeMultiAgentStream(
32495
- state,
32496
- this.activeChildParents,
32497
- void 0,
32498
- result
33792
+ interceptMultiAgentStream(channel2, operation) {
33793
+ this.unsubscribers.push(
33794
+ channel2.intercept(
33795
+ (target, thisArg, args, additional) => instrumentStrandsStreamInvocation({
33796
+ finalize: (state, error, output) => finalizeMultiAgentStream(
33797
+ state,
33798
+ this.activeChildParents,
33799
+ error,
33800
+ output
33801
+ ),
33802
+ handleChunk: (state, chunk) => handleMultiAgentStreamEvent(state, chunk, this.activeChildParents),
33803
+ invoke: () => Reflect.apply(target, thisArg, args),
33804
+ name: "Strands multi-agent",
33805
+ start: () => startMultiAgentStream(
33806
+ args[0],
33807
+ extractOrchestrator(additional.orchestrator, thisArg),
33808
+ operation,
33809
+ this.activeChildParents
33810
+ )
33811
+ })
33812
+ )
33813
+ );
33814
+ }
33815
+ };
33816
+ function instrumentStrandsStreamInvocation(options) {
33817
+ let state;
33818
+ try {
33819
+ state = options.start();
33820
+ } catch (error) {
33821
+ debugLogger.error(`Error starting ${options.name} instrumentation:`, error);
33822
+ }
33823
+ let result;
33824
+ try {
33825
+ result = runWithAutoInstrumentationSuppressed(options.invoke);
33826
+ } catch (error) {
33827
+ if (state) {
33828
+ try {
33829
+ options.finalize(state, error);
33830
+ } catch (instrumentationError) {
33831
+ debugLogger.error(
33832
+ `Error handling ${options.name} instrumentation failure:`,
33833
+ instrumentationError
32499
33834
  );
32500
- states.delete(event);
32501
- },
32502
- error: (event) => {
32503
- const state = states.get(event);
32504
- if (!state || !event.error) {
32505
- return;
32506
- }
32507
- finalizeMultiAgentStream(state, this.activeChildParents, event.error);
32508
- states.delete(event);
32509
33835
  }
32510
- };
32511
- tracingChannel.subscribe(handlers);
32512
- this.unsubscribers.push(() => {
32513
- unbindAutoInstrumentationSuppression?.();
32514
- tracingChannel.unsubscribe(handlers);
32515
- });
33836
+ }
33837
+ throw error;
32516
33838
  }
32517
- };
32518
- function startAgentStream(event, activeChildParents) {
32519
- const agent = extractAgent(event);
33839
+ if (state) {
33840
+ try {
33841
+ if (isAsyncIterable(result)) {
33842
+ patchStreamIfNeeded(result, {
33843
+ aroundNext: (callback) => runWithAutoInstrumentationSuppressed(callback),
33844
+ onChunk: (chunk) => options.handleChunk(state, chunk),
33845
+ onComplete: () => options.finalize(state),
33846
+ onError: (error) => options.finalize(state, error)
33847
+ });
33848
+ } else {
33849
+ options.finalize(state, void 0, result);
33850
+ }
33851
+ } catch (error) {
33852
+ debugLogger.error(
33853
+ `Error finalizing ${options.name} instrumentation:`,
33854
+ error
33855
+ );
33856
+ }
33857
+ }
33858
+ return result;
33859
+ }
33860
+ function startAgentStream(input, agent, activeChildParents) {
32520
33861
  const model = agent?.model;
32521
33862
  const metadata = {
32522
33863
  ...extractAgentMetadata2(agent),
@@ -32526,17 +33867,14 @@ function startAgentStream(event, activeChildParents) {
32526
33867
  };
32527
33868
  const parentSpan = agent ? getOnlyChildParent(activeChildParents, agent) : void 0;
32528
33869
  const attachmentCache = createStrandsAttachmentCache();
32529
- const input = processStrandsInputAttachments(
32530
- event.arguments[0],
32531
- attachmentCache
32532
- );
33870
+ const processedInput = processStrandsInputAttachments(input, attachmentCache);
32533
33871
  const span = parentSpan ? withCurrent(
32534
33872
  parentSpan,
32535
33873
  () => startSpan(
32536
33874
  withSpanInstrumentationName(
32537
33875
  {
32538
33876
  event: {
32539
- input,
33877
+ input: processedInput,
32540
33878
  metadata
32541
33879
  },
32542
33880
  name: formatAgentSpanName(agent),
@@ -32549,7 +33887,7 @@ function startAgentStream(event, activeChildParents) {
32549
33887
  withSpanInstrumentationName(
32550
33888
  {
32551
33889
  event: {
32552
- input,
33890
+ input: processedInput,
32553
33891
  metadata
32554
33892
  },
32555
33893
  name: formatAgentSpanName(agent),
@@ -32567,22 +33905,21 @@ function startAgentStream(event, activeChildParents) {
32567
33905
  startTime: getCurrentUnixTimestamp()
32568
33906
  };
32569
33907
  }
32570
- function startMultiAgentStream(event, operation, activeChildParents) {
32571
- const orchestrator = extractOrchestrator(event);
33908
+ function startMultiAgentStream(input, orchestrator, operation, activeChildParents) {
32572
33909
  const metadata = {
32573
33910
  "strands.operation": operation,
32574
33911
  provider: "strands",
32575
33912
  ...orchestrator?.id ? { "strands.orchestrator.id": orchestrator.id } : {}
32576
33913
  };
32577
33914
  const parentSpan = orchestrator ? getOnlyChildParent(activeChildParents, orchestrator) : void 0;
32578
- const input = processStrandsInputAttachments(event.arguments[0]);
33915
+ const processedInput = processStrandsInputAttachments(input);
32579
33916
  const span = parentSpan ? withCurrent(
32580
33917
  parentSpan,
32581
33918
  () => startSpan(
32582
33919
  withSpanInstrumentationName(
32583
33920
  {
32584
33921
  event: {
32585
- input,
33922
+ input: processedInput,
32586
33923
  metadata
32587
33924
  },
32588
33925
  name: operation === "Graph.stream" ? "Strands Graph" : "Strands Swarm",
@@ -32595,7 +33932,7 @@ function startMultiAgentStream(event, operation, activeChildParents) {
32595
33932
  withSpanInstrumentationName(
32596
33933
  {
32597
33934
  event: {
32598
- input,
33935
+ input: processedInput,
32599
33936
  metadata
32600
33937
  },
32601
33938
  name: operation === "Graph.stream" ? "Strands Graph" : "Strands Swarm",
@@ -32966,12 +34303,12 @@ function finalizeMultiAgentStream(state, activeChildParents, error, output) {
32966
34303
  });
32967
34304
  state.span.end();
32968
34305
  }
32969
- function extractAgent(event) {
32970
- const candidate = event.agent ?? event.self;
34306
+ function extractAgent(agent, self) {
34307
+ const candidate = agent ?? self;
32971
34308
  return isObject(candidate) && typeof candidate.stream === "function" ? candidate : void 0;
32972
34309
  }
32973
- function extractOrchestrator(event) {
32974
- const candidate = event.orchestrator ?? event.self;
34310
+ function extractOrchestrator(orchestrator, self) {
34311
+ const candidate = orchestrator ?? self;
32975
34312
  return isObject(candidate) && typeof candidate.stream === "function" ? candidate : void 0;
32976
34313
  }
32977
34314
  function extractAgentMetadata2(agent) {
@@ -34491,7 +35828,7 @@ function braintrustEveHook(options) {
34491
35828
  }
34492
35829
  };
34493
35830
  }
34494
- function braintrustEveInstrumentation(options) {
35831
+ function createLegacyEveInstrumentation(options) {
34495
35832
  const state = options.defineState(EVE_TRACE_STATE_KEY, emptyEveTraceState);
34496
35833
  return {
34497
35834
  events: {
@@ -35879,8 +37216,10 @@ function capturedModelInput(modelInput) {
35879
37216
  const value = [];
35880
37217
  if (typeof instructions === "string") {
35881
37218
  value.push({ content: instructions, role: "system" });
35882
- } else if (instructions) {
37219
+ } else if (Array.isArray(instructions)) {
35883
37220
  value.push(...instructions.map(capturedEveModelMessage));
37221
+ } else if (instructions) {
37222
+ value.push(capturedEveModelMessage(instructions));
35884
37223
  }
35885
37224
  value.push(...messages.map(capturedEveModelMessage));
35886
37225
  try {
@@ -36124,6 +37463,472 @@ async function deterministicEveId(...parts) {
36124
37463
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
36125
37464
  }
36126
37465
 
37466
+ // src/instrumentation/plugins/eve-provider.ts
37467
+ var MAX_EVE_PROVIDER_CACHE_ENTRIES = 1e4;
37468
+ function createEveInstrumentationProvider(options = {}) {
37469
+ const bridge = new EveProviderBridge(options.metadata);
37470
+ return {
37471
+ // Eve versions before tracePolicy use capture to decide whether provider
37472
+ // events include content. Newer versions prefer tracePolicy when both are
37473
+ // present, so keep the deprecated field for backwards compatibility.
37474
+ capture: "content",
37475
+ tracePolicy: () => ({
37476
+ emit: true,
37477
+ recordInputs: true,
37478
+ recordOutputs: true
37479
+ }),
37480
+ events: {
37481
+ "action.completed": (event, context) => bridge.handleActionTerminal(event, context),
37482
+ "action.failed": (event, context) => bridge.handleActionTerminal(event, context),
37483
+ "action.started": (event, context) => bridge.handleActionStarted(event, context),
37484
+ "model.call.completed": (event, context) => bridge.handleModelTerminal(event, context),
37485
+ "model.call.failed": (event, context) => bridge.handleModelTerminal(event, context),
37486
+ "model.call.started": (event, context) => bridge.handleModelStarted(event, context),
37487
+ "step.attempt.completed": (event) => bridge.handleStepAttemptTerminal(event),
37488
+ "step.attempt.failed": (event) => bridge.handleStepAttemptTerminal(event),
37489
+ "turn.cancelled": (event, context) => bridge.handleTurnTerminal(event, context),
37490
+ "turn.completed": (event, context) => bridge.handleTurnTerminal(event, context),
37491
+ "turn.failed": (event, context) => bridge.handleTurnTerminal(event, context),
37492
+ "turn.started": (event, context) => bridge.handleTurnStarted(event, context)
37493
+ },
37494
+ flush,
37495
+ setup: options.setup
37496
+ };
37497
+ }
37498
+ var EveProviderBridge = class {
37499
+ constructor(metadata) {
37500
+ this.metadata = metadata;
37501
+ }
37502
+ metadata;
37503
+ activeActions = /* @__PURE__ */ new Map();
37504
+ activeModels = /* @__PURE__ */ new Map();
37505
+ modelsByAttempt = /* @__PURE__ */ new Map();
37506
+ settledOperations = new LRUCache({
37507
+ max: MAX_EVE_PROVIDER_CACHE_ENTRIES
37508
+ });
37509
+ turns = new LRUCache({
37510
+ max: MAX_EVE_PROVIDER_CACHE_ENTRIES
37511
+ });
37512
+ async handleTurnStarted(event, context) {
37513
+ await this.contain("turn start", async () => {
37514
+ if (this.settledOperations.has(event.idempotencyKey)) {
37515
+ context.state.set({ skip: true });
37516
+ return;
37517
+ }
37518
+ const { rowId, spanId } = await generateEveIds(
37519
+ "turn",
37520
+ event.idempotencyKey
37521
+ );
37522
+ const key = turnKey3(event.sessionId, event.turnId);
37523
+ const stored = storedSpan(context);
37524
+ if (stored && "exported" in stored && stored.rootSpanId) {
37525
+ this.turns.set(key, { rootSpanId: stored.rootSpanId, spanId });
37526
+ return;
37527
+ }
37528
+ let rootSpanId = spanId;
37529
+ let parentSpanId;
37530
+ if (event.parentLineage) {
37531
+ const parentTurnKey = turnKey3(
37532
+ event.parentLineage.sessionId,
37533
+ event.parentLineage.turnId
37534
+ );
37535
+ const parentTurn = this.turns.get(parentTurnKey);
37536
+ rootSpanId = parentTurn?.rootSpanId ?? // Eve propagates the root session through every nested subagent. Use
37537
+ // it instead of the immediate parent session when no ancestor ran
37538
+ // in-process.
37539
+ (await generateEveIds(
37540
+ "turn",
37541
+ turnIdempotencyKey(
37542
+ event.rootSessionId,
37543
+ event.parentLineage.turnId
37544
+ )
37545
+ )).spanId;
37546
+ parentSpanId = (await generateEveIds(
37547
+ "subagent",
37548
+ actionIdempotencyKey(
37549
+ event.parentLineage.sessionId,
37550
+ event.parentLineage.turnId,
37551
+ event.parentLineage.callId
37552
+ )
37553
+ )).spanId;
37554
+ }
37555
+ const metadata = this.spanMetadata(event.sessionId);
37556
+ const span = await this.startSpan(
37557
+ context,
37558
+ {
37559
+ event: { id: rowId, metadata },
37560
+ name: "eve.turn",
37561
+ parentSpanIds: parentSpanId ? { rootSpanId, spanId: parentSpanId } : { parentSpanIds: [], rootSpanId },
37562
+ spanAttributes: { type: "task" /* TASK */ },
37563
+ spanId
37564
+ },
37565
+ rootSpanId
37566
+ );
37567
+ span?.log({ metadata });
37568
+ this.turns.set(key, {
37569
+ rootSpanId,
37570
+ spanId
37571
+ });
37572
+ });
37573
+ }
37574
+ async handleTurnTerminal(event, context) {
37575
+ await this.contain("turn terminal", async () => {
37576
+ if (this.settledOperations.has(event.idempotencyKey)) {
37577
+ context.state.set(void 0);
37578
+ return;
37579
+ }
37580
+ const error = event.type === "turn.failed" ? event.error : void 0;
37581
+ this.drainActionsForTurn(event.sessionId, event.turnId, error);
37582
+ const stored = storedSpan(context);
37583
+ if (stored && "exported" in stored) {
37584
+ updateSpan({
37585
+ exported: stored.exported,
37586
+ ...error !== void 0 ? { error } : {},
37587
+ metrics: { end: Date.now() / 1e3 }
37588
+ });
37589
+ }
37590
+ this.settledOperations.set(event.idempotencyKey, true);
37591
+ context.state.set(void 0);
37592
+ this.turns.delete(turnKey3(event.sessionId, event.turnId));
37593
+ });
37594
+ }
37595
+ async handleModelStarted(event, context) {
37596
+ await this.contain("model start", async () => {
37597
+ if (this.settledOperations.has(event.idempotencyKey)) {
37598
+ context.state.set({ skip: true });
37599
+ return;
37600
+ }
37601
+ const parent = await this.parentForScope(event.scope);
37602
+ const { rowId, spanId } = await generateEveIds(
37603
+ "step",
37604
+ event.idempotencyKey
37605
+ );
37606
+ const input = event.input ? capturedModelInput(event.input) : void 0;
37607
+ const metadata = {
37608
+ ...this.spanMetadata(event.scope.sessionId),
37609
+ model: event.model.modelId,
37610
+ provider: event.model.provider
37611
+ };
37612
+ const span = await this.startSpan(context, {
37613
+ event: {
37614
+ id: rowId,
37615
+ ...input !== void 0 ? { input } : {},
37616
+ metadata
37617
+ },
37618
+ name: "eve.step",
37619
+ parentSpanIds: parent,
37620
+ spanAttributes: { type: "llm" /* LLM */ },
37621
+ spanId
37622
+ });
37623
+ if (!span) return;
37624
+ span.log({ ...input !== void 0 ? { input } : {}, metadata });
37625
+ this.activeModels.set(event.idempotencyKey, {
37626
+ span,
37627
+ turnKey: turnKey3(event.scope.sessionId, event.scope.turnId)
37628
+ });
37629
+ const keys = this.modelsByAttempt.get(event.scope.attemptId) ?? /* @__PURE__ */ new Set();
37630
+ keys.add(event.idempotencyKey);
37631
+ this.modelsByAttempt.set(event.scope.attemptId, keys);
37632
+ });
37633
+ }
37634
+ async handleModelTerminal(event, context) {
37635
+ await this.contain("model terminal", async () => {
37636
+ if (this.settledOperations.has(event.idempotencyKey)) {
37637
+ context.state.set(void 0);
37638
+ return;
37639
+ }
37640
+ const active = this.activeModels.get(event.idempotencyKey);
37641
+ if (active) {
37642
+ const span = active.span;
37643
+ if (event.type === "model.call.failed") {
37644
+ if (event.error !== void 0) span.log({ error: event.error });
37645
+ } else {
37646
+ span.log({
37647
+ metrics: usageMetrics(event.usage),
37648
+ output: modelOutput(event)
37649
+ });
37650
+ }
37651
+ span.end();
37652
+ } else {
37653
+ const stored = storedSpan(context);
37654
+ if (stored && "exported" in stored) {
37655
+ updateSpan({
37656
+ exported: stored.exported,
37657
+ ...event.type === "model.call.failed" ? event.error !== void 0 ? { error: event.error } : {} : { output: modelOutput(event) },
37658
+ metrics: {
37659
+ ...event.type === "model.call.completed" ? usageMetrics(event.usage) : {},
37660
+ end: Date.now() / 1e3
37661
+ }
37662
+ });
37663
+ }
37664
+ }
37665
+ this.settledOperations.set(event.idempotencyKey, true);
37666
+ this.forgetModel(event.scope.attemptId, event.idempotencyKey);
37667
+ context.state.set(void 0);
37668
+ });
37669
+ }
37670
+ async handleActionStarted(event, context) {
37671
+ await this.contain("action start", async () => {
37672
+ if (event.kind === "load-skill" || this.settledOperations.has(event.idempotencyKey)) {
37673
+ context.state.set({ skip: true });
37674
+ return;
37675
+ }
37676
+ const parent = await this.parentForScope(event.scope);
37677
+ const { rowId, spanId } = await generateEveIds(
37678
+ event.kind === "subagent-call" ? "subagent" : "tool",
37679
+ event.idempotencyKey
37680
+ );
37681
+ const metadata = {
37682
+ ...this.spanMetadata(event.scope.sessionId),
37683
+ "eve.action_kind": event.kind
37684
+ };
37685
+ const span = await this.startSpan(context, {
37686
+ event: {
37687
+ id: rowId,
37688
+ ...event.input !== void 0 ? { input: event.input } : {},
37689
+ metadata
37690
+ },
37691
+ name: event.name,
37692
+ parentSpanIds: parent,
37693
+ spanAttributes: { type: "tool" /* TOOL */ },
37694
+ spanId
37695
+ });
37696
+ if (!span) return;
37697
+ span.log({
37698
+ ...event.input !== void 0 ? { input: event.input } : {},
37699
+ metadata
37700
+ });
37701
+ this.activeActions.set(event.idempotencyKey, {
37702
+ span,
37703
+ turnKey: turnKey3(event.scope.sessionId, event.scope.turnId)
37704
+ });
37705
+ });
37706
+ }
37707
+ async handleActionTerminal(event, context) {
37708
+ await this.contain("action terminal", async () => {
37709
+ if (this.settledOperations.has(event.idempotencyKey)) {
37710
+ context.state.set(void 0);
37711
+ return;
37712
+ }
37713
+ const stored = storedSpan(context);
37714
+ if (stored?.skip) {
37715
+ context.state.set(void 0);
37716
+ return;
37717
+ }
37718
+ const active = this.activeActions.get(event.idempotencyKey);
37719
+ const end = finiteTimestamp(event.acceptedAtMs) ?? Date.now();
37720
+ if (active) {
37721
+ const span = active.span;
37722
+ if (event.type === "action.failed") {
37723
+ span.log({
37724
+ error: event.error ?? new Error(event.errorCode ?? `Eve action ${event.outcome}`)
37725
+ });
37726
+ } else if (event.output.type === "error") {
37727
+ span.log({
37728
+ error: event.output.error ?? new Error("Eve action returned an error")
37729
+ });
37730
+ } else {
37731
+ span.log({ output: event.output.output });
37732
+ }
37733
+ span.end({ endTime: end / 1e3 });
37734
+ } else {
37735
+ const stored2 = storedSpan(context);
37736
+ if (stored2 && "exported" in stored2) {
37737
+ updateSpan({
37738
+ exported: stored2.exported,
37739
+ ...event.type === "action.failed" ? {
37740
+ error: event.error ?? new Error(event.errorCode ?? `Eve action ${event.outcome}`)
37741
+ } : event.output.type === "error" ? {
37742
+ error: event.output.error ?? new Error("Eve action returned an error")
37743
+ } : { output: event.output.output },
37744
+ metrics: { end: end / 1e3 }
37745
+ });
37746
+ }
37747
+ }
37748
+ this.settledOperations.set(event.idempotencyKey, true);
37749
+ this.activeActions.delete(event.idempotencyKey);
37750
+ context.state.set(void 0);
37751
+ });
37752
+ }
37753
+ handleStepAttemptTerminal(event) {
37754
+ void this.contain("step attempt terminal", () => {
37755
+ const keys = this.modelsByAttempt.get(event.scope.attemptId);
37756
+ if (!keys) return;
37757
+ for (const key of keys) {
37758
+ const active = this.activeModels.get(key);
37759
+ if (!active) continue;
37760
+ if (event.type === "step.attempt.failed" && event.error !== void 0) {
37761
+ active.span.log({ error: event.error });
37762
+ }
37763
+ active.span.end();
37764
+ this.activeModels.delete(key);
37765
+ }
37766
+ this.modelsByAttempt.delete(event.scope.attemptId);
37767
+ });
37768
+ }
37769
+ async parentForScope(scope) {
37770
+ const key = turnKey3(scope.sessionId, scope.turnId);
37771
+ const known = this.turns.get(key);
37772
+ if (known) {
37773
+ return { rootSpanId: known.rootSpanId, spanId: known.spanId };
37774
+ }
37775
+ const [{ spanId }, { spanId: rootSpanId }] = await Promise.all([
37776
+ generateEveIds("turn", turnIdempotencyKey(scope.sessionId, scope.turnId)),
37777
+ generateEveIds(
37778
+ "turn",
37779
+ turnIdempotencyKey(
37780
+ scope.rootSessionId ?? scope.sessionId,
37781
+ scope.turnId
37782
+ )
37783
+ )
37784
+ ]);
37785
+ return { rootSpanId, spanId };
37786
+ }
37787
+ async startSpan(context, args, rootSpanId) {
37788
+ const span = withCurrent(
37789
+ NOOP_SPAN,
37790
+ () => _internalStartSpanWithInitialMerge(
37791
+ withSpanInstrumentationName(args ?? {}, INSTRUMENTATION_NAMES.EVE)
37792
+ )
37793
+ );
37794
+ try {
37795
+ context.state.set({
37796
+ exported: await span.export(),
37797
+ ...rootSpanId ? { rootSpanId } : {}
37798
+ });
37799
+ } catch (error) {
37800
+ debugLogger.warn("Error exporting Eve provider span:", error);
37801
+ }
37802
+ return span;
37803
+ }
37804
+ drainActionsForTurn(sessionId, turnId, error) {
37805
+ const key = turnKey3(sessionId, turnId);
37806
+ for (const [idempotencyKey, active] of this.activeActions) {
37807
+ if (active.turnKey !== key) continue;
37808
+ if (error !== void 0) active.span.log({ error });
37809
+ active.span.end();
37810
+ this.activeActions.delete(idempotencyKey);
37811
+ }
37812
+ }
37813
+ forgetModel(attemptId, idempotencyKey) {
37814
+ this.activeModels.delete(idempotencyKey);
37815
+ const keys = this.modelsByAttempt.get(attemptId);
37816
+ keys?.delete(idempotencyKey);
37817
+ if (keys?.size === 0) this.modelsByAttempt.delete(attemptId);
37818
+ }
37819
+ spanMetadata(sessionId) {
37820
+ return {
37821
+ ...this.metadata ?? {},
37822
+ "eve.session_id": sessionId
37823
+ };
37824
+ }
37825
+ async contain(operation, fn) {
37826
+ try {
37827
+ await fn();
37828
+ } catch (error) {
37829
+ debugLogger.warn(`Error in Eve provider ${operation}:`, error);
37830
+ }
37831
+ }
37832
+ };
37833
+ function storedSpan(context) {
37834
+ const value = context.state.get();
37835
+ if (!isObject(value)) return void 0;
37836
+ if (value["skip"] === true) return { skip: true };
37837
+ return typeof value["exported"] === "string" ? {
37838
+ exported: value["exported"],
37839
+ ...typeof value["rootSpanId"] === "string" && value["rootSpanId"].length > 0 ? { rootSpanId: value["rootSpanId"] } : {}
37840
+ } : void 0;
37841
+ }
37842
+ function modelOutput(event) {
37843
+ const content = event.content ?? [];
37844
+ let text = "";
37845
+ const reasoning = [];
37846
+ const toolCalls = [];
37847
+ for (const part of content) {
37848
+ if (part.type === "text") {
37849
+ text += part.text;
37850
+ } else if (part.type === "reasoning" && part.text.trim().length > 0) {
37851
+ reasoning.push({ content: part.text });
37852
+ } else if (part.type === "tool-call") {
37853
+ toolCalls.push({
37854
+ function: {
37855
+ arguments: safeJsonStringify(part.input),
37856
+ name: part.toolName
37857
+ },
37858
+ id: part.callId,
37859
+ type: "function"
37860
+ });
37861
+ }
37862
+ }
37863
+ return [
37864
+ {
37865
+ finish_reason: normalizeFinishReason2(event.finishReason),
37866
+ index: 0,
37867
+ message: {
37868
+ content: text || null,
37869
+ ...reasoning.length > 0 ? { reasoning } : {},
37870
+ role: "assistant",
37871
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
37872
+ }
37873
+ }
37874
+ ];
37875
+ }
37876
+ function usageMetrics(usage) {
37877
+ const promptTokens = nonNegativeNumber(usage.inputTokens);
37878
+ const completionTokens = nonNegativeNumber(usage.outputTokens);
37879
+ const cachedTokens = nonNegativeNumber(
37880
+ usage.inputTokenDetails?.cacheReadTokens
37881
+ );
37882
+ const cacheCreationTokens = nonNegativeNumber(
37883
+ usage.inputTokenDetails?.cacheWriteTokens
37884
+ );
37885
+ return {
37886
+ ...promptTokens !== void 0 ? { prompt_tokens: promptTokens } : {},
37887
+ ...completionTokens !== void 0 ? { completion_tokens: completionTokens } : {},
37888
+ ...promptTokens !== void 0 && completionTokens !== void 0 ? { tokens: promptTokens + completionTokens } : {},
37889
+ ...cachedTokens !== void 0 ? { prompt_cached_tokens: cachedTokens } : {},
37890
+ ...cacheCreationTokens !== void 0 ? { prompt_cache_creation_tokens: cacheCreationTokens } : {}
37891
+ };
37892
+ }
37893
+ function nonNegativeNumber(value) {
37894
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
37895
+ }
37896
+ function finiteTimestamp(value) {
37897
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
37898
+ }
37899
+ function normalizeFinishReason2(value) {
37900
+ if (value === "content-filter") return "content_filter";
37901
+ if (value === "tool-calls") return "tool_calls";
37902
+ return value;
37903
+ }
37904
+ function safeJsonStringify(value) {
37905
+ try {
37906
+ return JSON.stringify(value) ?? "null";
37907
+ } catch {
37908
+ return "null";
37909
+ }
37910
+ }
37911
+ function turnKey3(sessionId, turnId) {
37912
+ return `${sessionId}:${turnId}`;
37913
+ }
37914
+ function turnIdempotencyKey(sessionId, turnId) {
37915
+ return `turn:${sessionId}:${turnId}`;
37916
+ }
37917
+ function actionIdempotencyKey(sessionId, turnId, callId) {
37918
+ return `action:${sessionId}:${turnId}:${callId}`;
37919
+ }
37920
+
37921
+ // src/instrumentation/plugins/eve-instrumentation.ts
37922
+ var EVE_INSTRUMENTATION_PROVIDER = /* @__PURE__ */ Symbol.for("eve.instrumentation.provider");
37923
+ function braintrustEveInstrumentation(options) {
37924
+ const definition = "defineState" in options ? createLegacyEveInstrumentation(options) : createEveInstrumentationProvider(options);
37925
+ const declaration = {
37926
+ ...definition,
37927
+ [EVE_INSTRUMENTATION_PROVIDER]: true
37928
+ };
37929
+ return declaration;
37930
+ }
37931
+
36127
37932
  // src/instrumentation/config.ts
36128
37933
  var envIntegrationAliases = {
36129
37934
  openai: "openai",