dd-trace 5.132.0 → 5.134.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 (231) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-runner-setup.mjs +27 -0
  16. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  17. package/index.d.ts +41 -19
  18. package/initialize.mjs +1 -3
  19. package/openfeature/rules-based.d.ts +11 -0
  20. package/openfeature/rules-based.js +7 -0
  21. package/package.json +25 -22
  22. package/packages/datadog-core/src/storage.js +16 -0
  23. package/packages/datadog-esbuild/index.js +18 -12
  24. package/packages/datadog-instrumentations/index.js +2 -1
  25. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  26. package/packages/datadog-instrumentations/src/ai.js +3 -3
  27. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  28. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  29. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  30. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  31. package/packages/datadog-instrumentations/src/cucumber.js +65 -41
  32. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  33. package/packages/datadog-instrumentations/src/electron.js +6 -6
  34. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  35. package/packages/datadog-instrumentations/src/fs.js +2 -2
  36. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  37. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  38. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +28 -9
  39. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  40. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  41. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  42. package/packages/datadog-instrumentations/src/helpers/register.js +45 -1
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +89 -6
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/jest.js +12 -0
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  48. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/vitest.js +8 -0
  49. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +7 -7
  50. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +5 -0
  51. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  52. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  53. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  54. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  55. package/packages/datadog-instrumentations/src/jest.js +72 -67
  56. package/packages/datadog-instrumentations/src/knex.js +9 -5
  57. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  58. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  59. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  60. package/packages/datadog-instrumentations/src/mocha/utils.js +38 -4
  61. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -2
  62. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  63. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  64. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  65. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  66. package/packages/datadog-instrumentations/src/nova-sonic/session.js +492 -0
  67. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  68. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  69. package/packages/datadog-instrumentations/src/openai.js +7 -0
  70. package/packages/datadog-instrumentations/src/pino.js +5 -6
  71. package/packages/datadog-instrumentations/src/playwright.js +2 -0
  72. package/packages/datadog-instrumentations/src/redis.js +27 -13
  73. package/packages/datadog-instrumentations/src/router.js +30 -0
  74. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  75. package/packages/datadog-instrumentations/src/stripe.js +23 -12
  76. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +8 -5
  77. package/packages/datadog-instrumentations/src/vitest-main.js +16 -18
  78. package/packages/datadog-instrumentations/src/vitest-util.js +7 -4
  79. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  80. package/packages/datadog-instrumentations/src/vitest.js +9 -1
  81. package/packages/datadog-instrumentations/src/webdriverio.js +5 -8
  82. package/packages/datadog-instrumentations/src/ws.js +1 -4
  83. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  84. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  85. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  86. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  87. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  89. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  90. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  91. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  92. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  93. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  94. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +4 -5
  95. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  96. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  97. package/packages/datadog-plugin-cypress/src/support.js +3 -4
  98. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  99. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  100. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  101. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  102. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  103. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  104. package/packages/datadog-plugin-mocha/src/index.js +7 -10
  105. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +8 -4
  106. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  107. package/packages/datadog-plugin-next/src/index.js +1 -2
  108. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  109. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  110. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  111. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  112. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  113. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  114. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  115. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  116. package/packages/datadog-webpack/index.js +55 -43
  117. package/packages/datadog-webpack/src/loader.js +56 -8
  118. package/packages/dd-trace/src/aiguard/evaluation.js +143 -1
  119. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  120. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  121. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  122. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  123. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  124. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  125. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  126. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  127. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  128. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  129. package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
  130. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  131. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  132. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  133. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  134. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  135. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  136. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  137. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  138. package/packages/dd-trace/src/appsec/stack_trace.js +4 -4
  139. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  140. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +3 -2
  141. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  142. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  143. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  144. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  145. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  146. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  147. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  148. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  149. package/packages/dd-trace/src/config/defaults.js +5 -5
  150. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  151. package/packages/dd-trace/src/config/helper.js +34 -3
  152. package/packages/dd-trace/src/config/index.js +55 -9
  153. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  154. package/packages/dd-trace/src/config/parsers.js +2 -2
  155. package/packages/dd-trace/src/config/supported-configurations.json +44 -0
  156. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  157. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  158. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +6 -5
  159. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -5
  160. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -0
  161. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +4 -6
  162. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  163. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  164. package/packages/dd-trace/src/debugger/unsupported-runtime.js +16 -0
  165. package/packages/dd-trace/src/dogstatsd.js +1 -1
  166. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  167. package/packages/dd-trace/src/exporters/common/request.js +1 -5
  168. package/packages/dd-trace/src/id.js +14 -1
  169. package/packages/dd-trace/src/lambda/handler.js +179 -19
  170. package/packages/dd-trace/src/lambda/index.js +15 -1
  171. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  172. package/packages/dd-trace/src/llmobs/agent-manifest.js +19 -2
  173. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  174. package/packages/dd-trace/src/llmobs/constants/tags.js +15 -0
  175. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  176. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  177. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  178. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  180. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  181. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +5 -0
  182. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +64 -2
  183. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  184. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  185. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  186. package/packages/dd-trace/src/llmobs/sdk.js +15 -4
  187. package/packages/dd-trace/src/llmobs/span_processor.js +13 -2
  188. package/packages/dd-trace/src/llmobs/tagger.js +20 -10
  189. package/packages/dd-trace/src/llmobs/util.js +11 -6
  190. package/packages/dd-trace/src/llmobs/writers/base.js +4 -5
  191. package/packages/dd-trace/src/log/log.js +1 -5
  192. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  193. package/packages/dd-trace/src/openfeature/flagging_provider.js +48 -21
  194. package/packages/dd-trace/src/openfeature/index.js +6 -0
  195. package/packages/dd-trace/src/openfeature/writers/util.js +4 -4
  196. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  199. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  200. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +24 -24
  201. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  202. package/packages/dd-trace/src/otel-sampling.js +4 -5
  203. package/packages/dd-trace/src/otel-thread-ctx.js +79 -13
  204. package/packages/dd-trace/src/plugin_manager.js +1 -2
  205. package/packages/dd-trace/src/plugins/database.js +7 -16
  206. package/packages/dd-trace/src/plugins/tracing.js +0 -1
  207. package/packages/dd-trace/src/plugins/util/git.js +99 -59
  208. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  209. package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -1
  210. package/packages/dd-trace/src/plugins/util/test.js +50 -4
  211. package/packages/dd-trace/src/plugins/util/web.js +90 -20
  212. package/packages/dd-trace/src/priority_sampler.js +1 -2
  213. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  214. package/packages/dd-trace/src/proxy.js +1 -2
  215. package/packages/dd-trace/src/ritm.js +4 -2
  216. package/packages/dd-trace/src/serverless.js +5 -0
  217. package/packages/dd-trace/src/span_processor.js +13 -17
  218. package/packages/dd-trace/src/span_stats.js +4 -1
  219. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  220. package/packages/dd-trace/src/telemetry/logs/log-collector.js +1 -1
  221. package/packages/dd-trace/src/util.js +37 -4
  222. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  223. package/vendor/dist/@apm-js-collab/code-transformer/index.js +8 -8
  224. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  225. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  226. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  227. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  228. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  229. package/vendor/dist/protobufjs/index.js +1 -1
  230. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  231. package/vendor/dist/shell-quote/index.js +1 -1
@@ -533,7 +533,7 @@ class DogStatsDClient {
533
533
  for (const [key, value] of Object.entries(config.tags)) {
534
534
  // Skip runtime-id unless enabled as cardinality may be too high
535
535
  if (typeof value === 'string' && (key !== 'runtime-id' || config.runtimeMetricsRuntimeId)) {
536
- // https://docs.datadoghq.com/tagging/#defining-tags
536
+ // https://docs.datadoghq.com/getting_started/tagging/#define-tags
537
537
  const valueStripped = value.replaceAll(/[^a-z0-9_:./-]/ig, '_')
538
538
 
539
539
  tags.push(`${key}:${valueStripped}`)
@@ -2,7 +2,7 @@
2
2
 
3
3
  // From agent truncators: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/agent/truncator.go
4
4
 
5
- // Values from: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/truncate.go#L22-L27
5
+ // Values from: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize/truncate.go
6
6
  // MAX_RESOURCE_NAME_LENGTH the maximum length a span resource can have
7
7
  const MAX_RESOURCE_NAME_LENGTH = 5000
8
8
  // MAX_META_KEY_LENGTH the maximum length of metadata key
@@ -15,7 +15,7 @@ const MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION = 5000
15
15
  const MAX_METRIC_KEY_LENGTH = MAX_META_KEY_LENGTH
16
16
 
17
17
  // From agent normalizer:
18
- // https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize.go
18
+ // https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize/normalize.go
19
19
  // DEFAULT_SPAN_NAME is the default name we assign a span if it's missing and we have no reasonable fallback
20
20
  const DEFAULT_SPAN_NAME = 'unnamed_operation'
21
21
  // DEFAULT_SERVICE_NAME is the default name we assign a service if it's missing and we have no reasonable fallback
@@ -86,11 +86,7 @@ function request (data, options, callback) {
86
86
  const timeout = options.timeout || 2000
87
87
  const isSecure = options.protocol === 'https:'
88
88
  const client = isSecure ? https : http
89
- let dataArray = data
90
-
91
- if (!Array.isArray(data)) {
92
- dataArray = [data]
93
- }
89
+ const dataArray = Array.isArray(data) ? data : [data]
94
90
  const contentLength = byteLength(dataArray)
95
91
  options.headers['Content-Length'] = contentLength
96
92
 
@@ -6,6 +6,8 @@ const UINT_MAX = 4_294_967_296
6
6
 
7
7
  const data = new Uint8Array(8 * 8192)
8
8
  const zeroId = new Uint8Array(8)
9
+ // Reused for hex encoding: toString('hex') copies out, so the bytes need not outlive the call.
10
+ const hexScratch = Buffer.alloc(16)
9
11
 
10
12
  let batch = 0
11
13
 
@@ -35,7 +37,7 @@ class Identifier {
35
37
  */
36
38
  toString (radix = 16) {
37
39
  if (radix === 16) {
38
- this.#stringHex ??= Buffer.from(this.#buffer).toString('hex')
40
+ this.#stringHex ??= toHexString(this.#buffer)
39
41
  return this.#stringHex
40
42
  }
41
43
  if (radix === 10) {
@@ -190,6 +192,17 @@ function toNumberString (buffer, radix) {
190
192
  return str
191
193
  }
192
194
 
195
+ // Same string as Buffer.from(buffer).toString('hex'), without allocating a Buffer per call.
196
+ /**
197
+ * @param {number[] | Uint8Array} buffer
198
+ */
199
+ function toHexString (buffer) {
200
+ const length = buffer.length
201
+ if (length > hexScratch.length) return Buffer.from(buffer).toString('hex')
202
+ for (let i = 0; i < length; i++) hexScratch[i] = buffer[i]
203
+ return hexScratch.toString('hex', 0, length)
204
+ }
205
+
193
206
  // Simple pseudo-random 64-bit ID generator.
194
207
  /**
195
208
  * @returns {number[] | Uint8Array}
@@ -1,20 +1,24 @@
1
1
  'use strict'
2
2
 
3
- const log = require('../log')
3
+ const { types: { isProxy } } = require('node:util')
4
+
4
5
  const { channel } = require('../../../datadog-instrumentations/src/helpers/instrument')
5
6
  const { ERROR_MESSAGE, ERROR_TYPE } = require('../constants')
7
+ const log = require('../log')
6
8
  const { ImpendingTimeout } = require('./runtime/errors')
7
9
  const { extractContext } = require('./context')
8
10
 
11
+ const HANDLER_STREAMING = Symbol.for('aws.lambda.runtime.handler.streaming')
12
+ const HANDLER_HIGHWATERMARK = Symbol.for('aws.lambda.runtime.handler.streaming.highWaterMark')
13
+ const BEFORE_EXIT = Symbol.for('aws.lambda.beforeExit')
9
14
  const timeoutChannel = channel('apm:aws:lambda:timeout')
15
+ const monitoredContexts = new WeakSet()
10
16
  // Always crash the flushes when a message is received
11
17
  // from this channel.
12
18
  timeoutChannel.subscribe(_ => {
13
19
  crashFlush()
14
20
  })
15
21
 
16
- let __lambdaTimeout
17
-
18
22
  /**
19
23
  * Publishes to the `apm:aws:lambda:timeout` channel when
20
24
  * the AWS Lambda run time is about to end.
@@ -23,11 +27,15 @@ let __lambdaTimeout
23
27
  */
24
28
  function checkTimeout (context) {
25
29
  const remainingTimeInMillis = context.getRemainingTimeInMillis()
26
- const apmFlushDeadline = global._ddtrace._tracer._config.DD_APM_FLUSH_DEADLINE_MILLISECONDS
30
+ const apmFlushDeadline = global._ddtrace?._tracer?._config?.DD_APM_FLUSH_DEADLINE_MILLISECONDS
31
+ if (!Number.isFinite(remainingTimeInMillis) || !Number.isFinite(apmFlushDeadline)) return
27
32
 
28
- __lambdaTimeout = setTimeout(() => {
33
+ const timeout = setTimeout(() => {
29
34
  timeoutChannel.publish()
30
- }, remainingTimeInMillis - apmFlushDeadline)
35
+ }, Math.max(0, remainingTimeInMillis - apmFlushDeadline))
36
+ // eslint-disable-next-line eslint-rules/eslint-timer-unref -- AWS Lambda uses Node.js timers.
37
+ timeout.unref()
38
+ return timeout
31
39
  }
32
40
 
33
41
  /**
@@ -58,26 +66,178 @@ function crashFlush () {
58
66
  }
59
67
 
60
68
  /**
61
- * Patches your AWS Lambda handler function to add some tracing support.
69
+ * Monitors an AWS Lambda handler for impending timeouts without creating a span.
62
70
  *
63
71
  * @param {Function} lambdaHandler a Lambda handler function.
72
+ * @param {boolean} normalized whether datadog-lambda-js owns completion selection.
64
73
  */
65
- exports.datadog = function datadog (lambdaHandler) {
66
- return (...args) => {
74
+ exports.datadog = function datadog (lambdaHandler, normalized = false) {
75
+ function monitored (...args) {
67
76
  const context = extractContext(args)
77
+ if (!context || monitoredContexts.has(context)) return Reflect.apply(lambdaHandler, this, args)
68
78
 
69
- if (context) {
70
- checkTimeout(context)
79
+ const timeout = checkTimeout(context)
80
+ if (timeout === undefined) return Reflect.apply(lambdaHandler, this, args)
81
+
82
+ let completed = false
83
+ let callbackPending = false
84
+ /** @type {Array<[object, string | symbol, Function, Function]>} */
85
+ const completionMethods = []
86
+ const clear = () => {
87
+ if (completed) return
88
+ completed = true
89
+ clearTimeout(timeout)
90
+ for (const [target, name, original, wrapped] of completionMethods) {
91
+ try {
92
+ if (target[name] === wrapped) Reflect.set(target, name, original)
93
+ } catch (error) {
94
+ log.error('Error restoring Lambda completion %s: %s', name, error)
95
+ }
96
+ }
97
+ }
98
+ const clearOnHandlerCompletion = normalized
99
+ ? clear
100
+ : () => {
101
+ // Once a native callback selects event-loop drain, later handler signals are ignored by RIC.
102
+ if (!callbackPending) clear()
103
+ }
104
+
105
+ const contextIndex = args.indexOf(context)
106
+ const callbackObservable = contextIndex !== 2 && typeof args[2] === 'function'
107
+ if (!normalized) {
108
+ for (const name of ['done', 'succeed', 'fail']) {
109
+ wrapCompletion(context, name, clearOnHandlerCompletion, completionMethods)
110
+ }
71
111
  }
112
+ // Legacy Lambda runtimes complete idle invocations through this callback instead of the handler's callback.
113
+ wrapCompletion(globalThis, BEFORE_EXIT, clear, completionMethods)
72
114
 
73
- const result = lambdaHandler.apply(this, args)
74
- if (result && typeof result.then === 'function') {
75
- return result.then((res) => {
76
- clearTimeout(__lambdaTimeout)
77
- return res
78
- })
115
+ if (callbackObservable) {
116
+ const callback = args[2]
117
+ const beforeExitObservable = completionMethods.at(-1)?.[1] === BEFORE_EXIT
118
+ let callbackInvoked = false
119
+ args[2] = function (...callbackArgs) {
120
+ if (completed || callbackInvoked) return Reflect.apply(callback, this, callbackArgs)
121
+ callbackInvoked = true
122
+ let beforeExit
123
+ try {
124
+ if (beforeExitObservable && callbackArgs[0] == null && context.callbackWaitsForEmptyEventLoop) {
125
+ beforeExit = getDataProperty(globalThis, BEFORE_EXIT)
126
+ }
127
+ } catch (error) {
128
+ log.error('Error reading Lambda callback completion: %s', error)
129
+ }
130
+ if (typeof beforeExit !== 'function') clear()
131
+ try {
132
+ const result = Reflect.apply(callback, this, callbackArgs)
133
+ if (!completed) {
134
+ // A successful legacy callback installs a new listener while waiting for event-loop drain.
135
+ const replacement = getDataProperty(globalThis, BEFORE_EXIT)
136
+ if (typeof replacement !== 'function' || replacement === beforeExit ||
137
+ !wrapCompletion(globalThis, BEFORE_EXIT, clear, completionMethods)) clear()
138
+ else callbackPending = true
139
+ }
140
+ return result
141
+ } catch (error) {
142
+ clear()
143
+ throw error
144
+ }
145
+ }
79
146
  }
80
- clearTimeout(__lambdaTimeout)
81
- return result
147
+
148
+ try {
149
+ // The outer completion adapter owns this context during synchronous dispatch to nested wrappers.
150
+ monitoredContexts.add(context)
151
+ const result = Reflect.apply(lambdaHandler, this, args)
152
+ if (result && typeof result.then === 'function') {
153
+ return result.then(value => {
154
+ clearOnHandlerCompletion()
155
+ return value
156
+ }, error => {
157
+ clearOnHandlerCompletion()
158
+ throw error
159
+ })
160
+ }
161
+ // Legacy RIC supplies a callback; newer runtimes complete from the return value.
162
+ if (!callbackObservable) clear()
163
+ return result
164
+ } catch (error) {
165
+ clearOnHandlerCompletion()
166
+ throw error
167
+ } finally {
168
+ monitoredContexts.delete(context)
169
+ }
170
+ }
171
+
172
+ if (normalized) return monitored
173
+
174
+ // datadog-lambda-js uses arity and the runtime's streaming metadata to choose how to invoke the handler.
175
+ Object.defineProperty(monitored, 'length', { value: getDataProperty(lambdaHandler, 'length') ?? 0 })
176
+ for (const name of [HANDLER_STREAMING, HANDLER_HIGHWATERMARK, '_ddWrapped']) {
177
+ const value = getDataProperty(lambdaHandler, name)
178
+ if (value !== undefined) monitored[name] = value
179
+ }
180
+ if (!isProxy(lambdaHandler)) {
181
+ for (const name of Reflect.ownKeys(lambdaHandler)) {
182
+ if (name === 'arguments' || name === 'caller') continue
183
+ const descriptor = Object.getOwnPropertyDescriptor(lambdaHandler, name)
184
+ if (!Object.hasOwn(descriptor, 'value')) continue
185
+ const existing = Object.getOwnPropertyDescriptor(monitored, name)
186
+ if (existing?.configurable === false) {
187
+ // Intrinsic function slots such as prototype cannot change their descriptor shape.
188
+ if (existing.writable) {
189
+ Object.defineProperty(monitored, name, { value: descriptor.value, writable: descriptor.writable })
190
+ }
191
+ } else {
192
+ Object.defineProperty(monitored, name, descriptor)
193
+ }
194
+ }
195
+ }
196
+ return monitored
197
+ }
198
+
199
+ /**
200
+ * @param {object} target
201
+ * @param {string | symbol} name
202
+ * @param {() => void} clear
203
+ * @param {Array<[object, string | symbol, Function, Function]>} completionMethods
204
+ */
205
+ function wrapCompletion (target, name, clear, completionMethods) {
206
+ try {
207
+ const original = target[name]
208
+ if (typeof original !== 'function') return true
209
+ const wrapped = onCompletion(original, clear)
210
+ if (!Reflect.set(target, name, wrapped) || target[name] !== wrapped) return false
211
+ completionMethods.push([target, name, original, wrapped])
212
+ return true
213
+ } catch (error) {
214
+ log.error('Error wrapping Lambda completion %s: %s', name, error)
215
+ return false
216
+ }
217
+ }
218
+
219
+ /**
220
+ * @param {Function} callback
221
+ * @param {() => void} clear
222
+ */
223
+ function onCompletion (callback, clear) {
224
+ return function (...args) {
225
+ clear()
226
+ return Reflect.apply(callback, this, args)
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Reads metadata without executing application getters or Proxy traps.
232
+ *
233
+ * @param {unknown} target
234
+ * @param {string | symbol} name
235
+ */
236
+ function getDataProperty (target, name) {
237
+ if (target === null || (typeof target !== 'object' && typeof target !== 'function')) return
238
+ while (target !== null && !isProxy(target)) {
239
+ const descriptor = Object.getOwnPropertyDescriptor(target, name)
240
+ if (descriptor) return descriptor.value
241
+ target = Object.getPrototypeOf(target)
82
242
  }
83
243
  }
@@ -41,7 +41,6 @@ if (!getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')?.split(',').in
41
41
 
42
42
  return moduleExports
43
43
  })
44
- return
45
44
  }
46
45
 
47
46
  const moduleToPatch = 'datadog-lambda-js'
@@ -62,4 +61,19 @@ if (!getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')?.split(',').in
62
61
 
63
62
  return moduleExports
64
63
  })
64
+
65
+ // The published datadog-lambda-js ESM entrypoint imports this helper before initializing the tracer.
66
+ // Revisit cached copies through the registered hook without loading datadog-lambda-js early.
67
+ const helperSuffixes = [
68
+ path.join('datadog-lambda-js', 'dist', 'utils', 'handler.js'),
69
+ path.join('datadog-lambda-js', 'utils', 'handler.js'),
70
+ ]
71
+ for (const filename of Object.keys(require.cache)) {
72
+ if (!helperSuffixes.some(suffix => filename.endsWith(path.sep + suffix))) continue
73
+ try {
74
+ require(filename)
75
+ } catch (error) {
76
+ log.error('Error executing cached lambda hook for datadog-lambda-js: %s', error)
77
+ }
78
+ }
65
79
  }
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const path = require('path')
4
+ const { types: { isModuleNamespaceObject } } = require('node:util')
4
5
 
5
6
  const { datadog } = require('../handler')
6
7
  const { addHook } = require('../../../../datadog-instrumentations/src/helpers/instrument')
@@ -23,22 +24,48 @@ function patchDatadogLambdaHandler (datadogHandler) {
23
24
  return userHandler => datadogHandler(datadog(userHandler))
24
25
  }
25
26
 
27
+ /**
28
+ * Wraps datadog-lambda-js's promisifiedHandler factory, which wraps the customer's handler.
29
+ * The timeout monitor surrounds that adapter to observe the completion Promise selected by datadog-lambda-js.
30
+ * Legacy RIC completes returned Promises through context.succeed without waiting for event-loop drain.
31
+ *
32
+ * @param {object} moduleExports
33
+ */
34
+ function patchPromisifiedHandlerModule (moduleExports) {
35
+ shimmer.wrap(moduleExports, 'promisifiedHandler', promisify => {
36
+ return handler => datadog(promisify(handler), true)
37
+ })
38
+ return moduleExports
39
+ }
40
+
26
41
  /** @param {string} handlerPath */
27
42
  function patchLambdaModule (handlerPath) {
28
43
  return lambdaModule => {
29
- shimmer.wrap(lambdaModule, handlerPath, patchLambdaHandler)
30
- return lambdaModule
44
+ const descriptor = Object.getOwnPropertyDescriptor(lambdaModule, handlerPath)
45
+ if (typeof descriptor?.value === 'function') {
46
+ if (!descriptor.configurable && !descriptor.writable) return lambdaModule
47
+ // The monitor copies Lambda metadata without invoking application accessors or Proxy traps.
48
+ // Generic shimmer wrapping would copy the original properties back onto that safe wrapper.
49
+ descriptor.value = datadog(descriptor.value)
50
+ // Native ESM namespaces reject replacement even when their export reports writable: true.
51
+ const exports = isModuleNamespaceObject(lambdaModule)
52
+ ? Object.create(lambdaModule, Object.getOwnPropertyDescriptors(lambdaModule))
53
+ : lambdaModule
54
+ Object.defineProperty(exports, handlerPath, descriptor)
55
+ return exports
56
+ }
57
+ return shimmer.wrap(lambdaModule, handlerPath, datadog)
31
58
  }
32
59
  }
33
60
 
34
- /** @param {Function} lambdaHandler */
35
- function patchLambdaHandler (lambdaHandler) {
36
- return datadog(lambdaHandler)
37
- }
38
-
39
61
  const lambdaTaskRoot = getEnvironmentVariable('LAMBDA_TASK_ROOT')
40
62
  const originalLambdaHandler = getValueFromEnvSources('DD_LAMBDA_HANDLER')
41
63
 
64
+ // Observe the completion Promise selected by datadog-lambda-js inside the invocation span's async scope.
65
+ // The npm package keeps dist/; Lambda layer builds place its contents directly in the package root.
66
+ addHook({ name: 'datadog-lambda-js', file: 'dist/utils/handler.js' }, patchPromisifiedHandlerModule)
67
+ addHook({ name: 'datadog-lambda-js', file: 'utils/handler.js' }, patchPromisifiedHandlerModule)
68
+
42
69
  if (originalLambdaHandler === undefined) {
43
70
  addHook({ name: 'datadog-lambda-js' }, patchDatadogLambdaModule)
44
71
  } else {
@@ -35,7 +35,7 @@ const LABEL_FIELDS = ['name', 'instructions', 'model']
35
35
  * model_settings?: Record<string, unknown>,
36
36
  * tools?: AgentTool[],
37
37
  * }} AgentManifestFields
38
- * @typedef {{ manifest: AgentManifestFields }} AgentDeclaration
38
+ * @typedef {{ version?: string, manifest?: AgentManifestFields }} AgentDeclaration
39
39
  */
40
40
 
41
41
  /**
@@ -50,8 +50,9 @@ function buildAgentDeclaration (agent) {
50
50
  let declaration
51
51
  try {
52
52
  if (isPlainObject(agent)) {
53
+ const version = toAgentVersion(agent.version)
53
54
  const manifest = buildAgentManifest(agent)
54
- if (manifest !== undefined) declaration = { manifest }
55
+ if (version !== undefined || manifest !== undefined) declaration = { version, manifest }
55
56
  } else {
56
57
  log.warn('Dropping agent annotation, the agent must be a plain object.')
57
58
  }
@@ -105,6 +106,21 @@ function buildAgentManifest (agent) {
105
106
  return manifest
106
107
  }
107
108
 
109
+ /**
110
+ * Unset values (`undefined`, `null`, `''`) are ignored without a warning, so they leave an earlier version in place.
111
+ * A comma is rejected because intake splits tags on it.
112
+ *
113
+ * @param {unknown} version
114
+ */
115
+ function toAgentVersion (version) {
116
+ if (typeof version === 'string' && version !== '') {
117
+ if (!version.includes(',')) return version
118
+ log.warn('Dropping the agent version, it must not contain a comma.')
119
+ return
120
+ }
121
+ if (!isUnset(version)) log.warn('Dropping the agent version, it must be a string.')
122
+ }
123
+
108
124
  /**
109
125
  * @param {unknown} settings
110
126
  * @param {string[]} dropped
@@ -293,4 +309,5 @@ function isScalar (value) {
293
309
  module.exports = {
294
310
  MANUAL_FRAMEWORK_NAME,
295
311
  buildAgentDeclaration,
312
+ toAgentVersion,
296
313
  }
@@ -106,8 +106,9 @@ function fitsInlineAudioBudget (byteLength, maxBytes = LLMOBS_AUDIO_INLINE_MAX_B
106
106
  * @returns {AudioPart | undefined}
107
107
  */
108
108
  function formatAudioPartWithGuard (audioBytes, mimeType, maxBytes = LLMOBS_AUDIO_INLINE_MAX_BYTES) {
109
- if (!audioBytes?.length || !isRenderableAudioMime(mimeType)) return
110
- if (!fitsInlineAudioBudget(audioBytes.length, maxBytes)) return
109
+ if (!audioBytes?.length || !isRenderableAudioMime(mimeType) || !fitsInlineAudioBudget(audioBytes.length, maxBytes)) {
110
+ return
111
+ }
111
112
 
112
113
  return formatAudioPart(audioBytes, mimeType)
113
114
  }
@@ -7,7 +7,11 @@ const CACHE_READ_INPUT_TOKENS_METRIC_KEY = 'cache_read_input_tokens'
7
7
  const CACHE_WRITE_INPUT_TOKENS_METRIC_KEY = 'cache_write_input_tokens'
8
8
  const CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY = 'ephemeral_5m_input_tokens'
9
9
  const CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY = 'ephemeral_1h_input_tokens'
10
+ const CACHE_WRITE_30M_INPUT_TOKENS_METRIC_KEY = 'ephemeral_30m_input_tokens'
11
+ const CACHE_WRITE_12H_INPUT_TOKENS_METRIC_KEY = 'ephemeral_12h_input_tokens'
10
12
  const REASONING_OUTPUT_TOKENS_METRIC_KEY = 'reasoning_output_tokens'
13
+ const WEB_SEARCH_COUNT_METRIC_KEY = 'web_search_count'
14
+ const STORAGE_SEARCH_COUNT_METRIC_KEY = 'storage_search_count'
11
15
 
12
16
  module.exports = {
13
17
  SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval', 'experiment'],
@@ -19,6 +23,8 @@ module.exports = {
19
23
  METADATA: '_ml_obs.meta.metadata',
20
24
  COST_TAGS: '_ml_obs.meta.metadata._dd.cost_tags',
21
25
  AGENT_MANIFEST: '_ml_obs.meta.metadata._dd.agent_manifest',
26
+ AGENT_VERSION: '_ml_obs.agent_version',
27
+ AGENT_VERSION_TAG_KEY: 'agent_version',
22
28
  METRICS: '_ml_obs.metrics',
23
29
  TOOL_DEFINITIONS: '_ml_obs.meta.tool_definitions',
24
30
  ML_APP: '_ml_obs.meta.ml_app',
@@ -29,6 +35,7 @@ module.exports = {
29
35
 
30
36
  PARENT_AGENT_NAME: '_ml_obs.parent_agent_name',
31
37
  PARENT_AGENT_SPAN_ID: '_ml_obs.parent_agent_span_id',
38
+ PARENT_AGENT_VERSION: '_ml_obs.parent_agent_version',
32
39
  PROPAGATED_PARENT_AGENT_ID_KEY: '_dd.p.llmobs_pagent_span_id',
33
40
  PROPAGATED_PARENT_AGENT_NAME_KEY: '_dd.p.llmobs_pagent_name',
34
41
  PROPAGATED_SAMPLE_RATE_KEY: '_dd.p.llmobs_sr',
@@ -72,7 +79,11 @@ module.exports = {
72
79
  CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
73
80
  CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
74
81
  CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
82
+ CACHE_WRITE_30M_INPUT_TOKENS_METRIC_KEY,
83
+ CACHE_WRITE_12H_INPUT_TOKENS_METRIC_KEY,
75
84
  REASONING_OUTPUT_TOKENS_METRIC_KEY,
85
+ WEB_SEARCH_COUNT_METRIC_KEY,
86
+ STORAGE_SEARCH_COUNT_METRIC_KEY,
76
87
 
77
88
  // integrations build metric objects with these camelCase spellings. Null prototype: a metric
78
89
  // named after an `Object.prototype` member must not resolve to an inherited property.
@@ -84,7 +95,11 @@ module.exports = {
84
95
  cacheWriteTokens: CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
85
96
  cacheWrite5mTokens: CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
86
97
  cacheWrite1hTokens: CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
98
+ cacheWrite30mTokens: CACHE_WRITE_30M_INPUT_TOKENS_METRIC_KEY,
99
+ cacheWrite12hTokens: CACHE_WRITE_12H_INPUT_TOKENS_METRIC_KEY,
87
100
  reasoningOutputTokens: REASONING_OUTPUT_TOKENS_METRIC_KEY,
101
+ webSearchCount: WEB_SEARCH_COUNT_METRIC_KEY,
102
+ storageSearchCount: STORAGE_SEARCH_COUNT_METRIC_KEY,
88
103
  }),
89
104
 
90
105
  DROPPED_IO_COLLECTION_ERROR: 'dropped_io',
@@ -253,7 +253,7 @@ class Experiment {
253
253
  this.#external = options.external === true
254
254
  if (!this.#external) {
255
255
  if (!options.dataset) throw new Error('Experiment dataset is required')
256
- if (typeof options.task !== 'function') throw new Error('Experiment task is required')
256
+ if (typeof options.task !== 'function') throw new TypeError('Experiment task is required')
257
257
  }
258
258
 
259
259
  this.#client = client
@@ -239,11 +239,19 @@ function mergeChunkUsage (usage, chunk) {
239
239
  const mergedUsage = usage ?? { input_tokens: 0, output_tokens: 0 }
240
240
  mergedUsage.output_tokens = chunk.usage.output_tokens
241
241
 
242
+ // `message_delta` counts are cumulative; input grows past the `message_start` count when server
243
+ // tools (e.g. web search) feed results back into the model
244
+ const inputTokens = chunk.usage.input_tokens
245
+ if (inputTokens != null) mergedUsage.input_tokens = inputTokens
246
+
242
247
  const cacheCreationTokens = chunk.usage.cache_creation_input_tokens
243
248
  const cacheReadTokens = chunk.usage.cache_read_input_tokens
244
249
  if (cacheCreationTokens) mergedUsage.cache_creation_input_tokens = cacheCreationTokens
245
250
  if (cacheReadTokens) mergedUsage.cache_read_input_tokens = cacheReadTokens
246
251
 
252
+ const serverToolUse = chunk.usage.server_tool_use
253
+ if (serverToolUse) mergedUsage.server_tool_use = serverToolUse
254
+
247
255
  return mergedUsage
248
256
  }
249
257
 
@@ -280,6 +288,13 @@ function extractUsage (result) {
280
288
  metrics.cacheWrite1hTokens = 0
281
289
  }
282
290
 
291
+ // Read the usage counter, not server_tool_use blocks: dynamic filtering nests searches under code
292
+ // execution, and `response_inclusion: "excluded"` drops the blocks while the searches are still billed.
293
+ // Failed searches aren't billed and are already excluded. web_fetch_requests is skipped because web
294
+ // fetch has no per-call price.
295
+ const webSearchRequests = usage.server_tool_use?.web_search_requests
296
+ if (webSearchRequests > 0) metrics.webSearchCount = webSearchRequests
297
+
283
298
  return metrics
284
299
  }
285
300
 
@@ -84,8 +84,7 @@ function determineRole (candidate, parts = []) {
84
84
  * @param {string} role
85
85
  */
86
86
  function normalizeRole (role) {
87
- if (role === ROLES.MODEL) return ROLES.ASSISTANT
88
- if (role === ROLES.ASSISTANT) return ROLES.ASSISTANT
87
+ if (role === ROLES.MODEL || role === ROLES.ASSISTANT) return ROLES.ASSISTANT
89
88
  if (role === ROLES.USER) return ROLES.USER
90
89
  if (role === ROLES.REASONING) return ROLES.REASONING
91
90
  return ROLES.USER // default
@@ -62,11 +62,9 @@ class LangChainLLMObsChatModelHandler extends LangChainLLMObsHandler {
62
62
  */
63
63
  getTokenUsage (results) {
64
64
  const tokens = this.checkTokenUsageChatOrLLMResult(results)
65
- if (tokens.totalTokens > 0) return tokens
66
-
67
65
  // providers that report usage on each generated message instead of on `llmOutput`; counts are
68
66
  // summed per run so a run split across generations is totalled once
69
- if (!results.generations) return tokens
67
+ if (tokens.totalTokens > 0 || !results.generations) return tokens
70
68
 
71
69
  const tokensPerRunId = {}
72
70
  for (const messageSet of results.generations) {
@@ -76,7 +76,7 @@ class NextStreamLLMObsPlugin extends LLMObsPlugin {
76
76
  const { streamInputs: inputs, chunks } = streamData
77
77
  const input = inputs == null ? undefined : formatIO(inputs)
78
78
  const lastChunk = chunks.length > 0 ? chunks.at(-1) : undefined
79
- const output = !hasError && lastChunk != null ? formatIO(lastChunk) : undefined
79
+ const output = hasError || lastChunk == null ? undefined : formatIO(lastChunk)
80
80
 
81
81
  this._tagger.tagTextIO(span, input, output)
82
82