dd-trace 6.8.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 (179) 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 +9 -3
  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/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +30 -34
  44. package/index.d.ts +98 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +5 -3
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  55. package/packages/datadog-instrumentations/src/dns.js +21 -2
  56. package/packages/datadog-instrumentations/src/electron.js +1 -0
  57. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  58. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  59. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  60. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +33 -9
  62. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +118 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  66. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  67. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  68. package/packages/datadog-instrumentations/src/jest.js +979 -233
  69. package/packages/datadog-instrumentations/src/mocha/main.js +107 -9
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +64 -42
  71. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  72. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  73. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  74. package/packages/datadog-instrumentations/src/next.js +155 -23
  75. package/packages/datadog-instrumentations/src/openai.js +14 -0
  76. package/packages/datadog-instrumentations/src/pg.js +180 -6
  77. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  78. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  79. package/packages/datadog-instrumentations/src/promise.js +3 -3
  80. package/packages/datadog-instrumentations/src/router.js +195 -19
  81. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -10
  82. package/packages/datadog-instrumentations/src/vitest-main.js +360 -56
  83. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  84. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  85. package/packages/datadog-instrumentations/src/webdriverio.js +194 -18
  86. package/packages/datadog-instrumentations/src/when.js +3 -3
  87. package/packages/datadog-instrumentations/src/ws.js +5 -1
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  89. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  90. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  91. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  92. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  93. package/packages/datadog-plugin-electron/src/net.js +10 -4
  94. package/packages/datadog-plugin-http/src/client.js +1 -1
  95. package/packages/datadog-plugin-http2/src/client.js +1 -1
  96. package/packages/datadog-plugin-jest/src/index.js +1 -18
  97. package/packages/datadog-plugin-jest/src/util.js +2 -0
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  99. package/packages/datadog-plugin-mocha/src/index.js +318 -1
  100. package/packages/datadog-plugin-next/src/index.js +57 -9
  101. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  102. package/packages/datadog-plugin-pg/src/index.js +65 -1
  103. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  104. package/packages/datadog-plugin-vitest/src/index.js +95 -7
  105. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  106. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  107. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  108. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  109. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  110. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  111. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  112. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  113. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  115. package/packages/dd-trace/src/appsec/index.js +48 -476
  116. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  117. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  118. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  120. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  122. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  123. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  124. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  127. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  128. package/packages/dd-trace/src/config/helper.js +2 -0
  129. package/packages/dd-trace/src/config/index.js +16 -5
  130. package/packages/dd-trace/src/config/remote_config.js +1 -1
  131. package/packages/dd-trace/src/config/supported-configurations.json +8 -0
  132. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  133. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  134. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  135. package/packages/dd-trace/src/guardrails/log.js +1 -10
  136. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  137. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  138. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  139. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  140. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  141. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  142. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  143. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  144. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  145. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  146. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  147. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  148. package/packages/dd-trace/src/log/channels.js +1 -9
  149. package/packages/dd-trace/src/log/levels.js +12 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  151. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  153. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  154. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  155. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  156. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  157. package/packages/dd-trace/src/plugins/index.js +1 -0
  158. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  159. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  160. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  161. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  162. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  163. package/packages/dd-trace/src/plugins/util/test.js +319 -75
  164. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  165. package/packages/dd-trace/src/plugins/util/web.js +23 -1
  166. package/packages/dd-trace/src/profiler.js +1 -1
  167. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  168. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  169. package/packages/dd-trace/src/proxy.js +1 -0
  170. package/packages/dd-trace/src/ritm.js +5 -0
  171. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  172. package/packages/dd-trace/src/sampling_rule.js +4 -2
  173. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  174. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  175. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  176. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  177. package/vendor/dist/pprof-format/index.js +1 -1
  178. package/version.js +10 -8
  179. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -3,7 +3,6 @@
3
3
  const { channel } = require('dc-polyfill')
4
4
 
5
5
  const { isError } = require('../util')
6
- const tracerVersion = require('../../../../package.json').version
7
6
  const logger = require('../log')
8
7
  const { getValueFromEnvSources } = require('../config/helper')
9
8
  const Span = require('../opentracing/span')
@@ -13,6 +12,17 @@ const {
13
12
  INPUT_VALUE,
14
13
  TRACE_ID,
15
14
  } = require('./constants/tags')
15
+ const {
16
+ FEEDBACK_METRIC_TYPES,
17
+ FEEDBACK_TARGET_KEYS,
18
+ buildMetricTags,
19
+ validateAssessment,
20
+ validateLabel,
21
+ validateMetricType,
22
+ validateMetricValue,
23
+ validateReasoning,
24
+ validateTimestamp,
25
+ } = require('./eval-metric')
16
26
  const {
17
27
  getFunctionArguments,
18
28
  validateKind,
@@ -392,92 +402,35 @@ class LLMObs extends NoopLLMObs {
392
402
  }
393
403
 
394
404
  const timestampMs = options.timestampMs || Date.now()
395
- if (typeof timestampMs !== 'number' || timestampMs < 0) {
396
- err = 'invalid_timestamp'
397
- throw new Error('timestampMs must be a non-negative integer. Evaluation metric data will not be sent')
398
- }
405
+ validateTimestamp(timestampMs, 'evaluation')
399
406
 
400
407
  const { label, value, tags, reasoning, assessment, metadata } = options
401
408
  const metricType = options.metricType?.toLowerCase()
402
- if (!label) {
403
- err = 'invalid_metric_label'
404
- throw new Error('label must be the specified name of the evaluation metric')
405
- }
406
- if (!metricType || !['categorical', 'score', 'boolean', 'json'].includes(metricType)) {
407
- err = 'invalid_metric_type'
408
- throw new Error('metricType must be one of "categorical", "score", "boolean" or "json"')
409
- }
410
- if (metricType === 'categorical' && typeof value !== 'string') {
411
- err = 'invalid_metric_value'
412
- throw new Error('value must be a string for a categorical metric.')
413
- }
414
- if (metricType === 'score' && typeof value !== 'number') {
415
- err = 'invalid_metric_value'
416
- throw new Error('value must be a number for a score metric.')
417
- }
418
- if (metricType === 'boolean' && typeof value !== 'boolean') {
419
- err = 'invalid_metric_value'
420
- throw new Error('value must be a boolean for a boolean metric')
421
- }
422
- if (metricType === 'json' && (typeof value !== 'object' || value == null || Array.isArray(value))) {
423
- err = 'invalid_metric_value'
424
- throw new Error('value must be a JSON object for a json metric')
425
- }
426
- if (assessment != null && assessment !== 'pass' && assessment !== 'fail') {
427
- err = 'invalid_assessment'
428
- throw new Error('assessment must be pass or fail')
429
- }
430
- if (reasoning != null && typeof reasoning !== 'string') {
431
- err = 'invalid_reasoning'
432
- throw new Error('reasoning must be a string')
433
- }
409
+ const labelValue = validateLabel(label, 'evaluation')
410
+ validateMetricType(metricType, 'evaluation')
411
+ validateMetricValue(metricType, value)
412
+ validateAssessment(assessment)
413
+ validateReasoning(reasoning)
434
414
  if (metadata != null && (typeof metadata !== 'object' || Array.isArray(metadata))) {
435
415
  err = 'invalid_metadata'
436
416
  throw new Error('metadata must be a JSON object')
437
417
  }
438
418
 
439
- const evaluationTags = {
440
- 'ddtrace.version': tracerVersion,
441
- ml_app: mlApp,
442
- }
443
-
444
- if (tags) {
445
- for (const key in tags) {
446
- const tag = tags[key]
447
- if (typeof tag === 'string') {
448
- evaluationTags[key] = tag
449
- } else if (typeof tag.toString === 'function') {
450
- evaluationTags[key] = tag.toString()
451
- } else if (tag == null) {
452
- evaluationTags[key] = Object.prototype.toString.call(tag)
453
- } else {
454
- // should be a rare case
455
- // every object in JS has a toString, otherwise every primitive has its own toString
456
- // null and undefined are handled above
457
- err = 'invalid_tags'
458
- throw new Error('Failed to parse tags. Tags for evaluation metrics must be strings')
459
- }
460
- }
461
- }
462
-
463
- // When OTel tracing is enabled, add source:otel tag to allow backend to wait for OTel span conversion
464
- if (this._config.DD_TRACE_OTEL_ENABLED) {
465
- evaluationTags.source = 'otel'
466
- }
467
-
468
419
  const payload = {
420
+ event_kind: 'evaluation',
469
421
  join_on: {
470
422
  span: {
471
423
  span_id: spanId,
472
424
  trace_id: traceId,
473
425
  },
474
426
  },
475
- label,
427
+ label: labelValue,
476
428
  metric_type: metricType,
477
429
  ml_app: mlApp,
478
430
  [`${metricType}_value`]: value,
479
431
  timestamp_ms: timestampMs,
480
- tags: Object.entries(evaluationTags).map(([key, value]) => `${key}:${value}`),
432
+ // When OTel tracing is enabled, `source:otel` lets the backend wait for OTel span conversion
433
+ tags: buildMetricTags(tags, mlApp, 'evaluation', this._config.DD_TRACE_OTEL_ENABLED),
481
434
  }
482
435
  if (reasoning != null) {
483
436
  payload.reasoning = reasoning
@@ -491,11 +444,139 @@ class LLMObs extends NoopLLMObs {
491
444
  const currentStore = storage.getStore()
492
445
  const routing = currentStore?.routingContext
493
446
  evalMetricAppendCh.publish({ payload, routing })
447
+ } catch (e) {
448
+ if (e.ddErrorTag) err = e.ddErrorTag
449
+ throw e
494
450
  } finally {
495
451
  telemetry.recordSubmitEvaluation(options, err)
496
452
  }
497
453
  }
498
454
 
455
+ /**
456
+ * Submits end-user feedback for a span, trace, session, or customer-defined entity.
457
+ *
458
+ * Exactly one target must be provided: `span` (as returned by `llmobs.exportSpan()`) or
459
+ * `spanId` to target a span, `traceId` a trace, `sessionId` a session, or `feedbackJoinKey`
460
+ * a customer-defined entity.
461
+ * `label`, `metricType`, `value` and `submitter` are required, and are validated at call time.
462
+ * @param {object} [options] - The feedback options.
463
+ * @param {string} [options.label] - The name of the feedback metric.
464
+ * @param {'categorical' | 'score' | 'boolean' | 'json' | 'text'} [options.metricType] - The value type.
465
+ * @param {string | number | boolean | Record<string, unknown>} [options.value] - The feedback value,
466
+ * matching `metricType`.
467
+ * @param {{ id: string, type?: string }} [options.submitter] - Who submitted the feedback.
468
+ * @param {{ traceId: string, spanId: string }} [options.span] - Span context to attach the feedback to.
469
+ * @param {string} [options.spanId] - ID of the span to attach the feedback to.
470
+ * @param {string} [options.traceId] - ID of the trace to attach the feedback to.
471
+ * @param {string} [options.sessionId] - ID of the session to attach the feedback to.
472
+ * @param {string} [options.feedbackJoinKey] - Customer-defined key to attach the feedback to.
473
+ * @param {Record<string, string>} [options.tags] - Tags to attach to the feedback.
474
+ * @param {string} [options.mlApp] - The ML app name. Defaults to the configured one.
475
+ * @param {number} [options.timestampMs] - When the feedback was generated. Defaults to now.
476
+ * @param {'pass' | 'fail'} [options.assessment] - Assessment of the feedback.
477
+ * @param {string} [options.reasoning] - Explanation of the feedback.
478
+ * @returns {void}
479
+ */
480
+ submitFeedback (options = {}) {
481
+ if (!this.enabled) return
482
+
483
+ let err = ''
484
+ let targetType = 'other'
485
+ let metricTypeTag = 'other'
486
+ try {
487
+ const { span, spanId, traceId, sessionId, feedbackJoinKey, submitter } = options
488
+
489
+ // Resolved before any validation so telemetry still reports the metric type of a
490
+ // submission that fails on an unrelated field.
491
+ const metricType = options.metricType?.toLowerCase()
492
+ if (FEEDBACK_METRIC_TYPES.includes(metricType)) metricTypeTag = metricType
493
+
494
+ // The intake keys feedback off a single top-level identifier, so more than one target
495
+ // would be ambiguous and none would leave the feedback unattached. `span` also carries a
496
+ // traceId, but passing it is wire-equivalent to passing its `spanId` directly.
497
+ let targetName, targetValue
498
+ let targetCount = 0
499
+ if (span != null) { targetName = 'span'; targetValue = span.spanId; targetCount++ }
500
+ if (spanId != null) { targetName = 'spanId'; targetValue = spanId; targetCount++ }
501
+ if (traceId != null) { targetName = 'traceId'; targetValue = traceId; targetCount++ }
502
+ if (sessionId != null) { targetName = 'sessionId'; targetValue = sessionId; targetCount++ }
503
+ if (feedbackJoinKey != null) { targetName = 'feedbackJoinKey'; targetValue = feedbackJoinKey; targetCount++ }
504
+
505
+ if (targetCount !== 1) {
506
+ err = 'invalid_target_count'
507
+ throw new Error(
508
+ 'Exactly one of `span`, `spanId`, `traceId`, `sessionId` or `feedbackJoinKey` ' +
509
+ 'must be specified to submit feedback.'
510
+ )
511
+ }
512
+
513
+ targetType = FEEDBACK_TARGET_KEYS[targetName]
514
+ if (typeof targetValue !== 'string' || !targetValue) {
515
+ if (targetName === 'span') {
516
+ err = 'invalid_span'
517
+ throw new TypeError(
518
+ '`span` must be an object containing a non-empty string spanId. ' +
519
+ '`llmobs.exportSpan()` can be used to generate this object from a given span.'
520
+ )
521
+ }
522
+ err = `invalid_${targetType}`
523
+ throw new TypeError(`\`${targetName}\` must be a non-empty string`)
524
+ }
525
+
526
+ if (typeof submitter?.id !== 'string' || !submitter.id) {
527
+ err = 'invalid_submitter'
528
+ throw new TypeError('submitter must be an object containing a non-empty string id')
529
+ }
530
+ if (submitter.type != null && typeof submitter.type !== 'string') {
531
+ err = 'invalid_submitter'
532
+ throw new TypeError('submitter.type must be a string')
533
+ }
534
+
535
+ const mlApp = options.mlApp || this._config.llmobs.mlApp
536
+ if (!mlApp) {
537
+ err = 'missing_ml_app'
538
+ throw new Error('ML App name is required for sending feedback. Feedback data will not be sent.')
539
+ }
540
+
541
+ const timestampMs = options.timestampMs || Date.now()
542
+ validateTimestamp(timestampMs, 'feedback')
543
+
544
+ const { label, value, tags, reasoning, assessment } = options
545
+ const labelValue = validateLabel(label, 'feedback')
546
+ validateMetricType(metricType, 'feedback')
547
+ validateMetricValue(metricType, value)
548
+ validateAssessment(assessment)
549
+ validateReasoning(reasoning)
550
+
551
+ const payload = {
552
+ event_kind: 'feedback',
553
+ [targetType]: targetValue,
554
+ label: labelValue,
555
+ metric_type: metricType,
556
+ ml_app: mlApp,
557
+ [`${metricType}_value`]: value,
558
+ timestamp_ms: timestampMs,
559
+ tags: buildMetricTags(tags, mlApp, 'feedback'),
560
+ submitter: submitter.type == null ? { id: submitter.id } : { id: submitter.id, type: submitter.type },
561
+ }
562
+ if (reasoning != null) {
563
+ payload.reasoning = reasoning
564
+ }
565
+ if (assessment != null) {
566
+ payload.assessment = assessment
567
+ }
568
+
569
+ const currentStore = storage.getStore()
570
+ const routing = currentStore?.routingContext
571
+ evalMetricAppendCh.publish({ payload, routing })
572
+ } catch (e) {
573
+ if (e.ddErrorTag) err = e.ddErrorTag
574
+ throw e
575
+ } finally {
576
+ telemetry.recordSubmitFeedback(metricTypeTag, targetType, err)
577
+ }
578
+ }
579
+
499
580
  annotationContext (options, fn) {
500
581
  if (!this.enabled) return fn()
501
582
 
@@ -183,6 +183,23 @@ function recordSubmitEvaluation (options, err, value = 1) {
183
183
  llmobsMetrics.count('evals_submitted', tags).inc(value)
184
184
  }
185
185
 
186
+ /**
187
+ * @param {string} metricType - The submitted metric type, `'other'` if unrecognized.
188
+ * @param {string} targetType - The payload key the feedback was attached to, `'other'` if unresolved.
189
+ * @param {string} err - The telemetry error tag, empty when the submission succeeded.
190
+ * @param {number} [value]
191
+ * @returns {void}
192
+ */
193
+ function recordSubmitFeedback (metricType, targetType, err, value = 1) {
194
+ const tags = {
195
+ error: Number(!!err),
196
+ metric_type: metricType,
197
+ target_type: targetType,
198
+ }
199
+ if (err) tags.error_type = err
200
+ llmobsMetrics.count('feedback_submitted', tags).inc(value)
201
+ }
202
+
186
203
  function recordLLMObsUserProcessorCalled (error, value = 1) {
187
204
  const tags = { error: error ? 1 : 0 }
188
205
  llmobsMetrics.count('user_processor_called', tags).inc(value)
@@ -201,5 +218,6 @@ module.exports = {
201
218
  recordUserFlush,
202
219
  recordExportSpan,
203
220
  recordSubmitEvaluation,
221
+ recordSubmitFeedback,
204
222
  recordLLMObsUserProcessorCalled,
205
223
  }
@@ -88,6 +88,9 @@ class BaseLLMObsWriter {
88
88
  }
89
89
 
90
90
  /**
91
+ * @param {Record<string, unknown>} event
92
+ * @param {{ apiKey?: string, site?: string }} [routing]
93
+ * @param {number} [byteLength]
91
94
  * @returns {boolean} `true` if the event was buffered, `false` if it was dropped
92
95
  * (e.g. the per-routing buffer was full). Callers that depend on the event
93
96
  * actually being submitted should check this value.
@@ -7,6 +7,8 @@ const { fetchAgentInfo } = require('../../agent/info')
7
7
 
8
8
  /**
9
9
  * @param {import('../../config/config-base')} config
10
+ * @param {(agentless: boolean) => void} setWritersAgentlessValue
11
+ * @returns {void}
10
12
  */
11
13
  function setAgentStrategy (config, setWritersAgentlessValue) {
12
14
  const agentlessEnabled = config.llmobs.agentlessEnabled
@@ -2,15 +2,7 @@
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
4
 
5
- const Level = {
6
- trace: 10,
7
- debug: 20,
8
- info: 30,
9
- warn: 40,
10
- error: 50,
11
- critical: 50,
12
- off: 100,
13
- }
5
+ const Level = require('./levels')
14
6
 
15
7
  const traceChannel = channel('datadog:log:trace')
16
8
  const debugChannel = channel('datadog:log:debug')
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ // The guardrails logger requires this file, so it has to parse on Node.js 0.8.
4
+ module.exports = {
5
+ trace: 10,
6
+ debug: 20,
7
+ info: 30,
8
+ warn: 40,
9
+ error: 50,
10
+ critical: 50,
11
+ off: 100
12
+ }
@@ -63,7 +63,7 @@ class ExposuresWriter extends BaseFFEWriter {
63
63
  * @param {import('../../config/config-base')} config - Tracer configuration object
64
64
  */
65
65
  constructor (config) {
66
- const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
66
+ const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
67
67
  const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
68
68
  const fullEndpoint = `${basePath}/${endpoint}`
69
69
 
@@ -18,7 +18,7 @@ function setAgentStrategy (config, setWriterEnabledValue) {
18
18
  }
19
19
 
20
20
  const endpoints = agentInfo.endpoints
21
- const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/+$/, '')
21
+ const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
22
22
  const hasEndpoint = Array.isArray(endpoints) &&
23
23
  endpoints.includes(normalizedPath) || endpoints.includes(normalizedPath + '/')
24
24
 
@@ -22,7 +22,7 @@ class BatchLogRecordProcessor {
22
22
  /**
23
23
  * Creates a new BatchLogRecordProcessor instance.
24
24
  *
25
- * @param {OtlpHttpLogExporter} exporter - Log processor for exporting batches to Datadog Agent
25
+ * @param {import('./otlp_http_log_exporter')} exporter - Log processor for exporting batches to Datadog Agent
26
26
  * @param {number} batchTimeout - Timeout in milliseconds for batch processing
27
27
  * @param {number} maxExportBatchSize - Maximum number of log records per batch
28
28
  */
@@ -9,6 +9,8 @@ const { METRIC_TYPES } = require('./constants')
9
9
  /**
10
10
  * @typedef {import('@opentelemetry/api').MetricOptions} MetricOptions
11
11
  * @typedef {import('@opentelemetry/core').InstrumentationScope} InstrumentationScope
12
+ * @typedef {Counter | UpDownCounter | Histogram | Gauge |
13
+ * ObservableGauge | ObservableCounter | ObservableUpDownCounter} Instrument
12
14
  */
13
15
 
14
16
  /**
@@ -25,7 +27,7 @@ class Meter {
25
27
  /**
26
28
  * Creates a new Meter instance.
27
29
  *
28
- * @param {MeterProvider} meterProvider - Parent meter provider
30
+ * @param {import('./meter_provider')} meterProvider - Parent meter provider
29
31
  * @param {InstrumentationScope} instrumentationScope - Instrumentation scope information
30
32
  * @param {string} [instrumentationScope.name] - Meter name (defaults to 'dd-trace-js')
31
33
  * @param {string} [instrumentationScope.version] - Meter version (defaults to tracer version)
@@ -50,11 +52,13 @@ class Meter {
50
52
  * Instruments are cached by type and normalized (lowercase) name.
51
53
  *
52
54
  *
55
+ * @template {Instrument} T
53
56
  * @param {string} name - Instrument name (will be normalized to lowercase)
54
57
  * @param {string} type - Instrument type (e.g., 'counter', 'histogram', 'gauge')
55
- * @param {Function} InstrumentClass - Constructor for the instrument type
56
- * @param {MetricOptions} [options] - Instrument options (description, unit, etc.)
57
- * @returns {Instrument} The instrument instance (new or cached)
58
+ * @param {new (name: string, options: object, instrumentationScope: InstrumentationScope,
59
+ * reader: object) => T} InstrumentClass - Constructor for the instrument type
60
+ * @param {MetricOptions} options - Instrument options (description, unit, etc.)
61
+ * @returns {T} The instrument instance (new or cached)
58
62
  */
59
63
  #getOrCreateInstrument (name, type, InstrumentClass, options) {
60
64
  const normalizedName = name.toLowerCase()
@@ -109,7 +109,7 @@ class PeriodicMetricReader {
109
109
  /**
110
110
  * Creates a new PeriodicMetricReader instance.
111
111
  *
112
- * @param {OtlpHttpMetricExporter} exporter - Metric exporter for sending to Datadog Agent
112
+ * @param {import('./otlp_http_metric_exporter')} exporter - Metric exporter for sending to Datadog Agent
113
113
  * @param {number} exportInterval - Export interval in milliseconds
114
114
  * @param {string} temporalityPreference - Temporality preference: DELTA, CUMULATIVE, or LOWMEMORY
115
115
  * @param {number} maxBatchedQueueSize - Maximum number of measurements to queue before dropping
@@ -38,6 +38,10 @@ function hrTimeToMilliseconds (hrTime) {
38
38
  /**
39
39
  * Several of these attributes are not yet supported by the Node.js OTel API.
40
40
  * We check for old equivalents where we can, but not all had equivalents.
41
+ *
42
+ * @param {string | undefined} spanName
43
+ * @param {import('@opentelemetry/api').SpanKind} kind
44
+ * @param {import('@opentelemetry/api').Attributes} attributes
41
45
  */
42
46
  function spanNameMapper (spanName, kind, attributes) {
43
47
  if (spanName) return spanName
@@ -169,7 +169,7 @@ function getTestSuiteLevelVisibilityTags (testSuiteSpan, testFramework) {
169
169
  }
170
170
 
171
171
  /**
172
- * Keeps non-TIA settings while disabling suite skipping and coverage collection.
172
+ * Keeps non-TIA settings while disabling suite skipping and per-suite coverage collection.
173
173
  *
174
174
  * @param {object} libraryConfig
175
175
  * @returns {object}
@@ -178,7 +178,6 @@ function disableTestImpactAnalysis (libraryConfig) {
178
178
  return Object.freeze({
179
179
  ...libraryConfig,
180
180
  isCodeCoverageEnabled: false,
181
- isCoverageReportUploadEnabled: false,
182
181
  isItrEnabled: false,
183
182
  isSuitesSkippingEnabled: false,
184
183
  })
@@ -260,6 +259,7 @@ module.exports = class CiPlugin extends Plugin {
260
259
  {
261
260
  ...this.testConfiguration,
262
261
  isCoverageReportUploadEnabled: this.libraryConfig?.isCoverageReportUploadEnabled,
262
+ isLineCoverageSupported: this.constructor.id !== 'vitest',
263
263
  },
264
264
  (err, skippableSuites, itrCorrelationId, skippableSuitesCoverage) => {
265
265
  if (err) {
@@ -461,12 +461,28 @@ module.exports = class CiPlugin extends Plugin {
461
461
  this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, logMessage)
462
462
  }
463
463
  })
464
+
465
+ this.addSub(`ci:${this.constructor.id}:worker-report:telemetry`, data => {
466
+ const telemetryEvents = JSON.parse(data)
467
+ for (const event of telemetryEvents) {
468
+ if (event.type === 'ciVisEvent') {
469
+ this.telemetry.ciVisEvent(event.name, event.testLevel, {
470
+ ...event.tags,
471
+ testFramework: event.testFramework,
472
+ isUnsupportedCIProvider: event.isUnsupportedCIProvider,
473
+ })
474
+ } else if (event.type === 'count') {
475
+ this.telemetry.count(event.name, event.tags, event.value)
476
+ } else if (event.type === 'distribution') {
477
+ this.telemetry.distribution(event.name, event.tags, event.measure)
478
+ }
479
+ }
480
+ })
464
481
  }
465
482
 
466
483
  get telemetry () {
467
484
  const testFramework = this.testFramework || this.constructor.id
468
485
  const exporter = this.tracer?._exporter
469
- // TODO: only jest worker supported yet
470
486
  const isSupportedWorker = exporter && typeof exporter.exportTelemetry === 'function'
471
487
  const ciProviderName = this.ciProviderName
472
488
 
@@ -1157,11 +1173,11 @@ module.exports = class CiPlugin extends Plugin {
1157
1173
  return
1158
1174
  }
1159
1175
 
1160
- const { filePath, format } = coverageReports[reportIndex]
1176
+ const { filePath, fileDevice, fileInode, format } = coverageReports[reportIndex]
1161
1177
  reportIndex++
1162
1178
 
1163
1179
  this.tracer._exporter.uploadCoverageReport(
1164
- { filePath, format, testEnvironmentMetadata: this.testEnvironmentMetadata },
1180
+ { filePath, fileDevice, fileInode, format, testEnvironmentMetadata: this.testEnvironmentMetadata },
1165
1181
  (err) => {
1166
1182
  if (err) {
1167
1183
  failedCount++
@@ -36,6 +36,7 @@ const plugins = {
36
36
  get '@smithy/core' () { return require('../../../datadog-plugin-aws-sdk/src') },
37
37
  get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
38
38
  get '@vitest/runner' () { return require('../../../datadog-plugin-vitest/src') },
39
+ get '@wdio/jasmine-framework' () { return require('../../../datadog-plugin-mocha/src') },
39
40
  get '@wdio/local-runner' () { return require('../../../datadog-plugin-mocha/src') },
40
41
  get '@langchain/langgraph' () { return require('../../../datadog-plugin-langgraph/src') },
41
42
  get '@openai/agents' () { return require('../../../datadog-plugin-openai-agents/src') },
@@ -114,6 +114,9 @@ const uniq = (items) => [...new Set(items)]
114
114
  *
115
115
  * This is much more robust than relying on hardcoded paths, especially on self-hosted runners
116
116
  * and GHES environments where the runner may be installed under arbitrary directories/users.
117
+ *
118
+ * @param {string | undefined} runnerTemp value of the `RUNNER_TEMP` environment variable
119
+ * @returns {string[]}
117
120
  */
118
121
  function getGithubDiagnosticDirsFromEnv (runnerTemp) {
119
122
  const dirs = []
@@ -182,6 +185,9 @@ function expandGlobPattern (pattern) {
182
185
  /**
183
186
  * Expands a mixed list of literal directories and glob patterns into concrete
184
187
  * directories. Literals pass through unchanged (existence is checked later).
188
+ *
189
+ * @param {string[]} candidates
190
+ * @returns {string[]}
185
191
  */
186
192
  function expandDiagnosticDirCandidates (candidates) {
187
193
  const expanded = []
@@ -38,6 +38,8 @@ const {
38
38
  const { filterSensitiveInfoFromRepository } = require('./url')
39
39
  const { cachedExec } = require('./git-cache')
40
40
 
41
+ /** @typedef {NonNullable<ReturnType<string['match']>>} RegExpMatch */
42
+
41
43
  const legacyStorage = storage('legacy')
42
44
 
43
45
  const GIT_REV_LIST_MAX_BUFFER = 12 * 1024 * 1024 // 12MB
@@ -116,12 +118,12 @@ function isShallowRepository () {
116
118
 
117
119
  function getGitVersion () {
118
120
  const gitVersionString = sanitizedExec('git', ['version'])
119
- const gitVersionMatches = /** @type {RegExpMatchArray} */ (gitVersionString.match(/git version (\d+)\.(\d+)\.(\d+)/))
121
+ const gitVersionMatches = /** @type {RegExpMatch} */ (gitVersionString.match(/git version (\d+)\.(\d+)\.(\d+)/))
120
122
  try {
121
123
  return {
122
- major: Number.parseInt(gitVersionMatches[1]),
123
- minor: Number.parseInt(gitVersionMatches[2]),
124
- patch: Number.parseInt(gitVersionMatches[3]),
124
+ major: Number.parseInt(gitVersionMatches[1], 10),
125
+ minor: Number.parseInt(gitVersionMatches[2], 10),
126
+ patch: Number.parseInt(gitVersionMatches[3], 10),
125
127
  }
126
128
  } catch {
127
129
  return null
@@ -71,7 +71,7 @@ function decomposeServerUrl (rawUrl, obfuscatedUrl) {
71
71
  address = stripIpv6Brackets(hostname)
72
72
  }
73
73
  if (parsed.port) {
74
- const parsedPort = Number.parseInt(parsed.port)
74
+ const parsedPort = Number.parseInt(parsed.port, 10)
75
75
  if (parsedPort > 0) port = parsedPort
76
76
  }
77
77
  path = parsed.pathname || '/'
@@ -210,7 +210,7 @@ function applyHttpOtelSemantics (formattedSpan) {
210
210
  // metric (the OTLP exporter serializes meta as stringValue but metrics as
211
211
  // intValue) — mirroring how server.port is handled below. Guard against a
212
212
  // non-numeric status, which would otherwise write a NaN metric.
213
- statusCode = Number.parseInt(status)
213
+ statusCode = Number.parseInt(status, 10)
214
214
  if (Number.isFinite(statusCode)) newMetrics[HTTP_RESPONSE_STATUS_CODE] = statusCode
215
215
  }
216
216
 
@@ -36,7 +36,7 @@ const privateIPMatcher = new net.BlockList()
36
36
  for (const cidr of privateCIDRs) {
37
37
  const [address, prefix] = cidr.split('/')
38
38
 
39
- privateIPMatcher.addSubnet(address, Number.parseInt(prefix), net.isIPv6(address) ? 'ipv6' : 'ipv4')
39
+ privateIPMatcher.addSubnet(address, Number.parseInt(prefix, 10), net.isIPv6(address) ? 'ipv6' : 'ipv4')
40
40
  }
41
41
 
42
42
  function extractIp (config, req) {
@@ -184,6 +184,9 @@ function isDDInstrumentationFile (fileName) {
184
184
  * This function extracts the `myscript.js:10:3` part, passes it, returns the file name, line number, and column
185
185
  * number and sets the `index` to the start of the whole location string.
186
186
  *
187
+ * @param {string} stack the stack trace, or the nested location string when recursing
188
+ * @param {number} start index of the first character of the frame in `stack`
189
+ * @param {number} index index to start parsing backwards from
187
190
  * @returns {[string, string, string, number]|undefined}
188
191
  */
189
192
  function parseLocation (stack, start, index) {