@@ -530,45 +530,37 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe
530530 // A single model instance shared by two concurrent `streamText` calls carries only one
531531 // captured-parent slot, so both model calls must still land under their own `invoke_agent` — not
532532 // collapse onto whichever operation resolved the shared model last.
533- // `ai` v7 publishes the top-level `streamText`/`step` channel events through a code path that
534- // loads `node:diagnostics_channel` via `process.getBuiltinModule()`, which was only added in
535- // Node 20.16 / 22.3 and never backported to Node 18. On Node 18 that lookup returns undefined,
536- // so the `streamText` event is never published and no `invoke_agent` span is created. The
537- // non-streaming ops load the channel via dynamic `import()` and are unaffected.
538- test . skipIf ( version === '7' && nodeVersion === 18 ) (
539- 'parents concurrent streamText calls that share one model instance correctly' ,
540- async ( ) => {
541- await createRunner ( )
542- . expect ( { transaction : { transaction : 'main' } } )
543- . expect ( {
544- span : container => {
545- const invokeAgents = container . items . filter (
546- span => span . attributes ?. [ 'sentry.op' ] ?. value === 'gen_ai.invoke_agent' ,
547- ) ;
548- const generateContents = container . items . filter (
549- span => span . attributes ?. [ 'sentry.op' ] ?. value === 'gen_ai.generate_content' ,
550- ) ;
551-
552- // Two concurrent operations -> two invoke_agent + two generate_content spans.
553- expect ( invokeAgents ) . toHaveLength ( 2 ) ;
554- expect ( generateContents ) . toHaveLength ( 2 ) ;
555-
556- const agentSpanIds = new Set ( invokeAgents . map ( span => span . span_id ) ) ;
557-
558- // Each model call lands under an invoke_agent span...
559- for ( const span of generateContents ) {
560- expect ( agentSpanIds . has ( span . parent_span_id ! ) ) . toBe ( true ) ;
561- }
562- // ...a distinct one each (no cross-attribution despite the shared model instance)...
563- expect ( new Set ( generateContents . map ( span => span . parent_span_id ) ) . size ) . toBe ( 2 ) ;
564- // ...and both operations sit under the same `main` parent.
565- expect ( new Set ( invokeAgents . map ( span => span . parent_span_id ) ) . size ) . toBe ( 1 ) ;
566- } ,
567- } )
568- . start ( )
569- . completed ( ) ;
570- } ,
571- ) ;
533+ test ( 'parents concurrent streamText calls that share one model instance correctly' , async ( ) => {
534+ await createRunner ( )
535+ . expect ( { transaction : { transaction : 'main' } } )
536+ . expect ( {
537+ span : container => {
538+ const invokeAgents = container . items . filter (
539+ span => span . attributes ?. [ 'sentry.op' ] ?. value === 'gen_ai.invoke_agent' ,
540+ ) ;
541+ const generateContents = container . items . filter (
542+ span => span . attributes ?. [ 'sentry.op' ] ?. value === 'gen_ai.generate_content' ,
543+ ) ;
544+
545+ // Two concurrent operations -> two invoke_agent + two generate_content spans.
546+ expect ( invokeAgents ) . toHaveLength ( 2 ) ;
547+ expect ( generateContents ) . toHaveLength ( 2 ) ;
548+
549+ const agentSpanIds = new Set ( invokeAgents . map ( span => span . span_id ) ) ;
550+
551+ // Each model call lands under an invoke_agent span...
552+ for ( const span of generateContents ) {
553+ expect ( agentSpanIds . has ( span . parent_span_id ! ) ) . toBe ( true ) ;
554+ }
555+ // ...a distinct one each (no cross-attribution despite the shared model instance)...
556+ expect ( new Set ( generateContents . map ( span => span . parent_span_id ) ) . size ) . toBe ( 2 ) ;
557+ // ...and both operations sit under the same `main` parent.
558+ expect ( new Set ( invokeAgents . map ( span => span . parent_span_id ) ) . size ) . toBe ( 1 ) ;
559+ } ,
560+ } )
561+ . start ( )
562+ . completed ( ) ;
563+ } ) ;
572564 } ,
573565 {
574566 additionalDependencies : {
@@ -582,40 +574,32 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe
582574 'scenario-stream-text.mjs' ,
583575 'instrument.mjs' ,
584576 ( createRunner , test ) => {
585- // `ai` v7 publishes the top-level `streamText`/`step` channel events through a code path that
586- // loads `node:diagnostics_channel` via `process.getBuiltinModule()`, which was only added in
587- // Node 20.16 / 22.3 and never backported to Node 18. On Node 18 that lookup returns undefined,
588- // so the `streamText` event is never published and no `invoke_agent` span is created. The
589- // non-streaming ops load the channel via dynamic `import()` and are unaffected.
590- test . skipIf ( version === '7' && nodeVersion === 18 ) (
591- 'creates streamText spans with the model call parented to invoke_agent' ,
592- async ( ) => {
593- await createRunner ( )
594- . expect ( { transaction : { transaction : 'main' } } )
595- . expect ( {
596- span : container => {
597- // Every emitted gen_ai span carries the version-appropriate origin.
598- container . items
599- . filter ( s => String ( s . attributes ?. [ 'sentry.op' ] ?. value ?? '' ) . startsWith ( 'gen_ai.' ) )
600- . forEach ( s => expect ( s . attributes ?. [ 'sentry.origin' ] ?. value ) . toBe ( expectedOrigin ) ) ;
601- const invokeAgent = container . items . find (
602- span => span . attributes ?. [ 'sentry.op' ] ?. value === 'gen_ai.invoke_agent' ,
603- ) ! ;
604- expect ( invokeAgent ) . toBeDefined ( ) ;
605- expect ( invokeAgent . attributes ?. [ 'vercel.ai.operationId' ] ?. value ) . toBe ( 'ai.streamText' ) ;
606-
607- const generateContent = container . items . find (
608- span => span . attributes ?. [ 'sentry.op' ] ?. value === 'gen_ai.generate_content' ,
609- ) ! ;
610- expect ( generateContent ) . toBeDefined ( ) ;
611- expect ( generateContent . parent_span_id ) . toBe ( invokeAgent . span_id ) ;
612- expect ( generateContent . attributes ?. [ 'vercel.ai.operationId' ] ?. value ) . toBe ( 'ai.streamText.doStream' ) ;
613- } ,
614- } )
615- . start ( )
616- . completed ( ) ;
617- } ,
618- ) ;
577+ test ( 'creates streamText spans with the model call parented to invoke_agent' , async ( ) => {
578+ await createRunner ( )
579+ . expect ( { transaction : { transaction : 'main' } } )
580+ . expect ( {
581+ span : container => {
582+ // Every emitted gen_ai span carries the version-appropriate origin.
583+ container . items
584+ . filter ( s => String ( s . attributes ?. [ 'sentry.op' ] ?. value ?? '' ) . startsWith ( 'gen_ai.' ) )
585+ . forEach ( s => expect ( s . attributes ?. [ 'sentry.origin' ] ?. value ) . toBe ( expectedOrigin ) ) ;
586+ const invokeAgent = container . items . find (
587+ span => span . attributes ?. [ 'sentry.op' ] ?. value === 'gen_ai.invoke_agent' ,
588+ ) ! ;
589+ expect ( invokeAgent ) . toBeDefined ( ) ;
590+ expect ( invokeAgent . attributes ?. [ 'vercel.ai.operationId' ] ?. value ) . toBe ( 'ai.streamText' ) ;
591+
592+ const generateContent = container . items . find (
593+ span => span . attributes ?. [ 'sentry.op' ] ?. value === 'gen_ai.generate_content' ,
594+ ) ! ;
595+ expect ( generateContent ) . toBeDefined ( ) ;
596+ expect ( generateContent . parent_span_id ) . toBe ( invokeAgent . span_id ) ;
597+ expect ( generateContent . attributes ?. [ 'vercel.ai.operationId' ] ?. value ) . toBe ( 'ai.streamText.doStream' ) ;
598+ } ,
599+ } )
600+ . start ( )
601+ . completed ( ) ;
602+ } ) ;
619603 } ,
620604 {
621605 additionalDependencies : {
0 commit comments