dd-trace 5.75.0 → 5.77.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 (53) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/ci/init.js +1 -0
  3. package/package.json +15 -15
  4. package/packages/datadog-instrumentations/src/express.js +18 -7
  5. package/packages/datadog-instrumentations/src/jest.js +16 -16
  6. package/packages/datadog-instrumentations/src/openai.js +8 -0
  7. package/packages/datadog-instrumentations/src/playwright.js +47 -0
  8. package/packages/datadog-instrumentations/src/router.js +9 -8
  9. package/packages/datadog-instrumentations/src/vitest.js +94 -8
  10. package/packages/datadog-plugin-cucumber/src/index.js +2 -1
  11. package/packages/datadog-plugin-grpc/src/util.js +5 -1
  12. package/packages/datadog-plugin-http/src/client.js +5 -1
  13. package/packages/datadog-plugin-http2/src/client.js +5 -1
  14. package/packages/datadog-plugin-openai/src/stream-helpers.js +26 -1
  15. package/packages/datadog-plugin-openai/src/tracing.js +46 -1
  16. package/packages/datadog-plugin-vitest/src/index.js +5 -1
  17. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  18. package/packages/dd-trace/src/appsec/channels.js +1 -0
  19. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +7 -0
  20. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  21. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -3
  22. package/packages/dd-trace/src/appsec/rasp/lfi.js +23 -9
  23. package/packages/dd-trace/src/appsec/stack_trace.js +6 -6
  24. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -0
  25. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +30 -7
  26. package/packages/dd-trace/src/config.js +10 -1
  27. package/packages/dd-trace/src/debugger/devtools_client/index.js +8 -1
  28. package/packages/dd-trace/src/debugger/devtools_client/log.js +15 -4
  29. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +5 -1
  30. package/packages/dd-trace/src/exporters/common/docker.js +1 -1
  31. package/packages/dd-trace/src/exporters/span-stats/writer.js +4 -5
  32. package/packages/dd-trace/src/format.js +4 -5
  33. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +9 -6
  34. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +3 -6
  35. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +3 -2
  36. package/packages/dd-trace/src/llmobs/plugins/openai.js +216 -12
  37. package/packages/dd-trace/src/llmobs/span_processor.js +4 -3
  38. package/packages/dd-trace/src/llmobs/tagger.js +9 -3
  39. package/packages/dd-trace/src/llmobs/util.js +1 -1
  40. package/packages/dd-trace/src/openfeature/writers/exposures.js +2 -2
  41. package/packages/dd-trace/src/opentelemetry/span.js +3 -2
  42. package/packages/dd-trace/src/plugins/ci_plugin.js +3 -1
  43. package/packages/dd-trace/src/plugins/util/serverless.js +1 -2
  44. package/packages/dd-trace/src/plugins/util/test.js +16 -1
  45. package/packages/dd-trace/src/plugins/util/web.js +5 -1
  46. package/packages/dd-trace/src/profiling/config.js +32 -10
  47. package/packages/dd-trace/src/proxy.js +2 -2
  48. package/packages/dd-trace/src/remote_config/capabilities.js +2 -0
  49. package/packages/dd-trace/src/remote_config/index.js +4 -0
  50. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
  51. package/packages/dd-trace/src/span_processor.js +4 -1
  52. package/packages/dd-trace/src/startup-log.js +24 -38
  53. package/packages/dd-trace/src/supported-configurations.json +1 -0
@@ -11,7 +11,7 @@ require,@opentelemetry/api,Apache license 2.0,Copyright OpenTelemetry Authors
11
11
  require,@opentelemetry/api-logs,Apache license 2.0,Copyright OpenTelemetry Authors
12
12
  require,@opentelemetry/core,Apache license 2.0,Copyright OpenTelemetry Authors
13
13
  require,@opentelemetry/resources,Apache license 2.0,Copyright OpenTelemetry Authors
14
- require,@isaacs/ttlcache,ISC,Copyright (c) 2022-2023 - Isaac Z. Schlueter and Contributors
14
+ require,@isaacs/ttlcache,Blue Oak,Copyright Isaac Z. Schlueter and Contributors
15
15
  require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
16
16
  require,dc-polyfill,MIT,Copyright 2023 Datadog Inc.
17
17
  require,escape-string-regexp,MIT,Copyright Sindre Sorhus
package/ci/init.js CHANGED
@@ -29,6 +29,7 @@ function detectTestWorkerType () {
29
29
  if (getEnvironmentVariable('MOCHA_WORKER_ID')) return 'mocha'
30
30
  if (getEnvironmentVariable('DD_PLAYWRIGHT_WORKER')) return 'playwright'
31
31
  if (getEnvironmentVariable('TINYPOOL_WORKER_ID')) return 'vitest'
32
+ if (getEnvironmentVariable('DD_VITEST_WORKER')) return 'vitest'
32
33
  return null
33
34
  }
34
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.75.0",
3
+ "version": "5.77.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -125,15 +125,15 @@
125
125
  "@datadog/native-appsec": "10.3.0",
126
126
  "@datadog/native-iast-taint-tracking": "4.0.0",
127
127
  "@datadog/native-metrics": "3.1.1",
128
- "@datadog/openfeature-node-server": "0.1.0-preview.12",
129
- "@datadog/pprof": "5.11.1",
128
+ "@datadog/openfeature-node-server": "0.1.0-preview.15",
129
+ "@datadog/pprof": "5.12.0",
130
130
  "@datadog/sketches-js": "2.1.1",
131
- "@datadog/wasm-js-rewriter": "4.0.1",
132
- "@isaacs/ttlcache": "^1.4.1",
131
+ "@datadog/wasm-js-rewriter": "5.0.1",
132
+ "@isaacs/ttlcache": "^2.0.1",
133
133
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
134
134
  "@opentelemetry/api-logs": "<1.0.0",
135
135
  "@opentelemetry/core": ">=1.14.0 <1.31.0",
136
- "@opentelemetry/resources": ">=1.0.0 <1.10.0",
136
+ "@opentelemetry/resources": ">=1.0.0 <1.31.0",
137
137
  "crypto-randomuuid": "^1.0.0",
138
138
  "dc-polyfill": "^0.1.10",
139
139
  "escape-string-regexp": "^5.0.0",
@@ -160,13 +160,13 @@
160
160
  "ttl-set": "^1.0.0"
161
161
  },
162
162
  "devDependencies": {
163
- "@babel/helpers": "^7.27.6",
163
+ "@babel/helpers": "^7.28.4",
164
164
  "@eslint/eslintrc": "^3.3.1",
165
- "@eslint/js": "^9.29.0",
165
+ "@eslint/js": "^9.39.0",
166
166
  "@msgpack/msgpack": "^3.1.2",
167
167
  "@openfeature/core": "^1.9.0",
168
168
  "@openfeature/server-sdk": "^1.20.0",
169
- "@stylistic/eslint-plugin": "^5.0.0",
169
+ "@stylistic/eslint-plugin": "^5.5.0",
170
170
  "@types/chai": "^4.3.16",
171
171
  "@types/mocha": "^10.0.10",
172
172
  "@types/node": "^18.19.106",
@@ -175,15 +175,15 @@
175
175
  "axios": "^1.12.2",
176
176
  "benchmark": "^2.1.4",
177
177
  "body-parser": "^2.2.0",
178
- "bun": "1.3.1",
178
+ "bun": "1.3.2",
179
179
  "chai": "^4.5.0",
180
- "eslint": "^9.29.0",
181
- "eslint-plugin-cypress": "^5.1.0",
180
+ "eslint": "^9.39.0",
181
+ "eslint-plugin-cypress": "^5.2.0",
182
182
  "eslint-plugin-import": "^2.32.0",
183
- "eslint-plugin-mocha": "^11.1.0",
184
- "eslint-plugin-n": "^17.20.0",
183
+ "eslint-plugin-mocha": "^11.2.0",
184
+ "eslint-plugin-n": "^17.23.1",
185
185
  "eslint-plugin-promise": "^7.2.1",
186
- "eslint-plugin-unicorn": "^61.0.2",
186
+ "eslint-plugin-unicorn": "^62.0.0",
187
187
  "express": "^5.1.0",
188
188
  "glob": "^10.4.5",
189
189
  "globals": "^16.3.0",
@@ -53,12 +53,21 @@ function wrapResponseRender (render) {
53
53
  return render.apply(this, arguments)
54
54
  }
55
55
 
56
+ const abortController = new AbortController()
56
57
  return responseRenderChannel.traceSync(
57
- render,
58
+ function () {
59
+ if (abortController.signal.aborted) {
60
+ const error = abortController.signal.reason || new Error('Aborted')
61
+ throw error
62
+ }
63
+
64
+ return render.apply(this, arguments)
65
+ },
58
66
  {
59
67
  req: this.req,
60
68
  view,
61
- options
69
+ options,
70
+ abortController
62
71
  },
63
72
  this,
64
73
  ...arguments
@@ -67,26 +76,28 @@ function wrapResponseRender (render) {
67
76
  }
68
77
 
69
78
  function wrapAppAll (all) {
70
- return function wrappedAll (path, ...otherArgs) {
71
- if (!routeAddedChannel.hasSubscribers) return all.call(this, path, ...otherArgs)
79
+ return function wrappedAll (...args) {
80
+ if (!routeAddedChannel.hasSubscribers) return all.apply(this, args)
72
81
 
82
+ const path = args[0]
73
83
  const paths = normalizeRoutePaths(path)
74
84
 
75
85
  for (const p of paths) {
76
86
  routeAddedChannel.publish({ method: '*', path: p })
77
87
  }
78
88
 
79
- return all.call(this, path, ...otherArgs)
89
+ return all.apply(this, args)
80
90
  }
81
91
  }
82
92
 
83
93
  // Wrap app.route() to instrument Route object
84
94
  function wrapAppRoute (route) {
85
- return function wrappedRoute (path, ...otherArgs) {
86
- const routeObj = route.call(this, path, ...otherArgs)
95
+ return function wrappedRoute (...args) {
96
+ const routeObj = route.apply(this, args)
87
97
 
88
98
  if (!routeAddedChannel.hasSubscribers) return routeObj
89
99
 
100
+ const path = args[0]
90
101
  const paths = normalizeRoutePaths(path)
91
102
 
92
103
  if (!paths.length) return routeObj
@@ -140,7 +140,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
140
140
  this.hasSnapshotTests = undefined
141
141
  this.testSuiteAbsolutePath = context.testPath
142
142
 
143
- this.displayName = config.projectConfig?.displayName?.name
143
+ this.displayName = config.projectConfig?.displayName?.name || config.displayName
144
144
  this.testEnvironmentOptions = getTestEnvironmentOptions(config)
145
145
 
146
146
  const repositoryRoot = this.testEnvironmentOptions._ddRepositoryRoot
@@ -411,23 +411,19 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
411
411
  } else {
412
412
  originalHookFns.set(hook, hookFn)
413
413
  }
414
- // The rule has a bug, see https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2164
415
- // eslint-disable-next-line unicorn/consistent-function-scoping
416
- const wrapperHook = function () {
414
+ const newHookFn = shimmer.wrapFunction(hookFn, hookFn => function () {
417
415
  return testFnCh.runStores(ctx, () => hookFn.apply(this, arguments))
418
- }
419
- // If we don't do this, the timeout will not be triggered
420
- Object.defineProperty(wrapperHook, 'length', { value: hookFn.length })
421
- hook.fn = wrapperHook
416
+ })
417
+ hook.fn = newHookFn
422
418
  }
423
419
  const originalFn = event.test.fn
424
420
  originalTestFns.set(event.test, originalFn)
425
- const wrapper = function () {
426
- return testFnCh.runStores(ctx, () => originalFn.apply(this, arguments))
427
- }
428
- // If we don't do this, the timeout will be not be triggered
429
- Object.defineProperty(wrapper, 'length', { value: originalFn.length })
430
- event.test.fn = wrapper
421
+
422
+ const newFn = shimmer.wrapFunction(event.test.fn, testFn => function () {
423
+ return testFnCh.runStores(ctx, () => testFn.apply(this, arguments))
424
+ })
425
+
426
+ event.test.fn = newFn
431
427
  })
432
428
  }
433
429
 
@@ -1293,7 +1289,7 @@ addHook({
1293
1289
 
1294
1290
  shimmer.wrap(Runtime.prototype, '_createJestObjectFor', _createJestObjectFor => function (from) {
1295
1291
  const result = _createJestObjectFor.apply(this, arguments)
1296
- const suiteFilePath = this._testPath
1292
+ const suiteFilePath = this._testPath || from
1297
1293
 
1298
1294
  shimmer.wrap(result, 'mock', mock => function (moduleName) {
1299
1295
  // If the library is mocked with `jest.mock`, we don't want to bypass jest's own require engine
@@ -1450,7 +1446,11 @@ addHook({
1450
1446
  }, (childProcessWorker) => {
1451
1447
  const ChildProcessWorker = childProcessWorker.default
1452
1448
  shimmer.wrap(ChildProcessWorker.prototype, 'send', sendWrapper)
1453
- shimmer.wrap(ChildProcessWorker.prototype, '_onMessage', onMessageWrapper)
1449
+ if (ChildProcessWorker.prototype._onMessage) {
1450
+ shimmer.wrap(ChildProcessWorker.prototype, '_onMessage', onMessageWrapper)
1451
+ } else if (ChildProcessWorker.prototype.onMessage) {
1452
+ shimmer.wrap(ChildProcessWorker.prototype, 'onMessage', onMessageWrapper)
1453
+ }
1454
1454
  return childProcessWorker
1455
1455
  })
1456
1456
 
@@ -22,6 +22,14 @@ const V4_PACKAGE_SHIMS = [
22
22
  methods: ['create'],
23
23
  streamedResponse: true
24
24
  },
25
+ {
26
+ file: 'resources/responses/responses',
27
+ targetClass: 'Responses',
28
+ baseResource: 'responses',
29
+ methods: ['create'],
30
+ streamedResponse: true,
31
+ versions: ['>=4.87.0']
32
+ },
25
33
  {
26
34
  file: 'resources/embeddings',
27
35
  targetClass: 'Embeddings',
@@ -68,6 +68,8 @@ let modifiedFiles = {}
68
68
  const quarantinedOrDisabledTestsAttemptToFix = []
69
69
  let quarantinedButNotAttemptToFixFqns = new Set()
70
70
  let rootDir = ''
71
+ let sessionProjects = []
72
+
71
73
  const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0' // TODO: remove this once we drop support for v5
72
74
 
73
75
  function isValidKnownTests (receivedKnownTests) {
@@ -495,6 +497,7 @@ function dispatcherHook (dispatcherExport) {
495
497
  const dispatcher = this
496
498
  const worker = createWorker.apply(this, arguments)
497
499
  const projects = getProjectsFromDispatcher(dispatcher)
500
+ sessionProjects = projects
498
501
 
499
502
  // for older versions of playwright, `shouldCreateTestSpan` should always be true,
500
503
  // since the `_runTest` function wrapper is not available for older versions
@@ -535,6 +538,7 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
535
538
  const dispatcher = this
536
539
  const worker = createWorker.apply(this, arguments)
537
540
  const projects = getProjectsFromDispatcher(dispatcher)
541
+ sessionProjects = projects
538
542
 
539
543
  worker.on('testBegin', ({ testId }) => {
540
544
  const test = getTestByTestId(dispatcher, testId)
@@ -1255,3 +1259,46 @@ addHook({
1255
1259
 
1256
1260
  return workerPackage
1257
1261
  })
1262
+
1263
+ function generateSummaryWrapper (generateSummary) {
1264
+ return function () {
1265
+ for (const test of this.suite.allTests()) {
1266
+ // https://github.com/microsoft/playwright/blob/bf92ffecff6f30a292b53430dbaee0207e0c61ad/packages/playwright/src/reporters/base.ts#L279
1267
+ const didNotRun = test.outcome() === 'skipped' &&
1268
+ (!test.results.length || test.expectedStatus !== 'skipped')
1269
+ if (didNotRun) {
1270
+ const {
1271
+ _requireFile: testSuiteAbsolutePath,
1272
+ location: { line: testSourceLine },
1273
+ } = test
1274
+ const browserName = getBrowserNameFromProjects(sessionProjects, test)
1275
+
1276
+ testSkipCh.publish({
1277
+ testName: getTestFullname(test),
1278
+ testSuiteAbsolutePath,
1279
+ testSourceLine,
1280
+ browserName,
1281
+ })
1282
+ }
1283
+ }
1284
+ return generateSummary.apply(this, arguments)
1285
+ }
1286
+ }
1287
+
1288
+ // If a playwright project B has a dependency on project A,
1289
+ // and project A fails, the tests in project B will not run.
1290
+ // This hook is used to report tests that did not run as skipped.
1291
+ // Note: this is different from tests skipped via test.skip() or test.fixme()
1292
+ addHook({
1293
+ name: 'playwright',
1294
+ file: 'lib/reporters/base.js',
1295
+ versions: ['>=1.38.0']
1296
+ }, (reportersPackage) => {
1297
+ // v1.50.0 changed the name of the base reporter from BaseReporter to TerminalReporter
1298
+ if (reportersPackage.TerminalReporter) {
1299
+ shimmer.wrap(reportersPackage.TerminalReporter.prototype, 'generateSummary', generateSummaryWrapper)
1300
+ } else if (reportersPackage.BaseReporter) {
1301
+ shimmer.wrap(reportersPackage.BaseReporter.prototype, 'generateSummary', generateSummaryWrapper)
1302
+ }
1303
+ return reportersPackage
1304
+ })
@@ -146,33 +146,34 @@ function createWrapRouterMethod (name) {
146
146
  }
147
147
 
148
148
  function wrapMethod (original) {
149
- return shimmer.wrapFunction(original, original => function methodWithTrace (fn, ...otherArgs) {
149
+ return shimmer.wrapFunction(original, original => function methodWithTrace (...args) {
150
150
  let offset = 0
151
151
  if (this.stack) {
152
152
  offset = Array.isArray(this.stack) ? this.stack.length : 1
153
153
  }
154
- const router = original.call(this, fn, ...otherArgs)
154
+ const router = original.apply(this, args)
155
155
 
156
156
  if (typeof this.stack === 'function') {
157
157
  this.stack = [{ handle: this.stack }]
158
158
  }
159
159
 
160
160
  if (routeAddedChannel.hasSubscribers) {
161
- routeAddedChannel.publish({ topOfStackFunc: methodWithTrace, layer: this.stack.at(-1) })
161
+ routeAddedChannel.publish({ topOfStackFunc: methodWithTrace, layer: this.stack?.at(-1) })
162
162
  }
163
163
 
164
+ const fn = args[0]
165
+
164
166
  // Publish only if this router was mounted by app.use() (prevents early '/sub/...')
165
167
  if (routeAddedChannel.hasSubscribers && isAppMounted(this) && this.stack?.length > offset) {
166
168
  // Handle nested router mounting for 'use' method
167
- if (original.name === 'use' && otherArgs.length >= 1) {
169
+ if (original.name === 'use' && args.length >= 2) {
168
170
  const { mountPaths, startIdx } = extractMountPaths(fn)
169
171
 
170
172
  if (mountPaths.length) {
171
173
  const parentPaths = getRouterMountPaths(this)
172
- const callArgs = [fn, ...otherArgs]
173
174
 
174
- for (let i = startIdx; i < callArgs.length; i++) {
175
- const nestedRouter = callArgs[i]
175
+ for (let i = startIdx; i < args.length; i++) {
176
+ const nestedRouter = args[i]
176
177
 
177
178
  if (!nestedRouter || typeof nestedRouter !== 'function') continue
178
179
 
@@ -206,7 +207,7 @@ function createWrapRouterMethod (name) {
206
207
  }
207
208
  }
208
209
 
209
- if (this.stack.length > offset) {
210
+ if (this.stack?.length > offset) {
210
211
  wrapStack(this.stack.slice(offset), extractMatchers(fn))
211
212
  }
212
213
 
@@ -60,6 +60,7 @@ let testManagementAttemptToFixRetries = 0
60
60
  let isDiEnabled = false
61
61
  let testCodeCoverageLinesTotal
62
62
  let isSessionStarted = false
63
+ let vitestPool = null
63
64
 
64
65
  const BREAKPOINT_HIT_GRACE_PERIOD_MS = 400
65
66
 
@@ -157,6 +158,14 @@ function isTestPackage (testPackage) {
157
158
  return testPackage.V?.name === 'VitestTestRunner'
158
159
  }
159
160
 
161
+ function hasForksPoolWorker (vitestPackage) {
162
+ return vitestPackage.f?.name === 'ForksPoolWorker'
163
+ }
164
+
165
+ function hasThreadsPoolWorker (vitestPackage) {
166
+ return vitestPackage.T?.name === 'ThreadsPoolWorker'
167
+ }
168
+
160
169
  function getSessionStatus (state) {
161
170
  if (state.getCountOfFailedTests() > 0) {
162
171
  return 'fail'
@@ -389,6 +398,7 @@ function getFinishWrapper (exitOrClose) {
389
398
  isEarlyFlakeDetectionEnabled,
390
399
  isEarlyFlakeDetectionFaulty,
391
400
  isTestManagementTestsEnabled,
401
+ vitestPool,
392
402
  onFinish
393
403
  })
394
404
 
@@ -418,11 +428,27 @@ function getCreateCliWrapper (vitestPackage, frameworkVersion) {
418
428
  }
419
429
 
420
430
  function threadHandler (thread) {
421
- if (workerProcesses.has(thread.process)) {
431
+ const { runtime } = thread
432
+ let workerProcess
433
+ if (runtime === 'child_process') {
434
+ vitestPool = 'child_process'
435
+ workerProcess = thread.process
436
+ } else if (runtime === 'worker_threads') {
437
+ vitestPool = 'worker_threads'
438
+ workerProcess = thread.thread
439
+ } else {
440
+ vitestPool = 'unknown'
441
+ }
442
+ if (!workerProcess) {
443
+ log.error('Vitest error: could not get process or thread from TinyPool#run')
422
444
  return
423
445
  }
424
- workerProcesses.add(thread.process)
425
- thread.process.on('message', (message) => {
446
+
447
+ if (workerProcesses.has(workerProcess)) {
448
+ return
449
+ }
450
+ workerProcesses.add(workerProcess)
451
+ workerProcess.on('message', (message) => {
426
452
  if (message.__tinypool_worker_message__ && message.data) {
427
453
  if (message.interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
428
454
  workerReportTraceCh.publish(message.data)
@@ -433,11 +459,7 @@ function threadHandler (thread) {
433
459
  })
434
460
  }
435
461
 
436
- addHook({
437
- name: 'tinypool',
438
- versions: ['>=1.0.0'],
439
- file: 'dist/index.js'
440
- }, (TinyPool) => {
462
+ function wrapTinyPoolRun (TinyPool) {
441
463
  shimmer.wrap(TinyPool.prototype, 'run', run => async function () {
442
464
  // We have to do this before and after because the threads list gets recycled, that is, the processes are re-created
443
465
  this.threads.forEach(threadHandler)
@@ -445,15 +467,79 @@ addHook({
445
467
  this.threads.forEach(threadHandler)
446
468
  return runResult
447
469
  })
470
+ }
471
+
472
+ addHook({
473
+ name: 'tinypool',
474
+ // version from tinypool@0.8 was used in vitest@1.6.0
475
+ versions: ['>=0.8.0 <1.0.0'],
476
+ file: 'dist/esm/index.js'
477
+ }, (TinyPool) => {
478
+ wrapTinyPoolRun(TinyPool)
479
+ return TinyPool
480
+ })
481
+
482
+ addHook({
483
+ name: 'tinypool',
484
+ versions: ['>=1.0.0'],
485
+ file: 'dist/index.js'
486
+ }, (TinyPool) => {
487
+ wrapTinyPoolRun(TinyPool)
448
488
 
449
489
  return TinyPool
450
490
  })
451
491
 
492
+ function getWrappedOn (on) {
493
+ return function (event, callback) {
494
+ if (event !== 'message') {
495
+ return on.apply(this, arguments)
496
+ }
497
+ // `arguments[1]` is the callback function, which
498
+ // we modify to intercept our messages to not interfere
499
+ // with vitest's own messages
500
+ arguments[1] = shimmer.wrapFunction(callback, callback => function (message) {
501
+ if (message.type !== 'Buffer' && Array.isArray(message)) {
502
+ const [interprocessCode, data] = message
503
+ if (interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE) {
504
+ workerReportTraceCh.publish(data)
505
+ } else if (interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE) {
506
+ workerReportLogsCh.publish(data)
507
+ }
508
+ // If we execute the callback vitest crashes, as the message is not supported
509
+ return
510
+ }
511
+ return callback.apply(this, arguments)
512
+ })
513
+ return on.apply(this, arguments)
514
+ }
515
+ }
516
+
452
517
  function getStartVitestWrapper (cliApiPackage, frameworkVersion) {
453
518
  if (!isCliApiPackage(cliApiPackage)) {
454
519
  return cliApiPackage
455
520
  }
456
521
  shimmer.wrap(cliApiPackage, 's', getCliOrStartVitestWrapper(frameworkVersion))
522
+
523
+ if (hasForksPoolWorker(cliApiPackage)) {
524
+ // function is async
525
+ shimmer.wrap(cliApiPackage.f.prototype, 'start', start => function () {
526
+ vitestPool = 'child_process'
527
+ this.env.DD_VITEST_WORKER = '1'
528
+
529
+ return start.apply(this, arguments)
530
+ })
531
+ shimmer.wrap(cliApiPackage.f.prototype, 'on', getWrappedOn)
532
+ }
533
+
534
+ if (hasThreadsPoolWorker(cliApiPackage)) {
535
+ // function is async
536
+ shimmer.wrap(cliApiPackage.T.prototype, 'start', start => function () {
537
+ vitestPool = 'worker_threads'
538
+ this.env.DD_VITEST_WORKER = '1'
539
+ return start.apply(this, arguments)
540
+ })
541
+ shimmer.wrap(cliApiPackage.T.prototype, 'on', getWrappedOn)
542
+ }
457
543
  return cliApiPackage
458
544
  }
459
545
 
@@ -51,7 +51,8 @@ const {
51
51
  } = require('../../dd-trace/src/ci-visibility/telemetry')
52
52
 
53
53
  const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
54
- const BREAKPOINT_SET_GRACE_PERIOD_MS = 200
54
+ const BREAKPOINT_SET_GRACE_PERIOD_MS = 400
55
+
55
56
  const isCucumberWorker = !!getEnvironmentVariable('CUCUMBER_WORKER_ID')
56
57
 
57
58
  class CucumberPlugin extends CiPlugin {
@@ -3,6 +3,10 @@
3
3
  const pick = require('../../datadog-core/src/utils/src/pick')
4
4
  const log = require('../../dd-trace/src/log')
5
5
 
6
+ function getEmptyObject () {
7
+ return {}
8
+ }
9
+
6
10
  module.exports = {
7
11
  getMethodMetadata (path, kind) {
8
12
  const tags = {
@@ -57,6 +61,6 @@ module.exports = {
57
61
  log.error('Expected \'%s\' to be an array or function.', filter)
58
62
  }
59
63
 
60
- return () => ({})
64
+ return getEmptyObject
61
65
  }
62
66
  }
@@ -183,13 +183,17 @@ function normalizeClientConfig (config) {
183
183
  }
184
184
  }
185
185
 
186
+ function is400ErrorCode (code) {
187
+ return code < 400 || code >= 500
188
+ }
189
+
186
190
  function getStatusValidator (config) {
187
191
  if (typeof config.validateStatus === 'function') {
188
192
  return config.validateStatus
189
193
  } else if (config.hasOwnProperty('validateStatus')) {
190
194
  log.error('Expected `validateStatus` to be a function.')
191
195
  }
192
- return code => code < 400 || code >= 500
196
+ return is400ErrorCode
193
197
  }
194
198
 
195
199
  function getFilter (config) {
@@ -162,13 +162,17 @@ function hasAmazonSignature (headers, path) {
162
162
  return false
163
163
  }
164
164
 
165
+ function is400ErrorCode (code) {
166
+ return code < 400 || code >= 500
167
+ }
168
+
165
169
  function getStatusValidator (config) {
166
170
  if (typeof config.validateStatus === 'function') {
167
171
  return config.validateStatus
168
172
  } else if (config.hasOwnProperty('validateStatus')) {
169
173
  log.error('Expected `validateStatus` to be a function.')
170
174
  }
171
- return code => code < 400 || code >= 500
175
+ return is400ErrorCode
172
176
  }
173
177
 
174
178
  function normalizeConfig (config) {
@@ -107,8 +107,33 @@ function constructChatCompletionResponseFromStreamedChunks (chunks, n) {
107
107
  })
108
108
  }
109
109
 
110
+ /**
111
+ * Constructs the entire response from a stream of OpenAI responses chunks.
112
+ * The responses API uses event-based streaming with delta chunks.
113
+ * @param {Array<Record<string, any>>} chunks
114
+ * @returns {Record<string, any>}
115
+ */
116
+ function constructResponseResponseFromStreamedChunks (chunks) {
117
+ // The responses API streams events with different types:
118
+ // - response.output_text.delta: incremental text deltas
119
+ // - response.output_text.done: complete text for a content part
120
+ // - response.output_item.done: complete output item with role
121
+ // - response.done/response.incomplete/response.completed: final response with output array and usage
122
+
123
+ // Find the last chunk with a complete response object (status: done, incomplete, or completed)
124
+ const responseStatusSet = new Set(['done', 'incomplete', 'completed'])
125
+
126
+ for (let i = chunks.length - 1; i >= 0; i--) {
127
+ const chunk = chunks[i]
128
+ if (chunk.response && responseStatusSet.has(chunk.response.status)) {
129
+ return chunk.response
130
+ }
131
+ }
132
+ }
133
+
110
134
  module.exports = {
111
135
  convertBuffersToObjects,
112
136
  constructCompletionResponseFromStreamedChunks,
113
- constructChatCompletionResponseFromStreamedChunks
137
+ constructChatCompletionResponseFromStreamedChunks,
138
+ constructResponseResponseFromStreamedChunks
114
139
  }