dd-trace 6.7.0 → 6.9.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 (272) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +14 -8
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
  20. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  21. package/ci/test-optimization-validation/generated-files.js +47 -37
  22. package/ci/test-optimization-validation/generated-test-contract.js +9 -7
  23. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  24. package/ci/test-optimization-validation/literal-glob.js +52 -0
  25. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  26. package/ci/test-optimization-validation/manifest-scaffold.js +301 -52
  27. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  28. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  29. package/ci/test-optimization-validation/offline-output.js +6 -6
  30. package/ci/test-optimization-validation/package-check.js +94 -0
  31. package/ci/test-optimization-validation/plan-writer.js +183 -10
  32. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  33. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  34. package/ci/test-optimization-validation/redaction.js +18 -27
  35. package/ci/test-optimization-validation/report-writer.js +182 -48
  36. package/ci/test-optimization-validation/result-semantics.js +20 -3
  37. package/ci/test-optimization-validation/runner-command.js +41 -21
  38. package/ci/test-optimization-validation/runner-contract.js +79 -43
  39. package/ci/test-optimization-validation/safe-files.js +4 -4
  40. package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
  41. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  42. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  43. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  44. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  45. package/ci/test-optimization-validation/test-output.js +2 -1
  46. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  47. package/ci/vitest-no-worker-init-setup.mjs +377 -84
  48. package/index.d.ts +175 -14
  49. package/init.js +1 -17
  50. package/initialize.mjs +11 -0
  51. package/loader-hook.mjs +6 -4
  52. package/package.json +12 -4
  53. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  54. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  55. package/packages/datadog-esbuild/index.js +4 -2
  56. package/packages/datadog-esbuild/src/utils.js +17 -2
  57. package/packages/datadog-instrumentations/src/ai.js +61 -34
  58. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +85 -9
  60. package/packages/datadog-instrumentations/src/cucumber.js +131 -51
  61. package/packages/datadog-instrumentations/src/dns.js +21 -2
  62. package/packages/datadog-instrumentations/src/electron.js +1 -0
  63. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  64. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  65. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  66. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  67. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  68. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  69. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  70. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  71. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  72. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -0
  73. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  74. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  75. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  76. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  77. package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
  78. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  79. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  80. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  81. package/packages/datadog-instrumentations/src/jest.js +986 -246
  82. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  83. package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
  84. package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
  85. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
  86. package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
  87. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  88. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  89. package/packages/datadog-instrumentations/src/next.js +155 -23
  90. package/packages/datadog-instrumentations/src/openai.js +14 -0
  91. package/packages/datadog-instrumentations/src/pg.js +180 -6
  92. package/packages/datadog-instrumentations/src/playwright.js +176 -81
  93. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  94. package/packages/datadog-instrumentations/src/promise.js +3 -3
  95. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  96. package/packages/datadog-instrumentations/src/router.js +195 -19
  97. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
  98. package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
  99. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  100. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  101. package/packages/datadog-instrumentations/src/webdriverio.js +1090 -0
  102. package/packages/datadog-instrumentations/src/when.js +3 -3
  103. package/packages/datadog-instrumentations/src/ws.js +5 -1
  104. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  105. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  106. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  107. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  108. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  109. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  110. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  111. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  112. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  113. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  114. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
  115. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  116. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  117. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  118. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  119. package/packages/datadog-plugin-electron/src/net.js +10 -4
  120. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  121. package/packages/datadog-plugin-fs/src/index.js +0 -4
  122. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  123. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  124. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  125. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  126. package/packages/datadog-plugin-http/src/client.js +3 -2
  127. package/packages/datadog-plugin-http2/src/client.js +1 -1
  128. package/packages/datadog-plugin-jest/src/index.js +3 -21
  129. package/packages/datadog-plugin-jest/src/util.js +4 -1
  130. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  131. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  132. package/packages/datadog-plugin-mocha/src/index.js +342 -6
  133. package/packages/datadog-plugin-next/src/index.js +57 -9
  134. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  135. package/packages/datadog-plugin-pg/src/index.js +65 -1
  136. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  137. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  138. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  139. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  140. package/packages/datadog-plugin-undici/src/index.js +2 -1
  141. package/packages/datadog-plugin-vitest/src/index.js +179 -18
  142. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  143. package/packages/datadog-webpack/index.js +2 -2
  144. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  145. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  146. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  147. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  148. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  149. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  150. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  151. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  152. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  153. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  154. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  155. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  156. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  157. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  158. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  159. package/packages/dd-trace/src/appsec/index.js +48 -476
  160. package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
  161. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  162. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  163. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  164. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  165. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
  166. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
  167. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
  168. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  169. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  170. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  171. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  172. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  173. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  174. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +2 -2
  175. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  176. package/packages/dd-trace/src/config/defaults.js +14 -6
  177. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
  178. package/packages/dd-trace/src/config/helper.js +2 -0
  179. package/packages/dd-trace/src/config/index.js +31 -5
  180. package/packages/dd-trace/src/config/parsers.js +19 -1
  181. package/packages/dd-trace/src/config/remote_config.js +1 -1
  182. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  183. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  184. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  185. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  186. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  187. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  188. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  189. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  190. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  191. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  192. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  193. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  194. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  195. package/packages/dd-trace/src/flare/index.js +2 -2
  196. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  197. package/packages/dd-trace/src/guardrails/log.js +1 -10
  198. package/packages/dd-trace/src/id.js +1 -1
  199. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  200. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  201. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  202. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  203. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  204. package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
  205. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  206. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  207. package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
  208. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  209. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  210. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  211. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  212. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  213. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  214. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  215. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  216. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  217. package/packages/dd-trace/src/llmobs/sdk.js +152 -78
  218. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  219. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  220. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  221. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  222. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  223. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  224. package/packages/dd-trace/src/log/channels.js +1 -9
  225. package/packages/dd-trace/src/log/levels.js +12 -0
  226. package/packages/dd-trace/src/noop/proxy.js +2 -2
  227. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  228. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  229. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  230. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  231. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  232. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
  233. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  234. package/packages/dd-trace/src/opentracing/span.js +1 -1
  235. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  236. package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
  237. package/packages/dd-trace/src/plugins/index.js +2 -0
  238. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  239. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  240. package/packages/dd-trace/src/plugins/util/ci.js +13 -6
  241. package/packages/dd-trace/src/plugins/util/git.js +7 -5
  242. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
  243. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  244. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  245. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  246. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  247. package/packages/dd-trace/src/plugins/util/test.js +356 -86
  248. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  249. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  250. package/packages/dd-trace/src/plugins/util/web.js +24 -2
  251. package/packages/dd-trace/src/priority_sampler.js +6 -3
  252. package/packages/dd-trace/src/profiler.js +1 -1
  253. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  254. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  255. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  256. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  257. package/packages/dd-trace/src/proxy.js +1 -0
  258. package/packages/dd-trace/src/ritm.js +10 -20
  259. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  260. package/packages/dd-trace/src/sampling_rule.js +4 -2
  261. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  262. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  263. package/packages/dd-trace/src/startup-log.js +3 -0
  264. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  265. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  266. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  267. package/packages/dd-trace/src/util.js +16 -0
  268. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  269. package/vendor/dist/pprof-format/index.js +1 -1
  270. package/vendor/dist/source-map/index.js +1 -1
  271. package/version.js +10 -8
  272. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -1,8 +1,90 @@
1
1
  'use strict'
2
2
 
3
+ const log = require('../../log')
4
+
5
+ class NoopDataset {
6
+ #name
7
+ #records
8
+
9
+ constructor (name = '', options = {}) {
10
+ this.#name = name
11
+ this.#records = (typeof options === 'string' ? [] : (options.records ?? [])).map(record => ({
12
+ id: record.id ?? null,
13
+ input: record.inputData,
14
+ expectedOutput: record.expectedOutput ?? null,
15
+ metadata: record.metadata ?? {},
16
+ }))
17
+ }
18
+
19
+ addRecord (input, expectedOutput, metadata) {
20
+ this.#records.push({ id: null, input, expectedOutput: expectedOutput ?? null, metadata: metadata ?? {} })
21
+ return this
22
+ }
23
+
24
+ push () {
25
+ return Promise.resolve({ pushedCount: 0, totalCount: 0 })
26
+ }
27
+
28
+ name () {
29
+ return this.#name
30
+ }
31
+
32
+ id () {
33
+ return null
34
+ }
35
+
36
+ projectId () {
37
+ return null
38
+ }
39
+
40
+ version () {
41
+ return null
42
+ }
43
+
44
+ latestVersion () {
45
+ return null
46
+ }
47
+
48
+ records () {
49
+ return [...this.#records]
50
+ }
51
+
52
+ recordIds () {
53
+ return []
54
+ }
55
+
56
+ url () {
57
+ return null
58
+ }
59
+ }
60
+
61
+ class NoopExperiment {
62
+ #name
63
+
64
+ constructor (name = '') {
65
+ this.#name = name
66
+ }
67
+
68
+ name () {
69
+ return this.#name
70
+ }
71
+
72
+ experimentId () {
73
+ return null
74
+ }
75
+
76
+ url () {
77
+ return null
78
+ }
79
+
80
+ run () {
81
+ return Promise.resolve({ experimentId: null, rows: [], url: null })
82
+ }
83
+ }
84
+
3
85
  // No-op Experiments used when LLM Observability is disabled or the API/APP keys
4
- // are not configured. Every operation throws a clear, actionable error rather
5
- // than silently doing nothing, so misconfiguration surfaces immediately.
86
+ // are not configured. Operations warn and return inert objects rather than
87
+ // throwing, so intentionally disabled experiments remain graceful.
6
88
  class NoopExperiments {
7
89
  #reason
8
90
 
@@ -10,20 +92,23 @@ class NoopExperiments {
10
92
  this.#reason = reason || 'LLMObs experiments are not available'
11
93
  }
12
94
 
13
- #unavailable () {
14
- return new Error(`LLMObs experiments unavailable: ${this.#reason}`)
95
+ #warn () {
96
+ log.warn('LLMObs experiments unavailable: %s', this.#reason)
15
97
  }
16
98
 
17
- createDataset () {
18
- throw this.#unavailable()
99
+ createDataset (name, options = {}) {
100
+ this.#warn()
101
+ return new NoopDataset(name, options)
19
102
  }
20
103
 
21
- pullDataset () {
22
- return Promise.reject(this.#unavailable())
104
+ pullDataset (name) {
105
+ this.#warn()
106
+ return Promise.resolve(new NoopDataset(name))
23
107
  }
24
108
 
25
- experiment () {
26
- throw this.#unavailable()
109
+ experiment (options = {}) {
110
+ this.#warn()
111
+ return new NoopExperiment(options.name)
27
112
  }
28
113
  }
29
114
 
@@ -22,13 +22,24 @@ class Row {
22
22
  }
23
23
  }
24
24
 
25
+ class ExperimentRun {
26
+ constructor (fields) {
27
+ this.runId = fields.runId
28
+ this.runIteration = fields.runIteration
29
+ this.rows = fields.rows
30
+ this.summaryEvaluations = fields.summaryEvaluations
31
+ }
32
+ }
33
+
25
34
  // Returned by Experiment.run().
26
35
  class ExperimentResult {
27
- constructor (experimentId, rows, url) {
36
+ constructor (experimentId, rows, url, runs = [], summaryEvaluations = {}) {
28
37
  this.experimentId = experimentId
29
38
  this.rows = rows
30
39
  this.url = url
40
+ this.runs = runs
41
+ this.summaryEvaluations = summaryEvaluations
31
42
  }
32
43
  }
33
44
 
34
- module.exports = { Row, ExperimentResult }
45
+ module.exports = { Row, ExperimentRun, ExperimentResult }
@@ -0,0 +1,220 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+
5
+ // Matches the backend and dd-trace-py evaluator metric label contract.
6
+ const EVALUATOR_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/
7
+
8
+ /**
9
+ * @typedef {null | string | number | boolean | NormalizedJsonValue[] | { [key: string]: NormalizedJsonValue }}
10
+ * NormalizedJsonValue
11
+ */
12
+
13
+ /**
14
+ * @param {object | null | undefined} value
15
+ * @returns {boolean}
16
+ */
17
+ function hasEntries (value) {
18
+ if (!value) return false
19
+ for (const key of Object.keys(value)) {
20
+ if (Object.hasOwn(value, key)) return true
21
+ }
22
+ return false
23
+ }
24
+
25
+ /**
26
+ * @param {string} name
27
+ */
28
+ function validateEvaluatorName (name) {
29
+ if (typeof name !== 'string') throw new TypeError('Evaluator name must be a string')
30
+ if (name.length === 0) throw new Error('Evaluator name cannot be empty')
31
+ if (!EVALUATOR_NAME_PATTERN.test(name)) {
32
+ throw new Error(
33
+ `Evaluator name '${name}' is invalid. Name must contain only alphanumeric characters, underscores, and hyphens.`
34
+ )
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @param {(...args: unknown[]) => unknown} fn
40
+ * @param {string} fallback
41
+ * @returns {string}
42
+ */
43
+ function functionName (fn, fallback) {
44
+ return typeof fn.name === 'string' && fn.name.length > 0 ? fn.name : fallback
45
+ }
46
+
47
+ /**
48
+ * @param {unknown} evaluators
49
+ * @param {string} kind
50
+ * @returns {Array<[string, (...args: unknown[]) => unknown]>}
51
+ */
52
+ function normalizeEvaluators (evaluators, kind) {
53
+ if (evaluators == null) return []
54
+
55
+ const normalized = []
56
+ if (Array.isArray(evaluators)) {
57
+ const indexesByName = new Map()
58
+ for (let i = 0; i < evaluators.length; i++) {
59
+ const evaluator = evaluators[i]
60
+ if (typeof evaluator !== 'function') throw new TypeError(`${kind} evaluator must be a function`)
61
+ const name = functionName(evaluator, `${kind}_evaluator_${i}`)
62
+ validateEvaluatorName(name)
63
+ if (indexesByName.has(name)) {
64
+ log.warn('Duplicate %s evaluator name %s; previous evaluator will be overwritten', kind, name)
65
+ normalized[indexesByName.get(name)] = [name, evaluator]
66
+ } else {
67
+ indexesByName.set(name, normalized.length)
68
+ normalized.push([name, evaluator])
69
+ }
70
+ }
71
+ return normalized
72
+ }
73
+
74
+ if (typeof evaluators !== 'object') {
75
+ throw new TypeError(`${kind} evaluators must be an array of functions or an object keyed by evaluator name`)
76
+ }
77
+
78
+ for (const [name, evaluator] of Object.entries(evaluators)) {
79
+ validateEvaluatorName(name)
80
+ if (typeof evaluator !== 'function') throw new TypeError(`${kind} evaluator '${name}' must be a function`)
81
+ normalized.push([name, evaluator])
82
+ }
83
+ return normalized
84
+ }
85
+
86
+ /**
87
+ * @param {unknown} value
88
+ * @returns {string}
89
+ */
90
+ function inferMetricType (value) {
91
+ if (typeof value === 'boolean') return 'boolean'
92
+ if (typeof value === 'number' && Number.isFinite(value)) return 'score'
93
+ if (typeof value === 'string') return 'categorical'
94
+ return 'json'
95
+ }
96
+
97
+ /**
98
+ * @param {unknown} value
99
+ * @param {WeakSet<object>} seen
100
+ * @returns {NormalizedJsonValue}
101
+ */
102
+ function normalizeJsonValue (value, seen) {
103
+ if (value === null) return null
104
+
105
+ const valueType = typeof value
106
+ if (valueType === 'string' || valueType === 'boolean') return value
107
+ if (valueType === 'number') return Number.isFinite(value) ? value : String(value)
108
+ if (valueType === 'bigint') return value.toString()
109
+ if (valueType === 'undefined') return null
110
+ if (valueType === 'symbol') return value.toString()
111
+ if (valueType === 'function') return { type: 'function', name: value.name || '<anonymous>' }
112
+
113
+ if (value instanceof Date) return value.toISOString()
114
+ if (value instanceof Error) {
115
+ return { type: value.name, message: value.message, stack: value.stack ?? '' }
116
+ }
117
+
118
+ if (seen.has(value)) return '[Circular]'
119
+ seen.add(value)
120
+
121
+ if (Array.isArray(value)) {
122
+ const normalized = new Array(value.length)
123
+ for (let i = 0; i < value.length; i++) normalized[i] = normalizeJsonValue(value[i], seen)
124
+ seen.delete(value)
125
+ return normalized
126
+ }
127
+
128
+ const normalized = {}
129
+ for (const [key, nestedValue] of Object.entries(value)) {
130
+ normalized[key] = normalizeJsonValue(nestedValue, seen)
131
+ }
132
+ seen.delete(value)
133
+ return normalized
134
+ }
135
+
136
+ /**
137
+ * @param {unknown} value
138
+ * @returns {{ [key: string]: NormalizedJsonValue }}
139
+ */
140
+ function normalizeJsonMetricValue (value) {
141
+ const normalized = normalizeJsonValue(value, new WeakSet())
142
+ if (normalized !== null && typeof normalized === 'object' && !Array.isArray(normalized)) return normalized
143
+ return { value: normalized }
144
+ }
145
+
146
+ /**
147
+ * @param {unknown} value
148
+ * @returns {string}
149
+ */
150
+ function stringify (value) {
151
+ if (value == null) return ''
152
+ if (typeof value === 'string') return value.toLowerCase()
153
+ if (typeof value === 'object') {
154
+ try {
155
+ const stringified = JSON.stringify(value)
156
+ if (stringified !== undefined) return stringified.toLowerCase()
157
+ } catch {}
158
+ }
159
+ return String(value).toLowerCase()
160
+ }
161
+
162
+ /**
163
+ * @param {Record<string, unknown> | undefined} userTags
164
+ * @param {Record<string, unknown>} autoTags
165
+ * @returns {string[]}
166
+ */
167
+ function buildTags (userTags, autoTags) {
168
+ const tags = new Map()
169
+ if ((userTags) != null) {
170
+ for (const [key, value] of Object.entries(userTags)) {
171
+ tags.set(key, `${key}:${value}`)
172
+ }
173
+ }
174
+ for (const [key, value] of Object.entries(autoTags)) {
175
+ if (value !== undefined && value !== null && value !== '') tags.set(key, `${key}:${value}`)
176
+ }
177
+ return [...tags.values()]
178
+ }
179
+
180
+ /**
181
+ * @param {Record<string, unknown> | undefined} userTags
182
+ * @param {Record<string, unknown>} autoTags
183
+ * @returns {Record<string, unknown>}
184
+ */
185
+ function buildExperimentTagObject (userTags, autoTags) {
186
+ return userTags ? { ...userTags, ...autoTags } : { ...autoTags }
187
+ }
188
+
189
+ /**
190
+ * @param {number} ms
191
+ * @returns {Promise<void>}
192
+ */
193
+ function sleep (ms) {
194
+ if (ms <= 0) return Promise.resolve()
195
+ return new Promise((resolve) => setTimeout(resolve, ms))
196
+ }
197
+
198
+ /**
199
+ * @param {Record<string, unknown> | undefined} recordMetadata
200
+ * @param {Record<string, unknown>} config
201
+ * @returns {Record<string, unknown>}
202
+ */
203
+ function buildSpanMetadata (recordMetadata, config) {
204
+ return recordMetadata
205
+ ? { ...recordMetadata, experiment_config: config }
206
+ : { experiment_config: config }
207
+ }
208
+
209
+ module.exports = {
210
+ buildExperimentTagObject,
211
+ buildSpanMetadata,
212
+ buildTags,
213
+ hasEntries,
214
+ inferMetricType,
215
+ normalizeEvaluators,
216
+ normalizeJsonMetricValue,
217
+ sleep,
218
+ stringify,
219
+ validateEvaluatorName,
220
+ }
@@ -80,6 +80,8 @@ class NoopLLMObs {
80
80
 
81
81
  submitEvaluation (llmobsSpanContext, options) {}
82
82
 
83
+ submitFeedback (options) {}
84
+
83
85
  flush () {}
84
86
 
85
87
  registerProcessor (processor) {}
@@ -118,7 +118,7 @@ class DdTelemetryPlugin extends BaseLLMObsPlugin {
118
118
  * @returns {string | undefined}
119
119
  */
120
120
  findToolName (toolName, toolDescription) {
121
- if (Number.isNaN(Number.parseInt(toolName))) return toolName
121
+ if (Number.isNaN(Number.parseInt(toolName, 10))) return toolName
122
122
 
123
123
  for (const availableTool of this.#availableTools) {
124
124
  const description = availableTool.description
@@ -322,7 +322,7 @@ function getToolNameFromTags (tags) {
322
322
  const toolName = tags['ai.toolCall.name']
323
323
  if (!toolName) return null
324
324
 
325
- const parsedToolName = Number.parseInt(toolName)
325
+ const parsedToolName = Number.parseInt(toolName, 10)
326
326
  if (!Number.isNaN(parsedToolName)) return null
327
327
 
328
328
  return toolName
@@ -93,7 +93,7 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
93
93
 
94
94
  const { usage } = chunk
95
95
  if (usage) {
96
- const responseUsage = response.usage ?? (response.usage = { input_tokens: 0, output_tokens: 0 })
96
+ const responseUsage = (response.usage ??= { input_tokens: 0, output_tokens: 0 })
97
97
  responseUsage.output_tokens = usage.output_tokens
98
98
 
99
99
  const cacheCreationTokens = usage.cache_creation_input_tokens
@@ -72,10 +72,10 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
72
72
  const cacheWriteTokenCount = headers['x-amzn-bedrock-cache-write-input-token-count']
73
73
 
74
74
  pendingTokenHeaders.set(requestId, {
75
- inputTokensFromHeaders: inputTokenCount && Number.parseInt(inputTokenCount),
76
- outputTokensFromHeaders: outputTokenCount && Number.parseInt(outputTokenCount),
77
- cacheReadTokensFromHeaders: cacheReadTokenCount && Number.parseInt(cacheReadTokenCount),
78
- cacheWriteTokensFromHeaders: cacheWriteTokenCount && Number.parseInt(cacheWriteTokenCount),
75
+ inputTokensFromHeaders: inputTokenCount && Number.parseInt(inputTokenCount, 10),
76
+ outputTokensFromHeaders: outputTokenCount && Number.parseInt(outputTokenCount, 10),
77
+ cacheReadTokensFromHeaders: cacheReadTokenCount && Number.parseInt(cacheReadTokenCount, 10),
78
+ cacheWriteTokensFromHeaders: cacheWriteTokenCount && Number.parseInt(cacheWriteTokenCount, 10),
79
79
  })
80
80
  })
81
81
 
@@ -138,7 +138,7 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
138
138
  #tagCommon ({ span, requestParams, textAndResponseReason, tokensFromHeaders }) {
139
139
  this._tagger.tagMetadata(span, {
140
140
  temperature: Number.parseFloat(requestParams.temperature) || 0,
141
- max_tokens: Number.parseInt(requestParams.maxTokens) || 0,
141
+ max_tokens: Number.parseInt(requestParams.maxTokens, 10) || 0,
142
142
  })
143
143
  this._tagger.tagLLMIO(span, requestParams.prompt, textAndResponseReason.messages)
144
144
  this._tagger.tagMetrics(span, extractTokens({
@@ -8,7 +8,7 @@ const { splitModel } = require('../../../../../datadog-plugin-claude-agent-sdk/s
8
8
  const subagentToolIds = new Set()
9
9
 
10
10
  function normalizeToolOutputString (raw) {
11
- const footerIndex = raw.search(/\s*agentId: [^\s]+ \(use SendMessage\b/)
11
+ const footerIndex = raw.search(/agentId: \S+ \(use SendMessage\b/)
12
12
  if (footerIndex === -1) return raw
13
13
 
14
14
  return raw.slice(0, footerIndex).trimEnd()
@@ -24,7 +24,7 @@ class GenAiLLMObsPlugin extends LLMObsPlugin {
24
24
  // Subscribe to streaming chunk events
25
25
  this.addSub('apm:google:genai:request:chunk', ({ ctx, chunk, done }) => {
26
26
  ctx.isStreaming = true
27
- ctx.chunks = ctx.chunks || []
27
+ ctx.chunks ||= []
28
28
 
29
29
  if (chunk) ctx.chunks.push(chunk)
30
30
  if (!done) return
@@ -36,7 +36,7 @@ class LangChainLLMObsHandler {
36
36
  const runIdBase = runId ? runId.split('-').slice(0, -1).join('-') : ''
37
37
 
38
38
  const responseMetadata = message.response_metadata || {}
39
- usage = usage || responseMetadata.usage || responseMetadata.tokenUsage || {}
39
+ usage ||= responseMetadata.usage || responseMetadata.tokenUsage || {}
40
40
 
41
41
  const inputTokens = usage.promptTokens || usage.inputTokens || usage.prompt_tokens || usage.input_tokens || 0
42
42
  const outputTokens =
@@ -113,7 +113,7 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
113
113
  }
114
114
 
115
115
  if (maxTokens) {
116
- metadata.maxTokens = Number.parseInt(maxTokens)
116
+ metadata.maxTokens = Number.parseInt(maxTokens, 10)
117
117
  }
118
118
 
119
119
  this._tagger.tagMetadata(span, metadata)