dd-trace 6.8.0 → 6.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +9 -3
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +140 -42
  44. package/index.d.ts +140 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +8 -5
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  55. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  56. package/packages/datadog-instrumentations/src/dns.js +21 -2
  57. package/packages/datadog-instrumentations/src/electron.js +1 -0
  58. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  59. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  60. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  61. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  62. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +199 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +62 -0
  68. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  69. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  70. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  71. package/packages/datadog-instrumentations/src/jest.js +980 -234
  72. package/packages/datadog-instrumentations/src/mocha/main.js +108 -10
  73. package/packages/datadog-instrumentations/src/mocha/utils.js +66 -50
  74. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  75. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  76. package/packages/datadog-instrumentations/src/mysql.js +46 -16
  77. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  78. package/packages/datadog-instrumentations/src/next.js +155 -23
  79. package/packages/datadog-instrumentations/src/openai.js +14 -0
  80. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  81. package/packages/datadog-instrumentations/src/pg.js +209 -11
  82. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  83. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  84. package/packages/datadog-instrumentations/src/promise.js +3 -3
  85. package/packages/datadog-instrumentations/src/router.js +195 -19
  86. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +85 -13
  87. package/packages/datadog-instrumentations/src/vitest-main.js +528 -72
  88. package/packages/datadog-instrumentations/src/vitest-util.js +106 -12
  89. package/packages/datadog-instrumentations/src/vitest-worker.js +382 -40
  90. package/packages/datadog-instrumentations/src/webdriverio.js +212 -22
  91. package/packages/datadog-instrumentations/src/when.js +3 -3
  92. package/packages/datadog-instrumentations/src/ws.js +5 -1
  93. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  94. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  95. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
  96. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  97. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  98. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  99. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -47
  100. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  101. package/packages/datadog-plugin-electron/src/net.js +10 -4
  102. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  103. package/packages/datadog-plugin-http/src/client.js +1 -1
  104. package/packages/datadog-plugin-http2/src/client.js +1 -1
  105. package/packages/datadog-plugin-jest/src/index.js +6 -22
  106. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  107. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  108. package/packages/datadog-plugin-kafkajs/src/producer.js +5 -3
  109. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  110. package/packages/datadog-plugin-mocha/src/index.js +832 -4
  111. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  112. package/packages/datadog-plugin-next/src/index.js +51 -21
  113. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  114. package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
  115. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  116. package/packages/datadog-plugin-pg/src/index.js +66 -1
  117. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  118. package/packages/datadog-plugin-vitest/src/index.js +103 -27
  119. package/packages/datadog-plugin-ws/src/util.js +2 -1
  120. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  121. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  122. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  123. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  124. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  125. package/packages/dd-trace/src/appsec/handlers/http-request.js +228 -0
  126. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  127. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  128. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  129. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  130. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  131. package/packages/dd-trace/src/appsec/index.js +48 -476
  132. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  133. package/packages/dd-trace/src/carrier.js +356 -0
  134. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  135. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  136. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  137. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  139. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  140. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  141. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  142. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  143. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  144. package/packages/dd-trace/src/config/generated-config-types.d.ts +5 -0
  145. package/packages/dd-trace/src/config/helper.js +2 -0
  146. package/packages/dd-trace/src/config/index.js +16 -5
  147. package/packages/dd-trace/src/config/remote_config.js +1 -1
  148. package/packages/dd-trace/src/config/supported-configurations.json +18 -0
  149. package/packages/dd-trace/src/datastreams/context.js +6 -1
  150. package/packages/dd-trace/src/datastreams/manager.js +5 -1
  151. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  152. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  153. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  154. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  155. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  156. package/packages/dd-trace/src/guardrails/log.js +1 -10
  157. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  158. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  159. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  160. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  161. package/packages/dd-trace/src/llmobs/index.js +34 -2
  162. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  163. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  164. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  165. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  166. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  167. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  168. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  169. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  170. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  171. package/packages/dd-trace/src/llmobs/util.js +78 -0
  172. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  173. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  174. package/packages/dd-trace/src/log/channels.js +1 -9
  175. package/packages/dd-trace/src/log/levels.js +12 -0
  176. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  177. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  178. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  179. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  180. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  181. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  182. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  183. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  184. package/packages/dd-trace/src/plugin_manager.js +2 -0
  185. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  186. package/packages/dd-trace/src/plugins/index.js +1 -0
  187. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  188. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  189. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  190. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  191. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +3 -62
  192. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  193. package/packages/dd-trace/src/plugins/util/test.js +397 -74
  194. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  195. package/packages/dd-trace/src/plugins/util/web.js +76 -3
  196. package/packages/dd-trace/src/profiler.js +1 -1
  197. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  198. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  199. package/packages/dd-trace/src/proxy.js +1 -0
  200. package/packages/dd-trace/src/ritm.js +5 -0
  201. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  202. package/packages/dd-trace/src/sampling_rule.js +4 -2
  203. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  204. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  205. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  206. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  207. package/vendor/dist/pprof-format/index.js +1 -1
  208. package/version.js +10 -8
  209. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
package/initialize.mjs CHANGED
@@ -20,6 +20,7 @@ import { isMainThread } from 'worker_threads'
20
20
 
21
21
  import {
22
22
  iitmExclusionRegExp,
23
+ initialize as hookInitialize,
23
24
  load as hookLoad,
24
25
  resolve as hookResolve,
25
26
  } from './loader-hook.mjs?initialize'
@@ -66,7 +67,17 @@ const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number)
66
67
 
67
68
  const brokenLoaders = NODE_MAJOR === 18 && NODE_MINOR === 0
68
69
 
70
+ // Node calls `initialize` only through `module.register`, never for `--loader`; without it
71
+ // import-in-the-middle keeps its default matcher and proxies every application module. Loaders
72
+ // before Node 18.19 share the application thread, where `loader-hook.mjs` builds no matcher.
73
+ let needsHookInitialize = !isMainThread && !brokenLoaders
74
+
69
75
  export async function load (url, context, nextLoad) {
76
+ if (needsHookInitialize) {
77
+ needsHookInitialize = false
78
+ hookInitialize()
79
+ }
80
+
70
81
  const loadHook = (brokenLoaders || iitmExclusionRegExp.test(url)) ? nextLoad : hookLoad
71
82
  return insertInit(await loadHook(url, context, nextLoad), url, context)
72
83
  }
package/loader-hook.mjs CHANGED
@@ -16,7 +16,6 @@ const require = Module.createRequire(import.meta.url)
16
16
  const isInitializeMainThread = isMainThread && import.meta.url.endsWith('?initialize')
17
17
  let syncImportInTheMiddleHook
18
18
 
19
- let getValueFromEnvSources
20
19
  let regexpEscape
21
20
 
22
21
  // Substrings of resolved URLs that import-in-the-middle must never wrap: re-export
@@ -32,13 +31,12 @@ const includeModuleNames = new Set()
32
31
  let moduleNameAlternation = ''
33
32
 
34
33
  if (!isInitializeMainThread) {
34
+ require('./packages/dd-trace/src/guardrails/apply-pm2-env.js')
35
35
  const regexpEscapeModule = require('./vendor/dist/escape-string-regexp/index.js')
36
36
  const hooks = require('./packages/datadog-instrumentations/src/helpers/hooks.js')
37
- const configHelper = require('./packages/dd-trace/src/config/helper.js')
38
37
  const { isRelativeRequire } = require('./packages/datadog-instrumentations/src/helpers/shared-utils.js')
39
38
 
40
39
  regexpEscape = regexpEscapeModule.default
41
- getValueFromEnvSources = configHelper.getValueFromEnvSources
42
40
 
43
41
  for (const moduleName of Object.keys(hooks)) {
44
42
  // Relative hooks resolve outside node_modules and are not instrumented here.
@@ -63,7 +61,11 @@ function prepareImportInTheMiddleOptions (data = {}) {
63
61
  // A consumer-owned shouldInclude predicate takes over the wrapping decision, so
64
62
  // iitm ignores the include/exclude arrays. Building the matcher here keeps the
65
63
  // synchronous and asynchronous loaders on one matching implementation.
66
- data.shouldInclude = createShouldInclude(getValueFromEnvSources('DD_IAST_SECURITY_CONTROLS_CONFIGURATION'))
64
+ // Lazily required and read without its registered default so a loader worker
65
+ // never loads the configuration defaults table. The default is unset regardless.
66
+ const { getValueFromEnvSources } = require('./packages/dd-trace/src/config/helper.js')
67
+
68
+ data.shouldInclude = createShouldInclude(getValueFromEnvSources('DD_IAST_SECURITY_CONTROLS_CONFIGURATION', true))
67
69
 
68
70
  return data
69
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.8.0",
3
+ "version": "6.10.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -15,16 +15,19 @@
15
15
  "generate:electron-package": "node scripts/generate-electron-package.js",
16
16
  "verify:config:types": "node scripts/generate-config-types.js --check",
17
17
  "verify:electron-package": "node scripts/generate-electron-package.js --check",
18
+ "generate:rewriter:targets": "node scripts/generate-rewriter-targets.js",
19
+ "verify:rewriter:targets": "node scripts/generate-rewriter-targets.js --check",
18
20
  "generate:supported-integrations": "node scripts/generate-supported-integrations.js",
19
21
  "verify:supported-integrations": "node scripts/generate-supported-integrations.js --check",
20
22
  "type:check": "tsc --noEmit -p tsconfig.dev.json",
21
23
  "type:doc:build": "cd docs && yarn && yarn build",
22
24
  "type:doc:test": "cd docs && yarn && yarn test",
23
- "lint": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 && npm run lint:codeowners:ci && npm run verify-exercised-tests",
25
+ "lint": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 && npm run verify:carrier-fields && npm run lint:codeowners:ci && npm run verify-exercised-tests",
24
26
  "lint:fix": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 --fix",
27
+ "verify:carrier-fields": "node scripts/verify-carrier-fields.mjs",
25
28
  "lint:inspect": "npx @eslint/config-inspector@latest",
26
29
  "lint:codeowners": "codeowners-audit",
27
- "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**' --glob='.agents/**' --glob='.claude/**' --glob='integration-tests/**'",
30
+ "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**' --glob='.agents/**' --glob='.claude/**' --glob='integration-tests/**' --glob='packages/dd-trace/src/guardrails/**'",
28
31
  "lint:editorconfig": "docker build -q -t ec .github/editorconfig-checker && docker run --rm --volume=$PWD:/check ec",
29
32
  "release:proposal": "node scripts/release/proposal",
30
33
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
@@ -37,7 +40,7 @@
37
40
  "test:appsec:plugins:ci": "yarn services && node scripts/c8-ci.js test:appsec:plugins",
38
41
  "test:debugger": "mocha \"packages/dd-trace/test/debugger/**/*.spec.js\"",
39
42
  "test:debugger:ci": "node scripts/c8-ci.js test:debugger",
40
- "test:eslint-rules": "node eslint-rules/*.test.mjs",
43
+ "test:eslint-rules": "node scripts/run-eslint-rule-tests.mjs eslint-rules/*.test.mjs",
41
44
  "test:trace:core": "node scripts/mocha-parallel-files.js --expose-gc --timeout 30000 -- \"packages/dd-trace/test/*.spec.js\" \"packages/dd-trace/test/{agent,ci-visibility,config,crashtracking,datastreams,encode,exporters,msgpack,opentelemetry,opentracing,payload-tagging,plugins,remote_config,service-naming,standalone,telemetry,external-logger}/**/*.spec.js\"",
42
45
  "test:trace:core:ci": "node scripts/c8-ci.js test:trace:core",
43
46
  "test:trace:guardrails": "mocha \"packages/dd-trace/test/guardrails/**/*.spec.js\"",
@@ -217,7 +220,7 @@
217
220
  "eslint-plugin-n": "^18.2.1",
218
221
  "eslint-plugin-promise": "^7.3.0",
219
222
  "eslint-plugin-sonarjs": "^4.2.0",
220
- "eslint-plugin-unicorn": "^72.0.0",
223
+ "eslint-plugin-unicorn": "^73.0.0",
221
224
  "express": "^5.1.0",
222
225
  "glob": "^10.4.5",
223
226
  "globals": "^17.7.0",
@@ -9,7 +9,6 @@ const digitRegex = /^\d+$/
9
9
  * { 'a.0.b': 'value' } -> { a: [{ b: 'value' }] }
10
10
  *
11
11
  * @param {object} tags - Key/value pairs of tags
12
- * @returns Object - Parsed tags
13
12
  */
14
13
  module.exports = function parseTags (tags) {
15
14
  const parsedTags = {}
@@ -85,10 +85,25 @@ function getSource (url, { format }) {
85
85
  }
86
86
  }
87
87
 
88
+ /**
89
+ * @typedef {[typeof LOAD_OPERATION, URL, object] | [typeof RESOLVE_OPERATION, string, object]} GetExportsOperation
90
+ */
91
+
92
+ /**
93
+ * @typedef {{ done: false, value: GetExportsOperation } | { done: true, value: Set<string> }} GetExportsResult
94
+ */
95
+
96
+ /**
97
+ * @typedef {{
98
+ * next: (value?: unknown) => GetExportsResult,
99
+ * throw: (error?: unknown) => GetExportsResult,
100
+ * }} GetExportsGenerator
101
+ */
102
+
88
103
  /**
89
104
  * Drives the generator returned by import-in-the-middle >=3.1.0 export discovery.
90
105
  *
91
- * @param {Generator<Array, Set<string>>} exportsGenerator Generator returned by getExports
106
+ * @param {GetExportsGenerator} exportsGenerator Generator returned by getExports
92
107
  * @param {(url: URL, context: object) => { source: string, format: string }} getSource
93
108
  * Function that loads module source
94
109
  * @returns {Set<string>}
@@ -116,6 +116,66 @@ function wrapModelWithLifecycle (model) {
116
116
  }
117
117
  }
118
118
 
119
+ /**
120
+ * Wraps an OTel span without changing its method receivers.
121
+ *
122
+ * OTel spans may use private fields, so methods must run against the original
123
+ * span. A per-invocation wrapper also preserves `ctx` without mutating the AI
124
+ * SDK's shared no-op span.
125
+ *
126
+ * @param {import('@opentelemetry/api').Span} span
127
+ * @param {object} ctx
128
+ * @returns {import('@opentelemetry/api').Span}
129
+ */
130
+ function createDelegatingSpan (span, ctx) {
131
+ return {
132
+ spanContext () {
133
+ return span.spanContext.apply(span, arguments)
134
+ },
135
+ setAttribute () {
136
+ span.setAttribute.apply(span, arguments)
137
+ return this
138
+ },
139
+ setAttributes (attributes) {
140
+ vercelAiSpanSetAttributesChannel.publish({ ctx, attributes })
141
+ span.setAttributes.apply(span, arguments)
142
+ return this
143
+ },
144
+ addEvent () {
145
+ span.addEvent.apply(span, arguments)
146
+ return this
147
+ },
148
+ addLink () {
149
+ span.addLink.apply(span, arguments)
150
+ return this
151
+ },
152
+ addLinks () {
153
+ span.addLinks.apply(span, arguments)
154
+ return this
155
+ },
156
+ setStatus () {
157
+ span.setStatus.apply(span, arguments)
158
+ return this
159
+ },
160
+ updateName () {
161
+ span.updateName.apply(span, arguments)
162
+ return this
163
+ },
164
+ isRecording () {
165
+ return span.isRecording.apply(span, arguments)
166
+ },
167
+ recordException (exception) {
168
+ ctx.error = exception
169
+ vercelAiTracingChannel.error.publish(ctx)
170
+ return span.recordException.apply(span, arguments)
171
+ },
172
+ end () {
173
+ vercelAiTracingChannel.asyncEnd.publish(ctx)
174
+ return span.end.apply(span, arguments)
175
+ },
176
+ }
177
+ }
178
+
119
179
  function wrapTracer (tracer) {
120
180
  if (tracers.has(tracer)) {
121
181
  return
@@ -136,40 +196,7 @@ function wrapTracer (tracer) {
136
196
 
137
197
  args[args.length - 1] = shimmer.wrapFunction(cb, function (originalCb) {
138
198
  return function (span) {
139
- // A plain delegating wrapper is used instead of Object.create(span) because
140
- // Object.create (and Proxy) cannot cross private-field brand checks — any span
141
- // method that reads a private field (e.g. BridgeSpanBase#statusCode) would throw
142
- // "Cannot read private member" on a prototype clone. Every method here calls
143
- // through to the real span instance so private fields always resolve correctly.
144
- const freshSpan = {
145
- spanContext () { return span.spanContext() },
146
- setAttribute (key, value) { span.setAttribute(key, value); return freshSpan },
147
- setAttributes (attributes) {
148
- vercelAiSpanSetAttributesChannel.publish({ ctx, attributes })
149
- span.setAttributes(attributes)
150
- return freshSpan
151
- },
152
- addEvent (name, attributesOrStartTime, startTime) {
153
- span.addEvent(name, attributesOrStartTime, startTime)
154
- return freshSpan
155
- },
156
- addLink (link, attrs) { span.addLink(link, attrs); return freshSpan },
157
- addLinks (links) { span.addLinks(links); return freshSpan },
158
- setStatus (status) { span.setStatus(status); return freshSpan },
159
- updateName (spanName) { span.updateName(spanName); return freshSpan },
160
- isRecording () { return span.isRecording() },
161
- recordException (exception, timeInput) {
162
- ctx.error = exception
163
- vercelAiTracingChannel.error.publish(ctx)
164
- span.recordException(exception, timeInput)
165
- },
166
- end (...endArgs) {
167
- vercelAiTracingChannel.asyncEnd.publish(ctx)
168
- span.end(...endArgs)
169
- },
170
- }
171
-
172
- return originalCb.call(this, freshSpan)
199
+ return originalCb.call(this, createDelegatingSpan(span, ctx))
173
200
  }
174
201
  })
175
202
 
@@ -15,12 +15,12 @@ function createGetNewLibraryCopyWrap (originalLib) {
15
15
  }
16
16
  }
17
17
 
18
- addHook({ name: 'bluebird', versions: ['>=2.0.2'] }, Promise => {
19
- shimmer.wrap(Promise.prototype, '_then', wrapThen)
20
- return Promise
18
+ addHook({ name: 'bluebird', versions: ['>=2.0.2'] }, LibraryPromise => {
19
+ shimmer.wrap(LibraryPromise.prototype, '_then', wrapThen)
20
+ return LibraryPromise
21
21
  })
22
22
 
23
- addHook({ name: 'bluebird', versions: ['^2.11.0', '^3.4.1'] }, Promise => {
24
- shimmer.wrap(Promise, 'getNewLibraryCopy', createGetNewLibraryCopyWrap(Promise))
25
- return Promise
23
+ addHook({ name: 'bluebird', versions: ['^2.11.0', '^3.4.1'] }, LibraryPromise => {
24
+ shimmer.wrap(LibraryPromise, 'getNewLibraryCopy', createGetNewLibraryCopyWrap(LibraryPromise))
25
+ return LibraryPromise
26
26
  })
@@ -13,6 +13,66 @@ const LOCAL_LIFECYCLE_LOOKAHEAD = 4
13
13
 
14
14
  const chunkEmitTimes = new WeakMap()
15
15
 
16
+ /**
17
+ * @typedef {{ session_id?: string } & Record<string, unknown>} Chunk
18
+ */
19
+
20
+ /**
21
+ * @typedef {{
22
+ * id: string,
23
+ * name?: string,
24
+ * input?: unknown,
25
+ * output?: unknown,
26
+ * error?: unknown,
27
+ * isInterrupt?: boolean,
28
+ * sessionId?: string,
29
+ * hookStartTime?: number,
30
+ * hookFinishTime?: number,
31
+ * }} ToolState
32
+ */
33
+
34
+ /**
35
+ * @typedef {{ taskStartedChunk?: Chunk, toolResultIndex?: number }} ToolLifecycle
36
+ */
37
+
38
+ /**
39
+ * @typedef {(startIndex: number, toolUseId: string) => ToolLifecycle} GetLifecycle
40
+ */
41
+
42
+ /**
43
+ * @typedef {{
44
+ * tools: Map<string, ToolState>,
45
+ * subagents: Map<string, Record<string, unknown>>,
46
+ * prompt?: string,
47
+ * model?: string,
48
+ * resume?: string,
49
+ * maxTurns?: number,
50
+ * permissionMode?: string,
51
+ * sessionId?: string,
52
+ * source?: string,
53
+ * cwd?: string,
54
+ * transcriptPath?: string,
55
+ * agentType?: string,
56
+ * endReason?: string,
57
+ * stopReason?: string,
58
+ * lastAssistantMessage?: string,
59
+ * }} SessionContext
60
+ */
61
+
62
+ /**
63
+ * @typedef {{
64
+ * stepIndex: number,
65
+ * startTime?: number,
66
+ * finishTime?: number,
67
+ * parentToolUseId?: string,
68
+ * sessionId?: string,
69
+ * chunks?: Chunk[],
70
+ * llmStartIdx?: number,
71
+ * llmEndIdx?: number,
72
+ * toolOutputs?: unknown[],
73
+ * }} StepContext
74
+ */
75
+
16
76
  function hasDownstreamSubscribers () {
17
77
  return queryChannel.asyncEnd.hasSubscribers ||
18
78
  queryChannel.error.hasSubscribers ||
@@ -287,10 +347,12 @@ function getToolResultContent (chunk, toolUseId) {
287
347
  }
288
348
 
289
349
  /**
290
- *
291
- * @param {Array<Record<string, unknown>>} chunks
350
+ * @param {Chunk[]} chunks
292
351
  * @param {number} startIndex
293
352
  * @param {string} toolUseId
353
+ * @param {SessionContext | null | undefined} sessionCtx
354
+ * @param {GetLifecycle} getLifecycle
355
+ * @param {ToolLifecycle} lifecycle
294
356
  * @returns {number} index in chunks where the next step should start iteration
295
357
  */
296
358
  function processTool (chunks, startIndex, toolUseId, sessionCtx, getLifecycle, lifecycle) {
@@ -342,9 +404,14 @@ function processTool (chunks, startIndex, toolUseId, sessionCtx, getLifecycle, l
342
404
  }
343
405
 
344
406
  /**
345
- *
346
- * @param {Array<Record<string, unknown>>} chunks
407
+ * @param {Chunk[]} chunks
347
408
  * @param {number} startIndex
409
+ * @param {number | undefined} stepStartTime
410
+ * @param {string | null | undefined} parentToolUseId
411
+ * @param {string | undefined} initialPrompt
412
+ * @param {StepContext | null | undefined} stepCtx
413
+ * @param {SessionContext | null | undefined} sessionCtx
414
+ * @param {GetLifecycle} getLifecycle
348
415
  * @returns {number} index in chunks where the next step should start iteration
349
416
  */
350
417
  function processStep (
@@ -447,7 +514,16 @@ function processStep (
447
514
  }
448
515
 
449
516
  /**
450
- * @param {Array<Record<string, unknown>>} chunks
517
+ * @param {Chunk[]} chunks
518
+ * @param {{
519
+ * sessionCtx?: SessionContext,
520
+ * session_id?: string,
521
+ * cwd?: string,
522
+ * permissionMode?: string,
523
+ * output?: unknown,
524
+ * arguments?: Array<{ prompt?: string }>,
525
+ * runInContext?: (fn: () => number) => number,
526
+ * }} agentCtx
451
527
  */
452
528
  function processChunks (chunks, agentCtx) {
453
529
  let chunkIndex = 0
@@ -29,6 +29,17 @@ const channels = {
29
29
 
30
30
  const disabledHeaderWeakSet = new WeakSet()
31
31
 
32
+ const EMPTY_CARRIER = Object.create(null)
33
+ /** @type {string[]} */
34
+ const NO_MERGEABLE_KEYS = []
35
+
36
+ /** @typedef {string | Buffer} NativeHeaderValue */
37
+ /** @typedef {Array<Record<string, NativeHeaderValue>>} NativeHeaders */
38
+ /** @typedef {Record<string, NativeHeaderValue | NativeHeaderValue[]>} NativeHeaderCarrier */
39
+
40
+ /** @type {NativeHeaders} */
41
+ const EMPTY_NATIVE_HEADERS = []
42
+
32
43
  // we need to store the offset per partition per topic for the consumer to track offsets for DSM
33
44
  const latestConsumerOffsets = new Map()
34
45
 
@@ -60,17 +71,47 @@ function instrumentBaseModule (module) {
60
71
  }
61
72
 
62
73
  const brokers = this.globalConfig?.['bootstrap.servers']
74
+ const nativeHeaders = Array.isArray(headers) ? headers : EMPTY_NATIVE_HEADERS
75
+
76
+ let carrier
77
+ let forwardCallerHeaders = false
78
+ let applicationValues = EMPTY_CARRIER
79
+ let headerKeys = NO_MERGEABLE_KEYS
80
+ if (nativeHeaders.length !== 0) {
81
+ // `__proto__` is a legal Kafka header key and propagation never
82
+ // writes it, so only the seeded carrier needs a null prototype.
83
+ const candidateCarrier = Object.create(null)
84
+ const candidateValues = Object.create(null)
85
+ const candidateKeys = seedNativeHeaderCarrier(nativeHeaders, candidateCarrier, candidateValues)
86
+ if (candidateKeys === undefined) {
87
+ forwardCallerHeaders = true
88
+ } else if (candidateKeys.length !== 0) {
89
+ carrier = candidateCarrier
90
+ applicationValues = candidateValues
91
+ headerKeys = candidateKeys
92
+ }
93
+ }
63
94
 
64
95
  const ctx = {
65
96
  topic,
66
- messages: [{ key, value: message }],
97
+ messages: [{
98
+ key,
99
+ value: message,
100
+ headers: carrier,
101
+ }],
67
102
  bootstrapServers: brokers,
68
103
  }
69
104
 
70
105
  return channels.producerStart.runStores(ctx, () => {
71
106
  try {
72
- const headers = convertHeaders(ctx.messages[0].headers)
73
- const result = produce.apply(this, [topic, partition, message, key, timestamp, opaque, headers])
107
+ const producedHeaders = forwardCallerHeaders
108
+ ? nativeHeaders
109
+ : mergeNativeHeaders(
110
+ nativeHeaders, headerKeys, applicationValues, ctx.messages[0].headers ?? EMPTY_CARRIER
111
+ )
112
+ const result = produce.apply(this, [
113
+ topic, partition, message, key, timestamp, opaque, producedHeaders,
114
+ ])
74
115
 
75
116
  ctx.result = result
76
117
  channels.producerCommit.publish(ctx)
@@ -432,7 +473,71 @@ function getLatestOffsets () {
432
473
  return [...latestConsumerOffsets.values()]
433
474
  }
434
475
 
435
- function convertHeaders (headers) {
436
- // convert headers from object to array of objects with 1 key and value per array entry
437
- return Object.entries(headers).map(([key, value]) => ({ [key.toString()]: value.toString() }))
476
+ /**
477
+ * `Producer::NodeProduce` reads the first *enumerable* own key of each entry and
478
+ * validates the value itself. A `null` or `undefined` entry aborts the process
479
+ * there, so those are the only entries we refuse to hand back to it.
480
+ *
481
+ * @param {NativeHeaders} headers Caller-owned.
482
+ * @param {NativeHeaderCarrier} carrier
483
+ * @param {NativeHeaderCarrier} applicationValues
484
+ * @returns {string[] | undefined} `undefined` when the binding has to reject the
485
+ * array itself; empty when it cannot consume the array at all.
486
+ * @see https://github.com/confluentinc/confluent-kafka-javascript/blob/v1.9.1/src/producer.cc
487
+ */
488
+ function seedNativeHeaderCarrier (headers, carrier, applicationValues) {
489
+ const keys = new Array(headers.length)
490
+ for (let i = 0; i < headers.length; i++) {
491
+ const header = headers[i]
492
+ if (header === null || header === undefined) return NO_MERGEABLE_KEYS
493
+
494
+ const key = Object.keys(header)[0]
495
+ if (key === undefined) return
496
+
497
+ keys[i] = key
498
+ const value = header[key]
499
+ if (typeof value !== 'string' && !Buffer.isBuffer(value)) return
500
+
501
+ if (i === 0) {
502
+ carrier[key] = value
503
+ applicationValues[key] = value
504
+ continue
505
+ }
506
+
507
+ const previousValue = carrier[key]
508
+ if (previousValue === undefined) {
509
+ carrier[key] = value
510
+ } else if (Array.isArray(previousValue)) {
511
+ previousValue.push(value)
512
+ } else {
513
+ carrier[key] = [previousValue, value]
514
+ }
515
+ applicationValues[key] = carrier[key]
516
+ }
517
+ return keys
518
+ }
519
+
520
+ /**
521
+ * @param {NativeHeaders} headers Caller-owned; surviving entries are forwarded
522
+ * by reference, never copied or mutated.
523
+ * @param {string[]} headerKeys Parallel to `headers`.
524
+ * @param {NativeHeaderCarrier} applicationValues Carrier contents before
525
+ * propagation ran.
526
+ * @param {NativeHeaderCarrier} carrier
527
+ */
528
+ function mergeNativeHeaders (headers, headerKeys, applicationValues, carrier) {
529
+ const merged = []
530
+ for (let i = 0; i < headerKeys.length; i++) {
531
+ const key = headerKeys[i]
532
+ if (carrier[key] === applicationValues[key]) {
533
+ merged.push(headers[i])
534
+ }
535
+ }
536
+
537
+ for (const key of Object.keys(carrier)) {
538
+ if (carrier[key] !== applicationValues[key]) {
539
+ merged.push({ [key]: carrier[key] })
540
+ }
541
+ }
542
+ return merged
438
543
  }