dd-trace 5.108.0 → 5.110.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 (174) hide show
  1. package/ci/init.js +4 -3
  2. package/index.d.ts +88 -25
  3. package/loader-hook.mjs +88 -2
  4. package/package.json +19 -14
  5. package/packages/datadog-esbuild/index.js +26 -0
  6. package/packages/datadog-esbuild/src/utils.js +46 -3
  7. package/packages/datadog-instrumentations/src/ai.js +43 -48
  8. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js-context-methods.js +18 -0
  9. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +111 -0
  10. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/electron.js +1 -1
  13. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  14. package/packages/datadog-instrumentations/src/graphql.js +17 -6
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  18. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  20. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/aws-durable-execution-sdk-js.js +31 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  24. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  25. package/packages/datadog-instrumentations/src/ioredis.js +0 -1
  26. package/packages/datadog-instrumentations/src/iovalkey.js +1 -2
  27. package/packages/datadog-instrumentations/src/koa.js +3 -2
  28. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  29. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  30. package/packages/datadog-instrumentations/src/next.js +44 -10
  31. package/packages/datadog-instrumentations/src/openai.js +77 -18
  32. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  33. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  34. package/packages/datadog-instrumentations/src/redis.js +0 -1
  35. package/packages/datadog-instrumentations/src/restify.js +4 -3
  36. package/packages/datadog-instrumentations/src/router.js +18 -5
  37. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  38. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  39. package/packages/datadog-instrumentations/src/vitest.js +323 -7
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/checkpoint.js +31 -0
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +55 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +114 -0
  44. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +128 -0
  45. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/index.js +19 -0
  46. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +224 -0
  47. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +43 -0
  48. package/packages/datadog-plugin-aws-sdk/src/base.js +14 -16
  49. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  50. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +100 -37
  51. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +108 -99
  52. package/packages/datadog-plugin-bullmq/src/filter.js +35 -0
  53. package/packages/datadog-plugin-bullmq/src/producer.js +84 -4
  54. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
  55. package/packages/datadog-plugin-fs/src/index.js +1 -0
  56. package/packages/datadog-plugin-http/src/client.js +8 -3
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  59. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  60. package/packages/datadog-plugin-redis/src/index.js +1 -2
  61. package/packages/datadog-plugin-undici/src/index.js +5 -2
  62. package/packages/datadog-plugin-vitest/src/index.js +11 -2
  63. package/packages/datadog-webpack/index.js +17 -1
  64. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  65. package/packages/dd-trace/src/aiguard/channels.js +0 -1
  66. package/packages/dd-trace/src/aiguard/index.js +11 -49
  67. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +46 -0
  68. package/packages/dd-trace/src/aiguard/integrations/openai.js +66 -0
  69. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +78 -0
  70. package/packages/{datadog-instrumentations/src/helpers/ai-messages.js → dd-trace/src/aiguard/messages/openai.js} +85 -193
  71. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +185 -0
  72. package/packages/dd-trace/src/aiguard/sdk.js +13 -1
  73. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  74. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  75. package/packages/dd-trace/src/appsec/channels.js +1 -0
  76. package/packages/dd-trace/src/appsec/downstream_requests.js +111 -58
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
  78. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
  79. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
  80. package/packages/dd-trace/src/appsec/index.js +10 -6
  81. package/packages/dd-trace/src/appsec/rasp/ssrf.js +19 -11
  82. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  83. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  84. package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
  85. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  86. package/packages/dd-trace/src/config/defaults.js +22 -3
  87. package/packages/dd-trace/src/config/generated-config-types.d.ts +538 -1
  88. package/packages/dd-trace/src/config/helper.js +133 -33
  89. package/packages/dd-trace/src/config/index.js +4 -1
  90. package/packages/dd-trace/src/config/parsers.js +19 -1
  91. package/packages/dd-trace/src/config/supported-configurations.json +45 -5
  92. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +49 -35
  93. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  94. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  95. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  96. package/packages/dd-trace/src/debugger/index.js +5 -0
  97. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  98. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  99. package/packages/dd-trace/src/dogstatsd.js +15 -8
  100. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  101. package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
  102. package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
  103. package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
  104. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  105. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  106. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  107. package/packages/dd-trace/src/guardrails/index.js +8 -5
  108. package/packages/dd-trace/src/index.js +6 -4
  109. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  110. package/packages/dd-trace/src/llmobs/index.js +16 -5
  111. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  112. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  113. package/packages/dd-trace/src/llmobs/sdk.js +5 -3
  114. package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
  115. package/packages/dd-trace/src/llmobs/tagger.js +55 -0
  116. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  117. package/packages/dd-trace/src/log/index.js +18 -20
  118. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -1
  119. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  120. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  121. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  122. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  123. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  124. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  125. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  126. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  127. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  128. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
  129. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  130. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  131. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  132. package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
  133. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  134. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  135. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  136. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  137. package/packages/dd-trace/src/plugin_manager.js +7 -1
  138. package/packages/dd-trace/src/plugins/ci_plugin.js +36 -3
  139. package/packages/dd-trace/src/plugins/index.js +3 -0
  140. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  141. package/packages/dd-trace/src/plugins/util/test.js +80 -0
  142. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  143. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  144. package/packages/dd-trace/src/priority_sampler.js +1 -13
  145. package/packages/dd-trace/src/profiling/config.js +129 -198
  146. package/packages/dd-trace/src/profiling/constants.js +4 -7
  147. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  148. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  149. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  150. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  151. package/packages/dd-trace/src/profiling/oom.js +71 -0
  152. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  153. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  154. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  155. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  156. package/packages/dd-trace/src/ritm.js +4 -2
  157. package/packages/dd-trace/src/serverless.js +1 -2
  158. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  159. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  160. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  161. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  162. package/packages/dd-trace/src/span_format.js +2 -1
  163. package/packages/dd-trace/src/span_processor.js +6 -0
  164. package/packages/dd-trace/src/telemetry/send-data.js +12 -10
  165. package/packages/dd-trace/src/tracer.js +15 -1
  166. package/packages/dd-trace/src/util.js +14 -0
  167. package/register.js +58 -1
  168. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  169. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  170. package/vendor/dist/pprof-format/index.js +1 -1
  171. package/vendor/dist/protobufjs/index.js +1 -1
  172. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  173. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
  174. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -8,6 +8,7 @@ const tags = require('../../../ext/tags')
8
8
  const formats = require('../../../ext/formats')
9
9
  const HTTP_HEADERS = formats.HTTP_HEADERS
10
10
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
11
+ const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
11
12
  const log = require('../../dd-trace/src/log')
12
13
  const { CLIENT_PORT_KEY, COMPONENT, ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants')
13
14
 
@@ -27,13 +28,17 @@ class HttpClientPlugin extends ClientPlugin {
27
28
  const protocol = options.protocol || agent.protocol || 'http:'
28
29
  const hostname = options.hostname || options.host || 'localhost'
29
30
  const host = options.port ? `${hostname}:${options.port}` : hostname
30
- const pathname = options.path || options.pathname
31
+ const base = `${protocol}//${host}`
32
+ // A URL object (e.g. from the fetch integration) carries the query in
33
+ // `options.search`, not `options.path`; keep it so url.full retains the query.
34
+ const pathname = options.path || `${options.pathname || ''}${options.search || ''}`
31
35
  const path = pathname ? pathname.split(/[?#]/)[0] : '/'
32
- const uri = `${protocol}//${host}${path}`
36
+ const uri = `${base}${path}`
33
37
 
34
38
  const allowed = this.config.filter(uri)
35
39
 
36
40
  const method = (options.method || 'GET').toUpperCase()
41
+ const otelSemantics = this.config.DD_TRACE_OTEL_SEMANTICS_ENABLED
37
42
  const childOf = store && allowed ? store.span : null
38
43
  // TODO delegate to super.startspan
39
44
  const span = this.startSpan(this.operationName(), {
@@ -46,7 +51,7 @@ class HttpClientPlugin extends ClientPlugin {
46
51
  'resource.name': method,
47
52
  'span.type': 'http',
48
53
  'http.method': method,
49
- 'http.url': uri,
54
+ 'http.url': otelSemantics ? buildClientHttpUrl(this.config, base, pathname, uri) : uri,
50
55
  'out.host': hostname,
51
56
  },
52
57
  metrics: {
@@ -10,6 +10,7 @@ const kinds = require('../../../ext/kinds')
10
10
  const formats = require('../../../ext/formats')
11
11
  const { COMPONENT, CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
12
12
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
13
+ const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
13
14
 
14
15
  const HTTP_HEADERS = formats.HTTP_HEADERS
15
16
  const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE
@@ -33,8 +34,10 @@ class Http2ClientPlugin extends ClientPlugin {
33
34
  const path = headers[HTTP2_HEADER_PATH] || '/'
34
35
  const pathname = path.split(/[?#]/)[0]
35
36
  const method = headers[HTTP2_HEADER_METHOD] || HTTP2_METHOD_GET
36
- const uri = `${sessionDetails.protocol}//${sessionDetails.host}:${sessionDetails.port}${pathname}`
37
+ const base = `${sessionDetails.protocol}//${sessionDetails.host}:${sessionDetails.port}`
38
+ const uri = `${base}${pathname}`
37
39
  const allowed = this.config.filter(uri)
40
+ const otelSemantics = this.config.DD_TRACE_OTEL_SEMANTICS_ENABLED
38
41
 
39
42
  const store = storage('legacy').getStore()
40
43
  const childOf = store && allowed ? store.span : null
@@ -48,7 +51,7 @@ class Http2ClientPlugin extends ClientPlugin {
48
51
  'resource.name': method,
49
52
  'span.type': 'http',
50
53
  'http.method': method,
51
- 'http.url': uri,
54
+ 'http.url': otelSemantics ? buildClientHttpUrl(this.config, base, path, uri) : uri,
52
55
  'out.host': sessionDetails.host,
53
56
  },
54
57
  metrics: {
@@ -11,11 +11,16 @@ class MySQLPlugin extends DatabasePlugin {
11
11
  constructor () {
12
12
  super(...arguments)
13
13
 
14
+ // Capture into `currentStore` (not `parentStore`) so connection:finish can
15
+ // restore the caller context even when the connection resolves inside an
16
+ // instrumentation skip (a noop store), as the mariadb pool does: the store
17
+ // binding only honors an explicit `currentStore` through a noop store.
18
+ // Without a skip (mysql/mysql2) this is unchanged.
14
19
  this.addSub(`apm:${this.component}:connection:start`, ctx => {
15
- ctx.parentStore = storage('legacy').getStore()
20
+ ctx.currentStore = storage('legacy').getStore()
16
21
  })
17
22
 
18
- this.addBind(`apm:${this.component}:connection:finish`, ctx => ctx.parentStore)
23
+ this.addBind(`apm:${this.component}:connection:finish`, ctx => ctx.currentStore)
19
24
  }
20
25
 
21
26
  bindStart (ctx) {
@@ -47,10 +47,10 @@ class SchemaExtractor {
47
47
 
48
48
  static extractProperty (field, schemaName, fieldName, builder, depth) {
49
49
  let array = false
50
- let description
51
50
  let ref
52
51
  let enumValues
53
52
 
53
+ const description = field.comment
54
54
  const resolvedType = field.resolvedType ? field.resolvedType.constructor.name : field.type
55
55
 
56
56
  const isRepeatedField = field.rule === 'repeated'
@@ -124,7 +124,7 @@ class SchemaExtractor {
124
124
  }
125
125
 
126
126
  iterateOverSchema (builder) {
127
- this.constructor.extractSchema(this.schema, builder, 0)
127
+ SchemaExtractor.extractSchema(this.schema, builder, 0)
128
128
  }
129
129
 
130
130
  static attachSchemaOnSpan (args, span, operation, tracer) {
@@ -28,7 +28,7 @@ class RedisPlugin extends CachePlugin {
28
28
  }
29
29
 
30
30
  bindStart (ctx) {
31
- const { db, command, args, argsStartIndex, connectionOptions, connectionName } = ctx
31
+ const { command, args, argsStartIndex, connectionOptions, connectionName } = ctx
32
32
 
33
33
  const resource = command
34
34
  const normalizedCommand = command.toUpperCase()
@@ -55,7 +55,6 @@ class RedisPlugin extends CachePlugin {
55
55
  type: this._spanType,
56
56
  meta: {
57
57
  'db.type': this._spanType,
58
- 'db.name': db || '0',
59
58
  [this.#rawCommandKey]: formatCommand(normalizedCommand, args, argsStartIndex),
60
59
  'out.host': connectionOptions.host,
61
60
  [CLIENT_PORT_KEY]: connectionOptions.port,
@@ -6,6 +6,7 @@ const tags = require('../../../ext/tags')
6
6
  const formats = require('../../../ext/formats')
7
7
  const HTTP_HEADERS = formats.HTTP_HEADERS
8
8
  const log = require('../../dd-trace/src/log')
9
+ const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
9
10
  const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
10
11
 
11
12
  const {
@@ -60,10 +61,12 @@ class UndiciPlugin extends HttpClientPlugin {
60
61
  }
61
62
 
62
63
  const host = port ? `${hostname}:${port}` : hostname
64
+ const base = `${protocol}//${host}`
63
65
  const pathname = path.split(/[?#]/)[0]
64
- const uri = `${protocol}//${host}${pathname}`
66
+ const uri = `${base}${pathname}`
65
67
 
66
68
  const allowed = this.config.filter(uri)
69
+ const otelSemantics = this.config.DD_TRACE_OTEL_SEMANTICS_ENABLED
67
70
  const childOf = store && allowed ? store.span : null
68
71
 
69
72
  const span = this.startSpan(this.operationName(), {
@@ -71,7 +74,7 @@ class UndiciPlugin extends HttpClientPlugin {
71
74
  meta: {
72
75
  'span.kind': 'client',
73
76
  'http.method': method,
74
- 'http.url': uri,
77
+ 'http.url': otelSemantics ? buildClientHttpUrl(this.config, base, path, uri) : uri,
75
78
  'out.host': hostname,
76
79
  },
77
80
  metrics: {
@@ -9,6 +9,7 @@ const {
9
9
  finishAllTraceSpans,
10
10
  getTestSuitePath,
11
11
  getTestSuiteCommonTags,
12
+ getTestLevelsMetadataTags,
12
13
  getTestSessionName,
13
14
  getIsFaultyEarlyFlakeDetection,
14
15
  TEST_SOURCE_FILE,
@@ -16,6 +17,7 @@ const {
16
17
  TEST_CODE_COVERAGE_LINES_PCT,
17
18
  TEST_CODE_OWNERS,
18
19
  TEST_COMMAND,
20
+ TEST_LEVELS_METADATA,
19
21
  TEST_SESSION_NAME,
20
22
  TEST_SOURCE_START,
21
23
  TEST_IS_NEW,
@@ -63,6 +65,8 @@ class VitestPlugin extends CiPlugin {
63
65
  testSessionId: testSessionSpanContext?.toTraceId(),
64
66
  testModuleId: testModuleSpanContext?.toSpanId(),
65
67
  testCommand: this.command,
68
+ repositoryRoot: this.repositoryRoot,
69
+ codeOwnersEntries: this.codeOwnersEntries,
66
70
  })
67
71
  })
68
72
 
@@ -307,10 +311,11 @@ class VitestPlugin extends CiPlugin {
307
311
  })
308
312
 
309
313
  this.addBind('ci:vitest:test-suite:start', (ctx) => {
310
- const { testSuiteAbsolutePath, frameworkVersion } = ctx
314
+ const { codeOwnersEntries, repositoryRoot, testSuiteAbsolutePath, frameworkVersion } = ctx
311
315
 
312
316
  const testCommand = ctx.testCommand || 'vitest run'
313
317
  const { testSessionId, testModuleId } = ctx
318
+ this._setRepositoryRoot(repositoryRoot, codeOwnersEntries)
314
319
  this.command = testCommand
315
320
  this.frameworkVersion = frameworkVersion
316
321
  const testSessionSpanContext = testSessionId && testModuleId
@@ -325,7 +330,11 @@ class VitestPlugin extends CiPlugin {
325
330
  const testSessionName = getTestSessionName(this.config, trimmedCommand, this.testEnvironmentMetadata)
326
331
  if (this.tracer._exporter.addMetadataTags) {
327
332
  this.tracer._exporter.addMetadataTags({
328
- '*': { [TEST_COMMAND]: testCommand, [TEST_SESSION_NAME]: testSessionName },
333
+ [TEST_LEVELS_METADATA]: {
334
+ [TEST_COMMAND]: testCommand,
335
+ [TEST_SESSION_NAME]: testSessionName,
336
+ ...getTestLevelsMetadataTags(this.testEnvironmentMetadata),
337
+ },
329
338
  test: getLibraryCapabilitiesTags(this.constructor.id),
330
339
  })
331
340
  }
@@ -6,6 +6,7 @@ const fs = require('node:fs')
6
6
  const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
7
7
  const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
8
8
  const hooks = require('../datadog-instrumentations/src/helpers/hooks')
9
+ const { matchesOptionalPeerFile } = require('../datadog-instrumentations/src/helpers/optional-peer-bundler')
9
10
  const { isESMFile } = require('../datadog-esbuild/src/utils')
10
11
  const log = require('./src/log')
11
12
 
@@ -130,11 +131,26 @@ class DatadogWebpackPlugin {
130
131
  nmf.hooks.afterResolve.tap(PLUGIN_NAME, (resolveData) => {
131
132
  const { createData } = resolveData
132
133
  const resource = createData?.resource
133
- if (!resource || !resource.includes('node_modules')) {
134
+ if (!resource) {
134
135
  return
135
136
  }
136
137
 
137
138
  const normalizedResource = resource.replaceAll('\\', '/')
139
+
140
+ // Rewrite optional-peer loads so installed peers get bundled and survive relocation
141
+ // (#8980); absent peers stay opaque, so a build that does not opt into the feature does
142
+ // not follow their dependency chain (#8635).
143
+ if (matchesOptionalPeerFile(normalizedResource)) {
144
+ createData.loaders = createData.loaders || []
145
+ createData.loaders.push({ loader: require.resolve('./src/optional-peer-loader') })
146
+ log.debug('INLINE: optional-peer loader applied to %s', normalizedResource)
147
+ return
148
+ }
149
+
150
+ if (!resource.includes('node_modules')) {
151
+ return
152
+ }
153
+
138
154
  const { pkg, path: modulePath, pkgJson } = extractPackageAndModulePath(normalizedResource)
139
155
  if (!pkg) {
140
156
  return
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ const { rewriteOptionalPeerLoads } = require('../../datadog-instrumentations/src/helpers/optional-peer-bundler')
4
+
5
+ /**
6
+ * Webpack loader applied to the optional-peer loader files. Rewrites each
7
+ * `requireOptionalPeer('name')` whose peer is installed at build time into a literal
8
+ * `require('name')` so webpack bundles the peer (#8980). Peers that are absent stay opaque, so
9
+ * builds that do not opt into the feature keep the #8635 guarantee.
10
+ *
11
+ * @param {string} source
12
+ * @returns {string}
13
+ */
14
+ module.exports = function optionalPeerLoader (source) {
15
+ this.cacheable(false)
16
+ return rewriteOptionalPeerLoads(source, this.context)
17
+ }
@@ -3,6 +3,5 @@
3
3
  const dc = require('dc-polyfill')
4
4
 
5
5
  module.exports = {
6
- aiguardChannel: dc.channel('dd-trace:ai:aiguard'),
7
6
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
8
7
  }
@@ -1,13 +1,16 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../log')
4
- const { incomingHttpRequestStart, aiguardChannel } = require('./channels')
4
+ const { incomingHttpRequestStart } = require('./channels')
5
+ const openaiIntegration = require('./integrations/openai')
6
+ const vercelAiIntegration = require('./integrations/vercel-ai')
5
7
  const AIGuard = require('./sdk')
6
- const { SOURCE_AUTO, INTEGRATION_NONE } = require('./tags')
7
8
 
8
9
  let isEnabled = false
9
10
  let aiguard
10
11
  let block
12
+ let disableOpenAIIntegration
13
+ let disableVercelAiIntegration
11
14
 
12
15
  function onIncomingHttpRequestStart () {
13
16
  // No-op: subscribing ensures the HTTP plugin spreads req onto the store
@@ -21,7 +24,8 @@ function enable (tracer, config) {
21
24
  block = config.experimental?.aiguard?.block !== false
22
25
 
23
26
  incomingHttpRequestStart.subscribe(onIncomingHttpRequestStart)
24
- aiguardChannel.subscribe(onEvaluate)
27
+ disableOpenAIIntegration = openaiIntegration.enable(aiguard, block)
28
+ disableVercelAiIntegration = vercelAiIntegration.enable(aiguard, block)
25
29
 
26
30
  isEnabled = true
27
31
  } catch (err) {
@@ -34,56 +38,14 @@ function disable () {
34
38
  if (!isEnabled) return
35
39
 
36
40
  incomingHttpRequestStart.unsubscribe(onIncomingHttpRequestStart)
37
- aiguardChannel.unsubscribe(onEvaluate)
41
+ disableOpenAIIntegration?.()
42
+ disableVercelAiIntegration?.()
38
43
 
39
44
  aiguard = undefined
40
45
  isEnabled = false
41
46
  block = false
42
- }
43
-
44
- /**
45
- * Handles channel messages with pre-converted messages.
46
- *
47
- * @param {object} ctx
48
- * @param {Array<object>} ctx.messages
49
- * @param {string} [ctx.integration]
50
- * @param {object} [ctx.parentSpan] - LLM span to parent the `ai_guard` span under.
51
- * @param {AbortController} ctx.abortController
52
- * @param {Array<Promise<void>>} ctx.pending - Subscribers push only when they evaluate.
53
- */
54
- function onEvaluate (ctx) {
55
- // Decline to evaluate empty payloads by not pushing to pending.
56
- if (!ctx.messages?.length) {
57
- return
58
- }
59
-
60
- const opts = {
61
- block,
62
- source: SOURCE_AUTO,
63
- integration: ctx.integration || INTEGRATION_NONE,
64
- childOf: ctx.parentSpan,
65
- }
66
-
67
- try {
68
- ctx.pending.push(aiguard.evaluate(ctx.messages, opts).catch(handleEvaluationError.bind(null, ctx)))
69
- } catch (err) {
70
- ctx.pending.push(Promise.resolve().then(() => handleEvaluationError(ctx, err)))
71
- }
72
- }
73
-
74
- /**
75
- * Handles an AI Guard evaluation failure.
76
- *
77
- * @param {object} ctx
78
- * @param {AbortController} ctx.abortController
79
- * @param {Error} err
80
- */
81
- function handleEvaluationError (ctx, err) {
82
- if (err.name === 'AIGuardAbortError') {
83
- ctx.abortController.abort(err)
84
- } else {
85
- log.error('AIGuard: unexpected error during evaluation: %s', err.message)
86
- }
47
+ disableOpenAIIntegration = undefined
48
+ disableVercelAiIntegration = undefined
87
49
  }
88
50
 
89
51
  module.exports = { enable, disable }
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+
5
+ /**
6
+ * Starts one AI Guard evaluation for a lifecycle ctx.
7
+ *
8
+ * Async evaluations are pushed synchronously during channel publication. If
9
+ * evaluation throws synchronously, the error is handled before publish returns.
10
+ *
11
+ * @param {object} ctx
12
+ * @param {AbortController} ctx.abortController
13
+ * @param {object} [ctx.parentSpan]
14
+ * @param {Array<Promise<void>>} ctx.pending
15
+ * @param {object} aiguard
16
+ * @param {Array<object>|undefined} messages
17
+ * @param {object} opts
18
+ */
19
+ function pushEvaluation (ctx, aiguard, messages, opts) {
20
+ if (!messages?.length) return
21
+
22
+ const evaluateOpts = ctx.parentSpan ? { ...opts, childOf: ctx.parentSpan } : opts
23
+
24
+ try {
25
+ ctx.pending.push(aiguard.evaluate(messages, evaluateOpts).catch(handleEvaluationError.bind(null, ctx)))
26
+ } catch (err) {
27
+ handleEvaluationError(ctx, err)
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Handles an AI Guard evaluation failure.
33
+ *
34
+ * @param {object} ctx
35
+ * @param {AbortController} ctx.abortController
36
+ * @param {Error} err
37
+ */
38
+ function handleEvaluationError (ctx, err) {
39
+ if (err.name === 'AIGuardAbortError') {
40
+ ctx.abortController.abort(err)
41
+ } else {
42
+ log.error('AIGuard: unexpected error during evaluation: %s', err.message)
43
+ }
44
+ }
45
+
46
+ module.exports = { pushEvaluation }
@@ -0,0 +1,66 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+
5
+ const {
6
+ getChatCompletionsInputMessages,
7
+ getChatCompletionsOutputMessages,
8
+ getResponsesInputMessages,
9
+ getResponsesOutputMessages,
10
+ } = require('../messages/openai')
11
+ const { SOURCE_AUTO } = require('../tags')
12
+ const { pushEvaluation } = require('./evaluate')
13
+
14
+ const chatCompletionsBeforeChannel = channel('dd-trace:openai:chat.completions:before')
15
+ const chatCompletionsAfterChannel = channel('dd-trace:openai:chat.completions:after')
16
+ const responsesBeforeChannel = channel('dd-trace:openai:responses:before')
17
+ const responsesAfterChannel = channel('dd-trace:openai:responses:after')
18
+
19
+ /**
20
+ * Subscribes AI Guard to OpenAI lifecycle channels.
21
+ *
22
+ * @param {object} aiguard
23
+ * @param {boolean} block
24
+ * @returns {() => void}
25
+ */
26
+ function enable (aiguard, block) {
27
+ const opts = { block, source: SOURCE_AUTO, integration: 'openai' }
28
+
29
+ function onChatCompletionsBefore (ctx) {
30
+ pushEvaluation(ctx, aiguard, getChatCompletionsInputMessages(ctx.args?.[0]), opts)
31
+ }
32
+
33
+ function onChatCompletionsAfter (ctx) {
34
+ const inputMessages = getChatCompletionsInputMessages(ctx.args?.[0])
35
+ if (!inputMessages?.length) return
36
+ for (const message of getChatCompletionsOutputMessages(ctx.body)) {
37
+ pushEvaluation(ctx, aiguard, [...inputMessages, message], opts)
38
+ }
39
+ }
40
+
41
+ function onResponsesBefore (ctx) {
42
+ pushEvaluation(ctx, aiguard, getResponsesInputMessages(ctx.args?.[0]), opts)
43
+ }
44
+
45
+ function onResponsesAfter (ctx) {
46
+ const inputMessages = getResponsesInputMessages(ctx.args?.[0])
47
+ if (!inputMessages?.length) return
48
+ const outputMessages = getResponsesOutputMessages(ctx.body)
49
+ if (!outputMessages.length) return
50
+ pushEvaluation(ctx, aiguard, [...inputMessages, ...outputMessages], opts)
51
+ }
52
+
53
+ chatCompletionsBeforeChannel.subscribe(onChatCompletionsBefore)
54
+ chatCompletionsAfterChannel.subscribe(onChatCompletionsAfter)
55
+ responsesBeforeChannel.subscribe(onResponsesBefore)
56
+ responsesAfterChannel.subscribe(onResponsesAfter)
57
+
58
+ return function disable () {
59
+ chatCompletionsBeforeChannel.unsubscribe(onChatCompletionsBefore)
60
+ chatCompletionsAfterChannel.unsubscribe(onChatCompletionsAfter)
61
+ responsesBeforeChannel.unsubscribe(onResponsesBefore)
62
+ responsesAfterChannel.unsubscribe(onResponsesAfter)
63
+ }
64
+ }
65
+
66
+ module.exports = { enable }
@@ -0,0 +1,78 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+
5
+ const { buildOutputMessages, convertVercelPromptToMessages } = require('../messages/vercel-ai')
6
+ const { SOURCE_AUTO } = require('../tags')
7
+ const { pushEvaluation } = require('./evaluate')
8
+
9
+ const doGenerateBeforeChannel = channel('dd-trace:vercel-ai:doGenerate:before')
10
+ const doGenerateAfterChannel = channel('dd-trace:vercel-ai:doGenerate:after')
11
+ const doStreamBeforeChannel = channel('dd-trace:vercel-ai:doStream:before')
12
+ const doStreamAfterChannel = channel('dd-trace:vercel-ai:doStream:after')
13
+
14
+ /**
15
+ * Subscribes AI Guard to Vercel AI lifecycle channels.
16
+ *
17
+ * @param {object} aiguard
18
+ * @param {boolean} block
19
+ * @returns {() => void}
20
+ */
21
+ function enable (aiguard, block) {
22
+ const opts = { block, source: SOURCE_AUTO, integration: 'ai' }
23
+
24
+ function onBefore (ctx) {
25
+ pushEvaluation(ctx, aiguard, convertVercelPromptToMessages(ctx.prompt), opts)
26
+ }
27
+
28
+ function onGenerateAfter (ctx) {
29
+ const inputMessages = convertVercelPromptToMessages(ctx.prompt)
30
+ if (!inputMessages.length || !ctx.result?.content?.length) return
31
+
32
+ pushEvaluation(ctx, aiguard, buildOutputMessages(inputMessages, ctx.result.content), opts)
33
+ }
34
+
35
+ function onStreamAfter (ctx) {
36
+ const inputMessages = convertVercelPromptToMessages(ctx.prompt)
37
+ if (!inputMessages.length || !ctx.chunks?.length) return
38
+
39
+ pushEvaluation(ctx, aiguard, buildOutputMessages(inputMessages, getStreamContent(ctx.chunks)), opts)
40
+ }
41
+
42
+ doGenerateBeforeChannel.subscribe(onBefore)
43
+ doGenerateAfterChannel.subscribe(onGenerateAfter)
44
+ doStreamBeforeChannel.subscribe(onBefore)
45
+ doStreamAfterChannel.subscribe(onStreamAfter)
46
+
47
+ return function disable () {
48
+ doGenerateBeforeChannel.unsubscribe(onBefore)
49
+ doGenerateAfterChannel.unsubscribe(onGenerateAfter)
50
+ doStreamBeforeChannel.unsubscribe(onBefore)
51
+ doStreamAfterChannel.unsubscribe(onStreamAfter)
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Converts Vercel stream chunks into the content shape used by doGenerate results.
57
+ *
58
+ * @param {Array<object>} chunks
59
+ * @returns {Array<object>}
60
+ */
61
+ function getStreamContent (chunks) {
62
+ const toolCalls = []
63
+ const textParts = []
64
+
65
+ for (const chunk of chunks) {
66
+ if (chunk?.type === 'tool-call') {
67
+ toolCalls.push(chunk)
68
+ } else if (chunk?.type === 'text-delta') {
69
+ textParts.push(chunk.textDelta)
70
+ }
71
+ }
72
+
73
+ if (toolCalls.length) return toolCalls
74
+ const text = textParts.join('')
75
+ return text ? [{ type: 'text', text }] : []
76
+ }
77
+
78
+ module.exports = { enable }