braintrust 3.5.0 → 3.7.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 (43) hide show
  1. package/dev/dist/index.d.mts +4 -2
  2. package/dev/dist/index.d.ts +4 -2
  3. package/dev/dist/index.js +2453 -612
  4. package/dev/dist/index.mjs +2150 -309
  5. package/dist/auto-instrumentations/bundler/esbuild.cjs +115 -6
  6. package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
  7. package/dist/auto-instrumentations/bundler/rollup.cjs +115 -6
  8. package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
  9. package/dist/auto-instrumentations/bundler/vite.cjs +115 -6
  10. package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
  11. package/dist/auto-instrumentations/bundler/webpack-loader.cjs +955 -0
  12. package/dist/auto-instrumentations/bundler/webpack-loader.d.ts +53 -0
  13. package/dist/auto-instrumentations/bundler/webpack.cjs +115 -6
  14. package/dist/auto-instrumentations/bundler/webpack.mjs +2 -2
  15. package/dist/auto-instrumentations/{chunk-DQTPSXJB.mjs → chunk-AKEXR4AL.mjs} +116 -7
  16. package/dist/auto-instrumentations/{chunk-F3TJZ3Z2.mjs → chunk-ZK2IYER2.mjs} +3 -1
  17. package/dist/auto-instrumentations/hook.mjs +199 -55
  18. package/dist/auto-instrumentations/index.cjs +116 -6
  19. package/dist/auto-instrumentations/index.d.mts +3 -1
  20. package/dist/auto-instrumentations/index.d.ts +3 -1
  21. package/dist/auto-instrumentations/index.mjs +3 -1
  22. package/dist/browser.d.mts +17 -4
  23. package/dist/browser.d.ts +17 -4
  24. package/dist/browser.js +2386 -440
  25. package/dist/browser.mjs +2386 -440
  26. package/dist/cli.js +2118 -273
  27. package/dist/edge-light.d.mts +1 -1
  28. package/dist/edge-light.d.ts +1 -1
  29. package/dist/edge-light.js +2348 -485
  30. package/dist/edge-light.mjs +2348 -485
  31. package/dist/index.d.mts +30 -17
  32. package/dist/index.d.ts +30 -17
  33. package/dist/index.js +2709 -761
  34. package/dist/index.mjs +2392 -444
  35. package/dist/instrumentation/index.d.mts +3 -0
  36. package/dist/instrumentation/index.d.ts +3 -0
  37. package/dist/instrumentation/index.js +2030 -274
  38. package/dist/instrumentation/index.mjs +2030 -274
  39. package/dist/workerd.d.mts +1 -1
  40. package/dist/workerd.d.ts +1 -1
  41. package/dist/workerd.js +2348 -485
  42. package/dist/workerd.mjs +2348 -485
  43. package/package.json +5 -1
@@ -2,6 +2,90 @@
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import * as diagnostics_channel from "node:diagnostics_channel";
4
4
  import * as path from "node:path";
5
+
6
+ // src/auto-instrumentations/patch-tracing-channel.ts
7
+ function patchTracingChannel(tracingChannelFn) {
8
+ const dummyChannel = tracingChannelFn("__braintrust_probe__");
9
+ const TracingChannel = dummyChannel?.constructor;
10
+ if (!TracingChannel?.prototype) {
11
+ return;
12
+ }
13
+ if (!Object.getOwnPropertyDescriptor(TracingChannel.prototype, "hasSubscribers")) {
14
+ Object.defineProperty(TracingChannel.prototype, "hasSubscribers", {
15
+ configurable: true,
16
+ enumerable: false,
17
+ get() {
18
+ return Boolean(
19
+ this.start?.hasSubscribers || this.end?.hasSubscribers || this.asyncStart?.hasSubscribers || this.asyncEnd?.hasSubscribers || this.error?.hasSubscribers
20
+ );
21
+ }
22
+ });
23
+ }
24
+ if (TracingChannel.prototype.tracePromise) {
25
+ TracingChannel.prototype.tracePromise = function(fn, context = {}, thisArg, ...args) {
26
+ const { start, end, asyncStart, asyncEnd, error } = this;
27
+ function publishRejected(err) {
28
+ context.error = err;
29
+ error?.publish(context);
30
+ asyncStart?.publish(context);
31
+ asyncEnd?.publish(context);
32
+ }
33
+ function publishResolved(result) {
34
+ context.result = result;
35
+ asyncStart?.publish(context);
36
+ asyncEnd?.publish(context);
37
+ }
38
+ return start.runStores(context, () => {
39
+ try {
40
+ const result = Reflect.apply(fn, thisArg, args);
41
+ end?.publish(context);
42
+ if (result && (typeof result === "object" || typeof result === "function") && typeof result.then === "function") {
43
+ if (result.constructor === Promise) {
44
+ return result.then(
45
+ (res) => {
46
+ publishResolved(res);
47
+ return res;
48
+ },
49
+ (err) => {
50
+ publishRejected(err);
51
+ return Promise.reject(err);
52
+ }
53
+ );
54
+ }
55
+ void result.then(
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ (resolved) => {
58
+ try {
59
+ publishResolved(resolved);
60
+ } catch {
61
+ }
62
+ },
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ (err) => {
65
+ try {
66
+ publishRejected(err);
67
+ } catch {
68
+ }
69
+ }
70
+ );
71
+ return result;
72
+ }
73
+ context.result = result;
74
+ asyncStart?.publish(context);
75
+ asyncEnd?.publish(context);
76
+ return result;
77
+ } catch (err) {
78
+ context.error = err;
79
+ error?.publish(context);
80
+ end?.publish(context);
81
+ throw err;
82
+ }
83
+ });
84
+ };
85
+ }
86
+ }
87
+
88
+ // src/node/config.ts
5
89
  import * as fs from "node:fs/promises";
6
90
  import * as os from "node:os";
7
91
  import * as fsSync from "node:fs";
@@ -4506,6 +4590,9 @@ var BRAINTRUST_ATTACHMENT = BraintrustAttachmentReference.shape.type.value;
4506
4590
  var EXTERNAL_ATTACHMENT = ExternalAttachmentReference.shape.type.value;
4507
4591
  var LOGS3_OVERFLOW_REFERENCE_TYPE = "logs3_overflow";
4508
4592
  var BRAINTRUST_PARAMS = Object.keys(BraintrustModelParams.shape);
4593
+ var RESET_CONTEXT_MANAGER_STATE = Symbol.for(
4594
+ "braintrust.resetContextManagerState"
4595
+ );
4509
4596
  var DEFAULT_MAX_REQUEST_SIZE = 6 * 1024 * 1024;
4510
4597
  var parametersRowSchema = z8.object({
4511
4598
  id: z8.string().uuid(),
@@ -4568,13 +4655,18 @@ function applyMaskingToField(maskingFunction, data, fieldName) {
4568
4655
  return `ERROR: Failed to mask field '${fieldName}' - ${errorType}`;
4569
4656
  }
4570
4657
  }
4658
+ var BRAINTRUST_CURRENT_SPAN_STORE = Symbol.for(
4659
+ "braintrust.currentSpanStore"
4660
+ );
4571
4661
  var ContextManager = class {
4572
4662
  };
4573
4663
  var BraintrustContextManager = class extends ContextManager {
4574
4664
  _currentSpan;
4665
+ [BRAINTRUST_CURRENT_SPAN_STORE];
4575
4666
  constructor() {
4576
4667
  super();
4577
4668
  this._currentSpan = isomorph_default.newAsyncLocalStorage();
4669
+ this[BRAINTRUST_CURRENT_SPAN_STORE] = this._currentSpan;
4578
4670
  }
4579
4671
  getParentSpanIds() {
4580
4672
  const currentSpan2 = this._currentSpan.getStore();
@@ -4781,6 +4873,9 @@ var BraintrustState = class _BraintrustState {
4781
4873
  resetIdGenState() {
4782
4874
  this._idGenerator = null;
4783
4875
  }
4876
+ [RESET_CONTEXT_MANAGER_STATE]() {
4877
+ this._contextManager = null;
4878
+ }
4784
4879
  get idGenerator() {
4785
4880
  if (this._idGenerator === null) {
4786
4881
  this._idGenerator = getIdGenerator();
@@ -9283,6 +9378,36 @@ function startSpanForEvent(config, event, channelName) {
9283
9378
  }
9284
9379
  return { span, startTime };
9285
9380
  }
9381
+ function ensureSpanStateForEvent(states, config, event, channelName) {
9382
+ const key = event;
9383
+ const existing = states.get(key);
9384
+ if (existing) {
9385
+ return existing;
9386
+ }
9387
+ const created = startSpanForEvent(config, event, channelName);
9388
+ states.set(key, created);
9389
+ return created;
9390
+ }
9391
+ function bindCurrentSpanStoreToStart(tracingChannel2, states, config, channelName) {
9392
+ const state = _internalGetGlobalState();
9393
+ const startChannel = tracingChannel2.start;
9394
+ const currentSpanStore = state?.contextManager ? state.contextManager[BRAINTRUST_CURRENT_SPAN_STORE] : void 0;
9395
+ if (!currentSpanStore || !startChannel) {
9396
+ return void 0;
9397
+ }
9398
+ startChannel.bindStore(
9399
+ currentSpanStore,
9400
+ (event) => ensureSpanStateForEvent(
9401
+ states,
9402
+ config,
9403
+ event,
9404
+ channelName
9405
+ ).span
9406
+ );
9407
+ return () => {
9408
+ startChannel.unbindStore(currentSpanStore);
9409
+ };
9410
+ }
9286
9411
  function logErrorAndEnd(states, event) {
9287
9412
  const spanData = states.get(event);
9288
9413
  if (!spanData) {
@@ -9298,15 +9423,19 @@ function traceAsyncChannel(channel2, config) {
9298
9423
  const tracingChannel2 = channel2.tracingChannel();
9299
9424
  const states = /* @__PURE__ */ new WeakMap();
9300
9425
  const channelName = channel2.channelName;
9426
+ const unbindCurrentSpanStore = bindCurrentSpanStoreToStart(
9427
+ tracingChannel2,
9428
+ states,
9429
+ config,
9430
+ channelName
9431
+ );
9301
9432
  const handlers = {
9302
9433
  start: (event) => {
9303
- states.set(
9434
+ ensureSpanStateForEvent(
9435
+ states,
9436
+ config,
9304
9437
  event,
9305
- startSpanForEvent(
9306
- config,
9307
- event,
9308
- channelName
9309
- )
9438
+ channelName
9310
9439
  );
9311
9440
  },
9312
9441
  asyncEnd: (event) => {
@@ -9348,6 +9477,7 @@ function traceAsyncChannel(channel2, config) {
9348
9477
  };
9349
9478
  tracingChannel2.subscribe(handlers);
9350
9479
  return () => {
9480
+ unbindCurrentSpanStore?.();
9351
9481
  tracingChannel2.unsubscribe(handlers);
9352
9482
  };
9353
9483
  }
@@ -9355,15 +9485,19 @@ function traceStreamingChannel(channel2, config) {
9355
9485
  const tracingChannel2 = channel2.tracingChannel();
9356
9486
  const states = /* @__PURE__ */ new WeakMap();
9357
9487
  const channelName = channel2.channelName;
9488
+ const unbindCurrentSpanStore = bindCurrentSpanStoreToStart(
9489
+ tracingChannel2,
9490
+ states,
9491
+ config,
9492
+ channelName
9493
+ );
9358
9494
  const handlers = {
9359
9495
  start: (event) => {
9360
- states.set(
9496
+ ensureSpanStateForEvent(
9497
+ states,
9498
+ config,
9361
9499
  event,
9362
- startSpanForEvent(
9363
- config,
9364
- event,
9365
- channelName
9366
- )
9500
+ channelName
9367
9501
  );
9368
9502
  },
9369
9503
  asyncEnd: (event) => {
@@ -9479,6 +9613,7 @@ function traceStreamingChannel(channel2, config) {
9479
9613
  };
9480
9614
  tracingChannel2.subscribe(handlers);
9481
9615
  return () => {
9616
+ unbindCurrentSpanStore?.();
9482
9617
  tracingChannel2.unsubscribe(handlers);
9483
9618
  };
9484
9619
  }
@@ -9486,15 +9621,19 @@ function traceSyncStreamChannel(channel2, config) {
9486
9621
  const tracingChannel2 = channel2.tracingChannel();
9487
9622
  const states = /* @__PURE__ */ new WeakMap();
9488
9623
  const channelName = channel2.channelName;
9624
+ const unbindCurrentSpanStore = bindCurrentSpanStoreToStart(
9625
+ tracingChannel2,
9626
+ states,
9627
+ config,
9628
+ channelName
9629
+ );
9489
9630
  const handlers = {
9490
9631
  start: (event) => {
9491
- states.set(
9632
+ ensureSpanStateForEvent(
9633
+ states,
9634
+ config,
9492
9635
  event,
9493
- startSpanForEvent(
9494
- config,
9495
- event,
9496
- channelName
9497
- )
9636
+ channelName
9498
9637
  );
9499
9638
  },
9500
9639
  end: (event) => {
@@ -9583,6 +9722,7 @@ function traceSyncStreamChannel(channel2, config) {
9583
9722
  };
9584
9723
  tracingChannel2.subscribe(handlers);
9585
9724
  return () => {
9725
+ unbindCurrentSpanStore?.();
9586
9726
  tracingChannel2.unsubscribe(handlers);
9587
9727
  };
9588
9728
  }
@@ -10354,7 +10494,7 @@ var AnthropicPlugin = class extends BasePlugin {
10354
10494
  this.unsubscribers.push(
10355
10495
  traceStreamingChannel(anthropicChannels.betaMessagesCreate, {
10356
10496
  ...anthropicConfig,
10357
- name: "anthropic.beta.messages.create"
10497
+ name: "anthropic.messages.create"
10358
10498
  })
10359
10499
  );
10360
10500
  }
@@ -10377,9 +10517,12 @@ function parseMetricsFromUsage2(usage) {
10377
10517
  return metrics;
10378
10518
  }
10379
10519
  function aggregateAnthropicStreamChunks(chunks) {
10380
- const deltas = [];
10520
+ const fallbackTextDeltas = [];
10521
+ const contentBlocks = {};
10522
+ const contentBlockDeltas = {};
10381
10523
  let metrics = {};
10382
10524
  let metadata = {};
10525
+ let role;
10383
10526
  for (const event of chunks) {
10384
10527
  switch (event?.type) {
10385
10528
  case "message_start":
@@ -10387,15 +10530,43 @@ function aggregateAnthropicStreamChunks(chunks) {
10387
10530
  const initialMetrics = parseMetricsFromUsage2(event.message.usage);
10388
10531
  metrics = { ...metrics, ...initialMetrics };
10389
10532
  }
10533
+ if (typeof event.message?.role === "string") {
10534
+ role = event.message.role;
10535
+ }
10536
+ break;
10537
+ case "content_block_start":
10538
+ if (event.content_block) {
10539
+ contentBlocks[event.index] = event.content_block;
10540
+ contentBlockDeltas[event.index] = [];
10541
+ }
10390
10542
  break;
10391
10543
  case "content_block_delta":
10392
10544
  if (event.delta?.type === "text_delta") {
10393
10545
  const text = event.delta.text;
10394
10546
  if (text) {
10395
- deltas.push(text);
10547
+ if (contentBlocks[event.index] !== void 0 || contentBlockDeltas[event.index] !== void 0) {
10548
+ contentBlockDeltas[event.index] ??= [];
10549
+ contentBlockDeltas[event.index].push(text);
10550
+ } else {
10551
+ fallbackTextDeltas.push(text);
10552
+ }
10553
+ }
10554
+ } else if (event.delta?.type === "input_json_delta") {
10555
+ const partialJson = event.delta.partial_json;
10556
+ if (partialJson) {
10557
+ contentBlockDeltas[event.index] ??= [];
10558
+ contentBlockDeltas[event.index].push(partialJson);
10396
10559
  }
10397
10560
  }
10398
10561
  break;
10562
+ case "content_block_stop":
10563
+ finalizeContentBlock(
10564
+ event.index,
10565
+ contentBlocks,
10566
+ contentBlockDeltas,
10567
+ fallbackTextDeltas
10568
+ );
10569
+ break;
10399
10570
  case "message_delta":
10400
10571
  if (event.usage) {
10401
10572
  const finalMetrics = parseMetricsFromUsage2(event.usage);
@@ -10407,7 +10578,21 @@ function aggregateAnthropicStreamChunks(chunks) {
10407
10578
  break;
10408
10579
  }
10409
10580
  }
10410
- const output = deltas.join("");
10581
+ const orderedContent = Object.entries(contentBlocks).map(([index, block]) => ({
10582
+ block,
10583
+ index: Number(index)
10584
+ })).filter(({ block }) => block !== void 0).sort((left, right) => left.index - right.index).map(({ block }) => block);
10585
+ let output = fallbackTextDeltas.join("");
10586
+ if (orderedContent.length > 0) {
10587
+ if (orderedContent.every(isTextContentBlock)) {
10588
+ output = orderedContent.map((block) => block.text).join("");
10589
+ } else {
10590
+ output = {
10591
+ ...role ? { role } : {},
10592
+ content: orderedContent
10593
+ };
10594
+ }
10595
+ }
10411
10596
  const finalized = finalizeAnthropicTokens(metrics);
10412
10597
  const filteredMetrics = Object.fromEntries(
10413
10598
  Object.entries(finalized).filter(
@@ -10420,6 +10605,49 @@ function aggregateAnthropicStreamChunks(chunks) {
10420
10605
  metadata
10421
10606
  };
10422
10607
  }
10608
+ function finalizeContentBlock(index, contentBlocks, contentBlockDeltas, fallbackTextDeltas) {
10609
+ const contentBlock = contentBlocks[index];
10610
+ if (!contentBlock) {
10611
+ return;
10612
+ }
10613
+ const text = contentBlockDeltas[index]?.join("") ?? "";
10614
+ if (isToolUseContentBlock(contentBlock)) {
10615
+ if (!text) {
10616
+ return;
10617
+ }
10618
+ try {
10619
+ contentBlocks[index] = {
10620
+ ...contentBlock,
10621
+ input: JSON.parse(text)
10622
+ };
10623
+ } catch {
10624
+ fallbackTextDeltas.push(text);
10625
+ delete contentBlocks[index];
10626
+ }
10627
+ return;
10628
+ }
10629
+ if (isTextContentBlock(contentBlock)) {
10630
+ if (!text) {
10631
+ delete contentBlocks[index];
10632
+ return;
10633
+ }
10634
+ contentBlocks[index] = {
10635
+ ...contentBlock,
10636
+ text
10637
+ };
10638
+ return;
10639
+ }
10640
+ if (text) {
10641
+ fallbackTextDeltas.push(text);
10642
+ }
10643
+ delete contentBlocks[index];
10644
+ }
10645
+ function isTextContentBlock(contentBlock) {
10646
+ return contentBlock.type === "text";
10647
+ }
10648
+ function isToolUseContentBlock(contentBlock) {
10649
+ return contentBlock.type === "tool_use";
10650
+ }
10423
10651
  function isAnthropicBase64ContentBlock(input) {
10424
10652
  return (input.type === "image" || input.type === "document") && isObject(input.source) && input.source.type === "base64";
10425
10653
  }
@@ -11542,12 +11770,15 @@ var claudeAgentSDKChannels = defineChannels(
11542
11770
  {
11543
11771
  query: channel({
11544
11772
  channelName: "query",
11545
- kind: "async"
11773
+ kind: "sync-stream"
11546
11774
  })
11547
11775
  }
11548
11776
  );
11549
11777
 
11550
11778
  // src/instrumentation/plugins/claude-agent-sdk-plugin.ts
11779
+ function isSubAgentToolName(toolName) {
11780
+ return toolName === "Agent" || toolName === "Task";
11781
+ }
11551
11782
  function filterSerializableOptions(options) {
11552
11783
  const allowedKeys = [
11553
11784
  "model",
@@ -11601,34 +11832,50 @@ function extractUsageFromMessage(message) {
11601
11832
  const cacheReadTokens = getNumberProperty(usage, "cache_read_input_tokens") || 0;
11602
11833
  const cacheCreationTokens = getNumberProperty(usage, "cache_creation_input_tokens") || 0;
11603
11834
  if (cacheReadTokens > 0 || cacheCreationTokens > 0) {
11604
- const cacheTokens = extractAnthropicCacheTokens(
11605
- cacheReadTokens,
11606
- cacheCreationTokens
11835
+ Object.assign(
11836
+ metrics,
11837
+ extractAnthropicCacheTokens(cacheReadTokens, cacheCreationTokens)
11607
11838
  );
11608
- Object.assign(metrics, cacheTokens);
11609
11839
  }
11610
11840
  if (Object.keys(metrics).length > 0) {
11611
11841
  Object.assign(metrics, finalizeAnthropicTokens(metrics));
11612
11842
  }
11613
11843
  return metrics;
11614
11844
  }
11615
- function buildLLMInput(prompt, conversationHistory) {
11616
- const promptMessage = typeof prompt === "string" ? { content: prompt, role: "user" } : void 0;
11617
- const inputParts = [
11618
- ...promptMessage ? [promptMessage] : [],
11619
- ...conversationHistory
11620
- ];
11845
+ function buildLLMInput(prompt, conversationHistory, capturedPromptMessages) {
11846
+ const promptMessages = [];
11847
+ if (typeof prompt === "string") {
11848
+ promptMessages.push({ content: prompt, role: "user" });
11849
+ } else if (capturedPromptMessages && capturedPromptMessages.length > 0) {
11850
+ for (const msg of capturedPromptMessages) {
11851
+ const role = msg.message?.role;
11852
+ const content = msg.message?.content;
11853
+ if (role && content !== void 0) {
11854
+ promptMessages.push({ content, role });
11855
+ }
11856
+ }
11857
+ }
11858
+ const inputParts = [...promptMessages, ...conversationHistory];
11621
11859
  return inputParts.length > 0 ? inputParts : void 0;
11622
11860
  }
11623
- async function createLLMSpanForMessages(messages, prompt, conversationHistory, options, startTime, parentSpan) {
11624
- if (messages.length === 0) return void 0;
11861
+ function formatCapturedMessages(messages) {
11862
+ return messages.length > 0 ? messages : [];
11863
+ }
11864
+ async function createLLMSpanForMessages(messages, prompt, conversationHistory, options, startTime, capturedPromptMessages, parentSpan) {
11865
+ if (messages.length === 0) {
11866
+ return void 0;
11867
+ }
11625
11868
  const lastMessage = messages[messages.length - 1];
11626
11869
  if (lastMessage.type !== "assistant" || !lastMessage.message?.usage) {
11627
11870
  return void 0;
11628
11871
  }
11629
11872
  const model = lastMessage.message.model || options.model;
11630
11873
  const usage = extractUsageFromMessage(lastMessage);
11631
- const input = buildLLMInput(prompt, conversationHistory);
11874
+ const input = buildLLMInput(
11875
+ prompt,
11876
+ conversationHistory,
11877
+ capturedPromptMessages
11878
+ );
11632
11879
  const outputs = messages.map(
11633
11880
  (m) => m.message?.content && m.message?.role ? { content: m.message.content, role: m.message.role } : void 0
11634
11881
  ).filter(
@@ -11636,21 +11883,359 @@ async function createLLMSpanForMessages(messages, prompt, conversationHistory, o
11636
11883
  );
11637
11884
  const span = startSpan({
11638
11885
  name: "anthropic.messages.create",
11886
+ parent: parentSpan,
11639
11887
  spanAttributes: {
11640
11888
  type: "llm" /* LLM */
11641
11889
  },
11642
- startTime,
11643
- parent: parentSpan
11890
+ startTime
11644
11891
  });
11645
11892
  span.log({
11646
11893
  input,
11647
- output: outputs,
11648
11894
  metadata: model ? { model } : void 0,
11649
- metrics: usage
11895
+ metrics: usage,
11896
+ output: outputs
11650
11897
  });
11651
11898
  await span.end();
11652
11899
  return lastMessage.message?.content && lastMessage.message?.role ? { content: lastMessage.message.content, role: lastMessage.message.role } : void 0;
11653
11900
  }
11901
+ function getMcpServerMetadata(serverName, mcpServers) {
11902
+ if (!serverName || !mcpServers) {
11903
+ return {};
11904
+ }
11905
+ const serverConfig = mcpServers[serverName];
11906
+ if (!serverConfig) {
11907
+ return {};
11908
+ }
11909
+ const metadata = {};
11910
+ if (serverConfig.type) {
11911
+ metadata["mcp.type"] = serverConfig.type;
11912
+ } else if (typeof serverConfig === "object" && "transport" in serverConfig) {
11913
+ metadata["mcp.type"] = "sdk";
11914
+ }
11915
+ if (serverConfig.url) {
11916
+ metadata["mcp.url"] = serverConfig.url;
11917
+ }
11918
+ if (serverConfig.command) {
11919
+ metadata["mcp.command"] = serverConfig.command;
11920
+ if (serverConfig.args) {
11921
+ metadata["mcp.args"] = serverConfig.args.join(" ");
11922
+ }
11923
+ }
11924
+ return metadata;
11925
+ }
11926
+ function parseToolName(rawToolName) {
11927
+ const mcpMatch = rawToolName.match(/^mcp__([^_]+)__(.+)$/);
11928
+ if (mcpMatch) {
11929
+ const [, mcpServer, toolName] = mcpMatch;
11930
+ return {
11931
+ displayName: `tool: ${mcpServer}/${toolName}`,
11932
+ mcpServer,
11933
+ rawToolName,
11934
+ toolName
11935
+ };
11936
+ }
11937
+ return {
11938
+ displayName: `tool: ${rawToolName}`,
11939
+ rawToolName,
11940
+ toolName: rawToolName
11941
+ };
11942
+ }
11943
+ function createToolTracingHooks(resolveParentSpan, activeToolSpans, mcpServers, subAgentSpans, endedSubAgentSpans) {
11944
+ const preToolUse = async (input, toolUseID) => {
11945
+ if (input.hook_event_name !== "PreToolUse" || !toolUseID) {
11946
+ return {};
11947
+ }
11948
+ if (isSubAgentToolName(input.tool_name)) {
11949
+ return {};
11950
+ }
11951
+ const parsed = parseToolName(input.tool_name);
11952
+ const toolSpan = startSpan({
11953
+ event: {
11954
+ input: input.tool_input,
11955
+ metadata: {
11956
+ "claude_agent_sdk.cwd": input.cwd,
11957
+ "claude_agent_sdk.raw_tool_name": parsed.rawToolName,
11958
+ "claude_agent_sdk.session_id": input.session_id,
11959
+ "gen_ai.tool.call.id": toolUseID,
11960
+ "gen_ai.tool.name": parsed.toolName,
11961
+ ...parsed.mcpServer && { "mcp.server": parsed.mcpServer },
11962
+ ...getMcpServerMetadata(parsed.mcpServer, mcpServers)
11963
+ }
11964
+ },
11965
+ name: parsed.displayName,
11966
+ parent: await resolveParentSpan(toolUseID),
11967
+ spanAttributes: { type: "tool" /* TOOL */ }
11968
+ });
11969
+ activeToolSpans.set(toolUseID, toolSpan);
11970
+ return {};
11971
+ };
11972
+ const postToolUse = async (input, toolUseID) => {
11973
+ if (input.hook_event_name !== "PostToolUse" || !toolUseID) {
11974
+ return {};
11975
+ }
11976
+ const subAgentSpan = subAgentSpans.get(toolUseID);
11977
+ if (subAgentSpan) {
11978
+ try {
11979
+ const response = input.tool_response;
11980
+ const metadata = {};
11981
+ if (response?.status) {
11982
+ metadata["claude_agent_sdk.status"] = response.status;
11983
+ }
11984
+ if (response?.totalDurationMs) {
11985
+ metadata["claude_agent_sdk.duration_ms"] = response.totalDurationMs;
11986
+ }
11987
+ if (response?.totalToolUseCount !== void 0) {
11988
+ metadata["claude_agent_sdk.tool_use_count"] = response.totalToolUseCount;
11989
+ }
11990
+ subAgentSpan.log({
11991
+ metadata,
11992
+ output: response?.content
11993
+ });
11994
+ } finally {
11995
+ subAgentSpan.end();
11996
+ endedSubAgentSpans.add(toolUseID);
11997
+ }
11998
+ return {};
11999
+ }
12000
+ const toolSpan = activeToolSpans.get(toolUseID);
12001
+ if (!toolSpan) {
12002
+ return {};
12003
+ }
12004
+ try {
12005
+ toolSpan.log({ output: input.tool_response });
12006
+ } finally {
12007
+ toolSpan.end();
12008
+ activeToolSpans.delete(toolUseID);
12009
+ }
12010
+ return {};
12011
+ };
12012
+ const postToolUseFailure = async (input, toolUseID) => {
12013
+ if (input.hook_event_name !== "PostToolUseFailure" || !toolUseID) {
12014
+ return {};
12015
+ }
12016
+ const subAgentSpan = subAgentSpans.get(toolUseID);
12017
+ if (subAgentSpan) {
12018
+ try {
12019
+ subAgentSpan.log({ error: input.error });
12020
+ } finally {
12021
+ subAgentSpan.end();
12022
+ endedSubAgentSpans.add(toolUseID);
12023
+ }
12024
+ return {};
12025
+ }
12026
+ const toolSpan = activeToolSpans.get(toolUseID);
12027
+ if (!toolSpan) {
12028
+ return {};
12029
+ }
12030
+ const parsed = parseToolName(input.tool_name);
12031
+ try {
12032
+ toolSpan.log({
12033
+ error: input.error,
12034
+ metadata: {
12035
+ "claude_agent_sdk.is_interrupt": input.is_interrupt,
12036
+ "claude_agent_sdk.session_id": input.session_id,
12037
+ "gen_ai.tool.call.id": toolUseID,
12038
+ "gen_ai.tool.name": parsed.toolName,
12039
+ ...parsed.mcpServer && { "mcp.server": parsed.mcpServer }
12040
+ }
12041
+ });
12042
+ } finally {
12043
+ toolSpan.end();
12044
+ activeToolSpans.delete(toolUseID);
12045
+ }
12046
+ return {};
12047
+ };
12048
+ return { postToolUse, postToolUseFailure, preToolUse };
12049
+ }
12050
+ function injectTracingHooks(options, resolveParentSpan, activeToolSpans, subAgentSpans, endedSubAgentSpans) {
12051
+ const { preToolUse, postToolUse, postToolUseFailure } = createToolTracingHooks(
12052
+ resolveParentSpan,
12053
+ activeToolSpans,
12054
+ options.mcpServers,
12055
+ subAgentSpans,
12056
+ endedSubAgentSpans
12057
+ );
12058
+ const existingHooks = options.hooks ?? {};
12059
+ return {
12060
+ ...options,
12061
+ hooks: {
12062
+ ...existingHooks,
12063
+ PostToolUse: [
12064
+ ...existingHooks.PostToolUse ?? [],
12065
+ { hooks: [postToolUse] }
12066
+ ],
12067
+ PostToolUseFailure: [
12068
+ ...existingHooks.PostToolUseFailure ?? [],
12069
+ {
12070
+ hooks: [postToolUseFailure]
12071
+ }
12072
+ ],
12073
+ PreToolUse: [
12074
+ ...existingHooks.PreToolUse ?? [],
12075
+ { hooks: [preToolUse] }
12076
+ ]
12077
+ }
12078
+ };
12079
+ }
12080
+ async function finalizeCurrentMessageGroup(state) {
12081
+ if (state.currentMessages.length === 0) {
12082
+ return;
12083
+ }
12084
+ const parentToolUseId = state.currentMessages[0]?.parent_tool_use_id ?? null;
12085
+ let parentSpan = await state.span.export();
12086
+ if (parentToolUseId) {
12087
+ const subAgentSpan = state.subAgentSpans.get(parentToolUseId);
12088
+ if (subAgentSpan) {
12089
+ parentSpan = await subAgentSpan.export();
12090
+ }
12091
+ }
12092
+ const finalMessage = await createLLMSpanForMessages(
12093
+ state.currentMessages,
12094
+ state.originalPrompt,
12095
+ state.finalResults,
12096
+ state.options,
12097
+ state.currentMessageStartTime,
12098
+ state.capturedPromptMessages,
12099
+ parentSpan
12100
+ );
12101
+ if (finalMessage) {
12102
+ state.finalResults.push(finalMessage);
12103
+ }
12104
+ const lastMessage = state.currentMessages[state.currentMessages.length - 1];
12105
+ if (lastMessage?.message?.usage) {
12106
+ state.accumulatedOutputTokens += getNumberProperty(lastMessage.message.usage, "output_tokens") || 0;
12107
+ }
12108
+ state.currentMessages.length = 0;
12109
+ }
12110
+ function maybeTrackToolUseContext(state, message) {
12111
+ if (message.type !== "assistant" || !Array.isArray(message.message?.content)) {
12112
+ return;
12113
+ }
12114
+ const parentToolUseId = message.parent_tool_use_id ?? null;
12115
+ for (const block of message.message.content) {
12116
+ if (typeof block !== "object" || block === null || !("type" in block) || block.type !== "tool_use" || !("id" in block) || typeof block.id !== "string") {
12117
+ continue;
12118
+ }
12119
+ state.toolUseToParent.set(block.id, parentToolUseId);
12120
+ if (block.name === "Task" && typeof block.input === "object" && block.input !== null && "subagent_type" in block.input && typeof block.input.subagent_type === "string") {
12121
+ state.pendingSubAgentNames.set(block.id, block.input.subagent_type);
12122
+ }
12123
+ }
12124
+ }
12125
+ async function maybeStartSubAgentSpan(state, message) {
12126
+ if (!("parent_tool_use_id" in message)) {
12127
+ return;
12128
+ }
12129
+ const parentToolUseId = message.parent_tool_use_id;
12130
+ if (!parentToolUseId) {
12131
+ return;
12132
+ }
12133
+ await ensureSubAgentSpan(
12134
+ state.pendingSubAgentNames,
12135
+ state.span,
12136
+ state.subAgentSpans,
12137
+ parentToolUseId
12138
+ );
12139
+ }
12140
+ async function ensureSubAgentSpan(pendingSubAgentNames, rootSpan, subAgentSpans, parentToolUseId) {
12141
+ const existingSpan = subAgentSpans.get(parentToolUseId);
12142
+ if (existingSpan) {
12143
+ return existingSpan;
12144
+ }
12145
+ const agentName = pendingSubAgentNames.get(parentToolUseId);
12146
+ const spanName = agentName ? `Agent: ${agentName}` : "Agent: sub-agent";
12147
+ const subAgentSpan = startSpan({
12148
+ event: {
12149
+ metadata: {
12150
+ ...agentName && { "claude_agent_sdk.agent_type": agentName }
12151
+ }
12152
+ },
12153
+ name: spanName,
12154
+ parent: await rootSpan.export(),
12155
+ spanAttributes: { type: "task" /* TASK */ }
12156
+ });
12157
+ subAgentSpans.set(parentToolUseId, subAgentSpan);
12158
+ return subAgentSpan;
12159
+ }
12160
+ async function handleStreamMessage(state, message) {
12161
+ maybeTrackToolUseContext(state, message);
12162
+ await maybeStartSubAgentSpan(state, message);
12163
+ const messageId = message.message?.id;
12164
+ if (messageId && messageId !== state.currentMessageId) {
12165
+ await finalizeCurrentMessageGroup(state);
12166
+ state.currentMessageId = messageId;
12167
+ state.currentMessageStartTime = getCurrentUnixTimestamp();
12168
+ }
12169
+ if (message.type === "assistant" && message.message?.usage) {
12170
+ state.currentMessages.push(message);
12171
+ }
12172
+ if (message.type !== "result" || !message.usage) {
12173
+ return;
12174
+ }
12175
+ const finalUsageMetrics = extractUsageFromMessage(message);
12176
+ if (state.currentMessages.length > 0 && finalUsageMetrics.completion_tokens !== void 0) {
12177
+ const lastMessage = state.currentMessages[state.currentMessages.length - 1];
12178
+ if (lastMessage?.message?.usage) {
12179
+ const adjustedTokens = finalUsageMetrics.completion_tokens - state.accumulatedOutputTokens;
12180
+ if (adjustedTokens >= 0) {
12181
+ lastMessage.message.usage.output_tokens = adjustedTokens;
12182
+ }
12183
+ const resultUsage = message.usage;
12184
+ if (resultUsage && typeof resultUsage === "object") {
12185
+ const cacheReadTokens = getNumberProperty(
12186
+ resultUsage,
12187
+ "cache_read_input_tokens"
12188
+ );
12189
+ if (cacheReadTokens !== void 0) {
12190
+ lastMessage.message.usage.cache_read_input_tokens = cacheReadTokens;
12191
+ }
12192
+ const cacheCreationTokens = getNumberProperty(
12193
+ resultUsage,
12194
+ "cache_creation_input_tokens"
12195
+ );
12196
+ if (cacheCreationTokens !== void 0) {
12197
+ lastMessage.message.usage.cache_creation_input_tokens = cacheCreationTokens;
12198
+ }
12199
+ }
12200
+ }
12201
+ }
12202
+ const metadata = {};
12203
+ if (message.num_turns !== void 0) {
12204
+ metadata.num_turns = message.num_turns;
12205
+ }
12206
+ if (message.session_id !== void 0) {
12207
+ metadata.session_id = message.session_id;
12208
+ }
12209
+ if (Object.keys(metadata).length > 0) {
12210
+ state.span.log({ metadata });
12211
+ }
12212
+ }
12213
+ async function finalizeQuerySpan(state) {
12214
+ try {
12215
+ await finalizeCurrentMessageGroup(state);
12216
+ state.span.log({
12217
+ output: state.finalResults.length > 0 ? state.finalResults[state.finalResults.length - 1] : void 0
12218
+ });
12219
+ if (state.capturedPromptMessages) {
12220
+ if (state.promptStarted()) {
12221
+ await state.promptDone;
12222
+ }
12223
+ if (state.capturedPromptMessages.length > 0) {
12224
+ state.span.log({
12225
+ input: formatCapturedMessages(state.capturedPromptMessages)
12226
+ });
12227
+ }
12228
+ }
12229
+ } finally {
12230
+ for (const [id, subAgentSpan] of state.subAgentSpans) {
12231
+ if (!state.endedSubAgentSpans.has(id)) {
12232
+ subAgentSpan.end();
12233
+ }
12234
+ }
12235
+ state.subAgentSpans.clear();
12236
+ state.span.end();
12237
+ }
12238
+ }
11654
12239
  var ClaudeAgentSDKPlugin = class extends BasePlugin {
11655
12240
  onEnable() {
11656
12241
  this.subscribeToQuery();
@@ -11661,19 +12246,36 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
11661
12246
  }
11662
12247
  this.unsubscribers = [];
11663
12248
  }
11664
- /**
11665
- * Subscribe to the query channel for agent interactions.
11666
- * Handles streaming responses and traces both the top-level agent task
11667
- * and individual LLM calls.
11668
- */
11669
12249
  subscribeToQuery() {
11670
12250
  const channel2 = claudeAgentSDKChannels.query.tracingChannel();
11671
12251
  const spans = /* @__PURE__ */ new WeakMap();
11672
12252
  const handlers = {
11673
12253
  start: (event) => {
11674
- const params = event.arguments[0];
11675
- const prompt = params?.prompt;
11676
- const options = params?.options ?? {};
12254
+ const params = event.arguments[0] ?? {};
12255
+ const originalPrompt = params.prompt;
12256
+ const options = params.options ?? {};
12257
+ const promptIsAsyncIterable = isAsyncIterable(originalPrompt);
12258
+ let promptStarted = false;
12259
+ let capturedPromptMessages;
12260
+ let resolvePromptDone;
12261
+ const promptDone = new Promise((resolve) => {
12262
+ resolvePromptDone = resolve;
12263
+ });
12264
+ if (promptIsAsyncIterable) {
12265
+ capturedPromptMessages = [];
12266
+ const promptStream = originalPrompt;
12267
+ params.prompt = (async function* () {
12268
+ promptStarted = true;
12269
+ try {
12270
+ for await (const message of promptStream) {
12271
+ capturedPromptMessages.push(message);
12272
+ yield message;
12273
+ }
12274
+ } finally {
12275
+ resolvePromptDone?.();
12276
+ }
12277
+ })();
12278
+ }
11677
12279
  const span = startSpan({
11678
12280
  name: "Claude Agent",
11679
12281
  spanAttributes: {
@@ -11683,163 +12285,115 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
11683
12285
  const startTime = getCurrentUnixTimestamp();
11684
12286
  try {
11685
12287
  span.log({
11686
- input: typeof prompt === "string" ? prompt : {
11687
- type: "streaming",
11688
- description: "AsyncIterable<ClaudeAgentSDKMessage>"
11689
- },
12288
+ input: typeof originalPrompt === "string" ? originalPrompt : promptIsAsyncIterable ? void 0 : originalPrompt !== void 0 ? String(originalPrompt) : void 0,
11690
12289
  metadata: filterSerializableOptions(options)
11691
12290
  });
11692
12291
  } catch (error) {
11693
12292
  console.error("Error extracting input for Claude Agent SDK:", error);
11694
12293
  }
12294
+ const activeToolSpans = /* @__PURE__ */ new Map();
12295
+ const subAgentSpans = /* @__PURE__ */ new Map();
12296
+ const endedSubAgentSpans = /* @__PURE__ */ new Set();
12297
+ const toolUseToParent = /* @__PURE__ */ new Map();
12298
+ const pendingSubAgentNames = /* @__PURE__ */ new Map();
12299
+ const optionsWithHooks = injectTracingHooks(
12300
+ options,
12301
+ async (toolUseID) => {
12302
+ const parentToolUseId = toolUseToParent.get(toolUseID);
12303
+ if (parentToolUseId) {
12304
+ const subAgentSpan = await ensureSubAgentSpan(
12305
+ pendingSubAgentNames,
12306
+ span,
12307
+ subAgentSpans,
12308
+ parentToolUseId
12309
+ );
12310
+ return subAgentSpan.export();
12311
+ }
12312
+ return span.export();
12313
+ },
12314
+ activeToolSpans,
12315
+ subAgentSpans,
12316
+ endedSubAgentSpans
12317
+ );
12318
+ params.options = optionsWithHooks;
12319
+ event.arguments[0] = params;
11695
12320
  spans.set(event, {
11696
- span,
11697
- startTime,
11698
- conversationHistory: [],
11699
- currentMessages: [],
12321
+ accumulatedOutputTokens: 0,
12322
+ activeToolSpans,
12323
+ capturedPromptMessages,
11700
12324
  currentMessageId: void 0,
11701
12325
  currentMessageStartTime: startTime,
11702
- accumulatedOutputTokens: 0
12326
+ currentMessages: [],
12327
+ endedSubAgentSpans,
12328
+ finalResults: [],
12329
+ options: optionsWithHooks,
12330
+ originalPrompt,
12331
+ pendingSubAgentNames,
12332
+ processing: Promise.resolve(),
12333
+ promptDone,
12334
+ promptStarted: () => promptStarted,
12335
+ span,
12336
+ subAgentSpans,
12337
+ toolUseToParent
11703
12338
  });
11704
12339
  },
11705
- asyncEnd: (event) => {
11706
- const spanData = spans.get(event);
11707
- if (!spanData) {
12340
+ end: (event) => {
12341
+ const state = spans.get(event);
12342
+ if (!state) {
11708
12343
  return;
11709
12344
  }
11710
12345
  const eventResult = event.result;
11711
12346
  if (eventResult === void 0) {
11712
- spanData.span.end();
12347
+ state.span.end();
11713
12348
  spans.delete(event);
11714
12349
  return;
11715
12350
  }
11716
12351
  if (isAsyncIterable(eventResult)) {
11717
12352
  patchStreamIfNeeded(eventResult, {
11718
- onChunk: async (message) => {
11719
- const currentTime = getCurrentUnixTimestamp();
11720
- const params = event.arguments[0];
11721
- const prompt = params?.prompt;
11722
- const options = params?.options ?? {};
11723
- const messageId = message.message?.id;
11724
- if (messageId && messageId !== spanData.currentMessageId) {
11725
- if (spanData.currentMessages.length > 0) {
11726
- const finalMessage = await createLLMSpanForMessages(
11727
- spanData.currentMessages,
11728
- prompt,
11729
- spanData.conversationHistory,
11730
- options,
11731
- spanData.currentMessageStartTime,
11732
- await spanData.span.export()
11733
- );
11734
- if (finalMessage) {
11735
- spanData.conversationHistory.push(finalMessage);
11736
- }
11737
- const lastMessage = spanData.currentMessages[spanData.currentMessages.length - 1];
11738
- if (lastMessage?.message?.usage) {
11739
- const outputTokens = getNumberProperty(
11740
- lastMessage.message.usage,
11741
- "output_tokens"
11742
- ) || 0;
11743
- spanData.accumulatedOutputTokens += outputTokens;
11744
- }
11745
- spanData.currentMessages = [];
11746
- }
11747
- spanData.currentMessageId = messageId;
11748
- spanData.currentMessageStartTime = currentTime;
11749
- }
11750
- if (message.type === "assistant" && message.message?.usage) {
11751
- spanData.currentMessages.push(message);
11752
- }
11753
- if (message.type === "result" && message.usage) {
11754
- const finalUsageMetrics = extractUsageFromMessage(message);
11755
- if (spanData.currentMessages.length > 0 && finalUsageMetrics.completion_tokens !== void 0) {
11756
- const lastMessage = spanData.currentMessages[spanData.currentMessages.length - 1];
11757
- if (lastMessage?.message?.usage) {
11758
- const adjustedTokens = finalUsageMetrics.completion_tokens - spanData.accumulatedOutputTokens;
11759
- if (adjustedTokens >= 0) {
11760
- lastMessage.message.usage.output_tokens = adjustedTokens;
11761
- }
11762
- }
11763
- }
11764
- const result_metadata = {};
11765
- if (message.num_turns !== void 0) {
11766
- result_metadata.num_turns = message.num_turns;
11767
- }
11768
- if (message.session_id !== void 0) {
11769
- result_metadata.session_id = message.session_id;
11770
- }
11771
- if (Object.keys(result_metadata).length > 0) {
11772
- spanData.span.log({
11773
- metadata: result_metadata
11774
- });
11775
- }
11776
- }
11777
- },
11778
- onComplete: async () => {
11779
- try {
11780
- const params = event.arguments[0];
11781
- const prompt = params?.prompt;
11782
- const options = params?.options ?? {};
11783
- if (spanData.currentMessages.length > 0) {
11784
- const finalMessage = await createLLMSpanForMessages(
11785
- spanData.currentMessages,
11786
- prompt,
11787
- spanData.conversationHistory,
11788
- options,
11789
- spanData.currentMessageStartTime,
11790
- await spanData.span.export()
11791
- );
11792
- if (finalMessage) {
11793
- spanData.conversationHistory.push(finalMessage);
11794
- }
11795
- }
11796
- spanData.span.log({
11797
- output: spanData.conversationHistory.length > 0 ? spanData.conversationHistory[spanData.conversationHistory.length - 1] : void 0
11798
- });
11799
- } catch (error) {
12353
+ onChunk: (message) => {
12354
+ maybeTrackToolUseContext(state, message);
12355
+ state.processing = state.processing.then(() => handleStreamMessage(state, message)).catch((error) => {
11800
12356
  console.error(
11801
- "Error extracting output for Claude Agent SDK:",
12357
+ "Error processing Claude Agent SDK stream chunk:",
11802
12358
  error
11803
12359
  );
11804
- } finally {
11805
- spanData.span.end();
12360
+ });
12361
+ },
12362
+ onComplete: () => {
12363
+ void state.processing.then(() => finalizeQuerySpan(state)).finally(() => {
11806
12364
  spans.delete(event);
11807
- }
12365
+ });
11808
12366
  },
11809
12367
  onError: (error) => {
11810
- spanData.span.log({
11811
- error: error.message
12368
+ void state.processing.then(() => {
12369
+ state.span.log({
12370
+ error: error.message
12371
+ });
12372
+ }).then(() => finalizeQuerySpan(state)).finally(() => {
12373
+ spans.delete(event);
11812
12374
  });
11813
- spanData.span.end();
11814
- spans.delete(event);
11815
12375
  }
11816
12376
  });
11817
- } else {
11818
- try {
11819
- spanData.span.log({
11820
- output: eventResult
11821
- });
11822
- } catch (error) {
11823
- console.error(
11824
- "Error extracting output for Claude Agent SDK:",
11825
- error
11826
- );
11827
- } finally {
11828
- spanData.span.end();
11829
- spans.delete(event);
11830
- }
12377
+ return;
12378
+ }
12379
+ try {
12380
+ state.span.log({ output: eventResult });
12381
+ } catch (error) {
12382
+ console.error("Error extracting output for Claude Agent SDK:", error);
12383
+ } finally {
12384
+ state.span.end();
12385
+ spans.delete(event);
11831
12386
  }
11832
12387
  },
11833
12388
  error: (event) => {
11834
- const spanData = spans.get(event);
11835
- if (!spanData || !event.error) {
12389
+ const state = spans.get(event);
12390
+ if (!state || !event.error) {
11836
12391
  return;
11837
12392
  }
11838
- const { span } = spanData;
11839
- span.log({
12393
+ state.span.log({
11840
12394
  error: event.error.message
11841
12395
  });
11842
- span.end();
12396
+ state.span.end();
11843
12397
  spans.delete(event);
11844
12398
  }
11845
12399
  };
@@ -11863,6 +12417,18 @@ var googleGenAIChannels = defineChannels("@google/genai", {
11863
12417
  });
11864
12418
 
11865
12419
  // src/instrumentation/plugins/google-genai-plugin.ts
12420
+ var GOOGLE_GENAI_INTERNAL_CONTEXT = {
12421
+ caller_filename: "<node-internal>",
12422
+ caller_functionname: "<node-internal>",
12423
+ caller_lineno: 0
12424
+ };
12425
+ function createWrapperParityEvent(args) {
12426
+ return {
12427
+ context: GOOGLE_GENAI_INTERNAL_CONTEXT,
12428
+ input: args.input,
12429
+ metadata: args.metadata
12430
+ };
12431
+ }
11866
12432
  var GoogleGenAIPlugin = class extends BasePlugin {
11867
12433
  onEnable() {
11868
12434
  this.subscribeToGoogleGenAIChannels();
@@ -11871,51 +12437,282 @@ var GoogleGenAIPlugin = class extends BasePlugin {
11871
12437
  this.unsubscribers = unsubscribeAll(this.unsubscribers);
11872
12438
  }
11873
12439
  subscribeToGoogleGenAIChannels() {
11874
- this.unsubscribers.push(
11875
- traceAsyncChannel(googleGenAIChannels.generateContent, {
11876
- name: "google-genai.generateContent",
11877
- type: "llm" /* LLM */,
11878
- extractInput: ([params]) => {
11879
- const input = serializeInput(params);
11880
- const metadata = extractMetadata(params);
11881
- return {
11882
- input,
11883
- metadata: { ...metadata, provider: "google-genai" }
11884
- };
11885
- },
11886
- extractOutput: (result) => {
11887
- return result;
11888
- },
11889
- extractMetrics: (result, startTime) => {
11890
- return extractGenerateContentMetrics(result, startTime);
11891
- }
11892
- })
12440
+ this.subscribeToGenerateContentChannel();
12441
+ this.subscribeToGenerateContentStreamChannel();
12442
+ }
12443
+ subscribeToGenerateContentChannel() {
12444
+ const tracingChannel2 = googleGenAIChannels.generateContent.tracingChannel();
12445
+ const states = /* @__PURE__ */ new WeakMap();
12446
+ const unbindCurrentSpanStore = bindCurrentSpanStoreToStart2(
12447
+ tracingChannel2,
12448
+ states,
12449
+ (event) => {
12450
+ const params = event.arguments[0];
12451
+ const input = serializeInput(params);
12452
+ const metadata = extractMetadata(params);
12453
+ const span = startSpan({
12454
+ name: "generate_content",
12455
+ spanAttributes: {
12456
+ type: "llm" /* LLM */
12457
+ },
12458
+ event: createWrapperParityEvent({ input, metadata })
12459
+ });
12460
+ return {
12461
+ span,
12462
+ startTime: getCurrentUnixTimestamp()
12463
+ };
12464
+ }
11893
12465
  );
11894
- this.unsubscribers.push(
11895
- traceStreamingChannel(googleGenAIChannels.generateContentStream, {
11896
- name: "google-genai.generateContentStream",
11897
- type: "llm" /* LLM */,
11898
- extractInput: ([params]) => {
12466
+ const handlers = {
12467
+ start: (event) => {
12468
+ ensureSpanState(states, event, () => {
12469
+ const params = event.arguments[0];
11899
12470
  const input = serializeInput(params);
11900
12471
  const metadata = extractMetadata(params);
12472
+ const span = startSpan({
12473
+ name: "generate_content",
12474
+ spanAttributes: {
12475
+ type: "llm" /* LLM */
12476
+ },
12477
+ event: createWrapperParityEvent({ input, metadata })
12478
+ });
11901
12479
  return {
11902
- input,
11903
- metadata: { ...metadata, provider: "google-genai" }
12480
+ span,
12481
+ startTime: getCurrentUnixTimestamp()
11904
12482
  };
11905
- },
11906
- extractOutput: (result) => {
11907
- return result;
11908
- },
11909
- extractMetrics: () => {
11910
- return {};
11911
- },
11912
- aggregateChunks: (chunks, _result, _endEvent, startTime) => {
11913
- return aggregateGenerateContentChunks(chunks, startTime);
12483
+ });
12484
+ },
12485
+ asyncEnd: (event) => {
12486
+ const spanState = states.get(event);
12487
+ if (!spanState) {
12488
+ return;
11914
12489
  }
11915
- })
11916
- );
12490
+ try {
12491
+ spanState.span.log({
12492
+ metrics: cleanMetrics(
12493
+ extractGenerateContentMetrics(
12494
+ event.result,
12495
+ spanState.startTime
12496
+ )
12497
+ ),
12498
+ output: event.result
12499
+ });
12500
+ } finally {
12501
+ spanState.span.end();
12502
+ states.delete(event);
12503
+ }
12504
+ },
12505
+ error: (event) => {
12506
+ logErrorAndEndSpan(states, event);
12507
+ }
12508
+ };
12509
+ tracingChannel2.subscribe(handlers);
12510
+ this.unsubscribers.push(() => {
12511
+ unbindCurrentSpanStore?.();
12512
+ tracingChannel2.unsubscribe(handlers);
12513
+ });
12514
+ }
12515
+ subscribeToGenerateContentStreamChannel() {
12516
+ const tracingChannel2 = googleGenAIChannels.generateContentStream.tracingChannel();
12517
+ const handlers = {
12518
+ start: (event) => {
12519
+ const streamEvent = event;
12520
+ const params = event.arguments[0];
12521
+ streamEvent.googleGenAIInput = serializeInput(params);
12522
+ streamEvent.googleGenAIMetadata = extractMetadata(params);
12523
+ },
12524
+ asyncEnd: (event) => {
12525
+ const streamEvent = event;
12526
+ patchGoogleGenAIStreamingResult({
12527
+ input: streamEvent.googleGenAIInput,
12528
+ metadata: streamEvent.googleGenAIMetadata,
12529
+ result: streamEvent.result
12530
+ });
12531
+ },
12532
+ error: () => {
12533
+ }
12534
+ };
12535
+ tracingChannel2.subscribe(handlers);
12536
+ this.unsubscribers.push(() => {
12537
+ tracingChannel2.unsubscribe(handlers);
12538
+ });
11917
12539
  }
11918
12540
  };
12541
+ function ensureSpanState(states, event, create) {
12542
+ const existing = states.get(event);
12543
+ if (existing) {
12544
+ return existing;
12545
+ }
12546
+ const created = create();
12547
+ states.set(event, created);
12548
+ return created;
12549
+ }
12550
+ function bindCurrentSpanStoreToStart2(tracingChannel2, states, create) {
12551
+ const state = _internalGetGlobalState();
12552
+ const startChannel = tracingChannel2.start;
12553
+ const currentSpanStore = state?.contextManager ? state.contextManager[BRAINTRUST_CURRENT_SPAN_STORE] : void 0;
12554
+ if (!startChannel?.bindStore || !currentSpanStore) {
12555
+ return void 0;
12556
+ }
12557
+ startChannel.bindStore(
12558
+ currentSpanStore,
12559
+ (event) => ensureSpanState(
12560
+ states,
12561
+ event,
12562
+ () => create(event)
12563
+ ).span
12564
+ );
12565
+ return () => {
12566
+ startChannel.unbindStore?.(currentSpanStore);
12567
+ };
12568
+ }
12569
+ function logErrorAndEndSpan(states, event) {
12570
+ const spanState = states.get(event);
12571
+ if (!spanState) {
12572
+ return;
12573
+ }
12574
+ spanState.span.log({
12575
+ error: event.error.message
12576
+ });
12577
+ spanState.span.end();
12578
+ states.delete(event);
12579
+ }
12580
+ function patchGoogleGenAIStreamingResult(args) {
12581
+ const { input, metadata, result } = args;
12582
+ if (!input || !metadata || !result || typeof result !== "object" || typeof result.next !== "function") {
12583
+ return false;
12584
+ }
12585
+ const chunks = [];
12586
+ let firstTokenTime = null;
12587
+ let finalized = false;
12588
+ let span = null;
12589
+ let startTime = null;
12590
+ const ensureSpan = () => {
12591
+ if (!span) {
12592
+ span = startSpan({
12593
+ name: "generate_content_stream",
12594
+ spanAttributes: {
12595
+ type: "llm" /* LLM */
12596
+ },
12597
+ event: {
12598
+ input,
12599
+ metadata
12600
+ }
12601
+ });
12602
+ startTime = getCurrentUnixTimestamp();
12603
+ }
12604
+ return span;
12605
+ };
12606
+ const finalize = (options) => {
12607
+ if (finalized || !span) {
12608
+ return;
12609
+ }
12610
+ finalized = true;
12611
+ if (options.result) {
12612
+ const { end, ...metricsWithoutEnd } = options.result.metrics;
12613
+ span.log({
12614
+ metrics: cleanMetrics(metricsWithoutEnd),
12615
+ output: options.result.aggregated
12616
+ });
12617
+ span.end(typeof end === "number" ? { endTime: end } : void 0);
12618
+ return;
12619
+ }
12620
+ if (options.error !== void 0) {
12621
+ span.log({
12622
+ error: options.error instanceof Error ? options.error.message : String(options.error)
12623
+ });
12624
+ }
12625
+ span.end();
12626
+ };
12627
+ const patchIterator = (iterator) => {
12628
+ if (typeof iterator !== "object" || iterator === null || "__braintrustGoogleGenAIPatched" in iterator) {
12629
+ return iterator;
12630
+ }
12631
+ const iteratorRecord = iterator;
12632
+ const originalNext = typeof iteratorRecord.next === "function" ? iteratorRecord.next.bind(iterator) : void 0;
12633
+ const originalReturn = typeof iteratorRecord.return === "function" ? iteratorRecord.return.bind(iterator) : void 0;
12634
+ const originalThrow = typeof iteratorRecord.throw === "function" ? iteratorRecord.throw.bind(iterator) : void 0;
12635
+ const asyncIteratorMethod = iteratorRecord[Symbol.asyncIterator];
12636
+ const originalAsyncIterator = typeof asyncIteratorMethod === "function" ? asyncIteratorMethod.bind(iterator) : void 0;
12637
+ Object.defineProperty(iteratorRecord, "__braintrustGoogleGenAIPatched", {
12638
+ configurable: true,
12639
+ enumerable: false,
12640
+ value: true,
12641
+ writable: false
12642
+ });
12643
+ if (originalNext) {
12644
+ iteratorRecord.next = async (...nextArgs) => {
12645
+ ensureSpan();
12646
+ try {
12647
+ const nextResult = await originalNext(
12648
+ ...nextArgs
12649
+ );
12650
+ if (!nextResult.done && nextResult.value) {
12651
+ if (firstTokenTime === null) {
12652
+ firstTokenTime = getCurrentUnixTimestamp();
12653
+ }
12654
+ chunks.push(nextResult.value);
12655
+ }
12656
+ if (nextResult.done && startTime !== null) {
12657
+ finalize({
12658
+ result: aggregateGenerateContentChunks(
12659
+ chunks,
12660
+ startTime,
12661
+ firstTokenTime
12662
+ )
12663
+ });
12664
+ }
12665
+ return nextResult;
12666
+ } catch (error) {
12667
+ finalize({ error });
12668
+ throw error;
12669
+ }
12670
+ };
12671
+ }
12672
+ if (originalReturn) {
12673
+ iteratorRecord.return = async (...returnArgs) => {
12674
+ ensureSpan();
12675
+ try {
12676
+ return await originalReturn(
12677
+ ...returnArgs
12678
+ );
12679
+ } finally {
12680
+ if (startTime !== null) {
12681
+ finalize({
12682
+ result: chunks.length > 0 ? aggregateGenerateContentChunks(
12683
+ chunks,
12684
+ startTime,
12685
+ firstTokenTime
12686
+ ) : void 0
12687
+ });
12688
+ } else {
12689
+ finalize({});
12690
+ }
12691
+ }
12692
+ };
12693
+ }
12694
+ if (originalThrow) {
12695
+ iteratorRecord.throw = async (...throwArgs) => {
12696
+ ensureSpan();
12697
+ try {
12698
+ return await originalThrow(
12699
+ ...throwArgs
12700
+ );
12701
+ } catch (error) {
12702
+ finalize({ error });
12703
+ throw error;
12704
+ }
12705
+ };
12706
+ }
12707
+ iteratorRecord[Symbol.asyncIterator] = () => {
12708
+ const asyncIterator = originalAsyncIterator ? originalAsyncIterator() : iterator;
12709
+ return patchIterator(asyncIterator);
12710
+ };
12711
+ return iterator;
12712
+ };
12713
+ patchIterator(result);
12714
+ return true;
12715
+ }
11919
12716
  function serializeInput(params) {
11920
12717
  const input = {
11921
12718
  model: params.model,
@@ -11924,11 +12721,13 @@ function serializeInput(params) {
11924
12721
  if (params.config) {
11925
12722
  const config = tryToDict(params.config);
11926
12723
  if (config) {
11927
- const tools = serializeTools(params);
11928
- if (tools) {
11929
- config.tools = tools;
11930
- }
11931
- input.config = config;
12724
+ const filteredConfig = {};
12725
+ Object.keys(config).forEach((key) => {
12726
+ if (key !== "tools") {
12727
+ filteredConfig[key] = config[key];
12728
+ }
12729
+ });
12730
+ input.config = filteredConfig;
11932
12731
  }
11933
12732
  }
11934
12733
  return input;
@@ -12015,12 +12814,18 @@ function extractMetadata(params) {
12015
12814
  });
12016
12815
  }
12017
12816
  }
12817
+ const tools = serializeTools(params);
12818
+ if (tools) {
12819
+ metadata.tools = tools;
12820
+ }
12018
12821
  return metadata;
12019
12822
  }
12020
12823
  function extractGenerateContentMetrics(response, startTime) {
12021
12824
  const metrics = {};
12022
- if (startTime) {
12825
+ if (startTime !== void 0) {
12023
12826
  const end = getCurrentUnixTimestamp();
12827
+ metrics.start = startTime;
12828
+ metrics.end = end;
12024
12829
  metrics.duration = end - startTime;
12025
12830
  }
12026
12831
  if (response?.usageMetadata) {
@@ -12032,116 +12837,1141 @@ function populateUsageMetrics(metrics, usage) {
12032
12837
  if (usage.promptTokenCount !== void 0) {
12033
12838
  metrics.prompt_tokens = usage.promptTokenCount;
12034
12839
  }
12035
- if (usage.candidatesTokenCount !== void 0) {
12036
- metrics.completion_tokens = usage.candidatesTokenCount;
12840
+ if (usage.candidatesTokenCount !== void 0) {
12841
+ metrics.completion_tokens = usage.candidatesTokenCount;
12842
+ }
12843
+ if (usage.totalTokenCount !== void 0) {
12844
+ metrics.tokens = usage.totalTokenCount;
12845
+ }
12846
+ if (usage.cachedContentTokenCount !== void 0) {
12847
+ metrics.prompt_cached_tokens = usage.cachedContentTokenCount;
12848
+ }
12849
+ if (usage.thoughtsTokenCount !== void 0) {
12850
+ metrics.completion_reasoning_tokens = usage.thoughtsTokenCount;
12851
+ }
12852
+ }
12853
+ function aggregateGenerateContentChunks(chunks, startTime, firstTokenTime) {
12854
+ const end = getCurrentUnixTimestamp();
12855
+ const metrics = {
12856
+ start: startTime,
12857
+ end,
12858
+ duration: end - startTime
12859
+ };
12860
+ if (firstTokenTime !== null) {
12861
+ metrics.time_to_first_token = firstTokenTime - startTime;
12862
+ }
12863
+ if (chunks.length === 0) {
12864
+ return { aggregated: {}, metrics };
12865
+ }
12866
+ let text = "";
12867
+ let thoughtText = "";
12868
+ const otherParts = [];
12869
+ let usageMetadata = null;
12870
+ let lastResponse = null;
12871
+ for (const chunk of chunks) {
12872
+ lastResponse = chunk;
12873
+ if (chunk.usageMetadata) {
12874
+ usageMetadata = chunk.usageMetadata;
12875
+ }
12876
+ if (chunk.candidates && Array.isArray(chunk.candidates)) {
12877
+ for (const candidate of chunk.candidates) {
12878
+ if (candidate.content?.parts) {
12879
+ for (const part of candidate.content.parts) {
12880
+ if (part.text !== void 0) {
12881
+ if (part.thought) {
12882
+ thoughtText += part.text;
12883
+ } else {
12884
+ text += part.text;
12885
+ }
12886
+ } else if (part.functionCall) {
12887
+ otherParts.push({ functionCall: part.functionCall });
12888
+ } else if (part.codeExecutionResult) {
12889
+ otherParts.push({
12890
+ codeExecutionResult: part.codeExecutionResult
12891
+ });
12892
+ } else if (part.executableCode) {
12893
+ otherParts.push({ executableCode: part.executableCode });
12894
+ }
12895
+ }
12896
+ }
12897
+ }
12898
+ }
12899
+ }
12900
+ const aggregated = {};
12901
+ const parts = [];
12902
+ if (thoughtText) {
12903
+ parts.push({ text: thoughtText, thought: true });
12904
+ }
12905
+ if (text) {
12906
+ parts.push({ text });
12907
+ }
12908
+ parts.push(...otherParts);
12909
+ if (parts.length > 0 && lastResponse?.candidates) {
12910
+ const candidates = [];
12911
+ for (const candidate of lastResponse.candidates) {
12912
+ const candidateDict = {
12913
+ content: {
12914
+ parts,
12915
+ role: "model"
12916
+ }
12917
+ };
12918
+ if (candidate.finishReason !== void 0) {
12919
+ candidateDict.finishReason = candidate.finishReason;
12920
+ }
12921
+ if (candidate.safetyRatings) {
12922
+ candidateDict.safetyRatings = candidate.safetyRatings;
12923
+ }
12924
+ candidates.push(candidateDict);
12925
+ }
12926
+ aggregated.candidates = candidates;
12927
+ }
12928
+ if (usageMetadata) {
12929
+ aggregated.usageMetadata = usageMetadata;
12930
+ populateUsageMetrics(metrics, usageMetadata);
12931
+ }
12932
+ if (text) {
12933
+ aggregated.text = text;
12934
+ }
12935
+ return { aggregated, metrics };
12936
+ }
12937
+ function cleanMetrics(metrics) {
12938
+ const cleaned = {};
12939
+ for (const [key, value] of Object.entries(metrics)) {
12940
+ if (value !== null && value !== void 0) {
12941
+ cleaned[key] = value;
12942
+ }
12943
+ }
12944
+ return cleaned;
12945
+ }
12946
+ function tryToDict(obj) {
12947
+ if (obj === null || obj === void 0) {
12948
+ return null;
12949
+ }
12950
+ if (typeof obj === "object") {
12951
+ if ("toJSON" in obj && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12952
+ typeof obj.toJSON === "function") {
12953
+ return obj.toJSON();
12954
+ }
12955
+ return obj;
12956
+ }
12957
+ return null;
12958
+ }
12959
+
12960
+ // src/instrumentation/plugins/openrouter-channels.ts
12961
+ var openRouterChannels = defineChannels("@openrouter/sdk", {
12962
+ chatSend: channel({
12963
+ channelName: "chat.send",
12964
+ kind: "async"
12965
+ }),
12966
+ embeddingsGenerate: channel({
12967
+ channelName: "embeddings.generate",
12968
+ kind: "async"
12969
+ }),
12970
+ betaResponsesSend: channel({
12971
+ channelName: "beta.responses.send",
12972
+ kind: "async"
12973
+ }),
12974
+ callModel: channel({
12975
+ channelName: "callModel",
12976
+ kind: "sync-stream"
12977
+ }),
12978
+ toolExecute: channel({
12979
+ channelName: "tool.execute",
12980
+ kind: "async"
12981
+ })
12982
+ });
12983
+
12984
+ // src/openrouter-utils.ts
12985
+ var TOKEN_NAME_MAP2 = {
12986
+ promptTokens: "prompt_tokens",
12987
+ inputTokens: "prompt_tokens",
12988
+ completionTokens: "completion_tokens",
12989
+ outputTokens: "completion_tokens",
12990
+ totalTokens: "tokens",
12991
+ prompt_tokens: "prompt_tokens",
12992
+ input_tokens: "prompt_tokens",
12993
+ completion_tokens: "completion_tokens",
12994
+ output_tokens: "completion_tokens",
12995
+ total_tokens: "tokens"
12996
+ };
12997
+ var TOKEN_DETAIL_PREFIX_MAP = {
12998
+ promptTokensDetails: "prompt",
12999
+ inputTokensDetails: "prompt",
13000
+ completionTokensDetails: "completion",
13001
+ outputTokensDetails: "completion",
13002
+ costDetails: "cost",
13003
+ prompt_tokens_details: "prompt",
13004
+ input_tokens_details: "prompt",
13005
+ completion_tokens_details: "completion",
13006
+ output_tokens_details: "completion",
13007
+ cost_details: "cost"
13008
+ };
13009
+ function camelToSnake(value) {
13010
+ return value.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
13011
+ }
13012
+ function parseOpenRouterMetricsFromUsage(usage) {
13013
+ if (!isObject(usage)) {
13014
+ return {};
13015
+ }
13016
+ const metrics = {};
13017
+ for (const [name, value] of Object.entries(usage)) {
13018
+ if (typeof value === "number") {
13019
+ metrics[TOKEN_NAME_MAP2[name] || camelToSnake(name)] = value;
13020
+ continue;
13021
+ }
13022
+ if (!isObject(value)) {
13023
+ continue;
13024
+ }
13025
+ const prefix = TOKEN_DETAIL_PREFIX_MAP[name];
13026
+ if (!prefix) {
13027
+ continue;
13028
+ }
13029
+ for (const [nestedName, nestedValue] of Object.entries(value)) {
13030
+ if (typeof nestedValue !== "number") {
13031
+ continue;
13032
+ }
13033
+ metrics[`${prefix}_${camelToSnake(nestedName)}`] = nestedValue;
13034
+ }
13035
+ }
13036
+ return metrics;
13037
+ }
13038
+ function extractOpenRouterUsageMetadata(usage) {
13039
+ if (!isObject(usage)) {
13040
+ return void 0;
13041
+ }
13042
+ const metadata = {};
13043
+ if (typeof usage.isByok === "boolean") {
13044
+ metadata.is_byok = usage.isByok;
13045
+ } else if (typeof usage.is_byok === "boolean") {
13046
+ metadata.is_byok = usage.is_byok;
13047
+ }
13048
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
13049
+ }
13050
+
13051
+ // src/openrouter-logging.ts
13052
+ var OMITTED_OPENROUTER_KEYS = /* @__PURE__ */ new Set([
13053
+ "execute",
13054
+ "render",
13055
+ "nextTurnParams",
13056
+ "requireApproval"
13057
+ ]);
13058
+ function parseOpenRouterModelString(model) {
13059
+ if (typeof model !== "string") {
13060
+ return { model };
13061
+ }
13062
+ const slashIndex = model.indexOf("/");
13063
+ if (slashIndex > 0 && slashIndex < model.length - 1) {
13064
+ return {
13065
+ provider: model.substring(0, slashIndex),
13066
+ model: model.substring(slashIndex + 1)
13067
+ };
13068
+ }
13069
+ return { model };
13070
+ }
13071
+ function isZodSchema2(value) {
13072
+ return value != null && typeof value === "object" && "_def" in value && typeof value._def === "object";
13073
+ }
13074
+ function serializeZodSchema2(schema) {
13075
+ try {
13076
+ return zodToJsonSchema(schema);
13077
+ } catch {
13078
+ return {
13079
+ type: "object",
13080
+ description: "Zod schema (conversion failed)"
13081
+ };
13082
+ }
13083
+ }
13084
+ function serializeOpenRouterTool(tool) {
13085
+ if (!isObject(tool)) {
13086
+ return tool;
13087
+ }
13088
+ const serialized = {};
13089
+ for (const [key, value] of Object.entries(tool)) {
13090
+ if (OMITTED_OPENROUTER_KEYS.has(key)) {
13091
+ continue;
13092
+ }
13093
+ if (key === "function" && isObject(value)) {
13094
+ serialized.function = sanitizeOpenRouterLoggedValue(value);
13095
+ continue;
13096
+ }
13097
+ serialized[key] = sanitizeOpenRouterLoggedValue(value);
13098
+ }
13099
+ return serialized;
13100
+ }
13101
+ function serializeOpenRouterToolsForLogging(tools) {
13102
+ if (!Array.isArray(tools)) {
13103
+ return void 0;
13104
+ }
13105
+ return tools.map((tool) => serializeOpenRouterTool(tool));
13106
+ }
13107
+ function sanitizeOpenRouterLoggedValue(value) {
13108
+ if (isZodSchema2(value)) {
13109
+ return serializeZodSchema2(value);
13110
+ }
13111
+ if (typeof value === "function") {
13112
+ return "[Function]";
13113
+ }
13114
+ if (Array.isArray(value)) {
13115
+ return value.map((entry) => sanitizeOpenRouterLoggedValue(entry));
13116
+ }
13117
+ if (!isObject(value)) {
13118
+ return value;
13119
+ }
13120
+ const sanitized = {};
13121
+ for (const [key, entry] of Object.entries(value)) {
13122
+ if (OMITTED_OPENROUTER_KEYS.has(key)) {
13123
+ continue;
13124
+ }
13125
+ if (key === "tools" && Array.isArray(entry)) {
13126
+ sanitized.tools = serializeOpenRouterToolsForLogging(entry);
13127
+ continue;
13128
+ }
13129
+ sanitized[key] = sanitizeOpenRouterLoggedValue(entry);
13130
+ }
13131
+ return sanitized;
13132
+ }
13133
+ function buildOpenRouterMetadata(metadata, httpReferer, xTitle) {
13134
+ const sanitized = sanitizeOpenRouterLoggedValue(metadata);
13135
+ const metadataRecord = isObject(sanitized) ? sanitized : {};
13136
+ const { model, provider: providerRouting, ...rest } = metadataRecord;
13137
+ const normalizedModel = parseOpenRouterModelString(model);
13138
+ return {
13139
+ ...rest,
13140
+ ...normalizedModel.model !== void 0 ? { model: normalizedModel.model } : {},
13141
+ ...providerRouting !== void 0 ? { providerRouting } : {},
13142
+ ...httpReferer !== void 0 ? { httpReferer } : {},
13143
+ ...xTitle !== void 0 ? { xTitle } : {},
13144
+ provider: normalizedModel.provider || "openrouter"
13145
+ };
13146
+ }
13147
+ function buildOpenRouterEmbeddingMetadata(metadata, httpReferer, xTitle) {
13148
+ const normalized = buildOpenRouterMetadata(metadata, httpReferer, xTitle);
13149
+ return typeof normalized.model === "string" ? {
13150
+ ...normalized,
13151
+ embedding_model: normalized.model
13152
+ } : normalized;
13153
+ }
13154
+ function extractOpenRouterCallModelInput(request) {
13155
+ return isObject(request) && "input" in request ? sanitizeOpenRouterLoggedValue(request.input) : void 0;
13156
+ }
13157
+ function extractOpenRouterCallModelMetadata(request) {
13158
+ if (!isObject(request)) {
13159
+ return { provider: "openrouter" };
13160
+ }
13161
+ const { input: _input, ...metadata } = request;
13162
+ return buildOpenRouterMetadata(metadata, void 0, void 0);
13163
+ }
13164
+ function extractOpenRouterResponseMetadata(result) {
13165
+ if (!isObject(result)) {
13166
+ return void 0;
13167
+ }
13168
+ const { output: _output, data: _data, usage, ...metadata } = result;
13169
+ const sanitized = sanitizeOpenRouterLoggedValue(metadata);
13170
+ const metadataRecord = isObject(sanitized) ? sanitized : {};
13171
+ const { model, provider, ...rest } = metadataRecord;
13172
+ const normalizedModel = parseOpenRouterModelString(model);
13173
+ const normalizedProvider = (typeof provider === "string" ? provider : void 0) || normalizedModel.provider;
13174
+ const usageMetadata = extractOpenRouterUsageMetadata(usage);
13175
+ const combined = {
13176
+ ...rest,
13177
+ ...normalizedModel.model !== void 0 ? { model: normalizedModel.model } : {},
13178
+ ...usageMetadata || {},
13179
+ ...normalizedProvider !== void 0 ? { provider: normalizedProvider } : {}
13180
+ };
13181
+ return Object.keys(combined).length > 0 ? combined : void 0;
13182
+ }
13183
+ function extractOpenRouterResponseOutput(response, fallbackOutput) {
13184
+ if (isObject(response) && "output" in response && response.output !== void 0) {
13185
+ return sanitizeOpenRouterLoggedValue(response.output);
13186
+ }
13187
+ if (fallbackOutput !== void 0) {
13188
+ return sanitizeOpenRouterLoggedValue(fallbackOutput);
13189
+ }
13190
+ return void 0;
13191
+ }
13192
+
13193
+ // src/openrouter-tool-wrapping.ts
13194
+ var OPENROUTER_WRAPPED_TOOL = Symbol("braintrust.openrouter.wrappedTool");
13195
+ var OPENROUTER_WRAPPED_CALL_MODEL_RESULT = Symbol(
13196
+ "braintrust.openrouter.wrappedCallModelResult"
13197
+ );
13198
+ var OPENROUTER_CALL_MODEL_STREAM_METHODS = [
13199
+ "getFullResponsesStream",
13200
+ "getItemsStream",
13201
+ "getNewMessagesStream",
13202
+ "getReasoningStream",
13203
+ "getTextStream",
13204
+ "getToolCallsStream",
13205
+ "getToolStream"
13206
+ ];
13207
+ var OPENROUTER_CALL_MODEL_CONTEXT_METHODS = [
13208
+ "cancel",
13209
+ "getPendingToolCalls",
13210
+ "getState",
13211
+ "getToolCalls",
13212
+ "requiresApproval"
13213
+ ];
13214
+ function patchOpenRouterCallModelRequestTools(request) {
13215
+ if (!Array.isArray(request.tools) || request.tools.length === 0) {
13216
+ return void 0;
13217
+ }
13218
+ const originalTools = request.tools;
13219
+ const wrappedTools = originalTools.map((tool) => wrapOpenRouterTool(tool));
13220
+ const didPatch = wrappedTools.some(
13221
+ (tool, index) => tool !== originalTools[index]
13222
+ );
13223
+ if (!didPatch) {
13224
+ return void 0;
13225
+ }
13226
+ request.tools = wrappedTools;
13227
+ return () => {
13228
+ request.tools = originalTools;
13229
+ };
13230
+ }
13231
+ function patchOpenRouterCallModelResult(span, result, request) {
13232
+ if (!isObject(result) || isWrappedCallModelResult(result)) {
13233
+ return false;
13234
+ }
13235
+ const resultLike = result;
13236
+ const hasInstrumentableMethod = typeof resultLike.getResponse === "function" || typeof resultLike.getText === "function" || OPENROUTER_CALL_MODEL_STREAM_METHODS.some(
13237
+ (methodName) => typeof resultLike[methodName] === "function"
13238
+ );
13239
+ if (!hasInstrumentableMethod) {
13240
+ return false;
13241
+ }
13242
+ Object.defineProperty(resultLike, OPENROUTER_WRAPPED_CALL_MODEL_RESULT, {
13243
+ value: true,
13244
+ enumerable: false,
13245
+ configurable: false
13246
+ });
13247
+ const originalGetResponse = typeof resultLike.getResponse === "function" ? resultLike.getResponse.bind(resultLike) : void 0;
13248
+ const originalGetInitialResponse = typeof resultLike.getInitialResponse === "function" ? resultLike.getInitialResponse.bind(resultLike) : void 0;
13249
+ const originalMakeFollowupRequest = typeof resultLike.makeFollowupRequest === "function" ? resultLike.makeFollowupRequest.bind(resultLike) : void 0;
13250
+ let ended = false;
13251
+ let tracedTurnCount = 0;
13252
+ const endSpanWithResult = async (response, fallbackOutput) => {
13253
+ if (ended) {
13254
+ return;
13255
+ }
13256
+ ended = true;
13257
+ const finalResponse = getFinalOpenRouterCallModelResponse(
13258
+ resultLike,
13259
+ response
13260
+ );
13261
+ if (finalResponse) {
13262
+ const rounds = getOpenRouterCallModelRounds(resultLike);
13263
+ const metadata = extractOpenRouterCallModelResultMetadata(
13264
+ finalResponse,
13265
+ rounds.length + 1
13266
+ );
13267
+ span.log({
13268
+ output: extractOpenRouterResponseOutput(finalResponse, fallbackOutput),
13269
+ ...metadata ? { metadata } : {},
13270
+ metrics: aggregateOpenRouterCallModelMetrics(rounds, finalResponse)
13271
+ });
13272
+ span.end();
13273
+ return;
13274
+ }
13275
+ if (fallbackOutput !== void 0) {
13276
+ span.log({
13277
+ output: fallbackOutput
13278
+ });
13279
+ }
13280
+ span.end();
13281
+ };
13282
+ const endSpanWithError = (error) => {
13283
+ if (ended) {
13284
+ return;
13285
+ }
13286
+ ended = true;
13287
+ span.log({
13288
+ error: normalizeError(error).message
13289
+ });
13290
+ span.end();
13291
+ };
13292
+ const finalizeFromResponse = async (fallbackOutput) => {
13293
+ if (!originalGetResponse) {
13294
+ await endSpanWithResult(void 0, fallbackOutput);
13295
+ return;
13296
+ }
13297
+ try {
13298
+ await endSpanWithResult(await originalGetResponse(), fallbackOutput);
13299
+ } catch {
13300
+ await endSpanWithResult(void 0, fallbackOutput);
13301
+ }
13302
+ };
13303
+ if (originalGetResponse) {
13304
+ resultLike.getResponse = async (...args) => {
13305
+ return await withCurrent(span, async () => {
13306
+ try {
13307
+ const response = await originalGetResponse(...args);
13308
+ await endSpanWithResult(response);
13309
+ return response;
13310
+ } catch (error) {
13311
+ endSpanWithError(error);
13312
+ throw error;
13313
+ }
13314
+ });
13315
+ };
13316
+ }
13317
+ if (typeof resultLike.getText === "function") {
13318
+ const originalGetText = resultLike.getText.bind(resultLike);
13319
+ resultLike.getText = async (...args) => {
13320
+ return await withCurrent(span, async () => {
13321
+ try {
13322
+ const text = await originalGetText(...args);
13323
+ await finalizeFromResponse(text);
13324
+ return text;
13325
+ } catch (error) {
13326
+ endSpanWithError(error);
13327
+ throw error;
13328
+ }
13329
+ });
13330
+ };
13331
+ }
13332
+ for (const methodName of OPENROUTER_CALL_MODEL_CONTEXT_METHODS) {
13333
+ if (typeof resultLike[methodName] !== "function") {
13334
+ continue;
13335
+ }
13336
+ const originalMethod = resultLike[methodName];
13337
+ resultLike[methodName] = async (...args) => {
13338
+ return await withCurrent(span, async () => {
13339
+ return await originalMethod.apply(resultLike, args);
13340
+ });
13341
+ };
13342
+ }
13343
+ for (const methodName of OPENROUTER_CALL_MODEL_STREAM_METHODS) {
13344
+ if (typeof resultLike[methodName] !== "function") {
13345
+ continue;
13346
+ }
13347
+ const originalMethod = resultLike[methodName];
13348
+ resultLike[methodName] = (...args) => {
13349
+ const stream = withCurrent(
13350
+ span,
13351
+ () => originalMethod.apply(resultLike, args)
13352
+ );
13353
+ if (!isAsyncIterable2(stream)) {
13354
+ return stream;
13355
+ }
13356
+ return wrapAsyncIterableWithSpan({
13357
+ finalize: finalizeFromResponse,
13358
+ iteratorFactory: () => stream[Symbol.asyncIterator](),
13359
+ onError: endSpanWithError,
13360
+ span
13361
+ });
13362
+ };
13363
+ }
13364
+ if (originalGetInitialResponse) {
13365
+ let initialTurnTraced = false;
13366
+ resultLike.getInitialResponse = async (...args) => {
13367
+ if (initialTurnTraced) {
13368
+ return await withCurrent(span, async () => {
13369
+ return await originalGetInitialResponse(...args);
13370
+ });
13371
+ }
13372
+ initialTurnTraced = true;
13373
+ const resolvedRequest = getOpenRouterResolvedRequest(resultLike, request);
13374
+ const childSpan = startOpenRouterCallModelTurnSpan({
13375
+ request: resolvedRequest,
13376
+ step: tracedTurnCount + 1,
13377
+ stepType: tracedTurnCount === 0 ? "initial" : "continue"
13378
+ });
13379
+ return await withCurrent(childSpan, async () => {
13380
+ try {
13381
+ const response = await originalGetInitialResponse(...args);
13382
+ tracedTurnCount++;
13383
+ finishOpenRouterCallModelTurnSpan({
13384
+ response,
13385
+ step: tracedTurnCount,
13386
+ stepType: tracedTurnCount === 1 ? "initial" : "continue",
13387
+ span: childSpan
13388
+ });
13389
+ return response;
13390
+ } catch (error) {
13391
+ childSpan.log({
13392
+ error: normalizeError(error).message
13393
+ });
13394
+ childSpan.end();
13395
+ throw error;
13396
+ }
13397
+ });
13398
+ };
13399
+ }
13400
+ if (originalMakeFollowupRequest) {
13401
+ resultLike.makeFollowupRequest = async (...args) => {
13402
+ const currentResponse = args[0];
13403
+ const toolResults = Array.isArray(args[1]) ? args[1] : [];
13404
+ const resolvedRequest = getOpenRouterResolvedRequest(resultLike, request);
13405
+ const followupRequest = buildOpenRouterFollowupRequest(
13406
+ resolvedRequest,
13407
+ currentResponse,
13408
+ toolResults
13409
+ );
13410
+ const childSpan = startOpenRouterCallModelTurnSpan({
13411
+ request: followupRequest,
13412
+ step: tracedTurnCount + 1,
13413
+ stepType: "continue"
13414
+ });
13415
+ return await withCurrent(childSpan, async () => {
13416
+ try {
13417
+ const response = await originalMakeFollowupRequest(...args);
13418
+ tracedTurnCount++;
13419
+ finishOpenRouterCallModelTurnSpan({
13420
+ response,
13421
+ step: tracedTurnCount,
13422
+ stepType: "continue",
13423
+ span: childSpan
13424
+ });
13425
+ return response;
13426
+ } catch (error) {
13427
+ childSpan.log({
13428
+ error: normalizeError(error).message
13429
+ });
13430
+ childSpan.end();
13431
+ throw error;
13432
+ }
13433
+ });
13434
+ };
13435
+ }
13436
+ return true;
13437
+ }
13438
+ function wrapOpenRouterTool(tool) {
13439
+ if (isWrappedTool(tool) || !tool.function || typeof tool.function !== "object" || typeof tool.function.execute !== "function") {
13440
+ return tool;
13441
+ }
13442
+ const toolName = tool.function.name || "tool";
13443
+ const originalExecute = tool.function.execute;
13444
+ const wrappedTool = {
13445
+ ...tool,
13446
+ function: {
13447
+ ...tool.function,
13448
+ execute(...args) {
13449
+ return traceToolExecution({
13450
+ args,
13451
+ execute: () => Reflect.apply(originalExecute, this, args),
13452
+ toolCallId: getToolCallId(args[1]),
13453
+ toolName
13454
+ });
13455
+ }
13456
+ }
13457
+ };
13458
+ Object.defineProperty(wrappedTool, OPENROUTER_WRAPPED_TOOL, {
13459
+ value: true,
13460
+ enumerable: false,
13461
+ configurable: false
13462
+ });
13463
+ return wrappedTool;
13464
+ }
13465
+ function isWrappedTool(tool) {
13466
+ return Boolean(tool[OPENROUTER_WRAPPED_TOOL]);
13467
+ }
13468
+ function isWrappedCallModelResult(value) {
13469
+ return Boolean(
13470
+ isObject(value) && value[OPENROUTER_WRAPPED_CALL_MODEL_RESULT]
13471
+ );
13472
+ }
13473
+ function traceToolExecution(args) {
13474
+ const tracingChannel2 = openRouterChannels.toolExecute.tracingChannel();
13475
+ const input = args.args.length > 0 ? args.args[0] : void 0;
13476
+ const event = {
13477
+ arguments: [input],
13478
+ span_info: {
13479
+ name: args.toolName
13480
+ },
13481
+ toolCallId: args.toolCallId,
13482
+ toolName: args.toolName
13483
+ };
13484
+ tracingChannel2.start.publish(event);
13485
+ try {
13486
+ const result = args.execute();
13487
+ return publishToolResult(tracingChannel2, event, result);
13488
+ } catch (error) {
13489
+ event.error = normalizeError(error);
13490
+ tracingChannel2.error.publish(event);
13491
+ throw error;
13492
+ }
13493
+ }
13494
+ function publishToolResult(tracingChannel2, event, result) {
13495
+ if (isPromiseLike(result)) {
13496
+ return result.then(
13497
+ (resolved) => {
13498
+ event.result = resolved;
13499
+ tracingChannel2.asyncEnd.publish(event);
13500
+ return resolved;
13501
+ },
13502
+ (error) => {
13503
+ event.error = normalizeError(error);
13504
+ tracingChannel2.error.publish(event);
13505
+ throw error;
13506
+ }
13507
+ );
13508
+ }
13509
+ event.result = result;
13510
+ tracingChannel2.asyncEnd.publish(event);
13511
+ return result;
13512
+ }
13513
+ function getToolCallId(context) {
13514
+ const toolContext = context;
13515
+ return typeof toolContext?.toolCall?.id === "string" ? toolContext.toolCall.id : void 0;
13516
+ }
13517
+ function extractOpenRouterCallModelResultMetadata(response, turnCount) {
13518
+ const combined = {
13519
+ ...extractOpenRouterResponseMetadata(response) || {},
13520
+ ...turnCount !== void 0 ? { turn_count: turnCount } : {}
13521
+ };
13522
+ return Object.keys(combined).length > 0 ? combined : void 0;
13523
+ }
13524
+ function getFinalOpenRouterCallModelResponse(result, response) {
13525
+ if (isObject(response)) {
13526
+ return response;
12037
13527
  }
12038
- if (usage.totalTokenCount !== void 0) {
12039
- metrics.tokens = usage.totalTokenCount;
13528
+ return isObject(result.finalResponse) ? result.finalResponse : void 0;
13529
+ }
13530
+ function getOpenRouterCallModelRounds(result) {
13531
+ if (!Array.isArray(result.allToolExecutionRounds)) {
13532
+ return [];
12040
13533
  }
12041
- if (usage.cachedContentTokenCount !== void 0) {
12042
- metrics.prompt_cached_tokens = usage.cachedContentTokenCount;
13534
+ return result.allToolExecutionRounds.filter((round) => isObject(round)).map((round) => ({
13535
+ response: isObject(round.response) ? round.response : void 0,
13536
+ round: typeof round.round === "number" ? round.round : void 0,
13537
+ toolResults: Array.isArray(round.toolResults) ? round.toolResults : []
13538
+ })).filter((round) => round.response !== void 0);
13539
+ }
13540
+ function aggregateOpenRouterCallModelMetrics(rounds, finalResponse) {
13541
+ const metrics = {};
13542
+ const responses = [
13543
+ ...rounds.map((round) => round.response).filter(isObject),
13544
+ finalResponse
13545
+ ];
13546
+ for (const response of responses) {
13547
+ const responseMetrics = parseOpenRouterMetricsFromUsage(response.usage);
13548
+ for (const [name, value] of Object.entries(responseMetrics)) {
13549
+ metrics[name] = (metrics[name] || 0) + value;
13550
+ }
12043
13551
  }
12044
- if (usage.thoughtsTokenCount !== void 0) {
12045
- metrics.completion_reasoning_tokens = usage.thoughtsTokenCount;
13552
+ return metrics;
13553
+ }
13554
+ function buildNextOpenRouterCallModelInput(currentInput, response, toolResults) {
13555
+ const normalizedInput = Array.isArray(currentInput) ? [...currentInput] : currentInput === void 0 ? [] : [currentInput];
13556
+ const responseOutput = Array.isArray(response.output) ? response.output : response.output === void 0 ? [] : [response.output];
13557
+ return [...normalizedInput, ...responseOutput, ...toolResults].map(
13558
+ (entry) => sanitizeOpenRouterLoggedValue(entry)
13559
+ );
13560
+ }
13561
+ function startOpenRouterCallModelTurnSpan(args) {
13562
+ const requestRecord = isObject(args.request) ? args.request : void 0;
13563
+ const metadata = requestRecord ? extractOpenRouterCallModelMetadata(requestRecord) : { provider: "openrouter" };
13564
+ if (isObject(metadata) && "tools" in metadata) {
13565
+ delete metadata.tools;
12046
13566
  }
13567
+ return startSpan({
13568
+ name: "openrouter.beta.responses.send",
13569
+ spanAttributes: {
13570
+ type: "llm" /* LLM */
13571
+ },
13572
+ event: {
13573
+ input: requestRecord ? extractOpenRouterCallModelInput(requestRecord) : void 0,
13574
+ metadata: {
13575
+ ...metadata,
13576
+ step: args.step,
13577
+ step_type: args.stepType
13578
+ }
13579
+ }
13580
+ });
12047
13581
  }
12048
- function aggregateGenerateContentChunks(chunks, startTime) {
12049
- const metrics = {};
12050
- if (startTime !== void 0) {
12051
- const end = getCurrentUnixTimestamp();
12052
- metrics.duration = end - startTime;
13582
+ function finishOpenRouterCallModelTurnSpan(args) {
13583
+ if (!isObject(args.response)) {
13584
+ args.span.end();
13585
+ return;
12053
13586
  }
12054
- let firstTokenTime = null;
12055
- if (chunks.length > 0 && firstTokenTime === null && startTime !== void 0) {
12056
- firstTokenTime = getCurrentUnixTimestamp();
12057
- metrics.time_to_first_token = firstTokenTime - startTime;
13587
+ args.span.log({
13588
+ output: extractOpenRouterResponseOutput(args.response),
13589
+ ...extractOpenRouterResponseMetadata(args.response) ? {
13590
+ metadata: {
13591
+ ...extractOpenRouterResponseMetadata(args.response),
13592
+ ...args.step !== void 0 ? { step: args.step } : {},
13593
+ ...args.stepType ? { step_type: args.stepType } : {}
13594
+ }
13595
+ } : {},
13596
+ metrics: parseOpenRouterMetricsFromUsage(args.response.usage)
13597
+ });
13598
+ args.span.end();
13599
+ }
13600
+ function getOpenRouterResolvedRequest(result, request) {
13601
+ if (isObject(result.resolvedRequest)) {
13602
+ return result.resolvedRequest;
12058
13603
  }
12059
- if (chunks.length === 0) {
12060
- return { output: {}, metrics };
13604
+ return request;
13605
+ }
13606
+ function buildOpenRouterFollowupRequest(request, currentResponse, toolResults) {
13607
+ if (!request) {
13608
+ return void 0;
12061
13609
  }
12062
- let text = "";
12063
- let thoughtText = "";
12064
- const otherParts = [];
12065
- let usageMetadata = null;
12066
- let lastResponse = null;
12067
- for (const chunk of chunks) {
12068
- lastResponse = chunk;
12069
- if (chunk.usageMetadata) {
12070
- usageMetadata = chunk.usageMetadata;
12071
- }
12072
- if (chunk.candidates && Array.isArray(chunk.candidates)) {
12073
- for (const candidate of chunk.candidates) {
12074
- if (candidate.content?.parts) {
12075
- for (const part of candidate.content.parts) {
12076
- if (part.text !== void 0) {
12077
- if (part.thought) {
12078
- thoughtText += part.text;
12079
- } else {
12080
- text += part.text;
13610
+ return {
13611
+ ...request,
13612
+ input: buildNextOpenRouterCallModelInput(
13613
+ extractOpenRouterCallModelInput(request),
13614
+ isObject(currentResponse) ? currentResponse : {},
13615
+ toolResults
13616
+ ),
13617
+ stream: false
13618
+ };
13619
+ }
13620
+ function wrapAsyncIterableWithSpan(args) {
13621
+ return {
13622
+ [Symbol.asyncIterator]() {
13623
+ const iterator = args.iteratorFactory();
13624
+ return {
13625
+ next(value) {
13626
+ return withCurrent(
13627
+ args.span,
13628
+ () => value === void 0 ? iterator.next() : iterator.next(value)
13629
+ ).then(
13630
+ async (result) => {
13631
+ if (result.done) {
13632
+ await args.finalize();
12081
13633
  }
12082
- } else if (part.functionCall) {
12083
- otherParts.push({ functionCall: part.functionCall });
12084
- } else if (part.codeExecutionResult) {
12085
- otherParts.push({
12086
- codeExecutionResult: part.codeExecutionResult
12087
- });
12088
- } else if (part.executableCode) {
12089
- otherParts.push({ executableCode: part.executableCode });
13634
+ return result;
13635
+ },
13636
+ (error) => {
13637
+ args.onError(error);
13638
+ throw error;
13639
+ }
13640
+ );
13641
+ },
13642
+ return(value) {
13643
+ if (typeof iterator.return !== "function") {
13644
+ return args.finalize().then(() => ({
13645
+ done: true,
13646
+ value
13647
+ }));
13648
+ }
13649
+ return withCurrent(args.span, () => iterator.return(value)).then(
13650
+ async (result) => {
13651
+ await args.finalize();
13652
+ return result;
13653
+ },
13654
+ (error) => {
13655
+ args.onError(error);
13656
+ throw error;
12090
13657
  }
13658
+ );
13659
+ },
13660
+ throw(error) {
13661
+ args.onError(error);
13662
+ if (typeof iterator.throw !== "function") {
13663
+ return Promise.reject(error);
12091
13664
  }
13665
+ return withCurrent(args.span, () => iterator.throw(error));
13666
+ },
13667
+ [Symbol.asyncIterator]() {
13668
+ return this;
12092
13669
  }
12093
- }
13670
+ };
12094
13671
  }
13672
+ };
13673
+ }
13674
+ function isAsyncIterable2(value) {
13675
+ return !!value && (typeof value === "object" || typeof value === "function") && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
13676
+ }
13677
+ function isPromiseLike(value) {
13678
+ return !!value && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
13679
+ }
13680
+ function normalizeError(error) {
13681
+ return error instanceof Error ? error : new Error(String(error));
13682
+ }
13683
+
13684
+ // src/instrumentation/plugins/openrouter-plugin.ts
13685
+ var OpenRouterPlugin = class extends BasePlugin {
13686
+ onEnable() {
13687
+ this.subscribeToOpenRouterChannels();
12095
13688
  }
12096
- const output = {};
12097
- const parts = [];
12098
- if (thoughtText) {
12099
- parts.push({ text: thoughtText, thought: true });
12100
- }
12101
- if (text) {
12102
- parts.push({ text });
13689
+ onDisable() {
13690
+ this.unsubscribers = unsubscribeAll(this.unsubscribers);
12103
13691
  }
12104
- parts.push(...otherParts);
12105
- if (parts.length > 0 && lastResponse?.candidates) {
12106
- const candidates = [];
12107
- for (const candidate of lastResponse.candidates) {
12108
- const candidateDict = {
12109
- content: {
12110
- parts,
12111
- role: "model"
13692
+ subscribeToOpenRouterChannels() {
13693
+ this.unsubscribers.push(
13694
+ traceStreamingChannel(openRouterChannels.chatSend, {
13695
+ name: "openrouter.chat.send",
13696
+ type: "llm" /* LLM */,
13697
+ extractInput: (args) => {
13698
+ const request = getOpenRouterRequestArg(args);
13699
+ const chatGenerationParams = isObject(request?.chatGenerationParams) ? request.chatGenerationParams : {};
13700
+ const httpReferer = request?.httpReferer;
13701
+ const xTitle = request?.xTitle;
13702
+ const { messages, ...metadata } = chatGenerationParams;
13703
+ return {
13704
+ input: messages,
13705
+ metadata: buildOpenRouterMetadata(metadata, httpReferer, xTitle)
13706
+ };
13707
+ },
13708
+ extractOutput: (result) => {
13709
+ return isObject(result) ? result.choices : void 0;
13710
+ },
13711
+ extractMetrics: (result, startTime) => {
13712
+ const metrics = parseOpenRouterMetricsFromUsage(result?.usage);
13713
+ if (startTime) {
13714
+ metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
13715
+ }
13716
+ return metrics;
13717
+ },
13718
+ aggregateChunks: aggregateOpenRouterChatChunks
13719
+ })
13720
+ );
13721
+ this.unsubscribers.push(
13722
+ traceAsyncChannel(openRouterChannels.embeddingsGenerate, {
13723
+ name: "openrouter.embeddings.generate",
13724
+ type: "llm" /* LLM */,
13725
+ extractInput: (args) => {
13726
+ const request = getOpenRouterRequestArg(args);
13727
+ const requestBody = isObject(request?.requestBody) ? request.requestBody : {};
13728
+ const httpReferer = request?.httpReferer;
13729
+ const xTitle = request?.xTitle;
13730
+ const { input, ...metadata } = requestBody;
13731
+ return {
13732
+ input,
13733
+ metadata: buildOpenRouterEmbeddingMetadata(
13734
+ metadata,
13735
+ httpReferer,
13736
+ xTitle
13737
+ )
13738
+ };
13739
+ },
13740
+ extractOutput: (result) => {
13741
+ if (!isObject(result)) {
13742
+ return void 0;
13743
+ }
13744
+ const embedding = result.data?.[0]?.embedding;
13745
+ return Array.isArray(embedding) ? { embedding_length: embedding.length } : void 0;
13746
+ },
13747
+ extractMetadata: (result) => {
13748
+ if (!isObject(result)) {
13749
+ return void 0;
13750
+ }
13751
+ return extractOpenRouterResponseMetadata(result);
13752
+ },
13753
+ extractMetrics: (result) => {
13754
+ return isObject(result) ? parseOpenRouterMetricsFromUsage(result.usage) : {};
12112
13755
  }
12113
- };
12114
- if (candidate.finishReason !== void 0) {
12115
- candidateDict.finishReason = candidate.finishReason;
12116
- }
12117
- if (candidate.safetyRatings) {
12118
- candidateDict.safetyRatings = candidate.safetyRatings;
13756
+ })
13757
+ );
13758
+ this.unsubscribers.push(
13759
+ traceStreamingChannel(openRouterChannels.betaResponsesSend, {
13760
+ name: "openrouter.beta.responses.send",
13761
+ type: "llm" /* LLM */,
13762
+ extractInput: (args) => {
13763
+ const request = getOpenRouterRequestArg(args);
13764
+ const openResponsesRequest = isObject(request?.openResponsesRequest) ? request.openResponsesRequest : {};
13765
+ const httpReferer = request?.httpReferer;
13766
+ const xTitle = request?.xTitle;
13767
+ const { input, ...metadata } = openResponsesRequest;
13768
+ return {
13769
+ input,
13770
+ metadata: buildOpenRouterMetadata(metadata, httpReferer, xTitle)
13771
+ };
13772
+ },
13773
+ extractOutput: (result) => extractOpenRouterResponseOutput(result),
13774
+ extractMetadata: (result) => extractOpenRouterResponseMetadata(result),
13775
+ extractMetrics: (result, startTime) => {
13776
+ const metrics = parseOpenRouterMetricsFromUsage(result?.usage);
13777
+ if (startTime) {
13778
+ metrics.time_to_first_token = getCurrentUnixTimestamp() - startTime;
13779
+ }
13780
+ return metrics;
13781
+ },
13782
+ aggregateChunks: aggregateOpenRouterResponseStreamEvents
13783
+ })
13784
+ );
13785
+ this.unsubscribers.push(
13786
+ traceSyncStreamChannel(openRouterChannels.callModel, {
13787
+ name: "openrouter.callModel",
13788
+ type: "llm" /* LLM */,
13789
+ extractInput: (args) => {
13790
+ const request = getOpenRouterCallModelRequestArg(args);
13791
+ return {
13792
+ input: request ? extractOpenRouterCallModelInput(request) : void 0,
13793
+ metadata: request ? extractOpenRouterCallModelMetadata(request) : { provider: "openrouter" }
13794
+ };
13795
+ },
13796
+ patchResult: ({ endEvent, result, span }) => {
13797
+ return patchOpenRouterCallModelResult(
13798
+ span,
13799
+ result,
13800
+ getOpenRouterCallModelRequestArg(endEvent.arguments)
13801
+ );
13802
+ }
13803
+ })
13804
+ );
13805
+ this.unsubscribers.push(
13806
+ traceStreamingChannel(openRouterChannels.toolExecute, {
13807
+ name: "openrouter.tool",
13808
+ type: "tool" /* TOOL */,
13809
+ extractInput: (args, event) => ({
13810
+ input: args[0],
13811
+ metadata: {
13812
+ provider: "openrouter",
13813
+ tool_name: event.toolName,
13814
+ ...event.toolCallId ? { tool_call_id: event.toolCallId } : {}
13815
+ }
13816
+ }),
13817
+ extractOutput: (result) => result,
13818
+ extractMetrics: () => ({}),
13819
+ aggregateChunks: (chunks) => ({
13820
+ output: chunks.length > 0 ? chunks[chunks.length - 1] : void 0,
13821
+ metrics: {}
13822
+ })
13823
+ })
13824
+ );
13825
+ const callModelChannel = openRouterChannels.callModel.tracingChannel();
13826
+ const callModelHandlers = {
13827
+ start: (event) => {
13828
+ const request = getOpenRouterCallModelRequestArg(event.arguments);
13829
+ if (!request) {
13830
+ return;
13831
+ }
13832
+ patchOpenRouterCallModelRequestTools(request);
12119
13833
  }
12120
- candidates.push(candidateDict);
12121
- }
12122
- output.candidates = candidates;
13834
+ };
13835
+ callModelChannel.subscribe(callModelHandlers);
13836
+ this.unsubscribers.push(() => {
13837
+ callModelChannel.unsubscribe(callModelHandlers);
13838
+ });
12123
13839
  }
12124
- if (usageMetadata) {
12125
- output.usageMetadata = usageMetadata;
12126
- populateUsageMetrics(metrics, usageMetadata);
13840
+ };
13841
+ function normalizeArgs(args) {
13842
+ if (Array.isArray(args)) {
13843
+ return args;
12127
13844
  }
12128
- if (text) {
12129
- output.text = text;
13845
+ if (isArrayLike(args)) {
13846
+ return Array.from(args);
12130
13847
  }
12131
- return { output, metrics };
13848
+ return [args];
12132
13849
  }
12133
- function tryToDict(obj) {
12134
- if (obj === null || obj === void 0) {
12135
- return null;
13850
+ function isArrayLike(value) {
13851
+ return isObject(value) && "length" in value && typeof value.length === "number" && Number.isInteger(value.length) && value.length >= 0;
13852
+ }
13853
+ function getOpenRouterRequestArg(args) {
13854
+ const normalizedArgs = normalizeArgs(args);
13855
+ const keyedCandidate = normalizedArgs.find(
13856
+ (arg) => isObject(arg) && ("chatGenerationParams" in arg || "requestBody" in arg || "openResponsesRequest" in arg)
13857
+ );
13858
+ if (isObject(keyedCandidate)) {
13859
+ return keyedCandidate;
12136
13860
  }
12137
- if (typeof obj === "object") {
12138
- if ("toJSON" in obj && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12139
- typeof obj.toJSON === "function") {
12140
- return obj.toJSON();
13861
+ const firstObjectArg = normalizedArgs.find((arg) => isObject(arg));
13862
+ return isObject(firstObjectArg) ? firstObjectArg : void 0;
13863
+ }
13864
+ function getOpenRouterCallModelRequestArg(args) {
13865
+ const firstObjectArg = normalizeArgs(args).find((arg) => isObject(arg));
13866
+ return isObject(firstObjectArg) ? firstObjectArg : void 0;
13867
+ }
13868
+ function aggregateOpenRouterChatChunks(chunks) {
13869
+ let role;
13870
+ let content = "";
13871
+ let toolCalls;
13872
+ let finishReason;
13873
+ let metrics = {};
13874
+ for (const chunk of chunks) {
13875
+ metrics = {
13876
+ ...metrics,
13877
+ ...parseOpenRouterMetricsFromUsage(chunk?.usage)
13878
+ };
13879
+ const choice = chunk?.choices?.[0];
13880
+ const delta = choice?.delta;
13881
+ if (!delta) {
13882
+ if (choice?.finish_reason !== void 0) {
13883
+ finishReason = choice.finish_reason;
13884
+ }
13885
+ continue;
13886
+ }
13887
+ if (!role && delta.role) {
13888
+ role = delta.role;
13889
+ }
13890
+ if (typeof delta.content === "string") {
13891
+ content += delta.content;
13892
+ }
13893
+ const choiceFinishReason = choice?.finishReason ?? choice?.finish_reason ?? void 0;
13894
+ const deltaFinishReason = delta.finishReason ?? delta.finish_reason ?? void 0;
13895
+ if (choiceFinishReason !== void 0) {
13896
+ finishReason = choiceFinishReason;
13897
+ } else if (deltaFinishReason !== void 0) {
13898
+ finishReason = deltaFinishReason;
13899
+ }
13900
+ const toolCallDeltas = Array.isArray(delta.toolCalls) ? delta.toolCalls : Array.isArray(delta.tool_calls) ? delta.tool_calls : void 0;
13901
+ if (!toolCallDeltas) {
13902
+ continue;
13903
+ }
13904
+ for (const toolDelta of toolCallDeltas) {
13905
+ if (!toolDelta?.function) {
13906
+ continue;
13907
+ }
13908
+ const toolIndex = toolDelta.index ?? 0;
13909
+ const existingToolCall = toolCalls?.[toolIndex];
13910
+ if (!existingToolCall || toolDelta.id && existingToolCall.id !== void 0 && existingToolCall.id !== toolDelta.id) {
13911
+ const nextToolCalls = [...toolCalls || []];
13912
+ nextToolCalls[toolIndex] = {
13913
+ index: toolIndex,
13914
+ id: toolDelta.id,
13915
+ type: toolDelta.type,
13916
+ function: {
13917
+ name: toolDelta.function.name,
13918
+ arguments: toolDelta.function.arguments || ""
13919
+ }
13920
+ };
13921
+ toolCalls = nextToolCalls;
13922
+ continue;
13923
+ }
13924
+ const current = existingToolCall;
13925
+ if (toolDelta.id && !current.id) {
13926
+ current.id = toolDelta.id;
13927
+ }
13928
+ if (toolDelta.type && !current.type) {
13929
+ current.type = toolDelta.type;
13930
+ }
13931
+ if (toolDelta.function.name && !current.function.name) {
13932
+ current.function.name = toolDelta.function.name;
13933
+ }
13934
+ current.function.arguments += toolDelta.function.arguments || "";
12141
13935
  }
12142
- return obj;
12143
13936
  }
12144
- return null;
13937
+ return {
13938
+ output: [
13939
+ {
13940
+ index: 0,
13941
+ message: {
13942
+ role,
13943
+ content: content || void 0,
13944
+ ...toolCalls ? { tool_calls: toolCalls } : {}
13945
+ },
13946
+ logprobs: null,
13947
+ finish_reason: finishReason
13948
+ }
13949
+ ],
13950
+ metrics
13951
+ };
13952
+ }
13953
+ function aggregateOpenRouterResponseStreamEvents(chunks) {
13954
+ let finalResponse;
13955
+ for (const chunk of chunks) {
13956
+ const response = chunk?.response;
13957
+ if (!response) {
13958
+ continue;
13959
+ }
13960
+ if (chunk.type === "response.completed" || chunk.type === "response.incomplete" || chunk.type === "response.failed") {
13961
+ finalResponse = response;
13962
+ }
13963
+ }
13964
+ if (!finalResponse) {
13965
+ return {
13966
+ output: void 0,
13967
+ metrics: {}
13968
+ };
13969
+ }
13970
+ return {
13971
+ output: extractOpenRouterResponseOutput(finalResponse),
13972
+ metrics: parseOpenRouterMetricsFromUsage(finalResponse.usage),
13973
+ ...extractOpenRouterResponseMetadata(finalResponse) ? { metadata: extractOpenRouterResponseMetadata(finalResponse) } : {}
13974
+ };
12145
13975
  }
12146
13976
 
12147
13977
  // src/instrumentation/braintrust-plugin.ts
@@ -12152,6 +13982,7 @@ var BraintrustPlugin = class extends BasePlugin {
12152
13982
  aiSDKPlugin = null;
12153
13983
  claudeAgentSDKPlugin = null;
12154
13984
  googleGenAIPlugin = null;
13985
+ openRouterPlugin = null;
12155
13986
  constructor(config = {}) {
12156
13987
  super();
12157
13988
  this.config = config;
@@ -12178,6 +14009,10 @@ var BraintrustPlugin = class extends BasePlugin {
12178
14009
  this.googleGenAIPlugin = new GoogleGenAIPlugin();
12179
14010
  this.googleGenAIPlugin.enable();
12180
14011
  }
14012
+ if (integrations.openrouter !== false) {
14013
+ this.openRouterPlugin = new OpenRouterPlugin();
14014
+ this.openRouterPlugin.enable();
14015
+ }
12181
14016
  }
12182
14017
  onDisable() {
12183
14018
  if (this.openaiPlugin) {
@@ -12200,6 +14035,10 @@ var BraintrustPlugin = class extends BasePlugin {
12200
14035
  this.googleGenAIPlugin.disable();
12201
14036
  this.googleGenAIPlugin = null;
12202
14037
  }
14038
+ if (this.openRouterPlugin) {
14039
+ this.openRouterPlugin.disable();
14040
+ this.openRouterPlugin = null;
14041
+ }
12203
14042
  }
12204
14043
  };
12205
14044
 
@@ -12271,7 +14110,8 @@ var PluginRegistry = class {
12271
14110
  vercel: true,
12272
14111
  aisdk: true,
12273
14112
  google: true,
12274
- claudeAgentSDK: true
14113
+ claudeAgentSDK: true,
14114
+ openrouter: true
12275
14115
  };
12276
14116
  }
12277
14117
  /**
@@ -12301,6 +14141,7 @@ function configureNode() {
12301
14141
  isomorph_default.getCallerLocation = getCallerLocation;
12302
14142
  isomorph_default.newAsyncLocalStorage = () => new AsyncLocalStorage();
12303
14143
  isomorph_default.newTracingChannel = (nameOrChannels) => diagnostics_channel.tracingChannel(nameOrChannels);
14144
+ patchTracingChannel(diagnostics_channel.tracingChannel);
12304
14145
  isomorph_default.processOn = (event, handler) => {
12305
14146
  process.on(event, handler);
12306
14147
  };
@@ -12405,7 +14246,7 @@ function isAsync(fn) {
12405
14246
  function isAsyncGenerator2(fn) {
12406
14247
  return fn[Symbol.toStringTag] === "AsyncGenerator";
12407
14248
  }
12408
- function isAsyncIterable2(obj) {
14249
+ function isAsyncIterable3(obj) {
12409
14250
  return typeof obj[Symbol.asyncIterator] === "function";
12410
14251
  }
12411
14252
  function wrapAsync(asyncFn) {
@@ -12455,7 +14296,7 @@ function _asyncMap(eachfn, arr, iteratee, callback) {
12455
14296
  callback(err, results);
12456
14297
  });
12457
14298
  }
12458
- function isArrayLike(value) {
14299
+ function isArrayLike2(value) {
12459
14300
  return value && typeof value.length === "number" && value.length >= 0 && value.length % 1 === 0;
12460
14301
  }
12461
14302
  var breakLoop = {};
@@ -12503,7 +14344,7 @@ function createObjectIterator(obj) {
12503
14344
  };
12504
14345
  }
12505
14346
  function createIterator(coll) {
12506
- if (isArrayLike(coll)) {
14347
+ if (isArrayLike2(coll)) {
12507
14348
  return createArrayIterator(coll);
12508
14349
  }
12509
14350
  var iterator = getIterator(coll);
@@ -12577,7 +14418,7 @@ var eachOfLimit$2 = (limit) => {
12577
14418
  if (isAsyncGenerator2(obj)) {
12578
14419
  return asyncEachOfLimit(obj, limit, iteratee, callback);
12579
14420
  }
12580
- if (isAsyncIterable2(obj)) {
14421
+ if (isAsyncIterable3(obj)) {
12581
14422
  return asyncEachOfLimit(obj[Symbol.asyncIterator](), limit, iteratee, callback);
12582
14423
  }
12583
14424
  var nextElem = createIterator(obj);
@@ -12649,7 +14490,7 @@ function eachOfGeneric(coll, iteratee, callback) {
12649
14490
  return eachOfLimit$1(coll, Infinity, iteratee, callback);
12650
14491
  }
12651
14492
  function eachOf(coll, iteratee, callback) {
12652
- var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric;
14493
+ var eachOfImplementation = isArrayLike2(coll) ? eachOfArrayLike : eachOfGeneric;
12653
14494
  return eachOfImplementation(coll, wrapAsync(iteratee), callback);
12654
14495
  }
12655
14496
  var eachOf$1 = awaitify(eachOf, 3);
@@ -13164,7 +15005,7 @@ function filterGeneric(eachfn, coll, iteratee, callback) {
13164
15005
  });
13165
15006
  }
13166
15007
  function _filter(eachfn, coll, iteratee, callback) {
13167
- var filter2 = isArrayLike(coll) ? filterArray : filterGeneric;
15008
+ var filter2 = isArrayLike2(coll) ? filterArray : filterGeneric;
13168
15009
  return filter2(eachfn, coll, wrapAsync(iteratee), callback);
13169
15010
  }
13170
15011
  function filter(coll, iteratee, callback) {
@@ -13239,7 +15080,7 @@ if (hasNextTick) {
13239
15080
  }
13240
15081
  var nextTick = wrap(_defer);
13241
15082
  var _parallel = awaitify((eachfn, tasks, callback) => {
13242
- var results = isArrayLike(tasks) ? [] : {};
15083
+ var results = isArrayLike2(tasks) ? [] : {};
13243
15084
  eachfn(tasks, (task, key, taskCb) => {
13244
15085
  wrapAsync(task)((err, ...result) => {
13245
15086
  if (result.length < 2) {
@@ -13920,7 +15761,7 @@ function callEvaluatorData(data) {
13920
15761
  baseExperiment
13921
15762
  };
13922
15763
  }
13923
- function isAsyncIterable3(value) {
15764
+ function isAsyncIterable4(value) {
13924
15765
  return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
13925
15766
  }
13926
15767
  function isIterable(value) {
@@ -14125,7 +15966,7 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
14125
15966
  }
14126
15967
  const resolvedDataResult = dataResult instanceof Promise ? await dataResult : dataResult;
14127
15968
  const dataIterable = (() => {
14128
- if (isAsyncIterable3(resolvedDataResult)) {
15969
+ if (isAsyncIterable4(resolvedDataResult)) {
14129
15970
  return resolvedDataResult;
14130
15971
  }
14131
15972
  if (Array.isArray(resolvedDataResult) || isIterable(resolvedDataResult)) {