dd-trace 6.7.0 → 6.8.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 (147) hide show
  1. package/ci/test-optimization-validation/command-output-policy.js +5 -5
  2. package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
  3. package/ci/test-optimization-validation/generated-files.js +9 -9
  4. package/ci/test-optimization-validation/generated-test-contract.js +1 -3
  5. package/ci/test-optimization-validation/manifest-scaffold.js +1 -2
  6. package/ci/test-optimization-validation/offline-output.js +6 -6
  7. package/ci/test-optimization-validation/runner-command.js +1 -1
  8. package/ci/test-optimization-validation/safe-files.js +4 -4
  9. package/ci/test-optimization-validation/scenarios/basic-reporting.js +1 -1
  10. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  11. package/ci/vitest-no-worker-init-setup.mjs +348 -51
  12. package/index.d.ts +77 -13
  13. package/package.json +9 -3
  14. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  15. package/packages/datadog-esbuild/index.js +4 -2
  16. package/packages/datadog-esbuild/src/utils.js +1 -1
  17. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
  18. package/packages/datadog-instrumentations/src/cucumber.js +9 -6
  19. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  20. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  21. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  22. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  27. package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
  28. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  29. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  30. package/packages/datadog-instrumentations/src/jest.js +8 -14
  31. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  32. package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
  33. package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
  34. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
  35. package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
  36. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  37. package/packages/datadog-instrumentations/src/playwright.js +3 -2
  38. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  39. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
  40. package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
  41. package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
  42. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  43. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  44. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  45. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  46. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  47. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  48. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  49. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  50. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  51. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
  52. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  53. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  54. package/packages/datadog-plugin-fs/src/index.js +0 -4
  55. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  56. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  57. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  58. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  59. package/packages/datadog-plugin-http/src/client.js +2 -1
  60. package/packages/datadog-plugin-jest/src/index.js +2 -3
  61. package/packages/datadog-plugin-jest/src/util.js +2 -1
  62. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  63. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  64. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  65. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  66. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  67. package/packages/datadog-plugin-undici/src/index.js +2 -1
  68. package/packages/datadog-plugin-vitest/src/index.js +84 -11
  69. package/packages/datadog-webpack/index.js +2 -2
  70. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  71. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  72. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  73. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  74. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  75. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  76. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  77. package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
  78. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  80. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -2
  81. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  84. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  85. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  86. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  87. package/packages/dd-trace/src/config/defaults.js +14 -6
  88. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -2
  89. package/packages/dd-trace/src/config/index.js +15 -0
  90. package/packages/dd-trace/src/config/parsers.js +19 -1
  91. package/packages/dd-trace/src/config/supported-configurations.json +5 -6
  92. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  93. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  94. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  96. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  97. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  98. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  99. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  100. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  101. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  102. package/packages/dd-trace/src/flare/index.js +2 -2
  103. package/packages/dd-trace/src/id.js +1 -1
  104. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  105. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  106. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  107. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  108. package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
  109. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  110. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  111. package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
  112. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  113. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  114. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  115. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  116. package/packages/dd-trace/src/llmobs/sdk.js +4 -11
  117. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  118. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  119. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  120. package/packages/dd-trace/src/noop/proxy.js +2 -2
  121. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  122. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  123. package/packages/dd-trace/src/opentracing/span.js +1 -1
  124. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  125. package/packages/dd-trace/src/plugins/ci_plugin.js +105 -20
  126. package/packages/dd-trace/src/plugins/index.js +1 -0
  127. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  128. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  129. package/packages/dd-trace/src/plugins/util/ci.js +7 -6
  130. package/packages/dd-trace/src/plugins/util/git.js +1 -1
  131. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
  132. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  133. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  134. package/packages/dd-trace/src/plugins/util/test.js +38 -12
  135. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  136. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  137. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  138. package/packages/dd-trace/src/priority_sampler.js +6 -3
  139. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  140. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  141. package/packages/dd-trace/src/ritm.js +5 -20
  142. package/packages/dd-trace/src/startup-log.js +3 -0
  143. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  144. package/packages/dd-trace/src/util.js +16 -0
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  146. package/vendor/dist/pprof-format/index.js +1 -1
  147. package/vendor/dist/source-map/index.js +1 -1
@@ -1,8 +1,90 @@
1
1
  'use strict'
2
2
 
3
+ const log = require('../../log')
4
+
5
+ class NoopDataset {
6
+ #name
7
+ #records
8
+
9
+ constructor (name = '', options = {}) {
10
+ this.#name = name
11
+ this.#records = (typeof options === 'string' ? [] : (options.records ?? [])).map(record => ({
12
+ id: record.id ?? null,
13
+ input: record.inputData,
14
+ expectedOutput: record.expectedOutput ?? null,
15
+ metadata: record.metadata ?? {},
16
+ }))
17
+ }
18
+
19
+ addRecord (input, expectedOutput, metadata) {
20
+ this.#records.push({ id: null, input, expectedOutput: expectedOutput ?? null, metadata: metadata ?? {} })
21
+ return this
22
+ }
23
+
24
+ push () {
25
+ return Promise.resolve({ pushedCount: 0, totalCount: 0 })
26
+ }
27
+
28
+ name () {
29
+ return this.#name
30
+ }
31
+
32
+ id () {
33
+ return null
34
+ }
35
+
36
+ projectId () {
37
+ return null
38
+ }
39
+
40
+ version () {
41
+ return null
42
+ }
43
+
44
+ latestVersion () {
45
+ return null
46
+ }
47
+
48
+ records () {
49
+ return [...this.#records]
50
+ }
51
+
52
+ recordIds () {
53
+ return []
54
+ }
55
+
56
+ url () {
57
+ return null
58
+ }
59
+ }
60
+
61
+ class NoopExperiment {
62
+ #name
63
+
64
+ constructor (name = '') {
65
+ this.#name = name
66
+ }
67
+
68
+ name () {
69
+ return this.#name
70
+ }
71
+
72
+ experimentId () {
73
+ return null
74
+ }
75
+
76
+ url () {
77
+ return null
78
+ }
79
+
80
+ run () {
81
+ return Promise.resolve({ experimentId: null, rows: [], url: null })
82
+ }
83
+ }
84
+
3
85
  // No-op Experiments used when LLM Observability is disabled or the API/APP keys
4
- // are not configured. Every operation throws a clear, actionable error rather
5
- // than silently doing nothing, so misconfiguration surfaces immediately.
86
+ // are not configured. Operations warn and return inert objects rather than
87
+ // throwing, so intentionally disabled experiments remain graceful.
6
88
  class NoopExperiments {
7
89
  #reason
8
90
 
@@ -10,20 +92,23 @@ class NoopExperiments {
10
92
  this.#reason = reason || 'LLMObs experiments are not available'
11
93
  }
12
94
 
13
- #unavailable () {
14
- return new Error(`LLMObs experiments unavailable: ${this.#reason}`)
95
+ #warn () {
96
+ log.warn('LLMObs experiments unavailable: %s', this.#reason)
15
97
  }
16
98
 
17
- createDataset () {
18
- throw this.#unavailable()
99
+ createDataset (name, options = {}) {
100
+ this.#warn()
101
+ return new NoopDataset(name, options)
19
102
  }
20
103
 
21
- pullDataset () {
22
- return Promise.reject(this.#unavailable())
104
+ pullDataset (name) {
105
+ this.#warn()
106
+ return Promise.resolve(new NoopDataset(name))
23
107
  }
24
108
 
25
- experiment () {
26
- throw this.#unavailable()
109
+ experiment (options = {}) {
110
+ this.#warn()
111
+ return new NoopExperiment(options.name)
27
112
  }
28
113
  }
29
114
 
@@ -22,13 +22,24 @@ class Row {
22
22
  }
23
23
  }
24
24
 
25
+ class ExperimentRun {
26
+ constructor (fields) {
27
+ this.runId = fields.runId
28
+ this.runIteration = fields.runIteration
29
+ this.rows = fields.rows
30
+ this.summaryEvaluations = fields.summaryEvaluations
31
+ }
32
+ }
33
+
25
34
  // Returned by Experiment.run().
26
35
  class ExperimentResult {
27
- constructor (experimentId, rows, url) {
36
+ constructor (experimentId, rows, url, runs = [], summaryEvaluations = {}) {
28
37
  this.experimentId = experimentId
29
38
  this.rows = rows
30
39
  this.url = url
40
+ this.runs = runs
41
+ this.summaryEvaluations = summaryEvaluations
31
42
  }
32
43
  }
33
44
 
34
- module.exports = { Row, ExperimentResult }
45
+ module.exports = { Row, ExperimentRun, ExperimentResult }
@@ -0,0 +1,218 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+
5
+ // Matches the backend and dd-trace-py evaluator metric label contract.
6
+ const EVALUATOR_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/
7
+
8
+ /**
9
+ * @typedef {null | string | number | boolean | NormalizedJsonValue[] | { [key: string]: NormalizedJsonValue }}
10
+ * NormalizedJsonValue
11
+ */
12
+
13
+ /**
14
+ * @param {object | null | undefined} value
15
+ * @returns {boolean}
16
+ */
17
+ function hasEntries (value) {
18
+ if (!value) return false
19
+ for (const key of Object.keys(value)) {
20
+ if (Object.hasOwn(value, key)) return true
21
+ }
22
+ return false
23
+ }
24
+
25
+ /**
26
+ * @param {string} name
27
+ */
28
+ function validateEvaluatorName (name) {
29
+ if (typeof name !== 'string') throw new TypeError('Evaluator name must be a string')
30
+ if (name.length === 0) throw new Error('Evaluator name cannot be empty')
31
+ if (!EVALUATOR_NAME_PATTERN.test(name)) {
32
+ throw new Error(
33
+ `Evaluator name '${name}' is invalid. Name must contain only alphanumeric characters, underscores, and hyphens.`
34
+ )
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @param {(...args: unknown[]) => unknown} fn
40
+ * @param {string} fallback
41
+ * @returns {string}
42
+ */
43
+ function functionName (fn, fallback) {
44
+ return typeof fn.name === 'string' && fn.name.length > 0 ? fn.name : fallback
45
+ }
46
+
47
+ /**
48
+ * @param {unknown} evaluators
49
+ * @param {string} kind
50
+ * @returns {Array<[string, (...args: unknown[]) => unknown]>}
51
+ */
52
+ function normalizeEvaluators (evaluators, kind) {
53
+ if (evaluators == null) return []
54
+
55
+ const normalized = []
56
+ if (Array.isArray(evaluators)) {
57
+ const indexesByName = new Map()
58
+ for (let i = 0; i < evaluators.length; i++) {
59
+ const evaluator = evaluators[i]
60
+ if (typeof evaluator !== 'function') throw new TypeError(`${kind} evaluator must be a function`)
61
+ const name = functionName(evaluator, `${kind}_evaluator_${i}`)
62
+ validateEvaluatorName(name)
63
+ if (indexesByName.has(name)) {
64
+ log.warn('Duplicate %s evaluator name %s; previous evaluator will be overwritten', kind, name)
65
+ normalized[indexesByName.get(name)] = [name, evaluator]
66
+ } else {
67
+ indexesByName.set(name, normalized.length)
68
+ normalized.push([name, evaluator])
69
+ }
70
+ }
71
+ return normalized
72
+ }
73
+
74
+ if (typeof evaluators !== 'object') {
75
+ throw new TypeError(`${kind} evaluators must be an array of functions or an object keyed by evaluator name`)
76
+ }
77
+
78
+ for (const [name, evaluator] of Object.entries(evaluators)) {
79
+ validateEvaluatorName(name)
80
+ if (typeof evaluator !== 'function') throw new TypeError(`${kind} evaluator '${name}' must be a function`)
81
+ normalized.push([name, evaluator])
82
+ }
83
+ return normalized
84
+ }
85
+
86
+ /**
87
+ * @param {unknown} value
88
+ * @returns {string}
89
+ */
90
+ function inferMetricType (value) {
91
+ if (typeof value === 'boolean') return 'boolean'
92
+ if (typeof value === 'number' && Number.isFinite(value)) return 'score'
93
+ if (typeof value === 'string') return 'categorical'
94
+ return 'json'
95
+ }
96
+
97
+ /**
98
+ * @param {unknown} value
99
+ * @param {WeakSet<object>} seen
100
+ * @returns {NormalizedJsonValue}
101
+ */
102
+ function normalizeJsonValue (value, seen) {
103
+ if (value === null) return null
104
+
105
+ const valueType = typeof value
106
+ if (valueType === 'string' || valueType === 'boolean') return value
107
+ if (valueType === 'number') return Number.isFinite(value) ? value : String(value)
108
+ if (valueType === 'bigint') return value.toString()
109
+ if (valueType === 'undefined') return null
110
+ if (valueType === 'symbol') return value.toString()
111
+ if (valueType === 'function') return { type: 'function', name: value.name || '<anonymous>' }
112
+
113
+ if (value instanceof Date) return value.toISOString()
114
+ if (value instanceof Error) {
115
+ return { type: value.name, message: value.message, stack: value.stack ?? '' }
116
+ }
117
+
118
+ if (seen.has(value)) return '[Circular]'
119
+ seen.add(value)
120
+
121
+ if (Array.isArray(value)) {
122
+ const normalized = new Array(value.length)
123
+ for (let i = 0; i < value.length; i++) normalized[i] = normalizeJsonValue(value[i], seen)
124
+ seen.delete(value)
125
+ return normalized
126
+ }
127
+
128
+ const normalized = {}
129
+ for (const [key, nestedValue] of Object.entries(value)) {
130
+ normalized[key] = normalizeJsonValue(nestedValue, seen)
131
+ }
132
+ seen.delete(value)
133
+ return normalized
134
+ }
135
+
136
+ /**
137
+ * @param {unknown} value
138
+ * @returns {{ [key: string]: NormalizedJsonValue }}
139
+ */
140
+ function normalizeJsonMetricValue (value) {
141
+ const normalized = normalizeJsonValue(value, new WeakSet())
142
+ if (normalized !== null && typeof normalized === 'object' && !Array.isArray(normalized)) return normalized
143
+ return { value: normalized }
144
+ }
145
+
146
+ /**
147
+ * @param {unknown} value
148
+ * @returns {string}
149
+ */
150
+ function stringify (value) {
151
+ if (value == null) return ''
152
+ if (typeof value === 'string') return value.toLowerCase()
153
+ if (typeof value === 'object') {
154
+ try {
155
+ const stringified = JSON.stringify(value)
156
+ if (stringified !== undefined) return stringified.toLowerCase()
157
+ } catch {}
158
+ }
159
+ return String(value).toLowerCase()
160
+ }
161
+
162
+ /**
163
+ * @param {Record<string, unknown> | undefined} userTags
164
+ * @param {Record<string, unknown>} autoTags
165
+ * @returns {string[]}
166
+ */
167
+ function buildTags (userTags, autoTags) {
168
+ const tags = new Map()
169
+ for (const [key, value] of Object.entries(userTags ?? {})) {
170
+ tags.set(key, `${key}:${value}`)
171
+ }
172
+ for (const [key, value] of Object.entries(autoTags)) {
173
+ if (value !== undefined && value !== null && value !== '') tags.set(key, `${key}:${value}`)
174
+ }
175
+ return [...tags.values()]
176
+ }
177
+
178
+ /**
179
+ * @param {Record<string, unknown> | undefined} userTags
180
+ * @param {Record<string, unknown>} autoTags
181
+ * @returns {Record<string, unknown>}
182
+ */
183
+ function buildExperimentTagObject (userTags, autoTags) {
184
+ return userTags ? { ...userTags, ...autoTags } : { ...autoTags }
185
+ }
186
+
187
+ /**
188
+ * @param {number} ms
189
+ * @returns {Promise<void>}
190
+ */
191
+ function sleep (ms) {
192
+ if (ms <= 0) return Promise.resolve()
193
+ return new Promise((resolve) => setTimeout(resolve, ms))
194
+ }
195
+
196
+ /**
197
+ * @param {Record<string, unknown> | undefined} recordMetadata
198
+ * @param {Record<string, unknown>} config
199
+ * @returns {Record<string, unknown>}
200
+ */
201
+ function buildSpanMetadata (recordMetadata, config) {
202
+ return recordMetadata
203
+ ? { ...recordMetadata, experiment_config: config }
204
+ : { experiment_config: config }
205
+ }
206
+
207
+ module.exports = {
208
+ buildExperimentTagObject,
209
+ buildSpanMetadata,
210
+ buildTags,
211
+ hasEntries,
212
+ inferMetricType,
213
+ normalizeEvaluators,
214
+ normalizeJsonMetricValue,
215
+ sleep,
216
+ stringify,
217
+ validateEvaluatorName,
218
+ }
@@ -93,7 +93,7 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
93
93
 
94
94
  const { usage } = chunk
95
95
  if (usage) {
96
- const responseUsage = response.usage ?? (response.usage = { input_tokens: 0, output_tokens: 0 })
96
+ const responseUsage = (response.usage ??= { input_tokens: 0, output_tokens: 0 })
97
97
  responseUsage.output_tokens = usage.output_tokens
98
98
 
99
99
  const cacheCreationTokens = usage.cache_creation_input_tokens
@@ -8,7 +8,7 @@ const { splitModel } = require('../../../../../datadog-plugin-claude-agent-sdk/s
8
8
  const subagentToolIds = new Set()
9
9
 
10
10
  function normalizeToolOutputString (raw) {
11
- const footerIndex = raw.search(/\s*agentId: [^\s]+ \(use SendMessage\b/)
11
+ const footerIndex = raw.search(/agentId: \S+ \(use SendMessage\b/)
12
12
  if (footerIndex === -1) return raw
13
13
 
14
14
  return raw.slice(0, footerIndex).trimEnd()
@@ -24,7 +24,7 @@ class GenAiLLMObsPlugin extends LLMObsPlugin {
24
24
  // Subscribe to streaming chunk events
25
25
  this.addSub('apm:google:genai:request:chunk', ({ ctx, chunk, done }) => {
26
26
  ctx.isStreaming = true
27
- ctx.chunks = ctx.chunks || []
27
+ ctx.chunks ||= []
28
28
 
29
29
  if (chunk) ctx.chunks.push(chunk)
30
30
  if (!done) return
@@ -36,7 +36,7 @@ class LangChainLLMObsHandler {
36
36
  const runIdBase = runId ? runId.split('-').slice(0, -1).join('-') : ''
37
37
 
38
38
  const responseMetadata = message.response_metadata || {}
39
- usage = usage || responseMetadata.usage || responseMetadata.tokenUsage || {}
39
+ usage ||= responseMetadata.usage || responseMetadata.tokenUsage || {}
40
40
 
41
41
  const inputTokens = usage.promptTokens || usage.inputTokens || usage.prompt_tokens || usage.input_tokens || 0
42
42
  const outputTokens =
@@ -35,12 +35,6 @@ class LLMObs extends NoopLLMObs {
35
35
  */
36
36
  #hasUserSpanProcessor = false
37
37
 
38
- /**
39
- * Lazily-created experiments facade (see ./experiments).
40
- * @type {import('./experiments').Experiments | undefined}
41
- */
42
- #experiments
43
-
44
38
  /**
45
39
  * @param {import('../tracer')} tracer - Tracer instance
46
40
  * @param {import('./index')} llmobsModule - LLMObs module instance
@@ -66,8 +60,7 @@ class LLMObs extends NoopLLMObs {
66
60
  * a clear message on use.
67
61
  */
68
62
  get experiments () {
69
- this.#experiments ??= createExperiments(this._config)
70
- return this.#experiments
63
+ return createExperiments(this._config, this)
71
64
  }
72
65
 
73
66
  enable (options = {}) {
@@ -318,14 +311,14 @@ class LLMObs extends NoopLLMObs {
318
311
  }
319
312
  throw e
320
313
  } finally {
321
- if (autoinstrumented === false) {
314
+ if (!autoinstrumented) {
322
315
  telemetry.recordLLMObsAnnotate(span, err)
323
316
  }
324
317
  }
325
318
  }
326
319
 
327
320
  exportSpan (span) {
328
- span = span || this._active()
321
+ span ||= this._active()
329
322
  let err = ''
330
323
  try {
331
324
  if (!span) {
@@ -426,7 +419,7 @@ class LLMObs extends NoopLLMObs {
426
419
  err = 'invalid_metric_value'
427
420
  throw new Error('value must be a boolean for a boolean metric')
428
421
  }
429
- if (metricType === 'json' && !(typeof value === 'object' && value != null && !Array.isArray(value))) {
422
+ if (metricType === 'json' && (typeof value !== 'object' || value == null || Array.isArray(value))) {
430
423
  err = 'invalid_metric_value'
431
424
  throw new Error('value must be a JSON object for a json metric')
432
425
  }
@@ -239,6 +239,14 @@ class LLMObsSpanProcessor {
239
239
 
240
240
  const apmTraceId = span.context().toTraceId(true)
241
241
  const llmobsTraceId = mlObsTags[TRACE_ID] ?? apmTraceId
242
+ const dd = {
243
+ span_id: span.context().toSpanId(),
244
+ trace_id: apmTraceId,
245
+ sample_rate: mlObsTags[SAMPLE_RATE],
246
+ sampling_decision: mlObsTags[SAMPLING_DECISION],
247
+ apm_trace_id: apmTraceId,
248
+ }
249
+ if (tags.experiment_id) dd.scope = 'experiments'
242
250
 
243
251
  const llmObsSpanEvent = {
244
252
  trace_id: llmobsTraceId,
@@ -251,13 +259,7 @@ class LLMObsSpanProcessor {
251
259
  status: error ? 'error' : 'ok',
252
260
  meta,
253
261
  metrics,
254
- _dd: {
255
- span_id: span.context().toSpanId(),
256
- trace_id: apmTraceId,
257
- sample_rate: mlObsTags[SAMPLE_RATE],
258
- sampling_decision: mlObsTags[SAMPLING_DECISION],
259
- apm_trace_id: apmTraceId,
260
- },
262
+ _dd: dd,
261
263
  }
262
264
 
263
265
  if (sessionId) llmObsSpanEvent.session_id = sessionId
@@ -158,8 +158,7 @@ class LLMObsTagger {
158
158
  if (modelName) this.tagModelName(span, modelName)
159
159
  if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
160
160
 
161
- sessionId = sessionId ||
162
- registry.get(parent)?.[SESSION_ID] ||
161
+ sessionId ||= registry.get(parent)?.[SESSION_ID] ||
163
162
  traceTags[SESSION_ID_TRACE_DEFAULT_KEY] ||
164
163
  traceTags[PROPAGATED_SESSION_ID_KEY]
165
164
  if (sessionId) this._setTag(span, SESSION_ID, sessionId)
@@ -49,12 +49,18 @@ class LLMObsSpanWriter extends BaseWriter {
49
49
  }
50
50
 
51
51
  makePayload (events) {
52
- return events.map(event => ({
53
- '_dd.stage': 'raw',
54
- '_dd.tracer_version': tracerVersion,
55
- event_type: this._eventType,
56
- spans: [event],
57
- }))
52
+ return events.map(event => {
53
+ const eventData = {
54
+ '_dd.stage': 'raw',
55
+ '_dd.tracer_version': tracerVersion,
56
+ event_type: this._eventType,
57
+ spans: [event],
58
+ }
59
+ if (event?._dd?.scope === 'experiments') {
60
+ eventData['_dd.scope'] = 'experiments'
61
+ }
62
+ return eventData
63
+ })
58
64
  }
59
65
 
60
66
  _truncateSpanEvent (event) {
@@ -55,7 +55,7 @@ class NoopProxy {
55
55
 
56
56
  if (typeof fn !== 'function') return
57
57
 
58
- options = options || {}
58
+ options ||= {}
59
59
 
60
60
  return this._tracer.trace(name, options, fn)
61
61
  }
@@ -68,7 +68,7 @@ class NoopProxy {
68
68
 
69
69
  if (typeof fn !== 'function') return fn
70
70
 
71
- options = options || {}
71
+ options ||= {}
72
72
 
73
73
  return this._tracer.wrap(name, options, fn)
74
74
  }
@@ -198,7 +198,7 @@ class OtlpTransformer extends OtlpTransformerBase {
198
198
  if (isJson) {
199
199
  dataPoint.startTimeUnixNano = String(dataPoint.startTimeUnixNano)
200
200
  dataPoint.timeUnixNano = String(dataPoint.timeUnixNano)
201
- dataPoint.count = dataPoint.count || 0
201
+ dataPoint.count ||= 0
202
202
  }
203
203
 
204
204
  return dataPoint
@@ -253,7 +253,8 @@ class PeriodicMetricReader {
253
253
  #collectAndExport (callback = () => {}) {
254
254
  // Atomically drain measurements for export. New measurements can be recorded
255
255
  // during export without interfering with this batch.
256
- const allMeasurements = this.#measurements.splice(0)
256
+ const allMeasurements = this.#measurements
257
+ this.#measurements = []
257
258
 
258
259
  for (const instrument of this.observableInstruments) {
259
260
  const observableMeasurements = instrument.collect()
@@ -440,7 +440,7 @@ class DatadogSpan {
440
440
  }
441
441
  }
442
442
 
443
- spanContext._trace.ticks = spanContext._trace.ticks || now()
443
+ spanContext._trace.ticks ||= now()
444
444
  if (startTime) {
445
445
  spanContext._trace.startTime = startTime
446
446
  }
@@ -8,7 +8,7 @@ const TRACE_ID_128 = '_dd.p.tid'
8
8
 
9
9
  class DatadogSpanContext {
10
10
  constructor (props) {
11
- props = props || {}
11
+ props ||= {}
12
12
 
13
13
  this._traceId = props.traceId
14
14
  this._spanId = props.spanId