braintrust 3.3.0 → 3.5.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 (60) hide show
  1. package/README.md +52 -67
  2. package/dev/dist/index.d.mts +98 -14
  3. package/dev/dist/index.d.ts +98 -14
  4. package/dev/dist/index.js +2751 -1463
  5. package/dev/dist/index.mjs +2383 -1095
  6. package/dist/auto-instrumentations/bundler/esbuild.cjs +476 -31
  7. package/dist/auto-instrumentations/bundler/esbuild.d.mts +2 -2
  8. package/dist/auto-instrumentations/bundler/esbuild.d.ts +2 -2
  9. package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
  10. package/dist/auto-instrumentations/bundler/rollup.cjs +476 -31
  11. package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
  12. package/dist/auto-instrumentations/bundler/vite.cjs +476 -31
  13. package/dist/auto-instrumentations/bundler/vite.d.mts +2 -2
  14. package/dist/auto-instrumentations/bundler/vite.d.ts +2 -2
  15. package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
  16. package/dist/auto-instrumentations/bundler/webpack.cjs +476 -31
  17. package/dist/auto-instrumentations/bundler/webpack.d.mts +2 -2
  18. package/dist/auto-instrumentations/bundler/webpack.d.ts +2 -2
  19. package/dist/auto-instrumentations/bundler/webpack.mjs +2 -2
  20. package/dist/auto-instrumentations/chunk-DQTPSXJB.mjs +733 -0
  21. package/dist/auto-instrumentations/chunk-EVUKFMHG.mjs +41 -0
  22. package/dist/auto-instrumentations/{chunk-OLOPGWTJ.mjs → chunk-F3TJZ3Z2.mjs} +1 -1
  23. package/dist/auto-instrumentations/chunk-VLEJ5AEK.mjs +41 -0
  24. package/dist/auto-instrumentations/hook.mjs +540 -37
  25. package/dist/auto-instrumentations/index.cjs +476 -31
  26. package/dist/auto-instrumentations/index.d.mts +5 -5
  27. package/dist/auto-instrumentations/index.d.ts +5 -5
  28. package/dist/auto-instrumentations/index.mjs +1 -1
  29. package/dist/auto-instrumentations/loader/cjs-patch.cjs +32 -10
  30. package/dist/auto-instrumentations/loader/cjs-patch.mjs +10 -5
  31. package/dist/auto-instrumentations/loader/esm-hook.mjs +11 -12
  32. package/dist/auto-instrumentations/loader/get-package-version.cjs +28 -8
  33. package/dist/auto-instrumentations/loader/get-package-version.d.mts +2 -1
  34. package/dist/auto-instrumentations/loader/get-package-version.d.ts +2 -1
  35. package/dist/auto-instrumentations/loader/get-package-version.mjs +3 -1
  36. package/dist/browser.d.mts +806 -306
  37. package/dist/browser.d.ts +806 -306
  38. package/dist/browser.js +3235 -2317
  39. package/dist/browser.mjs +3235 -2317
  40. package/dist/cli.js +2672 -1347
  41. package/dist/edge-light.d.mts +1 -1
  42. package/dist/edge-light.d.ts +1 -1
  43. package/dist/edge-light.js +3035 -2087
  44. package/dist/edge-light.mjs +3035 -2087
  45. package/dist/index.d.mts +806 -306
  46. package/dist/index.d.ts +806 -306
  47. package/dist/index.js +3570 -2654
  48. package/dist/index.mjs +3235 -2319
  49. package/dist/instrumentation/index.d.mts +16 -22
  50. package/dist/instrumentation/index.d.ts +16 -22
  51. package/dist/instrumentation/index.js +2241 -1077
  52. package/dist/instrumentation/index.mjs +2241 -1077
  53. package/dist/workerd.d.mts +1 -1
  54. package/dist/workerd.d.ts +1 -1
  55. package/dist/workerd.js +3035 -2087
  56. package/dist/workerd.mjs +3035 -2087
  57. package/package.json +26 -7
  58. package/dist/auto-instrumentations/chunk-KVX7OFPD.mjs +0 -288
  59. package/dist/auto-instrumentations/chunk-XDBPUTVE.mjs +0 -22
  60. package/dist/auto-instrumentations/chunk-ZEC7BCL4.mjs +0 -22
@@ -1,5 +1,98 @@
1
- // src/instrumentation/core/plugin.ts
2
- import { tracingChannel } from "dc-browser";
1
+ // src/isomorph.ts
2
+ var DefaultAsyncLocalStorage = class {
3
+ constructor() {
4
+ }
5
+ enterWith(_) {
6
+ }
7
+ run(_, callback) {
8
+ return callback();
9
+ }
10
+ getStore() {
11
+ return void 0;
12
+ }
13
+ };
14
+ var DefaultChannel = class {
15
+ constructor(name) {
16
+ this.name = name;
17
+ }
18
+ hasSubscribers = false;
19
+ subscribe(_subscription) {
20
+ }
21
+ unsubscribe(_subscription) {
22
+ return false;
23
+ }
24
+ bindStore(_store, _transform) {
25
+ }
26
+ unbindStore(_store) {
27
+ return false;
28
+ }
29
+ publish(_message) {
30
+ }
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ runStores(_message, fn, thisArg, ...args) {
33
+ return fn.apply(thisArg, args);
34
+ }
35
+ };
36
+ var DefaultTracingChannel = class {
37
+ start;
38
+ end;
39
+ asyncStart;
40
+ asyncEnd;
41
+ error;
42
+ constructor(nameOrChannels) {
43
+ if (typeof nameOrChannels === "string") {
44
+ this.start = new DefaultChannel(`tracing:${nameOrChannels}:start`);
45
+ this.end = new DefaultChannel(`tracing:${nameOrChannels}:end`);
46
+ this.asyncStart = new DefaultChannel(
47
+ `tracing:${nameOrChannels}:asyncStart`
48
+ );
49
+ this.asyncEnd = new DefaultChannel(`tracing:${nameOrChannels}:asyncEnd`);
50
+ this.error = new DefaultChannel(`tracing:${nameOrChannels}:error`);
51
+ return;
52
+ }
53
+ this.start = nameOrChannels.start ?? new DefaultChannel("tracing:start");
54
+ this.end = nameOrChannels.end ?? new DefaultChannel("tracing:end");
55
+ this.asyncStart = nameOrChannels.asyncStart ?? new DefaultChannel("tracing:asyncStart");
56
+ this.asyncEnd = nameOrChannels.asyncEnd ?? new DefaultChannel("tracing:asyncEnd");
57
+ this.error = nameOrChannels.error ?? new DefaultChannel("tracing:error");
58
+ }
59
+ get hasSubscribers() {
60
+ return this.start.hasSubscribers || this.end.hasSubscribers || this.asyncStart.hasSubscribers || this.asyncEnd.hasSubscribers || this.error.hasSubscribers;
61
+ }
62
+ subscribe(_handlers) {
63
+ }
64
+ unsubscribe(_handlers) {
65
+ return false;
66
+ }
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ traceSync(fn, _message, thisArg, ...args) {
69
+ return fn.apply(thisArg, args);
70
+ }
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ tracePromise(fn, _message, thisArg, ...args) {
73
+ return Promise.resolve(fn.apply(thisArg, args));
74
+ }
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ traceCallback(fn, _position, _message, thisArg, ...args) {
77
+ return fn.apply(thisArg, args);
78
+ }
79
+ };
80
+ var iso = {
81
+ buildType: "unknown",
82
+ // Will be set by configureBrowser() or configureNode()
83
+ getRepoInfo: async (_settings) => void 0,
84
+ getPastNAncestors: async () => [],
85
+ getEnv: (_name) => void 0,
86
+ getCallerLocation: () => void 0,
87
+ newAsyncLocalStorage: () => new DefaultAsyncLocalStorage(),
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ newTracingChannel: (nameOrChannels) => new DefaultTracingChannel(nameOrChannels),
90
+ processOn: (_0, _1) => {
91
+ },
92
+ basename: (filepath) => filepath.split(/[\\/]/).pop() || filepath,
93
+ writeln: (text) => console.log(text)
94
+ };
95
+ var isomorph_default = iso;
3
96
 
4
97
  // src/instrumentation/core/stream-patcher.ts
5
98
  function isAsyncIterable(value) {
@@ -16,7 +109,7 @@ function patchStreamIfNeeded(stream, options) {
16
109
  return stream;
17
110
  }
18
111
  const originalIteratorFn = stream[Symbol.asyncIterator];
19
- if (originalIteratorFn.__braintrust_patched) {
112
+ if ("__braintrust_patched" in originalIteratorFn && originalIteratorFn["__braintrust_patched"]) {
20
113
  return stream;
21
114
  }
22
115
  try {
@@ -57,7 +150,10 @@ function patchStreamIfNeeded(stream, options) {
57
150
  completed = true;
58
151
  if (options.onError) {
59
152
  try {
60
- options.onError(error, chunks);
153
+ options.onError(
154
+ error instanceof Error ? error : new Error(String(error)),
155
+ chunks
156
+ );
61
157
  } catch (handlerError) {
62
158
  console.error("Error in stream onError handler:", handlerError);
63
159
  }
@@ -85,7 +181,8 @@ function patchStreamIfNeeded(stream, options) {
85
181
  iterator.throw = async function(...args) {
86
182
  if (!completed) {
87
183
  completed = true;
88
- const error = args[0];
184
+ const rawError = args[0];
185
+ const error = rawError instanceof Error ? rawError : new Error(String(rawError));
89
186
  if (options.onError) {
90
187
  try {
91
188
  options.onError(error, chunks);
@@ -99,7 +196,9 @@ function patchStreamIfNeeded(stream, options) {
99
196
  }
100
197
  return iterator;
101
198
  };
102
- patchedIteratorFn.__braintrust_patched = true;
199
+ Object.defineProperty(patchedIteratorFn, "__braintrust_patched", {
200
+ value: true
201
+ });
103
202
  stream[Symbol.asyncIterator] = patchedIteratorFn;
104
203
  return stream;
105
204
  } catch (error) {
@@ -111,6 +210,111 @@ function patchStreamIfNeeded(stream, options) {
111
210
  // src/logger.ts
112
211
  import { v4 as uuidv42 } from "uuid";
113
212
 
213
+ // src/debug-logger.ts
214
+ var PREFIX = "[braintrust]";
215
+ var DEBUG_LOG_LEVEL_SYMBOL = Symbol.for("braintrust-debug-log-level");
216
+ var LOG_LEVEL_PRIORITY = {
217
+ error: 0,
218
+ warn: 1,
219
+ info: 2,
220
+ debug: 3
221
+ };
222
+ var hasWarnedAboutInvalidEnvValue = false;
223
+ var debugLogStateResolver = void 0;
224
+ function warnInvalidEnvValue(value) {
225
+ if (hasWarnedAboutInvalidEnvValue) {
226
+ return;
227
+ }
228
+ hasWarnedAboutInvalidEnvValue = true;
229
+ console.warn(
230
+ PREFIX,
231
+ `Invalid BRAINTRUST_DEBUG_LOG_LEVEL value "${value}". Expected "error", "warn", "info", or "debug".`
232
+ );
233
+ }
234
+ function normalizeDebugLogLevelOption(option) {
235
+ if (option === false) {
236
+ return void 0;
237
+ }
238
+ if (option === "error" || option === "warn" || option === "info" || option === "debug") {
239
+ return option;
240
+ }
241
+ throw new Error(
242
+ `Invalid debugLogLevel value "${option}". Expected false, "error", "warn", "info", or "debug".`
243
+ );
244
+ }
245
+ function parseDebugLogLevelEnv(value) {
246
+ if (!value) {
247
+ return void 0;
248
+ }
249
+ if (value === "error" || value === "warn" || value === "info" || value === "debug") {
250
+ return value;
251
+ }
252
+ warnInvalidEnvValue(value);
253
+ return void 0;
254
+ }
255
+ function getEnvDebugLogLevel() {
256
+ return parseDebugLogLevelEnv(isomorph_default.getEnv("BRAINTRUST_DEBUG_LOG_LEVEL"));
257
+ }
258
+ function setGlobalDebugLogLevel(level) {
259
+ globalThis[DEBUG_LOG_LEVEL_SYMBOL] = level;
260
+ }
261
+ function setDebugLogStateResolver(resolver) {
262
+ debugLogStateResolver = resolver;
263
+ }
264
+ function resolveDebugLogLevel(state) {
265
+ const stateLevel = state?.getDebugLogLevel?.();
266
+ const hasStateOverride = state?.hasDebugLogLevelOverride?.() ?? false;
267
+ if (hasStateOverride) {
268
+ return stateLevel;
269
+ }
270
+ const globalLevel = (
271
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
272
+ globalThis[DEBUG_LOG_LEVEL_SYMBOL]
273
+ );
274
+ if (globalLevel !== void 0) {
275
+ return globalLevel === false ? void 0 : globalLevel;
276
+ }
277
+ return getEnvDebugLogLevel();
278
+ }
279
+ function emit(method, state, args) {
280
+ const level = resolveDebugLogLevel(state);
281
+ if (!level || LOG_LEVEL_PRIORITY[method] > LOG_LEVEL_PRIORITY[level]) {
282
+ return;
283
+ }
284
+ if (method === "info") {
285
+ console.log(PREFIX, ...args);
286
+ } else if (method === "debug") {
287
+ console.debug(PREFIX, ...args);
288
+ } else if (method === "warn") {
289
+ console.warn(PREFIX, ...args);
290
+ } else {
291
+ console.error(PREFIX, ...args);
292
+ }
293
+ }
294
+ function createDebugLogger(state) {
295
+ const resolveState = () => state ?? debugLogStateResolver?.();
296
+ return {
297
+ info(...args) {
298
+ emit("info", resolveState(), args);
299
+ },
300
+ debug(...args) {
301
+ emit("debug", resolveState(), args);
302
+ },
303
+ warn(...args) {
304
+ emit("warn", resolveState(), args);
305
+ },
306
+ error(...args) {
307
+ emit("error", resolveState(), args);
308
+ }
309
+ };
310
+ }
311
+ var debugLogger = {
312
+ ...createDebugLogger(),
313
+ forState(state) {
314
+ return createDebugLogger(state);
315
+ }
316
+ };
317
+
114
318
  // src/queue.ts
115
319
  var DEFAULT_QUEUE_SIZE = 15e3;
116
320
  var Queue = class {
@@ -119,7 +323,7 @@ var Queue = class {
119
323
  enforceSizeLimit = false;
120
324
  constructor(maxSize) {
121
325
  if (maxSize < 1) {
122
- console.warn(
326
+ debugLogger.warn(
123
327
  `maxSize ${maxSize} is <1, using default ${DEFAULT_QUEUE_SIZE}`
124
328
  );
125
329
  maxSize = DEFAULT_QUEUE_SIZE;
@@ -1916,6 +2120,8 @@ var Experiment = z6.object({
1916
2120
  deleted_at: z6.union([z6.string(), z6.null()]).optional(),
1917
2121
  dataset_id: z6.union([z6.string(), z6.null()]).optional(),
1918
2122
  dataset_version: z6.union([z6.string(), z6.null()]).optional(),
2123
+ parameters_id: z6.union([z6.string(), z6.null()]).optional(),
2124
+ parameters_version: z6.union([z6.string(), z6.null()]).optional(),
1919
2125
  public: z6.boolean(),
1920
2126
  user_id: z6.union([z6.string(), z6.null()]).optional(),
1921
2127
  metadata: z6.union([z6.object({}).partial().passthrough(), z6.null()]).optional(),
@@ -1938,7 +2144,11 @@ var SpanType = z6.union([
1938
2144
  z6.null()
1939
2145
  ]);
1940
2146
  var SpanAttributes = z6.union([
1941
- z6.object({ name: z6.union([z6.string(), z6.null()]), type: SpanType }).partial().passthrough(),
2147
+ z6.object({
2148
+ name: z6.union([z6.string(), z6.null()]),
2149
+ type: SpanType,
2150
+ purpose: z6.union([z6.literal("scorer"), z6.null()])
2151
+ }).partial().passthrough(),
1942
2152
  z6.null()
1943
2153
  ]);
1944
2154
  var ExperimentEvent = z6.object({
@@ -2378,6 +2588,7 @@ var FunctionId = z6.union([
2378
2588
  version: z6.string()
2379
2589
  }),
2380
2590
  code: z6.string(),
2591
+ function_type: FunctionTypeEnum.and(z6.unknown()).optional(),
2381
2592
  name: z6.union([z6.string(), z6.null()]).optional()
2382
2593
  }),
2383
2594
  z6.object({
@@ -2607,7 +2818,12 @@ var TopicAutomationConfig = z6.object({
2607
2818
  topic_map_functions: z6.array(TopicMapFunctionAutomation),
2608
2819
  scope: z6.union([SpanScope, TraceScope, GroupScope, z6.null()]).optional(),
2609
2820
  data_scope: TopicAutomationDataScope.optional(),
2610
- btql_filter: z6.union([z6.string(), z6.null()]).optional()
2821
+ btql_filter: z6.union([z6.string(), z6.null()]).optional(),
2822
+ backfill_time_range: z6.union([
2823
+ z6.string(),
2824
+ z6.object({ from: z6.string(), to: z6.string() }),
2825
+ z6.null()
2826
+ ]).optional()
2611
2827
  });
2612
2828
  var ProjectAutomation = z6.object({
2613
2829
  id: z6.string().uuid(),
@@ -3461,34 +3677,6 @@ function devNullWritableStream() {
3461
3677
  });
3462
3678
  }
3463
3679
 
3464
- // src/isomorph.ts
3465
- var DefaultAsyncLocalStorage = class {
3466
- constructor() {
3467
- }
3468
- enterWith(_) {
3469
- }
3470
- run(_, callback) {
3471
- return callback();
3472
- }
3473
- getStore() {
3474
- return void 0;
3475
- }
3476
- };
3477
- var iso = {
3478
- buildType: "unknown",
3479
- // Will be set by configureBrowser() or configureNode()
3480
- getRepoInfo: async (_settings) => void 0,
3481
- getPastNAncestors: async () => [],
3482
- getEnv: (_name) => void 0,
3483
- getCallerLocation: () => void 0,
3484
- newAsyncLocalStorage: () => new DefaultAsyncLocalStorage(),
3485
- processOn: (_0, _1) => {
3486
- },
3487
- basename: (filepath) => filepath.split(/[\\/]/).pop() || filepath,
3488
- writeln: (text) => console.log(text)
3489
- };
3490
- var isomorph_default = iso;
3491
-
3492
3680
  // src/prompt-cache/disk-cache.ts
3493
3681
  function canUseDiskCache() {
3494
3682
  return !!(isomorph_default.hash && isomorph_default.gunzip && isomorph_default.gzip && isomorph_default.stat && isomorph_default.readFile && isomorph_default.writeFile && isomorph_default.utimes && isomorph_default.readdir && isomorph_default.mkdir && isomorph_default.unlink && isomorph_default.homedir);
@@ -4046,6 +4234,12 @@ var parametersRowSchema = z8.object({
4046
4234
  }),
4047
4235
  metadata: z8.union([z8.object({}).partial().passthrough(), z8.null()]).optional()
4048
4236
  });
4237
+ var InlineAttachmentReferenceSchema = z8.object({
4238
+ type: z8.literal("inline_attachment"),
4239
+ src: z8.string().min(1),
4240
+ content_type: z8.string().optional(),
4241
+ filename: z8.string().optional()
4242
+ });
4049
4243
  var LoginInvalidOrgError = class extends Error {
4050
4244
  constructor(message) {
4051
4245
  super(message);
@@ -4192,7 +4386,10 @@ var loginSchema = z8.strictObject({
4192
4386
  proxyUrl: z8.string(),
4193
4387
  loginToken: z8.string(),
4194
4388
  orgId: z8.string().nullish(),
4195
- gitMetadataSettings: GitMetadataSettings.nullish()
4389
+ gitMetadataSettings: GitMetadataSettings.nullish(),
4390
+ debugLogLevel: z8.enum(["error", "warn", "info", "debug"]).optional(),
4391
+ // Distinguishes explicit false from unset so env fallback stays disabled after deserialization.
4392
+ debugLogLevelDisabled: z8.boolean().optional()
4196
4393
  });
4197
4394
  var stateNonce = 0;
4198
4395
  var BraintrustState = class _BraintrustState {
@@ -4213,6 +4410,16 @@ var BraintrustState = class _BraintrustState {
4213
4410
  this._bgLogger = new SyncLazyValue(
4214
4411
  () => new HTTPBackgroundLogger(new LazyValue(defaultGetLogConn), loginParams)
4215
4412
  );
4413
+ if (loginParams.debugLogLevel !== void 0) {
4414
+ this.debugLogLevelConfigured = true;
4415
+ this.debugLogLevel = normalizeDebugLogLevelOption(
4416
+ loginParams.debugLogLevel
4417
+ );
4418
+ setGlobalDebugLogLevel(this.debugLogLevel ?? false);
4419
+ } else {
4420
+ this.debugLogLevel = getEnvDebugLogLevel();
4421
+ setGlobalDebugLogLevel(void 0);
4422
+ }
4216
4423
  this.resetLoginInfo();
4217
4424
  const memoryCache = new LRUCache({
4218
4425
  max: Number(isomorph_default.getEnv("BRAINTRUST_PROMPT_CACHE_MEMORY_MAX")) ?? 1 << 10
@@ -4257,6 +4464,8 @@ var BraintrustState = class _BraintrustState {
4257
4464
  proxyUrl = null;
4258
4465
  loggedIn = false;
4259
4466
  gitMetadataSettings;
4467
+ debugLogLevel;
4468
+ debugLogLevelConfigured = false;
4260
4469
  fetch = globalThis.fetch;
4261
4470
  _appConn = null;
4262
4471
  _apiConn = null;
@@ -4322,6 +4531,11 @@ var BraintrustState = class _BraintrustState {
4322
4531
  this.proxyUrl = other.proxyUrl;
4323
4532
  this.loggedIn = other.loggedIn;
4324
4533
  this.gitMetadataSettings = other.gitMetadataSettings;
4534
+ this.debugLogLevel = other.debugLogLevel;
4535
+ this.debugLogLevelConfigured = other.debugLogLevelConfigured;
4536
+ setGlobalDebugLogLevel(
4537
+ this.debugLogLevelConfigured ? this.debugLogLevel ?? false : void 0
4538
+ );
4325
4539
  this._appConn = other._appConn;
4326
4540
  this._apiConn = other._apiConn;
4327
4541
  this.loginReplaceApiConn(this.apiConn());
@@ -4346,7 +4560,9 @@ var BraintrustState = class _BraintrustState {
4346
4560
  orgName: this.orgName,
4347
4561
  apiUrl: this.apiUrl,
4348
4562
  proxyUrl: this.proxyUrl,
4349
- gitMetadataSettings: this.gitMetadataSettings
4563
+ gitMetadataSettings: this.gitMetadataSettings,
4564
+ ...this.debugLogLevel ? { debugLogLevel: this.debugLogLevel } : {},
4565
+ ...this.debugLogLevelConfigured && !this.debugLogLevel ? { debugLogLevelDisabled: true } : {}
4350
4566
  };
4351
4567
  }
4352
4568
  static deserialize(serialized, opts) {
@@ -4373,6 +4589,10 @@ var BraintrustState = class _BraintrustState {
4373
4589
  state.proxyConn().set_token(state.loginToken);
4374
4590
  }
4375
4591
  state.loggedIn = true;
4592
+ state.debugLogLevelConfigured = "debugLogLevel" in serializedParsed.data || !!serializedParsed.data.debugLogLevelDisabled;
4593
+ setGlobalDebugLogLevel(
4594
+ state.debugLogLevelConfigured ? state.debugLogLevel ?? false : void 0
4595
+ );
4376
4596
  state.loginReplaceApiConn(state.apiConn());
4377
4597
  return state;
4378
4598
  }
@@ -4385,7 +4605,22 @@ var BraintrustState = class _BraintrustState {
4385
4605
  setMaskingFunction(maskingFunction) {
4386
4606
  this.bgLogger().setMaskingFunction(maskingFunction);
4387
4607
  }
4608
+ setDebugLogLevel(option) {
4609
+ if (option === void 0) {
4610
+ return;
4611
+ }
4612
+ this.debugLogLevelConfigured = true;
4613
+ this.debugLogLevel = normalizeDebugLogLevelOption(option);
4614
+ setGlobalDebugLogLevel(this.debugLogLevel ?? false);
4615
+ }
4616
+ getDebugLogLevel() {
4617
+ return this.debugLogLevel;
4618
+ }
4619
+ hasDebugLogLevelOverride() {
4620
+ return this.debugLogLevelConfigured;
4621
+ }
4388
4622
  async login(loginParams) {
4623
+ this.setDebugLogLevel(loginParams.debugLogLevel);
4389
4624
  if (this.apiUrl && !loginParams.forceLogin) {
4390
4625
  return;
4391
4626
  }
@@ -4483,6 +4718,7 @@ var BraintrustState = class _BraintrustState {
4483
4718
  };
4484
4719
  var _globalState;
4485
4720
  var _internalGetGlobalState = () => _globalState;
4721
+ setDebugLogStateResolver(() => _internalGetGlobalState());
4486
4722
  var FailedHTTPResponse = class extends Error {
4487
4723
  status;
4488
4724
  text;
@@ -4597,7 +4833,7 @@ var HTTPConnection = class _HTTPConnection {
4597
4833
  return await resp.json();
4598
4834
  } catch (e) {
4599
4835
  if (i < tries - 1) {
4600
- console.log(
4836
+ debugLogger.debug(
4601
4837
  `Retrying API request ${object_type} ${JSON.stringify(args)} ${e.status} ${e.text}`
4602
4838
  );
4603
4839
  continue;
@@ -4809,7 +5045,7 @@ with a Blob/ArrayBuffer, or run the program on Node.js.`
4809
5045
  try {
4810
5046
  statSync(data);
4811
5047
  } catch (e) {
4812
- console.warn(`Failed to read file: ${e}`);
5048
+ debugLogger.warn(`Failed to read file: ${e}`);
4813
5049
  }
4814
5050
  }
4815
5051
  };
@@ -5521,7 +5757,7 @@ var HTTPBackgroundLogger = class _HTTPBackgroundLogger {
5521
5757
  this.queueDropLoggingPeriod = queueDropLoggingPeriodEnv;
5522
5758
  }
5523
5759
  if (isomorph_default.getEnv("BRAINTRUST_LOG_FLUSH_CHUNK_SIZE")) {
5524
- console.warn(
5760
+ debugLogger.warn(
5525
5761
  "BRAINTRUST_LOG_FLUSH_CHUNK_SIZE is deprecated and no longer has any effect. Log flushing now sends all items at once and batches them automatically. This environment variable will be removed in a future major release."
5526
5762
  );
5527
5763
  }
@@ -5583,7 +5819,10 @@ var HTTPBackgroundLogger = class _HTTPBackgroundLogger {
5583
5819
  const versionInfo = await conn.get_json("version");
5584
5820
  serverLimit = z8.object({ logs3_payload_max_bytes: z8.number().nullish() }).parse(versionInfo).logs3_payload_max_bytes ?? null;
5585
5821
  } catch (e) {
5586
- console.warn("Failed to fetch version info for payload limit:", e);
5822
+ debugLogger.warn(
5823
+ "Failed to fetch version info for payload limit:",
5824
+ e
5825
+ );
5587
5826
  }
5588
5827
  const validServerLimit = serverLimit !== null && serverLimit > 0 ? serverLimit : null;
5589
5828
  const canUseOverflow = validServerLimit !== null;
@@ -5727,16 +5966,16 @@ var HTTPBackgroundLogger = class _HTTPBackgroundLogger {
5727
5966
  if (isRetrying) {
5728
5967
  errmsg += ". Retrying";
5729
5968
  }
5730
- console.warn(errmsg);
5969
+ debugLogger.warn(errmsg);
5731
5970
  if (!isRetrying) {
5732
- console.warn(
5971
+ debugLogger.warn(
5733
5972
  `Failed to construct log records to flush after ${this.numTries} attempts. Dropping batch`
5734
5973
  );
5735
5974
  throw e;
5736
5975
  } else {
5737
- console.warn(e);
5976
+ debugLogger.warn(e);
5738
5977
  const sleepTimeS = BACKGROUND_LOGGER_BASE_SLEEP_TIME_S * 2 ** i;
5739
- console.info(`Sleeping for ${sleepTimeS}s`);
5978
+ debugLogger.info(`Sleeping for ${sleepTimeS}s`);
5740
5979
  await new Promise(
5741
5980
  (resolve) => setTimeout(resolve, sleepTimeS * 1e3)
5742
5981
  );
@@ -5837,15 +6076,15 @@ Error: ${errorText}`;
5837
6076
  this.logFailedPayloadsDir();
5838
6077
  }
5839
6078
  if (!isRetrying) {
5840
- console.warn(
6079
+ debugLogger.warn(
5841
6080
  `log request failed after ${this.numTries} retries. Dropping batch`
5842
6081
  );
5843
6082
  throw new Error(errMsg);
5844
6083
  } else {
5845
- console.warn(errMsg);
6084
+ debugLogger.warn(errMsg);
5846
6085
  if (isRetrying) {
5847
6086
  const sleepTimeS = BACKGROUND_LOGGER_BASE_SLEEP_TIME_S * 2 ** i;
5848
- console.info(`Sleeping for ${sleepTimeS}s`);
6087
+ debugLogger.info(`Sleeping for ${sleepTimeS}s`);
5849
6088
  await new Promise(
5850
6089
  (resolve) => setTimeout(resolve, sleepTimeS * 1e3)
5851
6090
  );
@@ -5860,7 +6099,7 @@ Error: ${errorText}`;
5860
6099
  this.queueDropLoggingState.numDropped += numItems;
5861
6100
  const timeNow = getCurrentUnixTimestamp();
5862
6101
  if (timeNow - this.queueDropLoggingState.lastLoggedTimestamp > this.queueDropLoggingPeriod) {
5863
- console.warn(
6102
+ debugLogger.warn(
5864
6103
  `Dropped ${this.queueDropLoggingState.numDropped} elements due to full queue`
5865
6104
  );
5866
6105
  if (this.failedPublishPayloadsDir) {
@@ -5892,7 +6131,7 @@ Error: ${errorText}`;
5892
6131
  await _HTTPBackgroundLogger.writePayloadToDir({ payloadDir, payload });
5893
6132
  }
5894
6133
  } catch (e) {
5895
- console.error(e);
6134
+ debugLogger.error(e);
5896
6135
  }
5897
6136
  }
5898
6137
  static async writePayloadToDir({
@@ -5900,7 +6139,7 @@ Error: ${errorText}`;
5900
6139
  payload
5901
6140
  }) {
5902
6141
  if (!(isomorph_default.pathJoin && isomorph_default.mkdir && isomorph_default.writeFile)) {
5903
- console.warn(
6142
+ debugLogger.warn(
5904
6143
  "Cannot dump payloads: filesystem-operations not supported on this platform"
5905
6144
  );
5906
6145
  return;
@@ -5913,7 +6152,7 @@ Error: ${errorText}`;
5913
6152
  await isomorph_default.mkdir(payloadDir, { recursive: true });
5914
6153
  await isomorph_default.writeFile(payloadFile, payload);
5915
6154
  } catch (e) {
5916
- console.error(
6155
+ debugLogger.error(
5917
6156
  `Failed to write failed payload to output file ${payloadFile}:
5918
6157
  `,
5919
6158
  e
@@ -5944,7 +6183,9 @@ Error: ${errorText}`;
5944
6183
  }
5945
6184
  }
5946
6185
  logFailedPayloadsDir() {
5947
- console.warn(`Logging failed payloads to ${this.failedPublishPayloadsDir}`);
6186
+ debugLogger.warn(
6187
+ `Logging failed payloads to ${this.failedPublishPayloadsDir}`
6188
+ );
5948
6189
  }
5949
6190
  // Should only be called by BraintrustState.
5950
6191
  internalReplaceApiConn(apiConn) {
@@ -6355,7 +6596,14 @@ var ObjectFetcher = class {
6355
6596
  async *fetchRecordsFromApi(batchSize) {
6356
6597
  const state = await this.getState();
6357
6598
  const objectId = await this.id;
6358
- const limit = batchSize ?? DEFAULT_FETCH_BATCH_SIZE;
6599
+ const batchLimit = batchSize ?? DEFAULT_FETCH_BATCH_SIZE;
6600
+ const internalLimit = this._internal_btql?.limit;
6601
+ const limit = batchSize !== void 0 ? batchSize : internalLimit ?? batchLimit;
6602
+ const internalBtqlWithoutReservedQueryKeys = Object.fromEntries(
6603
+ Object.entries(this._internal_btql ?? {}).filter(
6604
+ ([key]) => key !== "cursor" && key !== "limit" && key !== "select" && key !== "from"
6605
+ )
6606
+ );
6359
6607
  let cursor = void 0;
6360
6608
  let iterations = 0;
6361
6609
  while (true) {
@@ -6363,7 +6611,6 @@ var ObjectFetcher = class {
6363
6611
  `btql`,
6364
6612
  {
6365
6613
  query: {
6366
- ...this._internal_btql,
6367
6614
  select: [
6368
6615
  {
6369
6616
  op: "star"
@@ -6383,7 +6630,8 @@ var ObjectFetcher = class {
6383
6630
  ]
6384
6631
  },
6385
6632
  cursor,
6386
- limit
6633
+ limit,
6634
+ ...internalBtqlWithoutReservedQueryKeys
6387
6635
  },
6388
6636
  use_columnstore: false,
6389
6637
  brainstore_realtime: true,
@@ -6656,7 +6904,7 @@ var Experiment2 = class extends ObjectFetcher {
6656
6904
  scores = results["scores"];
6657
6905
  metrics = results["metrics"];
6658
6906
  } catch (e) {
6659
- console.warn(
6907
+ debugLogger.forState(state).warn(
6660
6908
  `Failed to fetch experiment scores and metrics: ${e}
6661
6909
 
6662
6910
  View complete results in Braintrust or run experiment.summarize() again.`
@@ -6733,7 +6981,7 @@ View complete results in Braintrust or run experiment.summarize() again.`
6733
6981
  * @deprecated This function is deprecated. You can simply remove it from your code.
6734
6982
  */
6735
6983
  async close() {
6736
- console.warn(
6984
+ debugLogger.forState(this.state).warn(
6737
6985
  "close is deprecated and will be removed in a future version of braintrust. It is now a no-op and can be removed"
6738
6986
  );
6739
6987
  return this.id;
@@ -6890,8 +7138,8 @@ var SpanImpl = class _SpanImpl {
6890
7138
  ...serializableInternalData,
6891
7139
  [IS_MERGE_FIELD]: this.isMerge
6892
7140
  });
6893
- if (partialRecord.metrics?.end) {
6894
- this.loggedEndTime = partialRecord.metrics?.end;
7141
+ if (typeof partialRecord.metrics?.end === "number") {
7142
+ this.loggedEndTime = partialRecord.metrics.end;
6895
7143
  }
6896
7144
  if (this.parentObjectType === 1 /* EXPERIMENT */) {
6897
7145
  const cachedSpan = {
@@ -7126,7 +7374,7 @@ var Dataset2 = class extends ObjectFetcher {
7126
7374
  constructor(state, lazyMetadata, pinnedVersion, legacy, _internal_btql) {
7127
7375
  const isLegacyDataset = legacy ?? DEFAULT_IS_LEGACY_DATASET;
7128
7376
  if (isLegacyDataset) {
7129
- console.warn(
7377
+ debugLogger.forState(state).warn(
7130
7378
  `Records will be fetched from this dataset in the legacy format, with the "expected" field renamed to "output". Please update your code to use "expected", and use \`braintrust.initDataset()\` with \`{ useOutput: false }\`, which will become the default in a future version of Braintrust.`
7131
7379
  );
7132
7380
  }
@@ -7357,7 +7605,7 @@ var Dataset2 = class extends ObjectFetcher {
7357
7605
  * @deprecated This function is deprecated. You can simply remove it from your code.
7358
7606
  */
7359
7607
  async close() {
7360
- console.warn(
7608
+ debugLogger.forState(this.state).warn(
7361
7609
  "close is deprecated and will be removed in a future version of braintrust. It is now a no-op and can be removed"
7362
7610
  );
7363
7611
  return this.id;
@@ -7368,6 +7616,49 @@ var Dataset2 = class extends ObjectFetcher {
7368
7616
  };
7369
7617
  var TEST_API_KEY = "___TEST_API_KEY__THIS_IS_NOT_REAL___";
7370
7618
 
7619
+ // src/instrumentation/core/channel-tracing-utils.ts
7620
+ function hasChannelSpanInfo(value) {
7621
+ return isObject(value) && isObject(value.span_info);
7622
+ }
7623
+ function getChannelSpanInfo(event) {
7624
+ if (isObject(event.span_info)) {
7625
+ return event.span_info;
7626
+ }
7627
+ const firstArg = event.arguments?.[0];
7628
+ if (hasChannelSpanInfo(firstArg)) {
7629
+ return firstArg.span_info;
7630
+ }
7631
+ return void 0;
7632
+ }
7633
+ function buildStartSpanArgs(config, event) {
7634
+ const spanInfo = getChannelSpanInfo(event);
7635
+ const spanAttributes = {
7636
+ type: config.type
7637
+ };
7638
+ if (isObject(spanInfo?.spanAttributes)) {
7639
+ mergeDicts(spanAttributes, spanInfo.spanAttributes);
7640
+ }
7641
+ return {
7642
+ name: typeof spanInfo?.name === "string" && spanInfo.name ? spanInfo.name : config.name,
7643
+ spanAttributes,
7644
+ spanInfoMetadata: isObject(spanInfo?.metadata) ? spanInfo.metadata : void 0
7645
+ };
7646
+ }
7647
+ function mergeInputMetadata(metadata, spanInfoMetadata) {
7648
+ if (!spanInfoMetadata) {
7649
+ return isObject(metadata) ? (
7650
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
7651
+ metadata
7652
+ ) : void 0;
7653
+ }
7654
+ const mergedMetadata = {};
7655
+ mergeDicts(mergedMetadata, spanInfoMetadata);
7656
+ if (isObject(metadata)) {
7657
+ mergeDicts(mergedMetadata, metadata);
7658
+ }
7659
+ return mergedMetadata;
7660
+ }
7661
+
7371
7662
  // src/instrumentation/core/plugin.ts
7372
7663
  var BasePlugin = class {
7373
7664
  enabled = false;
@@ -7399,23 +7690,25 @@ var BasePlugin = class {
7399
7690
  * @param handlers - Event handlers
7400
7691
  */
7401
7692
  subscribe(channelName, handlers) {
7402
- const channel = tracingChannel(channelName);
7403
- channel.subscribe(handlers);
7693
+ const channel2 = isomorph_default.newTracingChannel(channelName);
7694
+ channel2.subscribe(handlers);
7404
7695
  }
7405
7696
  /**
7406
7697
  * Subscribe to a channel for async methods (non-streaming).
7407
7698
  * Creates a span and logs input/output/metrics.
7408
7699
  */
7409
7700
  subscribeToChannel(channelName, config) {
7410
- const channel = tracingChannel(channelName);
7701
+ const channel2 = isomorph_default.newTracingChannel(channelName);
7411
7702
  const spans = /* @__PURE__ */ new WeakMap();
7412
7703
  const handlers = {
7413
7704
  start: (event) => {
7705
+ const { name, spanAttributes, spanInfoMetadata } = buildStartSpanArgs(
7706
+ config,
7707
+ event
7708
+ );
7414
7709
  const span = startSpan({
7415
- name: config.name,
7416
- spanAttributes: {
7417
- type: config.type
7418
- }
7710
+ name,
7711
+ spanAttributes
7419
7712
  });
7420
7713
  const startTime = getCurrentUnixTimestamp();
7421
7714
  spans.set(event, { span, startTime });
@@ -7423,7 +7716,7 @@ var BasePlugin = class {
7423
7716
  const { input, metadata } = config.extractInput(event.arguments);
7424
7717
  span.log({
7425
7718
  input,
7426
- metadata
7719
+ metadata: mergeInputMetadata(metadata, spanInfoMetadata)
7427
7720
  });
7428
7721
  } catch (error) {
7429
7722
  console.error(`Error extracting input for ${channelName}:`, error);
@@ -7436,10 +7729,12 @@ var BasePlugin = class {
7436
7729
  }
7437
7730
  const { span, startTime } = spanData;
7438
7731
  try {
7439
- const output = config.extractOutput(event.result);
7440
- const metrics = config.extractMetrics(event.result, startTime);
7732
+ const output = config.extractOutput(event.result, event);
7733
+ const metrics = config.extractMetrics(event.result, startTime, event);
7734
+ const metadata = config.extractMetadata?.(event.result, event);
7441
7735
  span.log({
7442
7736
  output,
7737
+ ...metadata !== void 0 ? { metadata } : {},
7443
7738
  metrics
7444
7739
  });
7445
7740
  } catch (error) {
@@ -7462,9 +7757,9 @@ var BasePlugin = class {
7462
7757
  spans.delete(event);
7463
7758
  }
7464
7759
  };
7465
- channel.subscribe(handlers);
7760
+ channel2.subscribe(handlers);
7466
7761
  this.unsubscribers.push(() => {
7467
- channel.unsubscribe(handlers);
7762
+ channel2.unsubscribe(handlers);
7468
7763
  });
7469
7764
  }
7470
7765
  /**
@@ -7472,15 +7767,17 @@ var BasePlugin = class {
7472
7767
  * Handles both streaming and non-streaming responses.
7473
7768
  */
7474
7769
  subscribeToStreamingChannel(channelName, config) {
7475
- const channel = tracingChannel(channelName);
7770
+ const channel2 = isomorph_default.newTracingChannel(channelName);
7476
7771
  const spans = /* @__PURE__ */ new WeakMap();
7477
7772
  const handlers = {
7478
7773
  start: (event) => {
7774
+ const { name, spanAttributes, spanInfoMetadata } = buildStartSpanArgs(
7775
+ config,
7776
+ event
7777
+ );
7479
7778
  const span = startSpan({
7480
- name: config.name,
7481
- spanAttributes: {
7482
- type: config.type
7483
- }
7779
+ name,
7780
+ spanAttributes
7484
7781
  });
7485
7782
  const startTime = getCurrentUnixTimestamp();
7486
7783
  spans.set(event, { span, startTime });
@@ -7488,7 +7785,7 @@ var BasePlugin = class {
7488
7785
  const { input, metadata } = config.extractInput(event.arguments);
7489
7786
  span.log({
7490
7787
  input,
7491
- metadata
7788
+ metadata: mergeInputMetadata(metadata, spanInfoMetadata)
7492
7789
  });
7493
7790
  } catch (error) {
7494
7791
  console.error(`Error extracting input for ${channelName}:`, error);
@@ -7501,24 +7798,39 @@ var BasePlugin = class {
7501
7798
  }
7502
7799
  const { span, startTime } = spanData;
7503
7800
  if (isAsyncIterable(event.result)) {
7801
+ let firstChunkTime;
7504
7802
  patchStreamIfNeeded(event.result, {
7803
+ onChunk: () => {
7804
+ if (firstChunkTime === void 0) {
7805
+ firstChunkTime = getCurrentUnixTimestamp();
7806
+ }
7807
+ },
7505
7808
  onComplete: (chunks) => {
7506
7809
  try {
7507
7810
  let output;
7508
7811
  let metrics;
7812
+ let metadata;
7509
7813
  if (config.aggregateChunks) {
7510
- const aggregated = config.aggregateChunks(chunks);
7814
+ const aggregated = config.aggregateChunks(
7815
+ chunks,
7816
+ event.result,
7817
+ event
7818
+ );
7511
7819
  output = aggregated.output;
7512
7820
  metrics = aggregated.metrics;
7821
+ metadata = aggregated.metadata;
7513
7822
  } else {
7514
- output = config.extractOutput(chunks);
7515
- metrics = config.extractMetrics(chunks, startTime);
7823
+ output = config.extractOutput(chunks, event);
7824
+ metrics = config.extractMetrics(chunks, startTime, event);
7516
7825
  }
7517
- if (!metrics.time_to_first_token && chunks.length > 0) {
7826
+ if (metrics.time_to_first_token === void 0 && firstChunkTime !== void 0) {
7827
+ metrics.time_to_first_token = firstChunkTime - startTime;
7828
+ } else if (metrics.time_to_first_token === void 0 && chunks.length > 0) {
7518
7829
  metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
7519
7830
  }
7520
7831
  span.log({
7521
7832
  output,
7833
+ ...metadata !== void 0 ? { metadata } : {},
7522
7834
  metrics
7523
7835
  });
7524
7836
  } catch (error) {
@@ -7539,10 +7851,16 @@ var BasePlugin = class {
7539
7851
  });
7540
7852
  } else {
7541
7853
  try {
7542
- const output = config.extractOutput(event.result);
7543
- const metrics = config.extractMetrics(event.result, startTime);
7854
+ const output = config.extractOutput(event.result, event);
7855
+ const metadata = config.extractMetadata ? config.extractMetadata(event.result, event) : void 0;
7856
+ const metrics = config.extractMetrics(
7857
+ event.result,
7858
+ startTime,
7859
+ event
7860
+ );
7544
7861
  span.log({
7545
7862
  output,
7863
+ ...metadata !== void 0 ? { metadata } : {},
7546
7864
  metrics
7547
7865
  });
7548
7866
  } catch (error) {
@@ -7566,9 +7884,9 @@ var BasePlugin = class {
7566
7884
  spans.delete(event);
7567
7885
  }
7568
7886
  };
7569
- channel.subscribe(handlers);
7887
+ channel2.subscribe(handlers);
7570
7888
  this.unsubscribers.push(() => {
7571
- channel.unsubscribe(handlers);
7889
+ channel2.unsubscribe(handlers);
7572
7890
  });
7573
7891
  }
7574
7892
  /**
@@ -7576,15 +7894,17 @@ var BasePlugin = class {
7576
7894
  * Used for methods like beta.chat.completions.stream() and responses.stream().
7577
7895
  */
7578
7896
  subscribeToSyncStreamChannel(channelName, config) {
7579
- const channel = tracingChannel(channelName);
7897
+ const channel2 = isomorph_default.newTracingChannel(channelName);
7580
7898
  const spans = /* @__PURE__ */ new WeakMap();
7581
7899
  const handlers = {
7582
7900
  start: (event) => {
7901
+ const { name, spanAttributes, spanInfoMetadata } = buildStartSpanArgs(
7902
+ config,
7903
+ event
7904
+ );
7583
7905
  const span = startSpan({
7584
- name: config.name,
7585
- spanAttributes: {
7586
- type: config.type
7587
- }
7906
+ name,
7907
+ spanAttributes
7588
7908
  });
7589
7909
  const startTime = getCurrentUnixTimestamp();
7590
7910
  spans.set(event, { span, startTime });
@@ -7592,7 +7912,7 @@ var BasePlugin = class {
7592
7912
  const { input, metadata } = config.extractInput(event.arguments);
7593
7913
  span.log({
7594
7914
  input,
7595
- metadata
7915
+ metadata: mergeInputMetadata(metadata, spanInfoMetadata)
7596
7916
  });
7597
7917
  } catch (error) {
7598
7918
  console.error(`Error extracting input for ${channelName}:`, error);
@@ -7676,9 +7996,9 @@ var BasePlugin = class {
7676
7996
  spans.delete(event);
7677
7997
  }
7678
7998
  };
7679
- channel.subscribe(handlers);
7999
+ channel2.subscribe(handlers);
7680
8000
  this.unsubscribers.push(() => {
7681
- channel.unsubscribe(handlers);
8001
+ channel2.unsubscribe(handlers);
7682
8002
  });
7683
8003
  }
7684
8004
  };
@@ -7701,134 +8021,645 @@ function isValidChannelName(channelName) {
7701
8021
  return /^braintrust:[^:]+:.+$/.test(channelName);
7702
8022
  }
7703
8023
 
7704
- // src/wrappers/attachment-utils.ts
7705
- function getExtensionFromMediaType(mediaType) {
7706
- const extensionMap = {
7707
- "image/png": "png",
7708
- "image/jpeg": "jpg",
7709
- "image/gif": "gif",
7710
- "image/webp": "webp",
7711
- "image/svg+xml": "svg",
7712
- "audio/mpeg": "mp3",
7713
- "audio/wav": "wav",
7714
- "audio/ogg": "ogg",
7715
- "video/mp4": "mp4",
7716
- "video/webm": "webm",
7717
- "application/pdf": "pdf",
7718
- "application/json": "json",
7719
- "text/plain": "txt",
7720
- "text/html": "html",
7721
- "text/csv": "csv"
7722
- };
7723
- return extensionMap[mediaType] || "bin";
8024
+ // src/instrumentation/core/channel-tracing.ts
8025
+ function isSyncStreamLike(value) {
8026
+ return !!value && typeof value === "object" && typeof value.on === "function";
7724
8027
  }
7725
- function convertDataToBlob(data, mediaType) {
8028
+ function hasChoices(value) {
8029
+ return !!value && typeof value === "object" && "choices" in value;
8030
+ }
8031
+ function normalizeMetadata(metadata) {
8032
+ return isObject(metadata) ? metadata : void 0;
8033
+ }
8034
+ function startSpanForEvent(config, event, channelName) {
8035
+ const { name, spanAttributes, spanInfoMetadata } = buildStartSpanArgs(
8036
+ config,
8037
+ event
8038
+ );
8039
+ const span = startSpan({
8040
+ name,
8041
+ spanAttributes
8042
+ });
8043
+ const startTime = getCurrentUnixTimestamp();
7726
8044
  try {
7727
- if (typeof data === "string") {
7728
- if (data.startsWith("data:")) {
7729
- const base64Match = data.match(/^data:[^;]+;base64,(.+)$/);
7730
- if (base64Match) {
7731
- const base64 = base64Match[1];
7732
- const binaryString = atob(base64);
7733
- const bytes = new Uint8Array(binaryString.length);
7734
- for (let i = 0; i < binaryString.length; i++) {
7735
- bytes[i] = binaryString.charCodeAt(i);
7736
- }
7737
- return new Blob([bytes], { type: mediaType });
7738
- }
7739
- } else if (data.startsWith("http://") || data.startsWith("https://")) {
7740
- return null;
7741
- } else {
7742
- const binaryString = atob(data);
7743
- const bytes = new Uint8Array(binaryString.length);
7744
- for (let i = 0; i < binaryString.length; i++) {
7745
- bytes[i] = binaryString.charCodeAt(i);
7746
- }
7747
- return new Blob([bytes], { type: mediaType });
7748
- }
7749
- } else if (data instanceof Uint8Array) {
7750
- return new Blob([data], { type: mediaType });
7751
- } else if (data instanceof ArrayBuffer) {
7752
- return new Blob([data], { type: mediaType });
7753
- } else if (typeof Buffer !== "undefined" && data instanceof Buffer) {
7754
- return new Blob([data], { type: mediaType });
7755
- }
7756
- } catch {
7757
- return null;
8045
+ const { input, metadata } = config.extractInput(
8046
+ event.arguments,
8047
+ event,
8048
+ span
8049
+ );
8050
+ span.log({
8051
+ input,
8052
+ metadata: mergeInputMetadata(metadata, spanInfoMetadata)
8053
+ });
8054
+ } catch (error) {
8055
+ console.error(`Error extracting input for ${channelName}:`, error);
7758
8056
  }
7759
- return null;
8057
+ return { span, startTime };
7760
8058
  }
7761
- function processInputAttachments(input) {
7762
- if (!input) {
7763
- return input;
8059
+ function logErrorAndEnd(states, event) {
8060
+ const spanData = states.get(event);
8061
+ if (!spanData) {
8062
+ return;
7764
8063
  }
7765
- let attachmentIndex = 0;
7766
- const processContentPart = (part) => {
7767
- if (!part || typeof part !== "object") {
7768
- return part;
7769
- }
7770
- if (part.type === "image" && part.image) {
7771
- let mediaType = "image/png";
7772
- if (typeof part.image === "string" && part.image.startsWith("data:")) {
7773
- const mediaTypeMatch = part.image.match(/^data:([^;]+);/);
7774
- if (mediaTypeMatch) {
7775
- mediaType = mediaTypeMatch[1];
7776
- }
7777
- } else if (part.mediaType) {
7778
- mediaType = part.mediaType;
8064
+ spanData.span.log({
8065
+ error: event.error.message
8066
+ });
8067
+ spanData.span.end();
8068
+ states.delete(event);
8069
+ }
8070
+ function traceAsyncChannel(channel2, config) {
8071
+ const tracingChannel = channel2.tracingChannel();
8072
+ const states = /* @__PURE__ */ new WeakMap();
8073
+ const channelName = channel2.channelName;
8074
+ const handlers = {
8075
+ start: (event) => {
8076
+ states.set(
8077
+ event,
8078
+ startSpanForEvent(
8079
+ config,
8080
+ event,
8081
+ channelName
8082
+ )
8083
+ );
8084
+ },
8085
+ asyncEnd: (event) => {
8086
+ const spanData = states.get(event);
8087
+ if (!spanData) {
8088
+ return;
7779
8089
  }
7780
- const blob = convertDataToBlob(part.image, mediaType);
7781
- if (blob) {
7782
- const filename = `input_image_${attachmentIndex}.${getExtensionFromMediaType(mediaType)}`;
7783
- attachmentIndex++;
7784
- const attachment = new Attachment({
7785
- data: blob,
7786
- filename,
7787
- contentType: mediaType
8090
+ const asyncEndEvent = event;
8091
+ const { span, startTime } = spanData;
8092
+ try {
8093
+ const output = config.extractOutput(
8094
+ asyncEndEvent.result,
8095
+ asyncEndEvent
8096
+ );
8097
+ const metrics = config.extractMetrics(
8098
+ asyncEndEvent.result,
8099
+ startTime,
8100
+ asyncEndEvent
8101
+ );
8102
+ const metadata = config.extractMetadata?.(
8103
+ asyncEndEvent.result,
8104
+ asyncEndEvent
8105
+ );
8106
+ span.log({
8107
+ output,
8108
+ ...normalizeMetadata(metadata) !== void 0 ? { metadata: normalizeMetadata(metadata) } : {},
8109
+ metrics
7788
8110
  });
7789
- return {
7790
- ...part,
7791
- image: attachment
7792
- };
7793
- }
8111
+ } catch (error) {
8112
+ console.error(`Error extracting output for ${channelName}:`, error);
8113
+ } finally {
8114
+ span.end();
8115
+ states.delete(event);
8116
+ }
8117
+ },
8118
+ error: (event) => {
8119
+ logErrorAndEnd(states, event);
7794
8120
  }
7795
- if (part.type === "file" && part.data) {
7796
- const mediaType = part.mediaType || "application/octet-stream";
7797
- const blob = convertDataToBlob(part.data, mediaType);
7798
- if (blob) {
7799
- const filename = part.filename || `input_file_${attachmentIndex}.${getExtensionFromMediaType(mediaType)}`;
7800
- attachmentIndex++;
7801
- const attachment = new Attachment({
7802
- data: blob,
7803
- filename,
7804
- contentType: mediaType
8121
+ };
8122
+ tracingChannel.subscribe(handlers);
8123
+ return () => {
8124
+ tracingChannel.unsubscribe(handlers);
8125
+ };
8126
+ }
8127
+ function traceStreamingChannel(channel2, config) {
8128
+ const tracingChannel = channel2.tracingChannel();
8129
+ const states = /* @__PURE__ */ new WeakMap();
8130
+ const channelName = channel2.channelName;
8131
+ const handlers = {
8132
+ start: (event) => {
8133
+ states.set(
8134
+ event,
8135
+ startSpanForEvent(
8136
+ config,
8137
+ event,
8138
+ channelName
8139
+ )
8140
+ );
8141
+ },
8142
+ asyncEnd: (event) => {
8143
+ const spanData = states.get(event);
8144
+ if (!spanData) {
8145
+ return;
8146
+ }
8147
+ const asyncEndEvent = event;
8148
+ const { span, startTime } = spanData;
8149
+ if (isAsyncIterable(asyncEndEvent.result)) {
8150
+ let firstChunkTime;
8151
+ patchStreamIfNeeded(asyncEndEvent.result, {
8152
+ onChunk: () => {
8153
+ if (firstChunkTime === void 0) {
8154
+ firstChunkTime = getCurrentUnixTimestamp();
8155
+ }
8156
+ },
8157
+ onComplete: (chunks) => {
8158
+ try {
8159
+ let output;
8160
+ let metrics;
8161
+ let metadata;
8162
+ if (config.aggregateChunks) {
8163
+ const aggregated = config.aggregateChunks(
8164
+ chunks,
8165
+ asyncEndEvent.result,
8166
+ asyncEndEvent,
8167
+ startTime
8168
+ );
8169
+ output = aggregated.output;
8170
+ metrics = aggregated.metrics;
8171
+ metadata = aggregated.metadata;
8172
+ } else {
8173
+ output = config.extractOutput(
8174
+ chunks,
8175
+ asyncEndEvent
8176
+ );
8177
+ metrics = config.extractMetrics(
8178
+ chunks,
8179
+ startTime,
8180
+ asyncEndEvent
8181
+ );
8182
+ }
8183
+ if (metrics.time_to_first_token === void 0 && firstChunkTime !== void 0) {
8184
+ metrics.time_to_first_token = firstChunkTime - startTime;
8185
+ } else if (metrics.time_to_first_token === void 0 && chunks.length > 0) {
8186
+ metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8187
+ }
8188
+ span.log({
8189
+ output,
8190
+ ...metadata !== void 0 ? { metadata } : {},
8191
+ metrics
8192
+ });
8193
+ } catch (error) {
8194
+ console.error(
8195
+ `Error extracting output for ${channelName}:`,
8196
+ error
8197
+ );
8198
+ } finally {
8199
+ span.end();
8200
+ states.delete(event);
8201
+ }
8202
+ },
8203
+ onError: (error) => {
8204
+ span.log({
8205
+ error: error.message
8206
+ });
8207
+ span.end();
8208
+ states.delete(event);
8209
+ }
8210
+ });
8211
+ return;
8212
+ }
8213
+ if (config.patchResult?.({
8214
+ channelName,
8215
+ endEvent: asyncEndEvent,
8216
+ result: asyncEndEvent.result,
8217
+ span,
8218
+ startTime
8219
+ })) {
8220
+ states.delete(event);
8221
+ return;
8222
+ }
8223
+ try {
8224
+ const output = config.extractOutput(
8225
+ asyncEndEvent.result,
8226
+ asyncEndEvent
8227
+ );
8228
+ const metrics = config.extractMetrics(
8229
+ asyncEndEvent.result,
8230
+ startTime,
8231
+ asyncEndEvent
8232
+ );
8233
+ const metadata = config.extractMetadata?.(
8234
+ asyncEndEvent.result,
8235
+ asyncEndEvent
8236
+ );
8237
+ span.log({
8238
+ output,
8239
+ ...normalizeMetadata(metadata) !== void 0 ? { metadata: normalizeMetadata(metadata) } : {},
8240
+ metrics
8241
+ });
8242
+ } catch (error) {
8243
+ console.error(`Error extracting output for ${channelName}:`, error);
8244
+ } finally {
8245
+ span.end();
8246
+ states.delete(event);
8247
+ }
8248
+ },
8249
+ error: (event) => {
8250
+ logErrorAndEnd(states, event);
8251
+ }
8252
+ };
8253
+ tracingChannel.subscribe(handlers);
8254
+ return () => {
8255
+ tracingChannel.unsubscribe(handlers);
8256
+ };
8257
+ }
8258
+ function traceSyncStreamChannel(channel2, config) {
8259
+ const tracingChannel = channel2.tracingChannel();
8260
+ const states = /* @__PURE__ */ new WeakMap();
8261
+ const channelName = channel2.channelName;
8262
+ const handlers = {
8263
+ start: (event) => {
8264
+ states.set(
8265
+ event,
8266
+ startSpanForEvent(
8267
+ config,
8268
+ event,
8269
+ channelName
8270
+ )
8271
+ );
8272
+ },
8273
+ end: (event) => {
8274
+ const spanData = states.get(event);
8275
+ if (!spanData) {
8276
+ return;
8277
+ }
8278
+ const { span, startTime } = spanData;
8279
+ const endEvent = event;
8280
+ if (config.patchResult?.({
8281
+ channelName,
8282
+ endEvent,
8283
+ result: endEvent.result,
8284
+ span,
8285
+ startTime
8286
+ })) {
8287
+ return;
8288
+ }
8289
+ const stream = endEvent.result;
8290
+ if (!isSyncStreamLike(stream)) {
8291
+ span.end();
8292
+ states.delete(event);
8293
+ return;
8294
+ }
8295
+ let first = true;
8296
+ stream.on("chunk", () => {
8297
+ if (first) {
8298
+ span.log({
8299
+ metrics: {
8300
+ time_to_first_token: getCurrentUnixTimestamp() - startTime
8301
+ }
8302
+ });
8303
+ first = false;
8304
+ }
8305
+ });
8306
+ stream.on("chatCompletion", (completion) => {
8307
+ try {
8308
+ if (hasChoices(completion)) {
8309
+ span.log({
8310
+ output: completion.choices
8311
+ });
8312
+ }
8313
+ } catch (error) {
8314
+ console.error(
8315
+ `Error extracting chatCompletion for ${channelName}:`,
8316
+ error
8317
+ );
8318
+ }
8319
+ });
8320
+ stream.on("event", (streamEvent) => {
8321
+ if (!config.extractFromEvent) {
8322
+ return;
8323
+ }
8324
+ try {
8325
+ if (first) {
8326
+ span.log({
8327
+ metrics: {
8328
+ time_to_first_token: getCurrentUnixTimestamp() - startTime
8329
+ }
8330
+ });
8331
+ first = false;
8332
+ }
8333
+ const extracted = config.extractFromEvent(streamEvent);
8334
+ if (extracted && Object.keys(extracted).length > 0) {
8335
+ span.log(extracted);
8336
+ }
8337
+ } catch (error) {
8338
+ console.error(`Error extracting event for ${channelName}:`, error);
8339
+ }
8340
+ });
8341
+ stream.on("end", () => {
8342
+ span.end();
8343
+ states.delete(event);
8344
+ });
8345
+ stream.on("error", (error) => {
8346
+ span.log({
8347
+ error: error.message
7805
8348
  });
8349
+ span.end();
8350
+ states.delete(event);
8351
+ });
8352
+ },
8353
+ error: (event) => {
8354
+ logErrorAndEnd(states, event);
8355
+ }
8356
+ };
8357
+ tracingChannel.subscribe(handlers);
8358
+ return () => {
8359
+ tracingChannel.unsubscribe(handlers);
8360
+ };
8361
+ }
8362
+ function unsubscribeAll(unsubscribers) {
8363
+ for (const unsubscribe of unsubscribers) {
8364
+ unsubscribe();
8365
+ }
8366
+ return [];
8367
+ }
8368
+
8369
+ // src/wrappers/attachment-utils.ts
8370
+ function getExtensionFromMediaType(mediaType) {
8371
+ const extensionMap = {
8372
+ "image/png": "png",
8373
+ "image/jpeg": "jpg",
8374
+ "image/gif": "gif",
8375
+ "image/webp": "webp",
8376
+ "image/svg+xml": "svg",
8377
+ "audio/mpeg": "mp3",
8378
+ "audio/wav": "wav",
8379
+ "audio/ogg": "ogg",
8380
+ "video/mp4": "mp4",
8381
+ "video/webm": "webm",
8382
+ "application/pdf": "pdf",
8383
+ "application/json": "json",
8384
+ "text/plain": "txt",
8385
+ "text/html": "html",
8386
+ "text/csv": "csv"
8387
+ };
8388
+ return extensionMap[mediaType] || "bin";
8389
+ }
8390
+ function convertDataToBlob(data, mediaType) {
8391
+ try {
8392
+ if (typeof data === "string") {
8393
+ if (data.startsWith("data:")) {
8394
+ const base64Match = data.match(/^data:[^;]+;base64,(.+)$/);
8395
+ if (base64Match) {
8396
+ const base64 = base64Match[1];
8397
+ const binaryString = atob(base64);
8398
+ const bytes = new Uint8Array(binaryString.length);
8399
+ for (let i = 0; i < binaryString.length; i++) {
8400
+ bytes[i] = binaryString.charCodeAt(i);
8401
+ }
8402
+ return new Blob([bytes], { type: mediaType });
8403
+ }
8404
+ } else if (data.startsWith("http://") || data.startsWith("https://")) {
8405
+ return null;
8406
+ } else {
8407
+ const binaryString = atob(data);
8408
+ const bytes = new Uint8Array(binaryString.length);
8409
+ for (let i = 0; i < binaryString.length; i++) {
8410
+ bytes[i] = binaryString.charCodeAt(i);
8411
+ }
8412
+ return new Blob([bytes], { type: mediaType });
8413
+ }
8414
+ } else if (data instanceof Uint8Array) {
8415
+ return new Blob([data], { type: mediaType });
8416
+ } else if (data instanceof ArrayBuffer) {
8417
+ return new Blob([data], { type: mediaType });
8418
+ } else if (typeof Buffer !== "undefined" && data instanceof Buffer) {
8419
+ return new Blob([data], { type: mediaType });
8420
+ }
8421
+ } catch {
8422
+ return null;
8423
+ }
8424
+ return null;
8425
+ }
8426
+ function processInputAttachments(input) {
8427
+ if (!input) {
8428
+ return input;
8429
+ }
8430
+ let attachmentIndex = 0;
8431
+ const inferMediaTypeFromDataUrl = (value, fallback) => {
8432
+ const mediaTypeMatch = value.match(/^data:([^;]+);/);
8433
+ return mediaTypeMatch?.[1] || fallback;
8434
+ };
8435
+ const toAttachment = (value, mediaType, filename) => {
8436
+ const blob = convertDataToBlob(value, mediaType);
8437
+ if (!blob) {
8438
+ return null;
8439
+ }
8440
+ return new Attachment({
8441
+ data: blob,
8442
+ filename,
8443
+ contentType: mediaType
8444
+ });
8445
+ };
8446
+ const processNode = (node) => {
8447
+ if (Array.isArray(node)) {
8448
+ return node.map(processNode);
8449
+ }
8450
+ if (!node || typeof node !== "object") {
8451
+ return node;
8452
+ }
8453
+ if (node.type === "image_url" && node.image_url && typeof node.image_url === "object" && typeof node.image_url.url === "string" && node.image_url.url.startsWith("data:")) {
8454
+ const mediaType = inferMediaTypeFromDataUrl(
8455
+ node.image_url.url,
8456
+ "image/png"
8457
+ );
8458
+ const filename = `image.${getExtensionFromMediaType(mediaType)}`;
8459
+ const attachment = toAttachment(node.image_url.url, mediaType, filename);
8460
+ if (attachment) {
7806
8461
  return {
7807
- ...part,
7808
- data: attachment
8462
+ ...node,
8463
+ image_url: {
8464
+ ...node.image_url,
8465
+ url: attachment
8466
+ }
7809
8467
  };
7810
8468
  }
7811
8469
  }
7812
- return part;
7813
- };
7814
- const processMessage = (message) => {
7815
- if (!message || typeof message !== "object") {
7816
- return message;
8470
+ if (node.type === "file" && node.file && typeof node.file === "object" && typeof node.file.file_data === "string" && node.file.file_data.startsWith("data:")) {
8471
+ const mediaType = inferMediaTypeFromDataUrl(
8472
+ node.file.file_data,
8473
+ "application/octet-stream"
8474
+ );
8475
+ const filename = typeof node.file.filename === "string" && node.file.filename ? node.file.filename : `document.${getExtensionFromMediaType(mediaType)}`;
8476
+ const attachment = toAttachment(node.file.file_data, mediaType, filename);
8477
+ if (attachment) {
8478
+ return {
8479
+ ...node,
8480
+ file: {
8481
+ ...node.file,
8482
+ file_data: attachment
8483
+ }
8484
+ };
8485
+ }
7817
8486
  }
7818
- if (Array.isArray(message.content)) {
7819
- return {
7820
- ...message,
7821
- content: message.content.map(processContentPart)
7822
- };
8487
+ if (node.type === "image" && node.image) {
8488
+ let mediaType = "image/png";
8489
+ if (typeof node.image === "string" && node.image.startsWith("data:")) {
8490
+ mediaType = inferMediaTypeFromDataUrl(node.image, mediaType);
8491
+ } else if (node.mediaType) {
8492
+ mediaType = node.mediaType;
8493
+ }
8494
+ const filename = `input_image_${attachmentIndex}.${getExtensionFromMediaType(mediaType)}`;
8495
+ const attachment = toAttachment(node.image, mediaType, filename);
8496
+ if (attachment) {
8497
+ attachmentIndex++;
8498
+ return {
8499
+ ...node,
8500
+ image: attachment
8501
+ };
8502
+ }
8503
+ }
8504
+ if (node.type === "file" && node.data) {
8505
+ const mediaType = node.mediaType || "application/octet-stream";
8506
+ const filename = node.filename || `input_file_${attachmentIndex}.${getExtensionFromMediaType(mediaType)}`;
8507
+ const attachment = toAttachment(node.data, mediaType, filename);
8508
+ if (attachment) {
8509
+ attachmentIndex++;
8510
+ return {
8511
+ ...node,
8512
+ data: attachment
8513
+ };
8514
+ }
8515
+ }
8516
+ const processed = {};
8517
+ for (const [key, value] of Object.entries(node)) {
8518
+ processed[key] = processNode(value);
7823
8519
  }
7824
- return message;
8520
+ return processed;
7825
8521
  };
7826
8522
  if (Array.isArray(input)) {
7827
- return input.map(processMessage);
7828
- } else if (typeof input === "object" && input.content) {
7829
- return processMessage(input);
8523
+ return input.map(processNode);
8524
+ }
8525
+ return processNode(input);
8526
+ }
8527
+
8528
+ // src/instrumentation/core/channel-definitions.ts
8529
+ function channel(spec) {
8530
+ return spec;
8531
+ }
8532
+ function defineChannels(pkg, channels) {
8533
+ return Object.fromEntries(
8534
+ Object.entries(channels).map(([key, spec]) => {
8535
+ const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
8536
+ if (spec.kind === "async") {
8537
+ const asyncSpec = spec;
8538
+ const tracingChannel2 = () => isomorph_default.newTracingChannel(
8539
+ fullChannelName
8540
+ );
8541
+ return [
8542
+ key,
8543
+ {
8544
+ ...asyncSpec,
8545
+ tracingChannel: tracingChannel2,
8546
+ tracePromise: (fn, context) => tracingChannel2().tracePromise(
8547
+ fn,
8548
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
8549
+ context
8550
+ )
8551
+ }
8552
+ ];
8553
+ }
8554
+ const syncSpec = spec;
8555
+ const tracingChannel = () => isomorph_default.newTracingChannel(
8556
+ fullChannelName
8557
+ );
8558
+ return [
8559
+ key,
8560
+ {
8561
+ ...syncSpec,
8562
+ tracingChannel,
8563
+ traceSync: (fn, context) => tracingChannel().traceSync(
8564
+ fn,
8565
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
8566
+ context
8567
+ )
8568
+ }
8569
+ ];
8570
+ })
8571
+ );
8572
+ }
8573
+
8574
+ // src/instrumentation/plugins/openai-channels.ts
8575
+ var openAIChannels = defineChannels("openai", {
8576
+ chatCompletionsCreate: channel({
8577
+ channelName: "chat.completions.create",
8578
+ kind: "async"
8579
+ }),
8580
+ embeddingsCreate: channel({
8581
+ channelName: "embeddings.create",
8582
+ kind: "async"
8583
+ }),
8584
+ betaChatCompletionsParse: channel({
8585
+ channelName: "beta.chat.completions.parse",
8586
+ kind: "async"
8587
+ }),
8588
+ betaChatCompletionsStream: channel({
8589
+ channelName: "beta.chat.completions.stream",
8590
+ kind: "sync-stream"
8591
+ }),
8592
+ moderationsCreate: channel({
8593
+ channelName: "moderations.create",
8594
+ kind: "async"
8595
+ }),
8596
+ responsesCreate: channel({
8597
+ channelName: "responses.create",
8598
+ kind: "async"
8599
+ }),
8600
+ responsesStream: channel({
8601
+ channelName: "responses.stream",
8602
+ kind: "sync-stream"
8603
+ }),
8604
+ responsesParse: channel({
8605
+ channelName: "responses.parse",
8606
+ kind: "async"
8607
+ })
8608
+ });
8609
+
8610
+ // src/openai-utils.ts
8611
+ var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
8612
+ var LEGACY_CACHED_HEADER = "x-cached";
8613
+ var X_CACHED_HEADER = "x-bt-cached";
8614
+ var TOKEN_NAME_MAP = {
8615
+ input_tokens: "prompt_tokens",
8616
+ output_tokens: "completion_tokens",
8617
+ total_tokens: "tokens"
8618
+ };
8619
+ var TOKEN_PREFIX_MAP = {
8620
+ input: "prompt",
8621
+ output: "completion"
8622
+ };
8623
+ function parseMetricsFromUsage(usage) {
8624
+ if (!usage) {
8625
+ return {};
8626
+ }
8627
+ const metrics = {};
8628
+ for (const [oaiName, value] of Object.entries(usage)) {
8629
+ if (typeof value === "number") {
8630
+ const metricName = TOKEN_NAME_MAP[oaiName] || oaiName;
8631
+ metrics[metricName] = value;
8632
+ continue;
8633
+ }
8634
+ if (!oaiName.endsWith("_tokens_details") || !isObject(value)) {
8635
+ continue;
8636
+ }
8637
+ const rawPrefix = oaiName.slice(0, -"_tokens_details".length);
8638
+ const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
8639
+ for (const [key, nestedValue] of Object.entries(value)) {
8640
+ if (typeof nestedValue !== "number") {
8641
+ continue;
8642
+ }
8643
+ metrics[`${prefix}_${key}`] = nestedValue;
8644
+ }
8645
+ }
8646
+ return metrics;
8647
+ }
8648
+ function parseCachedHeader(value) {
8649
+ if (!value) {
8650
+ return void 0;
7830
8651
  }
7831
- return input;
8652
+ return ["true", "hit"].includes(value.toLowerCase()) ? 1 : 0;
8653
+ }
8654
+ function getCachedMetricFromHeaders(headers) {
8655
+ if (!headers || typeof headers.get !== "function") {
8656
+ return void 0;
8657
+ }
8658
+ const cachedHeader = headers.get(X_CACHED_HEADER);
8659
+ if (cachedHeader) {
8660
+ return parseCachedHeader(cachedHeader);
8661
+ }
8662
+ return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
7832
8663
  }
7833
8664
 
7834
8665
  // src/instrumentation/plugins/openai-plugin.ts
@@ -7837,13 +8668,11 @@ var OpenAIPlugin = class extends BasePlugin {
7837
8668
  super();
7838
8669
  }
7839
8670
  onEnable() {
7840
- this.subscribeToStreamingChannel(
7841
- "orchestrion:openai:chat.completions.create",
7842
- {
8671
+ this.unsubscribers.push(
8672
+ traceStreamingChannel(openAIChannels.chatCompletionsCreate, {
7843
8673
  name: "Chat Completion",
7844
8674
  type: "llm" /* LLM */,
7845
- extractInput: (args) => {
7846
- const params = args[0] || {};
8675
+ extractInput: ([params]) => {
7847
8676
  const { messages, ...metadata } = params;
7848
8677
  return {
7849
8678
  input: processInputAttachments(messages),
@@ -7853,41 +8682,49 @@ var OpenAIPlugin = class extends BasePlugin {
7853
8682
  extractOutput: (result) => {
7854
8683
  return result?.choices;
7855
8684
  },
7856
- extractMetrics: (result, startTime) => {
7857
- const metrics = parseMetricsFromUsage(result?.usage);
8685
+ extractMetrics: (result, startTime, endEvent) => {
8686
+ const metrics = withCachedMetric(
8687
+ parseMetricsFromUsage(result?.usage),
8688
+ result,
8689
+ endEvent
8690
+ );
7858
8691
  if (startTime) {
7859
8692
  metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
7860
8693
  }
7861
8694
  return metrics;
7862
8695
  },
7863
8696
  aggregateChunks: aggregateChatCompletionChunks
7864
- }
8697
+ })
7865
8698
  );
7866
- this.subscribeToChannel("orchestrion:openai:embeddings.create", {
7867
- name: "Embedding",
7868
- type: "llm" /* LLM */,
7869
- extractInput: (args) => {
7870
- const params = args[0] || {};
7871
- const { input, ...metadata } = params;
7872
- return {
7873
- input,
7874
- metadata: { ...metadata, provider: "openai" }
7875
- };
7876
- },
7877
- extractOutput: (result) => {
7878
- return result?.data?.map((d) => d.embedding);
7879
- },
7880
- extractMetrics: (result) => {
7881
- return parseMetricsFromUsage(result?.usage);
7882
- }
7883
- });
7884
- this.subscribeToStreamingChannel(
7885
- "orchestrion:openai:beta.chat.completions.parse",
7886
- {
8699
+ this.unsubscribers.push(
8700
+ traceAsyncChannel(openAIChannels.embeddingsCreate, {
8701
+ name: "Embedding",
8702
+ type: "llm" /* LLM */,
8703
+ extractInput: ([params]) => {
8704
+ const { input, ...metadata } = params;
8705
+ return {
8706
+ input,
8707
+ metadata: { ...metadata, provider: "openai" }
8708
+ };
8709
+ },
8710
+ extractOutput: (result) => {
8711
+ const embedding = result?.data?.[0]?.embedding;
8712
+ return Array.isArray(embedding) ? { embedding_length: embedding.length } : void 0;
8713
+ },
8714
+ extractMetrics: (result, _startTime, endEvent) => {
8715
+ return withCachedMetric(
8716
+ parseMetricsFromUsage(result?.usage),
8717
+ result,
8718
+ endEvent
8719
+ );
8720
+ }
8721
+ })
8722
+ );
8723
+ this.unsubscribers.push(
8724
+ traceStreamingChannel(openAIChannels.betaChatCompletionsParse, {
7887
8725
  name: "Chat Completion",
7888
8726
  type: "llm" /* LLM */,
7889
- extractInput: (args) => {
7890
- const params = args[0] || {};
8727
+ extractInput: ([params]) => {
7891
8728
  const { messages, ...metadata } = params;
7892
8729
  return {
7893
8730
  input: processInputAttachments(messages),
@@ -7897,164 +8734,196 @@ var OpenAIPlugin = class extends BasePlugin {
7897
8734
  extractOutput: (result) => {
7898
8735
  return result?.choices;
7899
8736
  },
7900
- extractMetrics: (result, startTime) => {
7901
- const metrics = parseMetricsFromUsage(result?.usage);
8737
+ extractMetrics: (result, startTime, endEvent) => {
8738
+ const metrics = withCachedMetric(
8739
+ parseMetricsFromUsage(result?.usage),
8740
+ result,
8741
+ endEvent
8742
+ );
7902
8743
  if (startTime) {
7903
8744
  metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
7904
8745
  }
7905
8746
  return metrics;
7906
8747
  },
7907
8748
  aggregateChunks: aggregateChatCompletionChunks
7908
- }
8749
+ })
7909
8750
  );
7910
- this.subscribeToSyncStreamChannel(
7911
- "orchestrion:openai:beta.chat.completions.stream",
7912
- {
8751
+ this.unsubscribers.push(
8752
+ traceSyncStreamChannel(openAIChannels.betaChatCompletionsStream, {
7913
8753
  name: "Chat Completion",
7914
8754
  type: "llm" /* LLM */,
7915
- extractInput: (args) => {
7916
- const params = args[0] || {};
8755
+ extractInput: ([params]) => {
7917
8756
  const { messages, ...metadata } = params;
7918
8757
  return {
7919
8758
  input: processInputAttachments(messages),
7920
8759
  metadata: { ...metadata, provider: "openai" }
7921
8760
  };
7922
8761
  }
7923
- }
8762
+ })
7924
8763
  );
7925
- this.subscribeToChannel("orchestrion:openai:moderations.create", {
7926
- name: "Moderation",
7927
- type: "llm" /* LLM */,
7928
- extractInput: (args) => {
7929
- const params = args[0] || {};
7930
- const { input, ...metadata } = params;
7931
- return {
7932
- input,
7933
- metadata: { ...metadata, provider: "openai" }
7934
- };
7935
- },
7936
- extractOutput: (result) => {
7937
- return result?.results;
7938
- },
7939
- extractMetrics: () => {
7940
- return {};
7941
- }
7942
- });
7943
- this.subscribeToStreamingChannel("orchestrion:openai:responses.create", {
7944
- name: "openai.responses.create",
7945
- type: "llm" /* LLM */,
7946
- extractInput: (args) => {
7947
- const params = args[0] || {};
7948
- const { input, ...metadata } = params;
7949
- return {
7950
- input: processInputAttachments(input),
7951
- metadata: { ...metadata, provider: "openai" }
7952
- };
7953
- },
7954
- extractOutput: (result) => {
7955
- return processImagesInOutput(result?.output);
7956
- },
7957
- extractMetrics: (result, startTime) => {
7958
- const metrics = parseMetricsFromUsage(result?.usage);
7959
- if (startTime) {
7960
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
7961
- }
7962
- return metrics;
7963
- }
7964
- });
7965
- this.subscribeToSyncStreamChannel("orchestrion:openai:responses.stream", {
7966
- name: "openai.responses.stream",
7967
- type: "llm" /* LLM */,
7968
- extractInput: (args) => {
7969
- const params = args[0] || {};
7970
- const { input, ...metadata } = params;
7971
- return {
7972
- input: processInputAttachments(input),
7973
- metadata: { ...metadata, provider: "openai" }
7974
- };
7975
- },
7976
- extractFromEvent: (event) => {
7977
- if (!event || !event.type || !event.response) {
7978
- return {};
8764
+ this.unsubscribers.push(
8765
+ traceAsyncChannel(openAIChannels.moderationsCreate, {
8766
+ name: "Moderation",
8767
+ type: "llm" /* LLM */,
8768
+ extractInput: ([params]) => {
8769
+ const { input, ...metadata } = params;
8770
+ return {
8771
+ input,
8772
+ metadata: { ...metadata, provider: "openai" }
8773
+ };
8774
+ },
8775
+ extractOutput: (result) => {
8776
+ return result?.results;
8777
+ },
8778
+ extractMetrics: (result, _startTime, endEvent) => {
8779
+ return withCachedMetric(
8780
+ parseMetricsFromUsage(result?.usage),
8781
+ result,
8782
+ endEvent
8783
+ );
7979
8784
  }
7980
- const response = event.response;
7981
- if (event.type === "response.completed") {
8785
+ })
8786
+ );
8787
+ this.unsubscribers.push(
8788
+ traceStreamingChannel(openAIChannels.responsesCreate, {
8789
+ name: "openai.responses.create",
8790
+ type: "llm" /* LLM */,
8791
+ extractInput: ([params]) => {
8792
+ const { input, ...metadata } = params;
8793
+ return {
8794
+ input: processInputAttachments(input),
8795
+ metadata: { ...metadata, provider: "openai" }
8796
+ };
8797
+ },
8798
+ extractOutput: (result) => {
8799
+ return processImagesInOutput(result?.output);
8800
+ },
8801
+ extractMetadata: (result) => {
8802
+ if (!result) {
8803
+ return void 0;
8804
+ }
8805
+ const { output: _output, usage: _usage, ...metadata } = result;
8806
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
8807
+ },
8808
+ extractMetrics: (result, startTime, endEvent) => {
8809
+ const metrics = withCachedMetric(
8810
+ parseMetricsFromUsage(result?.usage),
8811
+ result,
8812
+ endEvent
8813
+ );
8814
+ if (startTime) {
8815
+ metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8816
+ }
8817
+ return metrics;
8818
+ },
8819
+ aggregateChunks: aggregateResponseStreamEvents
8820
+ })
8821
+ );
8822
+ this.unsubscribers.push(
8823
+ traceSyncStreamChannel(openAIChannels.responsesStream, {
8824
+ name: "openai.responses.create",
8825
+ type: "llm" /* LLM */,
8826
+ extractInput: ([params]) => {
8827
+ const { input, ...metadata } = params;
8828
+ return {
8829
+ input: processInputAttachments(input),
8830
+ metadata: { ...metadata, provider: "openai" }
8831
+ };
8832
+ },
8833
+ extractFromEvent: (event) => {
8834
+ if (event.type !== "response.completed" || !event.response) {
8835
+ return {};
8836
+ }
8837
+ const response = event.response;
7982
8838
  const data = {};
7983
- if (response?.output !== void 0) {
8839
+ if (response.output !== void 0) {
7984
8840
  data.output = processImagesInOutput(response.output);
7985
8841
  }
7986
- if (response) {
7987
- const { usage: _usage, output: _output, ...metadata } = response;
7988
- if (Object.keys(metadata).length > 0) {
7989
- data.metadata = metadata;
7990
- }
8842
+ const { usage: _usage, output: _output, ...metadata } = response;
8843
+ if (Object.keys(metadata).length > 0) {
8844
+ data.metadata = metadata;
7991
8845
  }
7992
- data.metrics = parseMetricsFromUsage(response?.usage);
8846
+ data.metrics = parseMetricsFromUsage(response.usage);
7993
8847
  return data;
7994
8848
  }
7995
- return {};
7996
- }
7997
- });
7998
- this.subscribeToStreamingChannel("orchestrion:openai:responses.parse", {
7999
- name: "openai.responses.parse",
8000
- type: "llm" /* LLM */,
8001
- extractInput: (args) => {
8002
- const params = args[0] || {};
8003
- const { input, ...metadata } = params;
8004
- return {
8005
- input: processInputAttachments(input),
8006
- metadata: { ...metadata, provider: "openai" }
8007
- };
8008
- },
8009
- extractOutput: (result) => {
8010
- return processImagesInOutput(result?.output);
8011
- },
8012
- extractMetrics: (result, startTime) => {
8013
- const metrics = parseMetricsFromUsage(result?.usage);
8014
- if (startTime) {
8015
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8016
- }
8017
- return metrics;
8018
- }
8019
- });
8849
+ })
8850
+ );
8851
+ this.unsubscribers.push(
8852
+ traceStreamingChannel(openAIChannels.responsesParse, {
8853
+ name: "openai.responses.parse",
8854
+ type: "llm" /* LLM */,
8855
+ extractInput: ([params]) => {
8856
+ const { input, ...metadata } = params;
8857
+ return {
8858
+ input: processInputAttachments(input),
8859
+ metadata: { ...metadata, provider: "openai" }
8860
+ };
8861
+ },
8862
+ extractOutput: (result) => {
8863
+ return processImagesInOutput(result?.output);
8864
+ },
8865
+ extractMetadata: (result) => {
8866
+ if (!result) {
8867
+ return void 0;
8868
+ }
8869
+ const { output: _output, usage: _usage, ...metadata } = result;
8870
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
8871
+ },
8872
+ extractMetrics: (result, startTime, endEvent) => {
8873
+ const metrics = withCachedMetric(
8874
+ parseMetricsFromUsage(result?.usage),
8875
+ result,
8876
+ endEvent
8877
+ );
8878
+ if (startTime) {
8879
+ metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8880
+ }
8881
+ return metrics;
8882
+ },
8883
+ aggregateChunks: aggregateResponseStreamEvents
8884
+ })
8885
+ );
8020
8886
  }
8021
8887
  onDisable() {
8888
+ this.unsubscribers = unsubscribeAll(this.unsubscribers);
8022
8889
  }
8023
8890
  };
8024
- var TOKEN_NAME_MAP = {
8025
- input_tokens: "prompt_tokens",
8026
- output_tokens: "completion_tokens",
8027
- total_tokens: "tokens"
8028
- };
8029
- var TOKEN_PREFIX_MAP = {
8030
- input: "prompt",
8031
- output: "completion"
8032
- };
8033
- function parseMetricsFromUsage(usage) {
8034
- if (!usage) {
8035
- return {};
8891
+ function getCachedMetricFromEndEvent(endEvent) {
8892
+ if (!isObject(endEvent)) {
8893
+ return void 0;
8036
8894
  }
8037
- const metrics = {};
8038
- for (const [oai_name, value] of Object.entries(usage)) {
8039
- if (typeof value === "number") {
8040
- const metricName = TOKEN_NAME_MAP[oai_name] || oai_name;
8041
- metrics[metricName] = value;
8042
- } else if (oai_name.endsWith("_tokens_details")) {
8043
- if (!isObject(value)) {
8044
- continue;
8045
- }
8046
- const rawPrefix = oai_name.slice(0, -"_tokens_details".length);
8047
- const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
8048
- for (const [key, n] of Object.entries(value)) {
8049
- if (typeof n !== "number") {
8050
- continue;
8051
- }
8052
- const metricName = `${prefix}_${key}`;
8053
- metrics[metricName] = n;
8054
- }
8055
- }
8895
+ const response = endEvent.response;
8896
+ if (!isObject(response)) {
8897
+ return void 0;
8056
8898
  }
8057
- return metrics;
8899
+ const headers = response.headers;
8900
+ if (!headers || typeof headers.get !== "function") {
8901
+ return void 0;
8902
+ }
8903
+ return getCachedMetricFromHeaders(headers);
8904
+ }
8905
+ function withCachedMetric(metrics, result, endEvent) {
8906
+ if (metrics.cached !== void 0) {
8907
+ return metrics;
8908
+ }
8909
+ const cachedFromEvent = getCachedMetricFromEndEvent(endEvent);
8910
+ if (cachedFromEvent !== void 0) {
8911
+ return {
8912
+ ...metrics,
8913
+ cached: cachedFromEvent
8914
+ };
8915
+ }
8916
+ if (!isObject(result)) {
8917
+ return metrics;
8918
+ }
8919
+ const cached = result[BRAINTRUST_CACHED_STREAM_METRIC];
8920
+ if (typeof cached !== "number") {
8921
+ return metrics;
8922
+ }
8923
+ return {
8924
+ ...metrics,
8925
+ cached
8926
+ };
8058
8927
  }
8059
8928
  function processImagesInOutput(output) {
8060
8929
  if (Array.isArray(output)) {
@@ -8085,7 +8954,7 @@ function processImagesInOutput(output) {
8085
8954
  }
8086
8955
  return output;
8087
8956
  }
8088
- function aggregateChatCompletionChunks(chunks) {
8957
+ function aggregateChatCompletionChunks(chunks, streamResult, endEvent) {
8089
8958
  let role = void 0;
8090
8959
  let content = void 0;
8091
8960
  let tool_calls = void 0;
@@ -8127,6 +8996,7 @@ function aggregateChatCompletionChunks(chunks) {
8127
8996
  }
8128
8997
  }
8129
8998
  }
8999
+ metrics = withCachedMetric(metrics, streamResult, endEvent);
8130
9000
  return {
8131
9001
  metrics,
8132
9002
  output: [
@@ -8143,9 +9013,33 @@ function aggregateChatCompletionChunks(chunks) {
8143
9013
  ]
8144
9014
  };
8145
9015
  }
8146
-
8147
- // src/instrumentation/plugins/anthropic-plugin.ts
8148
- import { tracingChannel as tracingChannel2 } from "dc-browser";
9016
+ function aggregateResponseStreamEvents(chunks, _streamResult, endEvent) {
9017
+ let output = void 0;
9018
+ let metrics = {};
9019
+ let metadata = void 0;
9020
+ for (const chunk of chunks) {
9021
+ if (!chunk || !chunk.type || !chunk.response) {
9022
+ continue;
9023
+ }
9024
+ if (chunk.type !== "response.completed") {
9025
+ continue;
9026
+ }
9027
+ const response = chunk.response;
9028
+ if (response?.output !== void 0) {
9029
+ output = processImagesInOutput(response.output);
9030
+ }
9031
+ const { usage: _usage, output: _output, ...rest } = response || {};
9032
+ if (Object.keys(rest).length > 0) {
9033
+ metadata = rest;
9034
+ }
9035
+ metrics = parseMetricsFromUsage(response?.usage);
9036
+ }
9037
+ return {
9038
+ output,
9039
+ metrics: withCachedMetric(metrics, void 0, endEvent),
9040
+ ...metadata !== void 0 ? { metadata } : {}
9041
+ };
9042
+ }
8149
9043
 
8150
9044
  // src/wrappers/anthropic-tokens-util.ts
8151
9045
  function finalizeAnthropicTokens(metrics) {
@@ -8167,215 +9061,75 @@ function extractAnthropicCacheTokens(cacheReadTokens = 0, cacheCreationTokens =
8167
9061
  return cacheTokens;
8168
9062
  }
8169
9063
 
9064
+ // src/instrumentation/plugins/anthropic-channels.ts
9065
+ var anthropicChannels = defineChannels("@anthropic-ai/sdk", {
9066
+ messagesCreate: channel({
9067
+ channelName: "messages.create",
9068
+ kind: "async"
9069
+ }),
9070
+ betaMessagesCreate: channel({
9071
+ channelName: "beta.messages.create",
9072
+ kind: "async"
9073
+ })
9074
+ });
9075
+
8170
9076
  // src/instrumentation/plugins/anthropic-plugin.ts
8171
9077
  var AnthropicPlugin = class extends BasePlugin {
8172
- unsubscribers = [];
8173
- onEnable() {
8174
- this.subscribeToAnthropicChannels();
8175
- }
8176
- onDisable() {
8177
- for (const unsubscribe of this.unsubscribers) {
8178
- unsubscribe();
8179
- }
8180
- this.unsubscribers = [];
8181
- }
8182
- subscribeToAnthropicChannels() {
8183
- this.subscribeToStreamingChannel("orchestrion:anthropic:messages.create", {
8184
- name: "anthropic.messages.create",
8185
- type: "llm" /* LLM */,
8186
- extractInput: (args) => {
8187
- const params = args[0] || {};
8188
- const input = coalesceInput(params.messages || [], params.system);
8189
- const metadata = filterFrom(params, ["messages", "system"]);
8190
- return {
8191
- input: processAttachmentsInInput(input),
8192
- metadata: { ...metadata, provider: "anthropic" }
8193
- };
8194
- },
8195
- extractOutput: (result) => {
8196
- return result ? { role: result.role, content: result.content } : null;
8197
- },
8198
- extractMetrics: (result, startTime) => {
8199
- const metrics = parseMetricsFromUsage2(result?.usage);
8200
- if (startTime) {
8201
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8202
- }
8203
- const finalized = finalizeAnthropicTokens(metrics);
8204
- return Object.fromEntries(
8205
- Object.entries(finalized).filter(([, v]) => v !== void 0)
8206
- );
8207
- },
8208
- extractMetadata: (result) => {
8209
- const metadata = {};
8210
- const metas = ["stop_reason", "stop_sequence"];
8211
- for (const m of metas) {
8212
- if (result?.[m] !== void 0) {
8213
- metadata[m] = result[m];
8214
- }
8215
- }
8216
- return metadata;
8217
- },
8218
- aggregateChunks: aggregateAnthropicStreamChunks,
8219
- isStreaming: (args) => {
8220
- return args[0]?.stream === true;
8221
- }
8222
- });
8223
- this.subscribeToStreamingChannel(
8224
- "orchestrion:anthropic:beta.messages.create",
8225
- {
8226
- name: "anthropic.beta.messages.create",
8227
- type: "llm" /* LLM */,
8228
- extractInput: (args) => {
8229
- const params = args[0] || {};
8230
- const input = coalesceInput(params.messages || [], params.system);
8231
- const metadata = filterFrom(params, ["messages", "system"]);
8232
- return {
8233
- input: processAttachmentsInInput(input),
8234
- metadata: { ...metadata, provider: "anthropic" }
8235
- };
8236
- },
8237
- extractOutput: (result) => {
8238
- return result ? { role: result.role, content: result.content } : null;
8239
- },
8240
- extractMetrics: (result, startTime) => {
8241
- const metrics = parseMetricsFromUsage2(result?.usage);
8242
- if (startTime) {
8243
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8244
- }
8245
- const finalized = finalizeAnthropicTokens(metrics);
8246
- return Object.fromEntries(
8247
- Object.entries(finalized).filter(([, v]) => v !== void 0)
8248
- );
8249
- },
8250
- extractMetadata: (result) => {
8251
- const metadata = {};
8252
- const metas = ["stop_reason", "stop_sequence"];
8253
- for (const m of metas) {
8254
- if (result?.[m] !== void 0) {
8255
- metadata[m] = result[m];
8256
- }
8257
- }
8258
- return metadata;
8259
- },
8260
- aggregateChunks: aggregateAnthropicStreamChunks,
8261
- isStreaming: (args) => {
8262
- return args[0]?.stream === true;
8263
- }
8264
- }
8265
- );
9078
+ onEnable() {
9079
+ this.subscribeToAnthropicChannels();
8266
9080
  }
8267
- /**
8268
- * Subscribe to a channel for async methods that may return streams.
8269
- * Handles both streaming and non-streaming responses based on the stream parameter.
8270
- */
8271
- subscribeToStreamingChannel(channelName, config) {
8272
- const channel = tracingChannel2(channelName);
8273
- const spans = /* @__PURE__ */ new WeakMap();
8274
- const handlers = {
8275
- start: (event) => {
8276
- const span = startSpan({
8277
- name: config.name,
8278
- spanAttributes: {
8279
- type: config.type
8280
- }
8281
- });
8282
- const startTime = getCurrentUnixTimestamp();
8283
- spans.set(event, { span, startTime });
8284
- try {
8285
- const { input, metadata } = config.extractInput(event.arguments);
8286
- span.log({
8287
- input,
8288
- metadata
8289
- });
8290
- } catch (error) {
8291
- console.error(`Error extracting input for ${channelName}:`, error);
8292
- }
9081
+ onDisable() {
9082
+ this.unsubscribers = unsubscribeAll(this.unsubscribers);
9083
+ }
9084
+ subscribeToAnthropicChannels() {
9085
+ const anthropicConfig = {
9086
+ name: "anthropic.messages.create",
9087
+ type: "llm" /* LLM */,
9088
+ extractInput: (args) => {
9089
+ const params = args[0] || {};
9090
+ const input = coalesceInput(params.messages || [], params.system);
9091
+ const metadata = filterFrom(params, ["messages", "system"]);
9092
+ return {
9093
+ input: processAttachmentsInInput(input),
9094
+ metadata: { ...metadata, provider: "anthropic" }
9095
+ };
8293
9096
  },
8294
- asyncEnd: (event) => {
8295
- const spanData = spans.get(event);
8296
- if (!spanData) {
8297
- return;
9097
+ extractOutput: (message) => {
9098
+ return message ? { role: message.role, content: message.content } : null;
9099
+ },
9100
+ extractMetrics: (message, startTime) => {
9101
+ const metrics = parseMetricsFromUsage2(message?.usage);
9102
+ if (startTime) {
9103
+ metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8298
9104
  }
8299
- const { span, startTime } = spanData;
8300
- const isStreaming = config.isStreaming ? config.isStreaming(event.arguments) : isAsyncIterable(event.result);
8301
- if (isStreaming && isAsyncIterable(event.result)) {
8302
- patchStreamIfNeeded(event.result, {
8303
- onComplete: (chunks) => {
8304
- try {
8305
- let output;
8306
- let metrics;
8307
- let metadata = {};
8308
- if (config.aggregateChunks) {
8309
- const aggregated = config.aggregateChunks(chunks);
8310
- output = aggregated.output;
8311
- metrics = aggregated.metrics;
8312
- metadata = aggregated.metadata || {};
8313
- } else {
8314
- output = config.extractOutput(chunks);
8315
- metrics = config.extractMetrics(chunks, startTime);
8316
- if (config.extractMetadata) {
8317
- metadata = config.extractMetadata(chunks);
8318
- }
8319
- }
8320
- if (!metrics.time_to_first_token && chunks.length > 0) {
8321
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8322
- }
8323
- span.log({
8324
- output,
8325
- metrics,
8326
- metadata
8327
- });
8328
- } catch (error) {
8329
- console.error(
8330
- `Error extracting output for ${channelName}:`,
8331
- error
8332
- );
8333
- } finally {
8334
- span.end();
8335
- }
8336
- },
8337
- onError: (error) => {
8338
- span.log({
8339
- error: error.message
8340
- });
8341
- span.end();
8342
- }
8343
- });
8344
- } else {
8345
- try {
8346
- const output = config.extractOutput(event.result);
8347
- const metrics = config.extractMetrics(event.result, startTime);
8348
- const metadata = config.extractMetadata ? config.extractMetadata(event.result) : {};
8349
- span.log({
8350
- output,
8351
- metrics,
8352
- metadata
8353
- });
8354
- } catch (error) {
8355
- console.error(`Error extracting output for ${channelName}:`, error);
8356
- } finally {
8357
- span.end();
8358
- spans.delete(event);
9105
+ const finalized = finalizeAnthropicTokens(metrics);
9106
+ return Object.fromEntries(
9107
+ Object.entries(finalized).filter(
9108
+ (entry) => entry[1] !== void 0
9109
+ )
9110
+ );
9111
+ },
9112
+ extractMetadata: (message) => {
9113
+ const metadata = {};
9114
+ const metas = ["stop_reason", "stop_sequence"];
9115
+ for (const m of metas) {
9116
+ if (message?.[m] !== void 0) {
9117
+ metadata[m] = message[m];
8359
9118
  }
8360
9119
  }
9120
+ return metadata;
8361
9121
  },
8362
- error: (event) => {
8363
- const spanData = spans.get(event);
8364
- if (!spanData) {
8365
- return;
8366
- }
8367
- const { span } = spanData;
8368
- span.log({
8369
- error: event.error.message
8370
- });
8371
- span.end();
8372
- spans.delete(event);
8373
- }
9122
+ aggregateChunks: (chunks) => aggregateAnthropicStreamChunks(chunks)
8374
9123
  };
8375
- channel.subscribe(handlers);
8376
- this.unsubscribers.push(() => {
8377
- channel.unsubscribe(handlers);
8378
- });
9124
+ this.unsubscribers.push(
9125
+ traceStreamingChannel(anthropicChannels.messagesCreate, anthropicConfig)
9126
+ );
9127
+ this.unsubscribers.push(
9128
+ traceStreamingChannel(anthropicChannels.betaMessagesCreate, {
9129
+ ...anthropicConfig,
9130
+ name: "anthropic.beta.messages.create"
9131
+ })
9132
+ );
8379
9133
  }
8380
9134
  };
8381
9135
  function parseMetricsFromUsage2(usage) {
@@ -8399,29 +9153,29 @@ function aggregateAnthropicStreamChunks(chunks) {
8399
9153
  const deltas = [];
8400
9154
  let metrics = {};
8401
9155
  let metadata = {};
8402
- for (const chunk of chunks) {
8403
- switch (chunk?.type) {
9156
+ for (const event of chunks) {
9157
+ switch (event?.type) {
8404
9158
  case "message_start":
8405
- if (chunk.message?.usage) {
8406
- const initialMetrics = parseMetricsFromUsage2(chunk.message.usage);
9159
+ if (event.message?.usage) {
9160
+ const initialMetrics = parseMetricsFromUsage2(event.message.usage);
8407
9161
  metrics = { ...metrics, ...initialMetrics };
8408
9162
  }
8409
9163
  break;
8410
9164
  case "content_block_delta":
8411
- if (chunk.delta?.type === "text_delta") {
8412
- const text = chunk.delta?.text;
9165
+ if (event.delta?.type === "text_delta") {
9166
+ const text = event.delta.text;
8413
9167
  if (text) {
8414
9168
  deltas.push(text);
8415
9169
  }
8416
9170
  }
8417
9171
  break;
8418
9172
  case "message_delta":
8419
- if (chunk.usage) {
8420
- const finalMetrics = parseMetricsFromUsage2(chunk.usage);
9173
+ if (event.usage) {
9174
+ const finalMetrics = parseMetricsFromUsage2(event.usage);
8421
9175
  metrics = { ...metrics, ...finalMetrics };
8422
9176
  }
8423
- if (chunk.delta) {
8424
- metadata = { ...metadata, ...chunk.delta };
9177
+ if (event.delta) {
9178
+ metadata = { ...metadata, ...event.delta };
8425
9179
  }
8426
9180
  break;
8427
9181
  }
@@ -8429,7 +9183,9 @@ function aggregateAnthropicStreamChunks(chunks) {
8429
9183
  const output = deltas.join("");
8430
9184
  const finalized = finalizeAnthropicTokens(metrics);
8431
9185
  const filteredMetrics = Object.fromEntries(
8432
- Object.entries(finalized).filter(([, v]) => v !== void 0)
9186
+ Object.entries(finalized).filter(
9187
+ (entry) => entry[1] !== void 0
9188
+ )
8433
9189
  );
8434
9190
  return {
8435
9191
  output,
@@ -8437,6 +9193,9 @@ function aggregateAnthropicStreamChunks(chunks) {
8437
9193
  metadata
8438
9194
  };
8439
9195
  }
9196
+ function isAnthropicBase64ContentBlock(input) {
9197
+ return (input.type === "image" || input.type === "document") && isObject(input.source) && input.source.type === "base64";
9198
+ }
8440
9199
  function convertBase64ToAttachment(source, contentType) {
8441
9200
  const mediaType = typeof source.media_type === "string" ? source.media_type : "image/png";
8442
9201
  const base64Data = source.data;
@@ -8460,14 +9219,14 @@ function convertBase64ToAttachment(source, contentType) {
8460
9219
  data: attachment
8461
9220
  };
8462
9221
  }
8463
- return source;
9222
+ return { ...source };
8464
9223
  }
8465
9224
  function processAttachmentsInInput(input) {
8466
9225
  if (Array.isArray(input)) {
8467
9226
  return input.map(processAttachmentsInInput);
8468
9227
  }
8469
9228
  if (isObject(input)) {
8470
- if ((input.type === "image" || input.type === "document") && isObject(input.source) && input.source.type === "base64") {
9229
+ if (isAnthropicBase64ContentBlock(input)) {
8471
9230
  return {
8472
9231
  ...input,
8473
9232
  source: convertBase64ToAttachment(input.source, input.type)
@@ -8495,306 +9254,755 @@ function filterFrom(obj, fieldsToRemove) {
8495
9254
  result[key] = value;
8496
9255
  }
8497
9256
  }
8498
- return result;
9257
+ return result;
9258
+ }
9259
+
9260
+ // src/wrappers/ai-sdk/normalize-logged-output.ts
9261
+ var REMOVE_NORMALIZED_VALUE = Symbol("braintrust.ai-sdk.remove-normalized");
9262
+ function normalizeAISDKLoggedOutput(value) {
9263
+ const normalized = normalizeAISDKLoggedValue(value);
9264
+ return normalized === REMOVE_NORMALIZED_VALUE ? {} : normalized;
9265
+ }
9266
+ function normalizeAISDKLoggedValue(value, context = {}) {
9267
+ if (Array.isArray(value)) {
9268
+ return value.map((entry) => normalizeAISDKLoggedValue(entry, context)).filter((entry) => entry !== REMOVE_NORMALIZED_VALUE);
9269
+ }
9270
+ if (!value || typeof value !== "object") {
9271
+ return value;
9272
+ }
9273
+ const nextInProviderMetadata = context.inProviderMetadata || context.parentKey === "providerMetadata" || context.parentKey === "experimental_providerMetadata";
9274
+ const normalizedEntries = [];
9275
+ for (const [key, entry] of Object.entries(value)) {
9276
+ if (key === "cachedPromptTokens" && entry === 0) {
9277
+ continue;
9278
+ }
9279
+ if (context.parentKey === "request" && key === "body" && entry === "<omitted>") {
9280
+ continue;
9281
+ }
9282
+ const normalizedEntry = normalizeAISDKLoggedValue(entry, {
9283
+ inProviderMetadata: nextInProviderMetadata,
9284
+ parentKey: key
9285
+ });
9286
+ if (normalizedEntry === REMOVE_NORMALIZED_VALUE) {
9287
+ continue;
9288
+ }
9289
+ normalizedEntries.push([key, normalizedEntry]);
9290
+ }
9291
+ if (normalizedEntries.length === 0) {
9292
+ if (context.parentKey === "request" || nextInProviderMetadata) {
9293
+ return REMOVE_NORMALIZED_VALUE;
9294
+ }
9295
+ return {};
9296
+ }
9297
+ return Object.fromEntries(normalizedEntries);
9298
+ }
9299
+
9300
+ // src/zod/utils.ts
9301
+ import { zodToJsonSchema as zodToJsonSchemaV3 } from "zod-to-json-schema";
9302
+ import * as z42 from "zod/v4";
9303
+ function isZodV4(zodObject) {
9304
+ return typeof zodObject === "object" && zodObject !== null && "_zod" in zodObject && zodObject._zod !== void 0;
9305
+ }
9306
+ function zodToJsonSchema(schema) {
9307
+ if (isZodV4(schema)) {
9308
+ return z42.toJSONSchema(schema, {
9309
+ target: "draft-7"
9310
+ });
9311
+ }
9312
+ return zodToJsonSchemaV3(schema);
9313
+ }
9314
+
9315
+ // src/wrappers/ai-sdk/tool-serialization.ts
9316
+ function isZodSchema(value) {
9317
+ return value != null && typeof value === "object" && "_def" in value && typeof value._def === "object";
9318
+ }
9319
+ function serializeZodSchema(schema) {
9320
+ try {
9321
+ return zodToJsonSchema(schema);
9322
+ } catch {
9323
+ return {
9324
+ type: "object",
9325
+ description: "Zod schema (conversion failed)"
9326
+ };
9327
+ }
9328
+ }
9329
+ function serializeTool(tool) {
9330
+ if (!tool || typeof tool !== "object") {
9331
+ return tool;
9332
+ }
9333
+ const serialized = { ...tool };
9334
+ if (isZodSchema(serialized.inputSchema)) {
9335
+ serialized.inputSchema = serializeZodSchema(serialized.inputSchema);
9336
+ }
9337
+ if (isZodSchema(serialized.parameters)) {
9338
+ serialized.parameters = serializeZodSchema(serialized.parameters);
9339
+ }
9340
+ if ("execute" in serialized) {
9341
+ delete serialized.execute;
9342
+ }
9343
+ if ("render" in serialized) {
9344
+ delete serialized.render;
9345
+ }
9346
+ return serialized;
9347
+ }
9348
+ function serializeAISDKToolsForLogging(tools) {
9349
+ if (!tools || typeof tools !== "object") {
9350
+ return tools;
9351
+ }
9352
+ if (Array.isArray(tools)) {
9353
+ return tools.map(serializeTool);
9354
+ }
9355
+ const serialized = {};
9356
+ for (const [key, tool] of Object.entries(tools)) {
9357
+ serialized[key] = serializeTool(tool);
9358
+ }
9359
+ return serialized;
9360
+ }
9361
+
9362
+ // src/instrumentation/plugins/ai-sdk-channels.ts
9363
+ var aiSDKChannels = defineChannels("ai", {
9364
+ generateText: channel({
9365
+ channelName: "generateText",
9366
+ kind: "async"
9367
+ }),
9368
+ streamText: channel({
9369
+ channelName: "streamText",
9370
+ kind: "async"
9371
+ }),
9372
+ streamTextSync: channel({
9373
+ channelName: "streamText.sync",
9374
+ kind: "sync-stream"
9375
+ }),
9376
+ generateObject: channel({
9377
+ channelName: "generateObject",
9378
+ kind: "async"
9379
+ }),
9380
+ streamObject: channel({
9381
+ channelName: "streamObject",
9382
+ kind: "async"
9383
+ }),
9384
+ streamObjectSync: channel({
9385
+ channelName: "streamObject.sync",
9386
+ kind: "sync-stream"
9387
+ }),
9388
+ agentGenerate: channel({
9389
+ channelName: "Agent.generate",
9390
+ kind: "async"
9391
+ }),
9392
+ agentStream: channel({
9393
+ channelName: "Agent.stream",
9394
+ kind: "async"
9395
+ }),
9396
+ toolLoopAgentGenerate: channel({
9397
+ channelName: "ToolLoopAgent.generate",
9398
+ kind: "async"
9399
+ }),
9400
+ toolLoopAgentStream: channel({
9401
+ channelName: "ToolLoopAgent.stream",
9402
+ kind: "async"
9403
+ })
9404
+ });
9405
+
9406
+ // src/instrumentation/plugins/ai-sdk-plugin.ts
9407
+ var DEFAULT_DENY_OUTPUT_PATHS = [
9408
+ // v3
9409
+ "roundtrips[].request.body",
9410
+ "roundtrips[].response.headers",
9411
+ "rawResponse.headers",
9412
+ "responseMessages",
9413
+ // v5
9414
+ "request.body",
9415
+ "response.body",
9416
+ "response.headers",
9417
+ "steps[].request.body",
9418
+ "steps[].response.body",
9419
+ "steps[].response.headers"
9420
+ ];
9421
+ var AUTO_PATCHED_MODEL = Symbol.for("braintrust.ai-sdk.auto-patched-model");
9422
+ var AUTO_PATCHED_TOOL = Symbol.for("braintrust.ai-sdk.auto-patched-tool");
9423
+ var AISDKPlugin = class extends BasePlugin {
9424
+ config;
9425
+ constructor(config = {}) {
9426
+ super();
9427
+ this.config = config;
9428
+ }
9429
+ onEnable() {
9430
+ this.subscribeToAISDK();
9431
+ }
9432
+ onDisable() {
9433
+ this.unsubscribers = unsubscribeAll(this.unsubscribers);
9434
+ }
9435
+ subscribeToAISDK() {
9436
+ const denyOutputPaths = this.config.denyOutputPaths || DEFAULT_DENY_OUTPUT_PATHS;
9437
+ this.unsubscribers.push(
9438
+ traceStreamingChannel(aiSDKChannels.generateText, {
9439
+ name: "generateText",
9440
+ type: "llm" /* LLM */,
9441
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9442
+ extractOutput: (result, endEvent) => {
9443
+ finalizeAISDKChildTracing(endEvent);
9444
+ return processAISDKOutput(result, denyOutputPaths);
9445
+ },
9446
+ extractMetrics: (result, _startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent),
9447
+ aggregateChunks: aggregateAISDKChunks
9448
+ })
9449
+ );
9450
+ this.unsubscribers.push(
9451
+ traceStreamingChannel(aiSDKChannels.streamText, {
9452
+ name: "streamText",
9453
+ type: "llm" /* LLM */,
9454
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9455
+ extractOutput: (result) => processAISDKOutput(result, denyOutputPaths),
9456
+ extractMetrics: (result, startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent, startTime),
9457
+ aggregateChunks: aggregateAISDKChunks,
9458
+ patchResult: ({ endEvent, result, span, startTime }) => patchAISDKStreamingResult({
9459
+ denyOutputPaths,
9460
+ endEvent,
9461
+ result,
9462
+ span,
9463
+ startTime
9464
+ })
9465
+ })
9466
+ );
9467
+ this.unsubscribers.push(
9468
+ traceSyncStreamChannel(aiSDKChannels.streamTextSync, {
9469
+ name: "streamText",
9470
+ type: "llm" /* LLM */,
9471
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9472
+ patchResult: ({ endEvent, result, span, startTime }) => patchAISDKStreamingResult({
9473
+ denyOutputPaths,
9474
+ endEvent,
9475
+ result,
9476
+ span,
9477
+ startTime
9478
+ })
9479
+ })
9480
+ );
9481
+ this.unsubscribers.push(
9482
+ traceStreamingChannel(aiSDKChannels.generateObject, {
9483
+ name: "generateObject",
9484
+ type: "llm" /* LLM */,
9485
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9486
+ extractOutput: (result, endEvent) => {
9487
+ finalizeAISDKChildTracing(endEvent);
9488
+ return processAISDKOutput(result, denyOutputPaths);
9489
+ },
9490
+ extractMetrics: (result, _startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent),
9491
+ aggregateChunks: aggregateAISDKChunks
9492
+ })
9493
+ );
9494
+ this.unsubscribers.push(
9495
+ traceStreamingChannel(aiSDKChannels.streamObject, {
9496
+ name: "streamObject",
9497
+ type: "llm" /* LLM */,
9498
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9499
+ extractOutput: (result) => processAISDKOutput(result, denyOutputPaths),
9500
+ extractMetrics: (result, startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent, startTime),
9501
+ aggregateChunks: aggregateAISDKChunks,
9502
+ patchResult: ({ endEvent, result, span, startTime }) => patchAISDKStreamingResult({
9503
+ denyOutputPaths,
9504
+ endEvent,
9505
+ result,
9506
+ span,
9507
+ startTime
9508
+ })
9509
+ })
9510
+ );
9511
+ this.unsubscribers.push(
9512
+ traceSyncStreamChannel(aiSDKChannels.streamObjectSync, {
9513
+ name: "streamObject",
9514
+ type: "llm" /* LLM */,
9515
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9516
+ patchResult: ({ endEvent, result, span, startTime }) => patchAISDKStreamingResult({
9517
+ denyOutputPaths,
9518
+ endEvent,
9519
+ result,
9520
+ span,
9521
+ startTime
9522
+ })
9523
+ })
9524
+ );
9525
+ this.unsubscribers.push(
9526
+ traceStreamingChannel(aiSDKChannels.agentGenerate, {
9527
+ name: "Agent.generate",
9528
+ type: "llm" /* LLM */,
9529
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9530
+ extractOutput: (result, endEvent) => {
9531
+ finalizeAISDKChildTracing(endEvent);
9532
+ return processAISDKOutput(result, denyOutputPaths);
9533
+ },
9534
+ extractMetrics: (result, _startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent),
9535
+ aggregateChunks: aggregateAISDKChunks
9536
+ })
9537
+ );
9538
+ this.unsubscribers.push(
9539
+ traceStreamingChannel(aiSDKChannels.agentStream, {
9540
+ name: "Agent.stream",
9541
+ type: "llm" /* LLM */,
9542
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9543
+ extractOutput: (result) => processAISDKOutput(result, denyOutputPaths),
9544
+ extractMetrics: (result, startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent, startTime),
9545
+ aggregateChunks: aggregateAISDKChunks,
9546
+ patchResult: ({ endEvent, result, span, startTime }) => patchAISDKStreamingResult({
9547
+ denyOutputPaths,
9548
+ endEvent,
9549
+ result,
9550
+ span,
9551
+ startTime
9552
+ })
9553
+ })
9554
+ );
9555
+ this.unsubscribers.push(
9556
+ traceStreamingChannel(aiSDKChannels.toolLoopAgentGenerate, {
9557
+ name: "ToolLoopAgent.generate",
9558
+ type: "llm" /* LLM */,
9559
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9560
+ extractOutput: (result, endEvent) => {
9561
+ finalizeAISDKChildTracing(endEvent);
9562
+ return processAISDKOutput(result, denyOutputPaths);
9563
+ },
9564
+ extractMetrics: (result, _startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent),
9565
+ aggregateChunks: aggregateAISDKChunks
9566
+ })
9567
+ );
9568
+ this.unsubscribers.push(
9569
+ traceStreamingChannel(aiSDKChannels.toolLoopAgentStream, {
9570
+ name: "ToolLoopAgent.stream",
9571
+ type: "llm" /* LLM */,
9572
+ extractInput: ([params], event, span) => prepareAISDKInput(params, event, span, denyOutputPaths),
9573
+ extractOutput: (result) => processAISDKOutput(result, denyOutputPaths),
9574
+ extractMetrics: (result, startTime, endEvent) => extractTopLevelAISDKMetrics(result, endEvent, startTime),
9575
+ aggregateChunks: aggregateAISDKChunks,
9576
+ patchResult: ({ endEvent, result, span, startTime }) => patchAISDKStreamingResult({
9577
+ denyOutputPaths,
9578
+ endEvent,
9579
+ result,
9580
+ span,
9581
+ startTime
9582
+ })
9583
+ })
9584
+ );
9585
+ }
9586
+ };
9587
+ function processAISDKInput(params) {
9588
+ if (!params) return params;
9589
+ const input = processInputAttachments(params);
9590
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
9591
+ return input;
9592
+ }
9593
+ const { tools: _tools, ...rest } = input;
9594
+ return rest;
9595
+ }
9596
+ function prepareAISDKInput(params, event, span, denyOutputPaths) {
9597
+ const input = processAISDKInput(params);
9598
+ const metadata = extractMetadataFromParams(params, event.self);
9599
+ const childTracing = prepareAISDKChildTracing(
9600
+ params,
9601
+ event.self,
9602
+ span,
9603
+ denyOutputPaths
9604
+ );
9605
+ event.__braintrust_ai_sdk_model_wrapped = childTracing.modelWrapped;
9606
+ if (childTracing.cleanup) {
9607
+ event.__braintrust_ai_sdk_cleanup = childTracing.cleanup;
9608
+ }
9609
+ return {
9610
+ input,
9611
+ metadata
9612
+ };
9613
+ }
9614
+ function extractTopLevelAISDKMetrics(result, event, startTime) {
9615
+ const metrics = hasModelChildTracing(event) ? {} : extractTokenMetrics(result);
9616
+ if (startTime) {
9617
+ metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
9618
+ }
9619
+ return metrics;
8499
9620
  }
8500
-
8501
- // src/instrumentation/plugins/ai-sdk-plugin.ts
8502
- import { tracingChannel as tracingChannel3 } from "dc-browser";
8503
- var DEFAULT_DENY_OUTPUT_PATHS = [
8504
- // v3
8505
- "roundtrips[].request.body",
8506
- "roundtrips[].response.headers",
8507
- "rawResponse.headers",
8508
- "responseMessages",
8509
- // v5
8510
- "request.body",
8511
- "response.body",
8512
- "response.headers",
8513
- "steps[].request.body",
8514
- "steps[].response.body",
8515
- "steps[].response.headers"
8516
- ];
8517
- var AISDKPlugin = class extends BasePlugin {
8518
- unsubscribers = [];
8519
- config;
8520
- constructor(config = {}) {
8521
- super();
8522
- this.config = config;
9621
+ function hasModelChildTracing(event) {
9622
+ return event?.__braintrust_ai_sdk_model_wrapped === true;
9623
+ }
9624
+ function extractMetadataFromParams(params, self) {
9625
+ const metadata = {
9626
+ braintrust: {
9627
+ integration_name: "ai-sdk",
9628
+ sdk_language: "typescript"
9629
+ }
9630
+ };
9631
+ const agentModel = self && typeof self === "object" && "model" in self && self.model ? self.model : self && typeof self === "object" && "settings" in self && self.settings?.model ? self.settings?.model : void 0;
9632
+ const { model, provider } = serializeModelWithProvider(
9633
+ params.model ?? agentModel
9634
+ );
9635
+ if (model) {
9636
+ metadata.model = model;
8523
9637
  }
8524
- onEnable() {
8525
- this.subscribeToAISDK();
9638
+ if (provider) {
9639
+ metadata.provider = provider;
8526
9640
  }
8527
- onDisable() {
8528
- for (const unsubscribe of this.unsubscribers) {
8529
- unsubscribe();
8530
- }
8531
- this.unsubscribers = [];
9641
+ const tools = serializeAISDKToolsForLogging(params.tools);
9642
+ if (tools) {
9643
+ metadata.tools = tools;
8532
9644
  }
8533
- subscribeToAISDK() {
8534
- const denyOutputPaths = this.config.denyOutputPaths || DEFAULT_DENY_OUTPUT_PATHS;
8535
- this.subscribeToStreamingChannel("orchestrion:ai-sdk:generateText", {
8536
- name: "generateText",
8537
- type: "llm" /* LLM */,
8538
- extractInput: (args) => {
8539
- const params = args[0] || {};
8540
- return {
8541
- input: processAISDKInput(params),
8542
- metadata: extractMetadataFromParams(params)
8543
- };
8544
- },
8545
- extractOutput: (result) => {
8546
- return processAISDKOutput(result, denyOutputPaths);
8547
- },
8548
- extractMetrics: (result, startTime) => {
8549
- const metrics = extractTokenMetrics(result);
8550
- if (startTime) {
8551
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8552
- }
8553
- return metrics;
8554
- },
8555
- aggregateChunks: aggregateAISDKChunks
8556
- });
8557
- this.subscribeToStreamingChannel("orchestrion:ai-sdk:streamText", {
8558
- name: "streamText",
8559
- type: "llm" /* LLM */,
8560
- extractInput: (args) => {
8561
- const params = args[0] || {};
8562
- return {
8563
- input: processAISDKInput(params),
8564
- metadata: extractMetadataFromParams(params)
8565
- };
8566
- },
8567
- extractOutput: (result) => {
8568
- return processAISDKOutput(result, denyOutputPaths);
8569
- },
8570
- extractMetrics: (result, startTime) => {
8571
- const metrics = extractTokenMetrics(result);
8572
- if (startTime) {
8573
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8574
- }
8575
- return metrics;
8576
- },
8577
- aggregateChunks: aggregateAISDKChunks
8578
- });
8579
- this.subscribeToStreamingChannel("orchestrion:ai-sdk:generateObject", {
8580
- name: "generateObject",
8581
- type: "llm" /* LLM */,
8582
- extractInput: (args) => {
8583
- const params = args[0] || {};
8584
- return {
8585
- input: processAISDKInput(params),
8586
- metadata: extractMetadataFromParams(params)
8587
- };
8588
- },
8589
- extractOutput: (result) => {
8590
- return processAISDKOutput(result, denyOutputPaths);
8591
- },
8592
- extractMetrics: (result, startTime) => {
8593
- const metrics = extractTokenMetrics(result);
8594
- if (startTime) {
8595
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8596
- }
8597
- return metrics;
8598
- },
8599
- aggregateChunks: aggregateAISDKChunks
8600
- });
8601
- this.subscribeToStreamingChannel("orchestrion:ai-sdk:streamObject", {
8602
- name: "streamObject",
8603
- type: "llm" /* LLM */,
8604
- extractInput: (args) => {
8605
- const params = args[0] || {};
8606
- return {
8607
- input: processAISDKInput(params),
8608
- metadata: extractMetadataFromParams(params)
8609
- };
8610
- },
8611
- extractOutput: (result) => {
8612
- return processAISDKOutput(result, denyOutputPaths);
8613
- },
8614
- extractMetrics: (result, startTime) => {
8615
- const metrics = extractTokenMetrics(result);
8616
- if (startTime) {
8617
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8618
- }
8619
- return metrics;
8620
- },
8621
- aggregateChunks: aggregateAISDKChunks
8622
- });
8623
- this.subscribeToStreamingChannel("orchestrion:ai-sdk:Agent.generate", {
8624
- name: "Agent.generate",
8625
- type: "llm" /* LLM */,
8626
- extractInput: (args) => {
8627
- const params = args[0] || {};
8628
- return {
8629
- input: processAISDKInput(params),
8630
- metadata: extractMetadataFromParams(params)
8631
- };
8632
- },
8633
- extractOutput: (result) => {
8634
- return processAISDKOutput(result, denyOutputPaths);
8635
- },
8636
- extractMetrics: (result, startTime) => {
8637
- const metrics = extractTokenMetrics(result);
8638
- if (startTime) {
8639
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
8640
- }
8641
- return metrics;
8642
- },
8643
- aggregateChunks: aggregateAISDKChunks
8644
- });
8645
- this.subscribeToStreamingChannel("orchestrion:ai-sdk:Agent.stream", {
8646
- name: "Agent.stream",
8647
- type: "llm" /* LLM */,
8648
- extractInput: (args) => {
8649
- const params = args[0] || {};
8650
- return {
8651
- input: processAISDKInput(params),
8652
- metadata: extractMetadataFromParams(params)
8653
- };
8654
- },
8655
- extractOutput: (result) => {
8656
- return processAISDKOutput(result, denyOutputPaths);
8657
- },
8658
- extractMetrics: (result, startTime) => {
8659
- const metrics = extractTokenMetrics(result);
8660
- if (startTime) {
8661
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
9645
+ return metadata;
9646
+ }
9647
+ function prepareAISDKChildTracing(params, self, parentSpan, denyOutputPaths) {
9648
+ const cleanup = [];
9649
+ const patchedModels = /* @__PURE__ */ new WeakSet();
9650
+ const patchedTools = /* @__PURE__ */ new WeakSet();
9651
+ let modelWrapped = false;
9652
+ const patchModel = (model) => {
9653
+ const resolvedModel = resolveAISDKModel(model);
9654
+ if (!resolvedModel || typeof resolvedModel !== "object" || typeof resolvedModel.doGenerate !== "function" || patchedModels.has(resolvedModel) || resolvedModel[AUTO_PATCHED_MODEL]) {
9655
+ return;
9656
+ }
9657
+ patchedModels.add(resolvedModel);
9658
+ resolvedModel[AUTO_PATCHED_MODEL] = true;
9659
+ modelWrapped = true;
9660
+ const originalDoGenerate = resolvedModel.doGenerate;
9661
+ const originalDoStream = resolvedModel.doStream;
9662
+ const baseMetadata = buildAISDKChildMetadata(resolvedModel);
9663
+ resolvedModel.doGenerate = async function doGeneratePatched(options) {
9664
+ return parentSpan.traced(
9665
+ async (span) => {
9666
+ const result = await Reflect.apply(
9667
+ originalDoGenerate,
9668
+ resolvedModel,
9669
+ [options]
9670
+ );
9671
+ span.log({
9672
+ output: processAISDKOutput(result, denyOutputPaths),
9673
+ metrics: extractTokenMetrics(result),
9674
+ ...buildResolvedMetadataPayload(result)
9675
+ });
9676
+ return result;
9677
+ },
9678
+ {
9679
+ name: "doGenerate",
9680
+ spanAttributes: {
9681
+ type: "llm" /* LLM */
9682
+ },
9683
+ event: {
9684
+ input: processAISDKInput(options),
9685
+ metadata: baseMetadata
9686
+ }
8662
9687
  }
8663
- return metrics;
8664
- },
8665
- aggregateChunks: aggregateAISDKChunks
8666
- });
8667
- }
8668
- /**
8669
- * Subscribe to a channel for async methods that may return streams.
8670
- * Handles both streaming and non-streaming responses.
8671
- */
8672
- subscribeToStreamingChannel(channelName, config) {
8673
- const channel = tracingChannel3(channelName);
8674
- const spans = /* @__PURE__ */ new WeakMap();
8675
- const handlers = {
8676
- start: (event) => {
8677
- const span = startSpan({
8678
- name: config.name,
9688
+ );
9689
+ };
9690
+ if (originalDoStream) {
9691
+ resolvedModel.doStream = async function doStreamPatched(options) {
9692
+ const span = parentSpan.startSpan({
9693
+ name: "doStream",
8679
9694
  spanAttributes: {
8680
- type: config.type
9695
+ type: "llm" /* LLM */
9696
+ },
9697
+ event: {
9698
+ input: processAISDKInput(options),
9699
+ metadata: baseMetadata
8681
9700
  }
8682
9701
  });
8683
- const startTime = getCurrentUnixTimestamp();
8684
- spans.set(event, { span, startTime });
8685
- try {
8686
- const { input, metadata } = config.extractInput(event.arguments);
8687
- span.log({
8688
- input,
8689
- metadata
8690
- });
8691
- } catch (error) {
8692
- console.error(`Error extracting input for ${channelName}:`, error);
8693
- }
8694
- },
8695
- asyncEnd: (event) => {
8696
- const spanData = spans.get(event);
8697
- if (!spanData) {
8698
- return;
8699
- }
8700
- const { span, startTime } = spanData;
8701
- if (isAsyncIterable(event.result)) {
8702
- patchStreamIfNeeded(event.result, {
8703
- onComplete: (chunks) => {
8704
- try {
8705
- let output;
8706
- let metrics;
8707
- if (config.aggregateChunks) {
8708
- const aggregated = config.aggregateChunks(chunks);
8709
- output = aggregated.output;
8710
- metrics = aggregated.metrics;
8711
- } else {
8712
- output = config.extractOutput(chunks);
8713
- metrics = config.extractMetrics(chunks, startTime);
9702
+ const result = await withCurrent(
9703
+ span,
9704
+ () => Reflect.apply(originalDoStream, resolvedModel, [options])
9705
+ );
9706
+ const output = {};
9707
+ let text = "";
9708
+ let reasoning = "";
9709
+ const toolCalls = [];
9710
+ let object = void 0;
9711
+ const transformStream = new TransformStream({
9712
+ transform(chunk, controller) {
9713
+ switch (chunk.type) {
9714
+ case "text-delta":
9715
+ text += extractTextDelta(chunk);
9716
+ break;
9717
+ case "reasoning-delta":
9718
+ if (chunk.delta) {
9719
+ reasoning += chunk.delta;
9720
+ } else if (chunk.text) {
9721
+ reasoning += chunk.text;
8714
9722
  }
8715
- if (!metrics.time_to_first_token && chunks.length > 0) {
8716
- metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
9723
+ break;
9724
+ case "tool-call":
9725
+ toolCalls.push(chunk);
9726
+ break;
9727
+ case "object":
9728
+ object = chunk.object;
9729
+ break;
9730
+ case "raw":
9731
+ if (chunk.rawValue) {
9732
+ const rawVal = chunk.rawValue;
9733
+ if (rawVal.delta?.content) {
9734
+ text += rawVal.delta.content;
9735
+ } else if (rawVal.choices?.[0]?.delta?.content) {
9736
+ text += rawVal.choices[0].delta.content;
9737
+ } else if (typeof rawVal.text === "string") {
9738
+ text += rawVal.text;
9739
+ } else if (typeof rawVal.content === "string") {
9740
+ text += rawVal.content;
9741
+ }
9742
+ }
9743
+ break;
9744
+ case "finish":
9745
+ output.text = text;
9746
+ output.reasoning = reasoning;
9747
+ output.toolCalls = toolCalls;
9748
+ output.finishReason = chunk.finishReason;
9749
+ output.usage = chunk.usage;
9750
+ if (object !== void 0) {
9751
+ output.object = object;
8717
9752
  }
8718
9753
  span.log({
8719
- output,
8720
- metrics
9754
+ output: processAISDKOutput(
9755
+ output,
9756
+ denyOutputPaths
9757
+ ),
9758
+ metrics: extractTokenMetrics(output),
9759
+ ...buildResolvedMetadataPayload(output)
8721
9760
  });
8722
- } catch (error) {
8723
- console.error(
8724
- `Error extracting output for ${channelName}:`,
8725
- error
8726
- );
8727
- } finally {
8728
9761
  span.end();
8729
- }
8730
- },
8731
- onError: (error) => {
8732
- span.log({
8733
- error: error.message
8734
- });
8735
- span.end();
9762
+ break;
9763
+ }
9764
+ controller.enqueue(chunk);
9765
+ }
9766
+ });
9767
+ return {
9768
+ ...result,
9769
+ stream: result.stream.pipeThrough(transformStream)
9770
+ };
9771
+ };
9772
+ }
9773
+ cleanup.push(() => {
9774
+ resolvedModel.doGenerate = originalDoGenerate;
9775
+ if (originalDoStream) {
9776
+ resolvedModel.doStream = originalDoStream;
9777
+ }
9778
+ delete resolvedModel[AUTO_PATCHED_MODEL];
9779
+ });
9780
+ };
9781
+ const patchTool = (tool, name) => {
9782
+ if (tool == null || typeof tool !== "object" || !("execute" in tool) || typeof tool.execute !== "function" || patchedTools.has(tool) || tool[AUTO_PATCHED_TOOL]) {
9783
+ return;
9784
+ }
9785
+ patchedTools.add(tool);
9786
+ tool[AUTO_PATCHED_TOOL] = true;
9787
+ const originalExecute = tool.execute;
9788
+ tool.execute = function executePatched(...args) {
9789
+ const result = Reflect.apply(originalExecute, this, args);
9790
+ if (isAsyncGenerator(result)) {
9791
+ return (async function* () {
9792
+ const span = parentSpan.startSpan({
9793
+ name,
9794
+ spanAttributes: {
9795
+ type: "tool" /* TOOL */
8736
9796
  }
8737
9797
  });
8738
- } else {
9798
+ span.log({ input: args.length === 1 ? args[0] : args });
8739
9799
  try {
8740
- const output = config.extractOutput(event.result);
8741
- const metrics = config.extractMetrics(event.result, startTime);
9800
+ let lastValue;
9801
+ for await (const value of result) {
9802
+ lastValue = value;
9803
+ yield value;
9804
+ }
9805
+ span.log({ output: lastValue });
9806
+ } catch (error) {
8742
9807
  span.log({
8743
- output,
8744
- metrics
9808
+ error: error instanceof Error ? error.message : String(error)
8745
9809
  });
8746
- } catch (error) {
8747
- console.error(`Error extracting output for ${channelName}:`, error);
9810
+ throw error;
8748
9811
  } finally {
8749
9812
  span.end();
8750
- spans.delete(event);
8751
9813
  }
9814
+ })();
9815
+ }
9816
+ return parentSpan.traced(
9817
+ async (span) => {
9818
+ span.log({ input: args.length === 1 ? args[0] : args });
9819
+ const awaitedResult = await result;
9820
+ span.log({ output: awaitedResult });
9821
+ return awaitedResult;
9822
+ },
9823
+ {
9824
+ name,
9825
+ spanAttributes: {
9826
+ type: "tool" /* TOOL */
9827
+ }
9828
+ }
9829
+ );
9830
+ };
9831
+ cleanup.push(() => {
9832
+ tool.execute = originalExecute;
9833
+ delete tool[AUTO_PATCHED_TOOL];
9834
+ });
9835
+ };
9836
+ const patchTools = (tools) => {
9837
+ if (!tools) {
9838
+ return;
9839
+ }
9840
+ const inferName = (tool, fallback) => tool && (tool.name || tool.toolName || tool.id) || fallback;
9841
+ if (Array.isArray(tools)) {
9842
+ tools.forEach(
9843
+ (tool, index) => patchTool(tool, inferName(tool, `tool[${index}]`))
9844
+ );
9845
+ return;
9846
+ }
9847
+ for (const [key, tool] of Object.entries(tools)) {
9848
+ patchTool(tool, key);
9849
+ }
9850
+ };
9851
+ if (params && typeof params === "object") {
9852
+ patchModel(params.model);
9853
+ patchTools(params.tools);
9854
+ }
9855
+ if (self && typeof self === "object") {
9856
+ const selfRecord = self;
9857
+ if (selfRecord.model !== void 0) {
9858
+ patchModel(selfRecord.model);
9859
+ }
9860
+ if (selfRecord.settings && typeof selfRecord.settings === "object") {
9861
+ if (selfRecord.settings.model !== void 0) {
9862
+ patchModel(selfRecord.settings.model);
9863
+ }
9864
+ if (selfRecord.settings.tools !== void 0) {
9865
+ patchTools(selfRecord.settings.tools);
9866
+ }
9867
+ }
9868
+ }
9869
+ return {
9870
+ cleanup: cleanup.length > 0 ? () => {
9871
+ while (cleanup.length > 0) {
9872
+ cleanup.pop()?.();
9873
+ }
9874
+ } : void 0,
9875
+ modelWrapped
9876
+ };
9877
+ }
9878
+ function finalizeAISDKChildTracing(event) {
9879
+ const cleanup = event?.__braintrust_ai_sdk_cleanup;
9880
+ if (event && typeof cleanup === "function") {
9881
+ cleanup();
9882
+ delete event.__braintrust_ai_sdk_cleanup;
9883
+ }
9884
+ }
9885
+ function patchAISDKStreamingResult(args) {
9886
+ const { denyOutputPaths, endEvent, result, span, startTime } = args;
9887
+ if (!result || typeof result !== "object") {
9888
+ return false;
9889
+ }
9890
+ const resultRecord = result;
9891
+ if (!isReadableStreamLike(resultRecord.baseStream)) {
9892
+ return false;
9893
+ }
9894
+ let firstChunkTime;
9895
+ const wrappedBaseStream = resultRecord.baseStream.pipeThrough(
9896
+ new TransformStream({
9897
+ transform(chunk, controller) {
9898
+ if (firstChunkTime === void 0) {
9899
+ firstChunkTime = getCurrentUnixTimestamp();
8752
9900
  }
9901
+ controller.enqueue(chunk);
8753
9902
  },
8754
- error: (event) => {
8755
- const spanData = spans.get(event);
8756
- if (!spanData) {
8757
- return;
9903
+ async flush() {
9904
+ const metrics = extractTopLevelAISDKMetrics(result, endEvent);
9905
+ if (metrics.time_to_first_token === void 0 && firstChunkTime !== void 0) {
9906
+ metrics.time_to_first_token = firstChunkTime - startTime;
8758
9907
  }
8759
- const { span } = spanData;
9908
+ const output = await processAISDKStreamingOutput(
9909
+ result,
9910
+ denyOutputPaths
9911
+ );
9912
+ const metadata = buildResolvedMetadataPayload(result).metadata;
8760
9913
  span.log({
8761
- error: event.error.message
9914
+ output,
9915
+ ...metadata ? { metadata } : {},
9916
+ metrics
8762
9917
  });
9918
+ finalizeAISDKChildTracing(endEvent);
8763
9919
  span.end();
8764
- spans.delete(event);
8765
9920
  }
8766
- };
8767
- channel.subscribe(handlers);
8768
- this.unsubscribers.push(() => {
8769
- channel.unsubscribe(handlers);
8770
- });
9921
+ })
9922
+ );
9923
+ Object.defineProperty(resultRecord, "baseStream", {
9924
+ configurable: true,
9925
+ enumerable: true,
9926
+ value: wrappedBaseStream,
9927
+ writable: true
9928
+ });
9929
+ return true;
9930
+ }
9931
+ function isReadableStreamLike(value) {
9932
+ return value != null && typeof value === "object" && typeof value.pipeThrough === "function";
9933
+ }
9934
+ async function processAISDKStreamingOutput(result, denyOutputPaths) {
9935
+ const output = processAISDKOutput(result, denyOutputPaths);
9936
+ if (!output || typeof output !== "object") {
9937
+ return output;
9938
+ }
9939
+ const outputRecord = output;
9940
+ try {
9941
+ if ("text" in result && typeof result.text === "string") {
9942
+ outputRecord.text = result.text;
9943
+ }
9944
+ } catch {
9945
+ }
9946
+ try {
9947
+ if ("object" in result) {
9948
+ const resolvedObject = await Promise.resolve(result.object);
9949
+ if (resolvedObject !== void 0) {
9950
+ outputRecord.object = resolvedObject;
9951
+ }
9952
+ }
9953
+ } catch {
8771
9954
  }
8772
- };
8773
- function processAISDKInput(params) {
8774
- if (!params) return params;
8775
- return processInputAttachments(params);
9955
+ return outputRecord;
8776
9956
  }
8777
- function extractMetadataFromParams(params) {
8778
- const metadata = {
9957
+ function buildAISDKChildMetadata(model) {
9958
+ const { model: modelId, provider } = serializeModelWithProvider(model);
9959
+ return {
9960
+ ...modelId ? { model: modelId } : {},
9961
+ ...provider ? { provider } : {},
8779
9962
  braintrust: {
8780
9963
  integration_name: "ai-sdk",
8781
9964
  sdk_language: "typescript"
8782
9965
  }
8783
9966
  };
8784
- const { model, provider } = serializeModelWithProvider(params.model);
8785
- if (model) {
8786
- metadata.model = model;
9967
+ }
9968
+ function buildResolvedMetadataPayload(result) {
9969
+ const gatewayInfo = extractGatewayRoutingInfo(result);
9970
+ const metadata = {};
9971
+ if (gatewayInfo?.provider) {
9972
+ metadata.provider = gatewayInfo.provider;
8787
9973
  }
8788
- if (provider) {
8789
- metadata.provider = provider;
9974
+ if (gatewayInfo?.model) {
9975
+ metadata.model = gatewayInfo.model;
8790
9976
  }
8791
- return metadata;
9977
+ if (result.finishReason !== void 0) {
9978
+ metadata.finish_reason = result.finishReason;
9979
+ }
9980
+ return Object.keys(metadata).length > 0 ? { metadata } : {};
9981
+ }
9982
+ function resolveAISDKModel(model) {
9983
+ if (typeof model !== "string") {
9984
+ return model;
9985
+ }
9986
+ const provider = globalThis.AI_SDK_DEFAULT_PROVIDER ?? null;
9987
+ if (provider && typeof provider.languageModel === "function") {
9988
+ return provider.languageModel(model);
9989
+ }
9990
+ return model;
9991
+ }
9992
+ function extractTextDelta(chunk) {
9993
+ if (typeof chunk.textDelta === "string") return chunk.textDelta;
9994
+ if (typeof chunk.delta === "string") return chunk.delta;
9995
+ if (typeof chunk.text === "string") return chunk.text;
9996
+ if (typeof chunk.content === "string") return chunk.content;
9997
+ return "";
9998
+ }
9999
+ function isAsyncGenerator(value) {
10000
+ return value != null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function" && typeof value.next === "function" && typeof value.return === "function" && typeof value.throw === "function";
8792
10001
  }
8793
10002
  function processAISDKOutput(output, denyOutputPaths) {
8794
10003
  if (!output) return output;
8795
- const getterValues = extractGetterValues(output);
8796
- const merged = { ...output, ...getterValues };
8797
- return omit(merged, denyOutputPaths);
10004
+ const merged = extractSerializableOutputFields(output);
10005
+ return normalizeAISDKLoggedOutput(omit(merged, denyOutputPaths));
8798
10006
  }
8799
10007
  function extractTokenMetrics(result) {
8800
10008
  const metrics = {};
@@ -8844,12 +10052,14 @@ function extractTokenMetrics(result) {
8844
10052
  }
8845
10053
  return metrics;
8846
10054
  }
8847
- function aggregateAISDKChunks(chunks) {
10055
+ function aggregateAISDKChunks(chunks, _result, endEvent) {
8848
10056
  const lastChunk = chunks[chunks.length - 1];
8849
10057
  const output = {};
8850
10058
  let metrics = {};
10059
+ let metadata;
8851
10060
  if (lastChunk) {
8852
- metrics = extractTokenMetrics(lastChunk);
10061
+ metrics = hasModelChildTracing(endEvent) ? {} : extractTokenMetrics(lastChunk);
10062
+ metadata = buildResolvedMetadataPayload(lastChunk).metadata;
8853
10063
  if (lastChunk.text !== void 0) {
8854
10064
  output.text = lastChunk.text;
8855
10065
  }
@@ -8863,7 +10073,8 @@ function aggregateAISDKChunks(chunks) {
8863
10073
  output.toolCalls = lastChunk.toolCalls;
8864
10074
  }
8865
10075
  }
8866
- return { output, metrics };
10076
+ finalizeAISDKChildTracing(endEvent);
10077
+ return { output, metrics, metadata };
8867
10078
  }
8868
10079
  function extractGetterValues(obj) {
8869
10080
  const getterValues = {};
@@ -8883,7 +10094,7 @@ function extractGetterValues(obj) {
8883
10094
  ];
8884
10095
  for (const name of getterNames) {
8885
10096
  try {
8886
- if (obj && name in obj && typeof obj[name] !== "function") {
10097
+ if (obj && name in obj && isSerializableOutputValue(obj[name])) {
8887
10098
  getterValues[name] = obj[name];
8888
10099
  }
8889
10100
  } catch {
@@ -8891,6 +10102,47 @@ function extractGetterValues(obj) {
8891
10102
  }
8892
10103
  return getterValues;
8893
10104
  }
10105
+ function extractSerializableOutputFields(output) {
10106
+ const serialized = {};
10107
+ const directFieldNames = [
10108
+ "steps",
10109
+ "request",
10110
+ "responseMessages",
10111
+ "warnings",
10112
+ "rawResponse",
10113
+ "response",
10114
+ "providerMetadata",
10115
+ "experimental_providerMetadata"
10116
+ ];
10117
+ for (const name of directFieldNames) {
10118
+ try {
10119
+ const value = output?.[name];
10120
+ if (isSerializableOutputValue(value)) {
10121
+ serialized[name] = value;
10122
+ }
10123
+ } catch {
10124
+ }
10125
+ }
10126
+ return {
10127
+ ...serialized,
10128
+ ...extractGetterValues(output)
10129
+ };
10130
+ }
10131
+ function isSerializableOutputValue(value) {
10132
+ if (typeof value === "function") {
10133
+ return false;
10134
+ }
10135
+ if (value && typeof value === "object" && typeof value.then === "function") {
10136
+ return false;
10137
+ }
10138
+ if (value && typeof value === "object" && typeof value.getReader === "function") {
10139
+ return false;
10140
+ }
10141
+ if (value && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function") {
10142
+ return false;
10143
+ }
10144
+ return true;
10145
+ }
8894
10146
  function serializeModelWithProvider(model) {
8895
10147
  const modelId = typeof model === "string" ? model : model?.modelId;
8896
10148
  const explicitProvider = typeof model === "object" ? model?.provider : void 0;
@@ -8916,6 +10168,25 @@ function parseGatewayModelString(modelString) {
8916
10168
  }
8917
10169
  return { model: modelString };
8918
10170
  }
10171
+ function extractGatewayRoutingInfo(result) {
10172
+ if (result?.steps && Array.isArray(result.steps) && result.steps.length > 0) {
10173
+ const routing2 = result.steps[0]?.providerMetadata?.gateway?.routing;
10174
+ if (routing2) {
10175
+ return {
10176
+ provider: routing2.resolvedProvider || routing2.finalProvider,
10177
+ model: routing2.resolvedProviderApiModelId
10178
+ };
10179
+ }
10180
+ }
10181
+ const routing = result?.providerMetadata?.gateway?.routing;
10182
+ if (routing) {
10183
+ return {
10184
+ provider: routing.resolvedProvider || routing.finalProvider,
10185
+ model: routing.resolvedProviderApiModelId
10186
+ };
10187
+ }
10188
+ return null;
10189
+ }
8919
10190
  function extractCostFromResult(result) {
8920
10191
  if (result?.steps && Array.isArray(result.steps) && result.steps.length > 0) {
8921
10192
  let totalCost = 0;
@@ -9009,7 +10280,10 @@ function omitAtPath(obj, keys) {
9009
10280
  if (Array.isArray(obj)) {
9010
10281
  obj.forEach((item) => {
9011
10282
  if (remainingKeys.length > 0) {
9012
- omitAtPath(item, remainingKeys);
10283
+ omitAtPath(
10284
+ item,
10285
+ remainingKeys
10286
+ );
9013
10287
  }
9014
10288
  });
9015
10289
  }
@@ -9019,7 +10293,10 @@ function omitAtPath(obj, keys) {
9019
10293
  }
9020
10294
  } else {
9021
10295
  if (obj && typeof obj === "object" && firstKey in obj) {
9022
- omitAtPath(obj[firstKey], remainingKeys);
10296
+ omitAtPath(
10297
+ obj[firstKey],
10298
+ remainingKeys
10299
+ );
9023
10300
  }
9024
10301
  }
9025
10302
  }
@@ -9032,8 +10309,18 @@ function omit(obj, paths) {
9032
10309
  return result;
9033
10310
  }
9034
10311
 
10312
+ // src/instrumentation/plugins/claude-agent-sdk-channels.ts
10313
+ var claudeAgentSDKChannels = defineChannels(
10314
+ "@anthropic-ai/claude-agent-sdk",
10315
+ {
10316
+ query: channel({
10317
+ channelName: "query",
10318
+ kind: "async"
10319
+ })
10320
+ }
10321
+ );
10322
+
9035
10323
  // src/instrumentation/plugins/claude-agent-sdk-plugin.ts
9036
- import { tracingChannel as tracingChannel4 } from "dc-browser";
9037
10324
  function filterSerializableOptions(options) {
9038
10325
  const allowedKeys = [
9039
10326
  "model",
@@ -9117,7 +10404,9 @@ async function createLLMSpanForMessages(messages, prompt, conversationHistory, o
9117
10404
  const input = buildLLMInput(prompt, conversationHistory);
9118
10405
  const outputs = messages.map(
9119
10406
  (m) => m.message?.content && m.message?.role ? { content: m.message.content, role: m.message.role } : void 0
9120
- ).filter((c) => c !== void 0);
10407
+ ).filter(
10408
+ (c) => c !== void 0
10409
+ );
9121
10410
  const span = startSpan({
9122
10411
  name: "anthropic.messages.create",
9123
10412
  spanAttributes: {
@@ -9136,7 +10425,6 @@ async function createLLMSpanForMessages(messages, prompt, conversationHistory, o
9136
10425
  return lastMessage.message?.content && lastMessage.message?.role ? { content: lastMessage.message.content, role: lastMessage.message.role } : void 0;
9137
10426
  }
9138
10427
  var ClaudeAgentSDKPlugin = class extends BasePlugin {
9139
- unsubscribers = [];
9140
10428
  onEnable() {
9141
10429
  this.subscribeToQuery();
9142
10430
  }
@@ -9152,12 +10440,13 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
9152
10440
  * and individual LLM calls.
9153
10441
  */
9154
10442
  subscribeToQuery() {
9155
- const channel = tracingChannel4("orchestrion:claude-agent-sdk:query");
10443
+ const channel2 = claudeAgentSDKChannels.query.tracingChannel();
9156
10444
  const spans = /* @__PURE__ */ new WeakMap();
9157
10445
  const handlers = {
9158
10446
  start: (event) => {
9159
- const params = event.arguments[0] ?? {};
9160
- const { prompt, options = {} } = params;
10447
+ const params = event.arguments[0];
10448
+ const prompt = params?.prompt;
10449
+ const options = params?.options ?? {};
9161
10450
  const span = startSpan({
9162
10451
  name: "Claude Agent",
9163
10452
  spanAttributes: {
@@ -9169,7 +10458,7 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
9169
10458
  span.log({
9170
10459
  input: typeof prompt === "string" ? prompt : {
9171
10460
  type: "streaming",
9172
- description: "AsyncIterable<SDKMessage>"
10461
+ description: "AsyncIterable<ClaudeAgentSDKMessage>"
9173
10462
  },
9174
10463
  metadata: filterSerializableOptions(options)
9175
10464
  });
@@ -9191,12 +10480,19 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
9191
10480
  if (!spanData) {
9192
10481
  return;
9193
10482
  }
9194
- if (isAsyncIterable(event.result)) {
9195
- patchStreamIfNeeded(event.result, {
10483
+ const eventResult = event.result;
10484
+ if (eventResult === void 0) {
10485
+ spanData.span.end();
10486
+ spans.delete(event);
10487
+ return;
10488
+ }
10489
+ if (isAsyncIterable(eventResult)) {
10490
+ patchStreamIfNeeded(eventResult, {
9196
10491
  onChunk: async (message) => {
9197
10492
  const currentTime = getCurrentUnixTimestamp();
9198
10493
  const params = event.arguments[0];
9199
- const { prompt, options = {} } = params;
10494
+ const prompt = params?.prompt;
10495
+ const options = params?.options ?? {};
9200
10496
  const messageId = message.message?.id;
9201
10497
  if (messageId && messageId !== spanData.currentMessageId) {
9202
10498
  if (spanData.currentMessages.length > 0) {
@@ -9255,7 +10551,8 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
9255
10551
  onComplete: async () => {
9256
10552
  try {
9257
10553
  const params = event.arguments[0];
9258
- const { prompt, options = {} } = params;
10554
+ const prompt = params?.prompt;
10555
+ const options = params?.options ?? {};
9259
10556
  if (spanData.currentMessages.length > 0) {
9260
10557
  const finalMessage = await createLLMSpanForMessages(
9261
10558
  spanData.currentMessages,
@@ -9293,7 +10590,7 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
9293
10590
  } else {
9294
10591
  try {
9295
10592
  spanData.span.log({
9296
- output: event.result
10593
+ output: eventResult
9297
10594
  });
9298
10595
  } catch (error) {
9299
10596
  console.error(
@@ -9308,7 +10605,7 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
9308
10605
  },
9309
10606
  error: (event) => {
9310
10607
  const spanData = spans.get(event);
9311
- if (!spanData) {
10608
+ if (!spanData || !event.error) {
9312
10609
  return;
9313
10610
  }
9314
10611
  const { span } = spanData;
@@ -9319,53 +10616,39 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
9319
10616
  spans.delete(event);
9320
10617
  }
9321
10618
  };
9322
- channel.subscribe(handlers);
10619
+ channel2.subscribe(handlers);
9323
10620
  this.unsubscribers.push(() => {
9324
- channel.unsubscribe(handlers);
10621
+ channel2.unsubscribe(handlers);
9325
10622
  });
9326
10623
  }
9327
10624
  };
9328
10625
 
10626
+ // src/instrumentation/plugins/google-genai-channels.ts
10627
+ var googleGenAIChannels = defineChannels("@google/genai", {
10628
+ generateContent: channel({
10629
+ channelName: "models.generateContent",
10630
+ kind: "async"
10631
+ }),
10632
+ generateContentStream: channel({
10633
+ channelName: "models.generateContentStream",
10634
+ kind: "async"
10635
+ })
10636
+ });
10637
+
9329
10638
  // src/instrumentation/plugins/google-genai-plugin.ts
9330
- import { tracingChannel as tracingChannel5 } from "dc-browser";
9331
10639
  var GoogleGenAIPlugin = class extends BasePlugin {
9332
- unsubscribers = [];
9333
10640
  onEnable() {
9334
10641
  this.subscribeToGoogleGenAIChannels();
9335
10642
  }
9336
10643
  onDisable() {
9337
- for (const unsubscribe of this.unsubscribers) {
9338
- unsubscribe();
9339
- }
9340
- this.unsubscribers = [];
10644
+ this.unsubscribers = unsubscribeAll(this.unsubscribers);
9341
10645
  }
9342
10646
  subscribeToGoogleGenAIChannels() {
9343
- this.subscribeToChannel("orchestrion:google-genai:models.generateContent", {
9344
- name: "google-genai.generateContent",
9345
- type: "llm" /* LLM */,
9346
- extractInput: (args) => {
9347
- const params = args[0] || {};
9348
- const input = serializeInput(params);
9349
- const metadata = extractMetadata(params);
9350
- return {
9351
- input,
9352
- metadata: { ...metadata, provider: "google-genai" }
9353
- };
9354
- },
9355
- extractOutput: (result) => {
9356
- return result;
9357
- },
9358
- extractMetrics: (result, startTime) => {
9359
- return extractGenerateContentMetrics(result, startTime);
9360
- }
9361
- });
9362
- this.subscribeToGoogleStreamingChannel(
9363
- "orchestrion:google-genai:models.generateContentStream",
9364
- {
9365
- name: "google-genai.generateContentStream",
10647
+ this.unsubscribers.push(
10648
+ traceAsyncChannel(googleGenAIChannels.generateContent, {
10649
+ name: "google-genai.generateContent",
9366
10650
  type: "llm" /* LLM */,
9367
- extractInput: (args) => {
9368
- const params = args[0] || {};
10651
+ extractInput: ([params]) => {
9369
10652
  const input = serializeInput(params);
9370
10653
  const metadata = extractMetadata(params);
9371
10654
  return {
@@ -9373,150 +10656,37 @@ var GoogleGenAIPlugin = class extends BasePlugin {
9373
10656
  metadata: { ...metadata, provider: "google-genai" }
9374
10657
  };
9375
10658
  },
9376
- aggregateChunks: aggregateGenerateContentChunks
9377
- }
9378
- );
9379
- }
9380
- subscribeToChannel(channelName, config) {
9381
- const channel = tracingChannel5(channelName);
9382
- const spans = /* @__PURE__ */ new WeakMap();
9383
- const handlers = {
9384
- start: (event) => {
9385
- const span = startSpan({
9386
- name: config.name,
9387
- spanAttributes: {
9388
- type: config.type
9389
- }
9390
- });
9391
- const startTime = getCurrentUnixTimestamp();
9392
- spans.set(event, { span, startTime });
9393
- try {
9394
- const { input, metadata } = config.extractInput(event.arguments);
9395
- span.log({
9396
- input,
9397
- metadata
9398
- });
9399
- } catch (error) {
9400
- console.error(`Error extracting input for ${channelName}:`, error);
9401
- }
9402
- },
9403
- asyncEnd: (event) => {
9404
- const spanData = spans.get(event);
9405
- if (!spanData) {
9406
- return;
9407
- }
9408
- const { span, startTime } = spanData;
9409
- try {
9410
- const output = config.extractOutput(event.result);
9411
- const metrics = config.extractMetrics(event.result, startTime);
9412
- span.log({
9413
- output,
9414
- metrics
9415
- });
9416
- } catch (error) {
9417
- console.error(`Error extracting output for ${channelName}:`, error);
9418
- } finally {
9419
- span.end();
9420
- spans.delete(event);
9421
- }
9422
- },
9423
- error: (event) => {
9424
- const spanData = spans.get(event);
9425
- if (!spanData) {
9426
- return;
10659
+ extractOutput: (result) => {
10660
+ return result;
10661
+ },
10662
+ extractMetrics: (result, startTime) => {
10663
+ return extractGenerateContentMetrics(result, startTime);
9427
10664
  }
9428
- const { span } = spanData;
9429
- span.log({
9430
- error: event.error.message
9431
- });
9432
- span.end();
9433
- spans.delete(event);
9434
- }
9435
- };
9436
- channel.subscribe(handlers);
9437
- this.unsubscribers.push(() => {
9438
- channel.unsubscribe(handlers);
9439
- });
9440
- }
9441
- subscribeToGoogleStreamingChannel(channelName, config) {
9442
- const channel = tracingChannel5(channelName);
9443
- const spans = /* @__PURE__ */ new WeakMap();
9444
- const handlers = {
9445
- start: (event) => {
9446
- const span = startSpan({
9447
- name: config.name,
9448
- spanAttributes: {
9449
- type: config.type
9450
- }
9451
- });
9452
- const startTime = getCurrentUnixTimestamp();
9453
- spans.set(event, { span, startTime });
9454
- try {
9455
- const { input, metadata } = config.extractInput(event.arguments);
9456
- span.log({
10665
+ })
10666
+ );
10667
+ this.unsubscribers.push(
10668
+ traceStreamingChannel(googleGenAIChannels.generateContentStream, {
10669
+ name: "google-genai.generateContentStream",
10670
+ type: "llm" /* LLM */,
10671
+ extractInput: ([params]) => {
10672
+ const input = serializeInput(params);
10673
+ const metadata = extractMetadata(params);
10674
+ return {
9457
10675
  input,
9458
- metadata
9459
- });
9460
- } catch (error) {
9461
- console.error(`Error extracting input for ${channelName}:`, error);
9462
- }
9463
- },
9464
- asyncEnd: (event) => {
9465
- const spanData = spans.get(event);
9466
- if (!spanData) {
9467
- return;
9468
- }
9469
- const { span, startTime } = spanData;
9470
- if (isAsyncIterable(event.result)) {
9471
- patchStreamIfNeeded(event.result, {
9472
- onComplete: (chunks) => {
9473
- try {
9474
- const { output, metrics } = config.aggregateChunks(
9475
- chunks,
9476
- startTime
9477
- );
9478
- span.log({
9479
- output,
9480
- metrics
9481
- });
9482
- } catch (error) {
9483
- console.error(
9484
- `Error extracting output for ${channelName}:`,
9485
- error
9486
- );
9487
- } finally {
9488
- span.end();
9489
- }
9490
- },
9491
- onError: (error) => {
9492
- span.log({
9493
- error: error.message
9494
- });
9495
- span.end();
9496
- }
9497
- });
9498
- } else {
9499
- span.end();
9500
- spans.delete(event);
9501
- }
9502
- },
9503
- error: (event) => {
9504
- const spanData = spans.get(event);
9505
- if (!spanData) {
9506
- return;
10676
+ metadata: { ...metadata, provider: "google-genai" }
10677
+ };
10678
+ },
10679
+ extractOutput: (result) => {
10680
+ return result;
10681
+ },
10682
+ extractMetrics: () => {
10683
+ return {};
10684
+ },
10685
+ aggregateChunks: (chunks, _result, _endEvent, startTime) => {
10686
+ return aggregateGenerateContentChunks(chunks, startTime);
9507
10687
  }
9508
- const { span } = spanData;
9509
- span.log({
9510
- error: event.error.message
9511
- });
9512
- span.end();
9513
- spans.delete(event);
9514
- }
9515
- };
9516
- channel.subscribe(handlers);
9517
- this.unsubscribers.push(() => {
9518
- channel.unsubscribe(handlers);
9519
- });
10688
+ })
10689
+ );
9520
10690
  }
9521
10691
  };
9522
10692
  function serializeInput(params) {
@@ -9572,8 +10742,12 @@ function serializePart(part) {
9572
10742
  const buffer = typeof data === "string" ? typeof Buffer !== "undefined" ? Buffer.from(data, "base64") : new Uint8Array(
9573
10743
  atob(data).split("").map((c) => c.charCodeAt(0))
9574
10744
  ) : typeof Buffer !== "undefined" ? Buffer.from(data) : new Uint8Array(data);
10745
+ const arrayBuffer = buffer instanceof Uint8Array ? buffer.buffer.slice(
10746
+ buffer.byteOffset,
10747
+ buffer.byteOffset + buffer.byteLength
10748
+ ) : buffer;
9575
10749
  const attachment = new Attachment({
9576
- data: buffer,
10750
+ data: arrayBuffer,
9577
10751
  filename,
9578
10752
  contentType: mimeType || "application/octet-stream"
9579
10753
  });
@@ -9622,33 +10796,36 @@ function extractGenerateContentMetrics(response, startTime) {
9622
10796
  const end = getCurrentUnixTimestamp();
9623
10797
  metrics.duration = end - startTime;
9624
10798
  }
9625
- if (response.usageMetadata) {
9626
- const usage = response.usageMetadata;
9627
- if (usage.promptTokenCount !== void 0) {
9628
- metrics.prompt_tokens = usage.promptTokenCount;
9629
- }
9630
- if (usage.candidatesTokenCount !== void 0) {
9631
- metrics.completion_tokens = usage.candidatesTokenCount;
9632
- }
9633
- if (usage.totalTokenCount !== void 0) {
9634
- metrics.tokens = usage.totalTokenCount;
9635
- }
9636
- if (usage.cachedContentTokenCount !== void 0) {
9637
- metrics.prompt_cached_tokens = usage.cachedContentTokenCount;
9638
- }
9639
- if (usage.thoughtsTokenCount !== void 0) {
9640
- metrics.completion_reasoning_tokens = usage.thoughtsTokenCount;
9641
- }
10799
+ if (response?.usageMetadata) {
10800
+ populateUsageMetrics(metrics, response.usageMetadata);
9642
10801
  }
9643
10802
  return metrics;
9644
10803
  }
10804
+ function populateUsageMetrics(metrics, usage) {
10805
+ if (usage.promptTokenCount !== void 0) {
10806
+ metrics.prompt_tokens = usage.promptTokenCount;
10807
+ }
10808
+ if (usage.candidatesTokenCount !== void 0) {
10809
+ metrics.completion_tokens = usage.candidatesTokenCount;
10810
+ }
10811
+ if (usage.totalTokenCount !== void 0) {
10812
+ metrics.tokens = usage.totalTokenCount;
10813
+ }
10814
+ if (usage.cachedContentTokenCount !== void 0) {
10815
+ metrics.prompt_cached_tokens = usage.cachedContentTokenCount;
10816
+ }
10817
+ if (usage.thoughtsTokenCount !== void 0) {
10818
+ metrics.completion_reasoning_tokens = usage.thoughtsTokenCount;
10819
+ }
10820
+ }
9645
10821
  function aggregateGenerateContentChunks(chunks, startTime) {
9646
- const end = getCurrentUnixTimestamp();
9647
- const metrics = {
9648
- duration: end - startTime
9649
- };
10822
+ const metrics = {};
10823
+ if (startTime !== void 0) {
10824
+ const end = getCurrentUnixTimestamp();
10825
+ metrics.duration = end - startTime;
10826
+ }
9650
10827
  let firstTokenTime = null;
9651
- if (chunks.length > 0 && firstTokenTime === null) {
10828
+ if (chunks.length > 0 && firstTokenTime === null && startTime !== void 0) {
9652
10829
  firstTokenTime = getCurrentUnixTimestamp();
9653
10830
  metrics.time_to_first_token = firstTokenTime - startTime;
9654
10831
  }
@@ -9719,21 +10896,7 @@ function aggregateGenerateContentChunks(chunks, startTime) {
9719
10896
  }
9720
10897
  if (usageMetadata) {
9721
10898
  output.usageMetadata = usageMetadata;
9722
- if (usageMetadata.promptTokenCount !== void 0) {
9723
- metrics.prompt_tokens = usageMetadata.promptTokenCount;
9724
- }
9725
- if (usageMetadata.candidatesTokenCount !== void 0) {
9726
- metrics.completion_tokens = usageMetadata.candidatesTokenCount;
9727
- }
9728
- if (usageMetadata.totalTokenCount !== void 0) {
9729
- metrics.tokens = usageMetadata.totalTokenCount;
9730
- }
9731
- if (usageMetadata.cachedContentTokenCount !== void 0) {
9732
- metrics.prompt_cached_tokens = usageMetadata.cachedContentTokenCount;
9733
- }
9734
- if (usageMetadata.thoughtsTokenCount !== void 0) {
9735
- metrics.completion_reasoning_tokens = usageMetadata.thoughtsTokenCount;
9736
- }
10899
+ populateUsageMetrics(metrics, usageMetadata);
9737
10900
  }
9738
10901
  if (text) {
9739
10902
  output.text = text;
@@ -9745,7 +10908,8 @@ function tryToDict(obj) {
9745
10908
  return null;
9746
10909
  }
9747
10910
  if (typeof obj === "object") {
9748
- if (typeof obj.toJSON === "function") {
10911
+ if ("toJSON" in obj && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
10912
+ typeof obj.toJSON === "function") {
9749
10913
  return obj.toJSON();
9750
10914
  }
9751
10915
  return obj;