dd-trace 6.10.0 → 6.11.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 (137) hide show
  1. package/README.md +0 -17
  2. package/index.d.ts +108 -3
  3. package/initialize.mjs +28 -17
  4. package/openfeature.js +2 -2
  5. package/package.json +11 -8
  6. package/packages/datadog-esbuild/index.js +0 -34
  7. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  8. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  9. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  10. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  11. package/packages/datadog-instrumentations/src/cucumber.js +26 -11
  12. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  13. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  14. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  15. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  16. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  17. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  19. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  20. package/packages/datadog-instrumentations/src/jest.js +25 -17
  21. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  22. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +130 -17
  24. package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
  25. package/packages/datadog-instrumentations/src/playwright.js +149 -2
  26. package/packages/datadog-instrumentations/src/router.js +2 -0
  27. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  28. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  29. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  30. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  31. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  32. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  33. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  34. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  35. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  36. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  37. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  38. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  39. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  40. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  41. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  42. package/packages/datadog-plugin-electron/src/index.js +2 -0
  43. package/packages/datadog-plugin-fs/src/index.js +1 -1
  44. package/packages/datadog-plugin-jest/src/index.js +6 -0
  45. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  46. package/packages/datadog-plugin-nats/src/index.js +1 -1
  47. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  48. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  49. package/packages/datadog-plugin-playwright/src/index.js +42 -13
  50. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  51. package/packages/datadog-webpack/index.js +0 -11
  52. package/packages/dd-trace/index.js +0 -1
  53. package/packages/dd-trace/src/agent/info.js +6 -5
  54. package/packages/dd-trace/src/aiguard/client.js +100 -1
  55. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  56. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  57. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  58. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  59. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  60. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  61. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  62. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  63. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  64. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  66. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
  67. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  69. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  70. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
  71. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  72. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  73. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  74. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  75. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
  76. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  77. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  78. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
  79. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  80. package/packages/dd-trace/src/config/index.js +13 -2
  81. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  82. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  83. package/packages/dd-trace/src/datastreams/index.js +2 -0
  84. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  85. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  86. package/packages/dd-trace/src/datastreams/size.js +5 -0
  87. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
  88. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  89. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  90. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  91. package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
  92. package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
  93. package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
  94. package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
  95. package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
  96. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  97. package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
  98. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  99. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  100. package/packages/dd-trace/src/noop/proxy.js +3 -4
  101. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  102. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  103. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  104. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  105. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  106. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  107. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  108. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  109. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  110. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  111. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  112. package/packages/dd-trace/src/plugin_manager.js +3 -3
  113. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  114. package/packages/dd-trace/src/plugins/index.js +1 -0
  115. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  116. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  117. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  118. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  119. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  120. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  121. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  122. package/packages/dd-trace/src/proxy.js +38 -29
  123. package/packages/dd-trace/src/ritm.js +9 -2
  124. package/packages/dd-trace/src/serverless.js +37 -0
  125. package/packages/dd-trace/src/span_format.js +1 -0
  126. package/packages/dd-trace/src/span_stats.js +30 -3
  127. package/packages/dd-trace/src/storage-channels.js +86 -0
  128. package/packages/dd-trace/src/tracer.js +9 -2
  129. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  130. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  131. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  132. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  133. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  134. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  135. package/packages/dd-trace/src/feature-registry.js +0 -29
  136. package/packages/dd-trace/src/openfeature/register.js +0 -35
  137. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -0,0 +1,357 @@
1
+ 'use strict'
2
+
3
+ const { HTTP_CLIENT_IP, HTTP_USERAGENT, NETWORK_CLIENT_IP } = require('../../../../ext/tags')
4
+ const clone = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
5
+ const { USER_ID, USER_SESSION_ID } = require('../appsec/addresses')
6
+ const { getActiveRequest } = require('../appsec/store')
7
+ const { keepTrace } = require('../priority_sampler')
8
+ const { extractIp } = require('../plugins/util/ip_extractor')
9
+ const { AI_GUARD } = require('../standalone/product')
10
+ const telemetryMetrics = require('../telemetry/metrics')
11
+ const TAGS = require('./tags')
12
+
13
+ const ALLOW = 'ALLOW'
14
+
15
+ /** @typedef {import('../../../../index').aiguard.Message} Message */
16
+ /** @typedef {import('../opentracing/span')} Span */
17
+
18
+ /**
19
+ * @typedef {object} EvaluationResponse
20
+ * @property {string} action
21
+ * @property {string|undefined} reason
22
+ * @property {Array<unknown>} tags
23
+ * @property {Array<unknown>} sdsFindings
24
+ * @property {Record<string, unknown>} tagProbabilities
25
+ * @property {boolean} hasTagProbabilities
26
+ * @property {boolean} blockingEnabled
27
+ */
28
+
29
+ /**
30
+ * @typedef {object} EvaluationOutcome
31
+ * @property {{ action: string, reason: string|undefined, tags: Array<unknown>,
32
+ * tagProbabilities: Record<string, unknown>, sds: Array<unknown> }} result
33
+ * @property {boolean} shouldBlock
34
+ * @property {boolean} hasTagProbabilities
35
+ */
36
+
37
+ /**
38
+ * @typedef {object} EvaluationMetaStruct
39
+ * @property {Message[]} messages
40
+ * @property {Array<unknown>} [attack_categories]
41
+ * @property {Array<unknown>} [sds]
42
+ * @property {Record<string, unknown>} [tag_probs]
43
+ */
44
+
45
+ /**
46
+ * @typedef {object} EvaluationReport
47
+ * @property {Span} span
48
+ * @property {EvaluationMetaStruct} metaStruct
49
+ * @property {{ source: string, integration: string }} telemetryTags
50
+ */
51
+
52
+ /**
53
+ * Returns whether a value is a non-array object.
54
+ *
55
+ * @param {unknown} value
56
+ * @returns {value is Record<string, unknown>}
57
+ */
58
+ function isRecord (value) {
59
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
60
+ }
61
+
62
+ /**
63
+ * Parses the backend evaluation response into the tracer's internal contract.
64
+ *
65
+ * @param {unknown} body
66
+ * @returns {EvaluationResponse|undefined}
67
+ */
68
+ function parseEvaluationResponse (body) {
69
+ if (!isRecord(body) || !isRecord(body.data) || !isRecord(body.data.attributes)) return
70
+
71
+ const attributes = body.data.attributes
72
+ if (typeof attributes.action !== 'string' || attributes.action.length === 0) return
73
+
74
+ return {
75
+ action: attributes.action,
76
+ reason: typeof attributes.reason === 'string' ? attributes.reason : undefined,
77
+ tags: Array.isArray(attributes.tags) ? attributes.tags : [],
78
+ sdsFindings: Array.isArray(attributes.sds_findings) ? attributes.sds_findings : [],
79
+ tagProbabilities: isRecord(attributes.tag_probs) ? attributes.tag_probs : {},
80
+ hasTagProbabilities: isRecord(attributes.tag_probs),
81
+ blockingEnabled: attributes.is_blocking_enabled === true,
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Determines whether the current evaluation must abort the guarded operation.
87
+ *
88
+ * @param {boolean} block
89
+ * @param {{ action: string, blockingEnabled: boolean }} evaluation
90
+ * @returns {boolean}
91
+ */
92
+ function shouldBlockEvaluation (block, evaluation) {
93
+ return block && evaluation.blockingEnabled && evaluation.action !== ALLOW
94
+ }
95
+
96
+ /**
97
+ * Applies local evaluation policy and creates the result returned to the caller.
98
+ *
99
+ * @param {EvaluationResponse} evaluation
100
+ * @param {boolean} block
101
+ * @returns {EvaluationOutcome}
102
+ */
103
+ function createEvaluationOutcome (evaluation, block) {
104
+ return {
105
+ result: {
106
+ action: evaluation.action,
107
+ reason: evaluation.reason,
108
+ tags: evaluation.tags,
109
+ tagProbabilities: evaluation.tagProbabilities,
110
+ sds: evaluation.sdsFindings,
111
+ },
112
+ shouldBlock: shouldBlockEvaluation(block, evaluation),
113
+ hasTagProbabilities: evaluation.hasTagProbabilities,
114
+ }
115
+ }
116
+
117
+ const aiguardMetrics = telemetryMetrics.manager.namespace('ai_guard')
118
+
119
+ // Tags from the service entry span that must be mirrored onto every AI Guard span
120
+ // so anomaly detection pipelines can process each span independently.
121
+ const SERVICE_ENTRY_TAG_MAPPINGS = [
122
+ [HTTP_USERAGENT, TAGS.HTTP_USERAGENT_TAG_KEY],
123
+ [HTTP_CLIENT_IP, TAGS.HTTP_CLIENT_IP_TAG_KEY],
124
+ [NETWORK_CLIENT_IP, TAGS.NETWORK_CLIENT_IP_TAG_KEY],
125
+ [USER_ID, TAGS.USR_ID_TAG_KEY],
126
+ [USER_SESSION_ID, TAGS.USR_SESSION_ID_TAG_KEY],
127
+ ]
128
+
129
+ class EvaluationReporter {
130
+ #config
131
+ #maxMessagesLength
132
+ #maxContentSize
133
+
134
+ /**
135
+ * @param {import('../config/config-base')} config
136
+ */
137
+ constructor (config) {
138
+ this.#config = config
139
+ this.#maxMessagesLength = config.experimental.aiguard.maxMessagesLength
140
+ this.#maxContentSize = config.experimental.aiguard.maxContentSize
141
+ }
142
+
143
+ /**
144
+ * Initializes span and trace reporting before the AI Guard request starts.
145
+ *
146
+ * @param {Span} span
147
+ * @param {Message[]} messages
148
+ * @param {{ source: string, integration: string }} options
149
+ * @returns {EvaluationReport}
150
+ */
151
+ start (span, messages, options) {
152
+ const telemetryTags = { source: options.source, integration: options.integration }
153
+ const last = messages.at(-1)
154
+ const target = this.#isToolCall(last) ? 'tool' : 'prompt'
155
+ span.setTag(TAGS.TARGET_TAG_KEY, target)
156
+ if (target === 'tool') {
157
+ const name = this.#getToolName(last, messages)
158
+ if (name) {
159
+ span.setTag(TAGS.TOOL_NAME_TAG_KEY, name)
160
+ }
161
+ }
162
+
163
+ const metaStruct = {
164
+ messages: this.#buildMessagesForMetaStruct(messages, telemetryTags),
165
+ }
166
+ span.meta_struct = {
167
+ [TAGS.META_STRUCT_KEY]: metaStruct,
168
+ }
169
+
170
+ const rootSpan = span.context()?._trace?.started?.[0]
171
+ if (rootSpan) {
172
+ this.#setRootSpanClientIpTags(rootSpan)
173
+ this.#copyServiceEntryTagsToGuardSpan(span, rootSpan)
174
+ // This must happen before the request so its sampling decision reaches outgoing HTTP spans.
175
+ keepTrace(rootSpan, AI_GUARD)
176
+ rootSpan.setTag(TAGS.EVENT_TAG_KEY, 'true')
177
+ }
178
+
179
+ return {
180
+ span,
181
+ metaStruct,
182
+ telemetryTags,
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Reports a failed evaluation request.
188
+ *
189
+ * @param {EvaluationReport} report
190
+ * @param {string} errorType
191
+ * @returns {void}
192
+ */
193
+ fail (report, errorType) {
194
+ aiguardMetrics.count(TAGS.TELEMETRY_REQUESTS, { error: true, ...report.telemetryTags }).inc(1)
195
+ aiguardMetrics.count(TAGS.TELEMETRY_ERROR, { type: errorType, ...report.telemetryTags }).inc(1)
196
+ }
197
+
198
+ /**
199
+ * Reports a successful evaluation and its local policy outcome.
200
+ *
201
+ * @param {EvaluationReport} report
202
+ * @param {EvaluationOutcome} outcome
203
+ * @returns {void}
204
+ */
205
+ finish (report, outcome) {
206
+ const { result, shouldBlock } = outcome
207
+
208
+ if (result.tags.length > 0) report.metaStruct.attack_categories = result.tags
209
+ if (result.sds.length > 0) report.metaStruct.sds = result.sds
210
+ if (outcome.hasTagProbabilities) report.metaStruct.tag_probs = result.tagProbabilities
211
+
212
+ const requestTelemetryTags = {
213
+ action: result.action,
214
+ error: false,
215
+ block: shouldBlock,
216
+ ...report.telemetryTags,
217
+ }
218
+ aiguardMetrics.count(TAGS.TELEMETRY_REQUESTS, requestTelemetryTags).inc(1)
219
+
220
+ report.span.setTag(TAGS.ACTION_TAG_KEY, result.action)
221
+ if (result.reason) {
222
+ report.span.setTag(TAGS.REASON_TAG_KEY, result.reason)
223
+ }
224
+ if (shouldBlock) {
225
+ report.span.setTag(TAGS.BLOCKED_TAG_KEY, 'true')
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Returns a safe, size-limited message copy for the span meta-struct.
231
+ *
232
+ * @param {Message[]} messages
233
+ * @param {{ source: string, integration: string }} telemetryTags
234
+ * @returns {Message[]}
235
+ */
236
+ #buildMessagesForMetaStruct (messages, telemetryTags) {
237
+ const size = Math.min(messages.length, this.#maxMessagesLength)
238
+ if (messages.length > size) {
239
+ aiguardMetrics.count(TAGS.TELEMETRY_TRUNCATED, { type: 'messages', ...telemetryTags }).inc(1)
240
+ }
241
+ const result = []
242
+ let contentTruncated = false
243
+ for (let i = messages.length - size; i < messages.length; i++) {
244
+ const message = clone(messages[i])
245
+ if (message.content?.length > this.#maxContentSize) {
246
+ contentTruncated = true
247
+ message.content = message.content.slice(0, this.#maxContentSize)
248
+ }
249
+ result.push(message)
250
+ }
251
+ if (contentTruncated) {
252
+ aiguardMetrics.count(TAGS.TELEMETRY_TRUNCATED, { type: 'content', ...telemetryTags }).inc(1)
253
+ }
254
+ return result
255
+ }
256
+
257
+ /**
258
+ * Returns whether a message represents a tool call or tool output.
259
+ *
260
+ * @param {Message} message
261
+ * @returns {boolean}
262
+ */
263
+ #isToolCall (message) {
264
+ return Boolean(message.tool_calls || message.tool_call_id)
265
+ }
266
+
267
+ /**
268
+ * Resolves the tool name associated with a tool call or output message.
269
+ *
270
+ * @param {Message} message
271
+ * @param {Message[]} history
272
+ * @returns {string|null}
273
+ */
274
+ #getToolName (message, history) {
275
+ if (message.tool_calls) {
276
+ const names = message.tool_calls.map(tool => tool.function.name)
277
+ return names.length === 0 ? null : names.join(',')
278
+ }
279
+
280
+ const id = message.tool_call_id
281
+ for (let i = history.length - 2; i >= 0; i--) {
282
+ const item = history[i]
283
+ if (item.tool_calls) {
284
+ for (const toolCall of item.tool_calls) {
285
+ if (toolCall.id === id) {
286
+ return toolCall.function.name
287
+ }
288
+ }
289
+ }
290
+ }
291
+ return null
292
+ }
293
+
294
+ /**
295
+ * Adds missing client IP tags to the service entry span.
296
+ *
297
+ * @param {Span} rootSpan
298
+ * @returns {void}
299
+ */
300
+ #setRootSpanClientIpTags (rootSpan) {
301
+ const currentTags = rootSpan.context().getTags()
302
+ const needsHttpClientIp = !Object.hasOwn(currentTags, HTTP_CLIENT_IP)
303
+ const needsNetworkClientIp = !Object.hasOwn(currentTags, NETWORK_CLIENT_IP)
304
+
305
+ if (!needsHttpClientIp && !needsNetworkClientIp) return
306
+
307
+ const request = getActiveRequest()
308
+ if (!request) return
309
+
310
+ const newTags = {}
311
+ let hasNewTags = false
312
+
313
+ if (needsHttpClientIp) {
314
+ const clientIp = extractIp(this.#config, request)
315
+ if (clientIp) {
316
+ newTags[HTTP_CLIENT_IP] = clientIp
317
+ hasNewTags = true
318
+ }
319
+ }
320
+
321
+ if (needsNetworkClientIp) {
322
+ const networkClientIp = request.socket?.remoteAddress
323
+ if (networkClientIp) {
324
+ newTags[NETWORK_CLIENT_IP] = networkClientIp
325
+ hasNewTags = true
326
+ }
327
+ }
328
+
329
+ if (hasNewTags) {
330
+ rootSpan.addTags(newTags)
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Copies service entry tags required by anomaly detection to the AI Guard span.
336
+ *
337
+ * @param {Span} guardSpan
338
+ * @param {Span} rootSpan
339
+ * @returns {void}
340
+ */
341
+ #copyServiceEntryTagsToGuardSpan (guardSpan, rootSpan) {
342
+ const rootTags = rootSpan.context().getTags()
343
+ for (const [sourceTag, destinationTag] of SERVICE_ENTRY_TAG_MAPPINGS) {
344
+ const value = rootTags[sourceTag]
345
+ if (value !== undefined && value !== null) {
346
+ guardSpan.setTag(destinationTag, value)
347
+ }
348
+ }
349
+ }
350
+ }
351
+
352
+ module.exports = {
353
+ createEvaluationOutcome,
354
+ EvaluationReporter,
355
+ parseEvaluationResponse,
356
+ shouldBlockEvaluation,
357
+ }
@@ -0,0 +1,70 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+
5
+ const log = require('../../log')
6
+ const { getMessagesInputMessages, getMessagesOutputMessages } = require('../messages/anthropic')
7
+ const { SOURCE_AUTO } = require('../tags')
8
+ const { pushEvaluation } = require('./evaluate')
9
+
10
+ const messagesBeforeChannel = channel('dd-trace:anthropic:messages:before')
11
+ const messagesAfterChannel = channel('dd-trace:anthropic:messages:after')
12
+
13
+ let isEnabled = false
14
+ let aiguard
15
+ let opts
16
+
17
+ /**
18
+ * Subscribes AI Guard to Anthropic lifecycle channels.
19
+ *
20
+ * @param {object} aiguardInstance
21
+ * @param {boolean} block
22
+ */
23
+ function enable (aiguardInstance, block) {
24
+ if (isEnabled) return
25
+
26
+ aiguard = aiguardInstance
27
+ opts = { block, source: SOURCE_AUTO, integration: 'anthropic' }
28
+
29
+ messagesBeforeChannel.subscribe(onMessagesBefore)
30
+ messagesAfterChannel.subscribe(onMessagesAfter)
31
+
32
+ isEnabled = true
33
+ }
34
+
35
+ function disable () {
36
+ if (!isEnabled) return
37
+
38
+ messagesBeforeChannel.unsubscribe(onMessagesBefore)
39
+ messagesAfterChannel.unsubscribe(onMessagesAfter)
40
+
41
+ aiguard = undefined
42
+ opts = undefined
43
+ isEnabled = false
44
+ }
45
+
46
+ function onMessagesBefore (ctx) {
47
+ pushEvaluation(ctx, aiguard, getMessagesInputMessages(ctx.args?.[0]), opts)
48
+ }
49
+
50
+ function onMessagesAfter (ctx) {
51
+ const inputMessages = getMessagesInputMessages(ctx.args?.[0])
52
+ if (!inputMessages?.length) return
53
+
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
+ }
62
+ }
63
+
64
+ const outputMessages = getMessagesOutputMessages(body)
65
+ if (!outputMessages.length) return
66
+
67
+ pushEvaluation(ctx, aiguard, [...inputMessages, ...outputMessages], opts)
68
+ }
69
+
70
+ module.exports = { enable, disable }
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const anthropic = require('./anthropic')
3
4
  const openai = require('./openai')
4
5
  const vercelAi = require('./vercel-ai')
5
6
 
@@ -14,6 +15,7 @@ let isEnabled = false
14
15
  function enable (aiguard, block) {
15
16
  if (isEnabled) return
16
17
 
18
+ anthropic.enable(aiguard, block)
17
19
  openai.enable(aiguard, block)
18
20
  vercelAi.enable(aiguard, block)
19
21
 
@@ -23,12 +25,14 @@ function enable (aiguard, block) {
23
25
  function disable () {
24
26
  vercelAi.disable()
25
27
  openai.disable()
28
+ anthropic.disable()
26
29
  isEnabled = false
27
30
  }
28
31
 
29
32
  module.exports = {
30
33
  enable,
31
34
  disable,
35
+ anthropic,
32
36
  openai,
33
37
  vercelAi,
34
38
  }