dd-trace 5.110.0 → 5.112.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 (164) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/README.md +13 -8
  3. package/ci/init.js +21 -2
  4. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  5. package/ext/tags.js +2 -0
  6. package/index.d.ts +34 -1
  7. package/initialize.mjs +5 -6
  8. package/loader-hook.mjs +76 -55
  9. package/package.json +37 -34
  10. package/packages/datadog-instrumentations/src/ai.js +45 -0
  11. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  12. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  13. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  14. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  15. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  16. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  17. package/packages/datadog-instrumentations/src/fs.js +8 -6
  18. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  25. package/packages/datadog-instrumentations/src/jest.js +713 -63
  26. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  27. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  28. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  29. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  30. package/packages/datadog-instrumentations/src/mongoose.js +13 -2
  31. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  32. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
  33. package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
  34. package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
  35. package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
  36. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  37. package/packages/datadog-plugin-ai/src/index.js +1 -1
  38. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  39. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +19 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  44. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  45. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  46. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  47. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  48. package/packages/datadog-plugin-cucumber/src/index.js +15 -24
  49. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
  50. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  51. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  52. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  53. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  54. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  55. package/packages/datadog-plugin-graphql/src/execute.js +639 -12
  56. package/packages/datadog-plugin-graphql/src/index.js +37 -9
  57. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  58. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  59. package/packages/datadog-plugin-graphql/src/validate.js +17 -6
  60. package/packages/datadog-plugin-http/src/index.js +6 -8
  61. package/packages/datadog-plugin-jest/src/index.js +31 -15
  62. package/packages/datadog-plugin-mocha/src/index.js +40 -15
  63. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  64. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  65. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  66. package/packages/datadog-plugin-openai/src/services.js +2 -2
  67. package/packages/datadog-plugin-playwright/src/index.js +4 -3
  68. package/packages/datadog-plugin-vitest/src/index.js +120 -72
  69. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  70. package/packages/dd-trace/src/aiguard/index.js +9 -14
  71. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  72. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  73. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  74. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  75. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  76. package/packages/dd-trace/src/appsec/channels.js +3 -1
  77. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  78. package/packages/dd-trace/src/appsec/graphql.js +14 -11
  79. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
  80. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  81. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  82. package/packages/dd-trace/src/appsec/index.js +10 -1
  83. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  84. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  85. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  86. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  87. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  90. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  91. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
  92. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  93. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  94. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  96. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
  97. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  98. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  99. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  100. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  101. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  102. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  103. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  104. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  105. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  106. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  107. package/packages/dd-trace/src/config/defaults.js +3 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
  109. package/packages/dd-trace/src/config/helper.js +1 -0
  110. package/packages/dd-trace/src/config/index.js +35 -22
  111. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  112. package/packages/dd-trace/src/config/remote_config.js +1 -1
  113. package/packages/dd-trace/src/config/supported-configurations.json +127 -56
  114. package/packages/dd-trace/src/constants.js +7 -0
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  116. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  117. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  118. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  119. package/packages/dd-trace/src/llmobs/index.js +1 -1
  120. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  121. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  122. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  123. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  124. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  125. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  126. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  127. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  128. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  129. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  130. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  131. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  132. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  133. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  135. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  136. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  137. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  138. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  139. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  140. package/packages/dd-trace/src/plugin_manager.js +23 -7
  141. package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
  142. package/packages/dd-trace/src/plugins/index.js +2 -0
  143. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  144. package/packages/dd-trace/src/plugins/util/test.js +15 -9
  145. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  146. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  147. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  148. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  149. package/packages/dd-trace/src/proxy.js +8 -8
  150. package/packages/dd-trace/src/span_processor.js +5 -6
  151. package/packages/dd-trace/src/span_stats.js +96 -78
  152. package/packages/dd-trace/src/startup-log.js +2 -1
  153. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  154. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  155. package/packages/dd-trace/src/telemetry/index.js +2 -2
  156. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  157. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  158. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  159. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  160. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  161. package/vendor/dist/protobufjs/index.js +1 -1
  162. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  163. package/vendor/dist/shell-quote/index.js +1 -1
  164. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
@@ -30,12 +30,12 @@ const {
30
30
  TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
31
31
  TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
32
32
  TEST_HAS_FAILED_ALL_RETRIES,
33
- getLibraryCapabilitiesTags,
33
+ getLibraryCapabilitiesTags: getDefaultLibraryCapabilitiesTags,
34
34
  TEST_RETRY_REASON_TYPES,
35
- isModifiedTest,
36
35
  TEST_IS_MODIFIED,
37
36
  TEST_HAS_DYNAMIC_NAME,
38
37
  TEST_FINAL_STATUS,
38
+ TEST_IS_TEST_FRAMEWORK_WORKER,
39
39
  } = require('../../dd-trace/src/plugins/util/test')
40
40
  const { COMPONENT } = require('../../dd-trace/src/constants')
41
41
  const {
@@ -67,52 +67,10 @@ class VitestPlugin extends CiPlugin {
67
67
  testCommand: this.command,
68
68
  repositoryRoot: this.repositoryRoot,
69
69
  codeOwnersEntries: this.codeOwnersEntries,
70
+ testEnvironmentMetadata: this.testEnvironmentMetadata,
70
71
  })
71
72
  })
72
73
 
73
- this.addSub('ci:vitest:test:is-new', ({ knownTests, testSuiteAbsolutePath, testName, onDone }) => {
74
- // if for whatever reason the worker does not receive valid known tests, we don't consider it as new
75
- if (!knownTests.vitest) {
76
- return onDone(false)
77
- }
78
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
79
- const testsForThisTestSuite = knownTests.vitest[testSuite] || []
80
- onDone(!testsForThisTestSuite.includes(testName))
81
- })
82
-
83
- this.addSub('ci:vitest:test:is-attempt-to-fix', ({
84
- testManagementTests,
85
- testSuiteAbsolutePath,
86
- testName,
87
- onDone,
88
- }) => {
89
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
90
- const { isAttemptToFix } = this.getTestProperties(testManagementTests, testSuite, testName)
91
-
92
- onDone(isAttemptToFix ?? false)
93
- })
94
-
95
- this.addSub('ci:vitest:test:is-disabled', ({ testManagementTests, testSuiteAbsolutePath, testName, onDone }) => {
96
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
97
- const { isDisabled } = this.getTestProperties(testManagementTests, testSuite, testName)
98
-
99
- onDone(isDisabled)
100
- })
101
-
102
- this.addSub('ci:vitest:test:is-quarantined', ({ testManagementTests, testSuiteAbsolutePath, testName, onDone }) => {
103
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
104
- const { isQuarantined } = this.getTestProperties(testManagementTests, testSuite, testName)
105
-
106
- onDone(isQuarantined)
107
- })
108
-
109
- this.addSub('ci:vitest:test:is-modified', ({ modifiedFiles, testSuiteAbsolutePath, onDone }) => {
110
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
111
- const isModified = isModifiedTest(testSuite, 0, 0, modifiedFiles, this.constructor.id)
112
-
113
- onDone(isModified)
114
- })
115
-
116
74
  this.addSub('ci:vitest:is-early-flake-detection-faulty', ({
117
75
  knownTests,
118
76
  testFilepaths,
@@ -141,12 +99,16 @@ class VitestPlugin extends CiPlugin {
141
99
  isRetryReasonAttemptToFix,
142
100
  isRetryReasonAtr,
143
101
  isModified,
102
+ isTestFrameworkWorker,
103
+ requestErrorTags,
144
104
  } = ctx
145
105
 
146
106
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
147
- const store = storage('legacy').getStore()
107
+ const store = ctx.currentStore || storage('legacy').getStore()
108
+ const testSuiteSpan = store?.testSuiteSpan || this.testSuiteSpan
148
109
 
149
110
  const extraTags = {
111
+ ...requestErrorTags,
150
112
  [TEST_SOURCE_FILE]: testSuite,
151
113
  }
152
114
  if (isRetry) {
@@ -179,11 +141,14 @@ class VitestPlugin extends CiPlugin {
179
141
  if (isModified) {
180
142
  extraTags[TEST_IS_MODIFIED] = 'true'
181
143
  }
144
+ if (isTestFrameworkWorker) {
145
+ extraTags[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
146
+ }
182
147
 
183
148
  const span = this.startTestSpan(
184
149
  testName,
185
150
  testSuite,
186
- this.testSuiteSpan,
151
+ testSuiteSpan,
187
152
  extraTags
188
153
  )
189
154
 
@@ -204,7 +169,7 @@ class VitestPlugin extends CiPlugin {
204
169
  })
205
170
 
206
171
  this.addBind('ci:vitest:test:finish-time', (ctx) => {
207
- const { status, task, attemptToFixPassed, attemptToFixFailed } = ctx
172
+ const { status, task, attemptToFixPassed, attemptToFixFailed, duration } = ctx
208
173
  const span = ctx.currentStore?.span
209
174
 
210
175
  // we store the finish time to finish at a later hook
@@ -218,7 +183,8 @@ class VitestPlugin extends CiPlugin {
218
183
  span.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'false')
219
184
  }
220
185
 
221
- this.taskToFinishTime.set(task, span._getTime())
186
+ const finishTime = typeof duration === 'number' ? span._startTime + duration : span._getTime()
187
+ this.taskToFinishTime.set(task, finishTime)
222
188
 
223
189
  ctx.parentStore = ctx.currentStore
224
190
  ctx.currentStore = { ...ctx.currentStore, span }
@@ -227,7 +193,7 @@ class VitestPlugin extends CiPlugin {
227
193
  return ctx.currentStore
228
194
  })
229
195
 
230
- this.addSub('ci:vitest:test:pass', ({ span, task, finalStatus, earlyFlakeAbortReason }) => {
196
+ this.addSub('ci:vitest:test:pass', ({ span, task, finalStatus, earlyFlakeAbortReason, promises }) => {
231
197
  if (span) {
232
198
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(span))
233
199
  span.setTag(TEST_STATUS, 'pass')
@@ -237,8 +203,21 @@ class VitestPlugin extends CiPlugin {
237
203
  if (earlyFlakeAbortReason) {
238
204
  span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
239
205
  }
240
- span.finish(this.taskToFinishTime.get(task))
241
- finishAllTraceSpans(span)
206
+ const finish = () => {
207
+ span.finish(this.taskToFinishTime.get(task))
208
+ finishAllTraceSpans(span)
209
+ }
210
+
211
+ if (finalStatus) {
212
+ if (promises && this.diBreakpointHitPromise) {
213
+ promises.hitBreakpointPromise = this.waitForPreparedDiBreakpointHit().then(finish)
214
+ return
215
+ }
216
+ finish()
217
+ this.cancelDiBreakpointHitWait()
218
+ return
219
+ }
220
+ finish()
242
221
  }
243
222
  })
244
223
 
@@ -247,6 +226,7 @@ class VitestPlugin extends CiPlugin {
247
226
  duration,
248
227
  error,
249
228
  shouldSetProbe,
229
+ shouldWaitForHitProbe,
250
230
  promises,
251
231
  hasFailedAllRetries,
252
232
  attemptToFixFailed,
@@ -262,7 +242,8 @@ class VitestPlugin extends CiPlugin {
262
242
  const { file, line, stackIndex, setProbePromise } = probeInformation
263
243
  this.runningTestProbe = { file, line }
264
244
  this.testErrorStackIndex = stackIndex
265
- promises.setProbePromise = setProbePromise
245
+ this.prepareDiBreakpointHitWait()
246
+ promises.setProbePromise = this.waitForDiOperation(setProbePromise)
266
247
  }
267
248
  }
268
249
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(span))
@@ -283,27 +264,56 @@ class VitestPlugin extends CiPlugin {
283
264
  if (earlyFlakeAbortReason) {
284
265
  span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
285
266
  }
286
- if (duration) {
287
- span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
288
- } else {
289
- span.finish() // `duration` is empty for retries, so we'll use clock time
267
+ const finish = () => {
268
+ if (duration) {
269
+ span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
270
+ } else {
271
+ span.finish() // `duration` is empty for retries, so we'll use clock time
272
+ }
273
+ finishAllTraceSpans(span)
274
+ }
275
+
276
+ if (!shouldSetProbe && finalStatus && promises && this.diBreakpointHitPromise) {
277
+ promises.hitBreakpointPromise = this.waitForPreparedDiBreakpointHit().then(finish)
278
+ return
279
+ }
280
+ finish()
281
+ if (shouldWaitForHitProbe) {
282
+ this.prepareDiBreakpointHitWait()
283
+ } else if (!shouldSetProbe) {
284
+ this.cancelDiBreakpointHitWait()
285
+ }
286
+ })
287
+
288
+ this.addSub('ci:vitest:test:di:wait', ({ promises }) => {
289
+ if (this.di) {
290
+ promises.hitBreakpointPromise = this.waitForDiBreakpointHits()
290
291
  }
291
- finishAllTraceSpans(span)
292
292
  })
293
293
 
294
- this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath, isNew, isDisabled }) => {
294
+ this.addSub('ci:vitest:test:skip', ({
295
+ testName,
296
+ testSuiteAbsolutePath,
297
+ isNew,
298
+ isDisabled,
299
+ isTestFrameworkWorker,
300
+ requestErrorTags,
301
+ testSuiteSpan,
302
+ }) => {
295
303
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
296
304
  const testSpan = this.startTestSpan(
297
305
  testName,
298
306
  testSuite,
299
- this.testSuiteSpan,
307
+ testSuiteSpan || this.testSuiteSpan,
300
308
  {
309
+ ...requestErrorTags,
301
310
  [TEST_SOURCE_FILE]: testSuite,
302
311
  [TEST_SOURCE_START]: 1, // we can't get the proper start line in vitest
303
312
  [TEST_STATUS]: 'skip',
304
313
  [TEST_FINAL_STATUS]: 'skip',
305
314
  ...(isDisabled ? { [TEST_MANAGEMENT_IS_DISABLED]: 'true' } : {}),
306
315
  ...(isNew ? { [TEST_IS_NEW]: 'true' } : {}),
316
+ ...(isTestFrameworkWorker ? { [TEST_IS_TEST_FRAMEWORK_WORKER]: 'true' } : {}),
307
317
  }
308
318
  )
309
319
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(testSpan))
@@ -311,10 +321,25 @@ class VitestPlugin extends CiPlugin {
311
321
  })
312
322
 
313
323
  this.addBind('ci:vitest:test-suite:start', (ctx) => {
314
- const { codeOwnersEntries, repositoryRoot, testSuiteAbsolutePath, frameworkVersion } = ctx
324
+ const {
325
+ codeOwnersEntries,
326
+ repositoryRoot,
327
+ testEnvironmentMetadata,
328
+ requestErrorTags,
329
+ testSuiteAbsolutePath,
330
+ frameworkVersion,
331
+ isTestFrameworkWorker,
332
+ isVitestNoWorkerInitActive,
333
+ } = ctx
315
334
 
316
335
  const testCommand = ctx.testCommand || 'vitest run'
317
336
  const { testSessionId, testModuleId } = ctx
337
+ if (testEnvironmentMetadata) {
338
+ this.testEnvironmentMetadata = {
339
+ ...this.testEnvironmentMetadata,
340
+ ...testEnvironmentMetadata,
341
+ }
342
+ }
318
343
  this._setRepositoryRoot(repositoryRoot, codeOwnersEntries)
319
344
  this.command = testCommand
320
345
  this.frameworkVersion = frameworkVersion
@@ -329,13 +354,16 @@ class VitestPlugin extends CiPlugin {
329
354
  // test suites run in a different process, so they also need to init the metadata dictionary
330
355
  const testSessionName = getTestSessionName(this.config, trimmedCommand, this.testEnvironmentMetadata)
331
356
  if (this.tracer._exporter.addMetadataTags) {
357
+ const libraryCapabilitiesTags = this.getLibraryCapabilitiesTags(frameworkVersion, {
358
+ isVitestNoWorkerInitActive,
359
+ })
332
360
  this.tracer._exporter.addMetadataTags({
333
361
  [TEST_LEVELS_METADATA]: {
334
362
  [TEST_COMMAND]: testCommand,
335
363
  [TEST_SESSION_NAME]: testSessionName,
336
364
  ...getTestLevelsMetadataTags(this.testEnvironmentMetadata),
337
365
  },
338
- test: getLibraryCapabilitiesTags(this.constructor.id),
366
+ test: libraryCapabilitiesTags,
339
367
  })
340
368
  }
341
369
 
@@ -348,9 +376,13 @@ class VitestPlugin extends CiPlugin {
348
376
  testSuite,
349
377
  'vitest'
350
378
  ),
379
+ ...requestErrorTags,
351
380
  [TEST_SOURCE_FILE]: testSuite,
352
381
  [TEST_SOURCE_START]: 1,
353
382
  }
383
+ if (isTestFrameworkWorker) {
384
+ testSuiteMetadata[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
385
+ }
354
386
 
355
387
  const codeOwners = this.getCodeOwners(testSuiteMetadata)
356
388
  if (codeOwners) {
@@ -374,13 +406,17 @@ class VitestPlugin extends CiPlugin {
374
406
  return ctx.currentStore
375
407
  })
376
408
 
377
- this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, onFinish }) => {
409
+ this.addSub('ci:vitest:test-suite:finish', ({ testSuiteSpan, status, deferFlush, onFinish }) => {
378
410
  if (testSuiteSpan) {
379
411
  testSuiteSpan.setTag(TEST_STATUS, status)
380
412
  testSuiteSpan.finish()
381
413
  finishAllTraceSpans(testSuiteSpan)
382
414
  }
383
415
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
416
+ if (deferFlush) {
417
+ onFinish()
418
+ return
419
+ }
384
420
  this.tracer._exporter.flush(onFinish)
385
421
  if (this.runningTestProbe) {
386
422
  this.removeDiProbe(this.runningTestProbe)
@@ -409,9 +445,15 @@ class VitestPlugin extends CiPlugin {
409
445
  isEarlyFlakeDetectionEnabled,
410
446
  isEarlyFlakeDetectionFaulty,
411
447
  isTestManagementTestsEnabled,
448
+ requestErrorTags,
412
449
  vitestPool,
450
+ isVitestNoWorkerInitActive,
413
451
  onFinish,
414
452
  }) => {
453
+ for (const [tag, value] of Object.entries(requestErrorTags || {})) {
454
+ this.testSessionSpan.setTag(tag, value)
455
+ this.testModuleSpan.setTag(tag, value)
456
+ }
415
457
  this.testSessionSpan.setTag(TEST_STATUS, status)
416
458
  this.testModuleSpan.setTag(TEST_STATUS, status)
417
459
  if (error) {
@@ -438,12 +480,12 @@ class VitestPlugin extends CiPlugin {
438
480
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
439
481
  this.testSessionSpan.finish()
440
482
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session', {
441
- hasFailedTestReplay: this.libraryConfig?.isDiEnabled || undefined,
483
+ hasFailedTestReplay: this.libraryConfig?.isDiEnabled && !isVitestNoWorkerInitActive ? true : undefined,
442
484
  })
443
485
  finishAllTraceSpans(this.testSessionSpan)
444
486
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
445
487
  provider: this.ciProviderName,
446
- autoInjected: !!this._tracerConfig.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
488
+ autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
447
489
  })
448
490
  this.tracer._exporter.flush(onFinish)
449
491
  })
@@ -453,6 +495,19 @@ class VitestPlugin extends CiPlugin {
453
495
  })
454
496
  }
455
497
 
498
+ /**
499
+ * Returns Vitest library capability metadata tags.
500
+ * @param {string} frameworkVersion - The Vitest version.
501
+ * @param {object} [ctx] - Diagnostic channel context.
502
+ * @param {boolean} [ctx.isVitestNoWorkerInitActive] - Whether no-worker init is active for this run.
503
+ * @returns {Record<string, string|undefined>}
504
+ */
505
+ getLibraryCapabilitiesTags (frameworkVersion, ctx = {}) {
506
+ return getDefaultLibraryCapabilitiesTags(this.constructor.id, frameworkVersion, {
507
+ omitFailedTestReplay: ctx.isVitestNoWorkerInitActive,
508
+ })
509
+ }
510
+
456
511
  /**
457
512
  * Handles the coverage report by discovering and uploading it if enabled.
458
513
  * @param {string} rootDir - The root directory where coverage reports are located.
@@ -469,13 +524,6 @@ class VitestPlugin extends CiPlugin {
469
524
  onDone,
470
525
  })
471
526
  }
472
-
473
- getTestProperties (testManagementTests, testSuite, testName) {
474
- const { attempt_to_fix: isAttemptToFix, disabled: isDisabled, quarantined: isQuarantined } =
475
- testManagementTests?.vitest?.suites?.[testSuite]?.tests?.[testName]?.properties || {}
476
-
477
- return { isAttemptToFix, isDisabled, isQuarantined }
478
- }
479
527
  }
480
528
 
481
529
  module.exports = VitestPlugin
@@ -119,11 +119,14 @@ function wrapCallback (original, wrapper) {
119
119
  * object.
120
120
  * @param {string | symbol} name - The property key of the method to wrap.
121
121
  * @param {(original: Function) => (...args: unknown[]) => unknown} wrapper - The wrapper function.
122
- * @param {{ replaceGetter?: boolean }} [options] - If `replaceGetter` is set to
123
- * true, the getter is accessed and the getter is replaced with one that just
124
- * returns the earlier retrieved value. Use with care! This may only be done in
125
- * case the getter absolutely has no side effect and no setter is defined for the
126
- * property.
122
+ * @param {{ replaceGetter?: boolean }} [options] - By default the getter is
123
+ * wrapped in place, so each property access runs the wrapper. A getter+setter
124
+ * pair keeps its setter; a setter-only property throws. If `replaceGetter` is
125
+ * true, the getter is instead accessed once and replaced with one returning the
126
+ * resolved wrapped value — for a lazy getter+setter pair (e.g. Node 20's
127
+ * `fs.opendir`) the setter is rebuilt to materialize a writable data property on
128
+ * assignment, keeping the descriptor observationally identical for downstream
129
+ * consumers. Use with care! This may only be done when the getter has no side effect.
127
130
  * @returns {Record<string | symbol, unknown> | Function | undefined} The target object with
128
131
  * the wrapped method.
129
132
  */
@@ -151,17 +154,13 @@ function wrap (target, name, wrapper, options) {
151
154
  enumerable: false,
152
155
  }
153
156
 
154
- if (descriptor.set && (!descriptor.get || options?.replaceGetter)) {
155
- // It is possible to support these cases by instrumenting both the getter
156
- // and setter (or only the setter, in case that is a use case).
157
- // For now, this is not supported due to the complexity and the fact that
158
- // this is not a common use case.
159
- throw new Error(options?.replaceGetter
160
- ? 'Replacing a getter/setter pair is not supported. Implement if required.'
161
- : 'Replacing setters is not supported. Implement if required.')
157
+ // A setter-only property has nothing to wrap. Instrumenting the setter is not
158
+ // implemented; a getter+setter pair is handled below.
159
+ if (descriptor.set && !descriptor.get) {
160
+ throw new Error('Replacing setters is not supported. Implement if required.')
162
161
  }
163
162
 
164
- const original = descriptor.value ?? options?.replaceGetter ? target[name] : descriptor.get
163
+ const original = (descriptor.value ?? options?.replaceGetter) ? target[name] : descriptor.get
165
164
 
166
165
  assertMethod(target, name, original)
167
166
 
@@ -176,8 +175,30 @@ function wrap (target, name, wrapper, options) {
176
175
  }
177
176
  descriptor.value = wrapped
178
177
  } else {
179
- if (descriptor.get) {
180
- // `replaceGetter` may only be used when the getter has no side effect.
178
+ if (descriptor.set && options?.replaceGetter) {
179
+ // A lazy accessor pair (e.g. Node 20's `fs.opendir`). `original` already
180
+ // resolved the value through the getter. Keep the property an accessor pair
181
+ // so the shape stays observationally identical — a downstream consumer may
182
+ // read the descriptor or assign to it on a specific Node.js version. The
183
+ // getter returns the wrapped value; the setter mirrors the native lazy
184
+ // contract (assignment self-replaces the property with a writable data
185
+ // property holding the assigned value), so a caller that overwrites the
186
+ // method gets exactly what they set, unwrapped, as before. The descriptor
187
+ // is the original accessor descriptor (no `value`/`writable` slots), so
188
+ // reassigning `get`/`set` keeps it a valid accessor descriptor.
189
+ descriptor.get = () => wrapped
190
+ descriptor.set = function (value) {
191
+ Object.defineProperty(this, name, {
192
+ configurable: descriptor.configurable,
193
+ enumerable: descriptor.enumerable,
194
+ writable: true,
195
+ value,
196
+ })
197
+ }
198
+ } else if (descriptor.get) {
199
+ // Wrap the getter in place; for a getter+setter pair the original setter
200
+ // stays untouched. `replaceGetter` (no side effect on read) instead returns
201
+ // the value resolved once into `wrapped`.
181
202
  descriptor.get = options?.replaceGetter ? () => wrapped : wrapped
182
203
  } else {
183
204
  descriptor.value = wrapped
@@ -2,15 +2,11 @@
2
2
 
3
3
  const log = require('../log')
4
4
  const { incomingHttpRequestStart } = require('./channels')
5
- const openaiIntegration = require('./integrations/openai')
6
- const vercelAiIntegration = require('./integrations/vercel-ai')
5
+ const integrations = require('./integrations')
7
6
  const AIGuard = require('./sdk')
8
7
 
9
8
  let isEnabled = false
10
9
  let aiguard
11
- let block
12
- let disableOpenAIIntegration
13
- let disableVercelAiIntegration
14
10
 
15
11
  function onIncomingHttpRequestStart () {
16
12
  // No-op: subscribing ensures the HTTP plugin spreads req onto the store
@@ -21,31 +17,30 @@ function enable (tracer, config) {
21
17
 
22
18
  try {
23
19
  aiguard = new AIGuard(tracer, config)
24
- block = config.experimental?.aiguard?.block !== false
20
+ const block = config.experimental?.aiguard?.block !== false
25
21
 
26
22
  incomingHttpRequestStart.subscribe(onIncomingHttpRequestStart)
27
- disableOpenAIIntegration = openaiIntegration.enable(aiguard, block)
28
- disableVercelAiIntegration = vercelAiIntegration.enable(aiguard, block)
23
+ integrations.enable(aiguard, block)
29
24
 
30
25
  isEnabled = true
31
26
  } catch (err) {
32
27
  log.error('AIGuard: unexpected error during initialization: %s', err.message)
33
- disable()
28
+ reset()
34
29
  }
35
30
  }
36
31
 
37
32
  function disable () {
38
33
  if (!isEnabled) return
39
34
 
35
+ reset()
36
+ }
37
+
38
+ function reset () {
40
39
  incomingHttpRequestStart.unsubscribe(onIncomingHttpRequestStart)
41
- disableOpenAIIntegration?.()
42
- disableVercelAiIntegration?.()
40
+ integrations.disable()
43
41
 
44
42
  aiguard = undefined
45
43
  isEnabled = false
46
- block = false
47
- disableOpenAIIntegration = undefined
48
- disableVercelAiIntegration = undefined
49
44
  }
50
45
 
51
46
  module.exports = { enable, disable }
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const openai = require('./openai')
4
+ const vercelAi = require('./vercel-ai')
5
+
6
+ let isEnabled = false
7
+
8
+ /**
9
+ * Enables all AI Guard provider integrations.
10
+ *
11
+ * @param {object} aiguard
12
+ * @param {boolean} block
13
+ */
14
+ function enable (aiguard, block) {
15
+ if (isEnabled) return
16
+
17
+ openai.enable(aiguard, block)
18
+ vercelAi.enable(aiguard, block)
19
+
20
+ isEnabled = true
21
+ }
22
+
23
+ function disable () {
24
+ vercelAi.disable()
25
+ openai.disable()
26
+ isEnabled = false
27
+ }
28
+
29
+ module.exports = {
30
+ enable,
31
+ disable,
32
+ openai,
33
+ vercelAi,
34
+ }
@@ -16,51 +16,65 @@ const chatCompletionsAfterChannel = channel('dd-trace:openai:chat.completions:af
16
16
  const responsesBeforeChannel = channel('dd-trace:openai:responses:before')
17
17
  const responsesAfterChannel = channel('dd-trace:openai:responses:after')
18
18
 
19
+ let isEnabled = false
20
+ let aiguard
21
+ let opts
22
+
19
23
  /**
20
24
  * Subscribes AI Guard to OpenAI lifecycle channels.
21
25
  *
22
- * @param {object} aiguard
26
+ * @param {object} aiguardInstance
23
27
  * @param {boolean} block
24
- * @returns {() => void}
25
28
  */
26
- function enable (aiguard, block) {
27
- const opts = { block, source: SOURCE_AUTO, integration: 'openai' }
28
-
29
- function onChatCompletionsBefore (ctx) {
30
- pushEvaluation(ctx, aiguard, getChatCompletionsInputMessages(ctx.args?.[0]), opts)
31
- }
32
-
33
- function onChatCompletionsAfter (ctx) {
34
- const inputMessages = getChatCompletionsInputMessages(ctx.args?.[0])
35
- if (!inputMessages?.length) return
36
- for (const message of getChatCompletionsOutputMessages(ctx.body)) {
37
- pushEvaluation(ctx, aiguard, [...inputMessages, message], opts)
38
- }
39
- }
29
+ function enable (aiguardInstance, block) {
30
+ if (isEnabled) return
40
31
 
41
- function onResponsesBefore (ctx) {
42
- pushEvaluation(ctx, aiguard, getResponsesInputMessages(ctx.args?.[0]), opts)
43
- }
44
-
45
- function onResponsesAfter (ctx) {
46
- const inputMessages = getResponsesInputMessages(ctx.args?.[0])
47
- if (!inputMessages?.length) return
48
- const outputMessages = getResponsesOutputMessages(ctx.body)
49
- if (!outputMessages.length) return
50
- pushEvaluation(ctx, aiguard, [...inputMessages, ...outputMessages], opts)
51
- }
32
+ aiguard = aiguardInstance
33
+ opts = { block, source: SOURCE_AUTO, integration: 'openai' }
52
34
 
53
35
  chatCompletionsBeforeChannel.subscribe(onChatCompletionsBefore)
54
36
  chatCompletionsAfterChannel.subscribe(onChatCompletionsAfter)
55
37
  responsesBeforeChannel.subscribe(onResponsesBefore)
56
38
  responsesAfterChannel.subscribe(onResponsesAfter)
57
39
 
58
- return function disable () {
59
- chatCompletionsBeforeChannel.unsubscribe(onChatCompletionsBefore)
60
- chatCompletionsAfterChannel.unsubscribe(onChatCompletionsAfter)
61
- responsesBeforeChannel.unsubscribe(onResponsesBefore)
62
- responsesAfterChannel.unsubscribe(onResponsesAfter)
40
+ isEnabled = true
41
+ }
42
+
43
+ function disable () {
44
+ if (!isEnabled) return
45
+
46
+ chatCompletionsBeforeChannel.unsubscribe(onChatCompletionsBefore)
47
+ chatCompletionsAfterChannel.unsubscribe(onChatCompletionsAfter)
48
+ responsesBeforeChannel.unsubscribe(onResponsesBefore)
49
+ responsesAfterChannel.unsubscribe(onResponsesAfter)
50
+
51
+ aiguard = undefined
52
+ opts = undefined
53
+ isEnabled = false
54
+ }
55
+
56
+ function onChatCompletionsBefore (ctx) {
57
+ pushEvaluation(ctx, aiguard, getChatCompletionsInputMessages(ctx.args?.[0]), opts)
58
+ }
59
+
60
+ function onChatCompletionsAfter (ctx) {
61
+ const inputMessages = getChatCompletionsInputMessages(ctx.args?.[0])
62
+ if (!inputMessages?.length) return
63
+ for (const message of getChatCompletionsOutputMessages(ctx.body)) {
64
+ pushEvaluation(ctx, aiguard, [...inputMessages, message], opts)
63
65
  }
64
66
  }
65
67
 
66
- module.exports = { enable }
68
+ function onResponsesBefore (ctx) {
69
+ pushEvaluation(ctx, aiguard, getResponsesInputMessages(ctx.args?.[0]), opts)
70
+ }
71
+
72
+ function onResponsesAfter (ctx) {
73
+ const inputMessages = getResponsesInputMessages(ctx.args?.[0])
74
+ if (!inputMessages?.length) return
75
+ const outputMessages = getResponsesOutputMessages(ctx.body)
76
+ if (!outputMessages.length) return
77
+ pushEvaluation(ctx, aiguard, [...inputMessages, ...outputMessages], opts)
78
+ }
79
+
80
+ module.exports = { enable, disable }