dd-trace 6.11.0 → 6.13.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 (139) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  3. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  4. package/index.d.ts +143 -25
  5. package/package.json +26 -25
  6. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  7. package/packages/datadog-instrumentations/src/cucumber.js +4 -4
  8. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  9. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  12. package/packages/datadog-instrumentations/src/jest.js +140 -18
  13. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  14. package/packages/datadog-instrumentations/src/mariadb.js +7 -0
  15. package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
  16. package/packages/datadog-instrumentations/src/pino.js +15 -4
  17. package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
  18. package/packages/datadog-instrumentations/src/playwright.js +67 -53
  19. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +13 -12
  20. package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
  21. package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
  22. package/packages/datadog-instrumentations/src/winston.js +6 -3
  23. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  24. package/packages/datadog-plugin-http/src/client.js +2 -15
  25. package/packages/datadog-plugin-http2/src/client.js +2 -15
  26. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  27. package/packages/datadog-plugin-playwright/src/index.js +13 -1
  28. package/packages/datadog-plugin-undici/src/index.js +2 -15
  29. package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
  30. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  31. package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
  32. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  33. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  34. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  35. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  37. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  38. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  39. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  40. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  41. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  42. package/packages/dd-trace/src/carrier.js +107 -48
  43. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  44. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
  46. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
  47. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
  49. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  50. package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
  51. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
  52. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
  53. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  54. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  55. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +4 -2
  56. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  57. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
  58. package/packages/dd-trace/src/config/index.js +2 -1
  59. package/packages/dd-trace/src/config/supported-configurations.json +27 -0
  60. package/packages/dd-trace/src/debugger/constants.js +1 -0
  61. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  62. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -10
  63. package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
  64. package/packages/dd-trace/src/debugger/index.js +4 -2
  65. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  66. package/packages/dd-trace/src/dogstatsd.js +45 -18
  67. package/packages/dd-trace/src/encode/agentless-json.js +5 -4
  68. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  69. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  70. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  71. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  72. package/packages/dd-trace/src/exporters/agent/index.js +22 -2
  73. package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
  74. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  75. package/packages/dd-trace/src/exporters/common/writer.js +24 -2
  76. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  77. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  78. package/packages/dd-trace/src/flush.js +102 -0
  79. package/packages/dd-trace/src/llmobs/experiments/client.js +10 -2
  80. package/packages/dd-trace/src/llmobs/experiments/dataset.js +301 -14
  81. package/packages/dd-trace/src/llmobs/experiments/experiment.js +440 -112
  82. package/packages/dd-trace/src/llmobs/experiments/index.js +68 -44
  83. package/packages/dd-trace/src/llmobs/experiments/noop.js +103 -12
  84. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  85. package/packages/dd-trace/src/llmobs/experiments/util.js +81 -4
  86. package/packages/dd-trace/src/llmobs/index.js +80 -17
  87. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  88. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  89. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  90. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  91. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  92. package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
  93. package/packages/dd-trace/src/noop/span.js +5 -0
  94. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  95. package/packages/dd-trace/src/openfeature/index.js +7 -4
  96. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  97. package/packages/dd-trace/src/openfeature/writers/exposures.js +70 -15
  98. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  99. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  100. package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
  101. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  102. package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
  103. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  104. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
  105. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
  106. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  107. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  108. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  109. package/packages/dd-trace/src/opentracing/propagation/text_map.js +429 -352
  110. package/packages/dd-trace/src/opentracing/span.js +28 -0
  111. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  112. package/packages/dd-trace/src/plugin_manager.js +2 -0
  113. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  114. package/packages/dd-trace/src/plugins/util/test.js +74 -21
  115. package/packages/dd-trace/src/plugins/util/web.js +2 -65
  116. package/packages/dd-trace/src/profiling/oom.js +4 -3
  117. package/packages/dd-trace/src/profiling/profiler.js +7 -0
  118. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  119. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  120. package/packages/dd-trace/src/proxy.js +36 -8
  121. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  122. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
  123. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
  124. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
  125. package/packages/dd-trace/src/serverless/vercel.js +122 -0
  126. package/packages/dd-trace/src/serverless.js +37 -20
  127. package/packages/dd-trace/src/span_stats.js +40 -3
  128. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  129. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  130. package/packages/dd-trace/src/tracer.js +22 -0
  131. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  132. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  133. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  134. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  135. package/vendor/dist/https-proxy-agent/index.js +4 -0
  136. package/vendor/dist/protobufjs/index.js +1 -1
  137. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  138. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  139. package/vendor/dist/proxy-from-env/index.js +1 -0
@@ -16,6 +16,7 @@ const {
16
16
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
17
17
  TEST_SUITE_EXECUTION_ID,
18
18
  } = require('../../dd-trace/src/plugins/util/test')
19
+ const { publishWithCompletion } = require('./helpers/channel')
19
20
  const { addHook, channel, tracingChannel } = require('./helpers/instrument')
20
21
  const {
21
22
  CONFIGURATION_REQUEST,
@@ -38,6 +39,7 @@ const testSuiteStartCh = channel('ci:mocha:test-suite:start')
38
39
  const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
39
40
  const knownTestsCh = channel('ci:mocha:known-tests')
40
41
  const libraryConfigurationCh = channel('ci:mocha:library-configuration')
42
+ const logSubmissionFlushCh = channel('ci:log-submission:flush')
41
43
  const modifiedFilesCh = channel('ci:mocha:modified-files')
42
44
  const testManagementTestsCh = channel('ci:mocha:test-management-tests')
43
45
  const workerConfigurationCh = channel('ci:mocha:worker:configuration')
@@ -46,6 +48,7 @@ const workerReportTelemetryCh = channel('ci:mocha:worker-report:telemetry')
46
48
  const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
47
49
 
48
50
  const jasmineAdapterInitCh = tracingChannel('orchestrion:@wdio/jasmine-framework:JasmineAdapter_init')
51
+ const baseReporterWaitForSyncCh = tracingChannel('orchestrion:@wdio/runner:BaseReporter_waitForSync')
49
52
  const launcherStartInstanceCh = tracingChannel('orchestrion:@wdio/cli:Launcher_startInstance')
50
53
  const localRunnerRunCh = tracingChannel('orchestrion:@wdio/local-runner:LocalRunner_run')
51
54
  const localRunnerShutdownCh = tracingChannel('orchestrion:@wdio/local-runner:LocalRunner_shutdown')
@@ -1000,6 +1003,29 @@ function finishCoordinator (state, error, onDone) {
1000
1003
  })
1001
1004
  }
1002
1005
 
1006
+ /**
1007
+ * Delays WebdriverIO worker exit until pending log-submission requests settle.
1008
+ *
1009
+ * @param {{
1010
+ * resolveCallback?: (onDone: () => void) => void,
1011
+ * rejectCallback?: (onDone: () => void) => void
1012
+ * }} context
1013
+ * @returns {void}
1014
+ */
1015
+ function waitForLogSubmissionAtWorkerExit (context) {
1016
+ if (!isWebdriverioWorker || !logSubmissionFlushCh.hasSubscribers) {
1017
+ return
1018
+ }
1019
+
1020
+ const waitForLogs = onDone => publishWithCompletion(logSubmissionFlushCh, {}, onDone)
1021
+ context.resolveCallback = waitForLogs
1022
+ context.rejectCallback = waitForLogs
1023
+ }
1024
+
1025
+ baseReporterWaitForSyncCh.asyncEnd.subscribe(
1026
+ /** @type {import('node:diagnostics_channel').ChannelListener} */ (waitForLogSubmissionAtWorkerExit)
1027
+ )
1028
+
1003
1029
  // dc-polyfill supports partial tracing-channel subscribers, unlike the Node.js type definition.
1004
1030
  // @ts-expect-error
1005
1031
  jasmineAdapterInitCh.subscribe({
@@ -12,12 +12,15 @@ const patched = new WeakSet()
12
12
  const configureCh = channel('ci:log-submission:winston:configure')
13
13
  const addTransport = channel('ci:log-submission:winston:add-transport')
14
14
 
15
- addHook({ name: 'winston', file: 'lib/winston/transports/index.js', versions: ['>=3'] }, transportsPackage => {
15
+ addHook({ name: 'winston', versions: ['>=3'] }, winston => {
16
16
  if (configureCh.hasSubscribers) {
17
- configureCh.publish(transportsPackage.Http)
17
+ configureCh.publish({
18
+ createJsonFormat: winston.format.json,
19
+ StreamTransport: winston.transports.Stream,
20
+ })
18
21
  }
19
22
 
20
- return transportsPackage
23
+ return winston
21
24
  })
22
25
 
23
26
  addHook({ name: 'winston', file: 'lib/winston/logger.js', versions: ['>=3'] }, Logger => {
@@ -409,6 +409,7 @@ beforeEach(function () {
409
409
  rumCookiePromise = setRumCorrelationCookie(traceId)
410
410
  }
411
411
  if (shouldSkip) {
412
+ // Test Optimization requested this runtime skip through the Cypress support hook.
412
413
  this.skip()
413
414
  }
414
415
  if (rumCookiePromise) {
@@ -8,8 +8,8 @@ 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 { getClientStatusValidator } = require('../../dd-trace/src/plugins/util/status-validator')
11
12
  const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
12
- const log = require('../../dd-trace/src/log')
13
13
  const { stripQueryAndFragment } = require('../../dd-trace/src/util')
14
14
  const { CLIENT_PORT_KEY, COMPONENT, ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants')
15
15
 
@@ -167,7 +167,7 @@ function addRequestHeaders (req, span, config) {
167
167
  }
168
168
 
169
169
  function normalizeClientConfig (config) {
170
- const validateStatus = getStatusValidator(config)
170
+ const validateStatus = getClientStatusValidator(config)
171
171
  const filter = getFilter(config)
172
172
  const propagationFilter = getFilter({ blocklist: config.propagationBlocklist })
173
173
  const headers = getHeaders(config)
@@ -183,19 +183,6 @@ function normalizeClientConfig (config) {
183
183
  }
184
184
  }
185
185
 
186
- function is400ErrorCode (code) {
187
- return code < 400 || code >= 500
188
- }
189
-
190
- function getStatusValidator (config) {
191
- if (typeof config.validateStatus === 'function') {
192
- return config.validateStatus
193
- } else if (config.hasOwnProperty('validateStatus')) {
194
- log.error('Expected `validateStatus` to be a function.')
195
- }
196
- return is400ErrorCode
197
- }
198
-
199
186
  function getFilter (config) {
200
187
  config = { ...config, blocklist: config.blocklist || [] }
201
188
 
@@ -4,12 +4,12 @@ const URL = require('url').URL
4
4
 
5
5
  const { storage } = require('../../datadog-core')
6
6
  const ClientPlugin = require('../../dd-trace/src/plugins/client')
7
- const log = require('../../dd-trace/src/log')
8
7
  const tags = require('../../../ext/tags')
9
8
  const kinds = require('../../../ext/kinds')
10
9
  const formats = require('../../../ext/formats')
11
10
  const { COMPONENT, CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
12
11
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
12
+ const { getClientStatusValidator } = require('../../dd-trace/src/plugins/util/status-validator')
13
13
  const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
14
14
 
15
15
  const HTTP_HEADERS = formats.HTTP_HEADERS
@@ -165,21 +165,8 @@ function hasAmazonSignature (headers, path) {
165
165
  return false
166
166
  }
167
167
 
168
- function is400ErrorCode (code) {
169
- return code < 400 || code >= 500
170
- }
171
-
172
- function getStatusValidator (config) {
173
- if (typeof config.validateStatus === 'function') {
174
- return config.validateStatus
175
- } else if (config.hasOwnProperty('validateStatus')) {
176
- log.error('Expected `validateStatus` to be a function.')
177
- }
178
- return is400ErrorCode
179
- }
180
-
181
168
  function normalizeConfig (config) {
182
- const validateStatus = getStatusValidator(config)
169
+ const validateStatus = getClientStatusValidator(config)
183
170
  const filter = getFilter(config)
184
171
  const headers = getHeaders(config)
185
172
 
@@ -63,6 +63,7 @@ class OpenAIAgentsIntegration {
63
63
  #tracer
64
64
  #config
65
65
  #enabled = false
66
+ #llmobsEnabled = true
66
67
  #service
67
68
  /**
68
69
  * LLMObs is gated independently of APM tracing: when DD_LLMOBS_ENABLED is
@@ -95,10 +96,11 @@ class OpenAIAgentsIntegration {
95
96
  /**
96
97
  * Apply plugin lifecycle configuration.
97
98
  *
98
- * @param {{ enabled?: boolean, service?: string }} [config]
99
+ * @param {{ enabled?: boolean, llmobs?: boolean, service?: string }} [config]
99
100
  */
100
101
  configure (config) {
101
102
  this.#enabled = !!config?.enabled
103
+ this.#llmobsEnabled = config?.llmobs !== false
102
104
  this.#service = config?.service
103
105
  }
104
106
 
@@ -210,15 +212,17 @@ class OpenAIAgentsIntegration {
210
212
  completionRequested: false,
211
213
  })
212
214
 
213
- this.#tagger.registerLLMObsSpan(ddSpan, {
214
- kind: 'workflow',
215
- name,
216
- integration: COMPONENT,
217
- parent: llmobsParentStore?.span,
218
- sessionId: oaiTrace.groupId || undefined,
219
- })
220
- if (LLMObsTagger.tagMap.has(ddSpan)) {
221
- llmobsStorage.enterWith({ ...llmobsParentStore, span: ddSpan })
215
+ if (this.#isLLMObsEnabled()) {
216
+ this.#tagger.registerLLMObsSpan(ddSpan, {
217
+ kind: 'workflow',
218
+ name,
219
+ integration: COMPONENT,
220
+ parent: llmobsParentStore?.span,
221
+ sessionId: oaiTrace.groupId || undefined,
222
+ })
223
+ if (LLMObsTagger.tagMap.has(ddSpan)) {
224
+ llmobsStorage.enterWith({ ...llmobsParentStore, span: ddSpan })
225
+ }
222
226
  }
223
227
  }
224
228
 
@@ -680,7 +684,7 @@ class OpenAIAgentsIntegration {
680
684
  * @returns {boolean}
681
685
  */
682
686
  #isLLMObsEnabled () {
683
- return !!this.#config.llmobs?.DD_LLMOBS_ENABLED
687
+ return this.#llmobsEnabled && !!this.#config.llmobs?.DD_LLMOBS_ENABLED
684
688
  }
685
689
 
686
690
  /**
@@ -523,7 +523,15 @@ class PlaywrightPlugin extends CiPlugin {
523
523
 
524
524
  finishAllTraceSpans(span)
525
525
  if (this._tracerConfig.DD_PLAYWRIGHT_WORKER) {
526
- this.tracer._exporter.flush(onDone)
526
+ try {
527
+ this.tracer._exporter.flush(onDone)
528
+ } catch (error) {
529
+ // A synchronous flush failure must still release the worker, otherwise it hangs.
530
+ // Log rather than rethrow: an exception from this diagnostics-channel subscriber
531
+ // would surface as an uncaught error in the worker and crash the user's test run.
532
+ onDone?.()
533
+ log.error('Error flushing traces at Playwright worker shutdown', error)
534
+ }
527
535
  }
528
536
  })
529
537
 
@@ -534,6 +542,7 @@ class PlaywrightPlugin extends CiPlugin {
534
542
  testSourceLine,
535
543
  browserName,
536
544
  isNew,
545
+ isAttemptToFix,
537
546
  isDisabled,
538
547
  isModified,
539
548
  isQuarantined,
@@ -559,6 +568,9 @@ class PlaywrightPlugin extends CiPlugin {
559
568
  if (isNew) {
560
569
  span.setTag(TEST_IS_NEW, 'true')
561
570
  }
571
+ if (isAttemptToFix) {
572
+ span.setTag(TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX, 'true')
573
+ }
562
574
  if (isDisabled) {
563
575
  span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
564
576
  }
@@ -5,7 +5,7 @@ const { storage } = require('../../datadog-core')
5
5
  const tags = require('../../../ext/tags')
6
6
  const formats = require('../../../ext/formats')
7
7
  const HTTP_HEADERS = formats.HTTP_HEADERS
8
- const log = require('../../dd-trace/src/log')
8
+ const { getClientStatusValidator } = require('../../dd-trace/src/plugins/util/status-validator')
9
9
  const { buildClientHttpUrl } = require('../../dd-trace/src/plugins/util/url')
10
10
  const { stripQueryAndFragment } = require('../../dd-trace/src/util')
11
11
  const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
@@ -302,7 +302,7 @@ function normalizeHeaders (headers) {
302
302
  }
303
303
 
304
304
  function normalizeConfig (config) {
305
- const validateStatus = getStatusValidator(config)
305
+ const validateStatus = getClientStatusValidator(config)
306
306
  const hooks = getHooks(config)
307
307
 
308
308
  return {
@@ -312,19 +312,6 @@ function normalizeConfig (config) {
312
312
  }
313
313
  }
314
314
 
315
- function getStatusValidator (config) {
316
- if (typeof config.validateStatus === 'function') {
317
- return config.validateStatus
318
- } else if (Object.hasOwn(config, 'validateStatus')) {
319
- log.error('Expected `validateStatus` to be a function.')
320
- }
321
- return defaultValidateStatus
322
- }
323
-
324
- function defaultValidateStatus (code) {
325
- return code < 400 || code >= 500
326
- }
327
-
328
315
  function getHooks (config) {
329
316
  const request = config.hooks?.request ?? noop
330
317
 
@@ -8,11 +8,14 @@ const { keepTrace } = require('../priority_sampler')
8
8
  const { extractIp } = require('../plugins/util/ip_extractor')
9
9
  const { AI_GUARD } = require('../standalone/product')
10
10
  const telemetryMetrics = require('../telemetry/metrics')
11
+ const { normalizeRedactionReplacements, redactMessages } = require('./redaction')
11
12
  const TAGS = require('./tags')
12
13
 
13
14
  const ALLOW = 'ALLOW'
14
15
 
16
+ /** @typedef {import('../../../../index').aiguard.ContentPart} ContentPart */
15
17
  /** @typedef {import('../../../../index').aiguard.Message} Message */
18
+ /** @typedef {import('../../../../index').aiguard.RedactionReplacement} RedactionReplacement */
16
19
  /** @typedef {import('../opentracing/span')} Span */
17
20
 
18
21
  /**
@@ -24,19 +27,22 @@ const ALLOW = 'ALLOW'
24
27
  * @property {Record<string, unknown>} tagProbabilities
25
28
  * @property {boolean} hasTagProbabilities
26
29
  * @property {boolean} blockingEnabled
30
+ * @property {unknown} redactionReplacements
27
31
  */
28
32
 
29
33
  /**
30
34
  * @typedef {object} EvaluationOutcome
31
35
  * @property {{ action: string, reason: string|undefined, tags: Array<unknown>,
32
- * tagProbabilities: Record<string, unknown>, sds: Array<unknown> }} result
36
+ * tagProbabilities: Record<string, unknown>, sds: Array<unknown>, messages: Message[],
37
+ * redactionReplacements: RedactionReplacement[] }} result
33
38
  * @property {boolean} shouldBlock
34
39
  * @property {boolean} hasTagProbabilities
40
+ * @property {{ enabled: boolean, applied: boolean, failures: number }} redaction
35
41
  */
36
42
 
37
43
  /**
38
44
  * @typedef {object} EvaluationMetaStruct
39
- * @property {Message[]} messages
45
+ * @property {Message[]} [messages]
40
46
  * @property {Array<unknown>} [attack_categories]
41
47
  * @property {Array<unknown>} [sds]
42
48
  * @property {Record<string, unknown>} [tag_probs]
@@ -45,6 +51,7 @@ const ALLOW = 'ALLOW'
45
51
  /**
46
52
  * @typedef {object} EvaluationReport
47
53
  * @property {Span} span
54
+ * @property {Message[]} messages
48
55
  * @property {EvaluationMetaStruct} metaStruct
49
56
  * @property {{ source: string, integration: string }} telemetryTags
50
57
  */
@@ -79,6 +86,7 @@ function parseEvaluationResponse (body) {
79
86
  tagProbabilities: isRecord(attributes.tag_probs) ? attributes.tag_probs : {},
80
87
  hasTagProbabilities: isRecord(attributes.tag_probs),
81
88
  blockingEnabled: attributes.is_blocking_enabled === true,
89
+ redactionReplacements: attributes.redaction_replacements,
82
90
  }
83
91
  }
84
92
 
@@ -96,11 +104,16 @@ function shouldBlockEvaluation (block, evaluation) {
96
104
  /**
97
105
  * Applies local evaluation policy and creates the result returned to the caller.
98
106
  *
107
+ * @param {Message[]} messages
99
108
  * @param {EvaluationResponse} evaluation
100
- * @param {boolean} block
109
+ * @param {{ block: boolean, redactionEnabled: boolean }} options
101
110
  * @returns {EvaluationOutcome}
102
111
  */
103
- function createEvaluationOutcome (evaluation, block) {
112
+ function createEvaluationOutcome (messages, evaluation, options) {
113
+ const redaction = options.redactionEnabled
114
+ ? redactMessages(messages, evaluation.redactionReplacements)
115
+ : { messages, redacted: false, failures: 0 }
116
+
104
117
  return {
105
118
  result: {
106
119
  action: evaluation.action,
@@ -108,9 +121,16 @@ function createEvaluationOutcome (evaluation, block) {
108
121
  tags: evaluation.tags,
109
122
  tagProbabilities: evaluation.tagProbabilities,
110
123
  sds: evaluation.sdsFindings,
124
+ messages: redaction.messages,
125
+ redactionReplacements: normalizeRedactionReplacements(evaluation.redactionReplacements),
111
126
  },
112
- shouldBlock: shouldBlockEvaluation(block, evaluation),
127
+ shouldBlock: shouldBlockEvaluation(options.block, evaluation),
113
128
  hasTagProbabilities: evaluation.hasTagProbabilities,
129
+ redaction: {
130
+ enabled: options.redactionEnabled,
131
+ applied: redaction.redacted,
132
+ failures: redaction.failures,
133
+ },
114
134
  }
115
135
  }
116
136
 
@@ -130,6 +150,7 @@ class EvaluationReporter {
130
150
  #config
131
151
  #maxMessagesLength
132
152
  #maxContentSize
153
+ #redactionEnabled
133
154
 
134
155
  /**
135
156
  * @param {import('../config/config-base')} config
@@ -138,6 +159,7 @@ class EvaluationReporter {
138
159
  this.#config = config
139
160
  this.#maxMessagesLength = config.experimental.aiguard.maxMessagesLength
140
161
  this.#maxContentSize = config.experimental.aiguard.maxContentSize
162
+ this.#redactionEnabled = config.experimental.aiguard.redactionEnabled
141
163
  }
142
164
 
143
165
  /**
@@ -150,19 +172,18 @@ class EvaluationReporter {
150
172
  */
151
173
  start (span, messages, options) {
152
174
  const telemetryTags = { source: options.source, integration: options.integration }
153
- const last = messages.at(-1)
175
+ const evaluatedMessages = this.#redactionEnabled ? clone(messages) : messages
176
+ const last = evaluatedMessages.at(-1)
154
177
  const target = this.#isToolCall(last) ? 'tool' : 'prompt'
155
178
  span.setTag(TAGS.TARGET_TAG_KEY, target)
156
179
  if (target === 'tool') {
157
- const name = this.#getToolName(last, messages)
180
+ const name = this.#getToolName(last, evaluatedMessages)
158
181
  if (name) {
159
182
  span.setTag(TAGS.TOOL_NAME_TAG_KEY, name)
160
183
  }
161
184
  }
162
185
 
163
- const metaStruct = {
164
- messages: this.#buildMessagesForMetaStruct(messages, telemetryTags),
165
- }
186
+ const metaStruct = {}
166
187
  span.meta_struct = {
167
188
  [TAGS.META_STRUCT_KEY]: metaStruct,
168
189
  }
@@ -178,6 +199,7 @@ class EvaluationReporter {
178
199
 
179
200
  return {
180
201
  span,
202
+ messages: evaluatedMessages,
181
203
  metaStruct,
182
204
  telemetryTags,
183
205
  }
@@ -191,6 +213,7 @@ class EvaluationReporter {
191
213
  * @returns {void}
192
214
  */
193
215
  fail (report, errorType) {
216
+ report.metaStruct.messages = this.#buildMessagesForMetaStruct(report.messages, report.telemetryTags)
194
217
  aiguardMetrics.count(TAGS.TELEMETRY_REQUESTS, { error: true, ...report.telemetryTags }).inc(1)
195
218
  aiguardMetrics.count(TAGS.TELEMETRY_ERROR, { type: errorType, ...report.telemetryTags }).inc(1)
196
219
  }
@@ -203,18 +226,38 @@ class EvaluationReporter {
203
226
  * @returns {void}
204
227
  */
205
228
  finish (report, outcome) {
206
- const { result, shouldBlock } = outcome
229
+ const { result, redaction, shouldBlock } = outcome
207
230
 
208
231
  if (result.tags.length > 0) report.metaStruct.attack_categories = result.tags
209
232
  if (result.sds.length > 0) report.metaStruct.sds = result.sds
210
233
  if (outcome.hasTagProbabilities) report.metaStruct.tag_probs = result.tagProbabilities
211
234
 
212
- const requestTelemetryTags = {
213
- action: result.action,
214
- error: false,
215
- block: shouldBlock,
216
- ...report.telemetryTags,
235
+ if (redaction.enabled) {
236
+ report.span.setTag(TAGS.REDACTED_TAG_KEY, redaction.applied ? 'true' : 'false')
237
+ if (redaction.failures > 0) {
238
+ aiguardMetrics.count(TAGS.TELEMETRY_ERROR, {
239
+ type: TAGS.ERROR_TYPE_REDACTION,
240
+ ...report.telemetryTags,
241
+ }).inc(redaction.failures)
242
+ }
217
243
  }
244
+
245
+ report.metaStruct.messages = this.#buildMessagesForMetaStruct(result.messages, report.telemetryTags)
246
+
247
+ const requestTelemetryTags = redaction.enabled
248
+ ? {
249
+ action: result.action,
250
+ error: false,
251
+ block: shouldBlock,
252
+ redacted: redaction.applied,
253
+ ...report.telemetryTags,
254
+ }
255
+ : {
256
+ action: result.action,
257
+ error: false,
258
+ block: shouldBlock,
259
+ ...report.telemetryTags,
260
+ }
218
261
  aiguardMetrics.count(TAGS.TELEMETRY_REQUESTS, requestTelemetryTags).inc(1)
219
262
 
220
263
  report.span.setTag(TAGS.ACTION_TAG_KEY, result.action)
@@ -242,10 +285,7 @@ class EvaluationReporter {
242
285
  let contentTruncated = false
243
286
  for (let i = messages.length - size; i < messages.length; i++) {
244
287
  const message = clone(messages[i])
245
- if (message.content?.length > this.#maxContentSize) {
246
- contentTruncated = true
247
- message.content = message.content.slice(0, this.#maxContentSize)
248
- }
288
+ if (this.#truncateMessageContent(message)) contentTruncated = true
249
289
  result.push(message)
250
290
  }
251
291
  if (contentTruncated) {
@@ -254,6 +294,40 @@ class EvaluationReporter {
254
294
  return result
255
295
  }
256
296
 
297
+ /**
298
+ * Truncates text in a cloned message to one shared content-size limit.
299
+ *
300
+ * @param {{ content?: string|ContentPart[] }} message
301
+ * @returns {boolean}
302
+ */
303
+ #truncateMessageContent (message) {
304
+ const { content } = message
305
+ if (typeof content === 'string') {
306
+ if (content.length <= this.#maxContentSize) return false
307
+
308
+ message.content = content.slice(0, this.#maxContentSize)
309
+ return true
310
+ }
311
+
312
+ if (!Array.isArray(content)) return false
313
+
314
+ let remainingContentSize = this.#maxContentSize
315
+ let truncated = false
316
+ for (const part of content) {
317
+ const text = part?.text
318
+ if (typeof text !== 'string') continue
319
+
320
+ if (text.length > remainingContentSize) {
321
+ part.text = text.slice(0, remainingContentSize)
322
+ truncated = true
323
+ remainingContentSize = 0
324
+ } else {
325
+ remainingContentSize -= text.length
326
+ }
327
+ }
328
+ return truncated
329
+ }
330
+
257
331
  /**
258
332
  * Returns whether a message represents a tool call or tool output.
259
333
  *
@@ -74,18 +74,17 @@ function onStreamAfter (ctx) {
74
74
  */
75
75
  function getStreamContent (chunks) {
76
76
  const toolCalls = []
77
- const textParts = []
77
+ let text = ''
78
78
 
79
79
  for (const chunk of chunks) {
80
80
  if (chunk?.type === 'tool-call') {
81
81
  toolCalls.push(chunk)
82
82
  } else if (chunk?.type === 'text-delta') {
83
- textParts.push(chunk.textDelta)
83
+ text += chunk.delta ?? chunk.textDelta ?? ''
84
84
  }
85
85
  }
86
86
 
87
87
  if (toolCalls.length) return toolCalls
88
- const text = textParts.join('')
89
88
  return text ? [{ type: 'text', text }] : []
90
89
  }
91
90
 
@@ -269,27 +269,39 @@ function convertServerToolResultContent (content) {
269
269
  return content.error_message ? `${content.error_code}: ${content.error_message}` : content.error_code
270
270
  }
271
271
 
272
- const lines = []
272
+ let lines
273
273
  if (Array.isArray(content)) {
274
274
  for (const item of content) {
275
275
  if (!item || typeof item !== 'object') continue
276
276
  // text blocks (MCP / generic), then web-search title + url.
277
- if (typeof item.text === 'string') lines.push(item.text)
278
- if (typeof item.title === 'string') lines.push(item.title)
279
- if (typeof item.url === 'string') lines.push(item.url)
277
+ if (typeof item.text === 'string') {
278
+ lines = lines === undefined ? item.text : `${lines}\n${item.text}`
279
+ }
280
+ if (typeof item.title === 'string') {
281
+ lines = lines === undefined ? item.title : `${lines}\n${item.title}`
282
+ }
283
+ if (typeof item.url === 'string') {
284
+ lines = lines === undefined ? item.url : `${lines}\n${item.url}`
285
+ }
280
286
  }
281
287
  } else {
282
- if (typeof content.stdout === 'string' && content.stdout) lines.push(content.stdout)
283
- if (typeof content.stderr === 'string' && content.stderr) lines.push(content.stderr)
284
- if (typeof content.content === 'string' && content.content) lines.push(content.content)
288
+ if (typeof content.stdout === 'string' && content.stdout) lines = content.stdout
289
+ if (typeof content.stderr === 'string' && content.stderr) {
290
+ lines = lines === undefined ? content.stderr : `${lines}\n${content.stderr}`
291
+ }
292
+ if (typeof content.content === 'string' && content.content) {
293
+ lines = lines === undefined ? content.content : `${lines}\n${content.content}`
294
+ }
285
295
  if (Array.isArray(content.lines)) {
286
296
  for (const line of content.lines) {
287
- if (typeof line === 'string') lines.push(line)
297
+ if (typeof line === 'string') {
298
+ lines = lines === undefined ? line : `${lines}\n${line}`
299
+ }
288
300
  }
289
301
  }
290
302
  }
291
303
 
292
- return lines.join('\n') || '[tool result]'
304
+ return lines || '[tool result]'
293
305
  }
294
306
 
295
307
  /**
@@ -2,7 +2,15 @@
2
2
 
3
3
  class NoopAIGuard {
4
4
  evaluate (messages, opts) {
5
- return Promise.resolve({ action: 'ALLOW', reason: 'AI Guard is not enabled', tags: [], sds: [] })
5
+ return Promise.resolve({
6
+ action: 'ALLOW',
7
+ reason: 'AI Guard is not enabled',
8
+ tags: [],
9
+ tagProbabilities: {},
10
+ sds: [],
11
+ messages,
12
+ redactionReplacements: [],
13
+ })
6
14
  }
7
15
  }
8
16