dd-trace 6.21.0 → 6.22.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 (204) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  16. package/index.d.ts +25 -14
  17. package/initialize.mjs +1 -3
  18. package/openfeature/rules-based.d.ts +11 -0
  19. package/openfeature/rules-based.js +7 -0
  20. package/package.json +12 -10
  21. package/packages/datadog-core/src/storage.js +16 -0
  22. package/packages/datadog-esbuild/index.js +18 -12
  23. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  24. package/packages/datadog-instrumentations/src/ai.js +3 -3
  25. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  26. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  27. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  28. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  29. package/packages/datadog-instrumentations/src/cucumber.js +48 -33
  30. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  31. package/packages/datadog-instrumentations/src/electron.js +6 -6
  32. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  33. package/packages/datadog-instrumentations/src/fs.js +2 -2
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  35. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  36. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
  37. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  38. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  39. package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
  40. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  41. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
  42. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +7 -7
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  47. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  48. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  49. package/packages/datadog-instrumentations/src/jest.js +3 -10
  50. package/packages/datadog-instrumentations/src/knex.js +9 -5
  51. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  52. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  53. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  54. package/packages/datadog-instrumentations/src/mocha/utils.js +3 -2
  55. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -2
  56. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  57. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  58. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  59. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  60. package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
  61. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  62. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  63. package/packages/datadog-instrumentations/src/openai.js +7 -0
  64. package/packages/datadog-instrumentations/src/pino.js +5 -6
  65. package/packages/datadog-instrumentations/src/redis.js +27 -13
  66. package/packages/datadog-instrumentations/src/router.js +30 -0
  67. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  68. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
  69. package/packages/datadog-instrumentations/src/vitest-main.js +9 -15
  70. package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
  71. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  72. package/packages/datadog-instrumentations/src/vitest.js +2 -2
  73. package/packages/datadog-instrumentations/src/webdriverio.js +5 -8
  74. package/packages/datadog-instrumentations/src/ws.js +1 -4
  75. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  76. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  77. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  78. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  79. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  80. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  81. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  82. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  83. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  84. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  85. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  86. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -5
  87. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  88. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  89. package/packages/datadog-plugin-cypress/src/support.js +1 -4
  90. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  91. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  92. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  93. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  94. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  95. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  96. package/packages/datadog-plugin-mocha/src/index.js +7 -10
  97. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +8 -4
  98. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  99. package/packages/datadog-plugin-next/src/index.js +1 -2
  100. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  101. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  102. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  103. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  104. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  105. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  106. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  107. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  108. package/packages/datadog-webpack/index.js +55 -43
  109. package/packages/datadog-webpack/src/loader.js +56 -8
  110. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  111. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  112. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  113. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  114. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  115. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  116. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  117. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  118. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  119. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  120. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  121. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  122. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  123. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  124. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  125. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  126. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  127. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  128. package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
  129. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  130. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  131. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  132. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  133. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  134. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  135. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  136. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  137. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  138. package/packages/dd-trace/src/config/defaults.js +5 -5
  139. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
  140. package/packages/dd-trace/src/config/helper.js +34 -3
  141. package/packages/dd-trace/src/config/index.js +10 -9
  142. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  143. package/packages/dd-trace/src/config/parsers.js +2 -2
  144. package/packages/dd-trace/src/config/supported-configurations.json +35 -0
  145. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  146. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  147. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -0
  148. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  149. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  150. package/packages/dd-trace/src/dogstatsd.js +1 -1
  151. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  152. package/packages/dd-trace/src/exporters/common/request.js +1 -5
  153. package/packages/dd-trace/src/id.js +14 -1
  154. package/packages/dd-trace/src/lambda/handler.js +179 -19
  155. package/packages/dd-trace/src/lambda/index.js +15 -1
  156. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  157. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  158. package/packages/dd-trace/src/llmobs/constants/tags.js +12 -0
  159. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  160. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  161. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  162. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  163. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  164. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  165. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
  166. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  167. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  168. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  169. package/packages/dd-trace/src/llmobs/span_processor.js +1 -2
  170. package/packages/dd-trace/src/llmobs/tagger.js +2 -2
  171. package/packages/dd-trace/src/llmobs/util.js +1 -2
  172. package/packages/dd-trace/src/llmobs/writers/base.js +4 -5
  173. package/packages/dd-trace/src/log/log.js +1 -5
  174. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  175. package/packages/dd-trace/src/openfeature/flagging_provider.js +48 -21
  176. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  177. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  178. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  179. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  180. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
  181. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  182. package/packages/dd-trace/src/otel-sampling.js +4 -5
  183. package/packages/dd-trace/src/otel-thread-ctx.js +79 -13
  184. package/packages/dd-trace/src/plugins/database.js +7 -16
  185. package/packages/dd-trace/src/plugins/util/git.js +9 -25
  186. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  187. package/packages/dd-trace/src/plugins/util/test.js +48 -4
  188. package/packages/dd-trace/src/priority_sampler.js +1 -2
  189. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  190. package/packages/dd-trace/src/proxy.js +1 -2
  191. package/packages/dd-trace/src/ritm.js +4 -2
  192. package/packages/dd-trace/src/serverless.js +5 -0
  193. package/packages/dd-trace/src/span_processor.js +13 -17
  194. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  195. package/packages/dd-trace/src/util.js +29 -0
  196. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  197. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  198. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  199. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  200. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  201. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  202. package/vendor/dist/protobufjs/index.js +1 -1
  203. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  204. package/vendor/dist/shell-quote/index.js +1 -1
@@ -10,6 +10,11 @@ const RealtimeSession = require('./session')
10
10
  /**
11
11
  * @typedef {import('./session').TurnDescriptor} TurnDescriptor
12
12
  * @typedef {import('./turns').ResponseTurn} ResponseTurn
13
+ * @typedef {{
14
+ * session: RealtimeSession,
15
+ * socket: object | undefined,
16
+ * finalize: (failed?: boolean) => void,
17
+ * }} Connection
13
18
  */
14
19
 
15
20
  // One span per phase of a turn. `user speech` and `agent speech` share a channel: they differ only
@@ -36,7 +41,7 @@ const audioChannel = dc.channel('dd-trace:openai:realtime:audio')
36
41
  const USER_SPEECH = { operation: 'createRealtimeUserSpeech', llmobsName: 'user speech' }
37
42
  const AGENT_SPEECH = { operation: 'createRealtimeAgentSpeech', llmobsName: 'agent speech' }
38
43
 
39
- /** @type {WeakMap<object, { session: RealtimeSession, finalize: () => void }>} */
44
+ /** @type {WeakMap<object, Connection>} */
40
45
  const connections = new WeakMap()
41
46
 
42
47
  /**
@@ -92,7 +97,7 @@ function captureContext (turn) {
92
97
  * Start tracking a connection, reading the model and base path off the URL the SDK already built.
93
98
  *
94
99
  * @param {object} emitter
95
- * @returns {{ session: RealtimeSession, finalize: () => void }}
100
+ * @returns {Connection}
96
101
  */
97
102
  function createConnection (emitter) {
98
103
  let model
@@ -135,7 +140,7 @@ function createConnection (emitter) {
135
140
  session.finishSession(Date.now(), failed)
136
141
  }
137
142
 
138
- const connection = { session, finalize }
143
+ const connection = { session, socket: emitter.socket, finalize }
139
144
  attachSocketClose(emitter, finalize)
140
145
  return connection
141
146
  }
@@ -181,14 +186,21 @@ function attachSocketClose (emitter, finalize) {
181
186
 
182
187
  /**
183
188
  * @param {object} emitter
184
- * @returns {{ session: RealtimeSession, finalize: () => void } | undefined}
189
+ * @returns {Connection | undefined}
185
190
  */
186
191
  function getConnection (emitter) {
187
192
  const connection = connections.get(emitter)
188
- if (connection !== undefined) return connection
193
+ if (connection !== undefined) {
194
+ if (connection.socket === emitter.socket) return connection
195
+
196
+ // Opt-in SDK recovery keeps the emitter but replaces its socket with a new server session.
197
+ // Retire the old state before attaching close and exit cleanup to the replacement.
198
+ connection.finalize(true)
199
+ connections.delete(emitter)
200
+ }
189
201
 
190
202
  // Nothing is listening, so never allocate. Checked only on the miss path, so an established
191
- // connection pays one WeakMap lookup per event.
203
+ // connection reuses its state while the socket is unchanged.
192
204
  if (!turnChannel.start.hasSubscribers) return
193
205
 
194
206
  const created = createConnection(emitter)
@@ -829,7 +829,7 @@ class RealtimeSession {
829
829
  input.audio.totalDecodedBytes, inputFormat.mimeType, inputFormat.sampleRate
830
830
  )
831
831
  const userEnd = input.speechEndTime ??
832
- (userStart !== undefined && inputDurationMs !== undefined ? userStart + inputDurationMs : undefined)
832
+ (userStart === undefined || inputDurationMs === undefined ? undefined : userStart + inputDurationMs)
833
833
 
834
834
  const agentStart = turn.audio.startTime
835
835
  const playbackMs = segmentDurationMs(
@@ -58,6 +58,13 @@ const V4_PACKAGE_SHIMS = [
58
58
  versions: ['>=4.87.0'],
59
59
  interceptChannel: responsesInterceptChannel,
60
60
  },
61
+ {
62
+ file: 'resources/decisions',
63
+ targetClass: 'Decisions',
64
+ baseResource: 'decisions',
65
+ methods: ['create'],
66
+ versions: ['>=7.30.0'],
67
+ },
61
68
  {
62
69
  file: 'resources/embeddings',
63
70
  targetClass: 'Embeddings',
@@ -10,10 +10,9 @@ const logSubmissionCh = channel('ci:log-submission:log')
10
10
 
11
11
  /**
12
12
  * @param {string} symbol
13
- * @param {(original: Function) => Function} wrapper
14
13
  * @param {Function} pino
15
14
  */
16
- function wrapPino (symbol, wrapper, pino) {
15
+ function wrapPino (symbol, pino) {
17
16
  /**
18
17
  * @param {unknown[]} args
19
18
  * @returns {unknown}
@@ -25,7 +24,7 @@ function wrapPino (symbol, wrapper, pino) {
25
24
  configurable: true,
26
25
  enumerable: true,
27
26
  writable: true,
28
- value: wrapper(instance[symbol]),
27
+ value: wrapAsJson(instance[symbol]),
29
28
  })
30
29
 
31
30
  return instance
@@ -83,19 +82,19 @@ function wrapPrettyFactory (prettyFactory) {
83
82
  addHook({ name: 'pino', versions: ['2 - 3', '4'], patchDefault: true }, (pino) => {
84
83
  const asJsonSym = (pino.symbols && pino.symbols.asJsonSym) || 'asJson'
85
84
 
86
- return shimmer.wrapFunction(pino, pino => wrapPino(asJsonSym, wrapAsJson, pino))
85
+ return shimmer.wrapFunction(pino, pino => wrapPino(asJsonSym, pino))
87
86
  })
88
87
 
89
88
  addHook({ name: 'pino', versions: ['>=5 <6.8.0'], patchDefault: true }, (pino) => {
90
89
  const asJsonSym = ((pino.default || pino)?.symbols.asJsonSym) || 'asJson'
91
90
 
92
- return shimmer.wrapFunction(pino, pino => wrapPino(asJsonSym, wrapAsJson, pino.default || pino))
91
+ return shimmer.wrapFunction(pino, pino => wrapPino(asJsonSym, pino.default || pino))
93
92
  })
94
93
 
95
94
  addHook({ name: 'pino', versions: ['>=6.8.0'], patchDefault: false }, (pino) => {
96
95
  const asJsonSym = pino.symbols.asJsonSym
97
96
 
98
- const wrapped = shimmer.wrapFunction(pino, pino => wrapPino(asJsonSym, wrapAsJson, pino))
97
+ const wrapped = shimmer.wrapFunction(pino, pino => wrapPino(asJsonSym, pino))
99
98
  wrapped.pino = wrapped
100
99
  wrapped.default = wrapped
101
100
 
@@ -25,7 +25,7 @@ function wrapAddCommand (addCommand) {
25
25
  return startCh.runStores(ctx, () => {
26
26
  const res = addCommand.apply(this, arguments)
27
27
 
28
- res.then(() => finish(finishCh, errorCh, ctx), err => finish(finishCh, errorCh, ctx, err))
28
+ res.then(() => finish(ctx), err => finish(ctx, err))
29
29
 
30
30
  return res
31
31
  })
@@ -85,13 +85,11 @@ addHook({ name: '@redis/client', file: 'dist/lib/client/commands-queue.js', vers
85
85
 
86
86
  addHook({ name: 'redis', versions: ['>=2.6 <4'] }, redis => {
87
87
  shimmer.wrap(redis.RedisClient.prototype, 'internal_send_command', internalSendCommand => function (options) {
88
- if (!startCh.hasSubscribers) return internalSendCommand.apply(this, arguments)
89
-
90
- if (!options.callback) return internalSendCommand.apply(this, arguments)
88
+ if (!startCh.hasSubscribers || !options.callback) return internalSendCommand.apply(this, arguments)
91
89
 
92
90
  const ctx = getStartCtx(this, options.command, options.args)
93
91
  return startCh.runStores(ctx, () => {
94
- options.callback = wrapCallback(finishCh, errorCh, ctx, options.callback)
92
+ options.callback = wrapCallback(ctx, options.callback)
95
93
 
96
94
  try {
97
95
  return internalSendCommand.apply(this, arguments)
@@ -114,11 +112,11 @@ addHook({ name: 'redis', versions: ['>=0.12 <2.6'] }, redis => {
114
112
  const ctx = getStartCtx(this, command, args)
115
113
  return startCh.runStores(ctx, () => {
116
114
  if (typeof callback === 'function') {
117
- arguments[2] = wrapCallback(finishCh, errorCh, ctx, callback)
115
+ arguments[2] = wrapCallback(ctx, callback)
118
116
  } else if (Array.isArray(args) && typeof args.at(-1) === 'function') {
119
- args[args.length - 1] = wrapCallback(finishCh, errorCh, ctx, args.at(-1))
117
+ args[args.length - 1] = wrapCallback(ctx, args.at(-1))
120
118
  } else {
121
- arguments[2] = wrapCallback(finishCh, errorCh, ctx)
119
+ arguments[2] = wrapCallback(ctx)
122
120
  }
123
121
 
124
122
  try {
@@ -155,20 +153,36 @@ function getStartCtx (client, command, args, argsStartIndex) {
155
153
  }
156
154
  }
157
155
 
158
- function wrapCallback (finishCh, errorCh, ctx, callback) {
156
+ /**
157
+ * @param {Record<string, unknown> & { error?: Error }} ctx
158
+ * @param {Function} [callback]
159
+ */
160
+ function wrapCallback (ctx, callback) {
159
161
  if (typeof callback !== 'function') return callback
160
162
  return function (err) {
161
- return finish(finishCh, errorCh, ctx, err, callback, this, arguments)
163
+ return finish(ctx, err, callback, this, arguments)
162
164
  }
163
165
  }
164
166
 
165
- function finish (finishCh, errorCh, ctx, error, callback, thisArg, args) {
167
+ /**
168
+ * @param {Record<string, unknown> & { error?: Error }} ctx
169
+ * @param {Error} [error]
170
+ * @param {Function} [callback]
171
+ * @param {object} [thisArg]
172
+ * @param {Iterable<unknown>} [args]
173
+ */
174
+ function finish (ctx, error, callback, thisArg, args) {
166
175
  if (error) {
167
176
  ctx.error = error
168
177
  errorCh.publish(ctx)
169
178
  }
170
179
  if (callback) {
171
- return finishCh.runStores(ctx, callback, thisArg, ...args)
180
+ return finishCh.runStores(
181
+ ctx,
182
+ /** @type {Parameters<typeof finishCh.runStores>[1]} */ (callback),
183
+ thisArg,
184
+ ...(/** @type {Iterable<unknown>} */ (args))
185
+ )
172
186
  }
173
- finishCh.publish(ctx)
187
+ return finishCh.publish(ctx)
174
188
  }
@@ -91,6 +91,28 @@ function resolveLayerRouteOrForwardError (meta, layer, next) {
91
91
  }
92
92
  }
93
93
 
94
+ // Wrapped `next` -> the host continuation it calls.
95
+ const hostNexts = new WeakMap()
96
+
97
+ /**
98
+ * Routers store their continuation on `req.next` once per `handle()`, and host
99
+ * APIs call it directly (express `res.format`, `res.sendFile` and `res.render`
100
+ * errors). Point it at the wrapper the current handler receives when that
101
+ * wrapper wraps the same continuation: `req.next` is the host continuation
102
+ * itself, or an earlier layer's wrapper of it. Route handlers receive a
103
+ * route-local continuation from `Route.dispatch`, so `req.next` keeps the
104
+ * router-level one there. A nested router restores its parent's `req.next`
105
+ * when it finishes.
106
+ *
107
+ * @param {import('node:http').IncomingMessage | undefined} req
108
+ * @param {Function} hostNext
109
+ * @param {Function} wrappedNext
110
+ */
111
+ function adoptRequestNext (req, hostNext, wrappedNext) {
112
+ hostNexts.set(wrappedNext, hostNext)
113
+ if (req && (req.next === hostNext || hostNexts.get(req.next) === hostNext)) req.next = wrappedNext
114
+ }
115
+
94
116
  /**
95
117
  * Build the request/error dispatch wrappers for one host (`express` / `router`).
96
118
  * They wrap the layer's prototype dispatch and read the side-table metadata, so
@@ -171,6 +193,7 @@ function createLayerDispatchWrappers (name) {
171
193
  }
172
194
 
173
195
  const wrappedNext = typeof next === 'function' ? wrapNext(req, meta.name, next) : next
196
+ if (wrappedNext !== next) adoptRequestNext(req, next, wrappedNext)
174
197
  enterChannel.publish({ name: meta.name, req, route, layer: this })
175
198
 
176
199
  try {
@@ -199,6 +222,7 @@ function createLayerDispatchWrappers (name) {
199
222
  }
200
223
 
201
224
  const wrappedNext = typeof next === 'function' ? wrapNext(req, meta.name, next) : next
225
+ if (wrappedNext !== next) adoptRequestNext(req, next, wrappedNext)
202
226
  enterChannel.publish({ name: meta.name, req, route, layer: this })
203
227
 
204
228
  try {
@@ -223,6 +247,7 @@ function createLayerDispatchWrappers (name) {
223
247
 
224
248
  let calls = 0
225
249
  if (typeof next === 'function') {
250
+ const hostNext = next
226
251
  next = shimmer.wrapCallback(next, originalNext => function next (error) {
227
252
  calls++
228
253
  if (calls === 1) {
@@ -238,6 +263,7 @@ function createLayerDispatchWrappers (name) {
238
263
 
239
264
  originalNext.apply(this, arguments)
240
265
  })
266
+ adoptRequestNext(req, hostNext, next)
241
267
  }
242
268
 
243
269
  const route = needMultiMatch ? resolveLayerRoute(meta, layer) : captureRoute
@@ -273,6 +299,7 @@ function createLayerDispatchWrappers (name) {
273
299
 
274
300
  let calls = 0
275
301
  if (typeof next === 'function') {
302
+ const hostNext = next
276
303
  next = shimmer.wrapCallback(next, originalNext => function next (nextError) {
277
304
  calls++
278
305
  if (calls === 1) {
@@ -288,6 +315,7 @@ function createLayerDispatchWrappers (name) {
288
315
 
289
316
  originalNext.apply(this, arguments)
290
317
  })
318
+ adoptRequestNext(req, hostNext, next)
291
319
  }
292
320
 
293
321
  const route = needMultiMatch ? resolveLayerRoute(meta, layer) : captureRoute
@@ -336,6 +364,7 @@ function createLayerDispatchWrappers (name) {
336
364
  const nextIndex = isErrorHandler ? 3 : 2
337
365
  let calls = 0
338
366
  if (typeof args[nextIndex] === 'function') {
367
+ const hostNext = args[nextIndex]
339
368
  args[nextIndex] = shimmer.wrapCallback(args[nextIndex], originalNext => function next (error) {
340
369
  calls++
341
370
  if (calls === 1) {
@@ -351,6 +380,7 @@ function createLayerDispatchWrappers (name) {
351
380
 
352
381
  originalNext.apply(this, arguments)
353
382
  })
383
+ adoptRequestNext(req, hostNext, args[nextIndex])
354
384
  }
355
385
 
356
386
  const route = needMultiMatch ? resolveLayerRoute(meta, layer) : captureRoute
@@ -5,9 +5,7 @@
5
5
  /** @returns {{ isRumInstrumented: boolean, isRumActive: boolean, rumSamplingRate: number | null }} */
6
6
  function detectRum () {
7
7
  const isRumInstrumented = !!window.DD_RUM
8
- const isRumActive = window.DD_RUM && window.DD_RUM.getInternalContext
9
- ? !!window.DD_RUM.getInternalContext()
10
- : false
8
+ const isRumActive = !!(window.DD_RUM && window.DD_RUM.getInternalContext && window.DD_RUM.getInternalContext())
11
9
  const rumInitConfiguration = window.DD_RUM && window.DD_RUM.getInitConfiguration
12
10
  ? window.DD_RUM.getInitConfiguration()
13
11
  : undefined
@@ -26,9 +24,7 @@ function stopRumSession () {
26
24
 
27
25
  function stopRumSessionAndReportActivity () {
28
26
  if (window.DD_RUM && window.DD_RUM.stopSession) {
29
- const isRumActive = window.DD_RUM.getInternalContext
30
- ? !!window.DD_RUM.getInternalContext()
31
- : false
27
+ const isRumActive = !!(window.DD_RUM.getInternalContext && window.DD_RUM.getInternalContext())
32
28
  window.DD_RUM.stopSession()
33
29
  return isRumActive
34
30
  }
@@ -659,8 +659,7 @@ function createMainProcessReporter (reporterState) {
659
659
  },
660
660
 
661
661
  onTaskUpdate (packs, events) {
662
- if (!isReporterActive()) return
663
- if (!events) return
662
+ if (!isReporterActive() || !events) return
664
663
 
665
664
  for (const event of events) {
666
665
  if (event[1] === 'test-retried') {
@@ -685,8 +684,7 @@ function createMainProcessReporter (reporterState) {
685
684
  },
686
685
 
687
686
  onFinished (files) {
688
- if (!isReporterActive()) return
689
- if (!files) return
687
+ if (!isReporterActive() || !files) return
690
688
 
691
689
  for (const file of files) {
692
690
  const testModule = createTestModuleFromFile(file)
@@ -1276,10 +1274,12 @@ function reportFinalTestAttempt (testReport) {
1276
1274
  isQuarantined: testProperties.isQuarantined,
1277
1275
  })
1278
1276
  }
1277
+ const attemptIndex = task.meta?.__ddTestOptCurrentAttemptIndex ?? 0
1278
+ const attemptStartTime = task.meta?.__ddTestOptAttemptStartTimes?.[attemptIndex]
1279
1279
  const {
1280
1280
  isRumActive,
1281
1281
  testExecutionId,
1282
- } = getRumCorrelation(task)
1282
+ } = getRumCorrelation(task, attemptIndex)
1283
1283
  testSkipCh.publish({
1284
1284
  ...browserEnvironment,
1285
1285
  testName,
@@ -1291,6 +1291,8 @@ function reportFinalTestAttempt (testReport) {
1291
1291
  isRumActive,
1292
1292
  isTestFrameworkWorker: true,
1293
1293
  requestErrorTags: state.requestErrorTags,
1294
+ startTime: Number.isFinite(attemptStartTime) ? attemptStartTime : undefined,
1295
+ duration: task.meta?.__ddTestOptAttemptDurations?.[attemptIndex],
1294
1296
  testStartLine: task.location?.line,
1295
1297
  testExecutionId,
1296
1298
  ...testSuiteStore,
@@ -300,8 +300,7 @@ function configureEfdSuiteTracker (testFilepaths, repositoryRoot) {
300
300
  * @param {boolean} hasNewTest
301
301
  */
302
302
  function reserveEarlyFlakeDetectionSuite (testSuite, hasNewTest) {
303
- if (!isEfdSuiteAdmissionEnabled || typeof testSuite !== 'string') return false
304
- if (isEarlyFlakeDetectionFaulty) return false
303
+ if (!isEfdSuiteAdmissionEnabled || typeof testSuite !== 'string' || isEarlyFlakeDetectionFaulty) return false
305
304
 
306
305
  if (hasNewTest) {
307
306
  suitesWithNewTests.add(testSuite)
@@ -1201,11 +1200,7 @@ function getSortWrapper (sort, frameworkVersion) {
1201
1200
 
1202
1201
  function getFinishWrapper (exitOrClose) {
1203
1202
  return async function () {
1204
- if (finishedSessionContexts.has(this)) {
1205
- return exitOrClose.apply(this, arguments)
1206
- }
1207
-
1208
- if (!testSessionFinishCh.hasSubscribers) {
1203
+ if (finishedSessionContexts.has(this) || !testSessionFinishCh.hasSubscribers) {
1209
1204
  return exitOrClose.apply(this, arguments)
1210
1205
  }
1211
1206
  finishedSessionContexts.add(this)
@@ -1230,15 +1225,15 @@ function getFinishWrapper (exitOrClose) {
1230
1225
  const tests = getTypeTasks(this.state.getFiles())
1231
1226
  const hasExecutedTests = tests.some(test =>
1232
1227
  test.mode !== 'skip' && test.mode !== 'todo' &&
1233
- test.result?.state !== 'skip' && test.result?.state !== 'todo'
1228
+ (test.result?.state === 'pass' || test.result?.state === 'fail')
1234
1229
  )
1235
1230
  const hasErrors = hasRunError || error ||
1236
1231
  this.state.getCountOfFailedTests() > 0 || this.state.getUnhandledErrors().length > 0
1237
- const testSessionEmptyReason = !hasErrors && !hasExecutedTests
1238
- ? (isEmptyShard
1239
- ? 'zero_test_shard'
1240
- : tests.length > 0 || skippedSuites.length > 0 ? 'all_tests_skipped' : 'zero_tests')
1241
- : undefined
1232
+ const testSessionEmptyReason = hasErrors || hasExecutedTests
1233
+ ? undefined
1234
+ : isEmptyShard
1235
+ ? 'zero_test_shard'
1236
+ : tests.length > 0 || skippedSuites.length > 0 ? 'all_tests_skipped' : 'zero_tests'
1242
1237
  const status = hasErrors ? 'fail' : (testSessionEmptyReason ? 'skip' : 'pass')
1243
1238
  const flushPromise = getChannelPromise(testSessionFinishCh, {
1244
1239
  status,
@@ -1758,8 +1753,7 @@ async function reportTypecheckFile (file, sessionConfiguration, frameworkVersion
1758
1753
  }
1759
1754
 
1760
1755
  async function reportTypecheckResults (result, frameworkVersion, ctx, typechecker, files = result?.files) {
1761
- if (!testSuiteFinishCh.hasSubscribers) return
1762
- if (!Array.isArray(result?.files) || !Array.isArray(files)) return
1756
+ if (!testSuiteFinishCh.hasSubscribers || !Array.isArray(result?.files) || !Array.isArray(files)) return
1763
1757
 
1764
1758
  const setupState = ctx && mainProcessSetupStates.get(ctx)
1765
1759
  if (
@@ -33,7 +33,7 @@ const testManagementTestsCh = channel('ci:vitest:test-management-tests')
33
33
  const modifiedFilesCh = channel('ci:vitest:modified-files')
34
34
 
35
35
  const CLOSING_SCRIPT_TAG_RE = /<\/script/i
36
- const SERIALIZED_CONTEXT_PREFIX = '\u0000dd-vitest-context:'
36
+ const SERIALIZED_CONTEXT_PREFIX = '\0dd-vitest-context:'
37
37
 
38
38
  const workerReportTraceCh = channel('ci:vitest:worker-report:trace')
39
39
  const workerReportCoverageCh = channel('ci:vitest:worker-report:coverage')
@@ -299,7 +299,7 @@ function getVitestTestProperties (providedContext, testSuiteAbsolutePath, testNa
299
299
 
300
300
  return {
301
301
  testSuite: testProperties?.testSuite,
302
- isNew: Array.isArray(knownTests) ? !knownTests.includes(testName) : false,
302
+ isNew: Array.isArray(knownTests) && !knownTests.includes(testName),
303
303
  isModified: testProperties?.isModified === true,
304
304
  isAttemptToFix: testManagementProperties.isAttemptToFix,
305
305
  isDisabled: testManagementProperties.isDisabled,
@@ -354,8 +354,7 @@ function hasConcurrentTask (tasks) {
354
354
  if (!tasks) return false
355
355
 
356
356
  for (const task of tasks) {
357
- if (task.concurrent === true) return true
358
- if (hasConcurrentTask(task.tasks)) return true
357
+ if (task.concurrent === true || hasConcurrentTask(task.tasks)) return true
359
358
  }
360
359
 
361
360
  return false
@@ -3,9 +3,9 @@
3
3
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
4
4
  const { isTrue } = require('../../dd-trace/src/util')
5
5
 
6
- const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
7
-
8
6
  if (getEnvironmentVariable('DD_VITEST_WORKER') === '1') {
7
+ const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
8
+
9
9
  // In no-worker-init mode the main process reports Vitest events from reporter hooks.
10
10
  // Loading worker instrumentation here would reintroduce the worker tracer path.
11
11
  // eslint-disable-next-line eslint-rules/eslint-process-env
@@ -297,8 +297,7 @@ async function preloadRumNavigation () {
297
297
  try {
298
298
  const browser = this
299
299
  const testExecutionId = getRumTestExecutionId(browser)
300
- if (!canCorrelateRumBrowser(browser)) return
301
- if (!rumRunnerBrowsers.has(browser)) return
300
+ if (!canCorrelateRumBrowser(browser) || !rumRunnerBrowsers.has(browser)) return
302
301
 
303
302
  retainRumBrowser(browser)
304
303
  if (!testExecutionId) return
@@ -371,9 +370,7 @@ async function handleRumNavigation (context) {
371
370
  log.debug("RUM was detected on the page, but it isn't active because the sampling rate is below 100%")
372
371
  }
373
372
  const testExecutionId = getRumTestExecutionId(browser, isRumActive)
374
- if (sampledOut) return
375
-
376
- if (!testExecutionId) {
373
+ if (sampledOut || !testExecutionId) {
377
374
  return
378
375
  }
379
376
  if (!isRumActive && browser.isBidi) return
@@ -1736,9 +1733,7 @@ function handleWorkerMessage (state, workerRecord, message) {
1736
1733
  requestId,
1737
1734
  },
1738
1735
  })
1739
- if (!requestId || !uploadCh.hasSubscribers) {
1740
- respond(new Error('WebdriverIO media upload is not available'))
1741
- } else {
1736
+ if (requestId && uploadCh.hasSubscribers) {
1742
1737
  try {
1743
1738
  let signal
1744
1739
  if (isVideo) {
@@ -1754,6 +1749,8 @@ function handleWorkerMessage (state, workerRecord, message) {
1754
1749
  log.error('WebdriverIO media upload error: %s', error?.message || String(error))
1755
1750
  respond(error)
1756
1751
  }
1752
+ } else {
1753
+ respond(new Error('WebdriverIO media upload is not available'))
1757
1754
  }
1758
1755
  return
1759
1756
  }
@@ -291,10 +291,7 @@ function dataLength (data) {
291
291
  if (data instanceof Blob) {
292
292
  return data.size
293
293
  }
294
- if (ArrayBuffer.isView(data)) {
295
- return data.byteLength
296
- }
297
- if (data instanceof ArrayBuffer) {
294
+ if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer) {
298
295
  return data.byteLength
299
296
  }
300
297
  let total = 0
@@ -13,8 +13,7 @@ class AmqplibClientPlugin extends ClientPlugin {
13
13
  bindStart (ctx) {
14
14
  const { channel = {}, method, fields } = ctx
15
15
 
16
- if (method === 'basic.deliver' || method === 'basic.get') return
17
- if (method === 'basic.publish') return
16
+ if (method === 'basic.deliver' || method === 'basic.get' || method === 'basic.publish') return
18
17
 
19
18
  const stream = (channel.connection && channel.connection.stream) || {}
20
19
  const span = this.startSpan(this.operationName(), {
@@ -154,12 +154,8 @@ class SchemaExtractor {
154
154
  span.setTag(SCHEMA_NAME, descriptor.name)
155
155
  span.setTag(SCHEMA_OPERATION, operation)
156
156
 
157
- if (!tracer._dataStreamsProcessor.canSampleSchema(operation)) {
158
- return
159
- }
160
-
161
157
  // if the span is unsampled, do not sample the schema
162
- if (!tracer._prioritySampler.isSampled(span)) {
158
+ if (!tracer._dataStreamsProcessor.canSampleSchema(operation) || !tracer._prioritySampler.isSampled(span)) {
163
159
  return
164
160
  }
165
161
 
@@ -90,8 +90,7 @@ function finishOpenChildSpans (executeSpan) {
90
90
  if (!trace?.started) return
91
91
 
92
92
  for (const span of trace.started) {
93
- if (span === executeSpan) continue
94
- if (span._integrationName !== AwsDurableExecutionSdkJsHandlerPlugin.id) continue
93
+ if (span === executeSpan || span._integrationName !== AwsDurableExecutionSdkJsHandlerPlugin.id) continue
95
94
  if (span._duration === undefined) {
96
95
  span.finish()
97
96
  }
@@ -125,7 +125,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
125
125
  })
126
126
 
127
127
  this.addSub(`apm:aws:request:start:${this.serviceIdentifier}`, (ctx) => {
128
- if (!IS_SERVERLESS) return
128
+ if (!IS_SERVERLESS || !ctx.currentStore?.span) return
129
129
 
130
130
  const { awsRegion, awsService, currentStore, request } = ctx
131
131
  const peerServerlessStorage = storage('peerServerless')
@@ -145,10 +145,8 @@ class BaseAwsSdkPlugin extends ClientPlugin {
145
145
  }
146
146
  })
147
147
 
148
- this.addSub(`apm:aws:request:region:${this.serviceIdentifier}`, ({ region }) => {
149
- const store = storage('legacy').getStore()
150
- if (!store) return
151
- const { span } = store
148
+ this.addSub(`apm:aws:request:region:${this.serviceIdentifier}`, ({ region, currentStore }) => {
149
+ const span = currentStore?.span
152
150
  if (!span) return
153
151
  span.setTag('aws.region', region)
154
152
  span.setTag('region', region)
@@ -160,7 +158,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
160
158
 
161
159
  if (!IS_SERVERLESS) return
162
160
 
163
- const hostname = getHostname(store, region)
161
+ const hostname = getHostname(currentStore, region)
164
162
  if (!hostname) return
165
163
 
166
164
  span.setTag('peer.service', hostname)
@@ -338,8 +336,7 @@ function getHooks (config) {
338
336
  }
339
337
 
340
338
  function getHostname (store, region) {
341
- if (!store) return
342
- if (!region) return
339
+ if (!store || !region) return
343
340
  const { awsParams, awsService } = store
344
341
  switch (awsService) {
345
342
  case 'CloudWatchEvents':
@@ -2,15 +2,33 @@
2
2
 
3
3
  const CompositePlugin = require('../../../../dd-trace/src/plugins/composite')
4
4
  const BedrockRuntimeLLMObsPlugin = require('../../../../dd-trace/src/llmobs/plugins/bedrockruntime')
5
+ const SonicLLMObsPlugin = require('../../../../dd-trace/src/llmobs/plugins/nova-sonic')
5
6
  const BedrockRuntimeTracing = require('./tracing')
7
+ const SonicTracingPlugin = require('./sonic')
6
8
  class BedrockRuntimePlugin extends CompositePlugin {
7
9
  static id = 'bedrockruntime'
8
10
 
9
11
  static get plugins () {
10
12
  return {
11
13
  llmobs: BedrockRuntimeLLMObsPlugin,
14
+ sonicLlmobs: SonicLLMObsPlugin,
12
15
  tracing: BedrockRuntimeTracing,
16
+ sonicTracing: SonicTracingPlugin,
13
17
  }
14
18
  }
19
+
20
+ /** @param {import('../../../../../').plugins.aws_sdk | boolean} config */
21
+ configure (config) {
22
+ if (typeof config === 'boolean') config = { enabled: config }
23
+ const bedrock = config.bedrockruntime
24
+ if (config.enabled === false || bedrock === false || bedrock?.enabled === false ||
25
+ this._tracerConfig.DD_TRACE_AWS_SDK_BEDROCKRUNTIME_ENABLED === false) {
26
+ // Disable the whole service before any child can subscribe and enable conversation capture.
27
+ return super.configure(false)
28
+ }
29
+ const normalized = { ...config, ...bedrock }
30
+ if (config.llmobs === false) normalized.llmobs = false
31
+ super.configure(normalized)
32
+ }
15
33
  }
16
34
  module.exports = BedrockRuntimePlugin