dd-trace 6.20.1 → 6.22.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 (225) 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-setup.mjs +10 -7
  16. package/index.d.ts +116 -17
  17. package/initialize.mjs +1 -3
  18. package/openfeature/rules-based.d.ts +11 -0
  19. package/openfeature/rules-based.js +7 -0
  20. package/package.json +13 -11
  21. package/packages/datadog-core/src/storage.js +16 -0
  22. package/packages/datadog-esbuild/index.js +18 -12
  23. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  24. package/packages/datadog-instrumentations/src/ai.js +3 -3
  25. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  26. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  27. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  28. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  29. package/packages/datadog-instrumentations/src/cucumber.js +48 -33
  30. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  31. package/packages/datadog-instrumentations/src/electron.js +6 -6
  32. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  33. package/packages/datadog-instrumentations/src/fs.js +2 -2
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  35. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  36. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
  37. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  38. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  39. package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
  40. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  41. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
  42. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  48. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  49. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  50. package/packages/datadog-instrumentations/src/jest.js +122 -11
  51. package/packages/datadog-instrumentations/src/knex.js +9 -5
  52. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  53. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  54. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  55. package/packages/datadog-instrumentations/src/mocha/utils.js +127 -38
  56. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
  57. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  58. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  59. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  60. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  61. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  62. package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
  63. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  64. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  65. package/packages/datadog-instrumentations/src/openai.js +7 -0
  66. package/packages/datadog-instrumentations/src/pino.js +5 -6
  67. package/packages/datadog-instrumentations/src/playwright.js +3 -0
  68. package/packages/datadog-instrumentations/src/redis.js +27 -13
  69. package/packages/datadog-instrumentations/src/router.js +30 -0
  70. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  71. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
  72. package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
  73. package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
  74. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  75. package/packages/datadog-instrumentations/src/vitest.js +2 -2
  76. package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
  77. package/packages/datadog-instrumentations/src/ws.js +1 -4
  78. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  79. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  80. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  81. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  82. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  83. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  84. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  85. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  86. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  87. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  88. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  89. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -5
  90. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  91. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  92. package/packages/datadog-plugin-cypress/src/support.js +1 -4
  93. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  94. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  95. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  96. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  97. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +223 -39
  100. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
  101. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  102. package/packages/datadog-plugin-next/src/index.js +1 -2
  103. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  104. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  105. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  106. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  107. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  108. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  109. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  110. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  111. package/packages/datadog-webpack/index.js +55 -43
  112. package/packages/datadog-webpack/src/loader.js +56 -8
  113. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  114. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  115. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  116. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  117. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  118. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  119. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  120. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  121. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  122. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  123. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  125. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  126. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  127. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  128. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  129. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  130. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  131. package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
  132. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  135. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  137. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  138. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  139. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  140. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  141. package/packages/dd-trace/src/config/defaults.js +5 -5
  142. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
  143. package/packages/dd-trace/src/config/helper.js +34 -3
  144. package/packages/dd-trace/src/config/index.js +10 -9
  145. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  146. package/packages/dd-trace/src/config/parsers.js +2 -2
  147. package/packages/dd-trace/src/config/supported-configurations.json +35 -0
  148. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  149. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  150. package/packages/dd-trace/src/debugger/constants.js +1 -0
  151. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
  152. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
  153. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
  154. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  155. package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
  156. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  157. package/packages/dd-trace/src/debugger/index.js +24 -2
  158. package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
  159. package/packages/dd-trace/src/dogstatsd.js +1 -1
  160. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  161. package/packages/dd-trace/src/exporters/common/request.js +2 -5
  162. package/packages/dd-trace/src/exporters/common/url.js +1 -1
  163. package/packages/dd-trace/src/id.js +15 -5
  164. package/packages/dd-trace/src/lambda/handler.js +179 -19
  165. package/packages/dd-trace/src/lambda/index.js +15 -1
  166. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  167. package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
  168. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  169. package/packages/dd-trace/src/llmobs/constants/tags.js +13 -0
  170. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  171. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  172. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  173. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  174. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  175. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  176. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
  177. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  178. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  179. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  180. package/packages/dd-trace/src/llmobs/sdk.js +23 -6
  181. package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
  182. package/packages/dd-trace/src/llmobs/tagger.js +56 -3
  183. package/packages/dd-trace/src/llmobs/util.js +1 -2
  184. package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
  185. package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
  186. package/packages/dd-trace/src/log/log.js +1 -5
  187. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
  188. package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
  189. package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
  190. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  191. package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
  192. package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
  193. package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
  194. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  195. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  196. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  197. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  198. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
  199. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
  200. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  201. package/packages/dd-trace/src/otel-sampling.js +4 -5
  202. package/packages/dd-trace/src/otel-thread-ctx.js +81 -15
  203. package/packages/dd-trace/src/plugins/database.js +7 -16
  204. package/packages/dd-trace/src/plugins/util/git.js +9 -25
  205. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  206. package/packages/dd-trace/src/plugins/util/test.js +48 -4
  207. package/packages/dd-trace/src/priority_sampler.js +1 -2
  208. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  209. package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
  210. package/packages/dd-trace/src/proxy.js +3 -2
  211. package/packages/dd-trace/src/ritm.js +4 -2
  212. package/packages/dd-trace/src/serverless.js +10 -0
  213. package/packages/dd-trace/src/span_processor.js +13 -17
  214. package/packages/dd-trace/src/storage-channels.js +112 -46
  215. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  216. package/packages/dd-trace/src/util.js +29 -0
  217. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  218. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  219. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  220. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  221. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  222. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  223. package/vendor/dist/protobufjs/index.js +1 -1
  224. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  225. package/vendor/dist/shell-quote/index.js +1 -1
@@ -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 {
@@ -0,0 +1,296 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+
5
+ // Set by the SDK so a consumer can tell a hand-declared manifest from one read off a framework object.
6
+ const MANUAL_FRAMEWORK_NAME = 'manual'
7
+
8
+ // Provider-specific keys such as `extra_headers` can carry secrets, so only these are reported.
9
+ const ALLOWED_MODEL_SETTINGS_KEYS = new Set([
10
+ 'frequency_penalty',
11
+ 'logit_bias',
12
+ 'logprobs',
13
+ 'max_tokens',
14
+ 'parallel_tool_calls',
15
+ 'presence_penalty',
16
+ 'seed',
17
+ 'stop_sequences',
18
+ 'temperature',
19
+ 'timeout',
20
+ 'tool_choice',
21
+ 'top_k',
22
+ 'top_logprobs',
23
+ 'top_p',
24
+ ])
25
+
26
+ const LABEL_FIELDS = ['name', 'instructions', 'model']
27
+
28
+ /**
29
+ * @typedef {{ type?: string, required?: true }} AgentToolParameter
30
+ * @typedef {{ name: string, description?: string, parameters?: Record<string, AgentToolParameter> }} AgentTool
31
+ * @typedef {{
32
+ * name?: string,
33
+ * instructions?: string,
34
+ * model?: string,
35
+ * model_settings?: Record<string, unknown>,
36
+ * tools?: AgentTool[],
37
+ * }} AgentManifestFields
38
+ * @typedef {{ manifest: AgentManifestFields }} AgentDeclaration
39
+ */
40
+
41
+ /**
42
+ * Reads the agent a caller declared through `annotate({ agent })` or `annotationContext({ agent })` into an
43
+ * immutable snapshot. Never throws, since the caller's object may carry throwing getters or be a revoked Proxy,
44
+ * and this runs while an LLMObs span is being registered.
45
+ *
46
+ * @param {unknown} agent
47
+ * @returns {AgentDeclaration | undefined} undefined when the agent declares nothing reportable
48
+ */
49
+ function buildAgentDeclaration (agent) {
50
+ let declaration
51
+ try {
52
+ if (isPlainObject(agent)) {
53
+ const manifest = buildAgentManifest(agent)
54
+ if (manifest !== undefined) declaration = { manifest }
55
+ } else {
56
+ log.warn('Dropping agent annotation, the agent must be a plain object.')
57
+ }
58
+ } catch {
59
+ log.warn('Dropping agent annotation, its fields could not be read.')
60
+ }
61
+ return declaration
62
+ }
63
+
64
+ /**
65
+ * Validates each manifest field on its own, so one bad field cannot blank the rest. Unset values (`undefined`,
66
+ * `null`, `''`, `[]`, `{}`) are omitted so they declare nothing rather than erase an earlier declaration. Fields
67
+ * that are set but unreportable are dropped and logged by name, never by value.
68
+ *
69
+ * @param {Record<string, unknown>} agent
70
+ * @returns {AgentManifestFields | undefined}
71
+ */
72
+ function buildAgentManifest (agent) {
73
+ /** @type {string[]} */
74
+ const dropped = []
75
+ /** @type {AgentManifestFields | undefined} */
76
+ let manifest
77
+
78
+ for (const key of LABEL_FIELDS) {
79
+ const value = agent[key]
80
+ if (isUnset(value)) continue
81
+ if (typeof value === 'string') {
82
+ manifest ??= {}
83
+ manifest[key] = value
84
+ } else {
85
+ dropped.push(key)
86
+ }
87
+ }
88
+
89
+ const modelSettings = buildModelSettings(agent.modelSettings, dropped)
90
+ if (modelSettings) {
91
+ manifest ??= {}
92
+ manifest.model_settings = modelSettings
93
+ }
94
+
95
+ const tools = buildTools(agent.tools, dropped)
96
+ if (tools) {
97
+ manifest ??= {}
98
+ manifest.tools = tools
99
+ }
100
+
101
+ if (dropped.length > 0) {
102
+ log.warn('Dropping unsupported agent manifest fields: %s', dropped.join(', '))
103
+ }
104
+
105
+ return manifest
106
+ }
107
+
108
+ /**
109
+ * @param {unknown} settings
110
+ * @param {string[]} dropped
111
+ * @returns {Record<string, unknown> | undefined}
112
+ */
113
+ function buildModelSettings (settings, dropped) {
114
+ if (isUnset(settings)) return
115
+ if (!isPlainObject(settings)) {
116
+ dropped.push('modelSettings')
117
+ return
118
+ }
119
+
120
+ let allowed
121
+ for (const [key, value] of Object.entries(settings)) {
122
+ const wireKey = toSnakeCase(key)
123
+ if (!ALLOWED_MODEL_SETTINGS_KEYS.has(wireKey)) {
124
+ dropped.push(`modelSettings.${key}`)
125
+ continue
126
+ }
127
+ if (isUnset(value)) continue
128
+ const wireValue = toFlatScalarValue(value)
129
+ if (wireValue === null) continue
130
+ if (wireValue === undefined) {
131
+ dropped.push(`modelSettings.${key}`)
132
+ continue
133
+ }
134
+ allowed ??= {}
135
+ allowed[wireKey] = wireValue
136
+ }
137
+ return allowed
138
+ }
139
+
140
+ /**
141
+ * A JSON scalar, a flat array of scalars, or a flat object of numbers (`logit_bias`). No allowed setting nests, so
142
+ * anything deeper is dropped rather than coerced. Returns undefined for a value that cannot be reported, and null
143
+ * for an empty object, which declares nothing.
144
+ *
145
+ * @param {unknown} value
146
+ */
147
+ function toFlatScalarValue (value) {
148
+ if (isScalar(value)) return value
149
+ if (Array.isArray(value)) {
150
+ const items = [...value]
151
+ for (const item of items) {
152
+ if (!isScalar(item)) return
153
+ }
154
+ return items
155
+ }
156
+ if (!isPlainObject(value)) return
157
+
158
+ const entries = Object.entries(value)
159
+ if (entries.length === 0) return null
160
+ for (const [, item] of entries) {
161
+ if (!isFiniteNumber(item)) return
162
+ }
163
+ // Built from entries so a key such as `__proto__` becomes an own property rather than setting the prototype.
164
+ return Object.fromEntries(entries)
165
+ }
166
+
167
+ /**
168
+ * @param {unknown} declared
169
+ * @param {string[]} dropped
170
+ * @returns {AgentTool[] | undefined}
171
+ */
172
+ function buildTools (declared, dropped) {
173
+ if (isUnset(declared)) return
174
+ if (!Array.isArray(declared)) {
175
+ dropped.push('tools')
176
+ return
177
+ }
178
+
179
+ const tools = []
180
+ for (let index = 0; index < declared.length; index++) {
181
+ const tool = declared[index]
182
+ const name = isPlainObject(tool) ? tool.name : undefined
183
+ // An unnamed tool is unidentifiable once it ships.
184
+ if (typeof name !== 'string' || name === '') {
185
+ dropped.push(`tools[${index}]`)
186
+ continue
187
+ }
188
+
189
+ /** @type {AgentTool} */
190
+ const built = { name }
191
+ const description = tool.description
192
+ if (typeof description === 'string' && description !== '') built.description = description
193
+ const parameters = buildToolParameters(tool.parameters)
194
+ if (parameters === null) {
195
+ dropped.push(`tools[${index}].parameters`)
196
+ } else if (parameters) {
197
+ built.parameters = parameters
198
+ }
199
+ tools.push(built)
200
+ }
201
+ return tools.length > 0 ? tools : undefined
202
+ }
203
+
204
+ /**
205
+ * Flattens tool parameters to `{ param: { type?, required? } }`, the shape the Python framework integrations emit.
206
+ * `required` is omitted rather than reported false. Returns null for parameters that are set but not a plain
207
+ * object, such as a schema-library instance, so the caller can report them as dropped.
208
+ *
209
+ * @param {unknown} parameters
210
+ * @returns {Record<string, AgentToolParameter> | null | undefined}
211
+ */
212
+ function buildToolParameters (parameters) {
213
+ if (isUnset(parameters)) return
214
+ if (!isPlainObject(parameters)) return null
215
+
216
+ const { specs, required } = toolParameterSpecs(parameters)
217
+ const flattened = []
218
+ for (const [param, spec] of Object.entries(specs)) {
219
+ /** @type {AgentToolParameter} */
220
+ const entry = {}
221
+ if (isPlainObject(spec)) {
222
+ const { type } = spec
223
+ if (typeof type === 'string' && type !== '') entry.type = type
224
+ if (spec.required === true) entry.required = true
225
+ }
226
+ if (required?.has(param)) entry.required = true
227
+ if (entry.type === undefined && entry.required === undefined) continue
228
+ flattened.push([param, entry])
229
+ }
230
+ // Built from entries so a parameter named `__proto__` becomes an own property rather than setting the prototype.
231
+ return flattened.length > 0 ? Object.fromEntries(flattened) : undefined
232
+ }
233
+
234
+ /**
235
+ * Accepts a JSON Schema object as well as the documented `{ param: { type, required } }` mapping, since a JSON
236
+ * Schema is what OpenAI, Gemini and MCP tool definitions carry. Two markers are required rather than `properties`
237
+ * alone, so a mapping that declares a parameter named `properties` is still read as a mapping.
238
+ *
239
+ * @param {Record<string, unknown>} parameters
240
+ * @returns {{ specs: Record<string, unknown>, required?: Set<unknown> }}
241
+ */
242
+ function toolParameterSpecs (parameters) {
243
+ const { properties, required } = parameters
244
+ const hasRequiredList = Array.isArray(required)
245
+ if (!isPlainObject(properties) || !(parameters.type === 'object' || hasRequiredList)) {
246
+ return { specs: parameters }
247
+ }
248
+ return { specs: properties, required: hasRequiredList ? new Set(required) : undefined }
249
+ }
250
+
251
+ /**
252
+ * @param {string} key
253
+ */
254
+ function toSnakeCase (key) {
255
+ return key.replaceAll(/[A-Z]/g, letter => `_${letter.toLowerCase()}`)
256
+ }
257
+
258
+ /**
259
+ * Class instances are rejected so a schema-library object (Zod, the AI SDK `jsonSchema()` wrapper) is dropped rather
260
+ * than read field by field into a misleading manifest.
261
+ *
262
+ * @param {unknown} value
263
+ * @returns {value is Record<string, unknown>}
264
+ */
265
+ function isPlainObject (value) {
266
+ if (value === null || typeof value !== 'object') return false
267
+ const prototype = Object.getPrototypeOf(value)
268
+ return prototype === Object.prototype || prototype === null
269
+ }
270
+
271
+ /**
272
+ * @param {unknown} value
273
+ */
274
+ function isUnset (value) {
275
+ if (value === undefined || value === null || value === '') return true
276
+ return Array.isArray(value) && value.length === 0
277
+ }
278
+
279
+ /**
280
+ * @param {unknown} value
281
+ */
282
+ function isFiniteNumber (value) {
283
+ return typeof value === 'number' && Number.isFinite(value)
284
+ }
285
+
286
+ /**
287
+ * @param {unknown} value
288
+ */
289
+ function isScalar (value) {
290
+ return typeof value === 'string' || typeof value === 'boolean' || isFiniteNumber(value)
291
+ }
292
+
293
+ module.exports = {
294
+ MANUAL_FRAMEWORK_NAME,
295
+ buildAgentDeclaration,
296
+ }
@@ -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'],
@@ -18,6 +22,7 @@ module.exports = {
18
22
  INTEGRATION: '_ml_obs.integration',
19
23
  METADATA: '_ml_obs.meta.metadata',
20
24
  COST_TAGS: '_ml_obs.meta.metadata._dd.cost_tags',
25
+ AGENT_MANIFEST: '_ml_obs.meta.metadata._dd.agent_manifest',
21
26
  METRICS: '_ml_obs.metrics',
22
27
  TOOL_DEFINITIONS: '_ml_obs.meta.tool_definitions',
23
28
  ML_APP: '_ml_obs.meta.ml_app',
@@ -71,7 +76,11 @@ module.exports = {
71
76
  CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
72
77
  CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
73
78
  CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
79
+ CACHE_WRITE_30M_INPUT_TOKENS_METRIC_KEY,
80
+ CACHE_WRITE_12H_INPUT_TOKENS_METRIC_KEY,
74
81
  REASONING_OUTPUT_TOKENS_METRIC_KEY,
82
+ WEB_SEARCH_COUNT_METRIC_KEY,
83
+ STORAGE_SEARCH_COUNT_METRIC_KEY,
75
84
 
76
85
  // integrations build metric objects with these camelCase spellings. Null prototype: a metric
77
86
  // named after an `Object.prototype` member must not resolve to an inherited property.
@@ -83,7 +92,11 @@ module.exports = {
83
92
  cacheWriteTokens: CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
84
93
  cacheWrite5mTokens: CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
85
94
  cacheWrite1hTokens: CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
95
+ cacheWrite30mTokens: CACHE_WRITE_30M_INPUT_TOKENS_METRIC_KEY,
96
+ cacheWrite12hTokens: CACHE_WRITE_12H_INPUT_TOKENS_METRIC_KEY,
86
97
  reasoningOutputTokens: REASONING_OUTPUT_TOKENS_METRIC_KEY,
98
+ webSearchCount: WEB_SEARCH_COUNT_METRIC_KEY,
99
+ storageSearchCount: STORAGE_SEARCH_COUNT_METRIC_KEY,
87
100
  }),
88
101
 
89
102
  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