|
7 | 7 | SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, |
8 | 8 | SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, |
9 | 9 | } from '../semanticAttributes'; |
| 10 | +import { GEN_AI_CONVERSATION_ID_ATTRIBUTE } from '../tracing/ai/gen-ai-attributes'; |
10 | 11 | import type { SentrySpan } from '../tracing/sentrySpan'; |
11 | 12 | import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from '../tracing/spanstatus'; |
12 | 13 | import { getCapturedScopesOnSpan } from '../tracing/utils'; |
@@ -149,6 +150,28 @@ export function spanToJSON(span: Span): SpanJSON { |
149 | 150 | // Handle a span from @opentelemetry/sdk-base-trace's `Span` class |
150 | 151 | if (spanIsOpenTelemetrySdkTraceBaseSpan(span)) { |
151 | 152 | const { attributes, startTime, name, endTime, status, links } = span; |
| 153 | + // Automatically inject conversation ID from scope if not already set |
| 154 | + if (!attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]) { |
| 155 | + // First try captured scopes (scopes at span creation time) |
| 156 | + const capturedScopes = getCapturedScopesOnSpan(span); |
| 157 | + // Try captured isolation scope first (where setConversationId sets it) |
| 158 | + let conversationId = capturedScopes.isolationScope?.getConversationId(); |
| 159 | + |
| 160 | + // Fallback to regular scope |
| 161 | + if (!conversationId) { |
| 162 | + conversationId = capturedScopes.scope?.getConversationId(); |
| 163 | + } |
| 164 | + |
| 165 | + // If not found in captured scopes, try current scopes (from AsyncLocalStorage) |
| 166 | + if (!conversationId) { |
| 167 | + const currentScope = getCurrentScope(); |
| 168 | + conversationId = currentScope?.getConversationId(); |
| 169 | + } |
| 170 | + |
| 171 | + if (conversationId) { |
| 172 | + attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE] = conversationId; |
| 173 | + } |
| 174 | + } |
152 | 175 |
|
153 | 176 | // In preparation for the next major of OpenTelemetry, we want to support |
154 | 177 | // looking up the parent span id according to the new API |
|
0 commit comments