dd-trace 6.13.0 → 6.14.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 (201) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/package.json +13 -10
  14. package/packages/datadog-esbuild/index.js +3 -1
  15. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  16. package/packages/datadog-instrumentations/src/fs.js +24 -20
  17. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  18. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  19. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  21. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  33. package/packages/datadog-instrumentations/src/jest.js +25 -20
  34. package/packages/datadog-instrumentations/src/knex.js +76 -0
  35. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  36. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  37. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  39. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  40. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  41. package/packages/datadog-instrumentations/src/net.js +14 -17
  42. package/packages/datadog-instrumentations/src/next.js +212 -62
  43. package/packages/datadog-instrumentations/src/openai.js +42 -40
  44. package/packages/datadog-instrumentations/src/pg.js +15 -0
  45. package/packages/datadog-instrumentations/src/playwright.js +109 -13
  46. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  47. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  48. package/packages/datadog-instrumentations/src/undici.js +53 -4
  49. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  50. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  51. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  52. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  53. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  54. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  56. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  57. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  58. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  59. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  60. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  62. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  63. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  64. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  65. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  66. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  67. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  68. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  69. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  70. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  71. package/packages/datadog-plugin-jest/src/index.js +0 -6
  72. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  73. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  74. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  75. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  76. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  77. package/packages/datadog-plugin-next/src/index.js +63 -29
  78. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  79. package/packages/datadog-plugin-undici/src/index.js +27 -41
  80. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  81. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  82. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  83. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  85. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  86. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  87. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  88. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  90. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  91. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  94. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  102. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  103. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  104. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  105. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  106. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  107. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  108. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  109. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  110. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  111. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  112. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  114. package/packages/dd-trace/src/config/index.js +73 -13
  115. package/packages/dd-trace/src/config/parsers.js +11 -1
  116. package/packages/dd-trace/src/config/stable.js +3 -3
  117. package/packages/dd-trace/src/config/supported-configurations.json +20 -2
  118. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  119. package/packages/dd-trace/src/debugger/config.js +12 -1
  120. package/packages/dd-trace/src/debugger/constants.js +1 -0
  121. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  122. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  123. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  124. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  125. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  126. package/packages/dd-trace/src/debugger/index.js +23 -3
  127. package/packages/dd-trace/src/dogstatsd.js +510 -76
  128. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  129. package/packages/dd-trace/src/encode/0.4.js +5 -5
  130. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  131. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  132. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  133. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  134. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  135. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  136. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  137. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  138. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  140. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  141. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  142. package/packages/dd-trace/src/flush.js +6 -3
  143. package/packages/dd-trace/src/id.js +14 -0
  144. package/packages/dd-trace/src/index.js +5 -2
  145. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  146. package/packages/dd-trace/src/llmobs/index.js +32 -32
  147. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  148. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  149. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  150. package/packages/dd-trace/src/llmobs/util.js +0 -33
  151. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  152. package/packages/dd-trace/src/log/index.js +7 -1
  153. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  154. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  155. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  156. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  157. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  158. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  159. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  160. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  161. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  162. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  163. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  164. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
  166. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  167. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  168. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  169. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  170. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  171. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  172. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  173. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  174. package/packages/dd-trace/src/profiling/config.js +1 -0
  175. package/packages/dd-trace/src/profiling/oom.js +7 -2
  176. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  177. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  178. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  179. package/packages/dd-trace/src/proxy.js +75 -5
  180. package/packages/dd-trace/src/remote_config/index.js +319 -101
  181. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  182. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  183. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  184. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  185. package/packages/dd-trace/src/sampling_rule.js +36 -9
  186. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  187. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  188. package/packages/dd-trace/src/serverless.js +10 -3
  189. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  190. package/packages/dd-trace/src/span_stats.js +24 -28
  191. package/packages/dd-trace/src/startup-log.js +3 -2
  192. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  193. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  194. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  195. package/packages/dd-trace/src/tracer.js +4 -5
  196. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  197. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  198. package/vendor/dist/protobufjs/index.js +1 -1
  199. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  200. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  201. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { isNativeError } = require('node:util').types
4
+
3
5
  const shimmer = require('../../datadog-shimmer')
4
6
  const nomenclature = require('../../dd-trace/src/service-naming')
5
7
  const spanEndingHook = require('../../dd-trace/src/opentelemetry/span-ending-hook')
@@ -12,11 +14,44 @@ const errorChannel = channel('apm:next:request:error')
12
14
  const pageLoadChannel = channel('apm:next:page:load')
13
15
  const bodyParsedChannel = channel('apm:next:body-parsed')
14
16
  const queryParsedChannel = channel('apm:next:query-parsed')
17
+ const backgroundRevalidationChannel = channel('apm:next:request:background-revalidation')
18
+
19
+ /**
20
+ * @typedef {import('node:http').IncomingMessage & {
21
+ * error?: unknown,
22
+ * originalRequest?: import('node:http').IncomingMessage
23
+ * }} NextNodeRequest
24
+ *
25
+ * @typedef {import('node:http').ServerResponse & {
26
+ * originalResponse?: import('node:http').ServerResponse
27
+ * }} NextNodeResponse
28
+ *
29
+ * @typedef {object} NextRequest
30
+ * @property {unknown} [error]
31
+ *
32
+ * @typedef {object} NextRequestContext
33
+ * @property {NextNodeRequest} req
34
+ * @property {NextNodeResponse} res
35
+ * @property {boolean} [finishOnResponse]
36
+ * @property {boolean} [handlerFinished]
37
+ * @property {boolean} [responseFinished]
38
+ * @property {unknown} [error]
39
+ * @property {NextRequest} [nextRequest]
40
+ *
41
+ * @typedef {object} ResponseGeneratorContext
42
+ * @property {boolean} [hasResolved]
43
+ *
44
+ * @typedef {object} HandleResponseOptions
45
+ * @property {NextNodeRequest} req
46
+ * @property {(context?: ResponseGeneratorContext) => Promise<unknown>} responseGenerator
47
+ *
48
+ * @typedef {object} ResponseCacheEntry
49
+ * @property {{ status?: number }} [value]
50
+ */
15
51
 
16
52
  const requests = new WeakSet()
17
53
  const nodeNextRequestsToNextRequests = new WeakMap()
18
- const requestErrors = new WeakMap()
19
-
54
+ const noFallbackErrorPrototypes = new WeakSet()
20
55
  // Next.js <= 14.2.6
21
56
  const MIDDLEWARE_HEADER = 'x-middleware-invoke'
22
57
 
@@ -149,7 +184,16 @@ function getRequestMeta (req, key) {
149
184
  return typeof key === 'string' ? meta[key] : meta
150
185
  }
151
186
 
152
- function instrument (req, res, handler, error) {
187
+ /**
188
+ * @template T
189
+ * @param {NextNodeRequest} req
190
+ * @param {NextNodeResponse} res
191
+ * @param {(ctx: NextRequestContext) => Promise<T>} handler
192
+ * @param {unknown} [error]
193
+ * @param {boolean} [finishOnResponse]
194
+ * @returns {Promise<T>}
195
+ */
196
+ function instrument (req, res, handler, error, finishOnResponse = false) {
153
197
  req = req.originalRequest || req
154
198
  res = res.originalResponse || res
155
199
 
@@ -165,7 +209,17 @@ function instrument (req, res, handler, error) {
165
209
 
166
210
  requests.add(req)
167
211
 
168
- const ctx = { req, res }
212
+ const ctx = finishOnResponse ? { req, res, finishOnResponse } : { req, res }
213
+ if (finishOnResponse) {
214
+ const onResponseFinish = () => {
215
+ res.removeListener('finish', onResponseFinish)
216
+ res.removeListener('close', onResponseFinish)
217
+ ctx.responseFinished = true
218
+ if (ctx.handlerFinished) publishFinish(ctx, ctx.error)
219
+ }
220
+ res.once('finish', onResponseFinish)
221
+ res.once('close', onResponseFinish)
222
+ }
169
223
  if (queryParsedChannel.hasSubscribers && req.url) {
170
224
  const queryIndex = req.url.indexOf('?')
171
225
  if (queryIndex !== -1) {
@@ -213,9 +267,8 @@ function wrapServeStatic (serveStatic) {
213
267
  }
214
268
  }
215
269
 
216
- function finish (ctx, result, err) {
217
- publishError(ctx.req, ctx, err)
218
- requestErrors.delete(ctx.req)
270
+ function publishFinish (ctx, error) {
271
+ publishError(ctx.req, ctx, error)
219
272
 
220
273
  const maybeNextRequest = nodeNextRequestsToNextRequests.get(ctx.req)
221
274
  if (maybeNextRequest) {
@@ -225,24 +278,35 @@ function finish (ctx, result, err) {
225
278
  finishChannel.publish(ctx)
226
279
  }
227
280
 
228
- function publishError (req, ctx, error) {
229
- if (!error) return
281
+ function finish (ctx, result, error) {
282
+ if (ctx.finishOnResponse) {
283
+ ctx.handlerFinished = true
284
+ if (error && !isNoFallbackError(error)) ctx.error = error
285
+ if (!ctx.responseFinished) return
286
+ }
230
287
 
231
- req = req.originalRequest || req
232
- let errors = requestErrors.get(req)
233
- if (errors?.has(error)) return
288
+ publishFinish(ctx, error)
289
+ }
234
290
 
235
- if (!errors) {
236
- errors = new Set()
237
- requestErrors.set(req, errors)
238
- }
239
- errors.add(error)
291
+ /** @param {unknown} error */
292
+ function isNoFallbackError (error) {
293
+ return isNativeError(error) && noFallbackErrorPrototypes.has(Object.getPrototypeOf(error))
294
+ }
295
+
296
+ /**
297
+ * @param {NextNodeRequest} req
298
+ * @param {NextRequestContext | undefined} ctx
299
+ * @param {unknown} error
300
+ */
301
+ function publishError (req, ctx, error) {
302
+ if (!error || isNoFallbackError(error)) return
240
303
 
241
304
  if (ctx) {
242
305
  ctx.error = error
243
306
  errorChannel.publish(ctx)
244
307
  } else {
245
- errorChannel.publish({ error })
308
+ req = req.originalRequest || req
309
+ errorChannel.publish({ req, error })
246
310
  }
247
311
  }
248
312
 
@@ -267,13 +331,29 @@ addHook({
267
331
  // From Next 15.4.1, route modules execute through precompiled runtime bundles that bypass the
268
332
  // classic server hooks above. Match bundler and experimental filename variants without enumerating
269
333
  // them so App Routes, Pages APIs, and App Pages reuse the existing Next request lifecycle.
334
+ // The route module classes and inherited methods are selected inside those bundles, so there is no
335
+ // stable source function for Orchestrion to rewrite.
270
336
  const patchedRouteModules = new WeakSet()
337
+ const routeResponses = new WeakMap()
338
+ const activeRouteRequests = new WeakMap()
271
339
  const COMPILED_RUNTIME_PATH = 'dist/compiled/next-server/'
340
+
341
+ /** @param {{ NoFallbackError: typeof Error }} noFallbackError */
342
+ function captureNoFallbackError (noFallbackError) {
343
+ noFallbackErrorPrototypes.add(noFallbackError.NoFallbackError.prototype)
344
+ return noFallbackError
345
+ }
346
+
347
+ // The sentinel identity only exists at runtime, so there is no source function for Orchestrion to rewrite.
348
+ addHook({
349
+ name: 'next',
350
+ versions: ['>=15.4.1'],
351
+ file: 'dist/shared/lib/no-fallback-error.external.js',
352
+ }, captureNoFallbackError)
353
+
272
354
  function wrapOnRequestError (onRequestError) {
273
355
  return function (req, error) {
274
- if (error) {
275
- errorChannel.publish({ error })
276
- }
356
+ if (error) publishError(req, undefined, error)
277
357
  return onRequestError.apply(this, arguments)
278
358
  }
279
359
  }
@@ -296,37 +376,127 @@ function publishRoutePage (ctx, routeModule, fallbackPage, isAppPath) {
296
376
 
297
377
  const pageData = getRoutePage(routeModule, fallbackPage)
298
378
  if (pageData.page) {
299
- pageLoadChannel.publish(isAppPath ? { ...pageData, isAppPath: true } : pageData)
379
+ if (isAppPath) pageData.isAppPath = true
380
+ pageLoadChannel.publish(pageData)
300
381
  }
301
382
  }
302
383
 
303
384
  function wrapAppRouteHandle (handle) {
304
385
  return function (req, context) {
386
+ if (finishChannel.hasSubscribers) {
387
+ const nodeRequest = activeRouteRequests.get(this)
388
+ if (nodeRequest) {
389
+ nodeNextRequestsToNextRequests.set(nodeRequest, req)
390
+ }
391
+ }
392
+
393
+ return handle.apply(this, arguments)
394
+ }
395
+ }
396
+
397
+ function wrapPrepare (prepare) {
398
+ return function (req, res) {
399
+ if (startChannel.hasSubscribers || queryParsedChannel.hasSubscribers) {
400
+ routeResponses.set(req, res)
401
+ }
402
+
403
+ return prepare.apply(this, arguments)
404
+ }
405
+ }
406
+
407
+ /**
408
+ * @param {(context?: ResponseGeneratorContext) => unknown} responseGenerator
409
+ * @param {object | undefined} routeModule
410
+ * @param {NextNodeRequest} req
411
+ * @returns {(context?: ResponseGeneratorContext) => unknown}
412
+ */
413
+ function wrapResponseGenerator (responseGenerator, routeModule, req) {
414
+ return function (context) {
415
+ if (context?.hasResolved) {
416
+ const nodeRequest = req.originalRequest || req
417
+ return backgroundRevalidationChannel.runStores(nodeRequest, () => responseGenerator.apply(this, arguments))
418
+ }
419
+ if (!routeModule) {
420
+ return responseGenerator.apply(this, arguments)
421
+ }
422
+
423
+ // Next calls the route module before the generator's first await. Limit the association to that
424
+ // synchronous call so concurrent requests can share the route module instance safely.
425
+ activeRouteRequests.set(routeModule, req)
426
+ try {
427
+ return responseGenerator.apply(this, arguments)
428
+ } finally {
429
+ activeRouteRequests.delete(routeModule)
430
+ }
431
+ }
432
+ }
433
+
434
+ /**
435
+ * @param {(options: HandleResponseOptions) => Promise<ResponseCacheEntry | undefined>} handleResponse
436
+ * @param {boolean} associateNextRequest
437
+ * @returns {(options: HandleResponseOptions) => Promise<ResponseCacheEntry | undefined>}
438
+ */
439
+ function wrapHandleResponse (handleResponse, associateNextRequest) {
440
+ return function (options) {
441
+ const req = options.req
305
442
  if (!startChannel.hasSubscribers && !queryParsedChannel.hasSubscribers) {
306
- return handle.apply(this, arguments)
443
+ return handleResponse.apply(this, arguments)
307
444
  }
308
445
 
309
- const res = { statusCode: 500 }
310
- nodeNextRequestsToNextRequests.set(req, req)
446
+ const res = routeResponses.get(req)
447
+ routeResponses.delete(req)
448
+
449
+ const routeModule = res && associateNextRequest ? this : undefined
450
+ options.responseGenerator = wrapResponseGenerator(options.responseGenerator, routeModule, req)
451
+
452
+ if (!res) return handleResponse.apply(this, arguments)
311
453
 
312
454
  return instrument(req, res, ctx => {
313
455
  publishRoutePage(ctx, this, undefined, true)
314
456
 
315
- return handle.apply(this, arguments).then(response => {
316
- if (ctx) ctx.res.statusCode = response?.status || 200
317
- return response
457
+ return handleResponse.apply(this, arguments).then(result => {
458
+ const statusCode = result?.value?.status
459
+ if (ctx && typeof statusCode === 'number') {
460
+ ctx.res.statusCode = statusCode
461
+ }
462
+
463
+ return result
464
+ }, error => {
465
+ if (ctx && (typeof ctx.res.statusCode !== 'number' || ctx.res.statusCode < 400)) {
466
+ ctx.res.statusCode = 500
467
+ }
468
+
469
+ throw error
318
470
  })
319
- })
471
+ }, undefined, finishChannel.hasSubscribers)
320
472
  }
321
473
  }
322
474
 
323
- function instrumentRouteModule (RouteModule, method, wrapper, handleErrors) {
475
+ /**
476
+ * @param {(options: HandleResponseOptions) => Promise<ResponseCacheEntry | undefined>} handleResponse
477
+ * @returns {(options: HandleResponseOptions) => Promise<ResponseCacheEntry | undefined>}
478
+ */
479
+ function wrapAppRouteHandleResponse (handleResponse) {
480
+ return wrapHandleResponse(handleResponse, true)
481
+ }
482
+
483
+ /**
484
+ * @param {(options: HandleResponseOptions) => Promise<ResponseCacheEntry | undefined>} handleResponse
485
+ * @returns {(options: HandleResponseOptions) => Promise<ResponseCacheEntry | undefined>}
486
+ */
487
+ function wrapAppPageHandleResponse (handleResponse) {
488
+ return wrapHandleResponse(handleResponse, false)
489
+ }
490
+
491
+ function instrumentRouteModule (RouteModule, wrappers, handleErrors) {
324
492
  const proto = RouteModule?.prototype
325
493
  if (!proto || patchedRouteModules.has(RouteModule)) return
326
494
 
327
495
  patchedRouteModules.add(RouteModule)
328
- if (typeof proto[method] === 'function') {
329
- shimmer.wrap(proto, method, wrapper)
496
+ for (const [method, wrapper] of wrappers) {
497
+ if (typeof proto[method] === 'function') {
498
+ shimmer.wrap(proto, method, wrapper)
499
+ }
330
500
  }
331
501
  if (handleErrors && typeof proto.onRequestError === 'function') {
332
502
  shimmer.wrap(proto, 'onRequestError', wrapOnRequestError)
@@ -334,7 +504,11 @@ function instrumentRouteModule (RouteModule, method, wrapper, handleErrors) {
334
504
  }
335
505
 
336
506
  function instrumentAppRouteRuntime (runtime) {
337
- instrumentRouteModule(runtime.AppRouteRouteModule, 'handle', wrapAppRouteHandle, true)
507
+ instrumentRouteModule(runtime.AppRouteRouteModule, [
508
+ ['prepare', wrapPrepare],
509
+ ['handleResponse', wrapAppRouteHandleResponse],
510
+ ['handle', wrapAppRouteHandle],
511
+ ], true)
338
512
  return runtime
339
513
  }
340
514
 
@@ -361,40 +535,16 @@ function wrapPagesApiRender (render) {
361
535
 
362
536
  function instrumentPagesApiRuntime (runtime) {
363
537
  const PagesAPIRouteModule = runtime.PagesAPIRouteModule || runtime.default
364
- instrumentRouteModule(PagesAPIRouteModule, 'render', wrapPagesApiRender, false)
538
+ instrumentRouteModule(PagesAPIRouteModule, [['render', wrapPagesApiRender]], false)
365
539
  return runtime
366
540
  }
367
541
 
368
- function wrapAppPageRender (render) {
369
- return function (req, res, context = {}) {
370
- if (!startChannel.hasSubscribers && !queryParsedChannel.hasSubscribers) {
371
- return render.apply(this, arguments)
372
- }
373
-
374
- return instrument(req, res, ctx => {
375
- publishRoutePage(ctx, this, context.page, true)
376
-
377
- return render.apply(this, arguments).then(result => {
378
- const statusCode = result?.metadata?.statusCode
379
- if (ctx && typeof statusCode === 'number') {
380
- ctx.res.statusCode = statusCode
381
- }
382
-
383
- return result
384
- }, error => {
385
- if (ctx && (typeof ctx.res.statusCode !== 'number' || ctx.res.statusCode < 400)) {
386
- ctx.res.statusCode = 500
387
- }
388
-
389
- throw error
390
- })
391
- })
392
- }
393
- }
394
-
395
542
  function instrumentAppPageRuntime (runtime) {
396
543
  const AppPageRouteModule = runtime.AppPageRouteModule || runtime.default
397
- instrumentRouteModule(AppPageRouteModule, 'render', wrapAppPageRender, true)
544
+ instrumentRouteModule(AppPageRouteModule, [
545
+ ['prepare', wrapPrepare],
546
+ ['handleResponse', wrapAppPageHandleResponse],
547
+ ], true)
398
548
  return runtime
399
549
  }
400
550
 
@@ -6,6 +6,7 @@ const { addHook } = require('./helpers/instrument')
6
6
 
7
7
  const ch = dc.tracingChannel('apm:openai:request')
8
8
  const onStreamedChunkCh = dc.channel('apm:openai:request:chunk')
9
+ const responsePromiseContexts = new WeakMap()
9
10
 
10
11
  // Provider lifecycle channels. Payloads stay OpenAI-native:
11
12
  // before { args, parentSpan, abortController, pending }
@@ -68,7 +69,7 @@ const V4_PACKAGE_SHIMS = [
68
69
  file: 'resources/responses/responses',
69
70
  targetClass: 'Responses',
70
71
  baseResource: 'responses',
71
- methods: ['create'],
72
+ methods: ['create'], // implicitly covers `parse` as well which calls `create` under the hood
72
73
  streamedResponse: true,
73
74
  versions: ['>=4.87.0'],
74
75
  },
@@ -264,6 +265,42 @@ function wrapStreamIterator (response, options, ctx) {
264
265
 
265
266
  const extensions = ['.js', '.mjs']
266
267
 
268
+ for (const extension of extensions) {
269
+ const apiPromiseShims = [
270
+ { file: `core${extension}`, versions: ['>=4 <5'] },
271
+ { file: `core/api-promise${extension}`, versions: ['>=5'] },
272
+ ]
273
+
274
+ for (const { file, versions } of apiPromiseShims) {
275
+ // APIPromise is a Promise subclass whose methods are dynamically replaced on pagination instances,
276
+ // so preserving the returned object's identity requires shimmer instead of Orchestrion.
277
+ addHook({ name: 'openai', file, versions }, exports => {
278
+ const { prototype } = exports.APIPromise
279
+
280
+ shimmer.wrap(prototype, 'parse', parse => function (...args) {
281
+ const state = responsePromiseContexts.get(this.responsePromise)
282
+ if (!state) return parse.apply(this, args)
283
+
284
+ const parsedPromise = parse.apply(this, args)
285
+ .then(body => Promise.all([this.responsePromise, body]))
286
+
287
+ return handleUnwrappedAPIPromise(parsedPromise, state)
288
+ })
289
+
290
+ shimmer.wrap(prototype, 'asResponse', asResponse => function (...args) {
291
+ const state = responsePromiseContexts.get(this.responsePromise)
292
+ const responsePromise = asResponse.apply(this, args)
293
+
294
+ if (!state?.getBeforeVerdict) return responsePromise
295
+
296
+ return Promise.all([state.getBeforeVerdict(), responsePromise]).then(([, response]) => response)
297
+ })
298
+
299
+ return exports
300
+ })
301
+ }
302
+ }
303
+
267
304
  for (const extension of extensions) {
268
305
  for (const shim of V4_PACKAGE_SHIMS) {
269
306
  const { file, targetClass, baseResource, methods, versions, streamedResponse } = shim
@@ -309,44 +346,8 @@ for (const extension of extensions) {
309
346
  }
310
347
  : null
311
348
 
312
- if (baseResource === 'chat.completions' && typeof apiProm._thenUnwrap === 'function') {
313
- // this should only ever be invoked from a client.beta.chat.completions.parse call
314
- shimmer.wrap(apiProm, '_thenUnwrap', origApiPromThenUnwrap => function (...args) {
315
- // TODO(sam.brenner): I wonder if we can patch the APIPromise prototype instead, although
316
- // we might not have access to everything we need...
317
-
318
- // this is a new apipromise instance
319
- const unwrappedPromise = origApiPromThenUnwrap.apply(this, args)
320
-
321
- shimmer.wrap(unwrappedPromise, 'parse', origApiPromParse => function (...args) {
322
- const parsedPromise = origApiPromParse.apply(this, args)
323
- .then(body => Promise.all([this.responsePromise, body]))
324
-
325
- return handleUnwrappedAPIPromise(
326
- parsedPromise, ctx, stream, getBeforeVerdict, afterChannel, parentSpan
327
- )
328
- })
329
-
330
- return unwrappedPromise
331
- })
332
- }
333
-
334
- // wrapping `parse` avoids problematic wrapping of `then` when trying to call
335
- // `withResponse` in userland code after. This way, we can return the whole `APIPromise`
336
- shimmer.wrap(apiProm, 'parse', origApiPromParse => function (...args) {
337
- const parsedPromise = origApiPromParse.apply(this, args)
338
- .then(body => Promise.all([this.responsePromise, body]))
339
-
340
- return handleUnwrappedAPIPromise(parsedPromise, ctx, stream, getBeforeVerdict, afterChannel, parentSpan)
341
- })
342
-
343
- // Gate `.asResponse()` callers on the before verdict so raw-response paths still block.
344
- if (beforeChannel && typeof apiProm.asResponse === 'function') {
345
- shimmer.wrap(apiProm, 'asResponse', origAsResponse => function (...args) {
346
- const responsePromise = origAsResponse.apply(this, args)
347
- return Promise.all([getBeforeVerdict(), responsePromise]).then(([, response]) => response)
348
- })
349
- }
349
+ const state = { ctx, stream, getBeforeVerdict, afterChannel, parentSpan }
350
+ responsePromiseContexts.set(apiProm.responsePromise, state)
350
351
 
351
352
  ch.end.publish(ctx)
352
353
 
@@ -359,7 +360,8 @@ for (const extension of extensions) {
359
360
  }
360
361
  }
361
362
 
362
- function handleUnwrappedAPIPromise (apiProm, ctx, stream, getBeforeVerdict, afterChannel, parentSpan) {
363
+ function handleUnwrappedAPIPromise (apiProm, state) {
364
+ const { ctx, stream, getBeforeVerdict, afterChannel, parentSpan } = state
363
365
  const gatedApiProm = getBeforeVerdict
364
366
  ? Promise.all([getBeforeVerdict(), apiProm]).then(([, result]) => result)
365
367
  : apiProm
@@ -11,10 +11,12 @@ const {
11
11
  const {
12
12
  clearPoolWaitTime,
13
13
  dispatchesAcquireSynchronously,
14
+ getPoolAcquireCapture,
14
15
  isPoolQueryAcquire,
15
16
  runOutsidePoolQueryAcquire,
16
17
  runPoolAcquireError,
17
18
  runWithPoolWait,
19
+ setPoolAcquireCaptureWaitTime,
18
20
  takePoolWaitTime,
19
21
  wrapPoolAcquireCarrier,
20
22
  wrapPoolQueryMethod,
@@ -69,6 +71,19 @@ addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
69
71
  * @returns {unknown}
70
72
  */
71
73
  const wrappedConnect = function (callback) {
74
+ const capture = getPoolAcquireCapture()
75
+ if (capture !== undefined && typeof callback !== 'function') {
76
+ const start = acquireStart(this)
77
+ const result = connect.apply(this, arguments)
78
+ return result.then(client => {
79
+ setPoolAcquireCaptureWaitTime(capture, acquireWait(start))
80
+ return client
81
+ }, error => {
82
+ setPoolAcquireCaptureWaitTime(capture, acquireWait(start))
83
+ throw error
84
+ })
85
+ }
86
+
72
87
  // Pool.query always supplies a callback, so a missing callback is an explicit acquire.
73
88
  if (typeof callback !== 'function') {
74
89
  if (!poolAcquireStartCh.hasSubscribers) {