@@ -62,10 +62,17 @@ function onVercelAiSpanStart(span: Span): void {
6262
6363function vercelAiEventProcessor ( event : Event ) : Event {
6464 if ( event . type === 'transaction' && event . spans ) {
65+ // First pass: process all spans normally
6566 for ( const span of event . spans ) {
6667 // this mutates spans in-place
6768 processEndedVercelAiSpan ( span ) ;
6869 }
70+
71+ // Second pass: accumulate tokens for gen_ai.invoke_agent spans
72+ // TODO: Determine how to handle token aggregation for tool call spans.
73+ for ( const span of event . spans ) {
74+ accumulateTokensFromChildSpans ( span , event . spans ) ;
75+ }
6976 }
7077 return event ;
7178}
@@ -241,6 +248,48 @@ export function addVercelAiProcessors(client: Client): void {
241248 client . addEventProcessor ( Object . assign ( vercelAiEventProcessor , { id : 'VercelAiEventProcessor' } ) ) ;
242249}
243250
251+ /**
252+ * For the gen_ai.invoke_agent span, correctly iterate over child spans and aggregate:
253+ * - Input tokens from client LLM child spans that include this attribute.
254+ * - Output tokens from client LLM child spans that include this attribute.
255+ * - Total tokens from client LLM child spans that include this attribute.
256+ *
257+ * Only immediate children of the invoke_agent span need to be considered,
258+ * since aggregation will automatically occur for each parent span.
259+ *
260+ */
261+ function accumulateTokensFromChildSpans ( spanJSON : SpanJSON , allSpans : SpanJSON [ ] ) : void {
262+ if ( spanJSON . op !== 'gen_ai.invoke_agent' ) {
263+ return ;
264+ }
265+ const childSpans = allSpans . filter ( childSpan => childSpan . parent_span_id === spanJSON . span_id ) ;
266+
267+ let totalInputTokens = 0 ;
268+ let totalOutputTokens = 0 ;
269+
270+ for ( const childSpan of childSpans ) {
271+ const inputTokens = childSpan . data [ GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE ] ;
272+ const outputTokens = childSpan . data [ GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE ] ;
273+
274+ if ( typeof inputTokens === 'number' ) {
275+ totalInputTokens += inputTokens ;
276+ }
277+ if ( typeof outputTokens === 'number' ) {
278+ totalOutputTokens += outputTokens ;
279+ }
280+ }
281+
282+ if ( totalInputTokens > 0 ) {
283+ spanJSON . data [ GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE ] = totalInputTokens ;
284+ }
285+ if ( totalOutputTokens > 0 ) {
286+ spanJSON . data [ GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE ] = totalOutputTokens ;
287+ }
288+ if ( totalInputTokens > 0 || totalOutputTokens > 0 ) {
289+ spanJSON . data [ 'gen_ai.usage.total_tokens' ] = totalInputTokens + totalOutputTokens ;
290+ }
291+ }
292+
244293function addProviderMetadataToAttributes ( attributes : SpanAttributes ) : void {
245294 const providerMetadata = attributes [ AI_RESPONSE_PROVIDER_METADATA_ATTRIBUTE ] as string | undefined ;
246295 if ( providerMetadata ) {
0 commit comments