dd-trace 6.10.0 → 6.12.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 (173) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/README.md +0 -17
  3. package/index.d.ts +128 -6
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +27 -23
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/cucumber.js +30 -15
  13. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  14. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  15. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  16. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  17. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  18. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  19. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  20. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  21. package/packages/datadog-instrumentations/src/jest.js +25 -17
  22. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  23. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +135 -19
  25. package/packages/datadog-instrumentations/src/playwright-reporter.js +247 -0
  26. package/packages/datadog-instrumentations/src/playwright.js +182 -35
  27. package/packages/datadog-instrumentations/src/router.js +2 -0
  28. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
  29. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  31. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  32. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  33. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  34. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  35. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  36. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  37. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  38. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  39. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  40. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  41. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  42. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  43. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  44. package/packages/datadog-plugin-electron/src/index.js +2 -0
  45. package/packages/datadog-plugin-fs/src/index.js +1 -1
  46. package/packages/datadog-plugin-jest/src/index.js +6 -0
  47. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  48. package/packages/datadog-plugin-nats/src/index.js +1 -1
  49. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  50. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  51. package/packages/datadog-plugin-playwright/src/index.js +46 -13
  52. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  53. package/packages/datadog-webpack/index.js +0 -11
  54. package/packages/dd-trace/index.js +0 -1
  55. package/packages/dd-trace/src/agent/info.js +6 -5
  56. package/packages/dd-trace/src/aiguard/client.js +100 -1
  57. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  58. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  59. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  60. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  61. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  62. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  63. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  64. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  65. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  67. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  68. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  69. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  70. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  71. package/packages/dd-trace/src/carrier.js +107 -48
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  73. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  74. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +29 -3
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +27 -9
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  78. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +26 -8
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  80. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  81. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  84. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -4
  85. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  86. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  87. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  88. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  89. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +10 -4
  90. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  91. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  92. package/packages/dd-trace/src/config/index.js +13 -2
  93. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  94. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  95. package/packages/dd-trace/src/datastreams/index.js +2 -0
  96. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  97. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  98. package/packages/dd-trace/src/datastreams/size.js +5 -0
  99. package/packages/dd-trace/src/debugger/constants.js +1 -0
  100. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  101. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -11
  102. package/packages/dd-trace/src/debugger/index.js +4 -2
  103. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  104. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  105. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  106. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  107. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  108. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  109. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  110. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  111. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  112. package/packages/dd-trace/src/llmobs/experiments/client.js +167 -5
  113. package/packages/dd-trace/src/llmobs/experiments/dataset.js +484 -92
  114. package/packages/dd-trace/src/llmobs/experiments/experiment.js +302 -29
  115. package/packages/dd-trace/src/llmobs/experiments/index.js +75 -64
  116. package/packages/dd-trace/src/llmobs/experiments/noop.js +135 -7
  117. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  118. package/packages/dd-trace/src/llmobs/experiments/util.js +101 -9
  119. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  120. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  121. package/packages/dd-trace/src/noop/proxy.js +3 -4
  122. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  123. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  124. package/packages/dd-trace/src/openfeature/index.js +7 -4
  125. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  126. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  127. package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
  128. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  129. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  130. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  131. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  132. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  133. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  135. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  136. package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
  137. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  138. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  139. package/packages/dd-trace/src/plugin_manager.js +3 -3
  140. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  141. package/packages/dd-trace/src/plugins/index.js +1 -0
  142. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  143. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  144. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  145. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  146. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  148. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  149. package/packages/dd-trace/src/proxy.js +38 -29
  150. package/packages/dd-trace/src/ritm.js +9 -2
  151. package/packages/dd-trace/src/serverless.js +37 -0
  152. package/packages/dd-trace/src/span_format.js +1 -0
  153. package/packages/dd-trace/src/span_stats.js +30 -3
  154. package/packages/dd-trace/src/storage-channels.js +86 -0
  155. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  156. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  157. package/packages/dd-trace/src/tracer.js +9 -2
  158. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  159. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  160. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  161. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  162. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  163. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  164. package/vendor/dist/https-proxy-agent/index.js +4 -0
  165. package/vendor/dist/protobufjs/index.js +1 -1
  166. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  167. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  168. package/vendor/dist/proxy-from-env/index.js +1 -0
  169. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  170. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  171. package/packages/dd-trace/src/feature-registry.js +0 -29
  172. package/packages/dd-trace/src/openfeature/register.js +0 -35
  173. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -1,23 +1,53 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../../log')
4
+ const { ExternalExperiment } = require('./experiment')
5
+
6
+ const NOOP_EXPERIMENT_ID = '00000000-0000-0000-0000-000000000000'
7
+ const NOOP_SPAN_ID = '0000000000000000'
8
+ const NOOP_TRACE_ID = '00000000000000000000000000000000'
4
9
 
5
10
  class NoopDataset {
6
11
  #name
12
+ #description
7
13
  #records
14
+ #filterTags
8
15
 
9
16
  constructor (name = '', options = {}) {
10
17
  this.#name = name
18
+ this.#description = typeof options === 'string' ? options : (options.description ?? '')
19
+ this.#filterTags = typeof options === 'string' ? [] : [...(options.filterTags ?? [])]
11
20
  this.#records = (typeof options === 'string' ? [] : (options.records ?? [])).map(record => ({
12
21
  id: record.id ?? null,
13
22
  input: record.inputData,
14
23
  expectedOutput: record.expectedOutput ?? null,
15
24
  metadata: record.metadata ?? {},
25
+ tags: [...(record.tags ?? [])],
16
26
  }))
17
27
  }
18
28
 
19
- addRecord (input, expectedOutput, metadata) {
20
- this.#records.push({ id: null, input, expectedOutput: expectedOutput ?? null, metadata: metadata ?? {} })
29
+ addRecord (input, expectedOutput, metadata, tags) {
30
+ this.#records.push({
31
+ id: null,
32
+ input,
33
+ expectedOutput: expectedOutput ?? null,
34
+ metadata: metadata ?? {},
35
+ tags: [...(tags ?? [])],
36
+ })
37
+ return this
38
+ }
39
+
40
+ update (index, fields) {
41
+ const record = this.#records[index]
42
+ if (record == null) return this
43
+ if (Object.hasOwn(fields, 'input')) record.input = fields.input
44
+ if (Object.hasOwn(fields, 'expectedOutput')) record.expectedOutput = fields.expectedOutput ?? null
45
+ if (Object.hasOwn(fields, 'metadata')) record.metadata = fields.metadata ?? {}
46
+ return this
47
+ }
48
+
49
+ delete (index) {
50
+ this.#records.splice(index, 1)
21
51
  return this
22
52
  }
23
53
 
@@ -25,10 +55,57 @@ class NoopDataset {
25
55
  return Promise.resolve({ pushedCount: 0, totalCount: 0 })
26
56
  }
27
57
 
58
+ /**
59
+ * Add tags to a dataset record.
60
+ * @param {number} index Dataset record index.
61
+ * @param {string[]} tags Tags in key:value format.
62
+ * @returns {NoopDataset} This dataset for chaining.
63
+ */
64
+ addTags (index, tags) {
65
+ const record = this.#records[index]
66
+ if (!record) return this
67
+ const normalizedTags = tags ?? []
68
+ record.tags = [...new Set([...(record.tags ?? []), ...normalizedTags])].sort()
69
+ return this
70
+ }
71
+
72
+ /**
73
+ * Remove tags from a dataset record.
74
+ * @param {number} index Dataset record index.
75
+ * @param {string[]} tags Tags in key:value format.
76
+ * @returns {NoopDataset} This dataset for chaining.
77
+ */
78
+ removeTags (index, tags) {
79
+ const record = this.#records[index]
80
+ if (!record) return this
81
+ const normalizedTags = tags ?? []
82
+ const removed = new Set(normalizedTags)
83
+ record.tags = (record.tags ?? []).filter(tag => !removed.has(tag)).sort()
84
+ return this
85
+ }
86
+
87
+ /**
88
+ * Replace all tags on a dataset record.
89
+ * @param {number} index Dataset record index.
90
+ * @param {string[]} tags Tags in key:value format.
91
+ * @returns {NoopDataset} This dataset for chaining.
92
+ */
93
+ replaceTags (index, tags) {
94
+ const record = this.#records[index]
95
+ if (!record) return this
96
+ const normalizedTags = tags ?? []
97
+ record.tags = [...normalizedTags]
98
+ return this
99
+ }
100
+
28
101
  name () {
29
102
  return this.#name
30
103
  }
31
104
 
105
+ description () {
106
+ return this.#description
107
+ }
108
+
32
109
  id () {
33
110
  return null
34
111
  }
@@ -45,6 +122,14 @@ class NoopDataset {
45
122
  return null
46
123
  }
47
124
 
125
+ /**
126
+ * Return the tags used to filter this dataset.
127
+ * @returns {string[]} Dataset record filter tags.
128
+ */
129
+ filterTags () {
130
+ return [...this.#filterTags]
131
+ }
132
+
48
133
  records () {
49
134
  return [...this.#records]
50
135
  }
@@ -60,9 +145,11 @@ class NoopDataset {
60
145
 
61
146
  class NoopExperiment {
62
147
  #name
148
+ #external
63
149
 
64
- constructor (name = '') {
150
+ constructor (name = '', external = false) {
65
151
  this.#name = name
152
+ this.#external = external
66
153
  }
67
154
 
68
155
  name () {
@@ -70,7 +157,7 @@ class NoopExperiment {
70
157
  }
71
158
 
72
159
  experimentId () {
73
- return null
160
+ return this.#external ? NOOP_EXPERIMENT_ID : null
74
161
  }
75
162
 
76
163
  url () {
@@ -80,6 +167,32 @@ class NoopExperiment {
80
167
  run () {
81
168
  return Promise.resolve({ experimentId: null, rows: [], url: null })
82
169
  }
170
+
171
+ /**
172
+ * @returns {Promise<{experimentId: string, spanId: string, traceId: string, url: null}>}
173
+ */
174
+ submitSpan () {
175
+ return Promise.resolve({
176
+ experimentId: NOOP_EXPERIMENT_ID,
177
+ spanId: NOOP_SPAN_ID,
178
+ traceId: NOOP_TRACE_ID,
179
+ url: null,
180
+ })
181
+ }
182
+
183
+ /**
184
+ * @returns {Promise<void>}
185
+ */
186
+ submitEvaluationMetrics () {
187
+ return Promise.resolve()
188
+ }
189
+
190
+ /**
191
+ * @returns {Promise<void>}
192
+ */
193
+ close () {
194
+ return Promise.resolve()
195
+ }
83
196
  }
84
197
 
85
198
  // No-op Experiments used when LLM Observability is disabled or the API/APP keys
@@ -87,9 +200,11 @@ class NoopExperiment {
87
200
  // throwing, so intentionally disabled experiments remain graceful.
88
201
  class NoopExperiments {
89
202
  #reason
203
+ #startExperiment
90
204
 
91
- constructor (reason) {
205
+ constructor (reason, options = {}) {
92
206
  this.#reason = reason || 'LLMObs experiments are not available'
207
+ this.#startExperiment = options.startExperiment
93
208
  }
94
209
 
95
210
  #warn () {
@@ -101,15 +216,28 @@ class NoopExperiments {
101
216
  return new NoopDataset(name, options)
102
217
  }
103
218
 
104
- pullDataset (name) {
219
+ pullDataset (name, options = {}) {
105
220
  this.#warn()
106
- return Promise.resolve(new NoopDataset(name))
221
+ return Promise.resolve(new NoopDataset(name, { filterTags: options.tags }))
107
222
  }
108
223
 
109
224
  experiment (options = {}) {
110
225
  this.#warn()
111
226
  return new NoopExperiment(options.name)
112
227
  }
228
+
229
+ /**
230
+ * @param {object} options
231
+ * @returns {Promise<ExternalExperiment>}
232
+ */
233
+ startExperiment (options = {}) {
234
+ if (this.#startExperiment !== undefined && options.projectName) {
235
+ return this.#startExperiment(options)
236
+ }
237
+
238
+ this.#warn()
239
+ return Promise.resolve(new ExternalExperiment(new NoopExperiment(options.name, true)))
240
+ }
113
241
  }
114
242
 
115
243
  module.exports = NoopExperiments
@@ -13,6 +13,7 @@ class Row {
13
13
  this.expectedOutput = fields.expectedOutput
14
14
  this.errorType = fields.errorType
15
15
  this.errorMessage = fields.errorMessage
16
+ this.errorStack = fields.errorStack
16
17
  this.evaluations = fields.evaluations
17
18
  this.evaluationErrors = fields.evaluationErrors
18
19
  }
@@ -22,6 +22,26 @@ function hasEntries (value) {
22
22
  return false
23
23
  }
24
24
 
25
+ function validateTagsList (tags) {
26
+ if (tags == null) return []
27
+ if (!Array.isArray(tags)) throw new TypeError('Tags must be an array of strings')
28
+ for (const tag of tags) {
29
+ if (typeof tag !== 'string') throw new TypeError('Each tag must be a string')
30
+ if (tag.indexOf(':') <= 0) {
31
+ throw new Error(`Tag '${tag}' is malformed. Tags must be in 'key:value' format (e.g., 'env:prod').`)
32
+ }
33
+ }
34
+ return [...tags]
35
+ }
36
+
37
+ function tagOperationsAreEmpty (operations) {
38
+ return operations == null || (
39
+ !Object.hasOwn(operations, 'replace') &&
40
+ !Object.hasOwn(operations, 'add') &&
41
+ !Object.hasOwn(operations, 'remove')
42
+ )
43
+ }
44
+
25
45
  /**
26
46
  * @param {string} name
27
47
  */
@@ -165,25 +185,58 @@ function stringify (value) {
165
185
  * @returns {string[]}
166
186
  */
167
187
  function buildTags (userTags, autoTags) {
168
- const tags = new Map()
188
+ const tagsByKey = new Map()
169
189
  if ((userTags) != null) {
170
190
  for (const [key, value] of Object.entries(userTags)) {
171
- tags.set(key, `${key}:${value}`)
191
+ const values = Array.isArray(value) ? value : [value]
192
+ tagsByKey.set(key, values.map(item => `${key}:${item}`))
172
193
  }
173
194
  }
174
195
  for (const [key, value] of Object.entries(autoTags)) {
175
- if (value !== undefined && value !== null && value !== '') tags.set(key, `${key}:${value}`)
196
+ if (value !== undefined && value !== null && value !== '') tagsByKey.set(key, [`${key}:${value}`])
176
197
  }
177
- return [...tags.values()]
198
+
199
+ const tags = []
200
+ for (const values of tagsByKey.values()) tags.push(...values)
201
+ return tags
178
202
  }
179
203
 
180
204
  /**
181
- * @param {Record<string, unknown> | undefined} userTags
182
- * @param {Record<string, unknown>} autoTags
205
+ * @param {Record<string, unknown> | undefined} baseTags
206
+ * @param {Record<string, unknown> | undefined} overrideTags
183
207
  * @returns {Record<string, unknown>}
184
208
  */
185
- function buildExperimentTagObject (userTags, autoTags) {
186
- return userTags ? { ...userTags, ...autoTags } : { ...autoTags }
209
+ function mergeTags (baseTags, overrideTags) {
210
+ return { ...baseTags, ...overrideTags }
211
+ }
212
+
213
+ /**
214
+ * @param {string[] | undefined} tags
215
+ * @returns {Record<string, string | string[]>}
216
+ */
217
+ function recordTagsToObject (tags) {
218
+ const result = {}
219
+ if (!Array.isArray(tags)) return result
220
+ for (const tag of tags) {
221
+ const separator = tag.indexOf(':')
222
+ if (separator <= 0) continue
223
+
224
+ const key = tag.slice(0, separator)
225
+ const value = tag.slice(separator + 1)
226
+ if (!Object.hasOwn(result, key)) {
227
+ Object.defineProperty(result, key, {
228
+ configurable: true,
229
+ enumerable: true,
230
+ value,
231
+ writable: true,
232
+ })
233
+ } else if (Array.isArray(result[key])) {
234
+ result[key].push(value)
235
+ } else {
236
+ result[key] = [result[key], value]
237
+ }
238
+ }
239
+ return result
187
240
  }
188
241
 
189
242
  /**
@@ -195,6 +248,40 @@ function sleep (ms) {
195
248
  return new Promise((resolve) => setTimeout(resolve, ms))
196
249
  }
197
250
 
251
+ /**
252
+ * @param {unknown} value
253
+ * @param {number} fallback
254
+ * @returns {number}
255
+ */
256
+ function timestampMs (value, fallback = Date.now()) {
257
+ if (value === null || value === undefined) return fallback
258
+ if (value instanceof Date) {
259
+ const timestamp = value.getTime()
260
+ return Number.isFinite(timestamp) ? timestamp : fallback
261
+ }
262
+ if (typeof value === 'number' && Number.isFinite(value)) return value
263
+ const parsed = Date.parse(String(value))
264
+ return Number.isFinite(parsed) ? parsed : fallback
265
+ }
266
+
267
+ /**
268
+ * @param {{ durationMs?: unknown, completedAt?: unknown }} row
269
+ * @param {number} startMs
270
+ * @returns {number}
271
+ */
272
+ function durationNs (row, startMs) {
273
+ if (typeof row.durationMs === 'number' && Number.isFinite(row.durationMs)) {
274
+ return Math.max(0, Math.round(row.durationMs * 1e6))
275
+ }
276
+
277
+ if (row.completedAt !== undefined) {
278
+ const completedMs = timestampMs(row.completedAt, startMs)
279
+ return Math.max(0, Math.round((completedMs - startMs) * 1e6))
280
+ }
281
+
282
+ return 0
283
+ }
284
+
198
285
  /**
199
286
  * @param {Record<string, unknown> | undefined} recordMetadata
200
287
  * @param {Record<string, unknown>} config
@@ -207,14 +294,19 @@ function buildSpanMetadata (recordMetadata, config) {
207
294
  }
208
295
 
209
296
  module.exports = {
210
- buildExperimentTagObject,
211
297
  buildSpanMetadata,
212
298
  buildTags,
299
+ durationNs,
213
300
  hasEntries,
214
301
  inferMetricType,
302
+ mergeTags,
215
303
  normalizeEvaluators,
216
304
  normalizeJsonMetricValue,
305
+ recordTagsToObject,
217
306
  sleep,
218
307
  stringify,
308
+ tagOperationsAreEmpty,
309
+ timestampMs,
219
310
  validateEvaluatorName,
311
+ validateTagsList,
220
312
  }
@@ -228,6 +228,7 @@ function getJsonStringValue (str, defaultValue) {
228
228
  function getModelMetadata (tags) {
229
229
  /** @type {Record<string, unknown>} */
230
230
  const modelMetadata = {}
231
+ let hasModelMetadata = false
231
232
  for (const tag of Object.keys(tags)) {
232
233
  const isModelMetadata = tag.startsWith(VERCEL_AI_MODEL_METADATA_PREFIX)
233
234
  if (isModelMetadata) {
@@ -235,6 +236,7 @@ function getModelMetadata (tags) {
235
236
  const metadataKey = lastCommaPosition === -1 ? tag : tag.slice(lastCommaPosition + 1)
236
237
  if (metadataKey && MODEL_METADATA_KEYS.has(metadataKey)) {
237
238
  modelMetadata[metadataKey] = tags[tag]
239
+ hasModelMetadata = true
238
240
  }
239
241
  } else {
240
242
  const isTelemetryMetadata = tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)
@@ -242,12 +244,13 @@ function getModelMetadata (tags) {
242
244
  const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
243
245
  if (metadataKey) {
244
246
  modelMetadata[metadataKey] = tags[tag]
247
+ hasModelMetadata = true
245
248
  }
246
249
  }
247
250
  }
248
251
  }
249
252
 
250
- return Object.keys(modelMetadata).length ? modelMetadata : null
253
+ return hasModelMetadata ? modelMetadata : null
251
254
  }
252
255
 
253
256
  /**
@@ -259,6 +262,7 @@ function getModelMetadata (tags) {
259
262
  function getGenerationMetadata (tags) {
260
263
  /** @type {Record<string, unknown>} */
261
264
  const metadata = {}
265
+ let hasMetadata = false
262
266
 
263
267
  for (const tag of Object.keys(tags)) {
264
268
  const isGenerationMetadata = tag.startsWith(VERCEL_AI_GENERATION_METADATA_PREFIX)
@@ -270,18 +274,20 @@ function getGenerationMetadata (tags) {
270
274
 
271
275
  const settingValue = tags[tag]
272
276
  metadata[settingKey] = settingValue
277
+ hasMetadata = true
273
278
  } else {
274
279
  const isTelemetryMetadata = tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)
275
280
  if (isTelemetryMetadata) {
276
281
  const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
277
282
  if (metadataKey) {
278
283
  metadata[metadataKey] = tags[tag]
284
+ hasMetadata = true
279
285
  }
280
286
  }
281
287
  }
282
288
  }
283
289
 
284
- return Object.keys(metadata).length ? metadata : null
290
+ return hasMetadata ? metadata : null
285
291
  }
286
292
 
287
293
  /**
@@ -307,6 +313,7 @@ function getGenerationMetadataFromEvent (event) {
307
313
  metadata[transformedKey] = value
308
314
  }
309
315
 
316
+ // eslint-disable-next-line no-restricted-syntax -- manual tracking would duplicate Object.assign semantics
310
317
  return Object.keys(metadata).length ? metadata : null
311
318
  }
312
319
 
@@ -432,6 +439,7 @@ function getLlmObsSpanName (operation, functionId) {
432
439
  */
433
440
  function getTelemetryMetadata (tags) {
434
441
  const metadata = {}
442
+ let hasMetadata = false
435
443
 
436
444
  for (const tag of Object.keys(tags)) {
437
445
  if (!tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)) continue
@@ -439,10 +447,11 @@ function getTelemetryMetadata (tags) {
439
447
  const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
440
448
  if (metadataKey) {
441
449
  metadata[metadataKey] = tags[tag]
450
+ hasMetadata = true
442
451
  }
443
452
  }
444
453
 
445
- return Object.keys(metadata).length ? metadata : null
454
+ return hasMetadata ? metadata : null
446
455
  }
447
456
 
448
457
  module.exports = {
@@ -84,9 +84,11 @@ class LLMObsPlugin extends TracingPlugin {
84
84
  }
85
85
 
86
86
  configure (config) {
87
- // we do not want to enable any LLMObs plugins if it is disabled on the tracer
87
+ // we do not want to enable any LLMObs plugins if it is disabled on the tracer, or if the
88
+ // integration opted out via `tracer.use(<name>, { llmobs: false })`. Opting out only disables
89
+ // the LLMObs layer: the integration keeps emitting APM spans and propagating trace context.
88
90
  const llmobsEnabled = this._tracerConfig.llmobs.DD_LLMOBS_ENABLED
89
- if (llmobsEnabled === false) {
91
+ if (llmobsEnabled === false || config?.llmobs === false) {
90
92
  config = typeof config === 'boolean' ? false : { ...config, enabled: false } // override to false
91
93
  }
92
94
  super.configure(config)
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const { features } = require('../feature-registry')
4
3
  const NoopAppsecSdk = require('../appsec/sdk/noop')
5
4
  const NoopLLMObsSDK = require('../llmobs/noop')
6
5
  const NoopAIGuardSDK = require('../aiguard/noop')
6
+ const NoopFlaggingProvider = require('../openfeature/noop')
7
7
  const NoopDogStatsDClient = require('./dogstatsd')
8
8
  const NoopTracer = require('./tracer')
9
9
 
@@ -12,6 +12,7 @@ const noopAppsec = new NoopAppsecSdk()
12
12
  const noopDogStatsDClient = new NoopDogStatsDClient()
13
13
  const noopLLMObs = new NoopLLMObsSDK(noop)
14
14
  const noopAIGuard = new NoopAIGuardSDK()
15
+ const noopFlaggingProviderInstance = new NoopFlaggingProvider()
15
16
  const noopProfiling = {
16
17
  setCustomLabelKeys () {},
17
18
  runWithLabels (labels, fn) { return fn() },
@@ -25,9 +26,7 @@ class NoopProxy {
25
26
  this.dogstatsd = noopDogStatsDClient
26
27
  this.llmobs = noopLLMObs
27
28
  this.aiguard = noopAIGuard
28
- for (const { name, noop } of Object.values(features)) {
29
- this[name] = noop
30
- }
29
+ this.openfeature = noopFlaggingProviderInstance
31
30
  this.setBaggageItem = (key, value) => {}
32
31
  this.getBaggageItem = (key) => {}
33
32
  this.getAllBaggageItems = () => {}
@@ -1,24 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
- /**
5
- * @constant
6
- * @type {string} Base path for EVP proxy agent endpoint
7
- */
8
- EVP_PROXY_AGENT_BASE_PATH: '/evp_proxy/v2/',
9
-
10
- /**
11
- * @constant
12
- * @type {string} HTTP header name for EVP subdomain routing
13
- */
14
- EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
15
-
16
- /**
17
- * @constant
18
- * @type {string} EVP subdomain value for event platform intake
19
- */
20
- EVP_SUBDOMAIN_VALUE: 'event-platform-intake',
21
-
22
4
  /**
23
5
  * @constant
24
6
  * @type {string} API endpoint for exposure events EVP track
@@ -1,14 +1,14 @@
1
1
  'use strict'
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
+
5
+ const { DatadogNodeServerProvider } = require('../../../../vendor/dist/@datadog/openfeature-node-server')
4
6
  const log = require('../log')
5
7
  const configurationSource = require('./configuration_source')
6
8
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
7
9
  const EvalMetricsHook = require('./eval-metrics-hook')
8
10
  const SpanEnrichmentHook = require('./span-enrichment-hook')
9
11
 
10
- const { DatadogNodeServerProvider } = require('./require-provider')
11
-
12
12
  /**
13
13
  * OpenFeature provider that integrates with Datadog's feature flagging system.
14
14
  * Extends DatadogNodeServerProvider to add tracer integration and configuration management.
@@ -48,6 +48,22 @@ class FlaggingProvider extends DatadogNodeServerProvider {
48
48
  this.#configurationSource?.start()
49
49
  }
50
50
 
51
+ /**
52
+ * @param {import('@openfeature/core').EvaluationContext} [context]
53
+ * @returns {Promise<void>}
54
+ */
55
+ initialize (context) {
56
+ const promise = super.initialize(context)
57
+
58
+ // `DatadogNodeServerProvider#initialize` starts a timer that is never unref'd, which would
59
+ // otherwise keep an idle process (a short script, a serverless handler) alive for up to
60
+ // `initializationTimeoutMs` while waiting for configuration to arrive.
61
+ // TODO: remove once `@datadog/openfeature-node-server` unrefs this timer itself.
62
+ this.initController?.timeoutId?.unref?.()
63
+
64
+ return promise
65
+ }
66
+
51
67
  /**
52
68
  * Called when the provider is shut down.
53
69
  * Cleans up resources including channel subscriptions.
@@ -3,7 +3,7 @@
3
3
  const { channel } = require('dc-polyfill')
4
4
  const log = require('../log')
5
5
  const ExposuresWriter = require('./writers/exposures')
6
- const { setAgentStrategy } = require('./writers/util')
6
+ const { setExposureDeliveryStrategy } = require('./writers/util')
7
7
 
8
8
  const exposureSubmitCh = channel('ffe:exposure:submit')
9
9
  const flushCh = channel('ffe:writers:flush')
@@ -40,12 +40,15 @@ function enable (config) {
40
40
  return
41
41
  }
42
42
 
43
- exposuresWriter = new ExposuresWriter(config)
43
+ const writer = new ExposuresWriter(config)
44
+ exposuresWriter = writer
44
45
  exposureSubmitCh.subscribe(_handleExposureSubmit)
45
46
  flushCh.subscribe(_handleFlush)
46
47
 
47
- setAgentStrategy(config, hasAgent => {
48
- exposuresWriter?.setEnabled(hasAgent)
48
+ setExposureDeliveryStrategy(config, (enabled, route) => {
49
+ if (exposuresWriter !== writer) return
50
+
51
+ writer.setEnabled(enabled, route)
49
52
  })
50
53
  }
51
54
 
@@ -6,7 +6,7 @@ const RemoteConfigCapabilities = require('../remote_config/capabilities')
6
6
  * Configures remote config handlers for openfeature feature flagging
7
7
  *
8
8
  * @param {import('../remote_config')} rc - RemoteConfig instance
9
- * @param {() => import('./flagging_provider')} getOpenfeatureProxy
9
+ * @param {() => InstanceType<ReturnType<typeof import('./flagging_provider')>>} getOpenfeatureProxy
10
10
  * @param {boolean} subscribe - Whether Agent Remote Config owns UFC delivery
11
11
  */
12
12
  function enable (rc, getOpenfeatureProxy, subscribe) {