braintrust 3.6.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.
@@ -24,25 +24,51 @@ function patchTracingChannel(tracingChannelFn) {
24
24
  if (TracingChannel.prototype.tracePromise) {
25
25
  TracingChannel.prototype.tracePromise = function(fn, context = {}, thisArg, ...args) {
26
26
  const { start, end, asyncStart, asyncEnd, error } = this;
27
- function reject2(err) {
27
+ function publishRejected(err) {
28
28
  context.error = err;
29
29
  error?.publish(context);
30
30
  asyncStart?.publish(context);
31
31
  asyncEnd?.publish(context);
32
- return Promise.reject(err);
33
32
  }
34
- function resolve(result) {
33
+ function publishResolved(result) {
35
34
  context.result = result;
36
35
  asyncStart?.publish(context);
37
36
  asyncEnd?.publish(context);
38
- return result;
39
37
  }
40
38
  return start.runStores(context, () => {
41
39
  try {
42
40
  const result = Reflect.apply(fn, thisArg, args);
43
41
  end?.publish(context);
44
42
  if (result && (typeof result === "object" || typeof result === "function") && typeof result.then === "function") {
45
- return result.then(resolve, reject2);
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;
46
72
  }
47
73
  context.result = result;
48
74
  asyncStart?.publish(context);
@@ -10468,7 +10494,7 @@ var AnthropicPlugin = class extends BasePlugin {
10468
10494
  this.unsubscribers.push(
10469
10495
  traceStreamingChannel(anthropicChannels.betaMessagesCreate, {
10470
10496
  ...anthropicConfig,
10471
- name: "anthropic.beta.messages.create"
10497
+ name: "anthropic.messages.create"
10472
10498
  })
10473
10499
  );
10474
10500
  }
@@ -10491,9 +10517,12 @@ function parseMetricsFromUsage2(usage) {
10491
10517
  return metrics;
10492
10518
  }
10493
10519
  function aggregateAnthropicStreamChunks(chunks) {
10494
- const deltas = [];
10520
+ const fallbackTextDeltas = [];
10521
+ const contentBlocks = {};
10522
+ const contentBlockDeltas = {};
10495
10523
  let metrics = {};
10496
10524
  let metadata = {};
10525
+ let role;
10497
10526
  for (const event of chunks) {
10498
10527
  switch (event?.type) {
10499
10528
  case "message_start":
@@ -10501,15 +10530,43 @@ function aggregateAnthropicStreamChunks(chunks) {
10501
10530
  const initialMetrics = parseMetricsFromUsage2(event.message.usage);
10502
10531
  metrics = { ...metrics, ...initialMetrics };
10503
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
+ }
10504
10542
  break;
10505
10543
  case "content_block_delta":
10506
10544
  if (event.delta?.type === "text_delta") {
10507
10545
  const text = event.delta.text;
10508
10546
  if (text) {
10509
- 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);
10510
10559
  }
10511
10560
  }
10512
10561
  break;
10562
+ case "content_block_stop":
10563
+ finalizeContentBlock(
10564
+ event.index,
10565
+ contentBlocks,
10566
+ contentBlockDeltas,
10567
+ fallbackTextDeltas
10568
+ );
10569
+ break;
10513
10570
  case "message_delta":
10514
10571
  if (event.usage) {
10515
10572
  const finalMetrics = parseMetricsFromUsage2(event.usage);
@@ -10521,7 +10578,21 @@ function aggregateAnthropicStreamChunks(chunks) {
10521
10578
  break;
10522
10579
  }
10523
10580
  }
10524
- 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
+ }
10525
10596
  const finalized = finalizeAnthropicTokens(metrics);
10526
10597
  const filteredMetrics = Object.fromEntries(
10527
10598
  Object.entries(finalized).filter(
@@ -10534,6 +10605,49 @@ function aggregateAnthropicStreamChunks(chunks) {
10534
10605
  metadata
10535
10606
  };
10536
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
+ }
10537
10651
  function isAnthropicBase64ContentBlock(input) {
10538
10652
  return (input.type === "image" || input.type === "document") && isObject(input.source) && input.source.type === "base64";
10539
10653
  }
@@ -11656,12 +11770,15 @@ var claudeAgentSDKChannels = defineChannels(
11656
11770
  {
11657
11771
  query: channel({
11658
11772
  channelName: "query",
11659
- kind: "async"
11773
+ kind: "sync-stream"
11660
11774
  })
11661
11775
  }
11662
11776
  );
11663
11777
 
11664
11778
  // src/instrumentation/plugins/claude-agent-sdk-plugin.ts
11779
+ function isSubAgentToolName(toolName) {
11780
+ return toolName === "Agent" || toolName === "Task";
11781
+ }
11665
11782
  function filterSerializableOptions(options) {
11666
11783
  const allowedKeys = [
11667
11784
  "model",
@@ -11715,34 +11832,50 @@ function extractUsageFromMessage(message) {
11715
11832
  const cacheReadTokens = getNumberProperty(usage, "cache_read_input_tokens") || 0;
11716
11833
  const cacheCreationTokens = getNumberProperty(usage, "cache_creation_input_tokens") || 0;
11717
11834
  if (cacheReadTokens > 0 || cacheCreationTokens > 0) {
11718
- const cacheTokens = extractAnthropicCacheTokens(
11719
- cacheReadTokens,
11720
- cacheCreationTokens
11835
+ Object.assign(
11836
+ metrics,
11837
+ extractAnthropicCacheTokens(cacheReadTokens, cacheCreationTokens)
11721
11838
  );
11722
- Object.assign(metrics, cacheTokens);
11723
11839
  }
11724
11840
  if (Object.keys(metrics).length > 0) {
11725
11841
  Object.assign(metrics, finalizeAnthropicTokens(metrics));
11726
11842
  }
11727
11843
  return metrics;
11728
11844
  }
11729
- function buildLLMInput(prompt, conversationHistory) {
11730
- const promptMessage = typeof prompt === "string" ? { content: prompt, role: "user" } : void 0;
11731
- const inputParts = [
11732
- ...promptMessage ? [promptMessage] : [],
11733
- ...conversationHistory
11734
- ];
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];
11735
11859
  return inputParts.length > 0 ? inputParts : void 0;
11736
11860
  }
11737
- async function createLLMSpanForMessages(messages, prompt, conversationHistory, options, startTime, parentSpan) {
11738
- 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
+ }
11739
11868
  const lastMessage = messages[messages.length - 1];
11740
11869
  if (lastMessage.type !== "assistant" || !lastMessage.message?.usage) {
11741
11870
  return void 0;
11742
11871
  }
11743
11872
  const model = lastMessage.message.model || options.model;
11744
11873
  const usage = extractUsageFromMessage(lastMessage);
11745
- const input = buildLLMInput(prompt, conversationHistory);
11874
+ const input = buildLLMInput(
11875
+ prompt,
11876
+ conversationHistory,
11877
+ capturedPromptMessages
11878
+ );
11746
11879
  const outputs = messages.map(
11747
11880
  (m) => m.message?.content && m.message?.role ? { content: m.message.content, role: m.message.role } : void 0
11748
11881
  ).filter(
@@ -11750,21 +11883,359 @@ async function createLLMSpanForMessages(messages, prompt, conversationHistory, o
11750
11883
  );
11751
11884
  const span = startSpan({
11752
11885
  name: "anthropic.messages.create",
11886
+ parent: parentSpan,
11753
11887
  spanAttributes: {
11754
11888
  type: "llm" /* LLM */
11755
11889
  },
11756
- startTime,
11757
- parent: parentSpan
11890
+ startTime
11758
11891
  });
11759
11892
  span.log({
11760
11893
  input,
11761
- output: outputs,
11762
11894
  metadata: model ? { model } : void 0,
11763
- metrics: usage
11895
+ metrics: usage,
11896
+ output: outputs
11764
11897
  });
11765
11898
  await span.end();
11766
11899
  return lastMessage.message?.content && lastMessage.message?.role ? { content: lastMessage.message.content, role: lastMessage.message.role } : void 0;
11767
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
+ }
11768
12239
  var ClaudeAgentSDKPlugin = class extends BasePlugin {
11769
12240
  onEnable() {
11770
12241
  this.subscribeToQuery();
@@ -11775,19 +12246,36 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
11775
12246
  }
11776
12247
  this.unsubscribers = [];
11777
12248
  }
11778
- /**
11779
- * Subscribe to the query channel for agent interactions.
11780
- * Handles streaming responses and traces both the top-level agent task
11781
- * and individual LLM calls.
11782
- */
11783
12249
  subscribeToQuery() {
11784
12250
  const channel2 = claudeAgentSDKChannels.query.tracingChannel();
11785
12251
  const spans = /* @__PURE__ */ new WeakMap();
11786
12252
  const handlers = {
11787
12253
  start: (event) => {
11788
- const params = event.arguments[0];
11789
- const prompt = params?.prompt;
11790
- 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
+ }
11791
12279
  const span = startSpan({
11792
12280
  name: "Claude Agent",
11793
12281
  spanAttributes: {
@@ -11797,163 +12285,115 @@ var ClaudeAgentSDKPlugin = class extends BasePlugin {
11797
12285
  const startTime = getCurrentUnixTimestamp();
11798
12286
  try {
11799
12287
  span.log({
11800
- input: typeof prompt === "string" ? prompt : {
11801
- type: "streaming",
11802
- description: "AsyncIterable<ClaudeAgentSDKMessage>"
11803
- },
12288
+ input: typeof originalPrompt === "string" ? originalPrompt : promptIsAsyncIterable ? void 0 : originalPrompt !== void 0 ? String(originalPrompt) : void 0,
11804
12289
  metadata: filterSerializableOptions(options)
11805
12290
  });
11806
12291
  } catch (error) {
11807
12292
  console.error("Error extracting input for Claude Agent SDK:", error);
11808
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;
11809
12320
  spans.set(event, {
11810
- span,
11811
- startTime,
11812
- conversationHistory: [],
11813
- currentMessages: [],
12321
+ accumulatedOutputTokens: 0,
12322
+ activeToolSpans,
12323
+ capturedPromptMessages,
11814
12324
  currentMessageId: void 0,
11815
12325
  currentMessageStartTime: startTime,
11816
- 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
11817
12338
  });
11818
12339
  },
11819
- asyncEnd: (event) => {
11820
- const spanData = spans.get(event);
11821
- if (!spanData) {
12340
+ end: (event) => {
12341
+ const state = spans.get(event);
12342
+ if (!state) {
11822
12343
  return;
11823
12344
  }
11824
12345
  const eventResult = event.result;
11825
12346
  if (eventResult === void 0) {
11826
- spanData.span.end();
12347
+ state.span.end();
11827
12348
  spans.delete(event);
11828
12349
  return;
11829
12350
  }
11830
12351
  if (isAsyncIterable(eventResult)) {
11831
12352
  patchStreamIfNeeded(eventResult, {
11832
- onChunk: async (message) => {
11833
- const currentTime = getCurrentUnixTimestamp();
11834
- const params = event.arguments[0];
11835
- const prompt = params?.prompt;
11836
- const options = params?.options ?? {};
11837
- const messageId = message.message?.id;
11838
- if (messageId && messageId !== spanData.currentMessageId) {
11839
- if (spanData.currentMessages.length > 0) {
11840
- const finalMessage = await createLLMSpanForMessages(
11841
- spanData.currentMessages,
11842
- prompt,
11843
- spanData.conversationHistory,
11844
- options,
11845
- spanData.currentMessageStartTime,
11846
- await spanData.span.export()
11847
- );
11848
- if (finalMessage) {
11849
- spanData.conversationHistory.push(finalMessage);
11850
- }
11851
- const lastMessage = spanData.currentMessages[spanData.currentMessages.length - 1];
11852
- if (lastMessage?.message?.usage) {
11853
- const outputTokens = getNumberProperty(
11854
- lastMessage.message.usage,
11855
- "output_tokens"
11856
- ) || 0;
11857
- spanData.accumulatedOutputTokens += outputTokens;
11858
- }
11859
- spanData.currentMessages = [];
11860
- }
11861
- spanData.currentMessageId = messageId;
11862
- spanData.currentMessageStartTime = currentTime;
11863
- }
11864
- if (message.type === "assistant" && message.message?.usage) {
11865
- spanData.currentMessages.push(message);
11866
- }
11867
- if (message.type === "result" && message.usage) {
11868
- const finalUsageMetrics = extractUsageFromMessage(message);
11869
- if (spanData.currentMessages.length > 0 && finalUsageMetrics.completion_tokens !== void 0) {
11870
- const lastMessage = spanData.currentMessages[spanData.currentMessages.length - 1];
11871
- if (lastMessage?.message?.usage) {
11872
- const adjustedTokens = finalUsageMetrics.completion_tokens - spanData.accumulatedOutputTokens;
11873
- if (adjustedTokens >= 0) {
11874
- lastMessage.message.usage.output_tokens = adjustedTokens;
11875
- }
11876
- }
11877
- }
11878
- const result_metadata = {};
11879
- if (message.num_turns !== void 0) {
11880
- result_metadata.num_turns = message.num_turns;
11881
- }
11882
- if (message.session_id !== void 0) {
11883
- result_metadata.session_id = message.session_id;
11884
- }
11885
- if (Object.keys(result_metadata).length > 0) {
11886
- spanData.span.log({
11887
- metadata: result_metadata
11888
- });
11889
- }
11890
- }
11891
- },
11892
- onComplete: async () => {
11893
- try {
11894
- const params = event.arguments[0];
11895
- const prompt = params?.prompt;
11896
- const options = params?.options ?? {};
11897
- if (spanData.currentMessages.length > 0) {
11898
- const finalMessage = await createLLMSpanForMessages(
11899
- spanData.currentMessages,
11900
- prompt,
11901
- spanData.conversationHistory,
11902
- options,
11903
- spanData.currentMessageStartTime,
11904
- await spanData.span.export()
11905
- );
11906
- if (finalMessage) {
11907
- spanData.conversationHistory.push(finalMessage);
11908
- }
11909
- }
11910
- spanData.span.log({
11911
- output: spanData.conversationHistory.length > 0 ? spanData.conversationHistory[spanData.conversationHistory.length - 1] : void 0
11912
- });
11913
- } catch (error) {
12353
+ onChunk: (message) => {
12354
+ maybeTrackToolUseContext(state, message);
12355
+ state.processing = state.processing.then(() => handleStreamMessage(state, message)).catch((error) => {
11914
12356
  console.error(
11915
- "Error extracting output for Claude Agent SDK:",
12357
+ "Error processing Claude Agent SDK stream chunk:",
11916
12358
  error
11917
12359
  );
11918
- } finally {
11919
- spanData.span.end();
12360
+ });
12361
+ },
12362
+ onComplete: () => {
12363
+ void state.processing.then(() => finalizeQuerySpan(state)).finally(() => {
11920
12364
  spans.delete(event);
11921
- }
12365
+ });
11922
12366
  },
11923
12367
  onError: (error) => {
11924
- spanData.span.log({
11925
- 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);
11926
12374
  });
11927
- spanData.span.end();
11928
- spans.delete(event);
11929
12375
  }
11930
12376
  });
11931
- } else {
11932
- try {
11933
- spanData.span.log({
11934
- output: eventResult
11935
- });
11936
- } catch (error) {
11937
- console.error(
11938
- "Error extracting output for Claude Agent SDK:",
11939
- error
11940
- );
11941
- } finally {
11942
- spanData.span.end();
11943
- spans.delete(event);
11944
- }
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);
11945
12386
  }
11946
12387
  },
11947
12388
  error: (event) => {
11948
- const spanData = spans.get(event);
11949
- if (!spanData || !event.error) {
12389
+ const state = spans.get(event);
12390
+ if (!state || !event.error) {
11950
12391
  return;
11951
12392
  }
11952
- const { span } = spanData;
11953
- span.log({
12393
+ state.span.log({
11954
12394
  error: event.error.message
11955
12395
  });
11956
- span.end();
12396
+ state.span.end();
11957
12397
  spans.delete(event);
11958
12398
  }
11959
12399
  };
@@ -11977,6 +12417,18 @@ var googleGenAIChannels = defineChannels("@google/genai", {
11977
12417
  });
11978
12418
 
11979
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
+ }
11980
12432
  var GoogleGenAIPlugin = class extends BasePlugin {
11981
12433
  onEnable() {
11982
12434
  this.subscribeToGoogleGenAIChannels();
@@ -11985,51 +12437,282 @@ var GoogleGenAIPlugin = class extends BasePlugin {
11985
12437
  this.unsubscribers = unsubscribeAll(this.unsubscribers);
11986
12438
  }
11987
12439
  subscribeToGoogleGenAIChannels() {
11988
- this.unsubscribers.push(
11989
- traceAsyncChannel(googleGenAIChannels.generateContent, {
11990
- name: "google-genai.generateContent",
11991
- type: "llm" /* LLM */,
11992
- extractInput: ([params]) => {
11993
- const input = serializeInput(params);
11994
- const metadata = extractMetadata(params);
11995
- return {
11996
- input,
11997
- metadata: { ...metadata, provider: "google-genai" }
11998
- };
11999
- },
12000
- extractOutput: (result) => {
12001
- return result;
12002
- },
12003
- extractMetrics: (result, startTime) => {
12004
- return extractGenerateContentMetrics(result, startTime);
12005
- }
12006
- })
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
+ }
12007
12465
  );
12008
- this.unsubscribers.push(
12009
- traceStreamingChannel(googleGenAIChannels.generateContentStream, {
12010
- name: "google-genai.generateContentStream",
12011
- type: "llm" /* LLM */,
12012
- extractInput: ([params]) => {
12466
+ const handlers = {
12467
+ start: (event) => {
12468
+ ensureSpanState(states, event, () => {
12469
+ const params = event.arguments[0];
12013
12470
  const input = serializeInput(params);
12014
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
+ });
12015
12479
  return {
12016
- input,
12017
- metadata: { ...metadata, provider: "google-genai" }
12480
+ span,
12481
+ startTime: getCurrentUnixTimestamp()
12018
12482
  };
12019
- },
12020
- extractOutput: (result) => {
12021
- return result;
12022
- },
12023
- extractMetrics: () => {
12024
- return {};
12025
- },
12026
- aggregateChunks: (chunks, _result, _endEvent, startTime) => {
12027
- return aggregateGenerateContentChunks(chunks, startTime);
12483
+ });
12484
+ },
12485
+ asyncEnd: (event) => {
12486
+ const spanState = states.get(event);
12487
+ if (!spanState) {
12488
+ return;
12028
12489
  }
12029
- })
12030
- );
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
+ });
12031
12539
  }
12032
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
+ }
12033
12716
  function serializeInput(params) {
12034
12717
  const input = {
12035
12718
  model: params.model,
@@ -12038,11 +12721,13 @@ function serializeInput(params) {
12038
12721
  if (params.config) {
12039
12722
  const config = tryToDict(params.config);
12040
12723
  if (config) {
12041
- const tools = serializeTools(params);
12042
- if (tools) {
12043
- config.tools = tools;
12044
- }
12045
- 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;
12046
12731
  }
12047
12732
  }
12048
12733
  return input;
@@ -12129,12 +12814,18 @@ function extractMetadata(params) {
12129
12814
  });
12130
12815
  }
12131
12816
  }
12817
+ const tools = serializeTools(params);
12818
+ if (tools) {
12819
+ metadata.tools = tools;
12820
+ }
12132
12821
  return metadata;
12133
12822
  }
12134
12823
  function extractGenerateContentMetrics(response, startTime) {
12135
12824
  const metrics = {};
12136
- if (startTime) {
12825
+ if (startTime !== void 0) {
12137
12826
  const end = getCurrentUnixTimestamp();
12827
+ metrics.start = startTime;
12828
+ metrics.end = end;
12138
12829
  metrics.duration = end - startTime;
12139
12830
  }
12140
12831
  if (response?.usageMetadata) {
@@ -12159,19 +12850,18 @@ function populateUsageMetrics(metrics, usage) {
12159
12850
  metrics.completion_reasoning_tokens = usage.thoughtsTokenCount;
12160
12851
  }
12161
12852
  }
12162
- function aggregateGenerateContentChunks(chunks, startTime) {
12163
- const metrics = {};
12164
- if (startTime !== void 0) {
12165
- const end = getCurrentUnixTimestamp();
12166
- metrics.duration = end - startTime;
12167
- }
12168
- let firstTokenTime = null;
12169
- if (chunks.length > 0 && firstTokenTime === null && startTime !== void 0) {
12170
- firstTokenTime = getCurrentUnixTimestamp();
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) {
12171
12861
  metrics.time_to_first_token = firstTokenTime - startTime;
12172
12862
  }
12173
12863
  if (chunks.length === 0) {
12174
- return { output: {}, metrics };
12864
+ return { aggregated: {}, metrics };
12175
12865
  }
12176
12866
  let text = "";
12177
12867
  let thoughtText = "";
@@ -12207,7 +12897,7 @@ function aggregateGenerateContentChunks(chunks, startTime) {
12207
12897
  }
12208
12898
  }
12209
12899
  }
12210
- const output = {};
12900
+ const aggregated = {};
12211
12901
  const parts = [];
12212
12902
  if (thoughtText) {
12213
12903
  parts.push({ text: thoughtText, thought: true });
@@ -12233,16 +12923,25 @@ function aggregateGenerateContentChunks(chunks, startTime) {
12233
12923
  }
12234
12924
  candidates.push(candidateDict);
12235
12925
  }
12236
- output.candidates = candidates;
12926
+ aggregated.candidates = candidates;
12237
12927
  }
12238
12928
  if (usageMetadata) {
12239
- output.usageMetadata = usageMetadata;
12929
+ aggregated.usageMetadata = usageMetadata;
12240
12930
  populateUsageMetrics(metrics, usageMetadata);
12241
12931
  }
12242
12932
  if (text) {
12243
- output.text = 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
+ }
12244
12943
  }
12245
- return { output, metrics };
12944
+ return cleaned;
12246
12945
  }
12247
12946
  function tryToDict(obj) {
12248
12947
  if (obj === null || obj === void 0) {