From ef004da3ff273148b2143b55a5ae2b9daef51f51 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 14 Apr 2026 11:20:00 +0200 Subject: [PATCH 1/4] implement --- .../core/src/integrations/conversationId.ts | 8 +++++++ .../lib/integrations/conversationId.test.ts | 24 +++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/core/src/integrations/conversationId.ts b/packages/core/src/integrations/conversationId.ts index c11b587d3a71..8b703a2f95b9 100644 --- a/packages/core/src/integrations/conversationId.ts +++ b/packages/core/src/integrations/conversationId.ts @@ -4,6 +4,7 @@ import { defineIntegration } from '../integration'; import { GEN_AI_CONVERSATION_ID_ATTRIBUTE } from '../semanticAttributes'; import type { IntegrationFn } from '../types-hoist/integration'; import type { Span } from '../types-hoist/span'; +import { spanToJSON } from '../utils/spanUtils'; const INTEGRATION_NAME = 'ConversationId'; @@ -18,6 +19,13 @@ const _conversationIdIntegration = (() => { const conversationId = scopeData.conversationId || isolationScopeData.conversationId; if (conversationId) { + const { op } = spanToJSON(span); + + // Only apply conversation ID to gen_ai spans + if (!op?.startsWith('gen_ai.')) { + return; + } + span.setAttribute(GEN_AI_CONVERSATION_ID_ATTRIBUTE, conversationId); } }); diff --git a/packages/core/test/lib/integrations/conversationId.test.ts b/packages/core/test/lib/integrations/conversationId.test.ts index e9ea9cc50d45..be69a1476e83 100644 --- a/packages/core/test/lib/integrations/conversationId.test.ts +++ b/packages/core/test/lib/integrations/conversationId.test.ts @@ -26,7 +26,7 @@ describe('ConversationId', () => { it('applies conversation ID from current scope to span', () => { getCurrentScope().setConversationId('conv_test_123'); - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBe('conv_test_123'); }); @@ -35,7 +35,7 @@ describe('ConversationId', () => { it('applies conversation ID from isolation scope when current scope does not have one', () => { getIsolationScope().setConversationId('conv_isolation_456'); - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBe('conv_isolation_456'); }); @@ -45,14 +45,14 @@ describe('ConversationId', () => { getCurrentScope().setConversationId('conv_current_789'); getIsolationScope().setConversationId('conv_isolation_999'); - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBe('conv_current_789'); }); }); it('does not apply conversation ID when not set in scope', () => { - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBeUndefined(); }); @@ -62,7 +62,7 @@ describe('ConversationId', () => { getCurrentScope().setConversationId('conv_test_123'); getCurrentScope().setConversationId(null); - startSpan({ name: 'test-span' }, span => { + startSpan({ name: 'test-span', op: 'gen_ai.chat' }, span => { const spanJSON = spanToJSON(span); expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBeUndefined(); }); @@ -71,8 +71,8 @@ describe('ConversationId', () => { it('applies conversation ID to nested spans', () => { getCurrentScope().setConversationId('conv_nested_abc'); - startSpan({ name: 'parent-span' }, () => { - startSpan({ name: 'child-span' }, childSpan => { + startSpan({ name: 'parent-span', op: 'gen_ai.invoke_agent' }, () => { + startSpan({ name: 'child-span', op: 'gen_ai.chat' }, childSpan => { const childJSON = spanToJSON(childSpan); expect(childJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBe('conv_nested_abc'); }); @@ -85,6 +85,7 @@ describe('ConversationId', () => { startSpan( { name: 'test-span', + op: 'gen_ai.chat', attributes: { [GEN_AI_CONVERSATION_ID_ATTRIBUTE]: 'conv_explicit', }, @@ -95,4 +96,13 @@ describe('ConversationId', () => { }, ); }); + + it('does not apply conversation ID to non-gen_ai spans', () => { + getCurrentScope().setConversationId('conv_test_123'); + + startSpan({ name: 'db-query', op: 'db.query' }, span => { + const spanJSON = spanToJSON(span); + expect(spanJSON.data[GEN_AI_CONVERSATION_ID_ATTRIBUTE]).toBeUndefined(); + }); + }); }); From 4069b08d21c4abd4c84056084c3042453d6b7909 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 14 Apr 2026 11:22:54 +0200 Subject: [PATCH 2/4] . --- packages/core/src/integrations/conversationId.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/integrations/conversationId.ts b/packages/core/src/integrations/conversationId.ts index 8b703a2f95b9..f9dbeaf9fef9 100644 --- a/packages/core/src/integrations/conversationId.ts +++ b/packages/core/src/integrations/conversationId.ts @@ -19,10 +19,8 @@ const _conversationIdIntegration = (() => { const conversationId = scopeData.conversationId || isolationScopeData.conversationId; if (conversationId) { - const { op } = spanToJSON(span); - // Only apply conversation ID to gen_ai spans - if (!op?.startsWith('gen_ai.')) { + if (!spanToJSON(span).op?.startsWith('gen_ai.')) { return; } From 81df583f3488ce6adc7d961886f33d70621701bb Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 14 Apr 2026 11:39:47 +0200 Subject: [PATCH 3/4] bump size --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 4100751f2c40..2d5baacbfd88 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -234,7 +234,7 @@ module.exports = [ path: createCDNPath('bundle.min.js'), gzip: false, brotli: false, - limit: '83 KB', + limit: '83.5 KB', }, { name: 'CDN Bundle (incl. Tracing) - uncompressed', From 0dae40f1efea892d690015889efd13e732ee94ef Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 14 Apr 2026 15:08:19 +0200 Subject: [PATCH 4/4] fix vercel tests --- packages/core/src/integrations/conversationId.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/core/src/integrations/conversationId.ts b/packages/core/src/integrations/conversationId.ts index f9dbeaf9fef9..445e3327419b 100644 --- a/packages/core/src/integrations/conversationId.ts +++ b/packages/core/src/integrations/conversationId.ts @@ -19,8 +19,13 @@ const _conversationIdIntegration = (() => { const conversationId = scopeData.conversationId || isolationScopeData.conversationId; if (conversationId) { - // Only apply conversation ID to gen_ai spans - if (!spanToJSON(span).op?.startsWith('gen_ai.')) { + const { op, data: attributes, description: name } = spanToJSON(span); + + // Only apply conversation ID to gen_ai spans. + // We also check for Vercel AI spans (ai.operationId attribute or ai.* span name) + // because the Vercel AI integration sets the gen_ai.* op in its own spanStart handler + // which fires after this, so the op is not yet available at this point. + if (!op?.startsWith('gen_ai.') && !attributes['ai.operationId'] && !name?.startsWith('ai.')) { return; }