dd-trace 5.103.0 → 5.105.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  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 +15 -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/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -14,11 +14,14 @@ const finishChannel = channel('apm:grpc:client:request:finish')
14
14
  const emitChannel = channel('apm:grpc:client:request:emit')
15
15
 
16
16
  function createWrapMakeRequest (type, hasPeer = false) {
17
+ const metadataIndex = type === types.client_stream || type === types.bidi ? 3 : 4
18
+
17
19
  return function wrapMakeRequest (makeRequest) {
18
20
  return function (path) {
19
- const args = ensureMetadata(this, arguments, 4)
21
+ if (!startChannel.hasSubscribers) return makeRequest.apply(this, arguments)
20
22
 
21
- return callMethod(this, makeRequest, args, path, args[4], type, hasPeer)
23
+ const { metadata, args } = resolveMetadata(this, arguments, metadataIndex)
24
+ return callMethod(this, makeRequest, args, path, metadata, type, hasPeer)
22
25
  }
23
26
  }
24
27
  }
@@ -82,9 +85,13 @@ function wrapMethod (method, path, type, hasPeer) {
82
85
  return method
83
86
  }
84
87
 
88
+ const metadataIndex = type === types.client_stream || type === types.bidi ? 0 : 1
89
+
85
90
  const wrapped = shimmer.wrapFunction(method, method => function () {
86
- const args = ensureMetadata(this, arguments, 1)
87
- return callMethod(this, method, args, path, args[1], type, hasPeer)
91
+ if (!startChannel.hasSubscribers) return method.apply(this, arguments)
92
+
93
+ const { metadata, args } = resolveMetadata(this, arguments, metadataIndex)
94
+ return callMethod(this, method, args, path, metadata, type, hasPeer)
88
95
  })
89
96
 
90
97
  patched.add(wrapped)
@@ -140,24 +147,25 @@ function createWrapEmit (ctx, hasPeer = false) {
140
147
  }
141
148
 
142
149
  function callMethod (client, method, args, path, metadata, type, hasPeer = false) {
143
- if (!startChannel.hasSubscribers) return method.apply(client, args)
144
-
145
- const length = args.length
146
- const callback = args[length - 1]
147
-
148
150
  const ctx = { metadata, path, type }
149
151
 
150
152
  return startChannel.runStores(ctx, () => {
151
153
  try {
154
+ let callArgs = args
155
+
152
156
  if (type === types.unary || type === types.client_stream) {
157
+ if (!Array.isArray(callArgs)) callArgs = [...callArgs]
158
+
159
+ const length = callArgs.length
160
+ const callback = callArgs[length - 1]
153
161
  if (typeof callback === 'function') {
154
- args[length - 1] = wrapCallback(ctx, callback)
162
+ callArgs[length - 1] = wrapCallback(ctx, callback)
155
163
  } else {
156
- args[length] = wrapCallback(ctx)
164
+ callArgs[length] = wrapCallback(ctx)
157
165
  }
158
166
  }
159
167
 
160
- const call = method.apply(client, args)
168
+ const call = method.apply(client, callArgs)
161
169
 
162
170
  if (call && typeof call.emit === 'function') {
163
171
  shimmer.wrap(call, 'emit', createWrapEmit(ctx, hasPeer))
@@ -167,36 +175,44 @@ function callMethod (client, method, args, path, metadata, type, hasPeer = false
167
175
  } catch (e) {
168
176
  ctx.error = e
169
177
  errorChannel.publish(ctx)
178
+ throw e
170
179
  }
171
180
  // No end channel needed
172
181
  })
173
182
  }
174
183
 
175
- function ensureMetadata (client, args, index) {
176
- const grpc = getGrpc(client)
177
-
178
- if (!client || !grpc) return args
179
-
180
- const meta = args[index]
181
- const normalized = []
182
-
183
- for (let i = 0; i < index; i++) {
184
- normalized.push(args[i])
184
+ /**
185
+ * Returns the `Metadata` for a gRPC client invocation, splicing or replacing
186
+ * one at `index` when the user did not pass their own.
187
+ *
188
+ * @param {object} client
189
+ * @param {ArrayLike<unknown>} args
190
+ * @param {number} index
191
+ * @returns {{ metadata: object | undefined, args: ArrayLike<unknown> }}
192
+ */
193
+ function resolveMetadata (client, args, index) {
194
+ const grpc = client && getGrpc(client)
195
+ if (!grpc) return { metadata: undefined, args }
196
+
197
+ const slot = args[index]
198
+
199
+ if (slot instanceof grpc.Metadata || slot?.constructor?.name === 'Metadata') {
200
+ return { metadata: slot, args }
185
201
  }
186
202
 
187
- if (!meta || !meta.constructor || meta.constructor.name !== 'Metadata') {
188
- normalized.push(new grpc.Metadata())
189
- }
190
-
191
- if (meta) {
192
- normalized.push(meta)
193
- }
203
+ const metadata = new grpc.Metadata()
194
204
 
195
- for (let i = index + 1; i < args.length; i++) {
196
- normalized.push(args[i])
205
+ if (slot == null) {
206
+ const out = [...args]
207
+ out[index] = metadata
208
+ return { metadata, args: out }
197
209
  }
198
210
 
199
- return normalized
211
+ const out = new Array(args.length + 1)
212
+ for (let i = 0; i < index; i++) out[i] = args[i]
213
+ out[index] = metadata
214
+ for (let i = index; i < args.length; i++) out[i + 1] = args[i]
215
+ return { metadata, args: out }
200
216
  }
201
217
 
202
218
  function getType (definition) {
@@ -1,5 +1,53 @@
1
1
  'use strict'
2
2
 
3
+ /**
4
+ * Returns the value as a string, JSON-stringifying it when it is not already a string.
5
+ * Returns the value unchanged when it is `null` or `undefined`.
6
+ *
7
+ * @param {unknown} value
8
+ * @returns {string|undefined|null}
9
+ */
10
+ function stringifyIfNeeded (value) {
11
+ if (value == null) return value
12
+ return typeof value === 'string' ? value : JSON.stringify(value)
13
+ }
14
+
15
+ const FILE_FALLBACK = '[file]'
16
+ const IMAGE_FALLBACK = '[image]'
17
+
18
+ const OPENAI_RESPONSE_TOOL_CALL_TYPES = new Set([
19
+ 'apply_patch_call',
20
+ 'code_interpreter_call',
21
+ 'computer_call',
22
+ 'custom_tool_call',
23
+ 'file_search_call',
24
+ 'function_call',
25
+ 'image_generation_call',
26
+ 'local_shell_call',
27
+ 'mcp_call',
28
+ 'shell_call',
29
+ 'web_search_call',
30
+ ])
31
+
32
+ const OPENAI_RESPONSE_TOOL_OUTPUT_TYPES = new Set([
33
+ 'apply_patch_call_output',
34
+ 'computer_call_output',
35
+ 'custom_tool_call_output',
36
+ 'function_call_output',
37
+ 'local_shell_call_output',
38
+ 'shell_call_output',
39
+ ])
40
+
41
+ /**
42
+ * Returns a stringified value, falling back to an empty string for absent values.
43
+ *
44
+ * @param {unknown} value
45
+ * @returns {string}
46
+ */
47
+ function stringifyOrEmpty (value) {
48
+ return stringifyIfNeeded(value) ?? ''
49
+ }
50
+
3
51
  /**
4
52
  * Converts a LanguageModelV2FilePart with an image mediaType to an AI guard style image_url content part.
5
53
  *
@@ -79,12 +127,11 @@ function convertVercelPromptToMessages (prompt) {
79
127
  if (part.type === 'text') {
80
128
  textParts.push(part.text)
81
129
  } else if (part.type === 'tool-call') {
82
- const args = part.args ?? part.input
83
130
  toolCalls.push({
84
131
  id: part.toolCallId,
85
132
  function: {
86
133
  name: part.toolName,
87
- arguments: typeof args === 'string' ? args : JSON.stringify(args),
134
+ arguments: stringifyIfNeeded(part.args ?? part.input),
88
135
  },
89
136
  })
90
137
  }
@@ -103,11 +150,10 @@ function convertVercelPromptToMessages (prompt) {
103
150
 
104
151
  for (const part of msg.content) {
105
152
  if (part.type === 'tool-result') {
106
- const result = part.result ?? part.output
107
153
  messages.push({
108
154
  role: 'tool',
109
155
  tool_call_id: part.toolCallId,
110
- content: typeof result === 'string' ? result : JSON.stringify(result),
156
+ content: stringifyIfNeeded(part.result ?? part.output),
111
157
  })
112
158
  }
113
159
  }
@@ -118,6 +164,58 @@ function convertVercelPromptToMessages (prompt) {
118
164
  return messages
119
165
  }
120
166
 
167
+ /**
168
+ * Converts OpenAI chat-completions messages to the message format expected by AI Guard.
169
+ *
170
+ * Modern `tool_calls` messages already match the expected shape. Deprecated chat
171
+ * completions `function_call` and `function` role messages are normalized to the
172
+ * equivalent tool-call shape so AI Guard can classify them as tool interactions.
173
+ *
174
+ * @param {Array<object>} messages
175
+ * @returns {Array<object>|undefined}
176
+ */
177
+ function normalizeOpenAIChatMessages (messages) {
178
+ if (!Array.isArray(messages) || messages.length === 0) return
179
+
180
+ const normalizedMessages = []
181
+ for (const message of messages) {
182
+ const normalized = normalizeOpenAIChatMessage(message)
183
+ if (normalized) normalizedMessages.push(normalized)
184
+ }
185
+ return normalizedMessages.length ? normalizedMessages : undefined
186
+ }
187
+
188
+ /**
189
+ * Converts one OpenAI chat-completions message to AI Guard's expected shape.
190
+ *
191
+ * @param {object} message
192
+ * @returns {object|undefined}
193
+ */
194
+ function normalizeOpenAIChatMessage (message) {
195
+ if (!message || typeof message !== 'object') return
196
+
197
+ if (message.role === 'function') {
198
+ return {
199
+ role: 'tool',
200
+ tool_call_id: message.tool_call_id ?? message.name,
201
+ content: stringifyOrEmpty(message.content),
202
+ }
203
+ }
204
+
205
+ if (!message.function_call) return message
206
+
207
+ const { function_call: functionCall, ...normalized } = message
208
+ const name = functionCall.name
209
+ normalized.tool_calls ??= [{
210
+ id: message.tool_call_id ?? name,
211
+ function: {
212
+ name,
213
+ arguments: stringifyOrEmpty(functionCall.arguments),
214
+ },
215
+ }]
216
+ return normalized
217
+ }
218
+
121
219
  /**
122
220
  * Converts LLM output tool calls to AI guard style message format.
123
221
  *
@@ -130,16 +228,13 @@ function buildToolCallOutputMessages (inputMessages, toolCalls) {
130
228
  ...inputMessages,
131
229
  {
132
230
  role: 'assistant',
133
- tool_calls: toolCalls.map(tc => {
134
- const args = tc.args ?? tc.input
135
- return {
136
- id: tc.toolCallId,
137
- function: {
138
- name: tc.toolName,
139
- arguments: typeof args === 'string' ? args : JSON.stringify(args),
140
- },
141
- }
142
- }),
231
+ tool_calls: toolCalls.map(tc => ({
232
+ id: tc.toolCallId,
233
+ function: {
234
+ name: tc.toolName,
235
+ arguments: stringifyIfNeeded(tc.args ?? tc.input),
236
+ },
237
+ })),
143
238
  },
144
239
  ]
145
240
  }
@@ -173,10 +268,223 @@ function buildOutputMessages (inputMessages, content) {
173
268
  return inputMessages
174
269
  }
175
270
 
271
+ /**
272
+ * Converts OpenAI Responses API input/output items to OpenAI chat-style messages.
273
+ *
274
+ * @param {string|Array<object>|undefined} items
275
+ * @param {string} defaultRole
276
+ * @returns {Array<object>}
277
+ */
278
+ function convertOpenAIResponseItemsToMessages (items, defaultRole) {
279
+ if (typeof items === 'string') return [{ role: defaultRole, content: items }]
280
+ if (!Array.isArray(items)) return []
281
+
282
+ const messages = []
283
+ for (const item of items) {
284
+ const converted = openAIResponseItemToMessage(item, defaultRole)
285
+ if (Array.isArray(converted)) {
286
+ for (const message of converted) messages.push(message)
287
+ } else if (converted) {
288
+ messages.push(converted)
289
+ }
290
+ }
291
+ return messages
292
+ }
293
+
294
+ /**
295
+ * Converts OpenAI reusable prompt variables to user messages for AI Guard.
296
+ *
297
+ * The reusable prompt template body is not available on the request, but its
298
+ * variables are user/application-provided content that OpenAI substitutes into
299
+ * the prompt. Screening them closes prompt-only `responses.create({ prompt })`
300
+ * calls and prompt variables used alongside `input`.
301
+ *
302
+ * @param {{variables?: Record<string, string|object>|null}|undefined|null} prompt
303
+ * @returns {Array<object>}
304
+ */
305
+ function convertOpenAIResponsePromptToMessages (prompt) {
306
+ const variables = prompt?.variables
307
+ if (!variables || typeof variables !== 'object') return []
308
+
309
+ const messages = []
310
+ for (const value of Object.values(variables)) {
311
+ const content = openAIResponsePromptVariableToMessageContent(value)
312
+ if (content != null) messages.push({ role: 'user', content })
313
+ }
314
+ return messages
315
+ }
316
+
317
+ /**
318
+ * Converts one OpenAI reusable prompt variable value to message content.
319
+ *
320
+ * Routes every variable through `openAIResponseContentToMessageContent` so the
321
+ * result follows the same string-when-text-only / array-when-multimodal shape
322
+ * convention used elsewhere in this file. Media variables that produce no
323
+ * usable content (e.g. an `input_image` with no URL or `file_id`) fall back to
324
+ * a stable text marker so AI Guard still observes that a media variable was
325
+ * attached.
326
+ *
327
+ * @param {string|object} value
328
+ * @returns {string|Array<{type: string, text?: string, image_url?: {url: string}}>|undefined}
329
+ */
330
+ function openAIResponsePromptVariableToMessageContent (value) {
331
+ let part
332
+ if (typeof value === 'string') {
333
+ part = { type: 'input_text', text: value }
334
+ } else if (value && typeof value === 'object') {
335
+ part = value
336
+ } else {
337
+ return
338
+ }
339
+
340
+ const content = openAIResponseContentToMessageContent([part])
341
+ if (content != null) return content
342
+ if (part.type === 'input_image') return IMAGE_FALLBACK
343
+ }
344
+
345
+ /**
346
+ * Converts one OpenAI Responses API item to an OpenAI chat-style message.
347
+ *
348
+ * @param {object} item
349
+ * @param {string} defaultRole
350
+ * @returns {object|Array<object>|undefined}
351
+ */
352
+ function openAIResponseItemToMessage (item, defaultRole) {
353
+ if (!item || typeof item !== 'object') return
354
+ const type = item.type ?? 'message'
355
+
356
+ if (type === 'message') {
357
+ const content = openAIResponseContentToMessageContent(item.content)
358
+ if (content != null) return { role: item.role || defaultRole, content }
359
+ } else if (OPENAI_RESPONSE_TOOL_CALL_TYPES.has(type)) {
360
+ return openAIResponseToolCallToMessages(item)
361
+ } else if (OPENAI_RESPONSE_TOOL_OUTPUT_TYPES.has(type)) {
362
+ return openAIResponseToolOutputToMessage(item)
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Converts a Responses API tool-call item to one or more chat-style messages.
368
+ *
369
+ * Most tool-call items represent only the assistant's tool request. MCP and
370
+ * image-generation items can also carry tool output on the same item, so include
371
+ * a linked tool message when output-like fields are present.
372
+ *
373
+ * @param {object} item
374
+ * @returns {object|Array<object>}
375
+ */
376
+ function openAIResponseToolCallToMessages (item) {
377
+ const toolCallId = item.call_id ?? item.id ?? item.name ?? item.type
378
+ const message = {
379
+ role: 'assistant',
380
+ tool_calls: [{
381
+ id: toolCallId,
382
+ function: {
383
+ name: item.name ?? item.server_label ?? item.type,
384
+ arguments: stringifyOrEmpty(item.arguments ?? item.input ?? item.action),
385
+ },
386
+ }],
387
+ }
388
+
389
+ if (item.output == null && item.result == null && item.error == null) return message
390
+ return [message, openAIResponseToolOutputToMessage(item)]
391
+ }
392
+
393
+ /**
394
+ * Converts a Responses API tool-output item to a chat-style tool message.
395
+ *
396
+ * @param {object} item
397
+ * @returns {object}
398
+ */
399
+ function openAIResponseToolOutputToMessage (item) {
400
+ return {
401
+ role: 'tool',
402
+ tool_call_id: item.call_id ?? item.id,
403
+ content: openAIResponseOutputValueToMessageContent(item.output ?? item.result ?? item.error),
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Converts Responses API tool output to message content.
409
+ *
410
+ * @param {unknown} output
411
+ * @returns {string|Array<{type: string, text?: string, image_url?: {url: string}}>}
412
+ */
413
+ function openAIResponseOutputValueToMessageContent (output) {
414
+ const content = openAIResponseContentToMessageContent(output)
415
+ return content ?? stringifyOrEmpty(output)
416
+ }
417
+
418
+ /**
419
+ * Converts OpenAI Responses API content to OpenAI chat-style message content.
420
+ *
421
+ * @param {string|Array<string|{type?: string, text?: string, refusal?: string,
422
+ * image_url?: string|{url?: string}, file_id?: string, file_url?: string,
423
+ * filename?: string}>|undefined} content
424
+ * @returns {string|Array<{type: string, text?: string, image_url?: {url: string}}>|undefined}
425
+ */
426
+ function openAIResponseContentToMessageContent (content) {
427
+ if (typeof content === 'string') return content
428
+ if (!Array.isArray(content)) return
429
+
430
+ const parts = []
431
+ let hasImages = false
432
+
433
+ for (const part of content) {
434
+ if (!part) continue
435
+ if (typeof part === 'string') {
436
+ parts.push({ type: 'text', text: part })
437
+ } else if ((part.type === 'input_text' || part.type === 'output_text' || part.type === 'text') &&
438
+ typeof part.text === 'string') {
439
+ parts.push({ type: 'text', text: part.text })
440
+ } else if (part.type === 'refusal' && typeof part.refusal === 'string') {
441
+ parts.push({ type: 'text', text: part.refusal })
442
+ } else if (part.type === 'input_image' || part.type === 'image_url') {
443
+ const image = openAIResponseImageContentPart(part)
444
+ if (image) {
445
+ hasImages = true
446
+ parts.push(image)
447
+ }
448
+ } else if (part.type === 'input_file') {
449
+ parts.push({ type: 'text', text: openAIResponseFileContentPart(part) })
450
+ }
451
+ }
452
+
453
+ if (!parts.length) return
454
+ if (hasImages) return parts
455
+ return parts.map(part => part.text).join('\n')
456
+ }
457
+
458
+ /**
459
+ * Converts an OpenAI image content part to AI Guard image_url content.
460
+ *
461
+ * @param {{image_url?: string|{url?: string}, file_id?: string, url?: string}} part
462
+ * @returns {{type: 'image_url', image_url: {url: string}}|undefined}
463
+ */
464
+ function openAIResponseImageContentPart (part) {
465
+ const url = typeof part.image_url === 'string' ? part.image_url : part.image_url?.url ?? part.url
466
+ if (url) return { type: 'image_url', image_url: { url } }
467
+ if (part.file_id) return { type: 'image_url', image_url: { url: part.file_id } }
468
+ }
469
+
470
+ /**
471
+ * Extracts a stable text marker from an OpenAI file content part.
472
+ *
473
+ * @param {{file_id?: string|null, file_url?: string, filename?: string, file_data?: string}} part
474
+ * @returns {string}
475
+ */
476
+ function openAIResponseFileContentPart (part) {
477
+ return part.file_id ?? part.file_url ?? part.filename ?? FILE_FALLBACK
478
+ }
479
+
176
480
  module.exports = {
177
481
  convertVercelPromptToMessages,
178
482
  convertFilePartToImageUrl,
483
+ normalizeOpenAIChatMessages,
179
484
  buildToolCallOutputMessages,
180
485
  buildTextOutputMessages,
181
486
  buildOutputMessages,
487
+ convertOpenAIResponseItemsToMessages,
488
+ convertOpenAIResponsePromptToMessages,
489
+ openAIResponseContentToMessageContent,
182
490
  }
@@ -45,7 +45,7 @@ function createCallbackInstrumentor (prefix, { captureResult = false } = {}) {
45
45
  }
46
46
 
47
47
  return startCh.runStores(ctx, () => {
48
- args[lastIndex] = shimmer.wrapFunction(cb, cb => function (error, ...rest) {
48
+ args[lastIndex] = shimmer.wrapCallback(cb, cb => function (error, ...rest) {
49
49
  if (error) {
50
50
  ctx.error = error
51
51
  errorCh.publish(ctx)
@@ -5,6 +5,7 @@ module.exports = {
5
5
  child_process: () => require('../child_process'),
6
6
  crypto: () => require('../crypto'),
7
7
  dns: () => require('../dns'),
8
+ 'dns/promises': () => require('../dns'),
8
9
  fs: { serverless: false, fn: () => require('../fs') },
9
10
  http: () => require('../http'),
10
11
  http2: () => require('../http2'),
@@ -21,6 +22,7 @@ module.exports = {
21
22
  '@modelcontextprotocol/sdk': () => require('../modelcontextprotocol-sdk'),
22
23
  'apollo-server-core': () => require('../apollo-server-core'),
23
24
  '@aws-sdk/smithy-client': () => require('../aws-sdk'),
25
+ '@azure/cosmos': { esmFirst: true, fn: () => require('../azure-cosmos') },
24
26
  '@azure/event-hubs': () => require('../azure-event-hubs'),
25
27
  '@azure/functions': () => require('../azure-functions'),
26
28
  'durable-functions': () => require('../azure-durable-functions'),
@@ -48,6 +50,7 @@ module.exports = {
48
50
  '@prisma/client': { esmFirst: true, fn: () => require('../prisma') },
49
51
  './runtime/library.js': () => require('../prisma'),
50
52
  '@redis/client': () => require('../redis'),
53
+ '@smithy/core': () => require('../aws-sdk'),
51
54
  '@smithy/smithy-client': () => require('../aws-sdk'),
52
55
  '@vitest/runner': { esmFirst: true, fn: () => require('../vitest') },
53
56
  aerospike: () => require('../aerospike'),
@@ -111,6 +114,7 @@ module.exports = {
111
114
  multer: () => require('../multer'),
112
115
  mysql: () => require('../mysql'),
113
116
  mysql2: () => require('../mysql2'),
117
+ '@nats-io/nats-core': () => require('../nats'),
114
118
  next: () => require('../next'),
115
119
  'node-serialize': () => require('../node-serialize'),
116
120
  nyc: () => require('../nyc'),
@@ -58,7 +58,8 @@ exports.getHooks = function getHooks (names) {
58
58
  * @param {string} [args.file] path to file within package to instrument. Defaults to 'index.js'.
59
59
  * @param {string} [args.filePattern] pattern to match files within package to instrument
60
60
  * @param {boolean} [args.patchDefault] whether to patch the default export. Defaults to true.
61
- * @param {(moduleExports: unknown, version: string, isIitm?: boolean) => unknown} [hook] Patches module exports
61
+ * @param {(moduleExports: unknown, version: string, isIitm?: boolean, hookMeta?: object) => unknown} [hook]
62
+ * Patches module exports
62
63
  */
63
64
  exports.addHook = function addHook ({ name, versions, file, filePattern, patchDefault }, hook) {
64
65
  if (!instrumentations[name]) {
@@ -1,5 +1,10 @@
1
1
  'use strict'
2
2
 
3
+ // Produce API key 0; v0–v2 use the legacy MessageSet format with no header
4
+ // field, so trace headers can only be carried on v3+ (Kafka >=0.11).
5
+ const PRODUCE_API_KEY = 0
6
+ const PRODUCE_VERSION_WITH_HEADERS = 3
7
+
3
8
  // Side-table mapping a kafkajs producer/consumer to the cluster captured at
4
9
  // creation time. The boundary uses it to read `cluster.brokerPool` lazily on
5
10
  // first send/consume instead of opening a parallel admin connection. A
@@ -35,7 +40,19 @@ function cloneMessages (messages, ensureHeaders) {
35
40
  return result
36
41
  }
37
42
 
43
+ /**
44
+ * @param {{ versions?: Record<number, { minVersion: number, maxVersion: number }> } | undefined} brokerPool
45
+ * kafkajs's `cluster.brokerPool`. `versions` is populated once the seed
46
+ * broker handshakes; before that, the answer is unknown and we return
47
+ * `true` so the caller defaults to injection.
48
+ */
49
+ function brokerSupportsMessageHeaders (brokerPool) {
50
+ const produce = brokerPool?.versions?.[PRODUCE_API_KEY]
51
+ return !produce || produce.maxVersion >= PRODUCE_VERSION_WITH_HEADERS
52
+ }
53
+
38
54
  module.exports = {
55
+ brokerSupportsMessageHeaders,
39
56
  clientToCluster,
40
57
  cloneMessages,
41
58
  }