dd-trace 5.75.0 → 5.77.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 (53) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/ci/init.js +1 -0
  3. package/package.json +15 -15
  4. package/packages/datadog-instrumentations/src/express.js +18 -7
  5. package/packages/datadog-instrumentations/src/jest.js +16 -16
  6. package/packages/datadog-instrumentations/src/openai.js +8 -0
  7. package/packages/datadog-instrumentations/src/playwright.js +47 -0
  8. package/packages/datadog-instrumentations/src/router.js +9 -8
  9. package/packages/datadog-instrumentations/src/vitest.js +94 -8
  10. package/packages/datadog-plugin-cucumber/src/index.js +2 -1
  11. package/packages/datadog-plugin-grpc/src/util.js +5 -1
  12. package/packages/datadog-plugin-http/src/client.js +5 -1
  13. package/packages/datadog-plugin-http2/src/client.js +5 -1
  14. package/packages/datadog-plugin-openai/src/stream-helpers.js +26 -1
  15. package/packages/datadog-plugin-openai/src/tracing.js +46 -1
  16. package/packages/datadog-plugin-vitest/src/index.js +5 -1
  17. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  18. package/packages/dd-trace/src/appsec/channels.js +1 -0
  19. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +7 -0
  20. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  21. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -3
  22. package/packages/dd-trace/src/appsec/rasp/lfi.js +23 -9
  23. package/packages/dd-trace/src/appsec/stack_trace.js +6 -6
  24. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -0
  25. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +30 -7
  26. package/packages/dd-trace/src/config.js +10 -1
  27. package/packages/dd-trace/src/debugger/devtools_client/index.js +8 -1
  28. package/packages/dd-trace/src/debugger/devtools_client/log.js +15 -4
  29. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +5 -1
  30. package/packages/dd-trace/src/exporters/common/docker.js +1 -1
  31. package/packages/dd-trace/src/exporters/span-stats/writer.js +4 -5
  32. package/packages/dd-trace/src/format.js +4 -5
  33. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +9 -6
  34. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +3 -6
  35. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +3 -2
  36. package/packages/dd-trace/src/llmobs/plugins/openai.js +216 -12
  37. package/packages/dd-trace/src/llmobs/span_processor.js +4 -3
  38. package/packages/dd-trace/src/llmobs/tagger.js +9 -3
  39. package/packages/dd-trace/src/llmobs/util.js +1 -1
  40. package/packages/dd-trace/src/openfeature/writers/exposures.js +2 -2
  41. package/packages/dd-trace/src/opentelemetry/span.js +3 -2
  42. package/packages/dd-trace/src/plugins/ci_plugin.js +3 -1
  43. package/packages/dd-trace/src/plugins/util/serverless.js +1 -2
  44. package/packages/dd-trace/src/plugins/util/test.js +16 -1
  45. package/packages/dd-trace/src/plugins/util/web.js +5 -1
  46. package/packages/dd-trace/src/profiling/config.js +32 -10
  47. package/packages/dd-trace/src/proxy.js +2 -2
  48. package/packages/dd-trace/src/remote_config/capabilities.js +2 -0
  49. package/packages/dd-trace/src/remote_config/index.js +4 -0
  50. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
  51. package/packages/dd-trace/src/span_processor.js +4 -1
  52. package/packages/dd-trace/src/startup-log.js +24 -38
  53. package/packages/dd-trace/src/supported-configurations.json +1 -0
@@ -11,7 +11,8 @@ const { MEASURED } = require('../../../ext/tags')
11
11
  const {
12
12
  convertBuffersToObjects,
13
13
  constructCompletionResponseFromStreamedChunks,
14
- constructChatCompletionResponseFromStreamedChunks
14
+ constructChatCompletionResponseFromStreamedChunks,
15
+ constructResponseResponseFromStreamedChunks
15
16
  } = require('./stream-helpers')
16
17
 
17
18
  const { DD_MAJOR } = require('../../../version')
@@ -59,6 +60,8 @@ class OpenAiTracingPlugin extends TracingPlugin {
59
60
  response = constructCompletionResponseFromStreamedChunks(chunks, n)
60
61
  } else if (methodName === 'createChatCompletion') {
61
62
  response = constructChatCompletionResponseFromStreamedChunks(chunks, n)
63
+ } else if (methodName === 'createResponse') {
64
+ response = constructResponseResponseFromStreamedChunks(chunks)
62
65
  }
63
66
 
64
67
  ctx.result = { data: response }
@@ -134,6 +137,10 @@ class OpenAiTracingPlugin extends TracingPlugin {
134
137
  case 'createEdit':
135
138
  createEditRequestExtraction(tags, payload, openaiStore)
136
139
  break
140
+
141
+ case 'createResponse':
142
+ createResponseRequestExtraction(tags, payload, openaiStore)
143
+ break
137
144
  }
138
145
 
139
146
  span.addTags(tags)
@@ -313,6 +320,10 @@ function normalizeMethodName (methodName) {
313
320
  case 'embeddings.create':
314
321
  return 'createEmbedding'
315
322
 
323
+ // responses
324
+ case 'responses.create':
325
+ return 'createResponse'
326
+
316
327
  // files
317
328
  case 'files.create':
318
329
  return 'createFile'
@@ -376,6 +387,16 @@ function createEditRequestExtraction (tags, payload, openaiStore) {
376
387
  openaiStore.instruction = instruction
377
388
  }
378
389
 
390
+ function createResponseRequestExtraction (tags, payload, openaiStore) {
391
+ // Extract model information
392
+ if (payload.model) {
393
+ tags['openai.request.model'] = payload.model
394
+ }
395
+
396
+ // Store the full payload for response extraction
397
+ openaiStore.responseData = payload
398
+ }
399
+
379
400
  function retrieveModelRequestExtraction (tags, payload) {
380
401
  tags['openai.request.id'] = payload.id
381
402
  }
@@ -410,6 +431,10 @@ function responseDataExtractionByMethod (methodName, tags, body, openaiStore) {
410
431
  commonCreateResponseExtraction(tags, body, openaiStore, methodName)
411
432
  break
412
433
 
434
+ case 'createResponse':
435
+ createResponseResponseExtraction(tags, body, openaiStore)
436
+ break
437
+
413
438
  case 'listFiles':
414
439
  case 'listFineTunes':
415
440
  case 'listFineTuneEvents':
@@ -513,6 +538,26 @@ function commonCreateResponseExtraction (tags, body, openaiStore, methodName) {
513
538
  openaiStore.choices = body.choices
514
539
  }
515
540
 
541
+ function createResponseResponseExtraction (tags, body, openaiStore) {
542
+ // Extract response ID if available
543
+ if (body.id) {
544
+ tags['openai.response.id'] = body.id
545
+ }
546
+
547
+ // Extract status if available
548
+ if (body.status) {
549
+ tags['openai.response.status'] = body.status
550
+ }
551
+
552
+ // Extract model from response if available
553
+ if (body.model) {
554
+ tags['openai.response.model'] = body.model
555
+ }
556
+
557
+ // Store the full response for potential future use
558
+ openaiStore.response = body
559
+ }
560
+
516
561
  // The server almost always responds with JSON
517
562
  function coerceResponseBody (body, methodName) {
518
563
  switch (methodName) {
@@ -6,6 +6,7 @@ const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
6
6
 
7
7
  const {
8
8
  TEST_STATUS,
9
+ VITEST_POOL,
9
10
  finishAllTraceSpans,
10
11
  getTestSuitePath,
11
12
  getTestSuiteCommonTags,
@@ -344,7 +345,6 @@ class VitestPlugin extends CiPlugin {
344
345
  finishAllTraceSpans(testSuiteSpan)
345
346
  }
346
347
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
347
- // TODO: too frequent flush - find for method in worker to decrease frequency
348
348
  this.tracer._exporter.flush(onFinish)
349
349
  if (this.runningTestProbe) {
350
350
  this.removeDiProbe(this.runningTestProbe)
@@ -373,6 +373,7 @@ class VitestPlugin extends CiPlugin {
373
373
  isEarlyFlakeDetectionEnabled,
374
374
  isEarlyFlakeDetectionFaulty,
375
375
  isTestManagementTestsEnabled,
376
+ vitestPool,
376
377
  onFinish
377
378
  }) => {
378
379
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -394,6 +395,9 @@ class VitestPlugin extends CiPlugin {
394
395
  if (isTestManagementTestsEnabled) {
395
396
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
396
397
  }
398
+ if (vitestPool) {
399
+ this.testSessionSpan.setTag(VITEST_POOL, vitestPool)
400
+ }
397
401
  this.testModuleSpan.finish()
398
402
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
399
403
  this.testSessionSpan.finish()
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const TTLCache = require('@isaacs/ttlcache')
3
+ const { TTLCache } = require('@isaacs/ttlcache')
4
4
  const web = require('../plugins/util/web')
5
5
  const log = require('../log')
6
6
  const { AUTO_REJECT, USER_REJECT } = require('../../../../ext/priority')
@@ -12,6 +12,7 @@ module.exports = {
12
12
  cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
13
13
  expressMiddlewareError: dc.channel('apm:express:middleware:error'),
14
14
  expressProcessParams: dc.channel('datadog:express:process_params:start'),
15
+ expressResponseRenderStart: dc.channel('tracing:datadog:express:response:render:start'),
15
16
  expressSession: dc.channel('datadog:express-session:middleware:finish'),
16
17
  fastifyBodyParser: dc.channel('datadog:fastify:body-parser:finish'),
17
18
  fastifyCookieParser: dc.channel('datadog:fastify-cookie:read:finish'),
@@ -68,6 +68,13 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
68
68
  }
69
69
  this.analyze(pathArguments)
70
70
  })
71
+
72
+ this.addSub('tracing:datadog:express:response:render:start', (ctx) => {
73
+ const store = storage('legacy').getStore()
74
+ if (!store) return
75
+
76
+ this.analyze([ctx.view])
77
+ })
71
78
  }
72
79
 
73
80
  _isExcluded (location) {
@@ -8,8 +8,8 @@ const STRINGIFY_SENSITIVE_KEY = STRINGIFY_RANGE_KEY + 'SENSITIVE'
8
8
  const STRINGIFY_SENSITIVE_NOT_STRING_KEY = STRINGIFY_SENSITIVE_KEY + 'NOTSTRING'
9
9
 
10
10
  // eslint-disable-next-line @stylistic/max-len
11
- const KEYS_REGEX_WITH_SENSITIVE_RANGES = new RegExp(`(?:"(${STRINGIFY_RANGE_KEY}_\\d+_))|(?:"(${STRINGIFY_SENSITIVE_KEY}_\\d+_(\\d+)_))|("${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_\\d+_([\\s0-9.a-zA-Z]*)")`, 'gm')
12
- const KEYS_REGEX_WITHOUT_SENSITIVE_RANGES = new RegExp(`"(${STRINGIFY_RANGE_KEY}_\\d+_)`, 'gm')
11
+ const KEYS_REGEX_WITH_SENSITIVE_RANGES = new RegExp(String.raw`(?:"(${STRINGIFY_RANGE_KEY}_\d+_))|(?:"(${STRINGIFY_SENSITIVE_KEY}_\d+_(\d+)_))|("${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_\d+_([\s0-9.a-zA-Z]*)")`, 'gm')
12
+ const KEYS_REGEX_WITHOUT_SENSITIVE_RANGES = new RegExp(String.raw`"(${STRINGIFY_RANGE_KEY}_\d+_)`, 'gm')
13
13
 
14
14
  const sensitiveValueRegex = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
15
15
 
@@ -84,9 +84,10 @@ function sendVulnerabilities (vulnerabilities, span) {
84
84
  const jsonToSend = vulnerabilitiesFormatter.toJson(validatedVulnerabilities)
85
85
 
86
86
  if (jsonToSend.vulnerabilities.length > 0) {
87
- const tags = {}
88
- // TODO: Store this outside of the span and set the tag in the exporter.
89
- tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
87
+ const tags = {
88
+ // TODO: Store this outside of the span and set the tag in the exporter.
89
+ [IAST_JSON_TAG_KEY]: JSON.stringify(jsonToSend)
90
+ }
90
91
  span.addTags(tags)
91
92
  }
92
93
  }
@@ -1,12 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { fsOperationStart, incomingHttpRequestStart } = require('../channels')
3
+ const { isAbsolute } = require('path')
4
+
5
+ const { fsOperationStart, incomingHttpRequestStart, expressResponseRenderStart } = require('../channels')
4
6
  const { storage } = require('../../../../datadog-core')
5
7
  const { enable: enableFsPlugin, disable: disableFsPlugin, RASP_MODULE } = require('./fs-plugin')
6
8
  const { FS_OPERATION_PATH } = require('../addresses')
7
9
  const waf = require('../waf')
8
10
  const { RULE_TYPES, handleResult } = require('./utils')
9
- const { isAbsolute } = require('path')
10
11
 
11
12
  let config
12
13
  let enabled
@@ -25,6 +26,7 @@ function enable (_config) {
25
26
  function disable () {
26
27
  if (fsOperationStart.hasSubscribers) fsOperationStart.unsubscribe(analyzeLfi)
27
28
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(onFirstReceivedRequest)
29
+ if (expressResponseRenderStart.hasSubscribers) expressResponseRenderStart.unsubscribe(analyzeLfiInResponseRender)
28
30
 
29
31
  disableFsPlugin(RASP_MODULE)
30
32
 
@@ -42,10 +44,18 @@ function onFirstReceivedRequest () {
42
44
 
43
45
  if (!analyzeSubscribed) {
44
46
  fsOperationStart.subscribe(analyzeLfi)
47
+ expressResponseRenderStart.subscribe(analyzeLfiInResponseRender)
45
48
  analyzeSubscribed = true
46
49
  }
47
50
  }
48
51
 
52
+ function analyzeLfiInResponseRender (ctx) {
53
+ const store = storage('legacy').getStore()
54
+ if (!store) return
55
+
56
+ analyzeLfiPath(ctx.view, ctx.req, store.res, ctx.abortController)
57
+ }
58
+
49
59
  function analyzeLfi (ctx) {
50
60
  const store = storage('legacy').getStore()
51
61
  if (!store) return
@@ -54,15 +64,19 @@ function analyzeLfi (ctx) {
54
64
  if (!req || !fs) return
55
65
 
56
66
  getPaths(ctx, fs).forEach(path => {
57
- const ephemeral = {
58
- [FS_OPERATION_PATH]: path
59
- }
67
+ analyzeLfiPath(path, req, res, ctx.abortController)
68
+ })
69
+ }
60
70
 
61
- const raspRule = { type: RULE_TYPES.LFI }
71
+ function analyzeLfiPath (path, req, res, abortController) {
72
+ const ephemeral = {
73
+ [FS_OPERATION_PATH]: path
74
+ }
62
75
 
63
- const result = waf.run({ ephemeral }, req, raspRule)
64
- handleResult(result, req, res, ctx.abortController, config, raspRule)
65
- })
76
+ const raspRule = { type: RULE_TYPES.LFI }
77
+
78
+ const result = waf.run({ ephemeral }, req, raspRule)
79
+ handleResult(result, req, res, abortController, config, raspRule)
66
80
  }
67
81
 
68
82
  function getPaths (ctx, fs) {
@@ -42,9 +42,9 @@ function filterOutFramesFromLibrary (callSiteList) {
42
42
  if (globalThis.__DD_ESBUILD_IAST_WITH_SM) {
43
43
  // bundled with SourceMap, get original file and line to discriminate if comes from dd-trace or not
44
44
  const callSiteLocation = {
45
- path: callSite.getFileName(),
46
- line: callSite.getLineNumber(),
47
- column: callSite.getColumnNumber()
45
+ path: callSite.getTranslatedFileName?.() ?? callSite.getFileName(),
46
+ line: callSite.getTranslatedLineNumber?.() ?? callSite.getLineNumber(),
47
+ column: callSite.getTranslatedColumnNumber?.() ?? callSite.getColumnNumber()
48
48
  }
49
49
  const { path } = getOriginalPathAndLineFromSourceMap(callSiteLocation)
50
50
  return !path?.startsWith(ddBasePath)
@@ -68,9 +68,9 @@ function getCallsiteFrames (maxDepth = 32, constructorOpt = getCallsiteFrames, c
68
68
  const callSite = filteredFrames[index]
69
69
  indexedFrames.push({
70
70
  id: index,
71
- file: callSite.getFileName(),
72
- line: callSite.getLineNumber(),
73
- column: callSite.getColumnNumber(),
71
+ file: callSite.getTranslatedFileName?.() ?? callSite.getFileName(),
72
+ line: callSite.getTranslatedLineNumber?.() ?? callSite.getLineNumber(),
73
+ column: callSite.getTranslatedColumnNumber?.() ?? callSite.getColumnNumber(),
74
74
  function: callSite.getFunctionName(),
75
75
  class_name: callSite.getTypeName(),
76
76
  isNative: callSite.isNative()
@@ -29,6 +29,9 @@ function getInterprocessTraceCode () {
29
29
  if (getEnvironmentVariable('TINYPOOL_WORKER_ID')) {
30
30
  return VITEST_WORKER_TRACE_PAYLOAD_CODE
31
31
  }
32
+ if (getEnvironmentVariable('DD_VITEST_WORKER')) {
33
+ return VITEST_WORKER_TRACE_PAYLOAD_CODE
34
+ }
32
35
  return null
33
36
  }
34
37
 
@@ -47,6 +50,9 @@ function getInterprocessLogsCode () {
47
50
  if (getEnvironmentVariable('TINYPOOL_WORKER_ID')) {
48
51
  return VITEST_WORKER_LOGS_PAYLOAD_CODE
49
52
  }
53
+ if (getEnvironmentVariable('DD_VITEST_WORKER')) {
54
+ return VITEST_WORKER_LOGS_PAYLOAD_CODE
55
+ }
50
56
  return null
51
57
  }
52
58
 
@@ -1,6 +1,11 @@
1
1
  'use strict'
2
2
  const { JSONEncoder } = require('../../encode/json-encoder')
3
3
  const { getEnvironmentVariable } = require('../../../config-helper')
4
+ const log = require('../../../log')
5
+ const {
6
+ VITEST_WORKER_TRACE_PAYLOAD_CODE,
7
+ VITEST_WORKER_LOGS_PAYLOAD_CODE
8
+ } = require('../../../plugins/util/test')
4
9
 
5
10
  class Writer {
6
11
  constructor (interprocessCode) {
@@ -26,24 +31,42 @@ class Writer {
26
31
  _sendPayload (data, onDone = () => {}) {
27
32
  // ## Jest
28
33
  // Only available when `child_process` is used for the jest worker.
29
- // https://github.com/facebook/jest/blob/bb39cb2c617a3334bf18daeca66bd87b7ccab28b/packages/jest-worker/README.md#experimental-worker
30
34
  // If worker_threads is used, this will not work
31
- // TODO: make it compatible with worker_threads
35
+ // TODO: make `jest` instrumentation compatible with worker_threads
36
+ // https://github.com/facebook/jest/blob/bb39cb2c617a3334bf18daeca66bd87b7ccab28b/packages/jest-worker/README.md#experimental-worker
32
37
 
33
38
  // ## Cucumber
34
39
  // This reports to the test's main process the same way test data is reported by Cucumber
35
40
  // See cucumber code:
36
41
  // https://github.com/cucumber/cucumber-js/blob/5ce371870b677fe3d1a14915dc535688946f734c/src/runtime/parallel/run_worker.ts#L13
37
- if (process.send) { // it only works if process.send is available
38
- const isVitestWorker = !!getEnvironmentVariable('TINYPOOL_WORKER_ID')
39
42
 
40
- const payload = isVitestWorker
41
- ? { __tinypool_worker_message__: true, interprocessCode: this._interprocessCode, data }
42
- : [this._interprocessCode, data]
43
+ // Old because vitest@>=4 uses `DD_VITEST_WORKER` and reports arrays just like other frameworks
44
+ // Before vitest@>=4, we need the `__tinypool_worker_message__` property, or tinypool will crash
45
+ const isVitestWorkerOld = !!getEnvironmentVariable('TINYPOOL_WORKER_ID')
46
+ const payload = isVitestWorkerOld
47
+ ? { __tinypool_worker_message__: true, interprocessCode: this._interprocessCode, data }
48
+ : [this._interprocessCode, data]
43
49
 
50
+ const isVitestTestWorker =
51
+ this._interprocessCode === VITEST_WORKER_TRACE_PAYLOAD_CODE ||
52
+ this._interprocessCode === VITEST_WORKER_LOGS_PAYLOAD_CODE
53
+
54
+ if (process.send) {
44
55
  process.send(payload, () => {
45
56
  onDone()
46
57
  })
58
+ } else if (isVitestTestWorker) { // TODO: worker_threads are only supported in vitest right now
59
+ const { isMainThread, parentPort } = require('worker_threads')
60
+ if (isMainThread) {
61
+ return onDone()
62
+ }
63
+ try {
64
+ parentPort.postMessage(payload)
65
+ } catch (error) {
66
+ log.error('Error posting message to parent port', error)
67
+ } finally {
68
+ onDone()
69
+ }
47
70
  } else {
48
71
  onDone()
49
72
  }
@@ -4,6 +4,7 @@ const fs = require('fs')
4
4
  const os = require('os')
5
5
  const uuid = require('crypto-randomuuid') // we need to keep the old uuid dep because of cypress
6
6
  const { URL } = require('url')
7
+
7
8
  const log = require('./log')
8
9
  const tagger = require('./tagger')
9
10
  const set = require('../../datadog-core/src/utils/src/set')
@@ -1507,4 +1508,12 @@ function getAgentUrl (url, options) {
1507
1508
  }
1508
1509
  }
1509
1510
 
1510
- module.exports = Config
1511
+ let configInstance = null
1512
+ function getConfig (options) {
1513
+ if (!configInstance) {
1514
+ configInstance = new Config(options)
1515
+ }
1516
+ return configInstance
1517
+ }
1518
+
1519
+ module.exports = getConfig
@@ -58,6 +58,11 @@ if (SUPPORT_ARRAY_BUFFER_RESIZE) {
58
58
  session.on('Debugger.paused', async ({ params }) => {
59
59
  const start = process.hrtime.bigint()
60
60
 
61
+ if (params.reason !== 'other') {
62
+ // This error should not be caught, and should exit the worker thread, effectively stopping the debugging session
63
+ throw new Error(`Unexpected Debugger.paused reason: ${params.reason}`)
64
+ }
65
+
61
66
  let maxReferenceDepth, maxCollectionSize, maxFieldCount, maxLength
62
67
  let sampled = false
63
68
  let numberOfProbesWithSnapshots = 0
@@ -168,7 +173,9 @@ session.on('Debugger.paused', async ({ params }) => {
168
173
  await session.post('Debugger.resume')
169
174
  const diff = process.hrtime.bigint() - start // TODO: Recored as telemetry (DEBUG-2858)
170
175
 
171
- log.debug(() => `[debugger:devtools_client] Finished processing breakpoints - main thread paused for: ${
176
+ // This doesn't measure the overhead of the CDP protocol. The actual pause time is slightly larger.
177
+ // On my machine I'm seeing around 1.7ms of overhead.
178
+ log.debug(() => `[debugger:devtools_client] Finished processing breakpoints - main thread paused for: ~${
172
179
  Number(diff) / 1_000_000
173
180
  } ms`)
174
181
 
@@ -1,10 +1,15 @@
1
1
  'use strict'
2
2
 
3
+ /** @typedef {'error'|'warn'|'info'|'debug'} Level */
4
+ /** @typedef {(...args: unknown[]) => void} LogFn */
5
+ /** @typedef {Record<Level, LogFn>} Logger */
6
+
3
7
  const { workerData } = require('node:worker_threads')
4
8
 
5
9
  // For testing purposes, we allow `workerData` to be undefined and fallback to a default config
6
- const { config: { debug, logLevel }, logPort } = workerData ?? { config: { debug: false } }
10
+ const { config: { debug = false, logLevel } = {}, logPort } = workerData ?? {}
7
11
 
12
+ /** @type {Level[]} */
8
13
  const LEVELS = ['error', 'warn', 'info', 'debug']
9
14
  const on = (level, ...args) => {
10
15
  if (typeof args[0] === 'function') {
@@ -14,6 +19,12 @@ const on = (level, ...args) => {
14
19
  }
15
20
  const off = () => {}
16
21
 
17
- for (const level of LEVELS) {
18
- module.exports[level] = debug && LEVELS.indexOf(logLevel) >= LEVELS.indexOf(level) ? on.bind(null, level) : off
19
- }
22
+ const threshold = LEVELS.indexOf(logLevel)
23
+
24
+ /** @type {Logger} */
25
+ module.exports = Object.fromEntries(
26
+ LEVELS.map(level => [
27
+ level,
28
+ debug && threshold >= LEVELS.indexOf(level) ? on.bind(null, level) : off
29
+ ])
30
+ )
@@ -13,6 +13,10 @@ module.exports = {
13
13
  getLocalStateForCallFrame
14
14
  }
15
15
 
16
+ function returnError () {
17
+ return new Error('Error getting local state')
18
+ }
19
+
16
20
  async function getLocalStateForCallFrame (
17
21
  callFrame,
18
22
  {
@@ -37,7 +41,7 @@ async function getLocalStateForCallFrame (
37
41
  // TODO: We might be able to get part of the scope chain.
38
42
  // Consider if we could set errors just for the part of the scope chain that throws during collection.
39
43
  log.error('[debugger:devtools_client] Error getting local state for call frame', err)
40
- return () => new Error('Error getting local state')
44
+ return returnError
41
45
  }
42
46
 
43
47
  // Delay calling `processRawState` so the caller gets a chance to resume the main thread before processing `rawState`
@@ -12,7 +12,7 @@ const uuidSource =
12
12
  const containerSource = '[0-9a-f]{64}'
13
13
  const taskSource = String.raw`[0-9a-f]{32}-\d+`
14
14
  const lineReg = /^(\d+):([^:]*):(.+)$/m
15
- const entityReg = new RegExp(`.*(${uuidSource}|${containerSource}|${taskSource})(?:\\.scope)?$`, 'm')
15
+ const entityReg = new RegExp(String.raw`.*(${uuidSource}|${containerSource}|${taskSource})(?:\.scope)?$`, 'm')
16
16
 
17
17
  let inode = 0
18
18
  let cgroup = ''
@@ -36,13 +36,12 @@ function makeRequest (data, url, cb) {
36
36
  'Datadog-Meta-Lang': 'javascript',
37
37
  'Datadog-Meta-Tracer-Version': pkg.version,
38
38
  'Content-Type': 'application/msgpack'
39
- }
39
+ },
40
+ protocol: url.protocol,
41
+ hostname: url.hostname,
42
+ port: url.port
40
43
  }
41
44
 
42
- options.protocol = url.protocol
43
- options.hostname = url.hostname
44
- options.port = url.port
45
-
46
45
  log.debug('Request to the intake: %j', options)
47
46
 
48
47
  request(data, options, (err, res) => {
@@ -32,13 +32,13 @@ const map = {
32
32
  'resource.name': 'resource'
33
33
  }
34
34
 
35
- function format (span) {
35
+ function format (span, isChunkRoot) {
36
36
  const formatted = formatSpan(span)
37
37
 
38
38
  extractSpanLinks(formatted, span)
39
39
  extractSpanEvents(formatted, span)
40
40
  extractRootTags(formatted, span)
41
- extractChunkTags(formatted, span)
41
+ extractChunkTags(formatted, span, isChunkRoot)
42
42
  extractTags(formatted, span)
43
43
 
44
44
  return formatted
@@ -192,11 +192,10 @@ function extractRootTags (formattedSpan, span) {
192
192
  addTag({}, formattedSpan.metrics, TOP_LEVEL_KEY, 1)
193
193
  }
194
194
 
195
- function extractChunkTags (formattedSpan, span) {
195
+ function extractChunkTags (formattedSpan, span, isChunkRoot) {
196
196
  const context = span.context()
197
- const isLocalRoot = span === context._trace.started[0]
198
197
 
199
- if (!isLocalRoot) return
198
+ if (!isChunkRoot) return
200
199
 
201
200
  for (const [key, value] of Object.entries(context._trace.tags)) {
202
201
  addTag(formattedSpan.meta, formattedSpan.metrics, key, value)
@@ -82,7 +82,9 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
82
82
  * @param {string} toolDescription
83
83
  * @returns {string | undefined}
84
84
  */
85
- findToolName (toolDescription) {
85
+ findToolName (toolName, toolDescription) {
86
+ if (Number.isNaN(Number.parseInt(toolName))) return toolName
87
+
86
88
  for (const availableTool of this.#availableTools) {
87
89
  const description = availableTool.description
88
90
  if (description === toolDescription && availableTool.id) {
@@ -260,16 +262,17 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
260
262
 
261
263
  const formattedToolCalls = []
262
264
  for (const toolCall of outputMessageToolCalls) {
263
- const toolCallArgs = getJsonStringValue(toolCall.args, {})
265
+ const toolArgs = toolCall.args ?? toolCall.input
266
+ const toolCallArgs = typeof toolArgs === 'string' ? getJsonStringValue(toolArgs, {}) : toolArgs
264
267
  const toolDescription = toolsForModel?.find(tool => toolCall.toolName === tool.name)?.description
265
- const name = this.findToolName(toolDescription)
268
+ const name = this.findToolName(toolCall.toolName, toolDescription)
266
269
  this.#toolCallIdsToName[toolCall.toolCallId] = name
267
270
 
268
271
  formattedToolCalls.push({
269
272
  arguments: toolCallArgs,
270
273
  name,
271
274
  toolId: toolCall.toolCallId,
272
- type: 'function'
275
+ type: toolCall.toolCallType ?? 'function'
273
276
  })
274
277
  }
275
278
 
@@ -317,10 +320,10 @@ class VercelAILLMObsPlugin extends BaseLLMObsPlugin {
317
320
  finalContent += part.text ?? part.data
318
321
  } else if (type === 'tool-call') {
319
322
  const toolDescription = toolsForModel?.find(tool => part.toolName === tool.name)?.description
320
- const name = this.findToolName(toolDescription)
323
+ const name = this.findToolName(part.toolName, toolDescription)
321
324
 
322
325
  toolCalls.push({
323
- arguments: part.args,
326
+ arguments: part.args ?? part.input,
324
327
  name,
325
328
  toolId: part.toolCallId,
326
329
  type: 'function'
@@ -242,12 +242,9 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
242
242
  const cacheWriteTokens = usage.cache_creation_input_tokens
243
243
  const cacheReadTokens = usage.cache_read_input_tokens
244
244
 
245
- const metrics = {}
246
-
247
- metrics.inputTokens =
248
- (inputTokens ?? 0) +
249
- (cacheWriteTokens ?? 0) +
250
- (cacheReadTokens ?? 0)
245
+ const metrics = {
246
+ inputTokens: (inputTokens ?? 0) + (cacheWriteTokens ?? 0) + (cacheReadTokens ?? 0)
247
+ }
251
248
 
252
249
  if (outputTokens) metrics.outputTokens = outputTokens
253
250
  const totalTokens = metrics.inputTokens + (outputTokens ?? 0)
@@ -37,8 +37,9 @@ class LangChainLLMObsHandler {
37
37
  return message
38
38
  }
39
39
  try {
40
- const messageContent = {}
41
- messageContent.content = message.content || ''
40
+ const messageContent = {
41
+ content: message.content || ''
42
+ }
42
43
 
43
44
  const role = this.getRole(message)
44
45
  if (role) messageContent.role = role