dd-trace 5.104.0 → 5.106.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 (159) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +16 -2
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
  11. package/packages/datadog-instrumentations/src/cucumber.js +390 -157
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  14. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  15. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  19. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  27. package/packages/datadog-instrumentations/src/hono.js +54 -3
  28. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  29. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  30. package/packages/datadog-instrumentations/src/jest.js +360 -150
  31. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  32. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  33. package/packages/datadog-instrumentations/src/nats.js +182 -0
  34. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  35. package/packages/datadog-instrumentations/src/openai.js +33 -18
  36. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  37. package/packages/datadog-instrumentations/src/pino.js +17 -5
  38. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  39. package/packages/datadog-instrumentations/src/router.js +76 -32
  40. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  41. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  42. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  45. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  46. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  47. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  48. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  49. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  50. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  51. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  52. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  53. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  54. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  55. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  56. package/packages/datadog-plugin-http/src/server.js +40 -15
  57. package/packages/datadog-plugin-jest/src/index.js +11 -3
  58. package/packages/datadog-plugin-jest/src/util.js +15 -8
  59. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  60. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  61. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  62. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  63. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  64. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  65. package/packages/datadog-plugin-nats/src/index.js +20 -0
  66. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  67. package/packages/datadog-plugin-nats/src/util.js +33 -0
  68. package/packages/datadog-plugin-next/src/index.js +5 -3
  69. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  70. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  71. package/packages/datadog-plugin-pino/src/index.js +42 -0
  72. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  73. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  74. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  75. package/packages/datadog-plugin-router/src/index.js +33 -44
  76. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  77. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  78. package/packages/datadog-plugin-winston/src/index.js +30 -0
  79. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  80. package/packages/dd-trace/src/aiguard/index.js +1 -1
  81. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  82. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  83. package/packages/dd-trace/src/appsec/index.js +1 -1
  84. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  85. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  86. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  87. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  88. package/packages/dd-trace/src/baggage.js +7 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  90. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  91. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  92. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  93. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  94. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  96. package/packages/dd-trace/src/encode/0.4.js +124 -108
  97. package/packages/dd-trace/src/encode/0.5.js +114 -26
  98. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  99. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  100. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  101. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  102. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  103. package/packages/dd-trace/src/id.js +15 -0
  104. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +92 -6
  105. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
  106. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  107. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  108. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  109. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  110. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  111. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  112. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  113. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  114. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  115. package/packages/dd-trace/src/llmobs/util.js +66 -3
  116. package/packages/dd-trace/src/log/index.js +1 -1
  117. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  118. package/packages/dd-trace/src/msgpack/index.js +96 -2
  119. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  120. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  121. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  122. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  123. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  124. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  125. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +22 -3
  126. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  127. package/packages/dd-trace/src/opentracing/propagation/text_map.js +64 -77
  128. package/packages/dd-trace/src/opentracing/span.js +59 -19
  129. package/packages/dd-trace/src/opentracing/span_context.js +50 -3
  130. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  131. package/packages/dd-trace/src/plugins/database.js +7 -6
  132. package/packages/dd-trace/src/plugins/index.js +4 -0
  133. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  134. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  135. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  136. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  137. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  138. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  139. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  140. package/packages/dd-trace/src/priority_sampler.js +2 -2
  141. package/packages/dd-trace/src/profiling/config.js +10 -23
  142. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
  143. package/packages/dd-trace/src/profiling/profiler.js +21 -11
  144. package/packages/dd-trace/src/profiling/profilers/wall.js +12 -7
  145. package/packages/dd-trace/src/sampling_rule.js +7 -7
  146. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  147. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  148. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  149. package/packages/dd-trace/src/span_format.js +190 -58
  150. package/packages/dd-trace/src/spanleak.js +1 -1
  151. package/packages/dd-trace/src/standalone/index.js +3 -3
  152. package/packages/dd-trace/src/tagger.js +0 -2
  153. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  154. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  155. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  156. package/vendor/dist/protobufjs/index.js +1 -1
  157. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  158. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  159. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -8,7 +8,6 @@ const { getCompileToRegexp } = require('./path-to-regexp')
8
8
  const {
9
9
  getRouterMountPaths,
10
10
  joinPath,
11
- getLayerMatchers,
12
11
  setLayerMatchers,
13
12
  isAppMounted,
14
13
  setRouterMountPath,
@@ -42,34 +41,47 @@ function createWrapRouterMethod (name, compile) {
42
41
  const nextChannel = channel(`apm:${name}:middleware:next`)
43
42
  const routeAddedChannel = channel(`apm:${name}:route:added`)
44
43
 
45
- function wrapLayerHandle (layer, original) {
46
- original._name = original._name || layer.name
44
+ function wrapLayerHandle (layer, original, matchers) {
45
+ // Resolve `name` once at wrap time: cached on the original for any code
46
+ // that reads `_name`, captured in the closure so the per-call body avoids
47
+ // the property-lookup / `||` fallback.
48
+ const name = original._name || layer.name || original.name
49
+ original._name = name
50
+
51
+ // Wrap-time matcher analysis. The single-pattern case yields a constant
52
+ // route; only multi-pattern stacks need a per-request layer.path match.
53
+ let captureRoute
54
+ let needMultiMatch = false
55
+ if (matchers.length !== 0 && !isFastStar(layer, matchers) && !isFastSlash(layer, matchers)) {
56
+ if (matchers.length === 1) {
57
+ captureRoute = matchers[0].path
58
+ } else {
59
+ needMultiMatch = true
60
+ }
61
+ }
47
62
 
48
- return shimmer.wrapFunction(original, original => function (...args) {
49
- if (!enterChannel.hasSubscribers) return original.apply(this, args)
63
+ // Split by arity: router only ever dispatches 3-arg request handlers
64
+ // through `Layer.handleRequest` and 4-arg error handlers through
65
+ // `Layer.handleError`. Specialising lets the per-call body use named
66
+ // parameters and `.call`, avoiding the rest-spread Array allocation that
67
+ // the unified shape forced on every middleware invocation.
68
+ return original.length === 4
69
+ ? shimmer.wrapFunction(original, errorHandlerLayerWrap(layer, name, captureRoute, needMultiMatch, matchers))
70
+ : shimmer.wrapFunction(original, requestHandlerLayerWrap(layer, name, captureRoute, needMultiMatch, matchers))
71
+ }
50
72
 
51
- const matchers = getLayerMatchers(layer)
52
- const lastIndex = args.length - 1
53
- const name = original._name || original.name
54
- const req = args[args.length > 3 ? 1 : 0]
55
- const next = args[lastIndex]
73
+ function requestHandlerLayerWrap (layer, name, captureRoute, needMultiMatch, matchers) {
74
+ return original => function (req, res, next) {
75
+ if (!enterChannel.hasSubscribers) return original.call(this, req, res, next)
56
76
 
57
- if (typeof next === 'function') {
58
- args[lastIndex] = wrapNext(req, next)
59
- }
77
+ const wrappedNext = typeof next === 'function' ? wrapNext(req, next) : next
60
78
 
61
- let route
62
-
63
- if (matchers?.length && !isFastStar(layer, matchers) && !isFastSlash(layer, matchers)) {
64
- if (matchers.length === 1) {
65
- // The host already matched this layer; the lone pattern is the route.
66
- route = matchers[0].path
67
- } else {
68
- for (const matcher of matchers) {
69
- if (matcher.regex?.test(layer.path)) {
70
- route = matcher.path
71
- break
72
- }
79
+ let route = captureRoute
80
+ if (needMultiMatch) {
81
+ for (const matcher of matchers) {
82
+ if (matcher.regex?.test(layer.path)) {
83
+ route = matcher.path
84
+ break
73
85
  }
74
86
  }
75
87
  }
@@ -77,7 +89,7 @@ function createWrapRouterMethod (name, compile) {
77
89
  enterChannel.publish({ name, req, route, layer })
78
90
 
79
91
  try {
80
- return original.apply(this, args)
92
+ return original.call(this, req, res, wrappedNext)
81
93
  } catch (error) {
82
94
  errorChannel.publish({ req, error })
83
95
  nextChannel.publish({ req })
@@ -87,15 +99,47 @@ function createWrapRouterMethod (name, compile) {
87
99
  } finally {
88
100
  exitChannel.publish({ req })
89
101
  }
90
- })
102
+ }
103
+ }
104
+
105
+ function errorHandlerLayerWrap (layer, name, captureRoute, needMultiMatch, matchers) {
106
+ return original => function (error, req, res, next) {
107
+ if (!enterChannel.hasSubscribers) return original.call(this, error, req, res, next)
108
+
109
+ const wrappedNext = typeof next === 'function' ? wrapNext(req, next) : next
110
+
111
+ let route = captureRoute
112
+ if (needMultiMatch) {
113
+ for (const matcher of matchers) {
114
+ if (matcher.regex?.test(layer.path)) {
115
+ route = matcher.path
116
+ break
117
+ }
118
+ }
119
+ }
120
+
121
+ enterChannel.publish({ name, req, route, layer })
122
+
123
+ try {
124
+ return original.call(this, error, req, res, wrappedNext)
125
+ } catch (caught) {
126
+ errorChannel.publish({ req, error: caught })
127
+ nextChannel.publish({ req })
128
+ finishChannel.publish({ req })
129
+
130
+ throw caught
131
+ } finally {
132
+ exitChannel.publish({ req })
133
+ }
134
+ }
91
135
  }
92
136
 
93
137
  function wrapStack (layers, matchers) {
94
138
  for (const layer of layers) {
95
139
  if (layer.__handle) { // express-async-errors
96
- layer.__handle = wrapLayerHandle(layer, layer.__handle)
140
+ layer.__handle = wrapLayerHandle(layer, layer.__handle, matchers)
97
141
  } else {
98
- layer.handle = wrapLayerHandle(layer, layer.handle)
142
+ layer.handle = wrapLayerHandle(layer, layer.handle, matchers)
99
143
  }
100
144
 
101
145
  setLayerMatchers(layer, matchers)
@@ -258,15 +302,15 @@ addHook({ name: 'router', versions: ['>=2'] }, Router => {
258
302
 
259
303
  shimmer.wrap(router, 'handle', function wrapHandle (originalHandle) {
260
304
  return function wrappedHandle (req, res, next) {
261
- const abortController = new AbortController()
262
-
263
305
  if (queryParserReadCh.hasSubscribers && req) {
306
+ const abortController = new AbortController()
307
+
264
308
  queryParserReadCh.publish({ req, res, query: req.query, abortController })
265
309
 
266
310
  if (abortController.signal.aborted) return
267
311
  }
268
312
 
269
- return originalHandle.apply(this, arguments)
313
+ return originalHandle.call(this, req, res, next)
270
314
  }
271
315
  })
272
316
 
@@ -97,7 +97,7 @@ function wrapStripe (Stripe) {
97
97
 
98
98
  addHook({
99
99
  name: 'stripe',
100
- versions: ['9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '>=20.0.0 <22'],
100
+ versions: ['>=9 <22'],
101
101
  }, Stripe => shimmer.wrapFunction(Stripe, wrapLegacyStripe))
102
102
 
103
103
  addHook({
@@ -140,7 +140,7 @@ class SchemaExtractor {
140
140
  return
141
141
  }
142
142
 
143
- if (span.context()._tags[SCHEMA_TYPE] && operation === 'serialization') {
143
+ if (span.context().getTag(SCHEMA_TYPE) && operation === 'serialization') {
144
144
  // we have already added a schema to this span, this call is an encode of nested schema types
145
145
  return
146
146
  }
@@ -3,7 +3,7 @@
3
3
  const BaseAwsSdkPlugin = require('../../base')
4
4
  const { parseModelId } = require('./utils')
5
5
 
6
- const enabledOperations = new Set(['invokeModel', 'invokeModelWithResponseStream'])
6
+ const enabledOperations = new Set(['invokeModel', 'invokeModelWithResponseStream', 'converse', 'converseStream'])
7
7
 
8
8
  class BedrockRuntime extends BaseAwsSdkPlugin {
9
9
  static id = 'bedrockruntime'
@@ -131,6 +131,7 @@ class Generation {
131
131
  outputTokens,
132
132
  cacheReadTokens,
133
133
  cacheWriteTokens,
134
+ messages,
134
135
  } = {}) {
135
136
  // stringify message as it could be a single generated message as well as a list of embeddings
136
137
  this.message = typeof message === 'string' ? message : JSON.stringify(message) || ''
@@ -143,6 +144,7 @@ class Generation {
143
144
  cacheReadTokens,
144
145
  cacheWriteTokens,
145
146
  }
147
+ this.messages = messages ?? [{ content: this.message, role: this.role }]
146
148
  }
147
149
  }
148
150
 
@@ -401,10 +403,7 @@ function extractTextAndResponseReason (response, provider, modelName) {
401
403
  message: output.message?.content[0]?.text ?? 'Unsupported content type',
402
404
  finishReason: body.stopReason,
403
405
  role: output.message?.role,
404
- inputTokens: body.usage?.inputTokens,
405
- outputTokens: body.usage?.outputTokens,
406
- cacheReadInputTokenCount: body.usage?.cacheReadInputTokenCount,
407
- cacheWriteInputTokenCount: body.usage?.cacheWriteInputTokenCount,
406
+ ...buildUsage(body.usage),
408
407
  })
409
408
  }
410
409
  break
@@ -476,6 +475,216 @@ function extractTextAndResponseReason (response, provider, modelName) {
476
475
  return new Generation()
477
476
  }
478
477
 
478
+ /**
479
+ * Convert a Converse content-block array to an LLMObs message array.
480
+ *
481
+ * @param {string} role
482
+ * @param {Array<object>} contentBlocks
483
+ * @returns {{ content?: string, role: string, toolCalls?: Array, toolResults?: Array } | undefined}
484
+ */
485
+ function extractMessagesFromConverseContent (role, contentBlocks) {
486
+ let content = ''
487
+ const toolCalls = []
488
+ const toolResults = []
489
+
490
+ for (const block of contentBlocks || []) {
491
+ if (block == null || typeof block !== 'object') continue
492
+ if (typeof block.text === 'string') {
493
+ content += block.text
494
+ } else if (block.toolUse) {
495
+ toolCalls.push(buildToolCall(block.toolUse))
496
+ } else if (block.toolResult) {
497
+ toolResults.push(buildToolResult(block.toolResult))
498
+ } else {
499
+ content += `[Unsupported content type: ${getContentBlockType(block)}]`
500
+ }
501
+ }
502
+
503
+ if (!content && toolCalls.length === 0 && toolResults.length === 0) return
504
+
505
+ const message = { role }
506
+ if (content) message.content = content
507
+ if (toolCalls.length > 0) message.toolCalls = toolCalls
508
+ if (toolResults.length > 0) message.toolResults = toolResults
509
+ return message
510
+ }
511
+
512
+ /**
513
+ * Resolve a Converse `ContentBlock`'s member type. The block is a key-presence
514
+ * tagged union (no `type` discriminator), so the active member is its sole own
515
+ * key. For forward-compat `$unknown` members the real type is the first element
516
+ * of the `[name, value]` tuple.
517
+ *
518
+ * @param {object} block
519
+ * @returns {string}
520
+ */
521
+ function getContentBlockType (block) {
522
+ const key = Object.keys(block)[0]
523
+ if (key === '$unknown') return block.$unknown?.[0] ?? 'unknown'
524
+ return key ?? 'unknown'
525
+ }
526
+
527
+ // Always emit at least one output message so downstream tagging has a role to attach to.
528
+ function toOutputMessages (role, contentBlocks) {
529
+ const message = extractMessagesFromConverseContent(role, contentBlocks)
530
+ return message ? [message] : [{ role, content: '' }]
531
+ }
532
+
533
+ function buildToolCall ({ name, input, toolUseId }) {
534
+ return { name: name ?? '', arguments: input ?? {}, toolId: toolUseId ?? '', type: 'toolUse' }
535
+ }
536
+
537
+ function parseToolInput (inputStr) {
538
+ try {
539
+ return JSON.parse(inputStr)
540
+ } catch {
541
+ log.warn('Failed to parse Converse stream toolUse.input JSON; emitting empty arguments')
542
+ return {}
543
+ }
544
+ }
545
+
546
+ function buildToolResult ({ toolUseId, content }) {
547
+ const result = (content || []).map(resolveToolResultItem).join('')
548
+ return { name: '', result, toolId: toolUseId ?? '', type: 'tool_result' }
549
+ }
550
+
551
+ function resolveToolResultItem (item) {
552
+ if (typeof item.text === 'string') return item.text
553
+ if (item.json != null) return JSON.stringify(item.json)
554
+ return `[Unsupported content type(s): ${getContentBlockType(item)}]`
555
+ }
556
+
557
+ function buildUsage (usage = {}) {
558
+ return {
559
+ inputTokens: usage.inputTokens,
560
+ outputTokens: usage.outputTokens,
561
+ cacheReadTokens: usage.cacheReadInputTokens ?? usage.cacheReadInputTokenCount,
562
+ cacheWriteTokens: usage.cacheWriteInputTokens ?? usage.cacheWriteInputTokenCount,
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Extract tool definitions from a Converse request's `toolConfig.tools`,
568
+ * mapping Bedrock's `toolSpec` shape to LLMObs `ToolDefinition` shape.
569
+ *
570
+ * @param {object} params - Converse request params with optional `toolConfig.tools[].toolSpec`.
571
+ * @returns {Array<{ name: string, description: string, schema: object }>}
572
+ */
573
+ function extractConverseToolDefinitions (params) {
574
+ const toolDefinitions = []
575
+ for (const tool of params.toolConfig?.tools || []) {
576
+ const toolSpec = tool?.toolSpec
577
+ if (!toolSpec?.name) continue
578
+ toolDefinitions.push({
579
+ name: toolSpec.name,
580
+ description: toolSpec.description ?? '',
581
+ schema: toolSpec.inputSchema ?? {},
582
+ })
583
+ }
584
+ return toolDefinitions
585
+ }
586
+
587
+ /**
588
+ * Extract request metadata + rendered input messages from a Converse /
589
+ * ConverseStream request.
590
+ *
591
+ * @param {{ modelId?: string, messages?: Array, system?: Array, inferenceConfig?: object, toolConfig?: object }} params
592
+ * @returns {RequestParams}
593
+ */
594
+ function extractRequestParamsConverse (params) {
595
+ const prompt = []
596
+ for (const block of params.system || []) {
597
+ if (typeof block?.text === 'string') prompt.push({ content: block.text, role: 'system' })
598
+ }
599
+ for (const msg of params.messages || []) {
600
+ if (msg == null || typeof msg !== 'object') continue
601
+ const message = extractMessagesFromConverseContent(msg.role || 'user', msg.content)
602
+ if (message) prompt.push(message)
603
+ }
604
+
605
+ const { temperature, topP, maxTokens, stopSequences } = params.inferenceConfig || {}
606
+ return new RequestParams({ prompt, temperature, topP, maxTokens, stopSequences })
607
+ }
608
+
609
+ /**
610
+ * Extract output messages + usage from a non-stream Converse response.
611
+ *
612
+ * @param {{ output?: { message?: { role?: string, content?: Array } }, stopReason?: string, usage?: object }} response
613
+ * @returns {Generation}
614
+ */
615
+ function extractTextAndResponseReasonConverse (response) {
616
+ const outputMessage = response?.output?.message
617
+ const role = outputMessage?.role || 'assistant'
618
+
619
+ return new Generation({
620
+ role,
621
+ finishReason: response?.stopReason || '',
622
+ ...buildUsage(response?.usage),
623
+ messages: toOutputMessages(role, outputMessage?.content),
624
+ })
625
+ }
626
+
627
+ /**
628
+ * Aggregate Converse stream events into a single output message + usage.
629
+ * One messageStart / messageStop pair per response, so one message out.
630
+ *
631
+ * Stream events describe the same content-block structure as the non-stream
632
+ * response, spread across start/delta chunks. We reassemble those chunks
633
+ * into a normalized content-block array and reuse the non-stream extractor.
634
+ *
635
+ * @param {Array<object>} chunks - Ordered ConverseStreamOutput events.
636
+ * @returns {Generation}
637
+ */
638
+ function extractTextAndResponseReasonConverseFromStream (chunks) {
639
+ let role = 'assistant'
640
+ let stopReason = ''
641
+ let usage = {}
642
+ const blocksByIdx = new Map()
643
+
644
+ for (const chunk of chunks || []) {
645
+ if (chunk.messageStart?.role) {
646
+ role = chunk.messageStart.role
647
+ } else if (chunk.messageStop?.stopReason) {
648
+ stopReason = chunk.messageStop.stopReason
649
+ } else if (chunk.metadata?.usage) {
650
+ usage = chunk.metadata.usage
651
+ } else if (chunk.contentBlockStart?.start?.toolUse) {
652
+ const { contentBlockIndex, start: { toolUse } } = chunk.contentBlockStart
653
+ blocksByIdx.set(contentBlockIndex, {
654
+ toolUse: { toolUseId: toolUse.toolUseId, name: toolUse.name, inputStr: '' },
655
+ })
656
+ } else if (chunk.contentBlockDelta) {
657
+ const { contentBlockIndex, delta } = chunk.contentBlockDelta
658
+ if (typeof delta?.text === 'string') {
659
+ const block = blocksByIdx.get(contentBlockIndex) ?? {}
660
+ block.text = (block.text ?? '') + delta.text
661
+ blocksByIdx.set(contentBlockIndex, block)
662
+ } else if (typeof delta?.toolUse?.input === 'string') {
663
+ const block = blocksByIdx.get(contentBlockIndex) ?? { toolUse: { inputStr: '' } }
664
+ block.toolUse ??= { inputStr: '' }
665
+ block.toolUse.inputStr += delta.toolUse.input
666
+ blocksByIdx.set(contentBlockIndex, block)
667
+ }
668
+ }
669
+ }
670
+
671
+ const contentBlocks = [...blocksByIdx.keys()].sort((a, b) => a - b).map(i => {
672
+ const block = blocksByIdx.get(i)
673
+ if (block.toolUse) {
674
+ const { toolUseId, name, inputStr } = block.toolUse
675
+ block.toolUse = { toolUseId, name, input: parseToolInput(inputStr) }
676
+ }
677
+ return block
678
+ })
679
+
680
+ return new Generation({
681
+ role,
682
+ finishReason: stopReason,
683
+ ...buildUsage(usage),
684
+ messages: toOutputMessages(role, contentBlocks),
685
+ })
686
+ }
687
+
479
688
  module.exports = {
480
689
  Generation,
481
690
  RequestParams,
@@ -483,5 +692,10 @@ module.exports = {
483
692
  parseModelId,
484
693
  extractRequestParams,
485
694
  extractTextAndResponseReason,
695
+ extractMessagesFromConverseContent,
696
+ extractConverseToolDefinitions,
697
+ extractRequestParamsConverse,
698
+ extractTextAndResponseReasonConverse,
699
+ extractTextAndResponseReasonConverseFromStream,
486
700
  PROVIDER,
487
701
  }
@@ -0,0 +1,144 @@
1
+ 'use strict'
2
+
3
+ const { storage } = require('../../datadog-core')
4
+
5
+ const DatabasePlugin = require('../../dd-trace/src/plugins/database')
6
+
7
+ class AzureCosmosPlugin extends DatabasePlugin {
8
+ static id = 'azure-cosmos'
9
+ // Channel prefix determines how the plugin subscribes to instrumentation events.
10
+ // Three patterns exist — set `static prefix` explicitly based on instrumentation type:
11
+ //
12
+ // Orchestrion: static prefix = 'tracing:orchestrion:<npm-package>:<channelName>'
13
+ // Shimmer + tracingChannel: static prefix = 'tracing:apm:<name>:<operation>'
14
+ // Shimmer + manual channels: omit prefix — defaults to `apm:${id}:${operation}`
15
+ static prefix = 'tracing:orchestrion:@azure/cosmos:executePlugins'
16
+ static peerServicePrecursors = ['db.name']
17
+
18
+ operationName () {
19
+ return 'cosmosdb.query'
20
+ }
21
+
22
+ asyncEnd (ctx) {
23
+ if (!ctx.span) return
24
+ const span = ctx.currentStore?.span
25
+ if (span) {
26
+ const result = ctx.result
27
+ if (result?.code) span.setTag('db.response.status_code', (result.code).toString())
28
+ if (result?.substatus) span.setTag('cosmosdb.response.sub_status_code', result.substatus)
29
+ span.finish()
30
+ }
31
+ }
32
+
33
+ error (ctx) {
34
+ if (!ctx.span) return
35
+ const span = ctx.currentStore?.span
36
+ if (span) {
37
+ const error = ctx.error
38
+ this.addError(error, span)
39
+ if (error?.code) span.setTag('db.response.status_code', (error.code).toString())
40
+ if (error?.substatus) span.setTag('cosmosdb.response.sub_status_code', error.substatus)
41
+ }
42
+ }
43
+
44
+ bindStart (ctx) {
45
+ const requestContext = ctx.arguments[1]
46
+ const resource = this.getResource(requestContext)
47
+ const { dbName, containerName } = this.getDbInfo(requestContext)
48
+ const connectionMode = this.getConnectionMode(requestContext)
49
+ const { outHost, userAgent } = this.getHttpInfo(requestContext)
50
+ const pluginOn = ctx.arguments[3]
51
+
52
+ if (pluginOn != null && requestContext.operationType != null && requestContext.resourceType != null) {
53
+ const operationType = requestContext.operationType
54
+ const resourceType = requestContext.resourceType
55
+ // only trace operations not requests (pluginOn)
56
+ // trace requests only if they are read or query operations not on docs
57
+ // prevents doubled read spans for createIfNotExists calls
58
+ if (pluginOn === 'request' && ((operationType !== 'read' && operationType !== 'query') ||
59
+ (operationType === 'read' && resourceType !== 'docs'))) {
60
+ return storage('legacy').getStore()
61
+ }
62
+
63
+ // separately, skip tracing read requests without a path, these don't
64
+ // represent CRUD operations on a resource we care about
65
+ // not returning current store because we don't want the child http.request spans
66
+ // to be created
67
+ if (operationType === 'read' && requestContext.path === '') {
68
+ return { noop: true }
69
+ }
70
+ }
71
+
72
+ const span = this.startSpan(this.operationName(), {
73
+ resource,
74
+ type: 'cosmosdb',
75
+ kind: 'client',
76
+ meta: {
77
+ component: 'azure_cosmos',
78
+ 'db.system': 'cosmosdb',
79
+ 'db.name': dbName,
80
+ 'cosmosdb.container': containerName,
81
+ 'cosmosdb.connection.mode': connectionMode,
82
+ 'http.useragent': userAgent,
83
+ 'out.host': outHost,
84
+ },
85
+ }, ctx)
86
+
87
+ ctx.span = span
88
+ return ctx.currentStore
89
+ }
90
+
91
+ getResource (requestContext) {
92
+ const path = requestContext.path
93
+ const parts = path.split('/')
94
+ let modified = false
95
+ for (let i = 2; i < parts.length; i += 2) {
96
+ if (parts[i].length > 0 && parts[i - 1] !== 'dbs' && parts[i - 1] !== 'colls') {
97
+ parts[i] = '?'
98
+ modified = true
99
+ }
100
+ }
101
+
102
+ return `${requestContext.operationType} ${modified ? parts.join('/') : path}`
103
+ }
104
+
105
+ getDbInfo (requestContext) {
106
+ let dbName = null
107
+ let containerName = null
108
+
109
+ if (requestContext.operationType === 'create' && requestContext.resourceType === 'dbs' &&
110
+ requestContext.body != null && requestContext.body.id != null) {
111
+ dbName = requestContext.body.id
112
+ }
113
+
114
+ let resourceLink = requestContext.path
115
+ if (resourceLink?.length > 1 && resourceLink.startsWith('/')) {
116
+ resourceLink = resourceLink.slice(1)
117
+ const parts = resourceLink.split('/')
118
+ if (parts.length > 0 && parts[0].toLowerCase() === 'dbs' && parts.length >= 2) {
119
+ dbName = parts[1]
120
+ if (parts.length >= 4 && parts[2].toLowerCase() === 'colls' && parts[3] !== '') {
121
+ containerName = parts[3]
122
+ }
123
+ }
124
+ }
125
+
126
+ return { dbName, containerName }
127
+ }
128
+
129
+ getConnectionMode (requestContext) {
130
+ const mode = requestContext.client?.connectionPolicy?.connectionMode
131
+ if (mode === 0) {
132
+ return 'gateway'
133
+ }
134
+ return 'direct'
135
+ }
136
+
137
+ getHttpInfo (requestContext) {
138
+ const outHost = requestContext.client?.cosmosClientOptions?.endpoint
139
+ const userAgent = requestContext.headers?.['User-Agent']
140
+ return { outHost, userAgent }
141
+ }
142
+ }
143
+
144
+ module.exports = AzureCosmosPlugin
@@ -62,7 +62,7 @@ class AzureEventHubsProducerPlugin extends ProducerPlugin {
62
62
  const contexts = spanContexts.get(eventData)
63
63
  if (contexts) {
64
64
  for (const spanContext of contexts) {
65
- span.addLink(spanContext)
65
+ span.addLink({ context: spanContext })
66
66
  }
67
67
  }
68
68
  }
@@ -13,6 +13,7 @@ const triggerMap = {
13
13
  serviceBusQueue: 'ServiceBus',
14
14
  serviceBusTopic: 'ServiceBus',
15
15
  eventHub: 'EventHubs',
16
+ cosmosDB: 'CosmosDB',
16
17
  }
17
18
 
18
19
  class AzureFunctionsPlugin extends TracingPlugin {
@@ -53,7 +54,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
53
54
  )
54
55
 
55
56
  span._integrationName = 'azure-functions'
56
- span.context()._tags.component = 'azure-functions'
57
+ span.context().setTag('component', 'azure-functions')
57
58
  span.addTags(meta)
58
59
  webContext.span = span
59
60
  webContext.azureFunctionCtx = ctx
@@ -127,6 +128,8 @@ function getMetaForTrigger ({ functionName, methodName, invocationContext }) {
127
128
  'resource.name': `EventHubs ${functionName}`,
128
129
  'span.kind': 'consumer',
129
130
  }
131
+ } else if (triggerMap[methodName] === 'CosmosDB') {
132
+ meta['resource.name'] = `CosmosDB ${functionName}`
130
133
  }
131
134
 
132
135
  return meta
@@ -155,7 +158,7 @@ function setSpanLinks (triggerType, tracer, span, ctx) {
155
158
  if (!props || Object.keys(props).length === 0) return
156
159
  const spanContext = tracer.extract('text_map', props)
157
160
  if (spanContext) {
158
- span.addLink(spanContext)
161
+ span.addLink({ context: spanContext })
159
162
  }
160
163
  }
161
164
 
@@ -56,7 +56,7 @@ class AzureServiceBusProducerPlugin extends ProducerPlugin {
56
56
  const contexts = spanContexts.get(messages)
57
57
  if (contexts) {
58
58
  for (const spanContext of contexts) {
59
- span.addLink(spanContext)
59
+ span.addLink({ context: spanContext })
60
60
  }
61
61
  }
62
62
  }
@@ -1,8 +1,36 @@
1
1
  'use strict'
2
2
 
3
+ const { buildLogHolder } = require('../../dd-trace/src/plugins/log_injection')
3
4
  const LogPlugin = require('../../dd-trace/src/plugins/log_plugin')
4
5
 
5
6
  class BunyanPlugin extends LogPlugin {
6
7
  static id = 'bunyan'
8
+
9
+ constructor (...args) {
10
+ super(...args)
11
+ this.addSub('apm:bunyan:log', (arg) => this.handleLog(arg))
12
+ }
13
+
14
+ /**
15
+ * Inject `dd` directly on the record bunyan hands us. bunyan builds the
16
+ * record inside `mkRecord` via `objCopy(log.fields)` and then copies the
17
+ * caller's fields onto the result, so the `rec` object that flows
18
+ * through `_emit` is always bunyan-owned, has `Object.prototype` for its
19
+ * prototype, and is never the caller's input directly. Mutating it adds
20
+ * `dd` for every consumer (JSON streams via `JSON.stringify(rec)`, raw
21
+ * streams via the record reference) without paying for a Proxy view.
22
+ *
23
+ * @param {{ message: object }} arg
24
+ */
25
+ handleLog (arg) {
26
+ const rec = arg.message
27
+ if (rec === null || typeof rec !== 'object' || Object.hasOwn(rec, 'dd')) return
28
+
29
+ const logHolder = buildLogHolder(this.tracer)
30
+ if (!logHolder) return
31
+
32
+ rec.dd = logHolder.dd
33
+ }
7
34
  }
35
+
8
36
  module.exports = BunyanPlugin