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.
- package/README.md +0 -17
- package/index.d.ts +108 -3
- package/initialize.mjs +28 -17
- package/openfeature.js +2 -2
- package/package.json +11 -8
- package/packages/datadog-esbuild/index.js +0 -34
- package/packages/datadog-instrumentations/src/anthropic.js +210 -15
- package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
- package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
- package/packages/datadog-instrumentations/src/bunyan.js +3 -16
- package/packages/datadog-instrumentations/src/cucumber.js +26 -11
- package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
- package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
- package/packages/datadog-instrumentations/src/cypress.js +2 -0
- package/packages/datadog-instrumentations/src/fastify.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
- package/packages/datadog-instrumentations/src/jest.js +25 -17
- package/packages/datadog-instrumentations/src/mariadb.js +278 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
- package/packages/datadog-instrumentations/src/mocha/utils.js +130 -17
- package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
- package/packages/datadog-instrumentations/src/playwright.js +149 -2
- package/packages/datadog-instrumentations/src/router.js +2 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
- package/packages/datadog-plugin-bunyan/src/index.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +11 -0
- package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
- package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
- package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
- package/packages/datadog-plugin-cypress/src/index.js +12 -6
- package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
- package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
- package/packages/datadog-plugin-electron/src/index.js +2 -0
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-mocha/src/index.js +56 -1
- package/packages/datadog-plugin-nats/src/index.js +1 -1
- package/packages/datadog-plugin-openai/src/tracing.js +41 -30
- package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
- package/packages/datadog-plugin-playwright/src/index.js +42 -13
- package/packages/datadog-plugin-vitest/src/index.js +6 -0
- package/packages/datadog-webpack/index.js +0 -11
- package/packages/dd-trace/index.js +0 -1
- package/packages/dd-trace/src/agent/info.js +6 -5
- package/packages/dd-trace/src/aiguard/client.js +100 -1
- package/packages/dd-trace/src/aiguard/errors.js +41 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
- package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
- package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
- package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
- package/packages/dd-trace/src/aiguard/sdk.js +22 -278
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +13 -2
- package/packages/dd-trace/src/config/major-overrides.js +7 -0
- package/packages/dd-trace/src/config/supported-configurations.json +14 -0
- package/packages/dd-trace/src/datastreams/index.js +2 -0
- package/packages/dd-trace/src/datastreams/manager.js +9 -2
- package/packages/dd-trace/src/datastreams/processor.js +15 -13
- package/packages/dd-trace/src/datastreams/size.js +5 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
- package/packages/dd-trace/src/exporters/common/writer.js +11 -6
- package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
- package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
- package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
- package/packages/dd-trace/src/noop/proxy.js +3 -4
- package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
- package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
- package/packages/dd-trace/src/opentracing/tracer.js +5 -2
- package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
- package/packages/dd-trace/src/plugin_manager.js +3 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/jest.js +4 -3
- package/packages/dd-trace/src/plugins/util/test.js +9 -9
- package/packages/dd-trace/src/plugins/util/web.js +3 -0
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
- package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
- package/packages/dd-trace/src/proxy.js +38 -29
- package/packages/dd-trace/src/ritm.js +9 -2
- package/packages/dd-trace/src/serverless.js +37 -0
- package/packages/dd-trace/src/span_format.js +1 -0
- package/packages/dd-trace/src/span_stats.js +30 -3
- package/packages/dd-trace/src/storage-channels.js +86 -0
- package/packages/dd-trace/src/tracer.js +9 -2
- package/packages/dd-trace/src/tracer_metadata.js +14 -1
- package/packages/dd-trace/src/web-tags-cache.js +132 -0
- package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
- package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
- package/packages/dd-trace/src/feature-registry.js +0 -29
- package/packages/dd-trace/src/openfeature/register.js +0 -35
- package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
|
@@ -79,6 +79,9 @@ function isPlaywrightFailureScreenshot (attachment) {
|
|
|
79
79
|
class PlaywrightPlugin extends CiPlugin {
|
|
80
80
|
static id = 'playwright'
|
|
81
81
|
|
|
82
|
+
#isFinalizingAfterError = false
|
|
83
|
+
#pendingTestFinishCallbacks = new Map()
|
|
84
|
+
|
|
82
85
|
constructor (...args) {
|
|
83
86
|
super(...args)
|
|
84
87
|
|
|
@@ -99,6 +102,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
99
102
|
})
|
|
100
103
|
|
|
101
104
|
this.addSub('ci:playwright:session:start', ({ isFailureScreenshotEnabled }) => {
|
|
105
|
+
this.#isFinalizingAfterError = false
|
|
102
106
|
if (!getConfig().testOptimization.DD_TEST_FAILURE_SCREENSHOTS_ENABLED) return
|
|
103
107
|
|
|
104
108
|
if (!isFailureScreenshotEnabled) {
|
|
@@ -127,8 +131,21 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
127
131
|
isEarlyFlakeDetectionEnabled,
|
|
128
132
|
isEarlyFlakeDetectionFaulty,
|
|
129
133
|
isTestManagementTestsEnabled,
|
|
134
|
+
error,
|
|
130
135
|
onDone,
|
|
131
136
|
}) => {
|
|
137
|
+
if (error) {
|
|
138
|
+
this.#isFinalizingAfterError = true
|
|
139
|
+
for (const testSuiteSpan of this._testSuiteSpansByTestSuiteAbsolutePath.values()) {
|
|
140
|
+
testSuiteSpan.setTag(TEST_STATUS, 'fail')
|
|
141
|
+
testSuiteSpan.setTag('error', error)
|
|
142
|
+
}
|
|
143
|
+
for (const [finishTest, abortController] of this.#pendingTestFinishCallbacks) {
|
|
144
|
+
finishTest(SCREENSHOT_UPLOAD_RESULT_ERROR)
|
|
145
|
+
abortController.abort()
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
132
149
|
const finishSession = () => {
|
|
133
150
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
134
151
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -139,20 +156,24 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
139
156
|
if (isEarlyFlakeDetectionFaulty) {
|
|
140
157
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
141
158
|
}
|
|
142
|
-
|
|
159
|
+
let sessionError = error
|
|
160
|
+
if (!sessionError && status === 'fail' && this.numFailedSuites > 0) {
|
|
143
161
|
let errorMessage = `Test suites failed: ${this.numFailedSuites}.`
|
|
144
162
|
if (this.numFailedTests > 0) {
|
|
145
163
|
errorMessage += ` Tests failed: ${this.numFailedTests}`
|
|
146
164
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
sessionError = new Error(errorMessage)
|
|
166
|
+
}
|
|
167
|
+
if (sessionError) {
|
|
168
|
+
this.testModuleSpan.setTag('error', sessionError)
|
|
169
|
+
this.testSessionSpan.setTag('error', sessionError)
|
|
150
170
|
}
|
|
151
171
|
|
|
152
172
|
if (isTestManagementTestsEnabled) {
|
|
153
173
|
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
154
174
|
}
|
|
155
175
|
|
|
176
|
+
this.tracer._exporter.exportDeferredTestSuiteSpans?.()
|
|
156
177
|
this.testModuleSpan.finish()
|
|
157
178
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
158
179
|
this.testSessionSpan.finish()
|
|
@@ -234,6 +255,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
234
255
|
this.numFailedSuites++
|
|
235
256
|
}
|
|
236
257
|
|
|
258
|
+
this.tracer._exporter.deferTestSuiteSpan?.(testSuiteSpan)
|
|
237
259
|
testSuiteSpan.finish()
|
|
238
260
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
239
261
|
})
|
|
@@ -304,16 +326,16 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
304
326
|
}
|
|
305
327
|
}
|
|
306
328
|
|
|
307
|
-
if (!formattedTestSpan || !screenshots) {
|
|
329
|
+
if (!formattedTestSpan || !screenshots || this.#isFinalizingAfterError) {
|
|
308
330
|
exportTraces()
|
|
309
331
|
return
|
|
310
332
|
}
|
|
311
333
|
|
|
312
334
|
this.pendingTestFinishes++
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
335
|
+
const abortController = new AbortController()
|
|
336
|
+
const finishTest = (screenshotUploadResult) => {
|
|
337
|
+
if (!this.#pendingTestFinishCallbacks.delete(finishTest)) return
|
|
338
|
+
|
|
317
339
|
const screenshotUploadTag = getScreenshotUploadTag(screenshotUploadResult)
|
|
318
340
|
if (screenshotUploadTag) {
|
|
319
341
|
formattedTestSpan.meta[screenshotUploadTag] = 'true'
|
|
@@ -323,11 +345,16 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
323
345
|
if (this.pendingTestFinishes === 0 && this.finishSession) {
|
|
324
346
|
this.finishSession()
|
|
325
347
|
}
|
|
326
|
-
}
|
|
348
|
+
}
|
|
349
|
+
this.#pendingTestFinishCallbacks.set(finishTest, abortController)
|
|
350
|
+
const uploadStarted = this.uploadTestScreenshots({
|
|
351
|
+
screenshots,
|
|
352
|
+
traceId: formattedTestSpan.trace_id.toString(10),
|
|
353
|
+
signal: abortController.signal,
|
|
354
|
+
}, finishTest)
|
|
327
355
|
if (uploadStarted) return
|
|
328
356
|
|
|
329
|
-
|
|
330
|
-
exportTraces()
|
|
357
|
+
finishTest()
|
|
331
358
|
})
|
|
332
359
|
|
|
333
360
|
this.addBind('ci:playwright:test:start', (ctx) => {
|
|
@@ -552,10 +579,11 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
552
579
|
* @param {object} options - Upload options
|
|
553
580
|
* @param {Array<object>} options.screenshots - Playwright test attachments
|
|
554
581
|
* @param {string} options.traceId - Test trace id used as the screenshot key
|
|
582
|
+
* @param {AbortSignal} options.signal - Signal used to cancel uploads during error finalization
|
|
555
583
|
* @param {(result: string|undefined) => void} onDone - Completion callback
|
|
556
584
|
* @returns {boolean} Whether at least one upload was started
|
|
557
585
|
*/
|
|
558
|
-
uploadTestScreenshots ({ screenshots, traceId }, onDone) {
|
|
586
|
+
uploadTestScreenshots ({ screenshots, traceId, signal }, onDone) {
|
|
559
587
|
const exporter = this.tracer?._exporter
|
|
560
588
|
if (!Array.isArray(screenshots) || !screenshots.length ||
|
|
561
589
|
!exporter?.canUploadTestScreenshots?.() ||
|
|
@@ -581,6 +609,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
581
609
|
traceId,
|
|
582
610
|
idempotencyKey: `${traceId}:${basename(filePath)}`,
|
|
583
611
|
capturedAtMs: getScreenshotCapturedAtMs(filePath, filePath),
|
|
612
|
+
signal,
|
|
584
613
|
}, (error, uploaded = true) => {
|
|
585
614
|
if (uploaded) {
|
|
586
615
|
uploadResults[resultIndex] = error
|
|
@@ -523,6 +523,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
523
523
|
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: coverageLibrary })
|
|
524
524
|
this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeFiles.length)
|
|
525
525
|
}
|
|
526
|
+
this.tracer._exporter.deferTestSuiteSpan?.(testSuiteSpan)
|
|
526
527
|
testSuiteSpan.finish()
|
|
527
528
|
finishAllTraceSpans(testSuiteSpan)
|
|
528
529
|
}
|
|
@@ -566,6 +567,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
566
567
|
this.addSub('ci:vitest:session:finish', ({
|
|
567
568
|
status,
|
|
568
569
|
error,
|
|
570
|
+
isTestSessionFinalizationError,
|
|
569
571
|
testCodeCoverageLinesTotal,
|
|
570
572
|
isEarlyFlakeDetectionEnabled,
|
|
571
573
|
isEarlyFlakeDetectionFaulty,
|
|
@@ -588,6 +590,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
588
590
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
589
591
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
590
592
|
if (error) {
|
|
593
|
+
if (isTestSessionFinalizationError) {
|
|
594
|
+
this.tracer._exporter.setDeferredTestSuiteError?.(error)
|
|
595
|
+
}
|
|
591
596
|
this.testModuleSpan.setTag('error', error)
|
|
592
597
|
this.testSessionSpan.setTag('error', error)
|
|
593
598
|
}
|
|
@@ -617,6 +622,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
617
622
|
if (vitestPool) {
|
|
618
623
|
this.testSessionSpan.setTag(VITEST_POOL, vitestPool)
|
|
619
624
|
}
|
|
625
|
+
this.tracer._exporter.exportDeferredTestSuiteSpans?.()
|
|
620
626
|
this.testModuleSpan.finish()
|
|
621
627
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
622
628
|
this.testSessionSpan.finish()
|
|
@@ -6,7 +6,6 @@ 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 { matchesOptionalPeerFile } = require('../datadog-instrumentations/src/helpers/optional-peer-bundler')
|
|
10
9
|
const { isESMFile } = require('../datadog-esbuild/src/utils')
|
|
11
10
|
const log = require('./src/log')
|
|
12
11
|
|
|
@@ -137,16 +136,6 @@ class DatadogWebpackPlugin {
|
|
|
137
136
|
|
|
138
137
|
const normalizedResource = resource.replaceAll('\\', '/')
|
|
139
138
|
|
|
140
|
-
// Rewrite optional-peer loads so installed peers get bundled and survive relocation
|
|
141
|
-
// (#8980); absent peers stay opaque, so a build that does not opt into the feature does
|
|
142
|
-
// not follow their dependency chain (#8635).
|
|
143
|
-
if (matchesOptionalPeerFile(normalizedResource)) {
|
|
144
|
-
createData.loaders ||= []
|
|
145
|
-
createData.loaders.push({ loader: require.resolve('./src/optional-peer-loader') })
|
|
146
|
-
log.debug('INLINE: optional-peer loader applied to %s', normalizedResource)
|
|
147
|
-
return
|
|
148
|
-
}
|
|
149
|
-
|
|
150
139
|
if (!resource.includes('node_modules')) {
|
|
151
140
|
return
|
|
152
141
|
}
|
|
@@ -17,8 +17,10 @@ module.exports = {
|
|
|
17
17
|
* Fetches agent information from the /info endpoint
|
|
18
18
|
* @param {URL} url - The agent URL
|
|
19
19
|
* @param {Function} callback - Callback function with signature (err, agentInfo)
|
|
20
|
+
* @param {{ deadline?: number, signal?: AbortSignal }} [options] - Request finalization options
|
|
21
|
+
* @param {Function} [makeRequest] - Request implementation
|
|
20
22
|
*/
|
|
21
|
-
function fetchAgentInfo (url, callback) {
|
|
23
|
+
function fetchAgentInfo (url, callback, options = {}, makeRequest = request) {
|
|
22
24
|
const urlKey = url.href
|
|
23
25
|
|
|
24
26
|
if (cachedUrl !== null && cachedUrl !== urlKey) {
|
|
@@ -29,10 +31,9 @@ function fetchAgentInfo (url, callback) {
|
|
|
29
31
|
return process.nextTick(callback, null, cachedData)
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}, (err, res) => {
|
|
34
|
+
options.path = '/info'
|
|
35
|
+
options.url = url
|
|
36
|
+
makeRequest('', options, (err, res) => {
|
|
36
37
|
if (err) {
|
|
37
38
|
return callback(err)
|
|
38
39
|
}
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const tracerVersion = require('../../../../package.json').version
|
|
4
|
+
const { AIGuardClientError } = require('./errors')
|
|
5
|
+
const { parseEvaluationResponse } = require('./evaluation')
|
|
6
|
+
const TAGS = require('./tags')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the AI Guard host for a Datadog site.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} site
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
function aiGuardHost (site) {
|
|
15
|
+
return site.split('.').length === 2 ? `app.${site}` : site
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sends a request to the AI Guard service.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} body
|
|
22
|
+
* @param {{ url: string, headers: Record<string, string>, timeout: number }} opts
|
|
23
|
+
* @returns {Promise<{ status: number, body: unknown }>}
|
|
24
|
+
*/
|
|
3
25
|
async function executeRequest (body, opts) {
|
|
4
26
|
const postData = JSON.stringify(body)
|
|
5
27
|
const headers = {
|
|
@@ -22,4 +44,81 @@ async function executeRequest (body, opts) {
|
|
|
22
44
|
}
|
|
23
45
|
}
|
|
24
46
|
|
|
25
|
-
|
|
47
|
+
class AIGuardClient {
|
|
48
|
+
#headers
|
|
49
|
+
#evaluateUrl
|
|
50
|
+
#timeout
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {import('../config/config-base')} config
|
|
54
|
+
*/
|
|
55
|
+
constructor (config) {
|
|
56
|
+
this.#headers = {
|
|
57
|
+
'DD-API-KEY': config.DD_API_KEY,
|
|
58
|
+
'DD-APPLICATION-KEY': config.DD_APP_KEY,
|
|
59
|
+
'DD-AI-GUARD-VERSION': tracerVersion,
|
|
60
|
+
'DD-AI-GUARD-SOURCE': 'SDK',
|
|
61
|
+
'DD-AI-GUARD-LANGUAGE': 'nodejs',
|
|
62
|
+
}
|
|
63
|
+
const endpoint = config.experimental.aiguard.endpoint || `https://${aiGuardHost(config.site)}/api/v2/ai-guard`
|
|
64
|
+
this.#evaluateUrl = `${endpoint}/evaluate`
|
|
65
|
+
this.#timeout = config.experimental.aiguard.timeout
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Evaluates messages and converts the service response to the internal evaluation contract.
|
|
70
|
+
*
|
|
71
|
+
* @param {import('../../../../index').aiguard.Message[]} messages
|
|
72
|
+
* @param {{ service: string, env: string }} meta
|
|
73
|
+
* @returns {Promise<NonNullable<ReturnType<typeof parseEvaluationResponse>>>}
|
|
74
|
+
*/
|
|
75
|
+
evaluate (messages, meta) {
|
|
76
|
+
const payload = {
|
|
77
|
+
data: {
|
|
78
|
+
attributes: {
|
|
79
|
+
messages,
|
|
80
|
+
meta,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
return executeRequest(
|
|
85
|
+
payload,
|
|
86
|
+
{ url: this.#evaluateUrl, headers: this.#headers, timeout: this.#timeout }
|
|
87
|
+
)
|
|
88
|
+
.then(response => this.#parseResponse(response))
|
|
89
|
+
.catch(cause => {
|
|
90
|
+
if (cause instanceof AIGuardClientError) throw cause
|
|
91
|
+
|
|
92
|
+
throw new AIGuardClientError(`Unexpected error calling AI Guard service: ${cause.message}`, {
|
|
93
|
+
cause,
|
|
94
|
+
telemetryType: TAGS.ERROR_TYPE_CLIENT,
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validates an AI Guard HTTP response.
|
|
101
|
+
*
|
|
102
|
+
* @param {{ status: number, body: unknown }} response
|
|
103
|
+
* @returns {NonNullable<ReturnType<typeof parseEvaluationResponse>>}
|
|
104
|
+
*/
|
|
105
|
+
#parseResponse (response) {
|
|
106
|
+
if (response.status !== 200) {
|
|
107
|
+
throw new AIGuardClientError(`AI Guard service call failed, status ${response.status}`, {
|
|
108
|
+
errors: response.body?.errors,
|
|
109
|
+
telemetryType: TAGS.ERROR_TYPE_STATUS,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const evaluation = parseEvaluationResponse(response.body)
|
|
114
|
+
if (!evaluation) {
|
|
115
|
+
throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`, {
|
|
116
|
+
telemetryType: TAGS.ERROR_TYPE_RESPONSE,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return evaluation
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = AIGuardClient
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class AIGuardAbortError extends Error {
|
|
4
|
+
/**
|
|
5
|
+
* @param {string|undefined} reason
|
|
6
|
+
* @param {Array<unknown>} tags
|
|
7
|
+
* @param {Record<string, unknown>} tagProbabilities
|
|
8
|
+
* @param {Array<unknown>} sds
|
|
9
|
+
*/
|
|
10
|
+
constructor (reason, tags, tagProbabilities, sds) {
|
|
11
|
+
super(reason)
|
|
12
|
+
this.name = 'AIGuardAbortError'
|
|
13
|
+
this.reason = reason
|
|
14
|
+
this.tags = tags
|
|
15
|
+
this.tagProbabilities = tagProbabilities
|
|
16
|
+
this.sds = sds || []
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class AIGuardClientError extends Error {
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} message
|
|
23
|
+
* @param {{ telemetryType: string, errors?: Array<unknown>, cause?: Error }} opts
|
|
24
|
+
*/
|
|
25
|
+
constructor (message, opts) {
|
|
26
|
+
super(message)
|
|
27
|
+
this.name = 'AIGuardClientError'
|
|
28
|
+
this.telemetryType = opts.telemetryType
|
|
29
|
+
if (opts.errors) {
|
|
30
|
+
this.errors = opts.errors
|
|
31
|
+
}
|
|
32
|
+
if (opts.cause) {
|
|
33
|
+
this.cause = opts.cause
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
AIGuardAbortError,
|
|
40
|
+
AIGuardClientError,
|
|
41
|
+
}
|