dd-trace 5.68.0 → 5.70.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/LICENSE-3rdparty.csv +6 -1
- package/index.d.ts +193 -1
- package/package.json +11 -2
- package/packages/datadog-core/src/storage.js +14 -13
- package/packages/datadog-esbuild/index.js +62 -26
- package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
- package/packages/datadog-instrumentations/src/jest.js +86 -84
- package/packages/datadog-instrumentations/src/mocha/utils.js +5 -21
- package/packages/datadog-instrumentations/src/playwright.js +5 -2
- package/packages/datadog-plugin-confluentinc-kafka-javascript/src/index.js +6 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +0 -1
- package/packages/datadog-plugin-mongodb-core/src/index.js +18 -5
- package/packages/dd-trace/src/aiguard/client.js +25 -0
- package/packages/dd-trace/src/aiguard/noop.js +9 -0
- package/packages/dd-trace/src/aiguard/sdk.js +173 -0
- package/packages/dd-trace/src/aiguard/tags.js +11 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +21 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +6 -3
- package/packages/dd-trace/src/appsec/stack_trace.js +20 -1
- package/packages/dd-trace/src/config-helper.js +8 -1
- package/packages/dd-trace/src/config.js +23 -0
- package/packages/dd-trace/src/config_defaults.js +5 -0
- package/packages/dd-trace/src/datastreams/index.js +23 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +11 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +8 -6
- package/packages/dd-trace/src/log/index.js +4 -4
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/payload-tagging/config/index.js +16 -0
- package/packages/dd-trace/src/payload-tagging/index.js +26 -15
- package/packages/dd-trace/src/payload-tagging/tagging.js +17 -8
- package/packages/dd-trace/src/pkg.js +3 -1
- package/packages/dd-trace/src/plugins/composite.js +3 -0
- package/packages/dd-trace/src/plugins/plugin.js +67 -0
- package/packages/dd-trace/src/plugins/util/git.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +19 -30
- package/packages/dd-trace/src/priority_sampler.js +70 -46
- package/packages/dd-trace/src/proxy.js +15 -0
- package/packages/dd-trace/src/rate_limiter.js +26 -1
- package/packages/dd-trace/src/sampling_rule.js +124 -2
- package/packages/dd-trace/src/span_sampler.js +19 -0
- package/packages/dd-trace/src/standalone/product.js +9 -0
- package/packages/dd-trace/src/standalone/tracesource.js +16 -1
- package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +13 -0
- package/packages/dd-trace/src/startup-log.js +19 -1
- package/packages/dd-trace/src/supported-configurations.json +6 -0
- package/packages/dd-trace/src/util.js +1 -1
- package/version.js +4 -2
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const NoopAIGuard = require('./noop')
|
|
4
|
+
const executeRequest = require('./client')
|
|
5
|
+
const {
|
|
6
|
+
AI_GUARD_RESOURCE,
|
|
7
|
+
AI_GUARD_TARGET_TAG_KEY,
|
|
8
|
+
AI_GUARD_REASON_TAG_KEY,
|
|
9
|
+
AI_GUARD_ACTION_TAG_KEY,
|
|
10
|
+
AI_GUARD_BLOCKED_TAG_KEY,
|
|
11
|
+
AI_GUARD_META_STRUCT_KEY,
|
|
12
|
+
AI_GUARD_TOOL_NAME_TAG_KEY
|
|
13
|
+
} = require('./tags')
|
|
14
|
+
const log = require('../log')
|
|
15
|
+
|
|
16
|
+
const ALLOW = 'ALLOW'
|
|
17
|
+
|
|
18
|
+
class AIGuardAbortError extends Error {
|
|
19
|
+
constructor (reason) {
|
|
20
|
+
super(reason)
|
|
21
|
+
this.name = 'AIGuardAbortError'
|
|
22
|
+
this.reason = reason
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class AIGuardClientError extends Error {
|
|
27
|
+
constructor (message, opts = {}) {
|
|
28
|
+
super(message)
|
|
29
|
+
this.name = 'AIGuardClientError'
|
|
30
|
+
if (opts.errors) {
|
|
31
|
+
this.errors = opts.errors
|
|
32
|
+
}
|
|
33
|
+
if (opts.cause) {
|
|
34
|
+
this.cause = opts.cause
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class AIGuard extends NoopAIGuard {
|
|
40
|
+
#initialized
|
|
41
|
+
#tracer
|
|
42
|
+
#headers
|
|
43
|
+
#evaluateUrl
|
|
44
|
+
#timeout
|
|
45
|
+
#maxMessagesLength
|
|
46
|
+
#maxContentSize
|
|
47
|
+
#meta
|
|
48
|
+
|
|
49
|
+
constructor (tracer, config) {
|
|
50
|
+
super()
|
|
51
|
+
|
|
52
|
+
if (!config.apiKey || !config.appKey) {
|
|
53
|
+
log.error('AIGuard: missing api and/or app keys, use env DD_API_KEY and DD_APP_KEY')
|
|
54
|
+
this.#initialized = false
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
this.#tracer = tracer
|
|
58
|
+
this.#headers = {
|
|
59
|
+
'DD-API-KEY': config.apiKey,
|
|
60
|
+
'DD-APPLICATION-KEY': config.appKey,
|
|
61
|
+
}
|
|
62
|
+
const endpoint = config.experimental.aiguard.endpoint || `https://app.${config.site}/api/v2/ai-guard`
|
|
63
|
+
this.#evaluateUrl = `${endpoint}/evaluate`
|
|
64
|
+
this.#timeout = config.experimental.aiguard.timeout
|
|
65
|
+
this.#maxMessagesLength = config.experimental.aiguard.maxMessagesLength
|
|
66
|
+
this.#maxContentSize = config.experimental.aiguard.maxContentSize
|
|
67
|
+
this.#meta = { service: config.service, env: config.env }
|
|
68
|
+
this.#initialized = true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#truncate (messages) {
|
|
72
|
+
const size = Math.min(messages.length, this.#maxMessagesLength)
|
|
73
|
+
const result = messages.slice(-size)
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < size; i++) {
|
|
76
|
+
const message = result[i]
|
|
77
|
+
if (message.content?.length > this.#maxContentSize) {
|
|
78
|
+
result[i] = { ...message, content: message.content.slice(0, this.#maxContentSize) }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#isToolCall (message) {
|
|
85
|
+
return message.tool_calls || message.tool_call_id
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#getToolName (message, history) {
|
|
89
|
+
// 1. assistant message with tool calls
|
|
90
|
+
if (message.tool_calls) {
|
|
91
|
+
const names = message.tool_calls.map((tool) => tool.function.name)
|
|
92
|
+
return names.length === 0 ? null : names.join(',')
|
|
93
|
+
}
|
|
94
|
+
// 2. assistant message with tool output (search the linked tool call in reverse order)
|
|
95
|
+
const id = message.tool_call_id
|
|
96
|
+
for (let i = history.length - 2; i >= 0; i--) {
|
|
97
|
+
const item = history[i]
|
|
98
|
+
if (item.tool_calls) {
|
|
99
|
+
for (const toolCall of item.tool_calls) {
|
|
100
|
+
if (toolCall.id === id) {
|
|
101
|
+
return toolCall.function.name
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
evaluate (messages, opts) {
|
|
110
|
+
if (!this.#initialized) {
|
|
111
|
+
return super.evaluate(messages, opts)
|
|
112
|
+
}
|
|
113
|
+
const { block = false } = opts ?? {}
|
|
114
|
+
return this.#tracer.trace(AI_GUARD_RESOURCE, {}, async (span) => {
|
|
115
|
+
const last = messages[messages.length - 1]
|
|
116
|
+
const target = this.#isToolCall(last) ? 'tool' : 'prompt'
|
|
117
|
+
span.setTag(AI_GUARD_TARGET_TAG_KEY, target)
|
|
118
|
+
if (target === 'tool') {
|
|
119
|
+
const name = this.#getToolName(last, messages)
|
|
120
|
+
if (name) {
|
|
121
|
+
span.setTag(AI_GUARD_TOOL_NAME_TAG_KEY, name)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
span.meta_struct = {
|
|
125
|
+
[AI_GUARD_META_STRUCT_KEY]: {
|
|
126
|
+
messages: this.#truncate(messages)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let response
|
|
130
|
+
try {
|
|
131
|
+
const payload = {
|
|
132
|
+
data: {
|
|
133
|
+
attributes: {
|
|
134
|
+
messages,
|
|
135
|
+
meta: this.#meta,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
response = await executeRequest(
|
|
140
|
+
payload,
|
|
141
|
+
{ url: this.#evaluateUrl, headers: this.#headers, timeout: this.#timeout })
|
|
142
|
+
} catch (e) {
|
|
143
|
+
throw new AIGuardClientError('Unexpected error calling AI Guard service', { cause: e })
|
|
144
|
+
}
|
|
145
|
+
if (response.status !== 200) {
|
|
146
|
+
throw new AIGuardClientError(
|
|
147
|
+
`AI Guard service call failed, status ${response.status}`,
|
|
148
|
+
{ errors: response.body?.errors })
|
|
149
|
+
}
|
|
150
|
+
let action, reason, blockingEnabled
|
|
151
|
+
try {
|
|
152
|
+
const attr = response.body.data.attributes
|
|
153
|
+
if (!attr.action) {
|
|
154
|
+
throw new Error('Action missing from response')
|
|
155
|
+
}
|
|
156
|
+
action = attr.action
|
|
157
|
+
reason = attr.reason
|
|
158
|
+
blockingEnabled = attr.is_blocking_enabled ?? false
|
|
159
|
+
} catch (e) {
|
|
160
|
+
throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`, { cause: e })
|
|
161
|
+
}
|
|
162
|
+
span.setTag(AI_GUARD_ACTION_TAG_KEY, action)
|
|
163
|
+
span.setTag(AI_GUARD_REASON_TAG_KEY, reason)
|
|
164
|
+
if (block && blockingEnabled && action !== ALLOW) {
|
|
165
|
+
span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
|
|
166
|
+
throw new AIGuardAbortError(reason)
|
|
167
|
+
}
|
|
168
|
+
return { action, reason }
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = AIGuard
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
AI_GUARD_RESOURCE: 'ai_guard',
|
|
5
|
+
AI_GUARD_TARGET_TAG_KEY: 'ai_guard.target',
|
|
6
|
+
AI_GUARD_TOOL_NAME_TAG_KEY: 'ai_guard.tool_name',
|
|
7
|
+
AI_GUARD_ACTION_TAG_KEY: 'ai_guard.action',
|
|
8
|
+
AI_GUARD_REASON_TAG_KEY: 'ai_guard.reason',
|
|
9
|
+
AI_GUARD_BLOCKED_TAG_KEY: 'ai_guard.blocked',
|
|
10
|
+
AI_GUARD_META_STRUCT_KEY: 'ai_guard'
|
|
11
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const process = require('process')
|
|
5
5
|
const { ddBasePath } = require('../../util')
|
|
6
|
+
const { getOriginalPathAndLineFromSourceMap } = require('./taint-tracking/rewriter')
|
|
6
7
|
const pathLine = {
|
|
7
8
|
getNodeModulesPaths,
|
|
8
9
|
getRelativePath,
|
|
@@ -30,8 +31,24 @@ function getNonDDCallSiteFrames (callSiteFrames, externallyExcludedPaths) {
|
|
|
30
31
|
const result = []
|
|
31
32
|
|
|
32
33
|
for (const callsite of callSiteFrames) {
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
let filepath = callsite.file
|
|
35
|
+
|
|
36
|
+
if (globalThis.__DD_ESBUILD_IAST_WITH_SM) {
|
|
37
|
+
const callsiteLocation = {
|
|
38
|
+
path: getRelativePath(filepath),
|
|
39
|
+
line: callsite.line,
|
|
40
|
+
column: callsite.column
|
|
41
|
+
}
|
|
42
|
+
const { path: originalPath, line, column } = getOriginalPathAndLineFromSourceMap(callsiteLocation)
|
|
43
|
+
callsite.path = filepath = originalPath
|
|
44
|
+
callsite.line = line
|
|
45
|
+
callsite.column = column
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
!isExcluded(callsite, externallyExcludedPaths) &&
|
|
50
|
+
(!filepath.includes(pathLine.ddBasePath) || globalThis.__DD_ESBUILD_IAST_WITH_NO_SM)
|
|
51
|
+
) {
|
|
35
52
|
callsite.path = getRelativePath(filepath)
|
|
36
53
|
callsite.isInternal = !path.isAbsolute(filepath)
|
|
37
54
|
|
|
@@ -43,12 +60,12 @@ function getNonDDCallSiteFrames (callSiteFrames, externallyExcludedPaths) {
|
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
function getRelativePath (filepath) {
|
|
46
|
-
return path.relative(process.cwd(), filepath)
|
|
63
|
+
return filepath && path.relative(process.cwd(), filepath)
|
|
47
64
|
}
|
|
48
65
|
|
|
49
66
|
function isExcluded (callsite, externallyExcludedPaths) {
|
|
50
67
|
if (callsite.isNative) return true
|
|
51
|
-
const filename = callsite.file
|
|
68
|
+
const filename = globalThis.__DD_ESBUILD_IAST_WITH_SM ? callsite.path : callsite.file
|
|
52
69
|
if (!filename) {
|
|
53
70
|
return true
|
|
54
71
|
}
|
|
@@ -44,7 +44,7 @@ function setGetOriginalPathAndLineFromSourceMapFunction (chainSourceMap, { getOr
|
|
|
44
44
|
? (path, line, column) => {
|
|
45
45
|
// if --enable-source-maps is present stacktraces of the rewritten files contain the original path, file and
|
|
46
46
|
// column because the sourcemap chaining is done during the rewriting process so we can skip it
|
|
47
|
-
return isPrivateModule(path) && !isDdTrace(path)
|
|
47
|
+
return !globalThis.__DD_ESBUILD_IAST_WITH_SM && isPrivateModule(path) && !isDdTrace(path)
|
|
48
48
|
? { path, line, column }
|
|
49
49
|
: getOriginalPathAndLineFromSourceMap(path, line, column)
|
|
50
50
|
}
|
|
@@ -170,7 +170,10 @@ function enableRewriter (telemetryVerbosity) {
|
|
|
170
170
|
const rewriter = getRewriter(telemetryVerbosity)
|
|
171
171
|
if (rewriter) {
|
|
172
172
|
shimPrepareStackTrace()
|
|
173
|
-
|
|
173
|
+
if (!globalThis.__DD_ESBUILD_IAST_WITH_SM && !globalThis.__DD_ESBUILD_IAST_WITH_NO_SM) {
|
|
174
|
+
// Avoid rewriting twice when application has been bundled
|
|
175
|
+
shimmer.wrap(Module.prototype, '_compile', compileMethod => getCompileMethodFn(compileMethod))
|
|
176
|
+
}
|
|
174
177
|
}
|
|
175
178
|
}
|
|
176
179
|
|
|
@@ -264,5 +267,5 @@ function enable (configArg) {
|
|
|
264
267
|
}
|
|
265
268
|
|
|
266
269
|
module.exports = {
|
|
267
|
-
enable, disable, getOriginalPathAndLineFromSourceMap
|
|
270
|
+
enable, disable, getOriginalPathAndLineFromSourceMap, getRewriter
|
|
268
271
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { ddBasePath } = require('../util')
|
|
4
|
+
const { getOriginalPathAndLineFromSourceMap } = require('./iast/taint-tracking/rewriter')
|
|
4
5
|
|
|
5
6
|
const LIBRARY_FRAMES_BUFFER = 20
|
|
6
7
|
|
|
@@ -32,7 +33,25 @@ function getCallSiteList (maxDepth = 100, constructorOpt) {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function filterOutFramesFromLibrary (callSiteList) {
|
|
35
|
-
return callSiteList.filter(callSite =>
|
|
36
|
+
return callSiteList.filter(callSite => {
|
|
37
|
+
if (globalThis.__DD_ESBUILD_IAST_WITH_NO_SM) {
|
|
38
|
+
// bundled and no SourceMap, not possible to discriminate if the frame comes from dd-trace code or not
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (globalThis.__DD_ESBUILD_IAST_WITH_SM) {
|
|
43
|
+
// bundled with SourceMap, get original file and line to discriminate if comes from dd-trace or not
|
|
44
|
+
const callSiteLocation = {
|
|
45
|
+
path: callSite.getFileName(),
|
|
46
|
+
line: callSite.getLineNumber(),
|
|
47
|
+
column: callSite.getColumnNumber()
|
|
48
|
+
}
|
|
49
|
+
const { path } = getOriginalPathAndLineFromSourceMap(callSiteLocation)
|
|
50
|
+
return !path?.startsWith(ddBasePath)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return !callSite.getFileName()?.startsWith(ddBasePath)
|
|
54
|
+
})
|
|
36
55
|
}
|
|
37
56
|
|
|
38
57
|
function getCallsiteFrames (maxDepth = 32, constructorOpt = getCallsiteFrames, callSiteListGetter = getCallSiteList) {
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
const { deprecate } = require('util')
|
|
6
6
|
const { supportedConfigurations, aliases, deprecations } = require('./supported-configurations.json')
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Types for environment variable handling.
|
|
10
|
+
*
|
|
11
|
+
* @typedef {keyof typeof supportedConfigurations} SupportedEnvKey
|
|
12
|
+
* @typedef {Partial<typeof process.env> & Partial<Record<SupportedEnvKey, string|undefined>>} TracerEnv
|
|
13
|
+
*/
|
|
14
|
+
|
|
8
15
|
const aliasToCanonical = {}
|
|
9
16
|
for (const canonical of Object.keys(aliases)) {
|
|
10
17
|
for (const alias of aliases[canonical]) {
|
|
@@ -32,7 +39,7 @@ module.exports = {
|
|
|
32
39
|
* Returns the environment variables that are supported by the tracer
|
|
33
40
|
* (including all non-Datadog/OTEL specific environment variables)
|
|
34
41
|
*
|
|
35
|
-
* @returns {
|
|
42
|
+
* @returns {TracerEnv} The environment variables
|
|
36
43
|
*/
|
|
37
44
|
getEnvironmentVariables () {
|
|
38
45
|
const configs = {}
|
|
@@ -286,6 +286,7 @@ class Config {
|
|
|
286
286
|
checkIfBothOtelAndDdEnvVarSet()
|
|
287
287
|
|
|
288
288
|
const DD_API_KEY = getEnvironmentVariable('DD_API_KEY')
|
|
289
|
+
const DD_APP_KEY = getEnvironmentVariable('DD_APP_KEY')
|
|
289
290
|
|
|
290
291
|
if (getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE') && (
|
|
291
292
|
getEnvironmentVariable('DD_TRACE_PROPAGATION_STYLE_INJECT') ||
|
|
@@ -338,6 +339,7 @@ class Config {
|
|
|
338
339
|
|
|
339
340
|
// TODO: refactor
|
|
340
341
|
this.apiKey = DD_API_KEY
|
|
342
|
+
this.appKey = DD_APP_KEY
|
|
341
343
|
|
|
342
344
|
// sent in telemetry event app-started
|
|
343
345
|
this.installSignature = {
|
|
@@ -485,6 +487,11 @@ class Config {
|
|
|
485
487
|
const {
|
|
486
488
|
AWS_LAMBDA_FUNCTION_NAME,
|
|
487
489
|
DD_AGENT_HOST,
|
|
490
|
+
DD_AI_GUARD_ENABLED,
|
|
491
|
+
DD_AI_GUARD_ENDPOINT,
|
|
492
|
+
DD_AI_GUARD_MAX_CONTENT_SIZE,
|
|
493
|
+
DD_AI_GUARD_MAX_MESSAGES_LENGTH,
|
|
494
|
+
DD_AI_GUARD_TIMEOUT,
|
|
488
495
|
DD_API_SECURITY_ENABLED,
|
|
489
496
|
DD_API_SECURITY_SAMPLE_DELAY,
|
|
490
497
|
DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED,
|
|
@@ -713,6 +720,14 @@ class Config {
|
|
|
713
720
|
this._envUnprocessed['dynamicInstrumentation.uploadInterval'] = DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS
|
|
714
721
|
this._setString(env, 'env', DD_ENV || tags.env)
|
|
715
722
|
this._setBoolean(env, 'traceEnabled', DD_TRACE_ENABLED)
|
|
723
|
+
this._setBoolean(env, 'experimental.aiguard.enabled', DD_AI_GUARD_ENABLED)
|
|
724
|
+
this._setString(env, 'experimental.aiguard.endpoint', DD_AI_GUARD_ENDPOINT)
|
|
725
|
+
env['experimental.aiguard.maxContentSize'] = maybeInt(DD_AI_GUARD_MAX_CONTENT_SIZE)
|
|
726
|
+
this._envUnprocessed['experimental.aiguard.maxContentSize'] = DD_AI_GUARD_MAX_CONTENT_SIZE
|
|
727
|
+
env['experimental.aiguard.maxMessagesLength'] = maybeInt(DD_AI_GUARD_MAX_MESSAGES_LENGTH)
|
|
728
|
+
this._envUnprocessed['experimental.aiguard.maxMessagesLength'] = DD_AI_GUARD_MAX_MESSAGES_LENGTH
|
|
729
|
+
env['experimental.aiguard.timeout'] = maybeInt(DD_AI_GUARD_TIMEOUT)
|
|
730
|
+
this._envUnprocessed['experimental.aiguard.timeout'] = DD_AI_GUARD_TIMEOUT
|
|
716
731
|
this._setBoolean(env, 'experimental.enableGetRumData', DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED)
|
|
717
732
|
this._setString(env, 'experimental.exporter', DD_TRACE_EXPERIMENTAL_EXPORTER)
|
|
718
733
|
if (AWS_LAMBDA_FUNCTION_NAME) env.flushInterval = 0
|
|
@@ -943,6 +958,14 @@ class Config {
|
|
|
943
958
|
this._optsUnprocessed['dynamicInstrumentation.uploadIntervalSeconds'] =
|
|
944
959
|
options.dynamicInstrumentation?.uploadIntervalSeconds
|
|
945
960
|
this._setString(opts, 'env', options.env || tags.env)
|
|
961
|
+
this._setBoolean(opts, 'experimental.aiguard.enabled', options.experimental?.aiguard?.enabled)
|
|
962
|
+
this._setString(opts, 'experimental.aiguard.endpoint', options.experimental?.aiguard?.endpoint)
|
|
963
|
+
opts['experimental.aiguard.maxMessagesLength'] = maybeInt(options.experimental?.aiguard?.maxMessagesLength)
|
|
964
|
+
this._optsUnprocessed['experimental.aiguard.maxMessagesLength'] = options.experimental?.aiguard?.maxMessagesLength
|
|
965
|
+
opts['experimental.aiguard.maxContentSize'] = maybeInt(options.experimental?.aiguard?.maxContentSize)
|
|
966
|
+
this._optsUnprocessed['experimental.aiguard.maxContentSize'] = options.experimental?.aiguard?.maxContentSize
|
|
967
|
+
opts['experimental.aiguard.timeout'] = maybeInt(options.experimental?.aiguard?.timeout)
|
|
968
|
+
this._optsUnprocessed['experimental.aiguard.timeout'] = options.experimental?.aiguard?.timeout
|
|
946
969
|
this._setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData)
|
|
947
970
|
this._setString(opts, 'experimental.exporter', options.experimental?.exporter)
|
|
948
971
|
opts.flushInterval = maybeInt(options.flushInterval)
|
|
@@ -68,6 +68,11 @@ module.exports = {
|
|
|
68
68
|
'dynamicInstrumentation.redactionExcludedIdentifiers': [],
|
|
69
69
|
'dynamicInstrumentation.uploadIntervalSeconds': 1,
|
|
70
70
|
env: undefined,
|
|
71
|
+
'experimental.aiguard.enabled': false,
|
|
72
|
+
'experimental.aiguard.endpoint': undefined,
|
|
73
|
+
'experimental.aiguard.maxMessagesLength': 16,
|
|
74
|
+
'experimental.aiguard.maxContentSize': 512 * 1024,
|
|
75
|
+
'experimental.aiguard.timeout': 10_000, // ms
|
|
71
76
|
'experimental.enableGetRumData': false,
|
|
72
77
|
'experimental.exporter': undefined,
|
|
73
78
|
flushInterval: 2000,
|
|
@@ -11,7 +11,14 @@ const {
|
|
|
11
11
|
// plugins instead of having dedicated DSM plugins that are themselves
|
|
12
12
|
// lazy loaded.
|
|
13
13
|
//
|
|
14
|
-
// TODO: Remove this when DSM has been moved to
|
|
14
|
+
// TODO: Remove this when DSM has been moved to dedicated plugins.
|
|
15
|
+
/**
|
|
16
|
+
* @template T extends new (...args: any[]) => any
|
|
17
|
+
* @param {() => T} classGetter
|
|
18
|
+
* @param {string[]} methods
|
|
19
|
+
* @param {string[]} staticMethods
|
|
20
|
+
* @returns {T}
|
|
21
|
+
*/
|
|
15
22
|
function lazyClass (classGetter, methods = [], staticMethods = []) {
|
|
16
23
|
let constructorArgs
|
|
17
24
|
let ActiveClass
|
|
@@ -50,22 +57,34 @@ function lazyClass (classGetter, methods = [], staticMethods = []) {
|
|
|
50
57
|
return LazyClass
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @type {typeof import('./pathway').DsmPathwayCodec}
|
|
62
|
+
*/
|
|
53
63
|
const DsmPathwayCodec = lazyClass(() => require('./pathway').DsmPathwayCodec, [], [
|
|
54
64
|
'encode',
|
|
55
65
|
'decode'
|
|
56
66
|
])
|
|
57
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @type {typeof import('./checkpointer').DataStreamsCheckpointer}
|
|
70
|
+
*/
|
|
58
71
|
const DataStreamsCheckpointer = lazyClass(() => require('./checkpointer').DataStreamsCheckpointer, [
|
|
59
72
|
'setProduceCheckpoint',
|
|
60
73
|
'setConsumeCheckpoint'
|
|
61
74
|
])
|
|
62
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @type {typeof import('./manager').DataStreamsManager}
|
|
78
|
+
*/
|
|
63
79
|
const DataStreamsManager = lazyClass(() => require('./manager').DataStreamsManager, [
|
|
64
80
|
'setCheckpoint',
|
|
65
81
|
'decodeDataStreamsContext'
|
|
66
82
|
])
|
|
67
83
|
|
|
68
84
|
// TODO: Are all those methods actually public?
|
|
85
|
+
/**
|
|
86
|
+
* @type {typeof import('./processor').DataStreamsProcessor}
|
|
87
|
+
*/
|
|
69
88
|
const DataStreamsProcessor = lazyClass(() => require('./processor').DataStreamsProcessor, [
|
|
70
89
|
'onInterval',
|
|
71
90
|
'bucketFromTimestamp',
|
|
@@ -79,6 +98,9 @@ const DataStreamsProcessor = lazyClass(() => require('./processor').DataStreamsP
|
|
|
79
98
|
'getSchema'
|
|
80
99
|
])
|
|
81
100
|
|
|
101
|
+
/**
|
|
102
|
+
* @type {typeof import('./schemas/schema_builder').SchemaBuilder}
|
|
103
|
+
*/
|
|
82
104
|
const SchemaBuilder = lazyClass(() => require('./schemas/schema_builder').SchemaBuilder, [
|
|
83
105
|
'build',
|
|
84
106
|
'addProperty',
|
|
@@ -80,7 +80,7 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
|
|
|
80
80
|
* We use the tool description as the next best identifier for a tool.
|
|
81
81
|
*
|
|
82
82
|
* @param {string} toolDescription
|
|
83
|
-
* @returns {string}
|
|
83
|
+
* @returns {string | undefined}
|
|
84
84
|
*/
|
|
85
85
|
findToolName (toolDescription) {
|
|
86
86
|
for (const availableTool of this.#availableTools) {
|
|
@@ -91,6 +91,9 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* @override
|
|
96
|
+
*/
|
|
94
97
|
getLLMObsSpanRegisterOptions (ctx) {
|
|
95
98
|
const span = ctx.currentStore?.span
|
|
96
99
|
const operation = getOperation(span)
|
|
@@ -100,6 +103,9 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
|
|
|
100
103
|
return { kind, name: getLlmObsSpanName(operation, ctx.attributes['ai.telemetry.functionId']) }
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
/**
|
|
107
|
+
* @override
|
|
108
|
+
*/
|
|
103
109
|
setLLMObsTags (ctx) {
|
|
104
110
|
const span = ctx.currentStore?.span
|
|
105
111
|
if (!span) return
|
|
@@ -212,6 +218,10 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
|
|
|
212
218
|
this._tagger.tagMetadata(span, metadata)
|
|
213
219
|
}
|
|
214
220
|
|
|
221
|
+
/**
|
|
222
|
+
* @param {import('../../../opentracing/span')} span
|
|
223
|
+
* @param {Record<string, unknown>} tags
|
|
224
|
+
*/
|
|
215
225
|
setLLMOperationTags (span, tags) {
|
|
216
226
|
const toolsForModel = tags['ai.prompt.tools']?.map(getJsonStringValue)
|
|
217
227
|
|
|
@@ -34,7 +34,7 @@ function getSpanTags (ctx) {
|
|
|
34
34
|
* getOperation(span) // 'doGenerate'
|
|
35
35
|
*
|
|
36
36
|
* @param {import('../../../opentracing/span')} span
|
|
37
|
-
* @returns {string}
|
|
37
|
+
* @returns {string | undefined}
|
|
38
38
|
*/
|
|
39
39
|
function getOperation (span) {
|
|
40
40
|
const name = span._name
|
|
@@ -45,8 +45,9 @@ function getOperation (span) {
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Get the LLM token usage from the span tags
|
|
48
|
-
* @
|
|
49
|
-
* @
|
|
48
|
+
* @template T extends {inputTokens: number, outputTokens: number, totalTokens: number}
|
|
49
|
+
* @param {T} tags
|
|
50
|
+
* @returns {Pick<T, 'inputTokens' | 'outputTokens' | 'totalTokens'>}
|
|
50
51
|
*/
|
|
51
52
|
function getUsage (tags) {
|
|
52
53
|
const usage = {}
|
|
@@ -64,9 +65,10 @@ function getUsage (tags) {
|
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Safely JSON parses a string value with a default fallback
|
|
68
|
+
* @template T typeof defaultValue
|
|
67
69
|
* @param {string} str
|
|
68
|
-
* @param {
|
|
69
|
-
* @returns {Record<string,
|
|
70
|
+
* @param {T} defaultValue
|
|
71
|
+
* @returns {Record<string, unknown> | string | Array<unknown> | null | T}
|
|
70
72
|
*/
|
|
71
73
|
function getJsonStringValue (str, defaultValue) {
|
|
72
74
|
let maybeValue = defaultValue
|
|
@@ -81,7 +83,7 @@ function getJsonStringValue (str, defaultValue) {
|
|
|
81
83
|
|
|
82
84
|
/**
|
|
83
85
|
* Get the model metadata from the span tags (top_p, top_k, temperature, etc.)
|
|
84
|
-
* @param {
|
|
86
|
+
* @param {Record<string, unknown>} tags
|
|
85
87
|
* @returns {Record<string, string> | null}
|
|
86
88
|
*/
|
|
87
89
|
function getModelMetadata (tags) {
|
|
@@ -111,11 +111,11 @@ const log = {
|
|
|
111
111
|
|
|
112
112
|
isEnabled (fleetStableConfigValue, localStableConfigValue) {
|
|
113
113
|
return isTrue(
|
|
114
|
-
|
|
114
|
+
fleetStableConfigValue ??
|
|
115
115
|
getEnvironmentVariable('DD_TRACE_DEBUG') ??
|
|
116
|
-
getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug')
|
|
117
|
-
|
|
118
|
-
config.enabled
|
|
116
|
+
(getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug' || undefined) ??
|
|
117
|
+
localStableConfigValue ??
|
|
118
|
+
config.enabled
|
|
119
119
|
)
|
|
120
120
|
},
|
|
121
121
|
|
|
@@ -4,18 +4,22 @@ const NoopTracer = require('./tracer')
|
|
|
4
4
|
const NoopAppsecSdk = require('../appsec/sdk/noop')
|
|
5
5
|
const NoopDogStatsDClient = require('./dogstatsd')
|
|
6
6
|
const NoopLLMObsSDK = require('../llmobs/noop')
|
|
7
|
+
const NoopAIGuardSDK = require('../aiguard/noop')
|
|
7
8
|
|
|
8
9
|
const noop = new NoopTracer()
|
|
9
10
|
const noopAppsec = new NoopAppsecSdk()
|
|
10
11
|
const noopDogStatsDClient = new NoopDogStatsDClient()
|
|
11
12
|
const noopLLMObs = new NoopLLMObsSDK(noop)
|
|
13
|
+
const noopAIGuard = new NoopAIGuardSDK()
|
|
12
14
|
|
|
15
|
+
/** @type {import('../../src/index')} Proxy */
|
|
13
16
|
class NoopProxy {
|
|
14
17
|
constructor () {
|
|
15
18
|
this._tracer = noop
|
|
16
19
|
this.appsec = noopAppsec
|
|
17
20
|
this.dogstatsd = noopDogStatsDClient
|
|
18
21
|
this.llmobs = noopLLMObs
|
|
22
|
+
this.aiguard = noopAIGuard
|
|
19
23
|
this.setBaggageItem = () => {}
|
|
20
24
|
this.getBaggageItem = () => {}
|
|
21
25
|
this.getAllBaggageItems = () => {}
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
const aws = require('./aws.json')
|
|
4
4
|
const sdks = { aws }
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Builds rules per service for a given SDK, appending user-provided rules.
|
|
8
|
+
*
|
|
9
|
+
* @param {Record<string, { request: string[], response: string[], expand: string[] }>} sdk
|
|
10
|
+
* @param {string[]} requestInput
|
|
11
|
+
* @param {string[]} responseInput
|
|
12
|
+
* @returns {Record<string, { request: string[], response: string[], expand: string[] }>}
|
|
13
|
+
*/
|
|
6
14
|
function getSDKRules (sdk, requestInput, responseInput) {
|
|
7
15
|
const sdkServiceRules = {}
|
|
8
16
|
for (const [service, serviceRules] of Object.entries(sdk)) {
|
|
@@ -17,6 +25,14 @@ function getSDKRules (sdk, requestInput, responseInput) {
|
|
|
17
25
|
return sdkServiceRules
|
|
18
26
|
}
|
|
19
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Appends input rules to all supported SDKs and returns a structure mapping SDK
|
|
30
|
+
* names to per-service rules.
|
|
31
|
+
*
|
|
32
|
+
* @param {string[]} [requestInput=[]]
|
|
33
|
+
* @param {string[]} [responseInput=[]]
|
|
34
|
+
* @returns {Record<string, Record<string, { request: string[], response: string[], expand: string[] }>>}
|
|
35
|
+
*/
|
|
20
36
|
function appendRules (requestInput = [], responseInput = []) {
|
|
21
37
|
const sdkRules = {}
|
|
22
38
|
for (const [name, sdk] of Object.entries(sdks)) {
|