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
@@ -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
 
@@ -0,0 +1,92 @@
1
+ 'use strict'
2
+
3
+ const { pcm16ToWav } = require('../audio-codec')
4
+ const { fitsInlineAudioBudget, formatAudioPartWithGuard } = require('../audio-utils')
5
+ const { AUDIO_FALLBACK, LLMOBS_AUDIO_INLINE_MAX_BYTES } = require('../constants/audio')
6
+ const { storage } = require('../storage')
7
+ const LLMObsPlugin = require('./base')
8
+
9
+ class SonicLLMObsPlugin extends LLMObsPlugin {
10
+ static id = 'bedrockruntime_sonic_llmobs'
11
+ static integration = 'bedrock'
12
+ static system = 'aws.bedrock'
13
+ static prefix = 'tracing:apm:aws:bedrockruntime:sonic:span'
14
+ static emitsGenAiApmTags = false
15
+
16
+ constructor (...args) {
17
+ super(...args)
18
+ this.addSub('dd-trace:aws:bedrockruntime:sonic:capture-context', context => {
19
+ if (!this._llmobsEnabled) return
20
+ context.enabled = true
21
+ // Capture once at invocation, even when absent or already finished. Scoped replay restores
22
+ // the consumer's context on every exit, including exceptions, without repairing IDs later.
23
+ const store = storage.getStore()
24
+ const previous = context.runInContext ?? (fn => fn())
25
+ context.runInContext = fn => previous(() => storage.run(store, fn))
26
+ })
27
+ }
28
+
29
+ /** @param {object} ctx */
30
+ getLLMObsSpanRegisterOptions (ctx) {
31
+ const options = {
32
+ kind: ctx.kind,
33
+ name: ctx.name,
34
+ sessionId: ctx.descriptor.sessionId,
35
+ }
36
+ if (ctx.kind === 'llm') {
37
+ options.modelName = ctx.model
38
+ options.modelProvider = 'amazon'
39
+ }
40
+ return options
41
+ }
42
+
43
+ /** @param {object} ctx */
44
+ start (ctx) {
45
+ super.start(ctx)
46
+ // Replay already has the complete descriptor. Annotate before any end subscriber can finish
47
+ // the span, including when disabling/re-enabling LLMObs changes subscriber registration order.
48
+ if (this._llmobsEnabledFor(ctx)) this.setLLMObsTags(ctx)
49
+ }
50
+
51
+ /** @param {object} ctx */
52
+ setLLMObsTags (ctx) {
53
+ const span = ctx.currentStore?.span
54
+ if (!span) return
55
+ const { turn, history, metadata } = ctx.descriptor
56
+ const text = turn.finalText || turn.speculativeText
57
+ if (ctx.kind !== 'llm') {
58
+ if (ctx.name === 'nova sonic audio turn') this._tagger.tagTextIO(span, turn.userText, text)
59
+ return
60
+ }
61
+
62
+ const input = { role: 'user', content: turn.userText || (turn.inputAudioObserved ? AUDIO_FALLBACK : '') }
63
+ const output = { role: 'assistant', content: text || (turn.outputAudioObserved ? AUDIO_FALLBACK : '') }
64
+ if (turn.tools.length) output.toolCalls = turn.tools
65
+ const inputs = [...history, input, ...turn.toolResults]
66
+ const outputs = [output]
67
+ // Reserve the serialized text/tools and metadata before spending one budget across both WAVs.
68
+ let budget = Math.max(0, LLMOBS_AUDIO_INLINE_MAX_BYTES -
69
+ Buffer.byteLength(JSON.stringify([inputs, outputs, metadata])) - 256)
70
+ for (const [message, pcm, rate] of [
71
+ [input, turn.inputPcm, turn.inputRate],
72
+ [output, turn.output.pcm(), turn.output.rate],
73
+ ]) {
74
+ if (!pcm?.length || !rate) continue
75
+ const part = fitsInlineAudioBudget(pcm.length + 44, budget)
76
+ ? formatAudioPartWithGuard(pcm16ToWav(pcm, rate), 'audio/wav', budget)
77
+ : undefined
78
+ if (part) {
79
+ message.audioParts = [part]
80
+ budget -= part.content.length
81
+ } else if (message === output) {
82
+ metadata.output_audio_omitted_reason = 'payload_limit'
83
+ }
84
+ }
85
+ if (turn.output.omittedReason) metadata.output_audio_omitted_reason = turn.output.omittedReason
86
+ this._tagger.tagLLMIO(span, inputs, outputs)
87
+ this._tagger.tagMetadata(span, metadata)
88
+ this._tagger.tagMetrics(span, turn.metrics)
89
+ }
90
+ }
91
+
92
+ module.exports = SonicLLMObsPlugin
@@ -8,7 +8,7 @@ const {
8
8
  } = require('../../constants/tags')
9
9
  const { audioMimeTypeFromFormat, formatAudioPart } = require('../../audio-utils')
10
10
  const { safeJsonParse } = require('../../util')
11
- const { AUDIO_MIME_TYPES } = require('./constants')
11
+ const { AUDIO_MIME_TYPES, IMAGE_FALLBACK, INPUT_TYPE_IMAGE } = require('./constants')
12
12
  const {
13
13
  extractChatTemplateFromInstructions,
14
14
  normalizePromptVariables,
@@ -16,6 +16,7 @@ const {
16
16
  extractContentParts,
17
17
  hasMultimodalInputs,
18
18
  getModelProviderAndClient,
19
+ getServerToolUsageMetrics,
19
20
  } = require('./utils')
20
21
 
21
22
  const allowedParamKeys = new Set([
@@ -107,6 +108,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
107
108
  this._tagEmbedding(span, inputs, response, error)
108
109
  } else if (operation === 'response') {
109
110
  this.#tagResponse(span, inputs, response, error)
111
+ } else if (operation === 'decision') {
112
+ this.#tagDecision(span, inputs, response, error)
110
113
  }
111
114
 
112
115
  if (!error) {
@@ -148,12 +151,20 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
148
151
  // Responses API - always include
149
152
  const cacheReadTokens = tokenUsage.input_tokens_details.cached_tokens
150
153
  if (cacheReadTokens !== undefined) metrics.cacheReadTokens = cacheReadTokens
154
+ const cacheWriteTokens = tokenUsage.input_tokens_details.cache_write_tokens
155
+ if (cacheWriteTokens != null) metrics.cacheWriteTokens = cacheWriteTokens
156
+ // Cache writes are billed at a different rate per TTL
157
+ const cacheWriteDetails = tokenUsage.input_tokens_details.cache_write_tokens_details
158
+ if (cacheWriteDetails?.ttl_30m_tokens != null) metrics.cacheWrite30mTokens = cacheWriteDetails.ttl_30m_tokens
159
+ if (cacheWriteDetails?.ttl_12h_tokens != null) metrics.cacheWrite12hTokens = cacheWriteDetails.ttl_12h_tokens
151
160
  } else if (tokenUsage.prompt_tokens_details) {
152
161
  // Chat/Completions API - only include if > 0
153
162
  const cacheReadTokens = tokenUsage.prompt_tokens_details.cached_tokens
154
163
  if (cacheReadTokens != null) {
155
164
  metrics.cacheReadTokens = cacheReadTokens
156
165
  }
166
+ const cacheWriteTokens = tokenUsage.prompt_tokens_details.cache_write_tokens
167
+ if (cacheWriteTokens != null) metrics.cacheWriteTokens = cacheWriteTokens
157
168
  }
158
169
  // Reasoning tokens - Responses API returns `output_tokens_details`, `completion_tokens_details`
159
170
  const reasoningOutputObject = tokenUsage.output_tokens_details ?? tokenUsage.completion_tokens_details
@@ -161,6 +172,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
161
172
  if (reasoningOutputTokens !== undefined) metrics.reasoningOutputTokens = reasoningOutputTokens
162
173
  }
163
174
 
175
+ Object.assign(metrics, getServerToolUsageMetrics(response))
176
+
164
177
  return metrics
165
178
  }
166
179
 
@@ -464,6 +477,30 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
464
477
 
465
478
  this._tagger.tagMetadata(span, outputMetadata) // update the metadata with the output metadata
466
479
  }
480
+
481
+ #tagDecision (span, inputs, response, error) {
482
+ const { input, questions } = inputs
483
+
484
+ const inputMessages = typeof input === 'string'
485
+ ? [{ role: 'user', content: input }]
486
+ : (Array.isArray(input) ? input : []).map(message => ({
487
+ role: message.role ?? 'user',
488
+ content: decisionMessageContent(message.content),
489
+ }))
490
+
491
+ if (Array.isArray(questions)) {
492
+ this._tagger.tagMetadata(span, { questions })
493
+ }
494
+
495
+ if (error) {
496
+ this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
497
+ return
498
+ }
499
+
500
+ this._tagger.tagLLMIO(span, inputMessages, [
501
+ { role: 'assistant', content: JSON.stringify(response.answers ?? []) },
502
+ ])
503
+ }
467
504
  }
468
505
 
469
506
  // TODO: this will be moved to the APM integration
@@ -485,13 +522,28 @@ function normalizeOpenAIResourceName (resource) {
485
522
  case 'responses.create':
486
523
  return 'createResponse'
487
524
 
525
+ // decisions
526
+ case 'decisions.create':
527
+ return 'createDecision'
528
+
488
529
  default:
489
530
  return resource
490
531
  }
491
532
  }
492
533
 
534
+ // Decision images must be inline data URLs, so they are replaced with a marker instead of copied onto the span
535
+ function decisionMessageContent (content) {
536
+ if (!Array.isArray(content)) return content ?? ''
537
+
538
+ return content
539
+ .map(part => (part?.type === INPUT_TYPE_IMAGE ? IMAGE_FALLBACK : part?.text ?? ''))
540
+ .filter(Boolean)
541
+ .join('\n')
542
+ }
543
+
493
544
  function gateResource (resource) {
494
- return ['createCompletion', 'createChatCompletion', 'createEmbedding', 'createResponse'].includes(resource)
545
+ return ['createCompletion', 'createChatCompletion', 'createEmbedding', 'createResponse', 'createDecision']
546
+ .includes(resource)
495
547
  ? resource
496
548
  : undefined
497
549
  }
@@ -506,6 +558,8 @@ function getOperation (resource) {
506
558
  return 'embedding'
507
559
  case 'createResponse':
508
560
  return 'response'
561
+ case 'createDecision':
562
+ return 'decision'
509
563
  default:
510
564
  // should never happen
511
565
  return 'unknown'
@@ -198,6 +198,43 @@ function getModelProviderAndClient (baseUrl = '') {
198
198
  return { modelProvider, client: 'OpenAI' }
199
199
  }
200
200
 
201
+ /**
202
+ * Count the billable server-side tool calls in an OpenAI Responses response.
203
+ *
204
+ * Web searches are read from the undocumented `tool_usage.web_search.num_requests` when present, otherwise
205
+ * counted from completed `web_search_call` items whose action is `search` (`open_page` / `find_in_page` steps are
206
+ * not billed). File searches have no usage counter, so each completed `file_search_call` item counts once.
207
+ * Zero counts are omitted.
208
+ *
209
+ * @param {Record<string, unknown>} [response] - Any OpenAI response; only Responses API fields are read
210
+ * @returns {{ webSearchCount?: number, storageSearchCount?: number }}
211
+ */
212
+ function getServerToolUsageMetrics (response) {
213
+ const metrics = {}
214
+ const reportedWebSearches = response?.tool_usage?.web_search?.num_requests
215
+ const hasReportedWebSearches = typeof reportedWebSearches === 'number'
216
+ if (hasReportedWebSearches && reportedWebSearches > 0) metrics.webSearchCount = reportedWebSearches
217
+
218
+ const output = response?.output
219
+ if (!Array.isArray(output)) return metrics
220
+
221
+ for (const item of output) {
222
+ if (item?.status !== 'completed') continue
223
+
224
+ if (item.type === 'web_search_call') {
225
+ if (hasReportedWebSearches) continue
226
+ // Items without an action (older payloads) are assumed to be searches.
227
+ const actionType = item.action?.type
228
+ if (actionType !== undefined && actionType !== 'search') continue
229
+ metrics.webSearchCount = (metrics.webSearchCount ?? 0) + 1
230
+ } else if (item.type === 'file_search_call') {
231
+ metrics.storageSearchCount = (metrics.storageSearchCount ?? 0) + 1
232
+ }
233
+ }
234
+
235
+ return metrics
236
+ }
237
+
201
238
  module.exports = {
202
239
  extractChatTemplateFromInstructions,
203
240
  normalizePromptVariables,
@@ -206,4 +243,5 @@ module.exports = {
206
243
  hasMultimodalInputs,
207
244
  getModelProviderAndClient,
208
245
  getOpenAIModelProvider,
246
+ getServerToolUsageMetrics,
209
247
  }
@@ -84,13 +84,11 @@ function promptFromData (data, source) {
84
84
  }
85
85
 
86
86
  /**
87
- * Copy a prompt with a different source.
88
87
  * @param {ManagedPrompt} prompt
89
- * @param {'registry'|'cache'|'fallback'|'ff'|'resolve'} source
90
88
  */
91
- function withSource (prompt, source) {
92
- if (source === prompt.source) return prompt
93
- return new ManagedPrompt({ ...prompt, source })
89
+ function withCacheSource (prompt) {
90
+ if (SOURCE_CACHE === prompt.source) return prompt
91
+ return new ManagedPrompt({ ...prompt, source: /** @type {ManagedPrompt['source']} */ (SOURCE_CACHE) })
94
92
  }
95
93
 
96
94
  function detailFromBody (body) {
@@ -310,7 +308,7 @@ class PromptManager {
310
308
  const cacheable = generationAtStart === this.cacheGeneration && latest
311
309
  if (result.prompt) {
312
310
  if (cacheable) {
313
- const cached = withSource(result.prompt, SOURCE_CACHE)
311
+ const cached = withCacheSource(result.prompt)
314
312
  if (hot) this.hotCache.set(request.key, cached)
315
313
  if (!request.resolve) this.warmCache.set(request.key, cached)
316
314
  }
@@ -335,7 +333,7 @@ class PromptManager {
335
333
  async #backgroundFetch (request, signal) {
336
334
  const result = await this.#fetchAndCache(request, { hot: false, signal })
337
335
  if (!result.cacheable) return
338
- if (result.prompt) return withSource(result.prompt, SOURCE_CACHE)
336
+ if (result.prompt) return withCacheSource(result.prompt)
339
337
  if (result.notFound) return
340
338
  throw new Error(result.reason)
341
339
  }
@@ -93,7 +93,7 @@ class ManagedPrompt {
93
93
  format (variables = {}) {
94
94
  if (typeof this.template === 'string') return render(this.template, variables)
95
95
  return this.template.flatMap(item => {
96
- if (!isPlaceholder(item)) return [{ ...item, content: render(item.content, variables) }]
96
+ if (!isPlaceholder(item)) return { ...item, content: render(item.content, variables) }
97
97
  if (!Object.hasOwn(variables, item.name)) {
98
98
  throw new TypeError(`Missing message placeholder variable '${item.name}'`)
99
99
  }
@@ -29,6 +29,8 @@ const {
29
29
  getFunctionArguments,
30
30
  validateKind,
31
31
  } = require('./util')
32
+ const { buildAgentDeclaration } = require('./agent-manifest')
33
+ /** @typedef {import('./agent-manifest').AgentDeclaration} AgentDeclaration */
32
34
  const { storage } = require('./storage')
33
35
  const telemetry = require('./telemetry')
34
36
  const LLMObsTagger = require('./tagger')
@@ -300,7 +302,7 @@ class LLMObs extends NoopLLMObs {
300
302
  throw new Error('LLMObs span must have a span kind specified')
301
303
  }
302
304
 
303
- const { inputData, outputData, metadata, metrics, tags, prompt, costTags, toolDefinitions } = options
305
+ const { inputData, outputData, metadata, metrics, tags, prompt, costTags, toolDefinitions, agent } = options
304
306
 
305
307
  const hasInputOrOutput = spanKind === 'experiment'
306
308
  ? inputData !== undefined || outputData !== undefined
@@ -338,6 +340,15 @@ class LLMObs extends NoopLLMObs {
338
340
  if (toolDefinitions != null) {
339
341
  this._tagger.tagToolDefinitions(span, toolDefinitions)
340
342
  }
343
+ if (agent != null) {
344
+ if (spanKind === 'agent') {
345
+ this._tagger.tagAgent(span, agent)
346
+ } else {
347
+ logger.warn(
348
+ 'Dropping agent annotation on non-agent span kind, annotating agents is only supported for agent spans.'
349
+ )
350
+ }
351
+ }
341
352
  } catch (e) {
342
353
  if (e.ddErrorTag) {
343
354
  err = e.ddErrorTag
@@ -602,14 +613,20 @@ class LLMObs extends NoopLLMObs {
602
613
  annotationContext (options, fn) {
603
614
  if (!this.enabled) return fn()
604
615
 
616
+ /** @type {{ annotationContext?: object, agentDeclarations?: AgentDeclaration[] } | undefined} */
605
617
  const currentStore = storage.getStore()
606
-
618
+ const { agent, ...contextOptions } = options ?? {}
619
+ /** @type {{ annotationContext: object, agentDeclarations?: AgentDeclaration[] }} */
607
620
  const store = {
608
621
  ...currentStore,
609
- annotationContext: {
610
- ...currentStore?.annotationContext,
611
- ...options,
612
- },
622
+ annotationContext: { ...currentStore?.annotationContext, ...contextOptions },
623
+ }
624
+
625
+ if (agent != null) {
626
+ const declaration = buildAgentDeclaration(agent)
627
+ // Outside `annotationContext`, whose keys come from the caller. Outermost first, so nested declarations fold
628
+ // onto the outer ones.
629
+ if (declaration) store.agentDeclarations = [...(currentStore?.agentDeclarations ?? []), declaration]
613
630
  }
614
631
 
615
632
  return storage.run(store, fn)
@@ -15,6 +15,7 @@ const {
15
15
  MODEL_PROVIDER,
16
16
  METADATA,
17
17
  COST_TAGS,
18
+ AGENT_MANIFEST,
18
19
  TOOL_DEFINITIONS,
19
20
  EXPERIMENT_INPUT,
20
21
  EXPERIMENT_OUTPUT,
@@ -43,6 +44,7 @@ const {
43
44
  DEFAULT_MODEL,
44
45
  } = require('./constants/tags')
45
46
  const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
47
+ const { MANUAL_FRAMEWORK_NAME } = require('./agent-manifest')
46
48
  const { setGenAiApmTags } = require('./gen-ai-tags')
47
49
  const telemetry = require('./telemetry')
48
50
  const LLMObsTagger = require('./tagger')
@@ -90,9 +92,8 @@ class LLMObsSpanProcessor {
90
92
 
91
93
  // TODO: instead of relying on the tagger's weakmap registry, can we use some namespaced storage correlation?
92
94
  process (span) {
93
- if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
94
95
  // if the span is not in our private tagger map, it is not an llmobs span
95
- if (!LLMObsTagger.tagMap.has(span)) return
96
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED || !LLMObsTagger.tagMap.has(span)) return
96
97
 
97
98
  try {
98
99
  this.#setGenAiApmTags(span)
@@ -152,15 +153,26 @@ class LLMObsSpanProcessor {
152
153
  meta.model_provider = (mlObsTags[MODEL_PROVIDER] || DEFAULT_MODEL).toLowerCase()
153
154
  }
154
155
 
155
- if (mlObsTags[METADATA] || mlObsTags[COST_TAGS]) {
156
+ const name = mlObsTags[NAME] || span._name
157
+ const agentManifest = spanKind === 'agent' ? mlObsTags[AGENT_MANIFEST] : undefined
158
+
159
+ if (mlObsTags[METADATA] || mlObsTags[COST_TAGS] || agentManifest) {
160
+ /** @type {Record<string, unknown>} */
156
161
  const metadata = {}
157
162
  if (mlObsTags[METADATA]) this.#addObject(mlObsTags[METADATA], metadata)
158
- // Only seed `metadata._dd` when there's something to put in it (currently cost_tags). Mirrors
159
- // dd-trace-py and the cross-language wire format enforced by system-tests — metadata-only
160
- // spans must not carry an empty `_dd: {}` block.
163
+ // Only seed `metadata._dd` when there's something to put in it. Mirrors dd-trace-py and the
164
+ // cross-language wire format enforced by system-tests, so metadata-only spans must not carry
165
+ // an empty `_dd: {}` block.
161
166
  if (mlObsTags[COST_TAGS]) {
162
167
  this.#getDdMetadata(metadata).cost_tags = mlObsTags[COST_TAGS]
163
168
  }
169
+ if (agentManifest) {
170
+ this.#getDdMetadata(metadata).agent_manifest = {
171
+ framework: MANUAL_FRAMEWORK_NAME,
172
+ ...agentManifest,
173
+ name: agentManifest.name ?? name,
174
+ }
175
+ }
164
176
  meta.metadata = metadata
165
177
  }
166
178
 
@@ -227,8 +239,6 @@ class LLMObsSpanProcessor {
227
239
  const sessionId = mlObsTags[SESSION_ID]
228
240
  const parentId = mlObsTags[PARENT_ID_KEY]
229
241
 
230
- const name = mlObsTags[NAME] || span._name
231
-
232
242
  const tags = this.#getTags(span, mlApp, sessionId, error)
233
243
  llmObsSpan._tags = tags
234
244
 
@@ -18,6 +18,7 @@ const {
18
18
  OUTPUT_VALUE,
19
19
  METADATA,
20
20
  COST_TAGS,
21
+ AGENT_MANIFEST,
21
22
  METRICS,
22
23
  TOOL_DEFINITIONS,
23
24
  PARENT_ID_KEY,
@@ -53,6 +54,7 @@ const {
53
54
  TRACE_ID,
54
55
  PROPAGATED_TRACE_ID_KEY,
55
56
  } = require('./constants/tags')
57
+ const { buildAgentDeclaration } = require('./agent-manifest')
56
58
  const { storage } = require('./storage')
57
59
  const {
58
60
  findGenAIAncestorSpanId,
@@ -116,8 +118,8 @@ class LLMObsTagger {
116
118
  integration,
117
119
  _decorator,
118
120
  } = {}) {
119
- if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
120
- if (!kind) return // do not register it in the map if it doesn't have an llmobs span kind
121
+ // Do not register it in the map if it does not have an LLMObs span kind.
122
+ if (!this.#config.llmobs.DD_LLMOBS_ENABLED || !kind) return
121
123
 
122
124
  const spanMlApp =
123
125
  mlApp ||
@@ -187,6 +189,11 @@ class LLMObsTagger {
187
189
  this.tagCostTags(span, annotationContext.costTags, 'annotation_context')
188
190
  }
189
191
 
192
+ const agentDeclarations = /** @type {import('./agent-manifest').AgentDeclaration[] | undefined} */ (
193
+ storage.getStore()?.agentDeclarations
194
+ )
195
+ if (agentDeclarations) this.#applyAgentDeclarations(span, agentDeclarations)
196
+
190
197
  // apply annotation context name
191
198
  const annotationContextName = annotationContext?.name
192
199
  if (annotationContextName) this._setTag(span, NAME, annotationContextName)
@@ -204,6 +211,43 @@ class LLMObsTagger {
204
211
  }
205
212
  }
206
213
 
214
+ /**
215
+ * Applies the agents declared by the enclosing annotation contexts, outermost first, to every span in the block.
216
+ * The manifest is emitted only if the span is an agent when it finishes, since some integrations promote a span
217
+ * to an agent after registration.
218
+ *
219
+ * @param {import('../opentracing/span')} span
220
+ * @param {import('./agent-manifest').AgentDeclaration[]} declarations
221
+ */
222
+ #applyAgentDeclarations (span, declarations) {
223
+ for (const declaration of declarations) {
224
+ this.#tagAgentManifestFields(span, declaration.manifest)
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Tags the agent a caller declared on an agent span, merged onto what earlier annotations declared.
230
+ *
231
+ * @param {import('../opentracing/span')} span
232
+ * @param {unknown} agent
233
+ */
234
+ tagAgent (span, agent) {
235
+ const declaration = buildAgentDeclaration(agent)
236
+ if (declaration) this.#tagAgentManifestFields(span, declaration.manifest)
237
+ }
238
+
239
+ /**
240
+ * @param {import('../opentracing/span')} span
241
+ * @param {import('./agent-manifest').AgentManifestFields} manifest
242
+ */
243
+ #tagAgentManifestFields (span, manifest) {
244
+ // A shallow update, matching dd-trace-py: each declaration is already validated, so a later field replaces an
245
+ // earlier one whole (`model_settings` included) and an unset or invalid one leaves it in place. Declarations are
246
+ // never mutated, so the first one is stored without a copy.
247
+ const existing = registry.get(span)?.[AGENT_MANIFEST]
248
+ this._setTag(span, AGENT_MANIFEST, existing ? { ...existing, ...manifest } : manifest)
249
+ }
250
+
207
251
  #tagSamplingDecision (span, parent) {
208
252
  const traceTags = span.context()._trace.tags
209
253
  const parentTags = registry.get(parent)
@@ -337,7 +381,16 @@ class LLMObsTagger {
337
381
  if (currentTags) {
338
382
  Object.assign(currentTags, tags)
339
383
  } else {
340
- this._setTag(span, TAGS, tags)
384
+ // Copied so a later annotation cannot write into a caller's object shared by an annotation context. The copy
385
+ // reads caller getters and Proxy traps during span registration, so it must not throw into the application.
386
+ let copy
387
+ try {
388
+ copy = { ...tags }
389
+ } catch {
390
+ log.warn('Dropping span tags that could not be read.')
391
+ return
392
+ }
393
+ this._setTag(span, TAGS, copy)
341
394
  }
342
395
  }
343
396
 
@@ -260,8 +260,7 @@ function findArgumentsBounds (str) {
260
260
 
261
261
  const memo = new WeakMap()
262
262
  function getFunctionArguments (fn, args = []) {
263
- if (!fn) return
264
- if (!args.length) return
263
+ if (!fn || !args.length) return
265
264
  if (args.length === 1) return args[0]
266
265
 
267
266
  try {
@@ -4,7 +4,7 @@ const { URL, format } = require('node:url')
4
4
  const path = require('node:path')
5
5
  const request = require('../../exporters/common/request')
6
6
  const { getEnvironmentVariable } = require('../../config/helper')
7
- const { createServerlessDeliveryTracker } = require('../../serverless')
7
+ const { createServerlessDeliveryTracker, IS_STANDARD_AWS_LAMBDA } = require('../../serverless')
8
8
 
9
9
  const logger = require('../../log')
10
10
 
@@ -23,13 +23,12 @@ class LLMObsBuffer {
23
23
  /**
24
24
  * @param {{
25
25
  * events: Record<string, unknown>[],
26
- * size: number,
27
26
  * routing?: { apiKey?: string, site?: string }
28
27
  * }} options
29
28
  */
30
- constructor ({ events, size, routing = {} }) {
29
+ constructor ({ events, routing = {} }) {
31
30
  this.events = events
32
- this.size = size
31
+ this.size = 0
33
32
  this.routing = routing
34
33
  }
35
34
 
@@ -53,7 +52,7 @@ class BaseLLMObsWriter {
53
52
  this._eventType = eventType
54
53
 
55
54
  /** @type {LLMObsBuffer} */
56
- this._buffer = new LLMObsBuffer({ events: [], size: 0 })
55
+ this._buffer = new LLMObsBuffer({ events: [] })
57
56
 
58
57
  /** @type {import('../../config/config-base')} */
59
58
  this._config = config
@@ -92,7 +91,7 @@ class BaseLLMObsWriter {
92
91
  const apiKey = routing.apiKey
93
92
  let buffer = this.#multiTenantBuffers.get(apiKey)
94
93
  if (!buffer) {
95
- buffer = new LLMObsBuffer({ events: [], size: 0, routing })
94
+ buffer = new LLMObsBuffer({ events: [], routing })
96
95
  this.#multiTenantBuffers.set(apiKey, buffer)
97
96
  }
98
97
  return buffer
@@ -118,6 +117,8 @@ class BaseLLMObsWriter {
118
117
 
119
118
  buffer.size += eventSize
120
119
  buffer.events.push(event)
120
+ // The standard Lambda sandbox can freeze before the periodic flush runs.
121
+ if (IS_STANDARD_AWS_LAMBDA) this.flush()
121
122
  return true
122
123
  }
123
124