dd-trace 5.70.0 → 5.71.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/index.d.ts +17 -0
- package/initialize.mjs +7 -1
- package/package.json +1 -1
- package/packages/datadog-instrumentations/src/anthropic.js +115 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-plugin-anthropic/src/index.js +17 -0
- package/packages/datadog-plugin-anthropic/src/tracing.js +30 -0
- package/packages/dd-trace/src/appsec/reporter.js +70 -21
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
- package/packages/dd-trace/src/config.js +2 -0
- package/packages/dd-trace/src/config_defaults.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic.js +282 -0
- package/packages/dd-trace/src/llmobs/tagger.js +35 -0
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/remote_config/capabilities.js +1 -0
- package/packages/dd-trace/src/remote_config/index.js +2 -0
- package/packages/dd-trace/src/supported-configurations.json +1 -0
- package/register.js +9 -1
package/index.d.ts
CHANGED
|
@@ -168,6 +168,7 @@ interface Plugins {
|
|
|
168
168
|
"aerospike": tracer.plugins.aerospike;
|
|
169
169
|
"amqp10": tracer.plugins.amqp10;
|
|
170
170
|
"amqplib": tracer.plugins.amqplib;
|
|
171
|
+
"anthropic": tracer.plugins.anthropic;
|
|
171
172
|
"apollo": tracer.plugins.apollo;
|
|
172
173
|
"avsc": tracer.plugins.avsc;
|
|
173
174
|
"aws-sdk": tracer.plugins.aws_sdk;
|
|
@@ -785,6 +786,8 @@ declare namespace tracer {
|
|
|
785
786
|
|
|
786
787
|
/** Whether to enable request body collection on RASP event
|
|
787
788
|
* @default false
|
|
789
|
+
*
|
|
790
|
+
* @deprecated Use UI and Remote Configuration to enable extended data collection
|
|
788
791
|
*/
|
|
789
792
|
bodyCollection?: boolean
|
|
790
793
|
},
|
|
@@ -809,20 +812,28 @@ declare namespace tracer {
|
|
|
809
812
|
},
|
|
810
813
|
/**
|
|
811
814
|
* Configuration for extended headers collection tied to security events
|
|
815
|
+
*
|
|
816
|
+
* @deprecated Use UI and Remote Configuration to enable extended data collection
|
|
812
817
|
*/
|
|
813
818
|
extendedHeadersCollection?: {
|
|
814
819
|
/** Whether to enable extended headers collection
|
|
815
820
|
* @default false
|
|
821
|
+
*
|
|
822
|
+
* @deprecated Use UI and Remote Configuration to enable extended data collection
|
|
816
823
|
*/
|
|
817
824
|
enabled: boolean,
|
|
818
825
|
|
|
819
826
|
/** Whether to redact collected headers
|
|
820
827
|
* @default true
|
|
828
|
+
*
|
|
829
|
+
* @deprecated Use UI and Remote Configuration to enable extended data collection
|
|
821
830
|
*/
|
|
822
831
|
redaction: boolean,
|
|
823
832
|
|
|
824
833
|
/** Specifies the maximum number of headers collected.
|
|
825
834
|
* @default 50
|
|
835
|
+
*
|
|
836
|
+
* @deprecated Use UI and Remote Configuration to enable extended data collection
|
|
826
837
|
*/
|
|
827
838
|
maxHeaders: number,
|
|
828
839
|
}
|
|
@@ -1530,6 +1541,12 @@ declare namespace tracer {
|
|
|
1530
1541
|
*/
|
|
1531
1542
|
interface amqplib extends Instrumentation {}
|
|
1532
1543
|
|
|
1544
|
+
/**
|
|
1545
|
+
* This plugin automatically instruments the
|
|
1546
|
+
* [anthropic](https://www.npmjs.com/package/@anthropic-ai/sdk) module.
|
|
1547
|
+
*/
|
|
1548
|
+
interface anthropic extends Instrumentation {}
|
|
1549
|
+
|
|
1533
1550
|
/**
|
|
1534
1551
|
* Currently this plugin automatically instruments
|
|
1535
1552
|
* [@apollo/gateway](https://github.com/apollographql/federation) for module versions >= v2.3.0.
|
package/initialize.mjs
CHANGED
|
@@ -36,7 +36,13 @@ ${result.source}`
|
|
|
36
36
|
const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number)
|
|
37
37
|
|
|
38
38
|
const brokenLoaders = NODE_MAJOR === 18 && NODE_MINOR === 0
|
|
39
|
-
const iitmExclusions = [
|
|
39
|
+
const iitmExclusions = [
|
|
40
|
+
/langsmith/,
|
|
41
|
+
/openai\/_shims/,
|
|
42
|
+
/openai\/resources\/chat\/completions\/messages/,
|
|
43
|
+
/openai\/agents-core\/dist\/shims/,
|
|
44
|
+
/@anthropic-ai\/sdk\/_shims/
|
|
45
|
+
]
|
|
40
46
|
|
|
41
47
|
export async function load (url, context, nextLoad) {
|
|
42
48
|
const iitmExclusionsMatch = iitmExclusions.some((exclusion) => exclusion.test(url))
|
package/package.json
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { addHook } = require('./helpers/instrument')
|
|
4
|
+
const shimmer = require('../../datadog-shimmer')
|
|
5
|
+
const { channel, tracingChannel } = require('dc-polyfill')
|
|
6
|
+
|
|
7
|
+
const anthropicTracingChannel = tracingChannel('apm:anthropic:request')
|
|
8
|
+
const onStreamedChunkCh = channel('apm:anthropic:request:chunk')
|
|
9
|
+
|
|
10
|
+
function wrapStreamIterator (iterator, ctx) {
|
|
11
|
+
return function () {
|
|
12
|
+
const itr = iterator.apply(this, arguments)
|
|
13
|
+
shimmer.wrap(itr, 'next', next => function () {
|
|
14
|
+
return next.apply(this, arguments)
|
|
15
|
+
.then(res => {
|
|
16
|
+
const { done, value: chunk } = res
|
|
17
|
+
onStreamedChunkCh.publish({ ctx, chunk, done })
|
|
18
|
+
|
|
19
|
+
if (done) {
|
|
20
|
+
finish(ctx)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return res
|
|
24
|
+
})
|
|
25
|
+
.catch(error => {
|
|
26
|
+
finish(ctx, null, error)
|
|
27
|
+
throw error
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return itr
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function wrapCreate (create) {
|
|
36
|
+
return function () {
|
|
37
|
+
if (!anthropicTracingChannel.start.hasSubscribers) {
|
|
38
|
+
return create.apply(this, arguments)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const options = arguments[0]
|
|
42
|
+
const stream = options.stream
|
|
43
|
+
|
|
44
|
+
const ctx = { options, resource: 'create' }
|
|
45
|
+
|
|
46
|
+
return anthropicTracingChannel.start.runStores(ctx, () => {
|
|
47
|
+
let apiPromise
|
|
48
|
+
try {
|
|
49
|
+
apiPromise = create.apply(this, arguments)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
finish(ctx, null, error)
|
|
52
|
+
throw error
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
shimmer.wrap(apiPromise, 'parse', parse => function () {
|
|
56
|
+
return parse.apply(this, arguments)
|
|
57
|
+
.then(response => {
|
|
58
|
+
if (stream) {
|
|
59
|
+
shimmer.wrap(response, Symbol.asyncIterator, iterator => wrapStreamIterator(iterator, ctx))
|
|
60
|
+
} else {
|
|
61
|
+
finish(ctx, response, null)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return response
|
|
65
|
+
}).catch(error => {
|
|
66
|
+
finish(ctx, null, error)
|
|
67
|
+
throw error
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
anthropicTracingChannel.end.publish(ctx)
|
|
72
|
+
|
|
73
|
+
return apiPromise
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function finish (ctx, result, error) {
|
|
79
|
+
if (error) {
|
|
80
|
+
ctx.error = error
|
|
81
|
+
anthropicTracingChannel.error.publish(ctx)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// streamed responses are handled and set separately
|
|
85
|
+
ctx.result ??= result
|
|
86
|
+
|
|
87
|
+
anthropicTracingChannel.asyncEnd.publish(ctx)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const extensions = ['js', 'mjs']
|
|
91
|
+
for (const extension of extensions) {
|
|
92
|
+
addHook({
|
|
93
|
+
name: '@anthropic-ai/sdk',
|
|
94
|
+
file: `resources/messages.${extension}`,
|
|
95
|
+
versions: ['>=0.14.0 <0.33.0']
|
|
96
|
+
}, exports => {
|
|
97
|
+
const Messages = exports.Messages
|
|
98
|
+
|
|
99
|
+
shimmer.wrap(Messages.prototype, 'create', wrapCreate)
|
|
100
|
+
|
|
101
|
+
return exports
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
addHook({
|
|
105
|
+
name: '@anthropic-ai/sdk',
|
|
106
|
+
file: `resources/messages/messages.${extension}`,
|
|
107
|
+
versions: ['>=0.33.0']
|
|
108
|
+
}, exports => {
|
|
109
|
+
const Messages = exports.Messages
|
|
110
|
+
|
|
111
|
+
shimmer.wrap(Messages.prototype, 'create', wrapCreate)
|
|
112
|
+
|
|
113
|
+
return exports
|
|
114
|
+
})
|
|
115
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
+
'@anthropic-ai/sdk': { esmFirst: true, fn: () => require('../anthropic') },
|
|
4
5
|
'@apollo/server': () => require('../apollo-server'),
|
|
5
6
|
'@apollo/gateway': () => require('../apollo'),
|
|
6
7
|
'apollo-server-core': () => require('../apollo-server-core'),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const CompositePlugin = require('../../dd-trace/src/plugins/composite')
|
|
4
|
+
const AnthropicTracingPlugin = require('./tracing')
|
|
5
|
+
const AnthropicLLMObsPlugin = require('../../dd-trace/src/llmobs/plugins/anthropic')
|
|
6
|
+
|
|
7
|
+
class AnthropicPlugin extends CompositePlugin {
|
|
8
|
+
static id = 'anthropic'
|
|
9
|
+
static get plugins () {
|
|
10
|
+
return {
|
|
11
|
+
llmobs: AnthropicLLMObsPlugin,
|
|
12
|
+
tracing: AnthropicTracingPlugin
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = AnthropicPlugin
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
|
|
5
|
+
class AnthropicTracingPlugin extends TracingPlugin {
|
|
6
|
+
static id = 'anthropic'
|
|
7
|
+
static operation = 'request'
|
|
8
|
+
static system = 'anthropic'
|
|
9
|
+
static prefix = 'tracing:apm:anthropic:request'
|
|
10
|
+
|
|
11
|
+
bindStart (ctx) {
|
|
12
|
+
const { resource, options } = ctx
|
|
13
|
+
|
|
14
|
+
this.startSpan('anthropic.request', {
|
|
15
|
+
meta: {
|
|
16
|
+
'resource.name': `Messages.${resource}`,
|
|
17
|
+
'anthropic.request.model': options.model
|
|
18
|
+
}
|
|
19
|
+
}, ctx)
|
|
20
|
+
|
|
21
|
+
return ctx.currentStore
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
asyncEnd (ctx) {
|
|
25
|
+
const span = ctx.currentStore?.span
|
|
26
|
+
span?.finish()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = AnthropicTracingPlugin
|
|
@@ -38,14 +38,20 @@ const config = {
|
|
|
38
38
|
|
|
39
39
|
const metricsQueue = new Map()
|
|
40
40
|
|
|
41
|
+
const extendedDataCollectionRequest = new WeakMap()
|
|
42
|
+
|
|
41
43
|
// following header lists are ordered in the same way the spec orders them, it doesn't matter but it's easier to compare
|
|
42
44
|
const contentHeaderList = [
|
|
43
45
|
'content-length',
|
|
44
|
-
'content-type',
|
|
45
46
|
'content-encoding',
|
|
46
47
|
'content-language'
|
|
47
48
|
]
|
|
48
49
|
|
|
50
|
+
const responseHeaderList = [
|
|
51
|
+
...contentHeaderList,
|
|
52
|
+
'content-type'
|
|
53
|
+
]
|
|
54
|
+
|
|
49
55
|
const identificationHeaders = [
|
|
50
56
|
'x-amzn-trace-id',
|
|
51
57
|
'cloudfront-viewer-ja3-fingerprint',
|
|
@@ -75,15 +81,27 @@ const requestHeadersList = [
|
|
|
75
81
|
...identificationHeaders
|
|
76
82
|
]
|
|
77
83
|
|
|
84
|
+
const redactedHeadersList = [
|
|
85
|
+
'authorization',
|
|
86
|
+
'proxy-authorization',
|
|
87
|
+
'www-authenticate',
|
|
88
|
+
'proxy-authenticate',
|
|
89
|
+
'authentication-info',
|
|
90
|
+
'proxy-authentication-info',
|
|
91
|
+
'cookie',
|
|
92
|
+
'set-cookie'
|
|
93
|
+
]
|
|
94
|
+
|
|
78
95
|
// these request headers are always collected - it breaks the expected spec orders
|
|
79
96
|
const REQUEST_HEADERS_MAP = mapHeaderAndTags(requestHeadersList, REQUEST_HEADER_TAG_PREFIX)
|
|
80
97
|
|
|
81
98
|
const EVENT_HEADERS_MAP = mapHeaderAndTags(eventHeadersList, REQUEST_HEADER_TAG_PREFIX)
|
|
82
99
|
|
|
83
|
-
const RESPONSE_HEADERS_MAP = mapHeaderAndTags(
|
|
100
|
+
const RESPONSE_HEADERS_MAP = mapHeaderAndTags(responseHeaderList, RESPONSE_HEADER_TAG_PREFIX)
|
|
84
101
|
|
|
85
102
|
const NON_EXTENDED_REQUEST_HEADERS = new Set([...requestHeadersList, ...eventHeadersList])
|
|
86
|
-
const NON_EXTENDED_RESPONSE_HEADERS = new Set(
|
|
103
|
+
const NON_EXTENDED_RESPONSE_HEADERS = new Set(responseHeaderList)
|
|
104
|
+
const REDACTED_HEADERS = new Set(redactedHeadersList)
|
|
87
105
|
|
|
88
106
|
function init (_config) {
|
|
89
107
|
config.headersExtendedCollectionEnabled = _config.extendedHeadersCollection.enabled
|
|
@@ -132,7 +150,9 @@ function filterExtendedHeaders (headers, excludedHeaderNames, tagPrefix, limit =
|
|
|
132
150
|
for (const [headerName, headerValue] of Object.entries(headers)) {
|
|
133
151
|
if (counter >= limit) break
|
|
134
152
|
if (!excludedHeaderNames.has(headerName)) {
|
|
135
|
-
result[getHeaderTag(tagPrefix, headerName)] =
|
|
153
|
+
result[getHeaderTag(tagPrefix, headerName)] = REDACTED_HEADERS.has(headerName)
|
|
154
|
+
? '<redacted>'
|
|
155
|
+
: String(headerValue)
|
|
136
156
|
counter++
|
|
137
157
|
}
|
|
138
158
|
}
|
|
@@ -140,7 +160,7 @@ function filterExtendedHeaders (headers, excludedHeaderNames, tagPrefix, limit =
|
|
|
140
160
|
return result
|
|
141
161
|
}
|
|
142
162
|
|
|
143
|
-
function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedResponseHeaders = {}) {
|
|
163
|
+
function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedResponseHeaders = {}, extendedDataCollection) {
|
|
144
164
|
// Mandatory
|
|
145
165
|
const mandatoryCollectedHeaders = filterHeaders(req.headers, REQUEST_HEADERS_MAP)
|
|
146
166
|
|
|
@@ -154,7 +174,8 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
|
|
|
154
174
|
const requestEventCollectedHeaders = filterHeaders(req.headers, EVENT_HEADERS_MAP)
|
|
155
175
|
const responseEventCollectedHeaders = filterHeaders(responseHeaders, RESPONSE_HEADERS_MAP)
|
|
156
176
|
|
|
157
|
-
|
|
177
|
+
// TODO headersExtendedCollectionEnabled and headersRedaction properties are deprecated to delete in a major
|
|
178
|
+
if ((!config.headersExtendedCollectionEnabled || config.headersRedaction) && !extendedDataCollection) {
|
|
158
179
|
// Standard collection
|
|
159
180
|
return Object.assign(
|
|
160
181
|
mandatoryCollectedHeaders,
|
|
@@ -163,12 +184,15 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
|
|
|
163
184
|
)
|
|
164
185
|
}
|
|
165
186
|
|
|
187
|
+
// TODO config.maxHeadersCollected is deprecated to delete in a major
|
|
188
|
+
const maxHeadersCollected = extendedDataCollection?.max_collected_headers ?? config.maxHeadersCollected
|
|
189
|
+
|
|
166
190
|
// Extended collection
|
|
167
|
-
const
|
|
168
|
-
config.maxHeadersCollected -
|
|
169
|
-
Object.keys(mandatoryCollectedHeaders).length -
|
|
191
|
+
const collectedHeadersCount = Object.keys(mandatoryCollectedHeaders).length +
|
|
170
192
|
Object.keys(requestEventCollectedHeaders).length
|
|
171
193
|
|
|
194
|
+
const requestExtendedHeadersAvailableCount = maxHeadersCollected - collectedHeadersCount
|
|
195
|
+
|
|
172
196
|
const requestEventExtendedCollectedHeaders =
|
|
173
197
|
filterExtendedHeaders(
|
|
174
198
|
req.headers,
|
|
@@ -178,7 +202,7 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
|
|
|
178
202
|
)
|
|
179
203
|
|
|
180
204
|
const responseExtendedHeadersAvailableCount =
|
|
181
|
-
|
|
205
|
+
maxHeadersCollected -
|
|
182
206
|
Object.keys(responseEventCollectedHeaders).length
|
|
183
207
|
|
|
184
208
|
const responseEventExtendedCollectedHeaders =
|
|
@@ -199,15 +223,15 @@ function getCollectedHeaders (req, res, shouldCollectEventHeaders, storedRespons
|
|
|
199
223
|
|
|
200
224
|
// Check discarded headers
|
|
201
225
|
const requestHeadersCount = Object.keys(req.headers).length
|
|
202
|
-
if (requestHeadersCount >
|
|
226
|
+
if (requestHeadersCount > maxHeadersCollected) {
|
|
203
227
|
headersTags['_dd.appsec.request.header_collection.discarded'] =
|
|
204
|
-
requestHeadersCount -
|
|
228
|
+
requestHeadersCount - maxHeadersCollected
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
const responseHeadersCount = Object.keys(responseHeaders).length
|
|
208
|
-
if (responseHeadersCount >
|
|
232
|
+
if (responseHeadersCount > maxHeadersCollected) {
|
|
209
233
|
headersTags['_dd.appsec.response.header_collection.discarded'] =
|
|
210
|
-
responseHeadersCount -
|
|
234
|
+
responseHeadersCount - maxHeadersCollected
|
|
211
235
|
}
|
|
212
236
|
|
|
213
237
|
return headersTags
|
|
@@ -307,7 +331,7 @@ function reportTruncationMetrics (rootSpan, metrics) {
|
|
|
307
331
|
}
|
|
308
332
|
}
|
|
309
333
|
|
|
310
|
-
function reportAttack (attackData) {
|
|
334
|
+
function reportAttack ({ events: attackData, actions }) {
|
|
311
335
|
const store = storage('legacy').getStore()
|
|
312
336
|
const req = store?.req
|
|
313
337
|
const rootSpan = web.root(req)
|
|
@@ -338,8 +362,14 @@ function reportAttack (attackData) {
|
|
|
338
362
|
|
|
339
363
|
rootSpan.addTags(newTags)
|
|
340
364
|
|
|
365
|
+
// TODO this should be deleted in a major
|
|
341
366
|
if (config.raspBodyCollection && isRaspAttack(attackData)) {
|
|
342
|
-
reportRequestBody(rootSpan, req.body)
|
|
367
|
+
reportRequestBody(rootSpan, req.body, true)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const extendedDataCollection = actions?.extended_data_collection
|
|
371
|
+
if (extendedDataCollection) {
|
|
372
|
+
extendedDataCollectionRequest.set(req, extendedDataCollection)
|
|
343
373
|
}
|
|
344
374
|
}
|
|
345
375
|
|
|
@@ -398,18 +428,29 @@ function truncateRequestBody (target, depth = 0) {
|
|
|
398
428
|
}
|
|
399
429
|
}
|
|
400
430
|
|
|
401
|
-
function reportRequestBody (rootSpan, requestBody) {
|
|
402
|
-
if (!requestBody) return
|
|
431
|
+
function reportRequestBody (rootSpan, requestBody, comesFromRaspAction = false) {
|
|
432
|
+
if (!requestBody || Object.keys(requestBody).length === 0) return
|
|
403
433
|
|
|
404
434
|
if (!rootSpan.meta_struct) {
|
|
405
435
|
rootSpan.meta_struct = {}
|
|
406
436
|
}
|
|
407
437
|
|
|
408
|
-
if (
|
|
438
|
+
if (rootSpan.meta_struct['http.request.body']) {
|
|
439
|
+
// If the rasp.exceed metric exists, set also the same for the new tag
|
|
440
|
+
const currentTags = rootSpan.context()._tags
|
|
441
|
+
const sizeExceedTagValue = currentTags['_dd.appsec.rasp.request_body_size.exceeded']
|
|
442
|
+
|
|
443
|
+
if (sizeExceedTagValue) {
|
|
444
|
+
rootSpan.setTag('_dd.appsec.request_body_size.exceeded', sizeExceedTagValue)
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
409
447
|
const { truncated, value } = truncateRequestBody(requestBody)
|
|
410
448
|
rootSpan.meta_struct['http.request.body'] = value
|
|
411
449
|
if (truncated) {
|
|
412
|
-
|
|
450
|
+
const sizeExceedTagKey = comesFromRaspAction
|
|
451
|
+
? '_dd.appsec.rasp.request_body_size.exceeded' // TODO old metric to delete in a major
|
|
452
|
+
: '_dd.appsec.request_body_size.exceeded'
|
|
453
|
+
rootSpan.setTag(sizeExceedTagKey, 'true')
|
|
413
454
|
}
|
|
414
455
|
}
|
|
415
456
|
}
|
|
@@ -496,7 +537,15 @@ function finishRequest (req, res, storedResponseHeaders) {
|
|
|
496
537
|
|
|
497
538
|
const tags = rootSpan.context()._tags
|
|
498
539
|
|
|
499
|
-
const
|
|
540
|
+
const extendedDataCollection = extendedDataCollectionRequest.get(req)
|
|
541
|
+
const newTags = getCollectedHeaders(
|
|
542
|
+
req, res, shouldCollectEventHeaders(tags), storedResponseHeaders, extendedDataCollection
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
if (extendedDataCollection) {
|
|
546
|
+
// TODO add support for fastify, req.body is not available in fastify
|
|
547
|
+
reportRequestBody(rootSpan, req.body)
|
|
548
|
+
}
|
|
500
549
|
|
|
501
550
|
if (tags['appsec.event'] === 'true' && typeof req.route?.path === 'string') {
|
|
502
551
|
newTags['http.endpoint'] = req.route.path
|
|
@@ -668,6 +668,7 @@ class Config {
|
|
|
668
668
|
this._envUnprocessed['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON
|
|
669
669
|
this._setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED)
|
|
670
670
|
this._setString(env, 'appsec.eventTracking.mode', DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE)
|
|
671
|
+
// TODO appsec.extendedHeadersCollection are deprecated, to delete in a major
|
|
671
672
|
this._setBoolean(env, 'appsec.extendedHeadersCollection.enabled', DD_APPSEC_COLLECT_ALL_HEADERS)
|
|
672
673
|
this._setBoolean(
|
|
673
674
|
env,
|
|
@@ -679,6 +680,7 @@ class Config {
|
|
|
679
680
|
this._setString(env, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP)
|
|
680
681
|
this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
|
|
681
682
|
this._setBoolean(env, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED)
|
|
683
|
+
// TODO Deprecated, to delete in a major
|
|
682
684
|
this._setBoolean(env, 'appsec.rasp.bodyCollection', DD_APPSEC_RASP_COLLECT_REQUEST_BODY)
|
|
683
685
|
env['appsec.rateLimit'] = maybeInt(DD_APPSEC_TRACE_RATE_LIMIT)
|
|
684
686
|
this._envUnprocessed['appsec.rateLimit'] = DD_APPSEC_TRACE_RATE_LIMIT
|
|
@@ -36,12 +36,14 @@ module.exports = {
|
|
|
36
36
|
'appsec.blockedTemplateJson': undefined,
|
|
37
37
|
'appsec.enabled': undefined,
|
|
38
38
|
'appsec.eventTracking.mode': 'identification',
|
|
39
|
+
// TODO appsec.extendedHeadersCollection is deprecated, to delete in a major
|
|
39
40
|
'appsec.extendedHeadersCollection.enabled': false,
|
|
40
41
|
'appsec.extendedHeadersCollection.redaction': true,
|
|
41
42
|
'appsec.extendedHeadersCollection.maxHeaders': 50,
|
|
42
43
|
'appsec.obfuscatorKeyRegex': defaultWafObfuscatorKeyRegex,
|
|
43
44
|
'appsec.obfuscatorValueRegex': defaultWafObfuscatorValueRegex,
|
|
44
45
|
'appsec.rasp.enabled': true,
|
|
46
|
+
// TODO Deprecated, to delete in a major
|
|
45
47
|
'appsec.rasp.bodyCollection': false,
|
|
46
48
|
'appsec.rateLimit': 100,
|
|
47
49
|
'appsec.rules': undefined,
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LLMObsPlugin = require('./base')
|
|
4
|
+
|
|
5
|
+
const ALLOWED_METADATA_KEYS = new Set([
|
|
6
|
+
'max_tokens',
|
|
7
|
+
'stop_sequences',
|
|
8
|
+
'temperature',
|
|
9
|
+
'top_k',
|
|
10
|
+
'top_p',
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
14
|
+
static integration = 'anthropic' // used for llmobs telemetry
|
|
15
|
+
static id = 'anthropic'
|
|
16
|
+
static prefix = 'tracing:apm:anthropic:request'
|
|
17
|
+
|
|
18
|
+
constructor () {
|
|
19
|
+
super(...arguments)
|
|
20
|
+
|
|
21
|
+
this.addSub('apm:anthropic:request:chunk', ({ ctx, chunk, done }) => {
|
|
22
|
+
ctx.chunks ??= []
|
|
23
|
+
const chunks = ctx.chunks
|
|
24
|
+
if (chunk) chunks.push(chunk)
|
|
25
|
+
|
|
26
|
+
if (!done) return
|
|
27
|
+
|
|
28
|
+
const response = { content: [] }
|
|
29
|
+
|
|
30
|
+
for (const chunk of chunks) {
|
|
31
|
+
switch (chunk.type) {
|
|
32
|
+
case 'message_start': {
|
|
33
|
+
const { message } = chunk
|
|
34
|
+
if (!message) continue
|
|
35
|
+
|
|
36
|
+
const { role, usage } = message
|
|
37
|
+
if (role) response.role = role
|
|
38
|
+
if (usage) response.usage = usage
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
case 'content_block_start': {
|
|
42
|
+
const contentBlock = chunk.content_block
|
|
43
|
+
if (!contentBlock) continue
|
|
44
|
+
|
|
45
|
+
const { type } = contentBlock
|
|
46
|
+
if (type === 'text') {
|
|
47
|
+
response.content.push({ type, text: contentBlock.text })
|
|
48
|
+
} else if (type === 'tool_use') {
|
|
49
|
+
response.content.push({ type, name: contentBlock.name, input: '', id: contentBlock.id })
|
|
50
|
+
}
|
|
51
|
+
break
|
|
52
|
+
}
|
|
53
|
+
case 'content_block_delta': {
|
|
54
|
+
const { delta } = chunk
|
|
55
|
+
if (!delta) continue
|
|
56
|
+
|
|
57
|
+
const { text } = delta
|
|
58
|
+
if (text) response.content[response.content.length - 1].text += text
|
|
59
|
+
|
|
60
|
+
const partialJson = delta.partial_json
|
|
61
|
+
if (partialJson && delta.type === 'input_json_delta') {
|
|
62
|
+
response.content[response.content.length - 1].input += partialJson
|
|
63
|
+
}
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
case 'content_block_stop': {
|
|
67
|
+
const type = response.content[response.content.length - 1].type
|
|
68
|
+
if (type === 'tool_use') {
|
|
69
|
+
const input = response.content[response.content.length - 1].input ?? '{}'
|
|
70
|
+
response.content[response.content.length - 1].input = JSON.parse(input)
|
|
71
|
+
}
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
case 'message_delta': {
|
|
75
|
+
const { delta } = chunk
|
|
76
|
+
|
|
77
|
+
const finishReason = delta?.stop_reason
|
|
78
|
+
if (finishReason) response.finish_reason = finishReason
|
|
79
|
+
|
|
80
|
+
const { usage } = chunk
|
|
81
|
+
if (usage) {
|
|
82
|
+
const responseUsage = response.usage ?? (response.usage = { input_tokens: 0, output_tokens: 0 })
|
|
83
|
+
responseUsage.output_tokens = usage.output_tokens
|
|
84
|
+
|
|
85
|
+
const cacheCreationTokens = usage.cache_creation_input_tokens
|
|
86
|
+
const cacheReadTokens = usage.cache_read_input_tokens
|
|
87
|
+
if (cacheCreationTokens) responseUsage.cache_creation_input_tokens = cacheCreationTokens
|
|
88
|
+
if (cacheReadTokens) responseUsage.cache_read_input_tokens = cacheReadTokens
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
case 'error': {
|
|
94
|
+
const { error } = chunk
|
|
95
|
+
if (!error) continue
|
|
96
|
+
|
|
97
|
+
response.error = {}
|
|
98
|
+
if (error.type) response.error.type = error.type
|
|
99
|
+
if (error.message) response.error.message = error.message
|
|
100
|
+
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ctx.result = response
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
111
|
+
const { options } = ctx
|
|
112
|
+
const { model } = options
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
kind: 'llm',
|
|
116
|
+
modelName: model,
|
|
117
|
+
modelProvider: 'anthropic'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setLLMObsTags (ctx) {
|
|
122
|
+
const span = ctx.currentStore?.span
|
|
123
|
+
if (!span) return
|
|
124
|
+
|
|
125
|
+
const { options, result } = ctx
|
|
126
|
+
|
|
127
|
+
this.#tagAnthropicInputMessages(span, options)
|
|
128
|
+
this.#tagAnthropicOutputMessages(span, result)
|
|
129
|
+
this.#tagAnthropicMetadata(span, options)
|
|
130
|
+
this.#tagAnthropicUsage(span, result)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#tagAnthropicInputMessages (span, options) {
|
|
134
|
+
const { system, messages } = options
|
|
135
|
+
const inputMessages = []
|
|
136
|
+
|
|
137
|
+
if (system) {
|
|
138
|
+
messages.unshift({ content: system, role: 'system' })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const message of messages) {
|
|
142
|
+
const { content, role } = message
|
|
143
|
+
|
|
144
|
+
if (typeof content === 'string') {
|
|
145
|
+
inputMessages.push({ content, role })
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const block of content) {
|
|
150
|
+
if (block.type === 'text') {
|
|
151
|
+
inputMessages.push({ content: block.text, role })
|
|
152
|
+
} else if (block.type === 'image') {
|
|
153
|
+
inputMessages.push({ content: '([IMAGE DETECTED])', role })
|
|
154
|
+
} else if (block.type === 'tool_use') {
|
|
155
|
+
const { text, name, id, type } = block
|
|
156
|
+
let input = block.input
|
|
157
|
+
if (typeof input === 'string') {
|
|
158
|
+
input = JSON.parse(input)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const toolCall = {
|
|
162
|
+
name,
|
|
163
|
+
arguments: input,
|
|
164
|
+
toolId: id,
|
|
165
|
+
type
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
inputMessages.push({ content: text ?? '', role, toolCalls: [toolCall] })
|
|
169
|
+
} else if (block.type === 'tool_result') {
|
|
170
|
+
const { content } = block
|
|
171
|
+
const formattedContent = this.#formatAnthropicToolResultContent(content)
|
|
172
|
+
const toolResult = {
|
|
173
|
+
result: formattedContent,
|
|
174
|
+
toolId: block.tool_use_id,
|
|
175
|
+
type: 'tool_result'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
inputMessages.push({ content: '', role, toolResults: [toolResult] })
|
|
179
|
+
} else {
|
|
180
|
+
inputMessages.push({ content: JSON.stringify(block), role })
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this._tagger.tagLLMIO(span, inputMessages)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#tagAnthropicOutputMessages (span, result) {
|
|
189
|
+
if (!result) return
|
|
190
|
+
|
|
191
|
+
const { content, role } = result
|
|
192
|
+
|
|
193
|
+
if (typeof content === 'string') {
|
|
194
|
+
this._tagger.tagLLMIO(span, null, [{ content, role }])
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const outputMessages = []
|
|
199
|
+
for (const block of content) {
|
|
200
|
+
const { text } = block
|
|
201
|
+
if (typeof text === 'string') {
|
|
202
|
+
outputMessages.push({ content: text, role })
|
|
203
|
+
} else if (block.type === 'tool_use') {
|
|
204
|
+
let input = block.input
|
|
205
|
+
if (typeof input === 'string') {
|
|
206
|
+
input = JSON.parse(input)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const toolCall = {
|
|
210
|
+
name: block.name,
|
|
211
|
+
arguments: input,
|
|
212
|
+
toolId: block.id,
|
|
213
|
+
type: block.type
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
outputMessages.push({ content: text ?? '', role, toolCalls: [toolCall] })
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this._tagger.tagLLMIO(span, null, outputMessages)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#tagAnthropicMetadata (span, options) {
|
|
224
|
+
const metadata = {}
|
|
225
|
+
for (const [key, value] of Object.entries(options)) {
|
|
226
|
+
if (ALLOWED_METADATA_KEYS.has(key)) {
|
|
227
|
+
metadata[key] = value
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this._tagger.tagMetadata(span, metadata)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#tagAnthropicUsage (span, result) {
|
|
235
|
+
if (!result) return
|
|
236
|
+
|
|
237
|
+
const { usage } = result
|
|
238
|
+
if (!usage) return
|
|
239
|
+
|
|
240
|
+
const inputTokens = usage.input_tokens
|
|
241
|
+
const outputTokens = usage.output_tokens
|
|
242
|
+
const cacheWriteTokens = usage.cache_creation_input_tokens
|
|
243
|
+
const cacheReadTokens = usage.cache_read_input_tokens
|
|
244
|
+
|
|
245
|
+
const metrics = {}
|
|
246
|
+
|
|
247
|
+
metrics.inputTokens =
|
|
248
|
+
(inputTokens ?? 0) +
|
|
249
|
+
(cacheWriteTokens ?? 0) +
|
|
250
|
+
(cacheReadTokens ?? 0)
|
|
251
|
+
|
|
252
|
+
if (outputTokens) metrics.outputTokens = outputTokens
|
|
253
|
+
const totalTokens = metrics.inputTokens + (outputTokens ?? 0)
|
|
254
|
+
if (totalTokens) metrics.totalTokens = totalTokens
|
|
255
|
+
|
|
256
|
+
if (cacheWriteTokens != null) metrics.cacheWriteTokens = cacheWriteTokens
|
|
257
|
+
if (cacheReadTokens != null) metrics.cacheReadTokens = cacheReadTokens
|
|
258
|
+
|
|
259
|
+
this._tagger.tagMetrics(span, metrics)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// maybe can make into a util file
|
|
263
|
+
#formatAnthropicToolResultContent (content) {
|
|
264
|
+
if (typeof content === 'string') {
|
|
265
|
+
return content
|
|
266
|
+
} else if (Array.isArray(content)) {
|
|
267
|
+
const formattedContent = []
|
|
268
|
+
for (const toolResultBlock of content) {
|
|
269
|
+
if (toolResultBlock.text) {
|
|
270
|
+
formattedContent.push(toolResultBlock.text)
|
|
271
|
+
} else if (toolResultBlock.type === 'image') {
|
|
272
|
+
formattedContent.push('([IMAGE DETECTED])')
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return formattedContent.join(',')
|
|
277
|
+
}
|
|
278
|
+
return JSON.stringify(content)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = AnthropicLLMObsPlugin
|
|
@@ -268,6 +268,32 @@ class LLMObsTagger {
|
|
|
268
268
|
return filteredToolCalls
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
#filterToolResults (toolResults) {
|
|
272
|
+
if (!Array.isArray(toolResults)) {
|
|
273
|
+
toolResults = [toolResults]
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const filteredToolResults = []
|
|
277
|
+
for (const toolResult of toolResults) {
|
|
278
|
+
if (typeof toolResult !== 'object') {
|
|
279
|
+
this.#handleFailure('Tool result must be an object.', 'invalid_io_messages')
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const { result, toolId, type } = toolResult
|
|
284
|
+
const toolResultObj = {}
|
|
285
|
+
|
|
286
|
+
const condition1 = this.#tagConditionalString(result, 'Tool result', toolResultObj, 'result')
|
|
287
|
+
const condition2 = this.#tagConditionalString(toolId, 'Tool ID', toolResultObj, 'tool_id')
|
|
288
|
+
const condition3 = this.#tagConditionalString(type, 'Tool type', toolResultObj, 'type')
|
|
289
|
+
|
|
290
|
+
if (condition1 && condition2 && condition3) {
|
|
291
|
+
filteredToolResults.push(toolResultObj)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return filteredToolResults
|
|
295
|
+
}
|
|
296
|
+
|
|
271
297
|
#tagMessages (span, data, key) {
|
|
272
298
|
if (!data) {
|
|
273
299
|
return
|
|
@@ -290,6 +316,7 @@ class LLMObsTagger {
|
|
|
290
316
|
|
|
291
317
|
const { content = '', role } = message
|
|
292
318
|
const toolCalls = message.toolCalls
|
|
319
|
+
const toolResults = message.toolResults
|
|
293
320
|
const toolId = message.toolId
|
|
294
321
|
const messageObj = { content }
|
|
295
322
|
|
|
@@ -308,6 +335,14 @@ class LLMObsTagger {
|
|
|
308
335
|
}
|
|
309
336
|
}
|
|
310
337
|
|
|
338
|
+
if (toolResults) {
|
|
339
|
+
const filteredToolResults = this.#filterToolResults(toolResults)
|
|
340
|
+
|
|
341
|
+
if (filteredToolResults.length) {
|
|
342
|
+
messageObj.tool_results = filteredToolResults
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
311
346
|
if (toolId) {
|
|
312
347
|
if (role === 'tool') {
|
|
313
348
|
condition = this.#tagConditionalString(toolId, 'Tool ID', messageObj, 'tool_id')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
+
get '@anthropic-ai/sdk' () { return require('../../../datadog-plugin-anthropic/src') },
|
|
4
5
|
get '@apollo/gateway' () { return require('../../../datadog-plugin-apollo/src') },
|
|
5
6
|
get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
|
|
6
7
|
get '@azure/functions' () { return require('../../../datadog-plugin-azure-functions/src') },
|
|
@@ -31,6 +31,7 @@ module.exports = {
|
|
|
31
31
|
ASM_RASP_CMDI: 1n << 37n,
|
|
32
32
|
ASM_DD_MULTICONFIG: 1n << 42n,
|
|
33
33
|
ASM_TRACE_TAGGING_RULES: 1n << 43n,
|
|
34
|
+
ASM_EXTENDED_DATA_COLLECTION: 1n << 44n,
|
|
34
35
|
/*
|
|
35
36
|
DO NOT ADD ARBITRARY CAPABILITIES IN YOUR CODE
|
|
36
37
|
UNLESS THEY ARE ALREADY DEFINED IN THE BACKEND SOURCE OF TRUTH
|
|
@@ -96,6 +96,7 @@ function enableWafUpdate (appsecConfig) {
|
|
|
96
96
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_HEADER_FINGERPRINT, true)
|
|
97
97
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_DD_MULTICONFIG, true)
|
|
98
98
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRACE_TAGGING_RULES, true)
|
|
99
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_EXTENDED_DATA_COLLECTION, true)
|
|
99
100
|
|
|
100
101
|
if (appsecConfig.rasp?.enabled) {
|
|
101
102
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SQLI, true)
|
|
@@ -134,6 +135,7 @@ function disableWafUpdate () {
|
|
|
134
135
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_HEADER_FINGERPRINT, false)
|
|
135
136
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_DD_MULTICONFIG, false)
|
|
136
137
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRACE_TAGGING_RULES, false)
|
|
138
|
+
rc.updateCapabilities(RemoteConfigCapabilities.ASM_EXTENDED_DATA_COLLECTION, false)
|
|
137
139
|
|
|
138
140
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SQLI, false)
|
|
139
141
|
rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SSRF, false)
|
|
@@ -176,6 +176,7 @@
|
|
|
176
176
|
"DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED": ["A"],
|
|
177
177
|
"DD_TRACE_AEROSPIKE_ENABLED": ["A"],
|
|
178
178
|
"DD_TRACE_AI_ENABLED": ["A"],
|
|
179
|
+
"DD_TRACE_ANTHROPIC_ENABLED": ["A"],
|
|
179
180
|
"DD_TRACE_AGENT_PORT": ["A"],
|
|
180
181
|
"DD_TRACE_AGENT_PROTOCOL_VERSION": ["A"],
|
|
181
182
|
"DD_TRACE_AGENT_URL": ["A"],
|
package/register.js
CHANGED
|
@@ -6,5 +6,13 @@ const { register } = require('node:module')
|
|
|
6
6
|
const { pathToFileURL } = require('node:url')
|
|
7
7
|
|
|
8
8
|
register('./loader-hook.mjs', pathToFileURL(__filename), {
|
|
9
|
-
data: {
|
|
9
|
+
data: {
|
|
10
|
+
exclude: [
|
|
11
|
+
/langsmith/,
|
|
12
|
+
/openai\/_shims/,
|
|
13
|
+
/openai\/resources\/chat\/completions\/messages/,
|
|
14
|
+
/openai\/agents-core\/dist\/shims/,
|
|
15
|
+
/@anthropic-ai\/sdk\/_shims/
|
|
16
|
+
]
|
|
17
|
+
}
|
|
10
18
|
})
|