dd-trace 6.14.0 → 6.16.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 (98) hide show
  1. package/README.md +15 -0
  2. package/ci/test-optimization-validation/framework-adapters/cucumber.js +4 -2
  3. package/ci/test-optimization-validation/package-check.js +4 -1
  4. package/ci/test-optimization-validation/scenarios/ci-wiring.js +1 -1
  5. package/index.d.ts +12 -0
  6. package/next.d.ts +27 -0
  7. package/next.js +3 -0
  8. package/package.json +17 -13
  9. package/packages/datadog-esbuild/index.js +14 -1
  10. package/packages/datadog-esbuild/src/resolver.js +284 -0
  11. package/packages/datadog-esbuild/src/utils.js +163 -119
  12. package/packages/datadog-instrumentations/src/ai.js +39 -97
  13. package/packages/datadog-instrumentations/src/anthropic.js +76 -134
  14. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +1 -1
  15. package/packages/datadog-instrumentations/src/azure-cosmos.js +1 -1
  16. package/packages/datadog-instrumentations/src/azure-durable-functions.js +23 -9
  17. package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
  18. package/packages/datadog-instrumentations/src/bullmq.js +1 -1
  19. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  20. package/packages/datadog-instrumentations/src/dd-trace-api.js +2 -2
  21. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  22. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +39 -14
  23. package/packages/datadog-instrumentations/src/helpers/instrument.js +25 -8
  24. package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +72 -0
  25. package/packages/datadog-instrumentations/src/helpers/register.js +9 -64
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +46 -14
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-durable-functions.js +17 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +6 -7
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +99 -70
  33. package/packages/datadog-instrumentations/src/jest.js +141 -25
  34. package/packages/datadog-instrumentations/src/langchain.js +1 -1
  35. package/packages/datadog-instrumentations/src/langgraph.js +1 -1
  36. package/packages/datadog-instrumentations/src/mercurius.js +1 -1
  37. package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
  38. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +1 -1
  39. package/packages/datadog-instrumentations/src/openai-agents.js +1 -1
  40. package/packages/datadog-instrumentations/src/openai.js +54 -76
  41. package/packages/datadog-instrumentations/src/playwright.js +4 -0
  42. package/packages/datadog-instrumentations/src/vitest-worker.js +7 -10
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +7 -1
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +1 -1
  45. package/packages/datadog-plugin-azure-durable-functions/src/index.js +96 -1
  46. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -24
  47. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -36
  48. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-request-context.js +31 -0
  49. package/packages/datadog-plugin-graphql/src/resolve-error.js +33 -11
  50. package/packages/datadog-turbopack/index.js +212 -0
  51. package/packages/datadog-turbopack/src/constants.js +5 -0
  52. package/packages/datadog-turbopack/src/loader.js +500 -0
  53. package/packages/dd-trace/src/aiguard/index.js +2 -1
  54. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +53 -25
  55. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +49 -25
  56. package/packages/dd-trace/src/aiguard/integrations/index.js +3 -2
  57. package/packages/dd-trace/src/aiguard/integrations/openai.js +59 -30
  58. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +116 -42
  59. package/packages/dd-trace/src/aiguard/messages/utils.js +29 -1
  60. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +25 -0
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  62. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
  63. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +2 -2
  64. package/packages/dd-trace/src/appsec/telemetry/api_security.js +1 -3
  65. package/packages/dd-trace/src/appsec/telemetry/common.js +4 -0
  66. package/packages/dd-trace/src/appsec/telemetry/index.js +23 -5
  67. package/packages/dd-trace/src/appsec/telemetry/rasp.js +1 -20
  68. package/packages/dd-trace/src/appsec/telemetry/user.js +1 -3
  69. package/packages/dd-trace/src/appsec/telemetry/waf.js +1 -20
  70. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  71. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
  72. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  73. package/packages/dd-trace/src/config/helper.js +10 -2
  74. package/packages/dd-trace/src/config/index.js +20 -5
  75. package/packages/dd-trace/src/config/supported-configurations.json +10 -0
  76. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  77. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +14 -1
  78. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
  79. package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
  80. package/packages/dd-trace/src/evp_proxy/discovery.js +3 -4
  81. package/packages/dd-trace/src/evp_proxy/path.js +12 -3
  82. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
  83. package/packages/dd-trace/src/exporters/common/docker.js +4 -1
  84. package/packages/dd-trace/src/exporters/common/proxy.js +55 -0
  85. package/packages/dd-trace/src/exporters/common/request.js +11 -3
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  87. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +5 -4
  88. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +106 -25
  89. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +12 -1
  90. package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
  91. package/packages/dd-trace/src/plugin_manager.js +9 -3
  92. package/packages/dd-trace/src/plugins/index.js +1 -0
  93. package/packages/dd-trace/src/plugins/util/test.js +4 -1
  94. package/packages/dd-trace/src/priority_sampler.js +4 -0
  95. package/packages/dd-trace/src/sampling_rule.js +3 -1
  96. package/packages/dd-trace/src/span_processor.js +18 -3
  97. package/packages/dd-trace/src/telemetry/send-data.js +5 -4
  98. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +0 -10
@@ -0,0 +1,500 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const Module = require('node:module')
5
+ const path = require('node:path')
6
+
7
+ const extractPackageAndModulePath = require(
8
+ '../../datadog-instrumentations/src/helpers/extract-package-and-module-path'
9
+ )
10
+ const hooks = require('../../datadog-instrumentations/src/helpers/hooks')
11
+ const instrumentations = require('../../datadog-instrumentations/src/helpers/instrumentations')
12
+ const { filename, matchesInstrumentation } = require('../../datadog-instrumentations/src/helpers/instrumentation-utils')
13
+ const { createBundlerRewriter } = require('../../datadog-instrumentations/src/helpers/rewriter')
14
+ const { getRewriteTarget } = require('../../datadog-instrumentations/src/helpers/rewriter/targets')
15
+ const { isESMFile } = require('../../datadog-esbuild/src/utils')
16
+ const { SYNTHETIC_EXTENSION } = require('./constants')
17
+
18
+ const CHANNEL = 'dd-trace:bundler:load'
19
+ const ARGUMENTS_BINDING = 1
20
+ const MODULE_BINDING = 2
21
+ const REQUIRE_BINDING = 4
22
+ const COMMONJS_BINDINGS = MODULE_BINDING | REQUIRE_BINDING
23
+ const targetPackages = new Set(Object.keys(hooks))
24
+ const entrypoints = new Map()
25
+ const loadedHooks = new Set()
26
+ const packageCache = new Map()
27
+ const rewriters = new Map()
28
+
29
+ /**
30
+ * @typedef {object} LoaderContext
31
+ * @property {(error: Error|undefined, code?: string, sourceMap?: string|object) => void} callback
32
+ * @property {(warning: Error) => void} [emitWarning]
33
+ * @property {string} resourcePath
34
+ */
35
+
36
+ /**
37
+ * @typedef {object} PackageInfo
38
+ * @property {string} [entrypoint]
39
+ * @property {boolean} esm
40
+ * @property {string} moduleName
41
+ * @property {string} name
42
+ * @property {string} packageJsonPath
43
+ * @property {string} version
44
+ */
45
+
46
+ /**
47
+ * Instruments a resolved foreign Node.js module.
48
+ *
49
+ * @this {LoaderContext}
50
+ * @param {string} source
51
+ * @param {string|object} [inputSourceMap]
52
+ */
53
+ module.exports = function loader (source, inputSourceMap) {
54
+ try {
55
+ const nativeResourcePath = fs.realpathSync(getResourcePath(this))
56
+ const resourcePath = nativeResourcePath.replaceAll('\\', '/')
57
+ const extracted = extractPackageAndModulePath(resourcePath)
58
+ if (!extracted.pkg || !targetPackages.has(extracted.pkg)) {
59
+ this.callback(undefined, source, inputSourceMap)
60
+ return
61
+ }
62
+
63
+ const rewriteTarget = getRewriteTarget(resourcePath)
64
+ const packageInfo = getPackageInfo(
65
+ nativeResourcePath,
66
+ extracted.pkg,
67
+ /** @type {string} */ (extracted.path),
68
+ path.normalize(/** @type {string} */ (extracted.pkgJson))
69
+ )
70
+ const esm = packageInfo.esm
71
+ const publications = esm ? [] : getPublications(resourcePath, packageInfo)
72
+ if (!rewriteTarget && publications.length === 0) {
73
+ this.callback(undefined, source, inputSourceMap)
74
+ return
75
+ }
76
+ if (publications.length > 0 && hasUnsafeCommonJsBindings(source)) {
77
+ this.emitWarning?.(new Error(`Skipped CommonJS publication for unsafe wrapper bindings in ${resourcePath}`))
78
+ this.callback(undefined, source, inputSourceMap)
79
+ return
80
+ }
81
+
82
+ const dcModule = relativeImport(path.dirname(nativeResourcePath), require.resolve('dc-polyfill'))
83
+ let code = source
84
+ let sourceMap = inputSourceMap
85
+ let rewritten = false
86
+ if (rewriteTarget) {
87
+ const rewrite = getRewriter(dcModule)
88
+ const result = rewrite(
89
+ source,
90
+ resourcePath,
91
+ esm ? 'module' : 'commonjs',
92
+ rewriteTarget,
93
+ inputSourceMap
94
+ )
95
+ code = /** @type {string} */ (result.code)
96
+ sourceMap = result.map
97
+ rewritten = code !== source
98
+ }
99
+
100
+ if (publications.length > 0) code = appendCommonJsPublications(code, publications, dcModule)
101
+ if (rewritten && publications.length === 0) {
102
+ if (!esm && hasUnsafeCommonJsBindings(code)) {
103
+ this.emitWarning?.(new Error(`Skipped CommonJS activation for unsafe wrapper bindings in ${resourcePath}`))
104
+ this.callback(undefined, source, inputSourceMap)
105
+ return
106
+ }
107
+ code = appendActivation(code, {
108
+ package: rewriteTarget.moduleName,
109
+ path: filename(rewriteTarget.moduleName, rewriteTarget.filePath),
110
+ version: packageInfo.version,
111
+ }, dcModule, esm)
112
+ }
113
+ this.callback(undefined, code, sourceMap)
114
+ } catch (error) {
115
+ const warning = error instanceof Error ? error : new Error(String(error))
116
+ this.emitWarning?.(warning)
117
+ this.callback(undefined, source, inputSourceMap)
118
+ }
119
+ }
120
+
121
+ /**
122
+ * @param {LoaderContext} context
123
+ * @returns {string}
124
+ */
125
+ function getResourcePath (context) {
126
+ const { resourcePath } = context
127
+ if (!resourcePath.endsWith(SYNTHETIC_EXTENSION) || fs.existsSync(resourcePath)) return resourcePath
128
+
129
+ const extensionlessPath = resourcePath.slice(0, -SYNTHETIC_EXTENSION.length)
130
+ return fs.existsSync(extensionlessPath) ? extensionlessPath : resourcePath
131
+ }
132
+
133
+ /**
134
+ * @param {string} nativeResourcePath
135
+ * @param {string} name
136
+ * @param {string} modulePath
137
+ * @param {string} packageJsonPath
138
+ * @returns {PackageInfo}
139
+ */
140
+ function getPackageInfo (nativeResourcePath, name, modulePath, packageJsonPath) {
141
+ let cached = packageCache.get(packageJsonPath)
142
+ if (!cached) {
143
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
144
+ cached = { packageJson, version: packageJson.version }
145
+ packageCache.set(packageJsonPath, cached)
146
+ }
147
+
148
+ return {
149
+ esm: isESMFile(nativeResourcePath, packageJsonPath, cached.packageJson),
150
+ moduleName: filename(name, modulePath),
151
+ name,
152
+ packageJsonPath,
153
+ version: cached.version,
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @param {string} resourcePath
159
+ * @param {PackageInfo} packageInfo
160
+ * @returns {Array<{ package: string, path: string, version: string }>}
161
+ */
162
+ function getPublications (resourcePath, packageInfo) {
163
+ loadPackageHook(packageInfo.name)
164
+ const entries = instrumentations[packageInfo.name]
165
+ if (!entries) return []
166
+
167
+ const publications = []
168
+ const paths = new Set()
169
+ for (const entry of entries) {
170
+ if (!entry.file && !entry.filePattern) {
171
+ packageInfo.entrypoint ??= getCommonJsEntrypoint(packageInfo.name, packageInfo.packageJsonPath)
172
+ }
173
+ const moduleName = entry.file || entry.filePattern
174
+ ? packageInfo.moduleName
175
+ : resourcePath === packageInfo.entrypoint ? packageInfo.name : undefined
176
+ if (!moduleName || paths.has(moduleName) ||
177
+ !matchesInstrumentation(packageInfo.name, packageInfo.version, moduleName, entry)) continue
178
+
179
+ paths.add(moduleName)
180
+ publications.push({ package: packageInfo.name, path: moduleName, version: packageInfo.version })
181
+ }
182
+ return publications
183
+ }
184
+
185
+ /**
186
+ * @param {string} name
187
+ * @param {string} packageJsonPath
188
+ * @returns {string}
189
+ */
190
+ function getCommonJsEntrypoint (name, packageJsonPath) {
191
+ const cached = entrypoints.get(packageJsonPath)
192
+ if (cached) return cached
193
+
194
+ const packageRequire = Module.createRequire(packageJsonPath)
195
+ let entrypoint
196
+ try {
197
+ entrypoint = packageRequire.resolve(name)
198
+ } catch {
199
+ entrypoint = packageRequire.resolve(path.dirname(packageJsonPath))
200
+ }
201
+ const resolved = fs.realpathSync(entrypoint).replaceAll('\\', '/')
202
+ entrypoints.set(packageJsonPath, resolved)
203
+ return resolved
204
+ }
205
+
206
+ /**
207
+ * @param {string} name
208
+ */
209
+ function loadPackageHook (name) {
210
+ if (loadedHooks.has(name)) return
211
+ const hook = hooks[name]
212
+ const load = hook?.fn ?? hook
213
+ if (typeof load === 'function') load()
214
+ loadedHooks.add(name)
215
+ }
216
+
217
+ /**
218
+ * @param {string} dcModule
219
+ * @returns {ReturnType<typeof createBundlerRewriter>}
220
+ */
221
+ function getRewriter (dcModule) {
222
+ const cached = rewriters.get(dcModule)
223
+ if (cached) return cached
224
+ const rewrite = createBundlerRewriter(dcModule)
225
+ rewriters.set(dcModule, rewrite)
226
+ return rewrite
227
+ }
228
+
229
+ /**
230
+ * @param {string} source
231
+ * @returns {boolean}
232
+ */
233
+ function hasUnsafeCommonJsBindings (source) {
234
+ try {
235
+ const { parse } = require('../../datadog-instrumentations/src/helpers/rewriter/compiler')
236
+ if (source.startsWith('#!')) source = `//${source.slice(2)}`
237
+ const program = parse(Module.wrap(source))
238
+ const wrapper = program.body[0]?.expression
239
+ return wrapper?.type !== 'FunctionExpression' || hasUnsafeNode(wrapper.body, 0, 0)
240
+ } catch {
241
+ return true
242
+ }
243
+ }
244
+
245
+ /**
246
+ * @param {object} node
247
+ * @param {number} functionDepth
248
+ * @param {number} shadowedBindings
249
+ * @param {object} [parent]
250
+ * @param {string} [parentKey]
251
+ * @returns {boolean}
252
+ */
253
+ function hasUnsafeNode (node, functionDepth, shadowedBindings, parent, parentKey) {
254
+ if (functionDepth === 0 && hasCommonJsDeclaration(node, parent)) return true
255
+ if (node.type === 'AssignmentExpression' && mutatesCommonJsBinding(node.left, shadowedBindings)) return true
256
+ if (node.type === 'UpdateExpression' && mutatesCommonJsBinding(node.argument, shadowedBindings)) return true
257
+ if ((node.type === 'ForInStatement' || node.type === 'ForOfStatement') &&
258
+ mutatesCommonJsBinding(node.left, shadowedBindings)) return true
259
+ if (node.type === 'CallExpression' && node.callee?.type === 'Identifier' && node.callee.name === 'eval') return true
260
+ if (!(shadowedBindings & ARGUMENTS_BINDING) && node.type === 'Identifier' && node.name === 'arguments' &&
261
+ isReferencedIdentifier(parent, parentKey)) return true
262
+
263
+ const nestedFunction = node.type === 'ArrowFunctionExpression' || node.type === 'FunctionDeclaration' ||
264
+ node.type === 'FunctionExpression'
265
+ if (nestedFunction) {
266
+ functionDepth++
267
+ shadowedBindings |= getFunctionBindings(node)
268
+ }
269
+ shadowedBindings |= getLexicalBindings(node)
270
+
271
+ for (const key of Object.keys(node)) {
272
+ if ((node.type === 'VariableDeclarator' && key === 'id') ||
273
+ (node.type === 'CatchClause' && key === 'param')) continue
274
+ const value = node[key]
275
+ if (Array.isArray(value)) {
276
+ for (const child of value) {
277
+ if (child?.type && hasUnsafeNode(child, functionDepth, shadowedBindings, node, key)) return true
278
+ }
279
+ } else if (value?.type && hasUnsafeNode(value, functionDepth, shadowedBindings, node, key)) {
280
+ return true
281
+ }
282
+ }
283
+ return false
284
+ }
285
+
286
+ /**
287
+ * @param {object} node
288
+ * @param {object} [parent]
289
+ * @returns {boolean}
290
+ */
291
+ function hasCommonJsDeclaration (node, parent) {
292
+ if (node.type === 'VariableDeclarator') {
293
+ return parent?.kind === 'var' && Boolean(getBindingMask(node.id) & COMMONJS_BINDINGS)
294
+ }
295
+ if (node.type === 'FunctionDeclaration') {
296
+ return Boolean(getBindingMask(node.id) & COMMONJS_BINDINGS)
297
+ }
298
+ return false
299
+ }
300
+
301
+ /**
302
+ * @param {object} node
303
+ * @param {number} shadowedBindings
304
+ * @returns {boolean}
305
+ */
306
+ function mutatesCommonJsBinding (node, shadowedBindings) {
307
+ return Boolean(getBindingMask(node) & COMMONJS_BINDINGS & ~shadowedBindings)
308
+ }
309
+
310
+ /**
311
+ * @param {object} node
312
+ * @returns {number}
313
+ */
314
+ function getBindingMask (node) {
315
+ if (!node) return 0
316
+ if (node.type === 'Identifier') {
317
+ if (node.name === 'arguments') return ARGUMENTS_BINDING
318
+ if (node.name === 'module') return MODULE_BINDING
319
+ return node.name === 'require' ? REQUIRE_BINDING : 0
320
+ }
321
+ if (node.type === 'AssignmentPattern') return getBindingMask(node.left)
322
+ if (node.type === 'RestElement') return getBindingMask(node.argument)
323
+ if (node.type === 'ArrayPattern') {
324
+ let bindings = 0
325
+ for (const element of node.elements) bindings |= getBindingMask(element)
326
+ return bindings
327
+ }
328
+ if (node.type === 'ObjectPattern') {
329
+ let bindings = 0
330
+ for (const property of node.properties) {
331
+ bindings |= getBindingMask(property.type === 'RestElement' ? property.argument : property.value)
332
+ }
333
+ return bindings
334
+ }
335
+ return 0
336
+ }
337
+
338
+ /**
339
+ * @param {object} node
340
+ * @returns {number}
341
+ */
342
+ function getFunctionBindings (node) {
343
+ let bindings = node.type === 'ArrowFunctionExpression' ? 0 : ARGUMENTS_BINDING
344
+ bindings |= getBindingMask(node.id)
345
+ for (const param of node.params) bindings |= getBindingMask(param)
346
+ return bindings | getVarBindings(node.body)
347
+ }
348
+
349
+ /**
350
+ * @param {object} node
351
+ * @returns {number}
352
+ */
353
+ function getVarBindings (node) {
354
+ if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionDeclaration' ||
355
+ node.type === 'FunctionExpression' || node.type === 'ClassDeclaration' || node.type === 'ClassExpression') return 0
356
+
357
+ let bindings = 0
358
+ if (node.type === 'VariableDeclaration' && node.kind === 'var') {
359
+ for (const declaration of node.declarations) bindings |= getBindingMask(declaration.id)
360
+ }
361
+ for (const key of Object.keys(node)) {
362
+ const value = node[key]
363
+ if (Array.isArray(value)) {
364
+ for (const child of value) {
365
+ if (child?.type) bindings |= getVarBindings(child)
366
+ }
367
+ } else if (value?.type) {
368
+ bindings |= getVarBindings(value)
369
+ }
370
+ }
371
+ return bindings
372
+ }
373
+
374
+ /**
375
+ * @param {object} node
376
+ * @returns {number}
377
+ */
378
+ function getLexicalBindings (node) {
379
+ let bindings = 0
380
+ if (node.type === 'BlockStatement') {
381
+ for (const statement of node.body) bindings |= getStatementBindings(statement)
382
+ } else if (node.type === 'SwitchStatement') {
383
+ for (const switchCase of node.cases) {
384
+ for (const statement of switchCase.consequent) bindings |= getStatementBindings(statement)
385
+ }
386
+ } else if (node.type === 'CatchClause') {
387
+ bindings = getBindingMask(node.param)
388
+ } else if (node.type === 'ForStatement') {
389
+ bindings = getLexicalDeclarationBindings(node.init)
390
+ } else if (node.type === 'ForInStatement' || node.type === 'ForOfStatement') {
391
+ bindings = getLexicalDeclarationBindings(node.left)
392
+ } else if (node.type === 'ClassExpression') {
393
+ bindings = getBindingMask(node.id)
394
+ }
395
+ return bindings
396
+ }
397
+
398
+ /**
399
+ * @param {object} node
400
+ * @returns {number}
401
+ */
402
+ function getStatementBindings (node) {
403
+ if (node.type === 'VariableDeclaration') return getLexicalDeclarationBindings(node)
404
+ if (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') return getBindingMask(node.id)
405
+ return 0
406
+ }
407
+
408
+ /**
409
+ * @param {object} [node]
410
+ * @returns {number}
411
+ */
412
+ function getLexicalDeclarationBindings (node) {
413
+ if (node?.type !== 'VariableDeclaration' || node.kind === 'var') return 0
414
+ let bindings = 0
415
+ for (const declaration of node.declarations) bindings |= getBindingMask(declaration.id)
416
+ return bindings
417
+ }
418
+
419
+ /**
420
+ * @param {object} parent
421
+ * @param {string} parentKey
422
+ * @returns {boolean}
423
+ */
424
+ function isReferencedIdentifier (parent, parentKey) {
425
+ if (parent.type === 'MemberExpression' && parentKey === 'property' && !parent.computed) return false
426
+ if ((parent.type === 'Property' || parent.type === 'MethodDefinition' || parent.type === 'PropertyDefinition') &&
427
+ parentKey === 'key' && !parent.computed) {
428
+ return parent.type === 'Property' && parent.shorthand
429
+ }
430
+ return parentKey !== 'label'
431
+ }
432
+
433
+ /**
434
+ * @param {string} source
435
+ * @param {Array<{ package: string, path: string, version: string }>} payloads
436
+ * @param {string} dcModule
437
+ * @returns {string}
438
+ */
439
+ function appendCommonJsPublications (source, payloads, dcModule) {
440
+ let publications = ''
441
+ let index = 0
442
+ for (const payload of payloads) {
443
+ publications += ` const payload${index} = {
444
+ module: module.exports,
445
+ package: ${JSON.stringify(payload.package)},
446
+ path: ${JSON.stringify(payload.path)},
447
+ version: ${JSON.stringify(payload.version)},
448
+ }
449
+ channel.publish(payload${index})
450
+ module.exports = payload${index++}.module
451
+ `
452
+ }
453
+
454
+ return `${source}
455
+ {
456
+ const dc = require(${JSON.stringify(dcModule)})
457
+ const channel = dc.channel('${CHANNEL}')
458
+ if (channel.hasSubscribers) {
459
+ ${publications} }
460
+ }
461
+ `
462
+ }
463
+
464
+ /**
465
+ * @param {string} source
466
+ * @param {{ package: string, path: string, version: string }} payload
467
+ * @param {string} dcModule
468
+ * @param {boolean} esm
469
+ * @returns {string}
470
+ */
471
+ function appendActivation (source, payload, dcModule, esm) {
472
+ if (esm) {
473
+ return `${source}
474
+ import ddTraceTurbopackDc from ${JSON.stringify(dcModule)}
475
+ {
476
+ const channel = ddTraceTurbopackDc.channel('${CHANNEL}')
477
+ if (channel.hasSubscribers) channel.publish(${JSON.stringify({ activate: true, ...payload })})
478
+ }
479
+ `
480
+ }
481
+
482
+ return `${source}
483
+ {
484
+ const dc = require(${JSON.stringify(dcModule)})
485
+ const channel = dc.channel('${CHANNEL}')
486
+ if (channel.hasSubscribers) channel.publish(${JSON.stringify({ activate: true, ...payload })})
487
+ }
488
+ `
489
+ }
490
+
491
+ /**
492
+ * @param {string} from
493
+ * @param {string} to
494
+ * @returns {string}
495
+ */
496
+ function relativeImport (from, to) {
497
+ let value = path.relative(from, to).replaceAll('\\', '/')
498
+ if (!value.startsWith('.')) value = `./${value}`
499
+ return value
500
+ }
@@ -18,9 +18,10 @@ function enable (tracer, config) {
18
18
  try {
19
19
  aiguard = new AIGuard(tracer, config)
20
20
  const block = config.experimental?.aiguard?.block !== false
21
+ const analyzeStreamResponses = config.DD_AI_GUARD_ANALYZE_STREAM_RESPONSES_ENABLED
21
22
 
22
23
  incomingHttpRequestStart.subscribe(onIncomingHttpRequestStart)
23
- integrations.enable(aiguard, block)
24
+ integrations.enable(aiguard, block, analyzeStreamResponses)
24
25
 
25
26
  isEnabled = true
26
27
  } catch (err) {
@@ -2,20 +2,20 @@
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
4
 
5
- const log = require('../../log')
6
5
  const { getMessagesInputMessages, getMessagesOutputMessages } = require('../messages/anthropic')
6
+ const { decode } = require('../messages/utils')
7
7
  const { SOURCE_AUTO } = require('../tags')
8
- const { pushEvaluation } = require('./evaluate')
8
+ const { evaluate } = require('./evaluate')
9
9
 
10
- const messagesBeforeChannel = channel('dd-trace:anthropic:messages:before')
11
- const messagesAfterChannel = channel('dd-trace:anthropic:messages:after')
10
+ const messagesPrepareChannel = channel('dd-trace:anthropic:messages:prepare')
11
+ const messagesInterceptChannel = channel('dd-trace:anthropic:messages:intercept')
12
12
 
13
13
  let isEnabled = false
14
14
  let aiguard
15
15
  let opts
16
16
 
17
17
  /**
18
- * Subscribes AI Guard to Anthropic lifecycle channels.
18
+ * Subscribes AI Guard to the Anthropic interception channels.
19
19
  *
20
20
  * @param {object} aiguardInstance
21
21
  * @param {boolean} block
@@ -26,8 +26,8 @@ function enable (aiguardInstance, block) {
26
26
  aiguard = aiguardInstance
27
27
  opts = { block, source: SOURCE_AUTO, integration: 'anthropic' }
28
28
 
29
- messagesBeforeChannel.subscribe(onMessagesBefore)
30
- messagesAfterChannel.subscribe(onMessagesAfter)
29
+ messagesPrepareChannel.subscribe(onMessagesPrepare)
30
+ messagesInterceptChannel.subscribe(onMessagesIntercept)
31
31
 
32
32
  isEnabled = true
33
33
  }
@@ -35,36 +35,64 @@ function enable (aiguardInstance, block) {
35
35
  function disable () {
36
36
  if (!isEnabled) return
37
37
 
38
- messagesBeforeChannel.unsubscribe(onMessagesBefore)
39
- messagesAfterChannel.unsubscribe(onMessagesAfter)
38
+ messagesPrepareChannel.unsubscribe(onMessagesPrepare)
39
+ messagesInterceptChannel.unsubscribe(onMessagesIntercept)
40
40
 
41
41
  aiguard = undefined
42
42
  opts = undefined
43
43
  isEnabled = false
44
44
  }
45
45
 
46
- function onMessagesBefore (ctx) {
47
- pushEvaluation(ctx, aiguard, getMessagesInputMessages(ctx.args?.[0]), opts)
46
+ /**
47
+ * Replaces the outgoing options with a JSON snapshot of the evaluated fields, so AI Guard judges
48
+ * exactly what the SDK serializes and sends, immune to later caller mutation.
49
+ *
50
+ * @param {{ arguments: Array<unknown> }} ctx
51
+ */
52
+ function onMessagesPrepare (ctx) {
53
+ const options = ctx.arguments[0]
54
+ if (!options || typeof options !== 'object') return
55
+
56
+ const evaluated = { messages: options.messages }
57
+ if (options.system !== undefined) evaluated.system = options.system
58
+
59
+ try {
60
+ // eslint-disable-next-line unicorn/prefer-structured-clone
61
+ ctx.arguments[0] = { ...options, ...JSON.parse(JSON.stringify(evaluated)) }
62
+ } catch {
63
+ // Unserializable input — judge the caller's options as they are.
64
+ }
48
65
  }
49
66
 
50
- function onMessagesAfter (ctx) {
51
- const inputMessages = getMessagesInputMessages(ctx.args?.[0])
67
+ function onMessagesIntercept (ctx) {
68
+ const inputMessages = getMessagesInputMessages(ctx.arguments?.[0])
52
69
  if (!inputMessages?.length) return
53
70
 
54
- let body = ctx.body
55
- if (typeof body === 'string') {
56
- try {
57
- body = JSON.parse(body)
58
- } catch {
59
- log.error('AIGuard: unable to decode Anthropic response body')
60
- return
61
- }
71
+ // `parse` and `asResponse` are both wrapped, and either may run more than once per call.
72
+ let inputEvaluation
73
+ ctx.beforeResult = () => {
74
+ if (!isEnabled) return
75
+ inputEvaluation ??= evaluate(ctx, aiguard, [inputMessages], opts)
76
+ return inputEvaluation
62
77
  }
63
78
 
64
- const outputMessages = getMessagesOutputMessages(body)
65
- if (!outputMessages.length) return
66
-
67
- pushEvaluation(ctx, aiguard, [...inputMessages, ...outputMessages], opts)
79
+ // One model call has one output however many readers observe it: `parse`, `json()`, `text()`
80
+ // and every `clone()` of the raw response share this callback.
81
+ let outputEvaluation
82
+ ctx.onResult = body => {
83
+ if (!isEnabled) return body
84
+
85
+ const outputMessages = decode(
86
+ () => getMessagesOutputMessages(body),
87
+ null,
88
+ 'AIGuard: unable to decode Anthropic response body: %s'
89
+ )
90
+ if (outputMessages === null) return body
91
+ if (!outputMessages.length) return body
92
+
93
+ outputEvaluation ??= evaluate(ctx, aiguard, [[...inputMessages, ...outputMessages]], opts)
94
+ return outputEvaluation.then(() => body)
95
+ }
68
96
  }
69
97
 
70
98
  module.exports = { enable, disable }