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.
Files changed (48) hide show
  1. package/LICENSE-3rdparty.csv +6 -1
  2. package/index.d.ts +193 -1
  3. package/package.json +11 -2
  4. package/packages/datadog-core/src/storage.js +14 -13
  5. package/packages/datadog-esbuild/index.js +62 -26
  6. package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
  7. package/packages/datadog-instrumentations/src/jest.js +86 -84
  8. package/packages/datadog-instrumentations/src/mocha/utils.js +5 -21
  9. package/packages/datadog-instrumentations/src/playwright.js +5 -2
  10. package/packages/datadog-plugin-confluentinc-kafka-javascript/src/index.js +6 -0
  11. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +0 -1
  12. package/packages/datadog-plugin-mongodb-core/src/index.js +18 -5
  13. package/packages/dd-trace/src/aiguard/client.js +25 -0
  14. package/packages/dd-trace/src/aiguard/noop.js +9 -0
  15. package/packages/dd-trace/src/aiguard/sdk.js +173 -0
  16. package/packages/dd-trace/src/aiguard/tags.js +11 -0
  17. package/packages/dd-trace/src/appsec/iast/path-line.js +21 -4
  18. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +6 -3
  19. package/packages/dd-trace/src/appsec/stack_trace.js +20 -1
  20. package/packages/dd-trace/src/config-helper.js +8 -1
  21. package/packages/dd-trace/src/config.js +23 -0
  22. package/packages/dd-trace/src/config_defaults.js +5 -0
  23. package/packages/dd-trace/src/datastreams/index.js +23 -1
  24. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +11 -1
  25. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +8 -6
  26. package/packages/dd-trace/src/log/index.js +4 -4
  27. package/packages/dd-trace/src/noop/proxy.js +4 -0
  28. package/packages/dd-trace/src/opentracing/span.js +1 -1
  29. package/packages/dd-trace/src/payload-tagging/config/index.js +16 -0
  30. package/packages/dd-trace/src/payload-tagging/index.js +26 -15
  31. package/packages/dd-trace/src/payload-tagging/tagging.js +17 -8
  32. package/packages/dd-trace/src/pkg.js +3 -1
  33. package/packages/dd-trace/src/plugins/composite.js +3 -0
  34. package/packages/dd-trace/src/plugins/plugin.js +67 -0
  35. package/packages/dd-trace/src/plugins/util/git.js +1 -1
  36. package/packages/dd-trace/src/plugins/util/test.js +19 -30
  37. package/packages/dd-trace/src/priority_sampler.js +70 -46
  38. package/packages/dd-trace/src/proxy.js +15 -0
  39. package/packages/dd-trace/src/rate_limiter.js +26 -1
  40. package/packages/dd-trace/src/sampling_rule.js +124 -2
  41. package/packages/dd-trace/src/span_sampler.js +19 -0
  42. package/packages/dd-trace/src/standalone/product.js +9 -0
  43. package/packages/dd-trace/src/standalone/tracesource.js +16 -1
  44. package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +13 -0
  45. package/packages/dd-trace/src/startup-log.js +19 -1
  46. package/packages/dd-trace/src/supported-configurations.json +6 -0
  47. package/packages/dd-trace/src/util.js +1 -1
  48. 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
- const filepath = callsite.file
34
- if (!isExcluded(callsite, externallyExcludedPaths) && !filepath.includes(pathLine.ddBasePath)) {
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
- shimmer.wrap(Module.prototype, '_compile', compileMethod => getCompileMethodFn(compileMethod))
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 => !callSite.getFileName()?.startsWith(ddBasePath))
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 {Partial<process.env>} The environment variables
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 dedicaed plugins.
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
- * @param {Record<string, string>} tags
49
- * @returns {{inputTokens: number, outputTokens: number, totalTokens: number}}
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 {any} defaultValue
69
- * @returns {Record<string, any> | string | Array<any>}
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 {import('../../../opentracing/span')} span
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
- (fleetStableConfigValue ??
114
+ fleetStableConfigValue ??
115
115
  getEnvironmentVariable('DD_TRACE_DEBUG') ??
116
- getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug') ||
117
- (localStableConfigValue ??
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 = () => {}
@@ -146,7 +146,7 @@ class DatadogSpan {
146
146
  }
147
147
 
148
148
  /**
149
- * @returns {DatadogSpanContext}
149
+ * @returns {import('../priority_sampler').DatadogSpanContext}
150
150
  */
151
151
  context () {
152
152
  return this._spanContext
@@ -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)) {