dd-trace 6.0.0 → 6.2.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/LICENSE-3rdparty.csv +1 -2
  2. package/index.d.ts +28 -1
  3. package/initialize.mjs +4 -2
  4. package/package.json +34 -30
  5. package/packages/datadog-instrumentations/src/ai.js +45 -0
  6. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  7. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  8. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  10. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  11. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
  19. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
  20. package/packages/datadog-instrumentations/src/jest.js +713 -63
  21. package/packages/datadog-instrumentations/src/mercurius.js +11 -0
  22. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  24. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  25. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  26. package/packages/datadog-instrumentations/src/mongoose.js +4 -5
  27. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
  28. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  29. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
  30. package/packages/datadog-instrumentations/src/vitest-main.js +26 -1
  31. package/packages/datadog-instrumentations/src/vitest-util.js +2 -0
  32. package/packages/datadog-instrumentations/src/vitest-worker.js +103 -34
  33. package/packages/datadog-plugin-ai/src/index.js +1 -1
  34. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  35. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  36. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
  37. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  38. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  39. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  40. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  41. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  42. package/packages/datadog-plugin-cucumber/src/index.js +14 -23
  43. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
  44. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  45. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  46. package/packages/datadog-plugin-graphql/src/execute.js +25 -25
  47. package/packages/datadog-plugin-graphql/src/index.js +23 -1
  48. package/packages/datadog-plugin-graphql/src/request.js +104 -0
  49. package/packages/datadog-plugin-graphql/src/utils.js +186 -2
  50. package/packages/datadog-plugin-graphql/src/validate.js +25 -2
  51. package/packages/datadog-plugin-jest/src/index.js +30 -14
  52. package/packages/datadog-plugin-mocha/src/index.js +39 -14
  53. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  54. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  55. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  56. package/packages/datadog-plugin-playwright/src/index.js +3 -2
  57. package/packages/datadog-plugin-vitest/src/index.js +43 -9
  58. package/packages/dd-trace/src/aiguard/index.js +9 -14
  59. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  60. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  61. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  62. package/packages/dd-trace/src/appsec/graphql.js +9 -6
  63. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
  64. package/packages/dd-trace/src/appsec/lambda.js +8 -8
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  66. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  67. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
  69. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
  70. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  71. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  72. package/packages/dd-trace/src/config/supported-configurations.json +37 -0
  73. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  74. package/packages/dd-trace/src/datastreams/writer.js +14 -2
  75. package/packages/dd-trace/src/encode/0.4.js +23 -2
  76. package/packages/dd-trace/src/encode/0.5.js +12 -1
  77. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
  78. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
  79. package/packages/dd-trace/src/exporters/common/writer.js +17 -1
  80. package/packages/dd-trace/src/guardrails/index.js +3 -1
  81. package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
  82. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  83. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  84. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  85. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  86. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  87. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  88. package/packages/dd-trace/src/msgpack/chunk.js +33 -1
  89. package/packages/dd-trace/src/msgpack/index.js +6 -1
  90. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  91. package/packages/dd-trace/src/plugin_manager.js +16 -0
  92. package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
  93. package/packages/dd-trace/src/plugins/index.js +5 -0
  94. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  95. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  96. package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
  97. package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
  98. package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
@@ -0,0 +1,452 @@
1
+ 'use strict'
2
+
3
+ const { isMap, isRegExp } = require('node:util').types
4
+
5
+ const DatabasePlugin = require('../../dd-trace/src/plugins/database')
6
+
7
+ class MongodbCoreQueryPlugin extends DatabasePlugin {
8
+ static id = 'mongodb-core'
9
+ static component = 'mongodb'
10
+ // avoid using db.name for peer.service since it includes the collection name
11
+ // should be removed if one day this will be fixed
12
+ /**
13
+ * @override
14
+ */
15
+ static peerServicePrecursors = []
16
+
17
+ /**
18
+ * @override
19
+ */
20
+ configure (config) {
21
+ super.configure(config)
22
+
23
+ this.config.heartbeatEnabled = config.heartbeatEnabled ??
24
+ this._tracerConfig.DD_TRACE_MONGODB_HEARTBEAT_ENABLED
25
+ this.config.obfuscateQuery = normaliseObfuscateQuery(
26
+ config.obfuscateQuery ?? this._tracerConfig.DD_TRACE_MONGODB_OBFUSCATE_QUERY
27
+ )
28
+ }
29
+
30
+ bindStart (ctx) {
31
+ const { ns, ops, options = {}, name } = ctx
32
+ // heartbeat commands can be disabled if this.config.heartbeatEnabled is false
33
+ if (!this.config.heartbeatEnabled && isHeartbeat(ops, this.config)) {
34
+ return
35
+ }
36
+ const query = getQuery(ops, this.config.obfuscateQuery)
37
+ const resource = truncate(getResource(this, ns, query, name))
38
+ const serviceResult = this.serviceName({ pluginConfig: this.config })
39
+ const span = this.startSpan(this.operationName(), {
40
+ service: serviceResult,
41
+ resource,
42
+ type: 'mongodb',
43
+ kind: 'client',
44
+ meta: {
45
+ // this is not technically correct since it includes the collection but we changing will break customer stuff
46
+ 'db.name': ns,
47
+ 'mongodb.query': query,
48
+ 'out.host': options.host,
49
+ 'out.port': options.port,
50
+ },
51
+ }, ctx)
52
+ const comment = this.injectDbmComment(span, ops.comment, serviceResult.name)
53
+ if (comment) {
54
+ ops.comment = comment
55
+ }
56
+
57
+ return ctx.currentStore
58
+ }
59
+
60
+ /**
61
+ * @override
62
+ */
63
+ getPeerService (tags) {
64
+ let ns = tags['db.name']
65
+ if (ns && tags['peer.service'] === undefined) {
66
+ const dotIndex = ns.indexOf('.')
67
+ if (dotIndex !== -1) {
68
+ ns = ns.slice(0, dotIndex)
69
+ }
70
+ // the mongo ns is either dbName either dbName.collection. So we keep the first part
71
+ tags['peer.service'] = ns
72
+ }
73
+ return super.getPeerService(tags)
74
+ }
75
+
76
+ injectDbmComment (span, comment, serviceName) {
77
+ const dbmTraceComment = this.createDbmComment(span, serviceName)
78
+
79
+ if (!dbmTraceComment) {
80
+ return comment
81
+ }
82
+
83
+ if (comment) {
84
+ // if the command already has a comment, append the dbm trace comment
85
+ if (typeof comment === 'string') {
86
+ comment += `,${dbmTraceComment}`
87
+ } else if (Array.isArray(comment)) {
88
+ comment.push(dbmTraceComment)
89
+ } // do nothing if the comment is not a string or an array
90
+ } else {
91
+ comment = dbmTraceComment
92
+ }
93
+
94
+ return comment
95
+ }
96
+ }
97
+
98
+ const MAX_DEPTH = 10
99
+ const MAX_QUERY_LENGTH = 10_000
100
+
101
+ function extractQuery (statements) {
102
+ if (statements.length === 1 && statements[0].q) return statements[0].q
103
+
104
+ const extractedQueries = []
105
+ for (let i = 0; i < statements.length; i++) {
106
+ if (statements[i].q) {
107
+ extractedQueries.push(statements[i].q)
108
+ }
109
+ }
110
+
111
+ return extractedQueries
112
+ }
113
+
114
+ /**
115
+ * @param {Record<string, unknown> | unknown[] | undefined} cmd
116
+ * @param {'none' | 'types' | 'redact'} mode
117
+ */
118
+ function getQuery (cmd, mode) {
119
+ if (!cmd || (typeof cmd !== 'object' && !Array.isArray(cmd))) return
120
+
121
+ if (Array.isArray(cmd)) return sanitiseAndStringify(extractQuery(cmd), mode)
122
+ if (cmd.query) return sanitiseAndStringify(cmd.query, mode)
123
+ if (cmd.filter) return sanitiseAndStringify(cmd.filter, mode)
124
+ if (cmd.pipeline) return sanitiseAndStringify(cmd.pipeline, mode)
125
+ if (cmd.deletes) return sanitiseAndStringify(extractQuery(cmd.deletes), mode)
126
+ if (cmd.updates) return sanitiseAndStringify(extractQuery(cmd.updates), mode)
127
+ }
128
+
129
+ function getResource (plugin, ns, query, operationName) {
130
+ let resource = `${operationName} ${ns}`
131
+
132
+ if (plugin.config.queryInResourceName && query) {
133
+ resource += ` ${query}`
134
+ }
135
+
136
+ return resource
137
+ }
138
+
139
+ function truncate (input) {
140
+ return input.length > MAX_QUERY_LENGTH ? input.slice(0, MAX_QUERY_LENGTH) : input
141
+ }
142
+
143
+ // Depth doubles as the cycle bound: a cycle pushes past MAX_DEPTH and bails,
144
+ // after which the slow path catches it via its ancestor stack.
145
+ /** @param {unknown} input */
146
+ function canStringifyDirect (input) {
147
+ if (input === null ||
148
+ typeof input !== 'object' ||
149
+ ArrayBuffer.isView(input) ||
150
+ input._bsontype !== undefined ||
151
+ isRegExp(input) ||
152
+ isMap(input) ||
153
+ typeof input.toJSON === 'function') {
154
+ return false
155
+ }
156
+ return canStringifyDirectWalk(input, 1)
157
+ }
158
+
159
+ /**
160
+ * @param {Record<string, unknown> | unknown[]} value
161
+ * @param {number} depth
162
+ */
163
+ function canStringifyDirectWalk (value, depth) {
164
+ if (depth > MAX_DEPTH) return false
165
+ const children = Array.isArray(value) ? value : Object.values(value)
166
+ for (const child of children) {
167
+ if (child === null ||
168
+ typeof child === 'string' ||
169
+ typeof child === 'number' ||
170
+ typeof child === 'boolean') {
171
+ continue
172
+ }
173
+ if (typeof child !== 'object' ||
174
+ ArrayBuffer.isView(child) ||
175
+ child._bsontype !== undefined ||
176
+ isRegExp(child) ||
177
+ isMap(child) ||
178
+ typeof child.toJSON === 'function') {
179
+ return false
180
+ }
181
+ if (!canStringifyDirectWalk(child, depth + 1)) return false
182
+ }
183
+ return true
184
+ }
185
+
186
+ /**
187
+ * @param {Record<string, unknown> | unknown[]} input
188
+ * @param {'none' | 'types' | 'redact'} mode
189
+ */
190
+ function sanitiseAndStringify (input, mode) {
191
+ if (mode === 'none') {
192
+ if (canStringifyDirect(input)) return JSON.stringify(input)
193
+ return buildNone(input, [])
194
+ }
195
+ if (mode === 'redact') return buildRedact(input, [])
196
+ return buildTypes(input, [])
197
+ }
198
+
199
+ const REDACT_LEAF = '"?"'
200
+
201
+ /**
202
+ * @param {RegExp} value
203
+ * @returns {string}
204
+ */
205
+ function stringifyRegExp (value) {
206
+ return `{"$regex":${JSON.stringify(value.source)},"$options":${JSON.stringify(value.flags)}}`
207
+ }
208
+
209
+ /**
210
+ * @param {Record<string, unknown> | unknown[]} value
211
+ * @param {object[]} ancestors
212
+ * @returns {string | undefined}
213
+ */
214
+ function buildNone (value, ancestors) {
215
+ // ArrayBuffer views (Buffer, every TypedArray, DataView) and Binary BSON
216
+ // wrappers redact at the leaf; the walker neither recurses into the bytes
217
+ // nor invokes any custom conversion.
218
+ const bsontype = value._bsontype
219
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' ||
220
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
221
+ return REDACT_LEAF
222
+ }
223
+
224
+ if (isRegExp(value)) return stringifyRegExp(value)
225
+
226
+ // Mirror JSON.stringify's contract: when `toJSON` is present, walk its
227
+ // result (wrappers like Timestamp / Decimal128 expand to a small object,
228
+ // ObjectId / Date flatten to a primitive).
229
+ if (typeof value.toJSON === 'function') {
230
+ const json = value.toJSON()
231
+ if (json === value) return REDACT_LEAF
232
+ // JSON.stringify keeps a null result as null (an invalid Date's toJSON
233
+ // returns null); only function / symbol / undefined results drop the key.
234
+ if (json === null) return 'null'
235
+ if (typeof json !== 'object') return classifyLeafForNone(json)
236
+ // A wrapper that exposes binary state through toJSON (Buffer-backed
237
+ // class with WeakMap state, etc.) returns a TypedArray here. Re-screen
238
+ // before the per-key walk would expand it element by element.
239
+ if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
240
+ value = json
241
+ } else if (bsontype !== undefined) {
242
+ return REDACT_LEAF
243
+ }
244
+
245
+ // The driver serializes a Map via its entries; mirror that as a document so
246
+ // the tag matches the wire shape.
247
+ if (isMap(value)) value = Object.fromEntries(value)
248
+
249
+ ancestors.push(value)
250
+
251
+ let result
252
+ if (Array.isArray(value)) {
253
+ result = '['
254
+ let sep = ''
255
+ for (let i = 0; i < value.length; i++) {
256
+ // JSON.stringify renders unsupported leaves (function, symbol, undefined) as null in arrays.
257
+ result += sep + (classifyForNone(value[i], ancestors) ?? 'null')
258
+ sep = ','
259
+ }
260
+ result += ']'
261
+ } else {
262
+ result = '{'
263
+ let sep = ''
264
+ for (const key of Object.keys(value)) {
265
+ const childResult = classifyForNone(value[key], ancestors)
266
+ if (childResult === undefined) continue
267
+ result += sep + JSON.stringify(key) + ':' + childResult
268
+ sep = ','
269
+ }
270
+ result += '}'
271
+ }
272
+ ancestors.pop()
273
+ return result
274
+ }
275
+
276
+ /**
277
+ * @param {unknown} child
278
+ * @param {object[]} ancestors
279
+ * @returns {string | undefined}
280
+ */
281
+ function classifyForNone (child, ancestors) {
282
+ if (typeof child !== 'object') return classifyLeafForNone(child)
283
+ if (child === null) return 'null'
284
+ return buildNone(child, ancestors)
285
+ }
286
+
287
+ /**
288
+ * @param {unknown} leaf
289
+ * @returns {string | undefined}
290
+ */
291
+ function classifyLeafForNone (leaf) {
292
+ // Implicit `undefined` for function / symbol / undefined matches the
293
+ // contract callers rely on: JSON.stringify drops those property values
294
+ // inside objects and writes `null` in arrays.
295
+ switch (typeof leaf) {
296
+ case 'string': return JSON.stringify(leaf)
297
+ case 'number': return Number.isFinite(leaf) ? String(leaf) : 'null'
298
+ case 'boolean': return leaf ? 'true' : 'false'
299
+ case 'bigint': return `"${String(leaf)}"`
300
+ }
301
+ }
302
+
303
+ /**
304
+ * @param {Record<string, unknown> | unknown[]} value
305
+ * @param {object[]} ancestors
306
+ */
307
+ function buildRedact (value, ancestors) {
308
+ const bsontype = value._bsontype
309
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
310
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
311
+ return REDACT_LEAF
312
+ }
313
+
314
+ // Mirror JSON.stringify: when `toJSON` is present, walk its result (which
315
+ // wrappers like Timestamp / Decimal128 expand to `{$timestamp: "..."}` etc).
316
+ // A primitive, null, or self-reference collapses to the sentinel — master's
317
+ // `value === original` short-circuit.
318
+ if (typeof value.toJSON === 'function') {
319
+ const json = value.toJSON()
320
+ if (typeof json !== 'object' || json === null || json === value) return REDACT_LEAF
321
+ // Re-screen: toJSON can return a TypedArray or Binary BSON wrapper.
322
+ if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
323
+ value = json
324
+ } else if (bsontype !== undefined) {
325
+ return REDACT_LEAF
326
+ }
327
+
328
+ if (isMap(value)) value = Object.fromEntries(value)
329
+
330
+ ancestors.push(value)
331
+
332
+ let result
333
+ if (Array.isArray(value)) {
334
+ result = '['
335
+ let sep = ''
336
+ for (let i = 0; i < value.length; i++) {
337
+ result += sep + classifyForRedact(value[i], ancestors)
338
+ sep = ','
339
+ }
340
+ result += ']'
341
+ } else {
342
+ result = '{'
343
+ let sep = ''
344
+ for (const key of Object.keys(value)) {
345
+ result += sep + JSON.stringify(key) + ':' + classifyForRedact(value[key], ancestors)
346
+ sep = ','
347
+ }
348
+ result += '}'
349
+ }
350
+ ancestors.pop()
351
+ return result
352
+ }
353
+
354
+ /**
355
+ * @param {unknown} child
356
+ * @param {object[]} ancestors
357
+ */
358
+ function classifyForRedact (child, ancestors) {
359
+ if (typeof child !== 'object' || child === null) return REDACT_LEAF
360
+ return buildRedact(child, ancestors)
361
+ }
362
+
363
+ const TYPE_OBJECT = '"object"'
364
+ const TYPE_NULL = '"null"'
365
+ const TYPE_BY_TYPEOF = {
366
+ string: '"string"',
367
+ number: '"number"',
368
+ boolean: '"boolean"',
369
+ bigint: '"bigint"',
370
+ undefined: '"undefined"',
371
+ }
372
+
373
+ /**
374
+ * @param {Record<string, unknown> | unknown[]} value
375
+ * @param {object[]} ancestors
376
+ */
377
+ function buildTypes (value, ancestors) {
378
+ const bsontype = value._bsontype
379
+ if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
380
+ ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
381
+ return TYPE_OBJECT
382
+ }
383
+
384
+ if (typeof value.toJSON === 'function') {
385
+ const json = value.toJSON()
386
+ if (typeof json !== 'object' ||
387
+ json === null ||
388
+ json === value ||
389
+ ArrayBuffer.isView(json) ||
390
+ json._bsontype === 'Binary') {
391
+ return TYPE_OBJECT
392
+ }
393
+ value = json
394
+ } else if (bsontype !== undefined) {
395
+ return TYPE_OBJECT
396
+ }
397
+
398
+ if (isMap(value)) value = Object.fromEntries(value)
399
+
400
+ ancestors.push(value)
401
+
402
+ let result
403
+ if (Array.isArray(value)) {
404
+ result = '['
405
+ let sep = ''
406
+ for (let i = 0; i < value.length; i++) {
407
+ // JSON.stringify renders unsupported leaves (function, symbol) as null in arrays.
408
+ result += sep + (classifyForTypes(value[i], ancestors) ?? 'null')
409
+ sep = ','
410
+ }
411
+ result += ']'
412
+ } else {
413
+ result = '{'
414
+ let sep = ''
415
+ for (const key of Object.keys(value)) {
416
+ const childResult = classifyForTypes(value[key], ancestors)
417
+ if (childResult === undefined) continue
418
+ result += sep + JSON.stringify(key) + ':' + childResult
419
+ sep = ','
420
+ }
421
+ result += '}'
422
+ }
423
+ ancestors.pop()
424
+ return result
425
+ }
426
+
427
+ /**
428
+ * @param {unknown} child
429
+ * @param {object[]} ancestors
430
+ */
431
+ function classifyForTypes (child, ancestors) {
432
+ if (typeof child !== 'object') return TYPE_BY_TYPEOF[typeof child]
433
+ if (child === null) return TYPE_NULL
434
+ return buildTypes(child, ancestors)
435
+ }
436
+
437
+ /** @param {unknown} value */
438
+ function normaliseObfuscateQuery (value) {
439
+ if (value === 'types' || value === 'redact') return value
440
+ return 'none'
441
+ }
442
+
443
+ function isHeartbeat (ops, config) {
444
+ // Check if it's a heartbeat command https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.md
445
+ return (
446
+ ops &&
447
+ typeof ops === 'object' &&
448
+ (ops.hello === 1 || ops.helloOk === true || ops.ismaster === 1 || ops.isMaster === 1)
449
+ )
450
+ }
451
+
452
+ module.exports = MongodbCoreQueryPlugin
@@ -87,7 +87,7 @@ class PlaywrightPlugin extends CiPlugin {
87
87
  if (isEarlyFlakeDetectionFaulty) {
88
88
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
89
89
  }
90
- if (this.numFailedSuites > 0) {
90
+ if (status === 'fail' && this.numFailedSuites > 0) {
91
91
  let errorMessage = `Test suites failed: ${this.numFailedSuites}.`
92
92
  if (this.numFailedTests > 0) {
93
93
  errorMessage += ` Tests failed: ${this.numFailedTests}`
@@ -113,6 +113,7 @@ class PlaywrightPlugin extends CiPlugin {
113
113
  appClosingTelemetry()
114
114
  this.tracer._exporter.flush(onDone)
115
115
  this.numFailedTests = 0
116
+ this.numFailedSuites = 0
116
117
  })
117
118
 
118
119
  this.addBind('ci:playwright:test-suite:start', (ctx) => {
@@ -408,7 +409,7 @@ class PlaywrightPlugin extends CiPlugin {
408
409
  }
409
410
  stepSpan.finish(stepStartTime + stepDuration)
410
411
  }
411
- if (testStatus === 'fail') {
412
+ if (finalStatus === 'fail') {
412
413
  this.numFailedTests++
413
414
  }
414
415
 
@@ -192,7 +192,7 @@ class VitestPlugin extends CiPlugin {
192
192
  return ctx.currentStore
193
193
  })
194
194
 
195
- this.addSub('ci:vitest:test:pass', ({ span, task, finalStatus, earlyFlakeAbortReason }) => {
195
+ this.addSub('ci:vitest:test:pass', ({ span, task, finalStatus, earlyFlakeAbortReason, promises }) => {
196
196
  if (span) {
197
197
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(span))
198
198
  span.setTag(TEST_STATUS, 'pass')
@@ -202,8 +202,21 @@ class VitestPlugin extends CiPlugin {
202
202
  if (earlyFlakeAbortReason) {
203
203
  span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
204
204
  }
205
- span.finish(this.taskToFinishTime.get(task))
206
- finishAllTraceSpans(span)
205
+ const finish = () => {
206
+ span.finish(this.taskToFinishTime.get(task))
207
+ finishAllTraceSpans(span)
208
+ }
209
+
210
+ if (finalStatus) {
211
+ if (promises && this.diBreakpointHitPromise) {
212
+ promises.hitBreakpointPromise = this.waitForPreparedDiBreakpointHit().then(finish)
213
+ return
214
+ }
215
+ finish()
216
+ this.cancelDiBreakpointHitWait()
217
+ return
218
+ }
219
+ finish()
207
220
  }
208
221
  })
209
222
 
@@ -212,6 +225,7 @@ class VitestPlugin extends CiPlugin {
212
225
  duration,
213
226
  error,
214
227
  shouldSetProbe,
228
+ shouldWaitForHitProbe,
215
229
  promises,
216
230
  hasFailedAllRetries,
217
231
  attemptToFixFailed,
@@ -227,7 +241,8 @@ class VitestPlugin extends CiPlugin {
227
241
  const { file, line, stackIndex, setProbePromise } = probeInformation
228
242
  this.runningTestProbe = { file, line }
229
243
  this.testErrorStackIndex = stackIndex
230
- promises.setProbePromise = setProbePromise
244
+ this.prepareDiBreakpointHitWait()
245
+ promises.setProbePromise = this.waitForDiOperation(setProbePromise)
231
246
  }
232
247
  }
233
248
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', this.getTestTelemetryTags(span))
@@ -248,12 +263,31 @@ class VitestPlugin extends CiPlugin {
248
263
  if (earlyFlakeAbortReason) {
249
264
  span.setTag(TEST_EARLY_FLAKE_ABORT_REASON, earlyFlakeAbortReason)
250
265
  }
251
- if (duration) {
252
- span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
253
- } else {
254
- span.finish() // `duration` is empty for retries, so we'll use clock time
266
+ const finish = () => {
267
+ if (duration) {
268
+ span.finish(span._startTime + duration - MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION) // milliseconds
269
+ } else {
270
+ span.finish() // `duration` is empty for retries, so we'll use clock time
271
+ }
272
+ finishAllTraceSpans(span)
273
+ }
274
+
275
+ if (!shouldSetProbe && finalStatus && promises && this.diBreakpointHitPromise) {
276
+ promises.hitBreakpointPromise = this.waitForPreparedDiBreakpointHit().then(finish)
277
+ return
278
+ }
279
+ finish()
280
+ if (shouldWaitForHitProbe) {
281
+ this.prepareDiBreakpointHitWait()
282
+ } else if (!shouldSetProbe) {
283
+ this.cancelDiBreakpointHitWait()
284
+ }
285
+ })
286
+
287
+ this.addSub('ci:vitest:test:di:wait', ({ promises }) => {
288
+ if (this.di) {
289
+ promises.hitBreakpointPromise = this.waitForDiBreakpointHits()
255
290
  }
256
- finishAllTraceSpans(span)
257
291
  })
258
292
 
259
293
  this.addSub('ci:vitest:test:skip', ({
@@ -2,15 +2,11 @@
2
2
 
3
3
  const log = require('../log')
4
4
  const { incomingHttpRequestStart } = require('./channels')
5
- const openaiIntegration = require('./integrations/openai')
6
- const vercelAiIntegration = require('./integrations/vercel-ai')
5
+ const integrations = require('./integrations')
7
6
  const AIGuard = require('./sdk')
8
7
 
9
8
  let isEnabled = false
10
9
  let aiguard
11
- let block
12
- let disableOpenAIIntegration
13
- let disableVercelAiIntegration
14
10
 
15
11
  function onIncomingHttpRequestStart () {
16
12
  // No-op: subscribing ensures the HTTP plugin spreads req onto the store
@@ -21,31 +17,30 @@ function enable (tracer, config) {
21
17
 
22
18
  try {
23
19
  aiguard = new AIGuard(tracer, config)
24
- block = config.experimental?.aiguard?.block !== false
20
+ const block = config.experimental?.aiguard?.block !== false
25
21
 
26
22
  incomingHttpRequestStart.subscribe(onIncomingHttpRequestStart)
27
- disableOpenAIIntegration = openaiIntegration.enable(aiguard, block)
28
- disableVercelAiIntegration = vercelAiIntegration.enable(aiguard, block)
23
+ integrations.enable(aiguard, block)
29
24
 
30
25
  isEnabled = true
31
26
  } catch (err) {
32
27
  log.error('AIGuard: unexpected error during initialization: %s', err.message)
33
- disable()
28
+ reset()
34
29
  }
35
30
  }
36
31
 
37
32
  function disable () {
38
33
  if (!isEnabled) return
39
34
 
35
+ reset()
36
+ }
37
+
38
+ function reset () {
40
39
  incomingHttpRequestStart.unsubscribe(onIncomingHttpRequestStart)
41
- disableOpenAIIntegration?.()
42
- disableVercelAiIntegration?.()
40
+ integrations.disable()
43
41
 
44
42
  aiguard = undefined
45
43
  isEnabled = false
46
- block = false
47
- disableOpenAIIntegration = undefined
48
- disableVercelAiIntegration = undefined
49
44
  }
50
45
 
51
46
  module.exports = { enable, disable }
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const openai = require('./openai')
4
+ const vercelAi = require('./vercel-ai')
5
+
6
+ let isEnabled = false
7
+
8
+ /**
9
+ * Enables all AI Guard provider integrations.
10
+ *
11
+ * @param {object} aiguard
12
+ * @param {boolean} block
13
+ */
14
+ function enable (aiguard, block) {
15
+ if (isEnabled) return
16
+
17
+ openai.enable(aiguard, block)
18
+ vercelAi.enable(aiguard, block)
19
+
20
+ isEnabled = true
21
+ }
22
+
23
+ function disable () {
24
+ vercelAi.disable()
25
+ openai.disable()
26
+ isEnabled = false
27
+ }
28
+
29
+ module.exports = {
30
+ enable,
31
+ disable,
32
+ openai,
33
+ vercelAi,
34
+ }