dd-trace 5.104.0 → 5.106.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.
Files changed (159) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +16 -2
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
  11. package/packages/datadog-instrumentations/src/cucumber.js +390 -157
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  14. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  15. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  19. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  27. package/packages/datadog-instrumentations/src/hono.js +54 -3
  28. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  29. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  30. package/packages/datadog-instrumentations/src/jest.js +360 -150
  31. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  32. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  33. package/packages/datadog-instrumentations/src/nats.js +182 -0
  34. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  35. package/packages/datadog-instrumentations/src/openai.js +33 -18
  36. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  37. package/packages/datadog-instrumentations/src/pino.js +17 -5
  38. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  39. package/packages/datadog-instrumentations/src/router.js +76 -32
  40. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  41. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  42. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  45. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  46. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  47. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  48. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  49. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  50. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  51. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  52. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  53. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  54. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  55. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  56. package/packages/datadog-plugin-http/src/server.js +40 -15
  57. package/packages/datadog-plugin-jest/src/index.js +11 -3
  58. package/packages/datadog-plugin-jest/src/util.js +15 -8
  59. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  60. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  61. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  62. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  63. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  64. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  65. package/packages/datadog-plugin-nats/src/index.js +20 -0
  66. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  67. package/packages/datadog-plugin-nats/src/util.js +33 -0
  68. package/packages/datadog-plugin-next/src/index.js +5 -3
  69. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  70. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  71. package/packages/datadog-plugin-pino/src/index.js +42 -0
  72. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  73. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  74. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  75. package/packages/datadog-plugin-router/src/index.js +33 -44
  76. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  77. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  78. package/packages/datadog-plugin-winston/src/index.js +30 -0
  79. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  80. package/packages/dd-trace/src/aiguard/index.js +1 -1
  81. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  82. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  83. package/packages/dd-trace/src/appsec/index.js +1 -1
  84. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  85. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  86. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  87. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  88. package/packages/dd-trace/src/baggage.js +7 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  90. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  91. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  92. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  93. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  94. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  96. package/packages/dd-trace/src/encode/0.4.js +124 -108
  97. package/packages/dd-trace/src/encode/0.5.js +114 -26
  98. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  99. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  100. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  101. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  102. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  103. package/packages/dd-trace/src/id.js +15 -0
  104. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +92 -6
  105. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
  106. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  107. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  108. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  109. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  110. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  111. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  112. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  113. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  114. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  115. package/packages/dd-trace/src/llmobs/util.js +66 -3
  116. package/packages/dd-trace/src/log/index.js +1 -1
  117. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  118. package/packages/dd-trace/src/msgpack/index.js +96 -2
  119. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  120. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  121. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  122. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  123. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  124. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  125. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +22 -3
  126. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  127. package/packages/dd-trace/src/opentracing/propagation/text_map.js +64 -77
  128. package/packages/dd-trace/src/opentracing/span.js +59 -19
  129. package/packages/dd-trace/src/opentracing/span_context.js +50 -3
  130. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  131. package/packages/dd-trace/src/plugins/database.js +7 -6
  132. package/packages/dd-trace/src/plugins/index.js +4 -0
  133. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  134. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  135. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  136. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  137. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  138. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  139. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  140. package/packages/dd-trace/src/priority_sampler.js +2 -2
  141. package/packages/dd-trace/src/profiling/config.js +10 -23
  142. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
  143. package/packages/dd-trace/src/profiling/profiler.js +21 -11
  144. package/packages/dd-trace/src/profiling/profilers/wall.js +12 -7
  145. package/packages/dd-trace/src/sampling_rule.js +7 -7
  146. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  147. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  148. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  149. package/packages/dd-trace/src/span_format.js +190 -58
  150. package/packages/dd-trace/src/spanleak.js +1 -1
  151. package/packages/dd-trace/src/standalone/index.js +3 -3
  152. package/packages/dd-trace/src/tagger.js +0 -2
  153. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  154. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  155. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  156. package/vendor/dist/protobufjs/index.js +1 -1
  157. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  158. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  159. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -41,7 +41,7 @@ const VERCEL_AI_GENERATION_METADATA_PREFIX = 'ai.settings.'
41
41
  */
42
42
  function getSpanTags (ctx) {
43
43
  const span = ctx.currentStore?.span
44
- return /** @type {SpanTags} */ (ctx.attributes ?? span?.context()._tags ?? {})
44
+ return /** @type {SpanTags} */ (ctx.attributes ?? span?.context().getTags() ?? {})
45
45
  }
46
46
 
47
47
  /**
@@ -66,11 +66,22 @@ function getOperation (span) {
66
66
  }
67
67
 
68
68
  /**
69
- * Get the LLM token usage from the span tags
70
- * Supports both AI SDK v4 (promptTokens/completionTokens) and v5 (inputTokens/outputTokens)
71
- * @template T extends {inputTokens: number, outputTokens: number, totalTokens: number}
72
- * @param {T} tags
73
- * @returns {Pick<T, 'inputTokens' | 'outputTokens' | 'totalTokens'>}
69
+ * Get the LLM token usage from the span tags.
70
+ *
71
+ * Supports both AI SDK v4 (promptTokens/completionTokens) and v5+
72
+ * (inputTokens/outputTokens), and surfaces prompt-cache metrics for providers
73
+ * that report them. The AI SDK convention is that `inputTokens` already
74
+ * includes cached tokens, so cache reads are reported as a subset of input
75
+ * tokens rather than added on top.
76
+ *
77
+ * @param {SpanTags} tags
78
+ * @returns {{
79
+ * inputTokens?: number,
80
+ * outputTokens?: number,
81
+ * totalTokens?: number,
82
+ * cacheReadTokens?: number,
83
+ * cacheWriteTokens?: number
84
+ * }}
74
85
  */
75
86
  function getUsage (tags) {
76
87
  const usage = {}
@@ -87,9 +98,84 @@ function getUsage (tags) {
87
98
  const totalTokens = tags['ai.usage.totalTokens'] ?? (inputTokens + outputTokens)
88
99
  if (!Number.isNaN(totalTokens)) usage.totalTokens = totalTokens
89
100
 
101
+ // Prompt-cache metrics. AI SDK v6 standardizes cache READ tokens via
102
+ // `ai.usage.cachedInputTokens`; cache WRITE tokens (and earlier AI SDK
103
+ // versions / providers that don't fill `cachedInputTokens`) are only
104
+ // available through provider-specific `ai.response.providerMetadata`.
105
+ // Skip zero values: the AI SDK sets `cachedInputTokens=0` on every span
106
+ // regardless of provider, so emitting it would add noise to spans that
107
+ // don't actually use prompt caching (e.g. OpenAI).
108
+ const providerCache = getProviderCacheTokens(tags['ai.response.providerMetadata'])
109
+
110
+ const cacheReadTokens = tags['ai.usage.cachedInputTokens'] ?? providerCache.cacheReadTokens
111
+ if (cacheReadTokens) usage.cacheReadTokens = cacheReadTokens
112
+
113
+ if (providerCache.cacheWriteTokens) usage.cacheWriteTokens = providerCache.cacheWriteTokens
114
+
115
+ // Normalize `inputTokens` to the sum convention used by `bedrockruntime.js`.
116
+ // Some SDK combinations (e.g. `ai@5` + `@ai-sdk/amazon-bedrock@3`) pass the
117
+ // raw fresh count through, which makes `nonCached = input - cacheRead -
118
+ // cacheWrite` go negative downstream.
119
+ //
120
+ // Detection: if `inputTokens < cacheSum`, the value cannot already be a sum
121
+ // that includes them (non-negative arithmetic). This is provider/version
122
+ // agnostic and won't double-count on stacks where the SDK already
123
+ // normalized (`ai@6` + `bedrock@4` / `anthropic@3`, OpenAI, Google).
124
+ if (usage.inputTokens != null) {
125
+ const cacheSum = (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0)
126
+ if (usage.inputTokens < cacheSum) {
127
+ usage.inputTokens += cacheSum
128
+ if (usage.totalTokens != null) {
129
+ usage.totalTokens = usage.inputTokens + (usage.outputTokens || 0)
130
+ }
131
+ }
132
+ }
133
+
90
134
  return usage
91
135
  }
92
136
 
137
+ /**
138
+ * Extract prompt-cache token counts from the stringified
139
+ * `ai.response.providerMetadata` attribute.
140
+ *
141
+ * The AI SDK does not standardize cache WRITE tokens on the usage object, and
142
+ * earlier versions / providers may also omit `ai.usage.cachedInputTokens`, so
143
+ * we read the provider-specific shape directly. Only Bedrock and Anthropic
144
+ * are handled here as they are the providers that report cache writes today.
145
+ *
146
+ * @see https://ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock#cache-points
147
+ * @see https://ai-sdk.dev/providers/ai-sdk-providers/anthropic#cache-control
148
+ *
149
+ * @param {string | undefined} providerMetadataJson
150
+ * @returns {{ cacheReadTokens?: number, cacheWriteTokens?: number }}
151
+ */
152
+ function getProviderCacheTokens (providerMetadataJson) {
153
+ if (!providerMetadataJson) return {}
154
+
155
+ const metadata = getJsonStringValue(providerMetadataJson, null)
156
+ if (!metadata || typeof metadata !== 'object') return {}
157
+
158
+ const result = {}
159
+
160
+ const bedrockUsage = metadata.bedrock?.usage
161
+ if (bedrockUsage) {
162
+ if (bedrockUsage.cacheReadInputTokens != null) result.cacheReadTokens = bedrockUsage.cacheReadInputTokens
163
+ if (bedrockUsage.cacheWriteInputTokens != null) result.cacheWriteTokens = bedrockUsage.cacheWriteInputTokens
164
+ }
165
+
166
+ const anthropic = metadata.anthropic
167
+ if (anthropic) {
168
+ if (result.cacheReadTokens == null && anthropic.cacheReadInputTokens != null) {
169
+ result.cacheReadTokens = anthropic.cacheReadInputTokens
170
+ }
171
+ if (result.cacheWriteTokens == null && anthropic.cacheCreationInputTokens != null) {
172
+ result.cacheWriteTokens = anthropic.cacheCreationInputTokens
173
+ }
174
+ }
175
+
176
+ return result
177
+ }
178
+
93
179
  /**
94
180
  * Safely JSON parses a string value with a default fallback
95
181
  * @template T typeof defaultValue
@@ -7,12 +7,22 @@ const {
7
7
  extractTextAndResponseReason,
8
8
  parseModelId,
9
9
  extractTextAndResponseReasonFromStream,
10
+ extractConverseToolDefinitions,
11
+ extractRequestParamsConverse,
12
+ extractTextAndResponseReasonConverse,
13
+ extractTextAndResponseReasonConverseFromStream,
10
14
  } = require('../../../../datadog-plugin-aws-sdk/src/services/bedrockruntime/utils')
11
15
  const BaseLLMObsPlugin = require('./base')
12
16
 
13
17
  const llmobsStore = storage('llmobs')
14
18
 
15
- const ENABLED_OPERATIONS = new Set(['invokeModel', 'invokeModelWithResponseStream'])
19
+ const ENABLED_OPERATIONS = new Set([
20
+ 'invokeModel',
21
+ 'invokeModelWithResponseStream',
22
+ 'converse',
23
+ 'converseStream',
24
+ ])
25
+ const CONVERSE_OPERATIONS = new Set(['converse', 'converseStream'])
16
26
 
17
27
  /**
18
28
  * @typedef {{
@@ -79,10 +89,18 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
79
89
  setLLMObsTags ({ ctx, request, span, response, modelProvider, modelName, tokensFromHeaders }) {
80
90
  const isStream = request?.operation?.toLowerCase().includes('stream')
81
91
  telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: 'bedrock' })
92
+ this.#registerSpan(span, request)
82
93
 
94
+ if (CONVERSE_OPERATIONS.has(request?.operation)) {
95
+ this.#tagConverseSpan({ ctx, request, span, response, tokensFromHeaders, isStream })
96
+ } else {
97
+ this.#tagInvokeModelSpan({ ctx, request, span, response, modelProvider, modelName, tokensFromHeaders, isStream })
98
+ }
99
+ }
100
+
101
+ #registerSpan (span, request) {
83
102
  const parent = llmobsStore.getStore()?.span
84
103
  // Use full modelId and unified provider for LLMObs (required for backend cost estimation).
85
- // Split modelProvider/modelName from parseModelId() are still used below for response parsing.
86
104
  this._tagger.registerLLMObsSpan(span, {
87
105
  parent,
88
106
  modelName: request.params.modelId.toLowerCase(),
@@ -91,38 +109,42 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
91
109
  name: 'bedrock-runtime.command',
92
110
  integration: 'bedrock',
93
111
  })
112
+ }
113
+
114
+ #tagConverseSpan ({ ctx, request, span, response, tokensFromHeaders, isStream }) {
115
+ const requestParams = extractRequestParamsConverse(request.params)
116
+ const textAndResponseReason = isStream
117
+ ? extractTextAndResponseReasonConverseFromStream(ctx.chunks)
118
+ : extractTextAndResponseReasonConverse(response)
119
+
120
+ const toolDefinitions = extractConverseToolDefinitions(request.params)
121
+ if (toolDefinitions.length > 0) this._tagger.tagToolDefinitions(span, toolDefinitions)
122
+ if (textAndResponseReason.finishReason) {
123
+ this._tagger.tagMetadata(span, { stop_reason: textAndResponseReason.finishReason })
124
+ }
125
+ this.#tagCommon({ span, requestParams, textAndResponseReason, tokensFromHeaders })
126
+ }
94
127
 
128
+ #tagInvokeModelSpan ({ ctx, request, span, response, modelProvider, modelName, tokensFromHeaders, isStream }) {
95
129
  const requestParams = extractRequestParams(request.params, modelProvider)
96
130
  // for streamed responses, we'll use the coerced response object we formed in the stream handler
97
131
  const textAndResponseReason = isStream
98
132
  ? extractTextAndResponseReasonFromStream(ctx.chunks, modelProvider, modelName)
99
133
  : extractTextAndResponseReason(response, modelProvider, modelName)
100
134
 
101
- // add metadata tags
135
+ this.#tagCommon({ span, requestParams, textAndResponseReason, tokensFromHeaders })
136
+ }
137
+
138
+ #tagCommon ({ span, requestParams, textAndResponseReason, tokensFromHeaders }) {
102
139
  this._tagger.tagMetadata(span, {
103
140
  temperature: Number.parseFloat(requestParams.temperature) || 0,
104
141
  max_tokens: Number.parseInt(requestParams.maxTokens) || 0,
105
142
  })
106
-
107
- // add I/O tags
108
- this._tagger.tagLLMIO(
109
- span,
110
- requestParams.prompt,
111
- [{ content: textAndResponseReason.message, role: textAndResponseReason.role }]
112
- )
113
-
114
- // add token metrics
115
- const { inputTokens, outputTokens, totalTokens, cacheReadTokens, cacheWriteTokens } = extractTokens({
143
+ this._tagger.tagLLMIO(span, requestParams.prompt, textAndResponseReason.messages)
144
+ this._tagger.tagMetrics(span, extractTokens({
116
145
  tokensFromHeaders,
117
146
  usage: textAndResponseReason.usage,
118
- })
119
- this._tagger.tagMetrics(span, {
120
- inputTokens,
121
- outputTokens,
122
- totalTokens,
123
- cacheReadTokens,
124
- cacheWriteTokens,
125
- })
147
+ }))
126
148
  }
127
149
  }
128
150
 
@@ -55,7 +55,7 @@ class GenAiLLMObsPlugin extends LLMObsPlugin {
55
55
 
56
56
  const inputs = args[0]
57
57
  const response = ctx.result
58
- const error = !!span.context()._tags.error
58
+ const error = !!span.context().getTag('error')
59
59
 
60
60
  const operation = getOperation(methodName)
61
61
 
@@ -7,7 +7,7 @@ class LangChainLLMObsHandler {
7
7
  }
8
8
 
9
9
  getName ({ span }) {
10
- return span?.context()._tags?.['resource.name']
10
+ return span?.context()?.getTag('resource.name')
11
11
  }
12
12
 
13
13
  setMetaTags () {}
@@ -44,7 +44,8 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
44
44
 
45
45
  getLLMObsSpanRegisterOptions (ctx) {
46
46
  const span = ctx.currentStore?.span
47
- const tags = span?.context()._tags || {}
47
+ const spanContext = span?.context()
48
+ const tags = spanContext?.getTags() || {}
48
49
 
49
50
  const modelProvider = tags['langchain.request.provider'] // could be undefined
50
51
  const modelName = tags['langchain.request.model'] // could be undefined
@@ -76,7 +77,7 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
76
77
  return
77
78
  }
78
79
 
79
- const provider = span?.context()._tags['langchain.request.provider']
80
+ const provider = span?.context()?.getTag('langchain.request.provider')
80
81
  const integrationName = this.getIntegrationName(type, provider)
81
82
  this.setMetadata(span, provider)
82
83
 
@@ -93,14 +94,15 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
93
94
  const metadata = {}
94
95
 
95
96
  // these fields won't be set for non model-based operations
97
+ const spanContext = span?.context()
96
98
  const temperature =
97
- span?.context()._tags[`langchain.request.${provider}.parameters.temperature`] ||
98
- span?.context()._tags[`langchain.request.${provider}.parameters.model_kwargs.temperature`]
99
+ spanContext?.getTag(`langchain.request.${provider}.parameters.temperature`) ||
100
+ spanContext?.getTag(`langchain.request.${provider}.parameters.model_kwargs.temperature`)
99
101
 
100
102
  const maxTokens =
101
- span?.context()._tags[`langchain.request.${provider}.parameters.max_tokens`] ||
102
- span?.context()._tags[`langchain.request.${provider}.parameters.maxTokens`] ||
103
- span?.context()._tags[`langchain.request.${provider}.parameters.model_kwargs.max_tokens`]
103
+ spanContext?.getTag(`langchain.request.${provider}.parameters.max_tokens`) ||
104
+ spanContext?.getTag(`langchain.request.${provider}.parameters.maxTokens`) ||
105
+ spanContext?.getTag(`langchain.request.${provider}.parameters.model_kwargs.max_tokens`)
104
106
 
105
107
  if (temperature) {
106
108
  metadata.temperature = Number.parseFloat(temperature)
@@ -35,7 +35,7 @@ class PregelStreamLLMObsPlugin extends LLMObsPlugin {
35
35
 
36
36
  class NextStreamLLMObsPlugin extends LLMObsPlugin {
37
37
  static id = 'llmobs_langgraph_next_stream'
38
- static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream_next'
38
+ static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream:next'
39
39
 
40
40
  start () {} // no-op: span was already registered by PregelStreamLLMObsPlugin
41
41
 
@@ -63,7 +63,7 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
63
63
 
64
64
  const inputs = ctx.args[0] // completion, chat completion, and embeddings take one argument
65
65
  const response = ctx.result?.data // no result if error
66
- const error = !!span.context()._tags.error
66
+ const error = !!span.context().getTag('error')
67
67
 
68
68
  const operation = getOperation(methodName)
69
69
 
@@ -11,8 +11,6 @@ const {
11
11
  SPAN_KIND,
12
12
  OUTPUT_VALUE,
13
13
  INPUT_VALUE,
14
- LLMOBS_TRACE_ID_BRIDGE_KEY,
15
- LLMOBS_PARENT_ID_BRIDGE_KEY,
16
14
  } = require('./constants/tags')
17
15
  const {
18
16
  getFunctionArguments,
@@ -553,20 +551,6 @@ class LLMObs extends NoopLLMObs {
553
551
  ...options,
554
552
  parent: parentStore?.span,
555
553
  })
556
-
557
- // Bridge tags read by the dd-go LLMObs trace-indexer to correlate OTel
558
- // gen_ai.* spans with SDK LLMObs spans. Written once per local trace,
559
- // on the first successful SDK LLMObs span registration. The shared
560
- // _trace.tags bag is serialized to the first span in every flushed
561
- // chunk's meta, so partial flush is covered automatically without a
562
- // separate flush-time processor. Writing only after registerLLMObsSpan
563
- // succeeds avoids poisoning _trace.tags with bridge tags pointing at a
564
- // span that will never produce an LLMObs event.
565
- const traceTags = span?.context?.()._trace?.tags
566
- if (this.enabled && traceTags && !traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY]) {
567
- traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = span.context().toTraceId(true)
568
- traceTags[LLMOBS_PARENT_ID_BRIDGE_KEY] = span.context().toSpanId()
569
- }
570
554
  }
571
555
 
572
556
  try {
@@ -107,7 +107,7 @@ class LLMObsSpanProcessor {
107
107
  // those cases avoids dd-go reparenting OTel children under a span that
108
108
  // has no corresponding LLMObs event.
109
109
  if (enqueued) {
110
- span.context()._tags[LLMOBS_SUBMITTED_TAG_KEY] = '1'
110
+ span.context().setTag(LLMOBS_SUBMITTED_TAG_KEY, '1')
111
111
  }
112
112
  } catch (e) {
113
113
  // this should be a rare case
@@ -123,7 +123,7 @@ class LLMObsSpanProcessor {
123
123
  format (span) {
124
124
  let inputType, outputType
125
125
 
126
- const spanTags = span.context()._tags
126
+ const spanTags = span.context().getTags()
127
127
  const mlObsTags = LLMObsTagger.tagMap.get(span)
128
128
 
129
129
  const spanKind = mlObsTags[SPAN_KIND]
@@ -318,7 +318,7 @@ class LLMObsSpanProcessor {
318
318
  language: 'javascript',
319
319
  }
320
320
 
321
- const errType = span.context()._tags[ERROR_TYPE] || error?.name
321
+ const errType = span.context().getTag(ERROR_TYPE) || error?.name
322
322
  if (errType) tags.error_type = errType
323
323
 
324
324
  if (sessionId) tags.session_id = sessionId
@@ -43,7 +43,7 @@ const {
43
43
  INSTRUMENTATION_METHOD_ANNOTATED,
44
44
  } = require('./constants/tags')
45
45
  const { storage } = require('./storage')
46
- const { validateCostTags } = require('./util')
46
+ const { findGenAIAncestorSpanId, validateCostTags, writeBridgeTags } = require('./util')
47
47
 
48
48
  // global registry of LLMObs spans
49
49
  // maps LLMObs spans to their annotations
@@ -97,6 +97,13 @@ class LLMObsTagger {
97
97
 
98
98
  this._register(span)
99
99
 
100
+ // When the registering span sits below an OTel `gen_ai.*` ancestor, use
101
+ // that ancestor as the parent_id fallback and suppress the bridge
102
+ // parent_id tag so the indexer doesn't invert the trace.
103
+ const genAIAncestorSpanId = findGenAIAncestorSpanId(span)
104
+
105
+ writeBridgeTags(span, { includeParentId: genAIAncestorSpanId === null })
106
+
100
107
  this._setTag(span, ML_APP, spanMlApp)
101
108
 
102
109
  if (name) this._setTag(span, NAME, name)
@@ -113,6 +120,7 @@ class LLMObsTagger {
113
120
  const parentId =
114
121
  parent?.context().toSpanId() ??
115
122
  span.context()._trace.tags[PROPAGATED_PARENT_ID_KEY] ??
123
+ genAIAncestorSpanId ??
116
124
  ROOT_PARENT_ID
117
125
  this._setTag(span, PARENT_ID_KEY, parentId)
118
126
 
@@ -45,7 +45,7 @@ function incrementLLMObsSpanStartCount (tags, value = 1) {
45
45
 
46
46
  function incrementLLMObsSpanFinishedCount (span, value = 1) {
47
47
  const mlObsTags = LLMObsTagger.tagMap.get(span)
48
- const spanTags = span.context()._tags
48
+ const spanTags = span.context().getTags()
49
49
 
50
50
  const isRootSpan = mlObsTags[PARENT_ID_KEY] === ROOT_PARENT_ID
51
51
  const hasSessionId = mlObsTags[SESSION_ID] != null
@@ -1,7 +1,11 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../log')
4
- const { SPAN_KINDS } = require('./constants/tags')
4
+ const {
5
+ LLMOBS_PARENT_ID_BRIDGE_KEY,
6
+ LLMOBS_TRACE_ID_BRIDGE_KEY,
7
+ SPAN_KINDS,
8
+ } = require('./constants/tags')
5
9
 
6
10
  // LLM I/O is overwhelmingly ASCII (English prompts and code). Walk once
7
11
  // looking for the first non-ASCII char; if there is none, hand the input
@@ -233,8 +237,8 @@ function getFunctionArguments (fn, args = []) {
233
237
  }
234
238
 
235
239
  function spanHasError (span) {
236
- const tags = span.context()._tags
237
- return !!(tags.error || tags['error.type'])
240
+ const spanContext = span.context()
241
+ return !!(spanContext.getTag('error') || spanContext.getTag('error.type'))
238
242
  }
239
243
 
240
244
  // LLM SDKs stream tool-call argument JSON across SSE chunks; a malformed
@@ -248,11 +252,70 @@ function safeJsonParse (value, fallback) {
248
252
  }
249
253
  }
250
254
 
255
+ // Bridge tags read by the trace-indexer to pull OTel `gen_ai.*` spans into
256
+ // the same LLMObs trace. Written once per local trace (first-writer wins on
257
+ // `_trace.tags`). Pass `includeParentId: false` when the span sits below an
258
+ // OTel `gen_ai.*` ancestor — without it the indexer treats this span as the
259
+ // LLMObs root and hoists the gen_ai ancestors under it, inverting the trace.
260
+ /**
261
+ * @param {import('../opentracing/span')} span
262
+ * @param {{ includeParentId?: boolean }} [opts]
263
+ */
264
+ function writeBridgeTags (span, { includeParentId = true } = {}) {
265
+ const traceTags = span?.context?.()._trace?.tags
266
+ if (!traceTags || traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY]) return
267
+ traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = span.context().toTraceId(true)
268
+ if (includeParentId) {
269
+ traceTags[LLMOBS_PARENT_ID_BRIDGE_KEY] = span.context().toSpanId()
270
+ }
271
+ }
272
+
273
+ // Walks the APM parent chain for the nearest ancestor with any `gen_ai.*`
274
+ // tag. Lets an auto-instrumented LLMObs span nested under a manual OTel
275
+ // workflow point its `parent_id` at the OTel parent so the SDK-emitted
276
+ // event renders under it instead of as a parallel root.
277
+ /**
278
+ * @param {import('../opentracing/span')} span
279
+ * @returns {string | null}
280
+ */
281
+ function findGenAIAncestorSpanId (span) {
282
+ const ctx = span?.context?.()
283
+ let parentId = ctx?._parentId?.toString(10)
284
+ if (!parentId || parentId === '0') return null
285
+
286
+ const started = ctx._trace?.started
287
+ if (!started || started.length === 0) return null
288
+
289
+ // Linear scan per hop — parent chains are short, avoids a per-call Map.
290
+ while (parentId && parentId !== '0') {
291
+ let parent = null
292
+ for (const s of started) {
293
+ if (s.context()._spanId.toString(10) === parentId) {
294
+ parent = s
295
+ break
296
+ }
297
+ }
298
+ if (!parent) return null
299
+
300
+ const tags = parent.context().getTags()
301
+ if (tags) {
302
+ for (const key of Object.keys(tags)) {
303
+ if (key.startsWith('gen_ai.')) return parentId
304
+ }
305
+ }
306
+
307
+ parentId = parent.context()._parentId?.toString(10)
308
+ }
309
+ return null
310
+ }
311
+
251
312
  module.exports = {
252
313
  encodeUnicode,
314
+ findGenAIAncestorSpanId,
253
315
  validateCostTags,
254
316
  validateKind,
255
317
  getFunctionArguments,
256
318
  safeJsonParse,
257
319
  spanHasError,
320
+ writeBridgeTags,
258
321
  }
@@ -107,7 +107,7 @@ function publishFormatted (ch, formatter, ...args) {
107
107
 
108
108
  function getErrorLog (err) {
109
109
  if (typeof err?.delegate === 'function') {
110
- const result = err.delegate()
110
+ const result = err.delegate(...err.args)
111
111
  return Array.isArray(result) ? Log.parse(...result) : Log.parse(result)
112
112
  }
113
113
  return err