dd-trace 6.16.0 → 6.17.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 (334) hide show
  1. package/ci/diagnose.js +0 -23
  2. package/ci/test-optimization-validation/approval-artifacts.js +0 -3
  3. package/ci/test-optimization-validation/approval.js +0 -6
  4. package/ci/test-optimization-validation/artifact-id.js +0 -1
  5. package/ci/test-optimization-validation/bounded-json.js +0 -1
  6. package/ci/test-optimization-validation/cli.js +0 -12
  7. package/ci/test-optimization-validation/command-output-policy.js +0 -1
  8. package/ci/test-optimization-validation/command-runner.js +0 -3
  9. package/ci/test-optimization-validation/environment.js +0 -4
  10. package/ci/test-optimization-validation/executable-approval.js +0 -1
  11. package/ci/test-optimization-validation/executable.js +0 -5
  12. package/ci/test-optimization-validation/framework-adapters/cucumber.js +0 -6
  13. package/ci/test-optimization-validation/framework-adapters/cypress.js +0 -3
  14. package/ci/test-optimization-validation/framework-adapters/playwright.js +0 -6
  15. package/ci/test-optimization-validation/framework-adapters/vitest.js +0 -4
  16. package/ci/test-optimization-validation/generated-test-contract.js +0 -5
  17. package/ci/test-optimization-validation/generated-verifier.js +0 -1
  18. package/ci/test-optimization-validation/literal-environment.js +0 -1
  19. package/ci/test-optimization-validation/manifest-scaffold.js +0 -17
  20. package/ci/test-optimization-validation/manifest-schema.js +0 -21
  21. package/ci/test-optimization-validation/offline-fixtures.js +0 -4
  22. package/ci/test-optimization-validation/plan-writer.js +0 -11
  23. package/ci/test-optimization-validation/preflight-runner.js +0 -1
  24. package/ci/test-optimization-validation/redaction.js +0 -4
  25. package/ci/test-optimization-validation/report-writer.js +0 -16
  26. package/ci/test-optimization-validation/runner-command.js +0 -3
  27. package/ci/test-optimization-validation/runner-contract.js +0 -4
  28. package/ci/test-optimization-validation/safe-files.js +0 -1
  29. package/ci/test-optimization-validation/scenarios/basic-reporting.js +0 -1
  30. package/ci/test-optimization-validation/scenarios/ci-wiring.js +0 -1
  31. package/ci/test-optimization-validation/source-text.js +0 -3
  32. package/ci/vitest-no-worker-init-setup.mjs +41 -16
  33. package/index.d.ts +165 -0
  34. package/package.json +17 -16
  35. package/packages/datadog-esbuild/src/utils.js +0 -2
  36. package/packages/datadog-instrumentations/src/aerospike.js +28 -6
  37. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -2
  38. package/packages/datadog-instrumentations/src/aws-sdk.js +0 -2
  39. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +0 -2
  40. package/packages/datadog-instrumentations/src/console.js +97 -0
  41. package/packages/datadog-instrumentations/src/cucumber.js +72 -12
  42. package/packages/datadog-instrumentations/src/cypress-config.js +0 -23
  43. package/packages/datadog-instrumentations/src/helpers/channel.js +0 -2
  44. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +0 -5
  45. package/packages/datadog-instrumentations/src/helpers/hooks.js +8 -0
  46. package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +0 -3
  47. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +0 -6
  48. package/packages/datadog-instrumentations/src/helpers/register.js +4 -0
  49. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +72 -24
  50. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +39 -0
  51. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +3 -17
  52. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +12 -0
  53. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +36 -0
  54. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/supabase.js +128 -0
  55. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +16 -0
  56. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +388 -0
  57. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +0 -5
  58. package/packages/datadog-instrumentations/src/helpers/router-helper.js +0 -1
  59. package/packages/datadog-instrumentations/src/jest.js +141 -51
  60. package/packages/datadog-instrumentations/src/knex.js +0 -1
  61. package/packages/datadog-instrumentations/src/mariadb-bundle.js +0 -24
  62. package/packages/datadog-instrumentations/src/mariadb.js +0 -2
  63. package/packages/datadog-instrumentations/src/mocha/common.js +0 -1
  64. package/packages/datadog-instrumentations/src/mocha/main.js +22 -25
  65. package/packages/datadog-instrumentations/src/mocha/utils.js +119 -49
  66. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +0 -6
  67. package/packages/datadog-instrumentations/src/mocha/worker.js +14 -17
  68. package/packages/datadog-instrumentations/src/mysql2.js +0 -2
  69. package/packages/datadog-instrumentations/src/openai-realtime/audio-accumulator.js +241 -0
  70. package/packages/datadog-instrumentations/src/openai-realtime/events.js +76 -0
  71. package/packages/datadog-instrumentations/src/openai-realtime/index.js +301 -0
  72. package/packages/datadog-instrumentations/src/openai-realtime/session.js +998 -0
  73. package/packages/datadog-instrumentations/src/openai-realtime/turns.js +211 -0
  74. package/packages/datadog-instrumentations/src/openai.js +51 -12
  75. package/packages/datadog-instrumentations/src/oracledb.js +9 -3
  76. package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -16
  77. package/packages/datadog-instrumentations/src/playwright.js +238 -55
  78. package/packages/datadog-instrumentations/src/postgres.js +7 -0
  79. package/packages/datadog-instrumentations/src/router.js +0 -2
  80. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +0 -2
  81. package/packages/datadog-instrumentations/src/sequelize.js +0 -1
  82. package/packages/datadog-instrumentations/src/supabase.js +15 -0
  83. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +18 -18
  84. package/packages/datadog-instrumentations/src/vitest-main.js +37 -17
  85. package/packages/datadog-instrumentations/src/vitest-util.js +7 -1
  86. package/packages/datadog-instrumentations/src/vitest-worker.js +49 -13
  87. package/packages/datadog-instrumentations/src/webdriverio.js +23 -42
  88. package/packages/datadog-instrumentations/src/ws.js +0 -1
  89. package/packages/datadog-plugin-ai/src/utils.js +0 -2
  90. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +0 -1
  91. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +0 -2
  92. package/packages/datadog-plugin-aws-sdk/src/base.js +0 -1
  93. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +0 -1
  94. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +0 -1
  95. package/packages/datadog-plugin-aws-sdk/src/util.js +0 -1
  96. package/packages/datadog-plugin-azure-durable-functions/src/index.js +0 -1
  97. package/packages/datadog-plugin-cucumber/src/index.js +10 -0
  98. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +171 -75
  99. package/packages/datadog-plugin-cypress/src/finalization.js +0 -3
  100. package/packages/datadog-plugin-cypress/src/source-map-utils.js +0 -4
  101. package/packages/datadog-plugin-cypress/src/support.js +52 -7
  102. package/packages/datadog-plugin-graphql/src/resolve-error.js +1 -2
  103. package/packages/datadog-plugin-graphql/src/utils.js +0 -5
  104. package/packages/datadog-plugin-jest/src/index.js +13 -0
  105. package/packages/datadog-plugin-kafkajs/src/producer.js +0 -1
  106. package/packages/datadog-plugin-mocha/src/index.js +39 -11
  107. package/packages/datadog-plugin-mongodb-core/src/query.js +0 -1
  108. package/packages/datadog-plugin-openai/src/index.js +16 -1
  109. package/packages/datadog-plugin-openai/src/realtime.js +144 -0
  110. package/packages/datadog-plugin-openai-agents/src/integration.js +0 -4
  111. package/packages/datadog-plugin-openai-agents/src/util.js +0 -1
  112. package/packages/datadog-plugin-playwright/src/index.js +76 -9
  113. package/packages/datadog-plugin-postgres/src/index.js +167 -0
  114. package/packages/datadog-plugin-prisma/src/datadog-tracing-helper.js +0 -1
  115. package/packages/datadog-plugin-redis/src/index.js +15 -1
  116. package/packages/datadog-plugin-supabase/src/error.js +18 -0
  117. package/packages/datadog-plugin-supabase/src/index.js +21 -0
  118. package/packages/datadog-plugin-supabase/src/supabase-auth-js-gotrueclient-getuser.js +69 -0
  119. package/packages/datadog-plugin-supabase/src/supabase-functions-js-functionsclient-invoke.js +70 -0
  120. package/packages/datadog-plugin-supabase/src/supabase-postgrest-js-postgrestbuilder-then.js +122 -0
  121. package/packages/datadog-plugin-supabase/src/supabase-realtime-js-realtimechannel-send.js +66 -0
  122. package/packages/datadog-plugin-supabase/src/supabase-storage-js-handle-request.js +93 -0
  123. package/packages/datadog-plugin-supabase/src/url.js +17 -0
  124. package/packages/datadog-plugin-vitest/src/index.js +10 -0
  125. package/packages/datadog-plugin-ws/src/util.js +0 -4
  126. package/packages/datadog-turbopack/index.js +0 -6
  127. package/packages/datadog-turbopack/src/loader.js +0 -16
  128. package/packages/datadog-webpack/src/loader.js +0 -1
  129. package/packages/dd-trace/src/aiguard/client.js +2 -3
  130. package/packages/dd-trace/src/aiguard/evaluation.js +6 -12
  131. package/packages/dd-trace/src/aiguard/index.js +2 -2
  132. package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
  133. package/packages/dd-trace/src/aiguard/integrations/openai.js +81 -7
  134. package/packages/dd-trace/src/aiguard/messages/anthropic.js +0 -2
  135. package/packages/dd-trace/src/aiguard/messages/openai.js +64 -1
  136. package/packages/dd-trace/src/aiguard/messages/utils.js +0 -1
  137. package/packages/dd-trace/src/aiguard/redaction.js +0 -1
  138. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  139. package/packages/dd-trace/src/appsec/activation.js +1 -1
  140. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +0 -5
  141. package/packages/dd-trace/src/appsec/api_security/sampler.js +0 -4
  142. package/packages/dd-trace/src/appsec/blocking/index.js +3 -3
  143. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -4
  144. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  145. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +5 -5
  146. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +1 -1
  147. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -1
  148. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +3 -3
  149. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +8 -10
  150. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -2
  151. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +4 -1
  152. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +7 -7
  153. package/packages/dd-trace/src/appsec/index.js +2 -2
  154. package/packages/dd-trace/src/appsec/lambda.js +71 -2
  155. package/packages/dd-trace/src/appsec/rasp/utils.js +5 -1
  156. package/packages/dd-trace/src/appsec/remote_config.js +8 -6
  157. package/packages/dd-trace/src/appsec/reporter.js +10 -6
  158. package/packages/dd-trace/src/appsec/rule_manager.js +3 -3
  159. package/packages/dd-trace/src/appsec/sdk/track_event.js +0 -2
  160. package/packages/dd-trace/src/appsec/waf/index.js +2 -2
  161. package/packages/dd-trace/src/appsec/waf/waf_manager.js +8 -5
  162. package/packages/dd-trace/src/carrier.js +0 -5
  163. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +104 -0
  164. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +0 -2
  165. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +34 -3
  166. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +0 -1
  167. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +0 -1
  168. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +0 -1
  169. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +0 -4
  170. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +0 -1
  171. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +0 -2
  172. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +37 -15
  173. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +0 -3
  174. package/packages/dd-trace/src/ci-visibility/exporters/request.js +0 -11
  175. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +0 -1
  176. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +0 -1
  177. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +0 -6
  178. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +2 -2
  179. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +0 -1
  180. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +0 -5
  181. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +0 -1
  182. package/packages/dd-trace/src/ci-visibility/telemetry.js +13 -0
  183. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +0 -1
  184. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +0 -2
  185. package/packages/dd-trace/src/ci-visibility/test-video.js +0 -1
  186. package/packages/dd-trace/src/config/defaults.js +3 -2
  187. package/packages/dd-trace/src/config/generated-config-types.d.ts +73 -67
  188. package/packages/dd-trace/src/config/helper.js +0 -1
  189. package/packages/dd-trace/src/config/index.js +24 -24
  190. package/packages/dd-trace/src/config/parsers.js +34 -6
  191. package/packages/dd-trace/src/config/remote_config.js +25 -109
  192. package/packages/dd-trace/src/config/supported-configurations.json +139 -10
  193. package/packages/dd-trace/src/datastreams/encoding.js +0 -1
  194. package/packages/dd-trace/src/datastreams/pathway.js +0 -1
  195. package/packages/dd-trace/src/debugger/constants.js +10 -0
  196. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +62 -10
  197. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  198. package/packages/dd-trace/src/debugger/devtools_client/guardrail-metrics.js +15 -0
  199. package/packages/dd-trace/src/debugger/devtools_client/index.js +103 -27
  200. package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +50 -7
  201. package/packages/dd-trace/src/debugger/devtools_client/log.js +3 -1
  202. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +21 -8
  203. package/packages/dd-trace/src/debugger/devtools_client/send.js +40 -14
  204. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +53 -13
  205. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +118 -71
  206. package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +0 -3
  207. package/packages/dd-trace/src/debugger/devtools_client/status.js +20 -6
  208. package/packages/dd-trace/src/debugger/guardrail-metrics.js +167 -0
  209. package/packages/dd-trace/src/debugger/index.js +50 -2
  210. package/packages/dd-trace/src/debugger/inspect-segment.js +0 -3
  211. package/packages/dd-trace/src/debugger/probe_sampler.js +133 -9
  212. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +31 -1
  213. package/packages/dd-trace/src/dogstatsd.js +0 -23
  214. package/packages/dd-trace/src/encode/0.4-cross-payload.js +0 -3
  215. package/packages/dd-trace/src/encode/0.4.js +0 -7
  216. package/packages/dd-trace/src/encode/0.5.js +0 -2
  217. package/packages/dd-trace/src/encode/tags-processors.js +0 -1
  218. package/packages/dd-trace/src/evp_proxy/discovery.js +0 -1
  219. package/packages/dd-trace/src/evp_proxy/path.js +0 -2
  220. package/packages/dd-trace/src/exporters/agent/writer.js +0 -1
  221. package/packages/dd-trace/src/exporters/agentless/index.js +0 -1
  222. package/packages/dd-trace/src/exporters/agentless/intake.js +0 -1
  223. package/packages/dd-trace/src/exporters/agentless/writer.js +0 -3
  224. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +0 -6
  225. package/packages/dd-trace/src/exporters/common/request.js +1 -0
  226. package/packages/dd-trace/src/exporters/common/url.js +0 -2
  227. package/packages/dd-trace/src/exporters/common/writer.js +0 -2
  228. package/packages/dd-trace/src/id.js +0 -11
  229. package/packages/dd-trace/src/llmobs/audio-codec.js +192 -0
  230. package/packages/dd-trace/src/llmobs/audio-utils.js +121 -0
  231. package/packages/dd-trace/src/llmobs/constants/audio.js +35 -0
  232. package/packages/dd-trace/src/llmobs/constants/prompts.js +14 -0
  233. package/packages/dd-trace/src/llmobs/constants/tags.js +17 -0
  234. package/packages/dd-trace/src/llmobs/eval-metric.js +0 -6
  235. package/packages/dd-trace/src/llmobs/experiments/dataset.js +0 -4
  236. package/packages/dd-trace/src/llmobs/experiments/experiment.js +0 -3
  237. package/packages/dd-trace/src/llmobs/experiments/index.js +7 -6
  238. package/packages/dd-trace/src/llmobs/experiments/util.js +1 -11
  239. package/packages/dd-trace/src/llmobs/index.js +1 -3
  240. package/packages/dd-trace/src/llmobs/noop.js +10 -0
  241. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +0 -4
  242. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +0 -1
  243. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +0 -4
  244. package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/utils.js +0 -2
  245. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +0 -2
  246. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +4 -6
  247. package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +285 -0
  248. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +16 -3
  249. package/packages/dd-trace/src/llmobs/prompts/cache.js +272 -0
  250. package/packages/dd-trace/src/llmobs/prompts/manager.js +639 -0
  251. package/packages/dd-trace/src/llmobs/prompts/noop.js +29 -0
  252. package/packages/dd-trace/src/llmobs/prompts/prompt.js +92 -0
  253. package/packages/dd-trace/src/llmobs/sdk.js +21 -6
  254. package/packages/dd-trace/src/llmobs/span_processor.js +81 -2
  255. package/packages/dd-trace/src/llmobs/tagger.js +30 -7
  256. package/packages/dd-trace/src/llmobs/telemetry.js +36 -4
  257. package/packages/dd-trace/src/llmobs/util.js +0 -34
  258. package/packages/dd-trace/src/llmobs/writers/base.js +0 -1
  259. package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
  260. package/packages/dd-trace/src/log/channels.js +3 -0
  261. package/packages/dd-trace/src/log/index.js +23 -5
  262. package/packages/dd-trace/src/log-submission/log-submission-plugin.js +74 -6
  263. package/packages/dd-trace/src/msgpack/chunk.js +0 -1
  264. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +0 -12
  265. package/packages/dd-trace/src/openfeature/encoding.js +0 -2
  266. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +0 -1
  267. package/packages/dd-trace/src/openfeature/index.js +0 -4
  268. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +0 -1
  269. package/packages/dd-trace/src/openfeature/span-enrichment.js +0 -4
  270. package/packages/dd-trace/src/openfeature/writers/base.js +0 -6
  271. package/packages/dd-trace/src/openfeature/writers/exposures.js +0 -5
  272. package/packages/dd-trace/src/openfeature/writers/util.js +0 -4
  273. package/packages/dd-trace/src/opentelemetry/logs/logger.js +0 -1
  274. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +0 -1
  275. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +0 -1
  276. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +0 -2
  277. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +0 -1
  278. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +0 -1
  279. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +0 -1
  280. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -14
  281. package/packages/dd-trace/src/opentelemetry/metrics/time.js +0 -1
  282. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +30 -13
  283. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +0 -1
  284. package/packages/dd-trace/src/opentelemetry/span-helpers.js +0 -1
  285. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +0 -1
  286. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +0 -3
  287. package/packages/dd-trace/src/opentracing/propagation/text_map.js +0 -6
  288. package/packages/dd-trace/src/opentracing/span_context.js +0 -1
  289. package/packages/dd-trace/src/otel-thread-ctx.js +38 -11
  290. package/packages/dd-trace/src/payload-tagging/tagging.js +15 -3
  291. package/packages/dd-trace/src/plugins/ci_plugin.js +0 -1
  292. package/packages/dd-trace/src/plugins/database.js +2 -6
  293. package/packages/dd-trace/src/plugins/index.js +7 -0
  294. package/packages/dd-trace/src/plugins/plugin.js +0 -4
  295. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  296. package/packages/dd-trace/src/plugins/util/git.js +1 -3
  297. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +0 -1
  298. package/packages/dd-trace/src/plugins/util/llm.js +0 -2
  299. package/packages/dd-trace/src/plugins/util/status-validator.js +0 -1
  300. package/packages/dd-trace/src/plugins/util/test.js +21 -20
  301. package/packages/dd-trace/src/plugins/util/url.js +0 -5
  302. package/packages/dd-trace/src/priority_sampler.js +0 -6
  303. package/packages/dd-trace/src/process-tags/index.js +0 -1
  304. package/packages/dd-trace/src/profiler.js +132 -35
  305. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +0 -1
  306. package/packages/dd-trace/src/profiling/profiler.js +101 -41
  307. package/packages/dd-trace/src/profiling/ssi-heuristics.js +29 -15
  308. package/packages/dd-trace/src/propagation-hash/index.js +0 -1
  309. package/packages/dd-trace/src/proxy.js +21 -45
  310. package/packages/dd-trace/src/random_sampler.js +0 -4
  311. package/packages/dd-trace/src/rate_limiter.js +0 -4
  312. package/packages/dd-trace/src/remote_config/capabilities.js +1 -9
  313. package/packages/dd-trace/src/remote_config/index.js +0 -1
  314. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +0 -3
  315. package/packages/dd-trace/src/sampler.js +0 -4
  316. package/packages/dd-trace/src/sampling_rule.js +0 -7
  317. package/packages/dd-trace/src/serverless/vercel.js +0 -1
  318. package/packages/dd-trace/src/serverless.js +0 -1
  319. package/packages/dd-trace/src/service-naming/index.js +0 -1
  320. package/packages/dd-trace/src/service-naming/schemas/util.js +0 -1
  321. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +7 -0
  322. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +5 -0
  323. package/packages/dd-trace/src/span_processor.js +2 -2
  324. package/packages/dd-trace/src/span_sampler.js +0 -1
  325. package/packages/dd-trace/src/standalone/product.js +2 -2
  326. package/packages/dd-trace/src/standalone/tracesource.js +0 -1
  327. package/packages/dd-trace/src/startup-log.js +1 -1
  328. package/packages/dd-trace/src/telemetry/endpoints.js +0 -8
  329. package/packages/dd-trace/src/telemetry/logs/index.js +16 -5
  330. package/packages/dd-trace/src/telemetry/logs/log-collector.js +16 -6
  331. package/packages/dd-trace/src/telemetry/metrics.js +0 -2
  332. package/packages/dd-trace/src/telemetry/telemetry.js +4 -1
  333. package/packages/dd-trace/src/util.js +13 -1
  334. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
@@ -0,0 +1,241 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ LLMOBS_AUDIO_ACCUMULATE_MAX_BYTES,
5
+ } = require('../../../dd-trace/src/llmobs/constants/audio')
6
+
7
+ /**
8
+ * Collects one side of a realtime turn's audio, decoding each base64 frame to a `Buffer` as it
9
+ * arrives.
10
+ *
11
+ * Decoding eagerly rather than retaining base64 costs 1x memory instead of 1.33x, and — because the
12
+ * segment is then just bytes — lets `trimLeading` and `capTo` cut at an exact byte offset with a
13
+ * `subarray`, where operating on base64 would have to decode and re-encode the straddling frame.
14
+ *
15
+ * `present` records that audio was seen at all, so a turn can still surface an `[audio]` marker when
16
+ * the bytes were dropped; `oversize` marks that the retention cap is currently spent, which stops
17
+ * `append` retaining anything further. It is a statement about the bytes held right now, not a
18
+ * permanent mark on the segment: `trimLeading` re-derives it from what survived, so shedding a
19
+ * lead-in that spent the cap reopens the segment for the speech that follows.
20
+ *
21
+ * Constructed with `retain = false` when nothing consumes the audio, which is the default APM-only
22
+ * configuration: the byte *count* still drives the speech windows, but no frame is decoded and no
23
+ * segment is held.
24
+ */
25
+ class AudioAccumulator {
26
+ /** @type {Buffer[]} */
27
+ chunks = []
28
+
29
+ present = false
30
+
31
+ oversize = false
32
+
33
+ /**
34
+ * Wall clock (epoch ms) when the first frame of this segment was observed, anchoring it on the
35
+ * session timeline. Set even when the bytes are later dropped, since `present` still surfaces a
36
+ * marker.
37
+ *
38
+ * @type {number | undefined}
39
+ */
40
+ startTime = undefined
41
+
42
+ /**
43
+ * The audio format in force when this segment's first frame arrived. A segment keeps the format it
44
+ * was captured under, so a later `session.update` cannot retime bytes that arrived before it.
45
+ *
46
+ * @type {string}
47
+ */
48
+ mimeType = ''
49
+
50
+ /** @type {number} */
51
+ sampleRate = 0
52
+
53
+ /**
54
+ * Total bytes seen for this segment, never reduced by the retention cap. Playback duration — and
55
+ * therefore the speaking window — is derived from this, so it stays accurate even when `chunks`
56
+ * was dropped.
57
+ */
58
+ totalDecodedBytes = 0
59
+
60
+ /** Bytes currently retained across `chunks`. */
61
+ #retainedBytes = 0
62
+
63
+ /**
64
+ * Whether anything consumes the bytes. When false only `totalDecodedBytes` is maintained.
65
+ *
66
+ * Resolved from `#shouldRetain` on the frame that opens the segment, not at construction: a
67
+ * pending input segment is created as soon as the previous turn starts and then sits open across
68
+ * the whole agent response, so construction time can be many seconds before any audio arrives and
69
+ * whatever was true then may not be true now. Deciding on the opening frame is as late as
70
+ * possible while still being once per segment, so retention can never flip mid-clip and leave a
71
+ * segment holding only part of itself.
72
+ *
73
+ * @type {boolean}
74
+ */
75
+ #retain = true
76
+
77
+ /** @type {() => boolean} */
78
+ #shouldRetain
79
+
80
+ /**
81
+ * @param {(() => boolean) | boolean} [shouldRetain]
82
+ */
83
+ constructor (shouldRetain = true) {
84
+ this.#shouldRetain = typeof shouldRetain === 'function' ? shouldRetain : () => shouldRetain
85
+ }
86
+
87
+ /**
88
+ * Add one base64 audio frame, reporting its decoded byte count for the input-buffer clock.
89
+ *
90
+ * @param {string} base64
91
+ * @param {number} now - Epoch ms at which this frame was observed.
92
+ * @param {string} [mimeType] - The session's audio format, recorded on the first frame only.
93
+ * @param {number} [sampleRate]
94
+ */
95
+ append (base64, now, mimeType = '', sampleRate = 0) {
96
+ if (typeof base64 !== 'string' || base64.length === 0) return 0
97
+
98
+ if (this.startTime === undefined) {
99
+ this.startTime = now
100
+ this.mimeType = mimeType
101
+ this.sampleRate = sampleRate
102
+ this.#retain = this.#shouldRetain()
103
+ }
104
+ this.present = true
105
+
106
+ // Size the frame without allocating — `byteLength` is exactly what `Buffer.from(base64,
107
+ // 'base64').length` would be — so every path that ends up discarding the frame pays nothing for
108
+ // it. Decoding first would make the retention cap a bound on what is *kept* rather than on peak
109
+ // allocation, and an oversize live stream would keep paying for discarded decodes.
110
+ const decodedLength = Buffer.byteLength(base64, 'base64')
111
+ this.totalDecodedBytes += decodedLength
112
+
113
+ if (!this.#retain || this.oversize) return decodedLength
114
+
115
+ if (this.#retainedBytes + decodedLength > LLMOBS_AUDIO_ACCUMULATE_MAX_BYTES) {
116
+ // Free what we had: the size guard would drop the whole segment anyway. `#retainedBytes` goes
117
+ // with it, so it never describes more bytes than `chunks` holds.
118
+ this.oversize = true
119
+ this.chunks = []
120
+ this.#retainedBytes = 0
121
+ return decodedLength
122
+ }
123
+
124
+ this.#retainedBytes += decodedLength
125
+ this.chunks.push(Buffer.from(base64, 'base64'))
126
+ return decodedLength
127
+ }
128
+
129
+ /**
130
+ * Drop this segment's first `decodedBytes` bytes.
131
+ *
132
+ * Cuts the pre-speech audio a continuously-streaming client appends — the microphone keeps sending
133
+ * while the agent talks — off the front of a user turn, so the captured audio covers the same
134
+ * window the span reports.
135
+ *
136
+ * @param {number} decodedBytes
137
+ */
138
+ trimLeading (decodedBytes) {
139
+ if (!(decodedBytes > 0)) return
140
+
141
+ if (decodedBytes >= this.totalDecodedBytes) {
142
+ // Everything seen so far precedes the onset, so the segment starts empty. Reset outright,
143
+ // retention cap included: otherwise a long silent lead-in — the buffer stays open across the
144
+ // whole previous agent response — could spend the cap before the speech even starts and leave
145
+ // the turn with nothing but an `[audio]` marker.
146
+ this.clear()
147
+ return
148
+ }
149
+
150
+ let remaining = decodedBytes
151
+ while (this.chunks.length > 0 && remaining > 0) {
152
+ const chunk = this.chunks[0]
153
+ if (chunk.length > remaining) {
154
+ this.chunks[0] = chunk.subarray(remaining)
155
+ break
156
+ }
157
+ this.chunks.shift()
158
+ remaining -= chunk.length
159
+ }
160
+
161
+ this.#retainedBytes = 0
162
+ for (const chunk of this.chunks) this.#retainedBytes += chunk.length
163
+
164
+ // Reopen retention around whatever survived. This is what the trim is for: on a continuously
165
+ // streaming client the lead-in is what spends the cap — the buffer stays open across the whole
166
+ // previous agent response — and once `append` trips, it drops every later frame. Leaving the
167
+ // segment closed here would discard the user's actual speech and leave the turn with nothing but
168
+ // an `[audio]` marker, which is the outcome trimming exists to prevent.
169
+ //
170
+ // A segment that had already tripped the cap holds nothing, so it reopens empty and the clip
171
+ // starts at the next frame. `audio_start_ms` carries the session's `prefix_padding_ms`, so the
172
+ // bytes missing from the front are that padding rather than speech — `speech_started` fires at
173
+ // the onset, before there is speech to lose. The duration derived from `totalDecodedBytes`
174
+ // therefore runs a padding's-width longer than the clip it describes, which is the price of
175
+ // keeping the speech at all.
176
+ this.oversize = this.#retainedBytes > LLMOBS_AUDIO_ACCUMULATE_MAX_BYTES
177
+
178
+ this.totalDecodedBytes -= decodedBytes
179
+ }
180
+
181
+ /**
182
+ * Shrink this segment to its first `decodedBytes` bytes.
183
+ *
184
+ * The mirror of `trimLeading`, for audio that was delivered but never heard: when the listener cuts
185
+ * the agent off, everything past that point is generated-but-unplayed. Absolute and shrink-only, so
186
+ * a client truncation and the server's acknowledgement of it apply the cap once between them.
187
+ *
188
+ * @param {number} decodedBytes
189
+ */
190
+ capTo (decodedBytes) {
191
+ if (decodedBytes >= this.totalDecodedBytes) return
192
+
193
+ if (decodedBytes <= 0) {
194
+ // Nothing was heard at all, so the segment is empty rather than zero-length-but-present.
195
+ this.clear()
196
+ return
197
+ }
198
+
199
+ let kept = 0
200
+ const chunks = []
201
+ for (const chunk of this.chunks) {
202
+ if (kept + chunk.length <= decodedBytes) {
203
+ chunks.push(chunk)
204
+ kept += chunk.length
205
+ continue
206
+ }
207
+ const partial = chunk.subarray(0, decodedBytes - kept)
208
+ if (partial.length > 0) {
209
+ chunks.push(partial)
210
+ kept += partial.length
211
+ }
212
+ break
213
+ }
214
+
215
+ this.chunks = chunks
216
+ this.#retainedBytes = kept
217
+ this.totalDecodedBytes = decodedBytes
218
+ }
219
+
220
+ /**
221
+ * The retained audio as one buffer. Empty when the segment was dropped or never had bytes.
222
+ *
223
+ * @returns {Buffer}
224
+ */
225
+ toBuffer () {
226
+ return this.chunks.length === 1 ? this.chunks[0] : Buffer.concat(this.chunks, this.#retainedBytes)
227
+ }
228
+
229
+ clear () {
230
+ this.chunks = []
231
+ this.present = false
232
+ this.oversize = false
233
+ this.startTime = undefined
234
+ this.mimeType = ''
235
+ this.sampleRate = 0
236
+ this.totalDecodedBytes = 0
237
+ this.#retainedBytes = 0
238
+ }
239
+ }
240
+
241
+ module.exports = AudioAccumulator
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @typedef {import('./turns').ToolCall} ToolCall
5
+ * @typedef {import('./turns').ToolResult} ToolResult
6
+ */
7
+
8
+ /**
9
+ * Collapse SDK naming drift so `response.output_audio.*` / `response.output_text.*` match their
10
+ * older `response.audio.*` / `response.text.*` equivalents.
11
+ *
12
+ * @param {string} eventType
13
+ */
14
+ function normalizeResponseEventType (eventType) {
15
+ // Only `response.*` events carry the renamed segments, and they are a small fraction of the
16
+ // stream, so skip the three replacements entirely for everything else.
17
+ if (!eventType.startsWith('response.')) return eventType
18
+
19
+ return eventType
20
+ .replace('.output_audio_transcript', '.audio_transcript')
21
+ .replace('.output_audio', '.audio')
22
+ .replace('.output_text', '.text')
23
+ }
24
+
25
+ /**
26
+ * Pull function and MCP tool usage out of a `response.done`'s output items.
27
+ *
28
+ * Function calls become tool calls — the app returns their result later via `function_call_output`,
29
+ * captured on the next turn's input. MCP calls run server-side, so their result is inline on the
30
+ * item and is captured as a tool result alongside the call.
31
+ *
32
+ * `arguments` is left as the raw JSON string the API sent; the LLM Observability plugin parses it,
33
+ * so this stays a provider-native extraction.
34
+ *
35
+ * @param {{ output?: Array<Record<string, unknown>> }} response
36
+ * @returns {{ toolCalls: ToolCall[], toolResults: ToolResult[] }}
37
+ */
38
+ function extractResponseTools (response) {
39
+ /** @type {ToolCall[]} */
40
+ const toolCalls = []
41
+ /** @type {ToolResult[]} */
42
+ const toolResults = []
43
+
44
+ const output = response?.output
45
+ if (!output) return { toolCalls, toolResults }
46
+
47
+ for (const item of output) {
48
+ if (item?.type === 'function_call') {
49
+ toolCalls.push({
50
+ name: String(item.name ?? ''),
51
+ arguments: item.arguments,
52
+ toolId: String(item.call_id ?? item.id ?? ''),
53
+ type: 'function',
54
+ })
55
+ } else if (item?.type === 'mcp_call') {
56
+ const toolId = String(item.id ?? '')
57
+ const name = String(item.name ?? '')
58
+
59
+ toolCalls.push({ name, arguments: item.arguments, toolId, type: 'mcp_call' })
60
+
61
+ const { output, error } = item
62
+ if (output != null || error != null) {
63
+ toolResults.push({
64
+ name,
65
+ result: String(output ?? error),
66
+ toolId,
67
+ type: 'mcp_tool_result',
68
+ })
69
+ }
70
+ }
71
+ }
72
+
73
+ return { toolCalls, toolResults }
74
+ }
75
+
76
+ module.exports = { extractResponseTools, normalizeResponseEventType }
@@ -0,0 +1,301 @@
1
+ 'use strict'
2
+
3
+ const dc = require('dc-polyfill')
4
+
5
+ const { getValueFromEnvSources } = require('../../../dd-trace/src/config/helper')
6
+ const log = require('../../../dd-trace/src/log')
7
+ const shimmer = require('../../../datadog-shimmer')
8
+ const RealtimeSession = require('./session')
9
+
10
+ /**
11
+ * @typedef {import('./session').TurnDescriptor} TurnDescriptor
12
+ * @typedef {import('./turns').ResponseTurn} ResponseTurn
13
+ */
14
+
15
+ // One span per phase of a turn. `user speech` and `agent speech` share a channel: they differ only
16
+ // by name and resource, which is data, not a type.
17
+ const turnChannel = dc.tracingChannel('apm:openai:realtime:turn')
18
+ const responseChannel = dc.tracingChannel('apm:openai:realtime:response')
19
+ const speechChannel = dc.tracingChannel('apm:openai:realtime:speech')
20
+
21
+ // Published once per turn at `response.created`. The tracing and LLM Observability plugins each
22
+ // compose a `runInContext` onto the turn, so the tree replayed at finalize nests under whatever
23
+ // context the caller had active — the active context at finalize is the socket's, not theirs.
24
+ const captureContextChannel = dc.channel('dd-trace:openai:realtime:capture-context')
25
+
26
+ // Never published on — subscribed to as a capability signal, the way `captureContextChannel` is
27
+ // probed before publishing. Only LLM Observability consumes a turn's audio bytes (the tracing
28
+ // plugins read the model and the phase boundaries), and a disabled plugin unsubscribes its channels,
29
+ // so `hasSubscribers` here is exactly "someone will read the audio". Without it the default
30
+ // APM-only configuration decodes and buffers every frame of every turn for no consumer.
31
+ //
32
+ // A dedicated channel is what makes the distinction possible: the two plugin families share the
33
+ // `apm:openai:realtime:*` prefixes, so the turn channel cannot tell them apart.
34
+ const audioChannel = dc.channel('dd-trace:openai:realtime:audio')
35
+
36
+ const USER_SPEECH = { operation: 'createRealtimeUserSpeech', llmobsName: 'user speech' }
37
+ const AGENT_SPEECH = { operation: 'createRealtimeAgentSpeech', llmobsName: 'agent speech' }
38
+
39
+ /** @type {WeakMap<object, { session: RealtimeSession, finalize: () => void }>} */
40
+ const connections = new WeakMap()
41
+
42
+ /**
43
+ * Live sessions, weakly, so a process exiting without closing its connections still reports the
44
+ * turns they hold. Weak because a realtime connection can outlive many turns and we must not be the
45
+ * reason one stays reachable.
46
+ *
47
+ * @type {Set<WeakRef<RealtimeSession>>}
48
+ */
49
+ const liveSessions = new Set()
50
+
51
+ function noop () {}
52
+
53
+ /**
54
+ * Replay a finished turn as a back-dated span tree. Nesting falls out of the synchronous scoping of
55
+ * `traceSync`: the phase spans are created inside the turn root's callback, so each picks the root
56
+ * up as its parent from the active store without any explicit parent stamping.
57
+ *
58
+ * @param {TurnDescriptor} turn
59
+ */
60
+ function emitTurn (turn) {
61
+ const run = turn.runInContext ?? (fn => fn())
62
+
63
+ run(() => {
64
+ turnChannel.traceSync(() => {
65
+ const { userSpeech, agentSpeech } = turn
66
+
67
+ if (userSpeech !== undefined) {
68
+ speechChannel.traceSync(noop, { turn, phase: USER_SPEECH, ...userSpeech })
69
+ }
70
+
71
+ responseChannel.traceSync(noop, {
72
+ turn,
73
+ startTime: turn.llm.startTime,
74
+ finishTime: turn.llm.finishTime,
75
+ })
76
+
77
+ if (agentSpeech !== undefined) {
78
+ speechChannel.traceSync(noop, { turn, phase: AGENT_SPEECH, ...agentSpeech })
79
+ }
80
+ }, { turn, startTime: turn.root.startTime, finishTime: turn.root.finishTime })
81
+ })
82
+ }
83
+
84
+ /**
85
+ * @param {ResponseTurn} turn
86
+ */
87
+ function captureContext (turn) {
88
+ if (captureContextChannel.hasSubscribers) captureContextChannel.publish(turn)
89
+ }
90
+
91
+ /**
92
+ * Start tracking a connection, reading the model and base path off the URL the SDK already built.
93
+ *
94
+ * @param {object} emitter
95
+ * @returns {{ session: RealtimeSession, finalize: () => void }}
96
+ */
97
+ function createConnection (emitter) {
98
+ let model
99
+ let basePath = ''
100
+
101
+ try {
102
+ const { url } = emitter
103
+ if (url) {
104
+ // Azure names it `deployment`; a sideband `call_id` connection has neither, and picks the
105
+ // model up from `session.created` instead.
106
+ model = url.searchParams.get('model') ?? url.searchParams.get('deployment') ?? undefined
107
+ // Origin and path only. The query string carries `Authorization` / `api-key` on the Azure
108
+ // path, before the SDK redacts them.
109
+ basePath = url.origin + url.pathname
110
+ }
111
+ } catch {
112
+ // The URL shape is not guaranteed across SDK versions; the session works without it.
113
+ }
114
+
115
+ // Asked per segment rather than snapshotted here: a plugin can be reconfigured while a connection
116
+ // is open, and a session that outlived its consumer would otherwise keep buffering audio nothing
117
+ // reads.
118
+ const session = new RealtimeSession({
119
+ emitTurn,
120
+ captureContext,
121
+ model,
122
+ basePath,
123
+ shouldRetainAudio: () => audioChannel.hasSubscribers,
124
+ })
125
+
126
+ const reference = new WeakRef(session)
127
+ liveSessions.add(reference)
128
+
129
+ /**
130
+ * @param {boolean} [failed] - Whether the connection ended abnormally, so a response still in
131
+ * flight is reported as failed rather than as a clean finish.
132
+ */
133
+ const finalize = (failed = false) => {
134
+ liveSessions.delete(reference)
135
+ session.finishSession(Date.now(), failed)
136
+ }
137
+
138
+ const connection = { session, finalize }
139
+ attachSocketClose(emitter, finalize)
140
+ return connection
141
+ }
142
+
143
+ // Close codes that mean the conversation ended on purpose: a normal close, the peer going away, and
144
+ // the "no status received" code `ws` reports for a close frame that carried none. Anything else — a
145
+ // dropped connection (1006), an internal error (1011) — cut a response short, so a turn still in
146
+ // flight is a failure rather than a clean finish.
147
+ const NORMAL_CLOSE_CODES = new Set([1000, 1001, 1005])
148
+
149
+ /**
150
+ * @param {unknown} code
151
+ */
152
+ function isAbnormalClose (code) {
153
+ return typeof code === 'number' && !NORMAL_CLOSE_CODES.has(code)
154
+ }
155
+
156
+ /**
157
+ * Finalize when the server closes the connection, so a caller that never calls `close()` — or whose
158
+ * connection drops — still reports its turns. `close` alone covers both transports: `ws` emits
159
+ * `error` then always `close`, and a global `WebSocket` that fails to connect does the same.
160
+ *
161
+ * @param {object} emitter
162
+ * @param {(failed?: boolean) => void} finalize
163
+ */
164
+ function attachSocketClose (emitter, finalize) {
165
+ try {
166
+ const { socket } = emitter
167
+ if (socket == null) return
168
+
169
+ // `ws` sockets are Node EventEmitters and also expose `addEventListener`, so check `on` first.
170
+ // The close code arrives as an argument on the `ws` surface and on the `CloseEvent` on the DOM
171
+ // one; an app-initiated `close()` goes through the shim below instead and is never flagged.
172
+ if (typeof socket.on === 'function') {
173
+ socket.once('close', code => finalize(isAbnormalClose(code)))
174
+ } else if (typeof socket.addEventListener === 'function') {
175
+ socket.addEventListener('close', event => finalize(isAbnormalClose(event?.code)), { once: true })
176
+ }
177
+ } catch (error) {
178
+ log.debug('Error attaching OpenAI realtime close listener: %s', error?.message)
179
+ }
180
+ }
181
+
182
+ /**
183
+ * @param {object} emitter
184
+ * @returns {{ session: RealtimeSession, finalize: () => void } | undefined}
185
+ */
186
+ function getConnection (emitter) {
187
+ const connection = connections.get(emitter)
188
+ if (connection !== undefined) return connection
189
+
190
+ // Nothing is listening, so never allocate. Checked only on the miss path, so an established
191
+ // connection pays one WeakMap lookup per event.
192
+ if (!turnChannel.start.hasSubscribers) return
193
+
194
+ const created = createConnection(emitter)
195
+ connections.set(emitter, created)
196
+ return created
197
+ }
198
+
199
+ /**
200
+ * Wrap the emitter's `_emit` — the single funnel every server event passes through, ahead of the
201
+ * app's own listeners.
202
+ *
203
+ * `_emit` is inherited from the SDK's generic `EventEmitter`, which its streaming helpers also use;
204
+ * `shimmer.wrap` defines an own property on the realtime prototype instead of touching that shared
205
+ * one.
206
+ *
207
+ * @param {object} prototype
208
+ */
209
+ function patchRealtimeEmitter (prototype) {
210
+ if (prototype == null) return
211
+
212
+ shimmer.wrap(prototype, '_emit', _emit => function (name, event) {
213
+ // `_emit` fires twice per server event — once as `event`, once as the event's own type — so the
214
+ // second half of the traffic is eliminated by a pointer compare before any other work.
215
+ if (name === 'event') {
216
+ const connection = getConnection(this)
217
+ // Observed before the app's listeners run: this feature is timestamps, and the app's own
218
+ // audio-playback callbacks would otherwise land inside the measured window.
219
+ if (connection !== undefined) connection.session.onServerEvent(event, Date.now())
220
+ }
221
+
222
+ return _emit.apply(this, arguments)
223
+ })
224
+ }
225
+
226
+ /**
227
+ * Wrap a concrete realtime transport's `send` (every client event) and `close` (session end).
228
+ *
229
+ * @param {object} prototype
230
+ */
231
+ function patchRealtimeTransport (prototype) {
232
+ if (prototype == null) return
233
+
234
+ shimmer.wrap(prototype, 'send', send => function (event) {
235
+ // Recorded only after the send succeeds, so a failed send doesn't attribute unsent audio to the
236
+ // next turn.
237
+ const result = send.apply(this, arguments)
238
+
239
+ const connection = getConnection(this)
240
+ if (connection !== undefined) connection.session.onClientEvent(event, Date.now())
241
+
242
+ return result
243
+ })
244
+
245
+ shimmer.wrap(prototype, 'close', close => function () {
246
+ const connection = connections.get(this)
247
+ try {
248
+ return close.apply(this, arguments)
249
+ } finally {
250
+ // In a `finally` deliberately. Every realtime transport wraps `socket.close()` in its own
251
+ // try/catch and routes a failure to `_onError` (which rejects a promise rather than
252
+ // rethrowing), so this call does not throw and the two placements are equivalent today —
253
+ // but if that ever changes, finalizing is what keeps the session from leaking. The socket's
254
+ // close event is the other half of that guarantee.
255
+ connection?.finalize()
256
+ }
257
+ })
258
+ }
259
+
260
+ /**
261
+ * Realtime is a large wrapping surface that buffers audio in memory, so it can be turned off on its
262
+ * own without giving up the rest of the OpenAI integration.
263
+ *
264
+ * Read through every configuration source, not just `process.env`: this is an operator-facing kill
265
+ * switch, and an org that sets it through Fleet Automation or local stable config would otherwise
266
+ * see it reported as disabled in configuration telemetry while the patching stayed on. Resolving it
267
+ * here also applies the option's registered boolean parser and its `true` default.
268
+ */
269
+ function realtimeEnabled () {
270
+ return getValueFromEnvSources('DD_OPENAI_REALTIME_ENABLED') !== false
271
+ }
272
+
273
+ /**
274
+ * Submit whatever live sessions still hold, at process exit.
275
+ *
276
+ * This is the only backstop for a connection the app drops without closing. A `FinalizationRegistry`
277
+ * would not help: the socket's own `message` handler closes over the emitter, and libuv holds the
278
+ * socket for as long as the connection is open, so the emitter is never unreachable while there is
279
+ * anything left to report.
280
+ */
281
+ function flushLiveSessions () {
282
+ for (const reference of liveSessions) {
283
+ const session = reference.deref()
284
+ if (session === undefined) {
285
+ liveSessions.delete(reference)
286
+ continue
287
+ }
288
+ // Idempotent, so a session already closed by `close()` or a socket close costs nothing here.
289
+ session.finishSession(Date.now())
290
+ }
291
+ liveSessions.clear()
292
+ }
293
+
294
+ globalThis[Symbol.for('dd-trace')]?.beforeExitHandlers?.add(flushLiveSessions)
295
+
296
+ module.exports = {
297
+ flushLiveSessions,
298
+ patchRealtimeEmitter,
299
+ patchRealtimeTransport,
300
+ realtimeEnabled,
301
+ }