dd-trace 5.117.0 → 5.119.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 (209) hide show
  1. package/ci/diagnose.js +162 -21
  2. package/ci/init.js +0 -1
  3. package/ci/runbook.md +187 -180
  4. package/ci/test-optimization-validation/approval-artifacts.js +3 -1
  5. package/ci/test-optimization-validation/approval.js +52 -15
  6. package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
  7. package/ci/test-optimization-validation/ci-discovery.js +1 -1
  8. package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
  9. package/ci/test-optimization-validation/ci-remediation.js +112 -39
  10. package/ci/test-optimization-validation/cli.js +516 -681
  11. package/ci/test-optimization-validation/command-blocker.js +188 -33
  12. package/ci/test-optimization-validation/command-output-policy.js +8 -31
  13. package/ci/test-optimization-validation/command-runner.js +189 -245
  14. package/ci/test-optimization-validation/environment.js +90 -0
  15. package/ci/test-optimization-validation/executable.js +159 -388
  16. package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
  17. package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
  18. package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
  19. package/ci/test-optimization-validation/generated-files.js +84 -22
  20. package/ci/test-optimization-validation/generated-test-contract.js +281 -0
  21. package/ci/test-optimization-validation/generated-verifier.js +49 -16
  22. package/ci/test-optimization-validation/literal-environment.js +57 -0
  23. package/ci/test-optimization-validation/manifest-loader.js +2 -22
  24. package/ci/test-optimization-validation/manifest-scaffold.js +815 -515
  25. package/ci/test-optimization-validation/manifest-schema.js +494 -702
  26. package/ci/test-optimization-validation/offline-fixtures.js +4 -1
  27. package/ci/test-optimization-validation/offline-output.js +6 -6
  28. package/ci/test-optimization-validation/plan-writer.js +243 -984
  29. package/ci/test-optimization-validation/preflight-runner.js +49 -55
  30. package/ci/test-optimization-validation/redaction.js +2 -1
  31. package/ci/test-optimization-validation/report-writer.js +391 -1357
  32. package/ci/test-optimization-validation/result-semantics.js +86 -0
  33. package/ci/test-optimization-validation/runner-command.js +249 -0
  34. package/ci/test-optimization-validation/runner-contract.js +664 -0
  35. package/ci/test-optimization-validation/safe-files.js +4 -4
  36. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
  39. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
  40. package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
  41. package/ci/test-optimization-validation/scenarios/test-management.js +6 -5
  42. package/ci/test-optimization-validation/source-text.js +87 -0
  43. package/ci/test-optimization-validation/test-output.js +12 -22
  44. package/ci/vitest-no-worker-init-setup.mjs +348 -51
  45. package/ext/tags.d.ts +1 -0
  46. package/index.d.ts +84 -13
  47. package/package.json +10 -4
  48. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  49. package/packages/datadog-esbuild/index.js +4 -2
  50. package/packages/datadog-esbuild/src/utils.js +1 -1
  51. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
  52. package/packages/datadog-instrumentations/src/cucumber.js +9 -6
  53. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  54. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  55. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  56. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  57. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
  58. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  59. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
  60. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  62. package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
  63. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  64. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  65. package/packages/datadog-instrumentations/src/jest.js +8 -14
  66. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  67. package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
  68. package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
  69. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
  70. package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
  71. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  72. package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
  73. package/packages/datadog-instrumentations/src/playwright.js +3 -2
  74. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  75. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
  76. package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
  77. package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
  78. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  79. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  80. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  81. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  82. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  83. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  84. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  85. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  86. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  87. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
  88. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  89. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  90. package/packages/datadog-plugin-fs/src/index.js +0 -4
  91. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  92. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  93. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  94. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  95. package/packages/datadog-plugin-http/src/client.js +2 -1
  96. package/packages/datadog-plugin-jest/src/index.js +2 -3
  97. package/packages/datadog-plugin-jest/src/util.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  100. package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
  101. package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
  102. package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
  103. package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
  104. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  105. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  106. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  107. package/packages/datadog-plugin-undici/src/index.js +2 -1
  108. package/packages/datadog-plugin-vitest/src/index.js +84 -11
  109. package/packages/datadog-webpack/index.js +2 -2
  110. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  111. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  112. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  113. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  115. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  116. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  117. package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
  118. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +50 -51
  121. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
  122. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  124. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  125. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
  126. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  127. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +7 -5
  128. package/packages/dd-trace/src/config/defaults.js +14 -6
  129. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
  130. package/packages/dd-trace/src/config/index.js +15 -0
  131. package/packages/dd-trace/src/config/parsers.js +19 -1
  132. package/packages/dd-trace/src/config/supported-configurations.json +12 -6
  133. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  134. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  135. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  136. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  137. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  138. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  139. package/packages/dd-trace/src/encode/span-stats.js +7 -1
  140. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  141. package/packages/dd-trace/src/exporter.js +16 -9
  142. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  143. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  144. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  145. package/packages/dd-trace/src/flare/index.js +2 -2
  146. package/packages/dd-trace/src/id.js +1 -1
  147. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  148. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  149. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  150. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  151. package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
  152. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  153. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  154. package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
  155. package/packages/dd-trace/src/llmobs/index.js +9 -1
  156. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
  157. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  158. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  159. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  160. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  161. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
  162. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
  163. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
  164. package/packages/dd-trace/src/llmobs/sdk.js +6 -12
  165. package/packages/dd-trace/src/llmobs/span_processor.js +14 -7
  166. package/packages/dd-trace/src/llmobs/tagger.js +24 -5
  167. package/packages/dd-trace/src/llmobs/util.js +56 -3
  168. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  169. package/packages/dd-trace/src/noop/proxy.js +2 -2
  170. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
  171. package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
  172. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
  173. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  174. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  175. package/packages/dd-trace/src/opentracing/span.js +1 -1
  176. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  177. package/packages/dd-trace/src/plugins/ci_plugin.js +114 -23
  178. package/packages/dd-trace/src/plugins/index.js +2 -0
  179. package/packages/dd-trace/src/plugins/outbound.js +4 -1
  180. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  181. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  182. package/packages/dd-trace/src/plugins/util/ci.js +7 -6
  183. package/packages/dd-trace/src/plugins/util/git.js +1 -1
  184. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
  185. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  186. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  187. package/packages/dd-trace/src/plugins/util/test.js +38 -12
  188. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  189. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  190. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  191. package/packages/dd-trace/src/priority_sampler.js +6 -3
  192. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  193. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  194. package/packages/dd-trace/src/ritm.js +5 -20
  195. package/packages/dd-trace/src/span_processor.js +1 -1
  196. package/packages/dd-trace/src/span_stats.js +22 -4
  197. package/packages/dd-trace/src/startup-log.js +3 -0
  198. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  199. package/packages/dd-trace/src/util.js +16 -0
  200. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  201. package/vendor/dist/pprof-format/index.js +1 -1
  202. package/vendor/dist/source-map/index.js +1 -1
  203. package/ci/test-optimization-validation/command-suitability.js +0 -471
  204. package/ci/test-optimization-validation/init-probe-preload.js +0 -163
  205. package/ci/test-optimization-validation/init-probe.js +0 -246
  206. package/ci/test-optimization-validation/late-initialization.js +0 -63
  207. package/ci/test-optimization-validation/local-command.js +0 -163
  208. package/ci/test-optimization-validation/setup-runner.js +0 -97
  209. package/ci/test-optimization-validation-manifest.schema.json +0 -1
@@ -1,14 +1,10 @@
1
- import { realpathSync } from 'node:fs'
2
- import { relative } from 'node:path'
3
- import { performance } from 'node:perf_hooks'
1
+ import { afterEach, beforeAll, beforeEach, inject } from 'vitest'
4
2
 
5
- import { afterEach, beforeAll, beforeEach } from 'vitest'
6
-
7
- // Instrumentation-less worker setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
8
- // It applies Test Optimization execution changes without initializing dd-trace in Vitest workers.
3
+ // Instrumentation-less setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
4
+ // It applies Test Optimization execution changes without initializing dd-trace and also supports Browser Mode.
9
5
  const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
10
- const isNoWorkerInitActive = getIsNoWorkerInitActive()
11
- const providedContext = isNoWorkerInitActive ? getProvidedContext() : {}
6
+ const providedContext = getProvidedContext()
7
+ const isNoWorkerInitActive = providedContext.isActive ?? getIsNoWorkerInitActive()
12
8
  const attemptToFixTests = providedContext.attemptToFixTests || {}
13
9
  const attemptToFixRetries = providedContext.attemptToFixRetries || 0
14
10
  const disabledTests = providedContext.disabledTests || {}
@@ -22,7 +18,9 @@ const isEarlyFlakeDetectionEnabled = providedContext.isEarlyFlakeDetectionEnable
22
18
  const knownTests = providedContext.knownTests || {}
23
19
  const modifiedFiles = providedContext.modifiedFiles || {}
24
20
  const quarantinedTests = providedContext.quarantinedTests || {}
25
- const repositoryRoot = realpath(providedContext.repositoryRoot || process.cwd())
21
+ const isRumCorrelationEnabled = providedContext.isRumCorrelationEnabled !== false
22
+ const rumTestExecutionIdCookieName = providedContext.rumTestExecutionIdCookieName
23
+ const testPropertiesByFilepath = providedContext.testPropertiesByFilepath || {}
26
24
  let setVitestTaskFn
27
25
  if (isNoWorkerInitActive) {
28
26
  try {
@@ -35,7 +33,21 @@ const earlyFlakeDetectionRetriesByTask = new WeakMap()
35
33
  const earlyFlakeDetectionSkippedResults = new WeakMap()
36
34
  const earlyFlakeDetectionStartByTask = new WeakMap()
37
35
  const nextAttemptIndexByTask = new WeakMap()
38
- const realpaths = new Map()
36
+ const retryAttemptIndexByTask = new WeakMap()
37
+ const usedRumTestExecutionIds = new Set()
38
+ let now
39
+ let timeOrigin
40
+ // Use an unfaked monotonic clock in Node and Vitest's parent frame in Browser Mode.
41
+ if (typeof globalThis.process?.uptime === 'function') {
42
+ now = () => globalThis.process.uptime() * 1000
43
+ timeOrigin = Date.now() - now()
44
+ } else {
45
+ const clock = globalThis.window?.parent && globalThis.window.parent !== globalThis.window
46
+ ? globalThis.window.parent.performance
47
+ : globalThis.performance
48
+ now = clock ? clock.now.bind(clock) : Date.now
49
+ timeOrigin = Number.isFinite(clock?.timeOrigin) ? clock.timeOrigin : Date.now() - now()
50
+ }
39
51
 
40
52
  if (isNoWorkerInitActive) {
41
53
  // eslint-disable-next-line no-empty-pattern
@@ -51,11 +63,12 @@ if (isNoWorkerInitActive) {
51
63
  const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTest(testSuite, testName)
52
64
  const isQuarantinedTest = quarantinedTests[testSuite]?.[testName] && !isAttemptToFixTest
53
65
  const attemptIndex = getNextAttemptIndex(task)
66
+ const attemptStart = now()
54
67
  if (attemptIndex > 0) {
55
68
  recordTestOptimizationStatus(task, attemptIndex - 1)
56
69
  }
57
70
 
58
- if (disabledTests[testSuite]?.[testName] && !isAttemptToFixTest) {
71
+ if (!isAttemptToFixTest && disabledTests[testSuite]?.[testName]) {
59
72
  skip('Skipped by Datadog Test Optimization')
60
73
  } else if (isAttemptToFixTest && attemptIndex > 0) {
61
74
  task.result.state = 'run'
@@ -66,27 +79,33 @@ if (isNoWorkerInitActive) {
66
79
  }
67
80
  }
68
81
 
69
- if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt || isQuarantinedTest) {
70
- onTestFinished(() => {
71
- if (attemptIndex === getFinalAttemptIndex(task)) {
72
- if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt) {
73
- recordTestOptimizationStatus(task, attemptIndex, true)
74
- }
75
- switchQuarantinedFinalFailure(task, attemptIndex)
82
+ prepareRumCorrelation(task, attemptIndex)
83
+
84
+ onTestFinished(() => {
85
+ recordTestAttemptTiming(task, attemptIndex, attemptStart)
86
+ recordRetryErrorCount(task)
87
+ if (
88
+ (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt || isQuarantinedTest) &&
89
+ attemptIndex === getFinalAttemptIndex(task)
90
+ ) {
91
+ if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt) {
92
+ recordTestOptimizationStatus(task, attemptIndex, true)
76
93
  }
77
- })
78
- }
94
+ switchQuarantinedFinalFailure(task, attemptIndex)
95
+ }
96
+ finishRumCorrelation(task, attemptIndex)
97
+ })
79
98
  })
80
99
 
81
100
  afterEach(function ({ task }) {
82
101
  const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
83
- if (restoreEarlyFlakeDetectionSkippedResult(task)) {
84
- return
85
- }
86
- if (attemptIndex === getFinalAttemptIndex(task)) {
102
+ const restoredEarlyFlakeDetectionResult = restoreEarlyFlakeDetectionSkippedResult(task)
103
+ if (!restoredEarlyFlakeDetectionResult && attemptIndex === getFinalAttemptIndex(task)) {
87
104
  recordTestOptimizationStatus(task, attemptIndex)
88
105
  }
89
- switchQuarantinedFinalFailure(task, attemptIndex)
106
+ if (!restoredEarlyFlakeDetectionResult) {
107
+ switchQuarantinedFinalFailure(task, attemptIndex)
108
+ }
90
109
  })
91
110
  }
92
111
 
@@ -110,6 +129,7 @@ function applyExecutionChanges (suite) {
110
129
  task.repeats = earlyFlakeDetectionRetries
111
130
  task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries
112
131
  }
132
+ wrapRetryCondition(task)
113
133
  }
114
134
  }
115
135
 
@@ -117,9 +137,194 @@ function getNextAttemptIndex (task) {
117
137
  const attemptIndex = nextAttemptIndexByTask.get(task) || 0
118
138
  nextAttemptIndexByTask.set(task, attemptIndex + 1)
119
139
  task.meta.__ddTestOptCurrentAttemptIndex = attemptIndex
140
+
141
+ const repeatCount = task.result?.repeatCount || 0
142
+ const retryAttempt = retryAttemptIndexByTask.get(task)
143
+ const retryAttemptIndex = retryAttempt?.repeatCount === repeatCount ? retryAttempt.index + 1 : 0
144
+ retryAttemptIndexByTask.set(task, {
145
+ index: retryAttemptIndex,
146
+ repeatCount,
147
+ })
148
+
120
149
  return attemptIndex
121
150
  }
122
151
 
152
+ /**
153
+ * Sets the RUM correlation cookie for a sequential browser test attempt.
154
+ *
155
+ * @param {object} task
156
+ * @param {number} attemptIndex
157
+ * @returns {void}
158
+ */
159
+ function prepareRumCorrelation (task, attemptIndex) {
160
+ // A per-origin cookie cannot identify overlapping attempts without racing.
161
+ if (!isRumCorrelationEnabled || isConcurrentTask(task)) return
162
+
163
+ if (
164
+ typeof window === 'undefined' ||
165
+ typeof document === 'undefined' ||
166
+ typeof rumTestExecutionIdCookieName !== 'string'
167
+ ) {
168
+ return
169
+ }
170
+
171
+ const testExecutionId = generateTestExecutionId()
172
+ if (
173
+ !testExecutionId ||
174
+ usedRumTestExecutionIds.has(testExecutionId) ||
175
+ !setRumCorrelationCookie(testExecutionId)
176
+ ) {
177
+ return
178
+ }
179
+
180
+ usedRumTestExecutionIds.add(testExecutionId)
181
+ task.meta.__ddTestOptRumTestExecutionIds ||= []
182
+ task.meta.__ddTestOptRumTestExecutionIds[attemptIndex] = testExecutionId
183
+ recordRumActivity(task, attemptIndex)
184
+ }
185
+
186
+ /**
187
+ * Records whether RUM was active and clears the attempt cookie.
188
+ *
189
+ * @param {object} task
190
+ * @param {number} attemptIndex
191
+ * @returns {void}
192
+ */
193
+ function finishRumCorrelation (task, attemptIndex) {
194
+ const testExecutionId = task.meta.__ddTestOptRumTestExecutionIds?.[attemptIndex]
195
+ if (!testExecutionId) return
196
+
197
+ recordRumActivity(task, attemptIndex)
198
+ clearRumCorrelationCookie(testExecutionId)
199
+ }
200
+
201
+ /**
202
+ * Retains evidence that RUM was active at any point observed during the attempt.
203
+ *
204
+ * @param {object} task
205
+ * @param {number} attemptIndex
206
+ * @returns {void}
207
+ */
208
+ function recordRumActivity (task, attemptIndex) {
209
+ if (!getIsRumActive(getRum())) return
210
+
211
+ task.meta.__ddTestOptRumActive ||= []
212
+ task.meta.__ddTestOptRumActive[attemptIndex] = true
213
+ }
214
+
215
+ /**
216
+ * Returns the RUM public API without letting application-defined accessors fail a test.
217
+ *
218
+ * @returns {object|undefined}
219
+ */
220
+ function getRum () {
221
+ try {
222
+ return window.DD_RUM
223
+ } catch {}
224
+ }
225
+
226
+ /**
227
+ * Returns whether the current RUM session is active.
228
+ *
229
+ * @param {object|undefined} rum
230
+ * @returns {boolean}
231
+ */
232
+ function getIsRumActive (rum) {
233
+ if (!rum) return false
234
+ if (typeof rum.getInternalContext !== 'function') return false
235
+
236
+ try {
237
+ return !!rum.getInternalContext()
238
+ } catch {
239
+ return false
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Returns whether a task or any containing suite is concurrent.
245
+ *
246
+ * @param {object} task
247
+ * @returns {boolean}
248
+ */
249
+ function isConcurrentTask (task) {
250
+ let currentTask = task
251
+ while (currentTask) {
252
+ if (currentTask.concurrent === true) return true
253
+ currentTask = currentTask.suite
254
+ }
255
+ return false
256
+ }
257
+
258
+ /**
259
+ * Generates a positive 63-bit decimal test execution ID.
260
+ *
261
+ * @returns {string|undefined}
262
+ */
263
+ function generateTestExecutionId () {
264
+ // This setup file executes in a browser even though lint validates it against the package's Node.js target.
265
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
266
+ if (typeof globalThis.crypto?.getRandomValues !== 'function') return
267
+
268
+ const words = new Uint32Array(2)
269
+ try {
270
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
271
+ globalThis.crypto.getRandomValues(words)
272
+ } catch {
273
+ return
274
+ }
275
+
276
+ words[0] &= 0x7F_FF_FF_FF
277
+ if (words[0] === 0 && words[1] === 0) return
278
+
279
+ return ((BigInt(words[0]) << 32n) | BigInt(words[1])).toString(10)
280
+ }
281
+
282
+ /**
283
+ * Sets and verifies the RUM correlation cookie for the current origin.
284
+ *
285
+ * @param {string} testExecutionId
286
+ * @returns {boolean}
287
+ */
288
+ function setRumCorrelationCookie (testExecutionId) {
289
+ try {
290
+ // eslint-disable-next-line unicorn/no-document-cookie
291
+ document.cookie = `${rumTestExecutionIdCookieName}=${testExecutionId}; path=/`
292
+ return getRumCorrelationCookie() === testExecutionId
293
+ } catch {
294
+ return false
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Clears the RUM correlation cookie if it still belongs to this attempt.
300
+ *
301
+ * @param {string} testExecutionId
302
+ * @returns {void}
303
+ */
304
+ function clearRumCorrelationCookie (testExecutionId) {
305
+ try {
306
+ if (getRumCorrelationCookie() === testExecutionId) {
307
+ // eslint-disable-next-line unicorn/no-document-cookie
308
+ document.cookie = `${rumTestExecutionIdCookieName}=; path=/; max-age=0`
309
+ }
310
+ } catch {}
311
+ }
312
+
313
+ /**
314
+ * Returns the current RUM correlation cookie value.
315
+ *
316
+ * @returns {string|undefined}
317
+ */
318
+ function getRumCorrelationCookie () {
319
+ const prefix = `${rumTestExecutionIdCookieName}=`
320
+ for (const cookie of document.cookie.split(';')) {
321
+ const normalizedCookie = cookie.trim()
322
+ if (normalizedCookie.startsWith(prefix)) {
323
+ return normalizedCookie.slice(prefix.length)
324
+ }
325
+ }
326
+ }
327
+
123
328
  function recordTestOptimizationStatus (task, attemptIndex = task.result?.repeatCount || 0, onlyIfNewErrors = false) {
124
329
  const testSuite = getTestSuite(task)
125
330
  const testName = getTestName(task)
@@ -206,6 +411,76 @@ function recordManualRepeatStatus (task, attemptIndex) {
206
411
  task.meta.__ddTestOptRepeatErrorCounts[attemptIndex] = task.result?.errors?.length || 0
207
412
  }
208
413
 
414
+ /**
415
+ * Records cumulative errors at the end of each configured retry attempt.
416
+ *
417
+ * @param {object} task
418
+ * @returns {void}
419
+ */
420
+ function recordRetryErrorCount (task) {
421
+ const retryLimit = getRetryLimit(task)
422
+ if (retryLimit <= 0) return
423
+
424
+ const retryCount = task.result?.retryCount || 0
425
+ task.meta.__ddTestOptRetryErrorCounts ||= []
426
+ task.meta.__ddTestOptRetryErrorCounts[retryCount] = task.result?.errors?.length || 0
427
+ }
428
+
429
+ /**
430
+ * Records the wall-clock start and elapsed time for one retry or repeat execution.
431
+ *
432
+ * @param {object} task
433
+ * @param {number} attemptIndex
434
+ * @param {number} attemptStart
435
+ * @returns {void}
436
+ */
437
+ function recordTestAttemptTiming (task, attemptIndex, attemptStart) {
438
+ task.meta.__ddTestOptAttemptStartTimes ||= []
439
+ task.meta.__ddTestOptAttemptDurations ||= []
440
+ task.meta.__ddTestOptAttemptStartTimes[attemptIndex] = timeOrigin + attemptStart
441
+ task.meta.__ddTestOptAttemptDurations[attemptIndex] = now() - attemptStart
442
+ }
443
+
444
+ /**
445
+ * Returns the configured retry count for numeric and object-form retry options.
446
+ *
447
+ * @param {object} task
448
+ * @returns {number}
449
+ */
450
+ function getRetryLimit (task) {
451
+ return typeof task.retry === 'number' ? task.retry : task.retry?.count || 0
452
+ }
453
+
454
+ /**
455
+ * Wraps Vitest's retry condition so final-attempt handling follows the condition's actual result.
456
+ *
457
+ * @param {object} task
458
+ * @returns {void}
459
+ */
460
+ function wrapRetryCondition (task) {
461
+ if (typeof task.retry !== 'object' || !task.retry?.condition || getRetryLimit(task) === 0) return
462
+
463
+ const condition = task.retry.condition
464
+ task.retry = {
465
+ ...task.retry,
466
+ condition (error) {
467
+ let shouldRetry = false
468
+ if (condition instanceof RegExp) {
469
+ shouldRetry = condition.test(error?.message || '')
470
+ } else if (typeof condition === 'function') {
471
+ shouldRetry = condition(error)
472
+ }
473
+
474
+ if (!shouldRetry && (task.result?.repeatCount || 0) >= (task.repeats || 0)) {
475
+ const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
476
+ recordTestOptimizationStatus(task, attemptIndex)
477
+ markQuarantinedFailure(task)
478
+ }
479
+ return shouldRetry
480
+ },
481
+ }
482
+ }
483
+
209
484
  function restoreEarlyFlakeDetectionSkippedResult (task) {
210
485
  if (!task.meta.__ddTestOptEfdSkipCurrentAttempt) {
211
486
  return false
@@ -236,7 +511,18 @@ function getFinalAttemptIndex (task) {
236
511
  if (isEarlyFlakeDetectionTest(testSuite, testName)) {
237
512
  return getEarlyFlakeDetectionRetryCountForTask(task)
238
513
  }
239
- return task.repeats
514
+
515
+ const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
516
+ const repeatCount = task.result?.repeatCount || 0
517
+ const repeatLimit = task.repeats || 0
518
+ const retryAttemptIndex = retryAttemptIndexByTask.get(task)?.index || 0
519
+ const retryLimit = getRetryLimit(task)
520
+ const retriesRemaining = task.result?.state === 'fail' || task.result?.state === 'run'
521
+ ? Math.max(retryLimit - retryAttemptIndex, 0)
522
+ : 0
523
+ const repeatsRemaining = Math.max(repeatLimit - repeatCount, 0)
524
+
525
+ return attemptIndex + retriesRemaining + (repeatsRemaining * (retryLimit + 1))
240
526
  }
241
527
 
242
528
  function switchQuarantinedFinalFailure (task, attemptIndex) {
@@ -250,9 +536,27 @@ function switchQuarantinedFinalFailure (task, attemptIndex) {
250
536
  return
251
537
  }
252
538
 
253
- const retryCount = task.result?.retryCount || 0
254
- const retryLimit = task.retry || 0
255
- if (retryCount < retryLimit || attemptIndex < getFinalAttemptIndex(task)) {
539
+ if (attemptIndex < getFinalAttemptIndex(task)) {
540
+ return
541
+ }
542
+
543
+ markQuarantinedFailure(task)
544
+ }
545
+
546
+ /**
547
+ * Converts a quarantined failure into the passing runner state expected by Vitest.
548
+ *
549
+ * @param {object} task
550
+ * @returns {void}
551
+ */
552
+ function markQuarantinedFailure (task) {
553
+ const testSuite = getTestSuite(task)
554
+ const testName = getTestName(task)
555
+ if (
556
+ !quarantinedTests[testSuite]?.[testName] ||
557
+ attemptToFixTests[testSuite]?.[testName] ||
558
+ task.result?.state !== 'fail'
559
+ ) {
256
560
  return
257
561
  }
258
562
 
@@ -292,7 +596,7 @@ function getPreviousErrorCount (errorCounts, repeatCount) {
292
596
 
293
597
  function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
294
598
  if (attemptIndex === 0) {
295
- earlyFlakeDetectionStartByTask.set(task, performance.now())
599
+ earlyFlakeDetectionStartByTask.set(task, now())
296
600
  return false
297
601
  }
298
602
 
@@ -308,12 +612,12 @@ function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
308
612
  }
309
613
 
310
614
  if (attemptIndex <= retryCount) {
311
- earlyFlakeDetectionStartByTask.set(task, performance.now())
615
+ earlyFlakeDetectionStartByTask.set(task, now())
312
616
  return false
313
617
  }
314
618
 
315
619
  if (!canReplaceVitestTaskFn()) {
316
- earlyFlakeDetectionStartByTask.set(task, performance.now())
620
+ earlyFlakeDetectionStartByTask.set(task, now())
317
621
  return false
318
622
  }
319
623
 
@@ -334,7 +638,7 @@ function getEarlyFlakeDetectionRetryCount (task) {
334
638
  }
335
639
 
336
640
  const executionStart = earlyFlakeDetectionStartByTask.get(task)
337
- const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
641
+ const duration = executionStart === undefined ? task.result?.duration ?? 0 : now() - executionStart
338
642
  for (const { key, limitMs } of earlyFlakeDetectionRetryThresholds) {
339
643
  if (duration < limitMs) {
340
644
  return earlyFlakeDetectionSlowRetries[key] ?? 0
@@ -376,21 +680,10 @@ function isModifiedTest (testSuite) {
376
680
  }
377
681
 
378
682
  function getTestSuite (task) {
379
- let filepath = realpaths.get(task.file.filepath)
380
- if (filepath === undefined) {
381
- filepath = realpath(task.file.filepath)
382
- realpaths.set(task.file.filepath, filepath)
383
- }
384
-
385
- return normalizePath(relative(repositoryRoot, filepath))
386
- }
387
-
388
- function realpath (filepath) {
389
- try {
390
- return realpathSync(filepath)
391
- } catch {
392
- return filepath
393
- }
683
+ const filepath = task.file.filepath
684
+ return testPropertiesByFilepath[filepath]?.testSuite ||
685
+ testPropertiesByFilepath[normalizePath(filepath)]?.testSuite ||
686
+ normalizePath(filepath)
394
687
  }
395
688
 
396
689
  function getTestName (task) {
@@ -423,8 +716,12 @@ function getIsNoWorkerInitActive () {
423
716
 
424
717
  function getProvidedContext () {
425
718
  try {
426
- return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {}
719
+ return inject('_ddVitestWorkerSetup') || {}
427
720
  } catch {
428
- return {}
721
+ try {
722
+ return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {}
723
+ } catch {
724
+ return {}
725
+ }
429
726
  }
430
727
  }
package/ext/tags.d.ts CHANGED
@@ -20,6 +20,7 @@ declare const tags: {
20
20
  HTTP_RESPONSE_HEADERS: 'http.response.headers'
21
21
  HTTP_USERAGENT: 'http.useragent',
22
22
  HTTP_CLIENT_IP: 'http.client_ip',
23
+ GRPC_STATUS_CODE: 'grpc.status.code'
23
24
  PATHWAY_HASH: 'pathway.hash'
24
25
  }
25
26
 
package/index.d.ts CHANGED
@@ -293,6 +293,7 @@ interface Plugins {
293
293
  "next": tracer.plugins.next;
294
294
  "nyc": tracer.plugins.nyc;
295
295
  "openai": tracer.plugins.openai;
296
+ "openai-agents": tracer.plugins.openai_agents;
296
297
  "opensearch": tracer.plugins.opensearch;
297
298
  "oracledb": tracer.plugins.oracledb;
298
299
  "playwright": tracer.plugins.playwright;
@@ -3138,6 +3139,12 @@ declare namespace tracer {
3138
3139
  */
3139
3140
  interface openai extends Instrumentation {}
3140
3141
 
3142
+ /**
3143
+ * This plugin automatically instruments the
3144
+ * [@openai/agents](https://www.npmjs.com/package/@openai/agents) library.
3145
+ */
3146
+ interface openai_agents extends Instrumentation {}
3147
+
3141
3148
  /**
3142
3149
  * This plugin automatically instruments the
3143
3150
  * [opensearch](https://github.com/opensearch-project/opensearch-js) module.
@@ -3924,29 +3931,74 @@ declare namespace tracer {
3924
3931
  flush (): void
3925
3932
  }
3926
3933
 
3934
+ /** JSON-serializable value accepted by LLMObs Experiments. */
3935
+ type JSONType = string | number | boolean | null | JSONType[] | { [key: string]: JSONType }
3936
+
3927
3937
  /**
3928
3938
  * A task run over each dataset record during an experiment.
3929
3939
  */
3930
- type ExperimentTask = (input: any, config: Record<string, any>) => any | Promise<any>
3940
+ type ExperimentTask = (
3941
+ input: JSONType,
3942
+ config: Record<string, JSONType>,
3943
+ metadata?: Record<string, JSONType>
3944
+ ) => JSONType | Promise<JSONType>
3931
3945
 
3932
3946
  /**
3933
3947
  * Scores a single task output. The return type selects the metric:
3934
- * `boolean` -> boolean, `number` -> score, anything else -> categorical.
3948
+ * `boolean` -> boolean, `number` -> score, `string` -> categorical, anything else -> json.
3949
+ */
3950
+ type ExperimentEvaluator = (
3951
+ input: JSONType,
3952
+ output: JSONType,
3953
+ expectedOutput: JSONType
3954
+ ) => JSONType | Promise<JSONType>
3955
+
3956
+ /**
3957
+ * Scores all rows in an experiment run and emits a summary metric.
3935
3958
  */
3936
- type ExperimentEvaluator = (input: any, output: any, expectedOutput: any) => any | Promise<any>
3959
+ type ExperimentSummaryEvaluator = (
3960
+ inputs: any[],
3961
+ outputs: any[],
3962
+ expectedOutputs: any[],
3963
+ evaluatorResults: Record<string, any[]>,
3964
+ metadata?: Array<Record<string, any>>
3965
+ ) => any | Promise<any>
3966
+
3967
+ interface CreateDatasetOptions {
3968
+ description?: string
3969
+ records?: Array<{
3970
+ id?: string,
3971
+ inputData: JSONType,
3972
+ expectedOutput?: JSONType,
3973
+ metadata?: Record<string, JSONType>
3974
+ }>
3975
+ }
3937
3976
 
3938
3977
  interface ExperimentOptions {
3939
3978
  name: string
3940
3979
  dataset: Dataset
3941
3980
  task: ExperimentTask
3942
- /** Evaluators keyed by metric label. */
3943
- evaluators?: Record<string, ExperimentEvaluator>
3981
+ /** Evaluators keyed by metric label, or named functions. */
3982
+ evaluators?: Record<string, ExperimentEvaluator> | ExperimentEvaluator[]
3983
+ /** Summary evaluators keyed by metric label, or named functions. */
3984
+ summaryEvaluators?: Record<string, ExperimentSummaryEvaluator> | ExperimentSummaryEvaluator[]
3944
3985
  description?: string
3945
- config?: Record<string, any>
3986
+ config?: Record<string, JSONType>
3946
3987
  tags?: Record<string, string>
3947
3988
  }
3948
3989
 
3990
+ interface ExperimentRunOptions {
3991
+ /** Maximum retries for task and evaluator failures. Default 0. */
3992
+ maxRetries?: number
3993
+ /** Delay before a retry, in milliseconds. Default 100 * (attempt + 1). */
3994
+ retryDelay?: (attempt: number) => number
3995
+ /** Reject on the first task/evaluator error instead of capturing it. Default false. */
3996
+ throwOnErrors?: boolean
3997
+ }
3998
+
3949
3999
  interface PullDatasetOptions {
4000
+ /** Dataset version to pull. Defaults to latest. */
4001
+ version?: number
3950
4002
  /** Wait until at least this many records are readable (absorbs write lag). */
3951
4003
  expectedRecordCount?: number
3952
4004
  /** Maximum total time to wait, in ms. Default 30000. */
@@ -3959,19 +4011,30 @@ declare namespace tracer {
3959
4011
  traceId: string
3960
4012
  startNs: number
3961
4013
  durationNs: number
3962
- input: any
3963
- output: any
3964
- expectedOutput: any
4014
+ input: JSONType
4015
+ output: JSONType
4016
+ expectedOutput: JSONType
3965
4017
  readonly isError: boolean
3966
4018
  errorType: string | null
3967
4019
  errorMessage: string | null
3968
- evaluations: Record<string, any>
4020
+ evaluations: Record<string, JSONType>
3969
4021
  evaluationErrors: Record<string, string>
3970
4022
  }
3971
4023
 
4024
+ interface ExperimentRun {
4025
+ runId: string
4026
+ runIteration: number
4027
+ rows: ExperimentResultRow[]
4028
+ summaryEvaluations: Record<string, { value: any, error: string | null }>
4029
+ }
4030
+
3972
4031
  interface ExperimentResult {
3973
4032
  experimentId: string
3974
4033
  rows: ExperimentResultRow[]
4034
+ /** Single-run summary evaluator results. */
4035
+ summaryEvaluations: Record<string, { value: any, error: string | null }>
4036
+ /** Experiment runs. P0 Node experiments currently return one run. */
4037
+ runs: ExperimentRun[]
3975
4038
  /** Dashboard URL for the experiment. */
3976
4039
  url: string
3977
4040
  }
@@ -3984,13 +4047,20 @@ declare namespace tracer {
3984
4047
  }
3985
4048
 
3986
4049
  interface Dataset {
3987
- addRecord (input: any, expectedOutput?: any, metadata?: Record<string, any>): Dataset
4050
+ addRecord (input: JSONType, expectedOutput?: JSONType, metadata?: Record<string, JSONType>): Dataset
3988
4051
  /** Creates the dataset remotely if needed and pushes any unpushed records. */
3989
4052
  push (): Promise<DatasetPushResult>
3990
4053
  name (): string
3991
4054
  id (): string | null
3992
4055
  projectId (): string | null
3993
- records (): Array<{ input: any, expectedOutput: any, metadata: Record<string, any> }>
4056
+ version (): number | null
4057
+ latestVersion (): number | null
4058
+ records (): Array<{
4059
+ id: string | null,
4060
+ input: JSONType,
4061
+ expectedOutput: JSONType,
4062
+ metadata: Record<string, JSONType>
4063
+ }>
3994
4064
  /** Dashboard URL for the dataset, or null until pushed. */
3995
4065
  url (): string | null
3996
4066
  }
@@ -3999,12 +4069,13 @@ declare namespace tracer {
3999
4069
  name (): string
4000
4070
  experimentId (): string | null
4001
4071
  url (): string | null
4002
- run (): Promise<ExperimentResult>
4072
+ run (options?: ExperimentRunOptions): Promise<ExperimentResult>
4003
4073
  }
4004
4074
 
4005
4075
  interface Experiments {
4006
4076
  /** Create a local dataset buffer; pushed on the first experiment run. */
4007
4077
  createDataset (name: string, description?: string): Dataset
4078
+ createDataset (name: string, options?: CreateDatasetOptions): Dataset
4008
4079
  /** Pull an existing dataset (with records) by name. */
4009
4080
  pullDataset (name: string, options?: PullDatasetOptions): Promise<Dataset>
4010
4081
  /** Build an experiment to run over a dataset. */