dd-trace 5.132.0 → 5.134.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 (231) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-runner-setup.mjs +27 -0
  16. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  17. package/index.d.ts +41 -19
  18. package/initialize.mjs +1 -3
  19. package/openfeature/rules-based.d.ts +11 -0
  20. package/openfeature/rules-based.js +7 -0
  21. package/package.json +25 -22
  22. package/packages/datadog-core/src/storage.js +16 -0
  23. package/packages/datadog-esbuild/index.js +18 -12
  24. package/packages/datadog-instrumentations/index.js +2 -1
  25. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  26. package/packages/datadog-instrumentations/src/ai.js +3 -3
  27. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  28. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  29. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  30. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  31. package/packages/datadog-instrumentations/src/cucumber.js +65 -41
  32. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  33. package/packages/datadog-instrumentations/src/electron.js +6 -6
  34. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  35. package/packages/datadog-instrumentations/src/fs.js +2 -2
  36. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  37. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  38. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +28 -9
  39. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  40. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  41. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  42. package/packages/datadog-instrumentations/src/helpers/register.js +45 -1
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +89 -6
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/jest.js +12 -0
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  48. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/vitest.js +8 -0
  49. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +7 -7
  50. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +5 -0
  51. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  52. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  53. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  54. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  55. package/packages/datadog-instrumentations/src/jest.js +72 -67
  56. package/packages/datadog-instrumentations/src/knex.js +9 -5
  57. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  58. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  59. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  60. package/packages/datadog-instrumentations/src/mocha/utils.js +38 -4
  61. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -2
  62. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  63. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  64. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  65. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  66. package/packages/datadog-instrumentations/src/nova-sonic/session.js +492 -0
  67. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  68. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  69. package/packages/datadog-instrumentations/src/openai.js +7 -0
  70. package/packages/datadog-instrumentations/src/pino.js +5 -6
  71. package/packages/datadog-instrumentations/src/playwright.js +2 -0
  72. package/packages/datadog-instrumentations/src/redis.js +27 -13
  73. package/packages/datadog-instrumentations/src/router.js +30 -0
  74. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  75. package/packages/datadog-instrumentations/src/stripe.js +23 -12
  76. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +8 -5
  77. package/packages/datadog-instrumentations/src/vitest-main.js +16 -18
  78. package/packages/datadog-instrumentations/src/vitest-util.js +7 -4
  79. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  80. package/packages/datadog-instrumentations/src/vitest.js +9 -1
  81. package/packages/datadog-instrumentations/src/webdriverio.js +5 -8
  82. package/packages/datadog-instrumentations/src/ws.js +1 -4
  83. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  84. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  85. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  86. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  87. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  89. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  90. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  91. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  92. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  93. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  94. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +4 -5
  95. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  96. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  97. package/packages/datadog-plugin-cypress/src/support.js +3 -4
  98. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  99. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  100. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  101. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  102. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  103. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  104. package/packages/datadog-plugin-mocha/src/index.js +7 -10
  105. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +8 -4
  106. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  107. package/packages/datadog-plugin-next/src/index.js +1 -2
  108. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  109. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  110. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  111. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  112. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  113. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  114. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  115. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  116. package/packages/datadog-webpack/index.js +55 -43
  117. package/packages/datadog-webpack/src/loader.js +56 -8
  118. package/packages/dd-trace/src/aiguard/evaluation.js +143 -1
  119. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  120. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  121. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  122. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  123. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  124. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  125. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  126. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  127. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  128. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  129. package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
  130. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  131. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  132. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  133. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  134. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  135. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  136. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  137. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  138. package/packages/dd-trace/src/appsec/stack_trace.js +4 -4
  139. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  140. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +3 -2
  141. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  142. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  143. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  144. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  145. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  146. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  147. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  148. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  149. package/packages/dd-trace/src/config/defaults.js +5 -5
  150. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  151. package/packages/dd-trace/src/config/helper.js +34 -3
  152. package/packages/dd-trace/src/config/index.js +55 -9
  153. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  154. package/packages/dd-trace/src/config/parsers.js +2 -2
  155. package/packages/dd-trace/src/config/supported-configurations.json +44 -0
  156. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  157. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  158. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +6 -5
  159. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -5
  160. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -0
  161. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +4 -6
  162. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  163. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  164. package/packages/dd-trace/src/debugger/unsupported-runtime.js +16 -0
  165. package/packages/dd-trace/src/dogstatsd.js +1 -1
  166. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  167. package/packages/dd-trace/src/exporters/common/request.js +1 -5
  168. package/packages/dd-trace/src/id.js +14 -1
  169. package/packages/dd-trace/src/lambda/handler.js +179 -19
  170. package/packages/dd-trace/src/lambda/index.js +15 -1
  171. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  172. package/packages/dd-trace/src/llmobs/agent-manifest.js +19 -2
  173. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  174. package/packages/dd-trace/src/llmobs/constants/tags.js +15 -0
  175. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  176. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  177. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  178. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  180. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  181. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +5 -0
  182. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +64 -2
  183. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  184. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  185. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  186. package/packages/dd-trace/src/llmobs/sdk.js +15 -4
  187. package/packages/dd-trace/src/llmobs/span_processor.js +13 -2
  188. package/packages/dd-trace/src/llmobs/tagger.js +20 -10
  189. package/packages/dd-trace/src/llmobs/util.js +11 -6
  190. package/packages/dd-trace/src/llmobs/writers/base.js +4 -5
  191. package/packages/dd-trace/src/log/log.js +1 -5
  192. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  193. package/packages/dd-trace/src/openfeature/flagging_provider.js +48 -21
  194. package/packages/dd-trace/src/openfeature/index.js +6 -0
  195. package/packages/dd-trace/src/openfeature/writers/util.js +4 -4
  196. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  199. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  200. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +24 -24
  201. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  202. package/packages/dd-trace/src/otel-sampling.js +4 -5
  203. package/packages/dd-trace/src/otel-thread-ctx.js +79 -13
  204. package/packages/dd-trace/src/plugin_manager.js +1 -2
  205. package/packages/dd-trace/src/plugins/database.js +7 -16
  206. package/packages/dd-trace/src/plugins/tracing.js +0 -1
  207. package/packages/dd-trace/src/plugins/util/git.js +99 -59
  208. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  209. package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -1
  210. package/packages/dd-trace/src/plugins/util/test.js +50 -4
  211. package/packages/dd-trace/src/plugins/util/web.js +90 -20
  212. package/packages/dd-trace/src/priority_sampler.js +1 -2
  213. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  214. package/packages/dd-trace/src/proxy.js +1 -2
  215. package/packages/dd-trace/src/ritm.js +4 -2
  216. package/packages/dd-trace/src/serverless.js +5 -0
  217. package/packages/dd-trace/src/span_processor.js +13 -17
  218. package/packages/dd-trace/src/span_stats.js +4 -1
  219. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  220. package/packages/dd-trace/src/telemetry/logs/log-collector.js +1 -1
  221. package/packages/dd-trace/src/util.js +37 -4
  222. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  223. package/vendor/dist/@apm-js-collab/code-transformer/index.js +8 -8
  224. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  225. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  226. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  227. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  228. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  229. package/vendor/dist/protobufjs/index.js +1 -1
  230. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  231. package/vendor/dist/shell-quote/index.js +1 -1
@@ -64,8 +64,7 @@ class DDOpenAIAgentsProcessor {
64
64
 
65
65
  onSpanStart (oaiSpan) {
66
66
  const integration = this._getIntegration()
67
- if (!integration?.enabled) return RESOLVED
68
- if (!oaiSpan?.spanData) return RESOLVED // guard NoopSpan
67
+ if (!integration?.enabled || !oaiSpan?.spanData) return RESOLVED // guard NoopSpan
69
68
  const type = oaiSpan.spanData.type
70
69
  const kind = SPAN_KIND_BY_TYPE[type]
71
70
  try {
@@ -84,8 +83,7 @@ class DDOpenAIAgentsProcessor {
84
83
 
85
84
  onSpanEnd (oaiSpan) {
86
85
  const integration = this._getIntegration()
87
- if (!integration?.enabled) return RESOLVED
88
- if (!oaiSpan?.spanData) return RESOLVED
86
+ if (!integration?.enabled || !oaiSpan?.spanData) return RESOLVED
89
87
  const type = oaiSpan.spanData.type
90
88
  const kind = SPAN_KIND_BY_TYPE[type]
91
89
  try {
@@ -1,9 +1,12 @@
1
1
  'use strict'
2
2
 
3
+ const { performance } = require('node:perf_hooks')
4
+
3
5
  const { CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
4
6
  const DatabasePlugin = require('../../dd-trace/src/plugins/database')
5
7
  const Plugin = require('../../dd-trace/src/plugins/plugin')
6
8
 
9
+ const ACQUIRE_START_CHANNEL = 'tracing:orchestrion:postgres:query:acquire:start'
7
10
  const PREPARE_START_CHANNEL = 'tracing:orchestrion:postgres:query:prepare:start'
8
11
 
9
12
  /**
@@ -14,6 +17,8 @@ const PREPARE_START_CHANNEL = 'tracing:orchestrion:postgres:query:prepare:start'
14
17
  * @property {string} [host]
15
18
  * @property {number} [pid]
16
19
  * @property {number} [port]
20
+ * @property {boolean} [poolWaitFinished]
21
+ * @property {number} [poolWaitStartMs]
17
22
  * @property {PostgresQuery} query
18
23
  * @property {string} [statement]
19
24
  * @property {string} user
@@ -63,12 +68,16 @@ class PostgresPlugin extends DatabasePlugin {
63
68
  /** @type {WeakMap<PostgresQuery, import('../../..').Span>} */
64
69
  #spans = new WeakMap()
65
70
 
71
+ /** @type {WeakMap<PostgresQuery, PostgresContext>} */
72
+ #waitContexts = new WeakMap()
73
+
66
74
  /**
67
75
  * @param {object} tracer
68
76
  * @param {import('../../dd-trace/src/config/config-base')} tracerConfig
69
77
  */
70
78
  constructor (tracer, tracerConfig) {
71
79
  super(tracer, tracerConfig)
80
+ this.addSub(ACQUIRE_START_CHANNEL, ctx => this.#finishPoolWait(ctx.query))
72
81
  this.#preparation = new PostgresPreparationPlugin(this.#prepare.bind(this), tracer, tracerConfig)
73
82
  }
74
83
 
@@ -112,12 +121,26 @@ class PostgresPlugin extends DatabasePlugin {
112
121
  }
113
122
 
114
123
  this.#spans.set(query, span)
124
+ this.#waitContexts.set(query, ctx)
115
125
  if (this.#dbmEnabled) {
116
126
  this.#dbmQueries.set(query, { service: service.name })
117
127
  }
118
128
  return ctx.currentStore
119
129
  }
120
130
 
131
+ /**
132
+ * @param {PostgresQuery} query
133
+ */
134
+ #finishPoolWait (query) {
135
+ const ctx = this.#waitContexts.get(query)
136
+ if (ctx === undefined) return
137
+
138
+ this.#waitContexts.delete(query)
139
+ ctx.poolWaitFinished = true
140
+ const waitTime = ctx.poolWaitStartMs === undefined ? 0 : performance.now() - ctx.poolWaitStartMs
141
+ ctx.currentStore.span.setTag('db.pool.wait_time_ms', waitTime)
142
+ }
143
+
121
144
  /**
122
145
  * @param {PostgresPreparationContext} ctx
123
146
  */
@@ -152,6 +175,7 @@ class PostgresPlugin extends DatabasePlugin {
152
175
  this.#dbmQueries.delete(query)
153
176
  if (span === undefined) return
154
177
 
178
+ this.#finishPoolWait(query)
155
179
  this.#spans.delete(query)
156
180
 
157
181
  const statement = dbmState?.statement ?? result.statement
@@ -144,12 +144,8 @@ class SchemaExtractor {
144
144
  span.setTag(SCHEMA_NAME, removeLeadingPeriod(descriptor.fullName))
145
145
  span.setTag(SCHEMA_OPERATION, operation)
146
146
 
147
- if (!tracer._dataStreamsProcessor.canSampleSchema(operation)) {
148
- return
149
- }
150
-
151
147
  // if the span is unsampled, do not sample the schema
152
- if (!tracer._prioritySampler.isSampled(span)) {
148
+ if (!tracer._dataStreamsProcessor.canSampleSchema(operation) || !tracer._prioritySampler.isSampled(span)) {
153
149
  return
154
150
  }
155
151
 
@@ -52,10 +52,7 @@ class RheaConsumerPlugin extends ConsumerPlugin {
52
52
 
53
53
  function getResourceNameFromMessage (msgObj) {
54
54
  let resourceName = 'amq.topic'
55
- let options = {}
56
- if (msgObj.receiver && msgObj.receiver.options) {
57
- options = msgObj.receiver.options
58
- }
55
+ const options = msgObj.receiver && msgObj.receiver.options ? msgObj.receiver.options : {}
59
56
  if (options.source && options.source.address) {
60
57
  resourceName = options.source.address
61
58
  }
@@ -354,6 +354,8 @@ class VitestPlugin extends CiPlugin {
354
354
  browserProjectName,
355
355
  testExecutionId,
356
356
  testStartLine,
357
+ startTime,
358
+ duration,
357
359
  }) => {
358
360
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
359
361
  const extraTags = {
@@ -380,10 +382,11 @@ class VitestPlugin extends CiPlugin {
380
382
  testSuite,
381
383
  testSuiteSpan || this.testSuiteSpan,
382
384
  extraTags,
383
- testExecutionId
385
+ testExecutionId,
386
+ startTime
384
387
  )
385
388
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(testSpan))
386
- testSpan.finish()
389
+ testSpan.finish(Number.isFinite(duration) && duration >= 0 ? testSpan._startTime + duration : undefined)
387
390
  })
388
391
 
389
392
  this.addBind('ci:vitest:test-suite:start', (ctx) => {
@@ -314,11 +314,11 @@ function assertMethod (target, name, method) {
314
314
  let message = 'No target object provided'
315
315
 
316
316
  if (target) {
317
- if (typeof target !== 'object' && typeof target !== 'function') {
318
- message = 'Invalid target'
319
- } else {
317
+ if (typeof target === 'object' || typeof target === 'function') {
320
318
  name = String(name)
321
319
  message = method ? `Original method ${name} is not a function` : `No original method ${name}`
320
+ } else {
321
+ message = 'Invalid target'
322
322
  }
323
323
  }
324
324
 
@@ -335,7 +335,8 @@ function assertMethod (target, name, method) {
335
335
  function assertNotClass (target) {
336
336
  // Frozen functions also have a non-writable `prototype`; inspect their source only then.
337
337
  if ('prototype' in target &&
338
- Object.getOwnPropertyDescriptor(target, 'prototype').writable === false &&
338
+ // Bound generators inherit `prototype` without owning one, so the descriptor may still be missing.
339
+ Object.getOwnPropertyDescriptor(target, 'prototype')?.writable === false &&
339
340
  (!Object.isFrozen(target) || Function.prototype.toString.call(target).startsWith('class'))) {
340
341
  throw new TypeError('Target is a native class constructor and cannot be wrapped.')
341
342
  }
@@ -6,11 +6,15 @@ 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 { getRewriteTarget } = require('../datadog-instrumentations/src/helpers/rewriter/targets')
9
10
  const { isESMFile } = require('../datadog-esbuild/src/utils')
11
+ const { getValueFromEnvSources } = require('../dd-trace/src/config/helper')
10
12
  const log = require('./src/log')
11
13
 
12
14
  const PLUGIN_NAME = 'DatadogWebpackPlugin'
13
15
 
16
+ const DD_BUNDLER_INJECT_GIT_METADATA = getValueFromEnvSources('DD_BUNDLER_INJECT_GIT_METADATA') !== false
17
+
14
18
  for (const hook of Object.values(hooks)) {
15
19
  if (hook !== null && typeof hook === 'object') {
16
20
  hook.fn()
@@ -80,50 +84,55 @@ class DatadogWebpackPlugin {
80
84
  }
81
85
  })
82
86
 
83
- const gitMetadata = getGitMetadata()
84
- if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
85
- const banner =
86
- 'if (typeof process === \'object\' && process !== null &&\n' +
87
- ' process.env !== null && typeof process.env === \'object\') {\n' +
88
- (gitMetadata.repositoryURL
89
- ? ` process.env.DD_GIT_REPOSITORY_URL = ${JSON.stringify(gitMetadata.repositoryURL)};\n`
90
- : '') +
91
- (gitMetadata.commitSHA
92
- ? ` process.env.DD_GIT_COMMIT_SHA = ${JSON.stringify(gitMetadata.commitSHA)};\n`
93
- : '') +
94
- '}\n'
95
-
96
- compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
97
- compilation.hooks.processAssets.tap(
98
- { name: PLUGIN_NAME, stage: -2000 },
99
- () => {
100
- for (const chunk of compilation.chunks) {
101
- if (!chunk.canBeInitial()) continue
102
- for (const filename of chunk.files) {
103
- if (!filename.endsWith('.js') && !filename.endsWith('.mjs')) continue
104
- compilation.updateAsset(filename, (old) => {
105
- const content = banner + old.source()
106
- return {
107
- source () { return content },
108
- size () { return Buffer.byteLength(content, 'utf8') },
109
- map () { return old.map() },
110
- sourceAndMap () { return { source: content, map: old.map() } },
111
- updateHash (hash) { hash.update(content) },
112
- }
113
- })
87
+ // Bake DD_GIT_* into assets unless DD_BUNDLER_INJECT_GIT_METADATA=false (stable artifact hashes).
88
+ if (DD_BUNDLER_INJECT_GIT_METADATA) {
89
+ const gitMetadata = getGitMetadata()
90
+ if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
91
+ const banner =
92
+ 'if (typeof process === \'object\' && process !== null &&\n' +
93
+ ' process.env !== null && typeof process.env === \'object\') {\n' +
94
+ (gitMetadata.repositoryURL
95
+ ? ` process.env.DD_GIT_REPOSITORY_URL = ${JSON.stringify(gitMetadata.repositoryURL)};\n`
96
+ : '') +
97
+ (gitMetadata.commitSHA
98
+ ? ` process.env.DD_GIT_COMMIT_SHA = ${JSON.stringify(gitMetadata.commitSHA)};\n`
99
+ : '') +
100
+ '}\n'
101
+
102
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
103
+ compilation.hooks.processAssets.tap(
104
+ { name: PLUGIN_NAME, stage: -2000 },
105
+ () => {
106
+ for (const chunk of compilation.chunks) {
107
+ if (!chunk.canBeInitial()) continue
108
+ for (const filename of chunk.files) {
109
+ if (!filename.endsWith('.js') && !filename.endsWith('.mjs')) continue
110
+ compilation.updateAsset(filename, (old) => {
111
+ const content = banner + old.source()
112
+ return {
113
+ source () { return content },
114
+ size () { return Buffer.byteLength(content, 'utf8') },
115
+ map () { return old.map() },
116
+ sourceAndMap () { return { source: content, map: old.map() } },
117
+ updateHash (hash) { hash.update(content) },
118
+ }
119
+ })
120
+ }
114
121
  }
115
122
  }
116
- }
117
- )
118
- })
123
+ )
124
+ })
119
125
 
120
- log.debug(
121
- 'Automatically injected git metadata (DD_GIT_REPOSITORY_URL: %s, DD_GIT_COMMIT_SHA: %s)',
122
- gitMetadata.repositoryURL || 'not available',
123
- gitMetadata.commitSHA || 'not available'
124
- )
126
+ log.debug(
127
+ 'Automatically injected git metadata (DD_GIT_REPOSITORY_URL: %s, DD_GIT_COMMIT_SHA: %s)',
128
+ gitMetadata.repositoryURL || 'not available',
129
+ gitMetadata.commitSHA || 'not available'
130
+ )
131
+ } else {
132
+ log.warn('No git metadata available - skipping injection')
133
+ }
125
134
  } else {
126
- log.warn('No git metadata available - skipping injection')
135
+ log.debug('Skipping git metadata injection (DD_BUNDLER_INJECT_GIT_METADATA=false)')
127
136
  }
128
137
 
129
138
  compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf) => {
@@ -146,8 +155,9 @@ class DatadogWebpackPlugin {
146
155
  }
147
156
 
148
157
  const request = resolveData.request
158
+ const isModuleOfInterest = modulesOfInterest.has(request) || modulesOfInterest.has(`${pkg}/${modulePath}`)
149
159
 
150
- if (!modulesOfInterest.has(request) && !modulesOfInterest.has(`${pkg}/${modulePath}`)) {
160
+ if (!isModuleOfInterest && getRewriteTarget(normalizedResource) === undefined) {
151
161
  return
152
162
  }
153
163
 
@@ -177,10 +187,12 @@ class DatadogWebpackPlugin {
177
187
  const version = packageJson.version
178
188
  const pkgPath = request === pkg ? pkg : `${pkg}/${modulePath}`
179
189
 
190
+ // Webpack runs loaders right-to-left, so appending makes this loader see the package's original source
191
+ // before any user transpiler lowers the syntax Orchestrion function queries match on.
180
192
  createData.loaders ||= []
181
- createData.loaders.unshift({
193
+ createData.loaders.push({
182
194
  loader: require.resolve('./src/loader'),
183
- options: { pkg, version, path: pkgPath },
195
+ options: { pkg, version, path: pkgPath, rewriteOnly: !isModuleOfInterest },
184
196
  })
185
197
 
186
198
  log.debug('LOAD: %s@%s, pkg "%s"', pkg, version, pkgPath)
@@ -1,26 +1,74 @@
1
1
  'use strict'
2
2
 
3
+ const { filename } = require('../../datadog-instrumentations/src/helpers/instrumentation-utils')
4
+ const { createBundlerRewriter } = require('../../datadog-instrumentations/src/helpers/rewriter')
5
+ const { getRewriteTarget } = require('../../datadog-instrumentations/src/helpers/rewriter/targets')
6
+
3
7
  const CHANNEL = 'dd-trace:bundler:load'
8
+ // Resolved from dd-trace so the injected require works even when dc-polyfill is not hoisted next to the target.
9
+ const DC_MODULE = require.resolve('dc-polyfill').replaceAll('\\', '/')
10
+ const DC_MODULE_LITERAL = JSON.stringify(DC_MODULE)
11
+
12
+ let rewrite
4
13
 
5
14
  /**
6
- * Webpack loader that appends a dc-polyfill channel publish to a CJS module.
7
- * Called for each module-of-interest identified by DatadogWebpackPlugin.
15
+ * Webpack loader called for each module-of-interest identified by DatadogWebpackPlugin.
16
+ * Orchestrion rewrite targets are rewritten and, for pure Orchestrion integrations, get an activation publish
17
+ * appended. Modules with export hooks get a dc-polyfill channel publish of their CJS exports appended.
8
18
  *
9
19
  * @param {string} source
20
+ * @param {string|object} [inputSourceMap]
10
21
  */
11
- module.exports = function loader (source) {
22
+ module.exports = function loader (source, inputSourceMap) {
12
23
  this.cacheable(false)
13
- const { pkg, version, path: pkgPath } = this.getOptions()
24
+ const { pkg, version, path: pkgPath, rewriteOnly } = this.getOptions()
25
+ const target = getRewriteTarget(this.resourcePath.replaceAll('\\', '/'))
26
+
27
+ let code = source
28
+ let map = inputSourceMap
29
+
30
+ if (target) {
31
+ rewrite ??= createBundlerRewriter(DC_MODULE)
32
+ const result = rewrite(source, this.resourcePath, 'commonjs', target, inputSourceMap)
33
+ code = result.code
34
+ map = result.map
35
+
36
+ if (code !== source && target.activate) {
37
+ const payload = JSON.stringify({
38
+ activate: true,
39
+ package: target.moduleName,
40
+ version,
41
+ path: filename(target.moduleName, target.filePath),
42
+ })
43
+ this.callback(
44
+ null,
45
+ `${code}\n;{\n` +
46
+ ` const __dd_ch = require(${DC_MODULE_LITERAL}).channel('${CHANNEL}');\n` +
47
+ ` if (__dd_ch.hasSubscribers) __dd_ch.publish(${payload});\n` +
48
+ '}\n',
49
+ map
50
+ )
51
+ return
52
+ }
53
+ }
54
+
55
+ // Rewrite-only targets have no export hook, so publishing their exports would only report a missing hook.
56
+ if (rewriteOnly) {
57
+ this.callback(null, code, map)
58
+ return
59
+ }
14
60
 
15
- return (
16
- source +
61
+ this.callback(
62
+ null,
63
+ code +
17
64
  '\n;{\n' +
18
- ' const __dd_dc = require(\'dc-polyfill\');\n' +
65
+ ` const __dd_dc = require(${DC_MODULE_LITERAL});\n` +
19
66
  ` const __dd_ch = __dd_dc.channel('${CHANNEL}');\n` +
20
67
  ' const __dd_mod = module.exports;\n' +
21
68
  ` const __dd_payload = { module: __dd_mod, version: '${version}', package: '${pkg}', path: '${pkgPath}' };\n` +
22
69
  ' __dd_ch.publish(__dd_payload);\n' +
23
70
  ' module.exports = __dd_payload.module;\n' +
24
- '}\n'
71
+ '}\n',
72
+ map
25
73
  )
26
74
  }
@@ -1,10 +1,24 @@
1
1
  'use strict'
2
2
 
3
- const { HTTP_CLIENT_IP, HTTP_USERAGENT, NETWORK_CLIENT_IP } = require('../../../../ext/tags')
3
+ const {
4
+ ERROR,
5
+ HTTP_CLIENT_IP,
6
+ HTTP_USERAGENT,
7
+ MANUAL_DROP,
8
+ MANUAL_KEEP,
9
+ NETWORK_CLIENT_IP,
10
+ RESOURCE_NAME,
11
+ SAMPLING_PRIORITY,
12
+ SERVICE_NAME,
13
+ SPAN_KIND,
14
+ SPAN_TYPE,
15
+ } = require('../../../../ext/tags')
4
16
  const createRfdc = require('../../../../vendor/dist/rfdc')
5
17
  const clone = createRfdc({ proto: false, circles: false })
6
18
  const { USER_ID, USER_SESSION_ID } = require('../appsec/addresses')
7
19
  const { getActiveRequest } = require('../appsec/store')
20
+ const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE, PROCESS_ID, SAMPLING_PRIORITY_KEY } = require('../constants')
21
+ const log = require('../log')
8
22
  const { keepTrace } = require('../priority_sampler')
9
23
  const { extractIp } = require('../plugins/util/ip_extractor')
10
24
  const { AI_GUARD } = require('../standalone/product')
@@ -20,6 +34,12 @@ const ALLOW = 'ALLOW'
20
34
  /** @typedef {import('../../../../index').aiguard.RedactionReplacement} RedactionReplacement */
21
35
  /** @typedef {import('../opentracing/span')} Span */
22
36
 
37
+ /**
38
+ * @typedef {object} ExtraEntry
39
+ * @property {string} key
40
+ * @property {unknown} value
41
+ */
42
+
23
43
  /**
24
44
  * @typedef {object} EvaluationResponse
25
45
  * @property {string} action
@@ -30,6 +50,8 @@ const ALLOW = 'ALLOW'
30
50
  * @property {boolean} hasTagProbabilities
31
51
  * @property {boolean} blockingEnabled
32
52
  * @property {unknown} redactionReplacements
53
+ * @property {ExtraEntry[]} extraTags
54
+ * @property {ExtraEntry[]} extraMetastruct
33
55
  */
34
56
 
35
57
  /**
@@ -40,6 +62,8 @@ const ALLOW = 'ALLOW'
40
62
  * @property {boolean} shouldBlock
41
63
  * @property {boolean} hasTagProbabilities
42
64
  * @property {{ enabled: boolean, applied: boolean, failures: number }} redaction
65
+ * @property {ExtraEntry[]} extraTags
66
+ * @property {ExtraEntry[]} extraMetastruct
43
67
  */
44
68
 
45
69
  /**
@@ -68,6 +92,38 @@ function isRecord (value) {
68
92
  return value !== null && typeof value === 'object' && !Array.isArray(value)
69
93
  }
70
94
 
95
+ // Shared by every evaluation without extra entries, so the common case allocates nothing.
96
+ const NO_EXTRA_ENTRIES = /** @type {ExtraEntry[]} */ (/** @type {unknown} */ (Object.freeze([])))
97
+
98
+ /**
99
+ * Normalizes a backend extra field into well-formed key/value entries. The backend sends lists of
100
+ * entries rather than objects because the dotted keys are not valid JSON:API member names. Values
101
+ * are kept as-is: which of them reach the span is decided when they are applied.
102
+ *
103
+ * @param {string} field
104
+ * @param {unknown} entries
105
+ * @returns {ExtraEntry[]}
106
+ */
107
+ function normalizeExtraEntries (field, entries) {
108
+ if (entries === undefined) return NO_EXTRA_ENTRIES
109
+ if (!Array.isArray(entries)) {
110
+ log.debug('AI Guard: ignoring %s of type %s', field, entries === null ? 'null' : typeof entries)
111
+ return NO_EXTRA_ENTRIES
112
+ }
113
+
114
+ const normalized = []
115
+ for (const entry of entries) {
116
+ const key = isRecord(entry) ? entry.key : undefined
117
+ // A __proto__ key is never valid: it cannot become an own property on the span tags or the meta-struct.
118
+ if (typeof key === 'string' && key.length > 0 && key !== '__proto__') {
119
+ normalized.push({ key, value: entry.value })
120
+ } else {
121
+ log.debug('AI Guard: ignoring malformed %s entry', field)
122
+ }
123
+ }
124
+ return normalized.length === 0 ? NO_EXTRA_ENTRIES : normalized
125
+ }
126
+
71
127
  /**
72
128
  * Parses the backend evaluation response into the tracer's internal contract.
73
129
  *
@@ -89,6 +145,9 @@ function parseEvaluationResponse (body) {
89
145
  hasTagProbabilities: isRecord(attributes.tag_probs),
90
146
  blockingEnabled: attributes.is_blocking_enabled === true,
91
147
  redactionReplacements: attributes.redaction_replacements,
148
+ // Malformed extra entries are dropped here instead of turning the response into a bad_response.
149
+ extraTags: normalizeExtraEntries('extra_tags', attributes.extra_tags),
150
+ extraMetastruct: normalizeExtraEntries('extra_metastruct', attributes.extra_metastruct),
92
151
  }
93
152
  }
94
153
 
@@ -132,6 +191,8 @@ function createEvaluationOutcome (messages, evaluation, options) {
132
191
  applied: redaction.redacted,
133
192
  failures: redaction.failures,
134
193
  },
194
+ extraTags: evaluation.extraTags,
195
+ extraMetastruct: evaluation.extraMetastruct,
135
196
  }
136
197
  }
137
198
 
@@ -147,6 +208,86 @@ const SERVICE_ENTRY_TAG_MAPPINGS = [
147
208
  [USER_SESSION_ID, TAGS.USR_SESSION_ID_TAG_KEY],
148
209
  ]
149
210
 
211
+ // Backend extra tags never override what the tracer computes itself, nor keys that control the
212
+ // span or its trace (sampling, service, resource, error state). Internal _dd keys are covered by
213
+ // isReservedExtraTag instead of being listed here.
214
+ const RESERVED_EXTRA_TAGS = new Set([
215
+ TAGS.ACTION_TAG_KEY,
216
+ TAGS.REASON_TAG_KEY,
217
+ TAGS.TARGET_TAG_KEY,
218
+ TAGS.TOOL_NAME_TAG_KEY,
219
+ TAGS.BLOCKED_TAG_KEY,
220
+ TAGS.REDACTED_TAG_KEY,
221
+ TAGS.EVENT_TAG_KEY,
222
+ ...SERVICE_ENTRY_TAG_MAPPINGS.map(([, destinationTag]) => destinationTag),
223
+ MANUAL_KEEP,
224
+ MANUAL_DROP,
225
+ SAMPLING_PRIORITY,
226
+ SERVICE_NAME,
227
+ RESOURCE_NAME,
228
+ SPAN_TYPE,
229
+ SPAN_KIND,
230
+ ERROR,
231
+ ERROR_TYPE,
232
+ ERROR_MESSAGE,
233
+ ERROR_STACK,
234
+ SAMPLING_PRIORITY_KEY,
235
+ PROCESS_ID,
236
+ 'analytics.event',
237
+ 'language',
238
+ 'runtime-id',
239
+ 'service.version',
240
+ 'version',
241
+ 'env',
242
+ ])
243
+
244
+ /**
245
+ * Returns whether a backend extra tag key must not be set on the span.
246
+ *
247
+ * @param {string} key
248
+ */
249
+ function isReservedExtraTag (key) {
250
+ // _dd is the tracer's internal namespace (_dd.*, _dd1.*): the span formatter writes sampling,
251
+ // top-level and propagation keys there and a span tag with the same key would overwrite them.
252
+ // This is not an AI Guard namespace filter; the backend owns ai_guard.* keys.
253
+ return key.startsWith('_dd') || RESERVED_EXTRA_TAGS.has(key)
254
+ }
255
+
256
+ const RESERVED_EXTRA_META_STRUCT_KEYS = new Set(['messages', 'attack_categories', 'sds', 'tag_probs'])
257
+
258
+ /**
259
+ * Copies backend-provided extra_tags to the span and extra_metastruct to the AI Guard meta-struct,
260
+ * skipping values and keys the span must not receive. Keys are used verbatim and entries are
261
+ * applied in order, so a repeated key keeps its last value.
262
+ *
263
+ * @param {Span} span
264
+ * @param {EvaluationMetaStruct} metaStruct
265
+ * @param {ExtraEntry[]} extraTags
266
+ * @param {ExtraEntry[]} extraMetastruct
267
+ */
268
+ function applyExtraSpanData (span, metaStruct, extraTags, extraMetastruct) {
269
+ // The client decodes values with JSON.parse, so a string check and a null check cover every JSON type.
270
+ for (const { key, value } of extraTags) {
271
+ if (typeof value !== 'string') {
272
+ log.debug('AI Guard: ignoring extra_tags entry %s with non-string value', key)
273
+ } else if (isReservedExtraTag(key)) {
274
+ log.debug('AI Guard: ignoring reserved extra tag %s', key)
275
+ } else {
276
+ span.setTag(key, value)
277
+ }
278
+ }
279
+
280
+ for (const { key, value } of extraMetastruct) {
281
+ if (value === undefined || value === null) {
282
+ log.debug('AI Guard: ignoring extra_metastruct entry %s with null value', key)
283
+ } else if (RESERVED_EXTRA_META_STRUCT_KEYS.has(key)) {
284
+ log.debug('AI Guard: ignoring reserved extra_metastruct key %s', key)
285
+ } else {
286
+ metaStruct[key] = value
287
+ }
288
+ }
289
+ }
290
+
150
291
  class EvaluationReporter {
151
292
  #config
152
293
  #maxMessagesLength
@@ -230,6 +371,7 @@ class EvaluationReporter {
230
371
  if (result.tags.length > 0) report.metaStruct.attack_categories = result.tags
231
372
  if (result.sds.length > 0) report.metaStruct.sds = result.sds
232
373
  if (outcome.hasTagProbabilities) report.metaStruct.tag_probs = result.tagProbabilities
374
+ applyExtraSpanData(report.span, report.metaStruct, outcome.extraTags, outcome.extraMetastruct)
233
375
 
234
376
  if (redaction.enabled) {
235
377
  report.span.setTag(TAGS.REDACTED_TAG_KEY, redaction.applied ? 'true' : 'false')
@@ -73,8 +73,7 @@ function onModelIntercept (ctx) {
73
73
  null,
74
74
  'AIGuard: unable to decode the model result: %s'
75
75
  )
76
- if (outputMessages === null) return result
77
- if (!outputMessages.length) return result
76
+ if (outputMessages === null || !outputMessages.length) return result
78
77
 
79
78
  return evaluate(ctx, aiguard, [outputMessages], opts).then(() => result)
80
79
  }
@@ -112,8 +111,7 @@ function interceptStreamedResult (ctx, result, inputMessages) {
112
111
  null,
113
112
  'AIGuard: unable to decode the streamed model result: %s'
114
113
  )
115
- if (outputMessages === null) return replayed
116
- if (!outputMessages.length) return replayed
114
+ if (outputMessages === null || !outputMessages.length) return replayed
117
115
 
118
116
  return evaluate(ctx, aiguard, [outputMessages], opts).then(() => replayed)
119
117
  })
@@ -10,8 +10,7 @@ const IMAGE_FALLBACK = '[image]'
10
10
  * @returns {string|undefined|null}
11
11
  */
12
12
  function stringifyIfNeeded (value) {
13
- if (value == null) return value
14
- if (typeof value === 'string') return value
13
+ if (value == null || typeof value === 'string') return value
15
14
 
16
15
  try {
17
16
  return JSON.stringify(value)
@@ -102,8 +102,14 @@ function resolveWritableString (root, path) {
102
102
  }
103
103
  }
104
104
 
105
- if (!node || typeof node !== 'object') return
106
- if (!Object.hasOwn(node, terminal.name) || typeof node[terminal.name] !== 'string') return
105
+ if (
106
+ !node ||
107
+ typeof node !== 'object' ||
108
+ !Object.hasOwn(node, terminal.name) ||
109
+ typeof node[terminal.name] !== 'string'
110
+ ) {
111
+ return
112
+ }
107
113
  return { container: node, key: terminal.name }
108
114
  }
109
115
 
@@ -383,8 +383,9 @@ function presenceBitmask (optionalGroups, present) {
383
383
  * @returns {string|null}
384
384
  */
385
385
  function normalizeRouteExpress (route, params, urlPath, parse, makeMatcher) {
386
- if (typeof route !== 'string' || !route) return null
387
- if (typeof parse !== 'function' || typeof makeMatcher !== 'function') return null
386
+ if (typeof route !== 'string' || !route || typeof parse !== 'function' || typeof makeMatcher !== 'function') {
387
+ return null
388
+ }
388
389
 
389
390
  let entry = routeCache.get(route)
390
391
  if (entry === undefined) {