dd-trace 5.117.0 → 5.118.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 (83) hide show
  1. package/ci/diagnose.js +162 -21
  2. package/ci/init.js +0 -1
  3. package/ci/runbook.md +187 -180
  4. package/ci/test-optimization-validation/approval-artifacts.js +3 -1
  5. package/ci/test-optimization-validation/approval.js +52 -15
  6. package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
  7. package/ci/test-optimization-validation/ci-discovery.js +1 -1
  8. package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
  9. package/ci/test-optimization-validation/ci-remediation.js +112 -39
  10. package/ci/test-optimization-validation/cli.js +516 -681
  11. package/ci/test-optimization-validation/command-blocker.js +188 -33
  12. package/ci/test-optimization-validation/command-output-policy.js +3 -26
  13. package/ci/test-optimization-validation/command-runner.js +189 -245
  14. package/ci/test-optimization-validation/environment.js +90 -0
  15. package/ci/test-optimization-validation/executable.js +159 -388
  16. package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
  17. package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
  18. package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
  19. package/ci/test-optimization-validation/generated-files.js +75 -13
  20. package/ci/test-optimization-validation/generated-test-contract.js +283 -0
  21. package/ci/test-optimization-validation/generated-verifier.js +49 -16
  22. package/ci/test-optimization-validation/literal-environment.js +57 -0
  23. package/ci/test-optimization-validation/manifest-loader.js +2 -22
  24. package/ci/test-optimization-validation/manifest-scaffold.js +816 -515
  25. package/ci/test-optimization-validation/manifest-schema.js +494 -702
  26. package/ci/test-optimization-validation/offline-fixtures.js +4 -1
  27. package/ci/test-optimization-validation/plan-writer.js +243 -984
  28. package/ci/test-optimization-validation/preflight-runner.js +49 -55
  29. package/ci/test-optimization-validation/redaction.js +2 -1
  30. package/ci/test-optimization-validation/report-writer.js +391 -1357
  31. package/ci/test-optimization-validation/result-semantics.js +86 -0
  32. package/ci/test-optimization-validation/runner-command.js +249 -0
  33. package/ci/test-optimization-validation/runner-contract.js +664 -0
  34. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
  35. package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
  36. package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
  37. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
  38. package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
  39. package/ci/test-optimization-validation/scenarios/test-management.js +4 -3
  40. package/ci/test-optimization-validation/source-text.js +87 -0
  41. package/ci/test-optimization-validation/test-output.js +12 -22
  42. package/ext/tags.d.ts +1 -0
  43. package/index.d.ts +7 -0
  44. package/package.json +2 -2
  45. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
  48. package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
  49. package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
  50. package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
  51. package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
  52. package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
  53. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +48 -49
  54. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
  55. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +6 -4
  56. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  57. package/packages/dd-trace/src/config/supported-configurations.json +7 -0
  58. package/packages/dd-trace/src/encode/span-stats.js +7 -1
  59. package/packages/dd-trace/src/exporter.js +16 -9
  60. package/packages/dd-trace/src/llmobs/index.js +9 -1
  61. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
  62. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
  63. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
  64. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
  65. package/packages/dd-trace/src/llmobs/sdk.js +2 -1
  66. package/packages/dd-trace/src/llmobs/span_processor.js +7 -2
  67. package/packages/dd-trace/src/llmobs/tagger.js +23 -3
  68. package/packages/dd-trace/src/llmobs/util.js +56 -3
  69. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
  70. package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
  71. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
  72. package/packages/dd-trace/src/plugins/ci_plugin.js +9 -3
  73. package/packages/dd-trace/src/plugins/index.js +1 -0
  74. package/packages/dd-trace/src/plugins/outbound.js +4 -1
  75. package/packages/dd-trace/src/span_processor.js +1 -1
  76. package/packages/dd-trace/src/span_stats.js +22 -4
  77. package/ci/test-optimization-validation/command-suitability.js +0 -471
  78. package/ci/test-optimization-validation/init-probe-preload.js +0 -163
  79. package/ci/test-optimization-validation/init-probe.js +0 -246
  80. package/ci/test-optimization-validation/late-initialization.js +0 -63
  81. package/ci/test-optimization-validation/local-command.js +0 -163
  82. package/ci/test-optimization-validation/setup-runner.js +0 -97
  83. package/ci/test-optimization-validation-manifest.schema.json +0 -1
@@ -20,6 +20,7 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags'
20
20
  const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata')
21
21
 
22
22
  const hostname = getHostname()
23
+ const EMPTY_SETTINGS = Object.freeze({})
23
24
 
24
25
  function getTestConfigurationTags (tags) {
25
26
  if (!tags) {
@@ -308,60 +309,58 @@ class CiVisibilityExporter extends BufferingExporter {
308
309
  }
309
310
 
310
311
  // Takes into account potential kill switches
311
- filterConfiguration (remoteConfiguration) {
312
- if (!remoteConfiguration) {
313
- return {}
314
- }
312
+ filterConfiguration (remoteConfiguration = EMPTY_SETTINGS) {
313
+ const { testOptimization } = this._config
315
314
  const {
316
- isCodeCoverageEnabled,
317
- isSuitesSkippingEnabled,
318
- isItrEnabled,
319
- requireGit,
320
- isEarlyFlakeDetectionEnabled,
315
+ DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED: isEarlyFlakeDetectionAllowed,
316
+ DD_CIVISIBILITY_FLAKY_RETRY_COUNT: flakyTestRetriesCount = 0,
317
+ DD_CIVISIBILITY_FLAKY_RETRY_ENABLED: isFlakyTestRetriesAllowed,
318
+ DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED: isImpactedTestsAllowed,
319
+ DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT: earlyFlakeDetectionRetryCount,
320
+ DD_TEST_FAILED_TEST_REPLAY_ENABLED: isFailedTestReplayAllowed,
321
+ DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: configuredAttemptToFixRetries = 0,
322
+ DD_TEST_MANAGEMENT_ENABLED: isTestManagementAllowed,
323
+ } = testOptimization
324
+ const hasEarlyFlakeDetectionRetryCount = earlyFlakeDetectionRetryCount !== undefined
325
+ const configuredSlowTestRetries = remoteConfiguration.earlyFlakeDetectionSlowTestRetries ?? EMPTY_SETTINGS
326
+ const earlyFlakeDetectionSlowTestRetries = hasEarlyFlakeDetectionRetryCount
327
+ ? Object.freeze({
328
+ '5s': earlyFlakeDetectionRetryCount,
329
+ '10s': earlyFlakeDetectionRetryCount,
330
+ '30s': earlyFlakeDetectionRetryCount,
331
+ '5m': earlyFlakeDetectionRetryCount,
332
+ })
333
+ : Object.isFrozen(configuredSlowTestRetries)
334
+ ? configuredSlowTestRetries
335
+ : Object.freeze({ ...configuredSlowTestRetries })
336
+ const earlyFlakeDetectionNumRetries = hasEarlyFlakeDetectionRetryCount
337
+ ? earlyFlakeDetectionRetryCount
338
+ : remoteConfiguration.earlyFlakeDetectionNumRetries ?? 0
339
+ const testManagementAttemptToFixRetries =
340
+ remoteConfiguration.testManagementAttemptToFixRetries ?? configuredAttemptToFixRetries
341
+
342
+ return Object.freeze({
343
+ isCodeCoverageEnabled: remoteConfiguration.isCodeCoverageEnabled === true,
344
+ isSuitesSkippingEnabled: remoteConfiguration.isSuitesSkippingEnabled === true,
345
+ isItrEnabled: remoteConfiguration.isItrEnabled === true,
346
+ requireGit: remoteConfiguration.requireGit === true,
347
+ isEarlyFlakeDetectionEnabled:
348
+ remoteConfiguration.isEarlyFlakeDetectionEnabled === true && isEarlyFlakeDetectionAllowed === true,
321
349
  earlyFlakeDetectionNumRetries,
322
350
  earlyFlakeDetectionSlowTestRetries,
323
- earlyFlakeDetectionFaultyThreshold,
324
- isFlakyTestRetriesEnabled,
325
- isDiEnabled,
326
- isKnownTestsEnabled,
327
- isTestManagementEnabled,
351
+ earlyFlakeDetectionFaultyThreshold: remoteConfiguration.earlyFlakeDetectionFaultyThreshold ?? 30,
352
+ isFlakyTestRetriesEnabled:
353
+ remoteConfiguration.isFlakyTestRetriesEnabled === true && isFlakyTestRetriesAllowed === true,
354
+ flakyTestRetriesCount,
355
+ isDiEnabled: remoteConfiguration.isDiEnabled === true && isFailedTestReplayAllowed === true,
356
+ isKnownTestsEnabled: remoteConfiguration.isKnownTestsEnabled === true,
357
+ isTestManagementEnabled:
358
+ remoteConfiguration.isTestManagementEnabled === true && isTestManagementAllowed === true,
328
359
  testManagementAttemptToFixRetries,
329
- isImpactedTestsEnabled,
330
- isCoverageReportUploadEnabled,
331
- } = remoteConfiguration
332
- const { testOptimization } = this._config
333
- const earlyFlakeDetectionRetryCount =
334
- testOptimization.DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT
335
- const hasEarlyFlakeDetectionRetryCount = earlyFlakeDetectionRetryCount !== undefined
336
- return {
337
- isCodeCoverageEnabled,
338
- isSuitesSkippingEnabled,
339
- isItrEnabled,
340
- requireGit,
341
- isEarlyFlakeDetectionEnabled:
342
- isEarlyFlakeDetectionEnabled && testOptimization.DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED,
343
- earlyFlakeDetectionNumRetries:
344
- hasEarlyFlakeDetectionRetryCount ? earlyFlakeDetectionRetryCount : earlyFlakeDetectionNumRetries,
345
- earlyFlakeDetectionSlowTestRetries: hasEarlyFlakeDetectionRetryCount
346
- ? {
347
- '5s': earlyFlakeDetectionRetryCount,
348
- '10s': earlyFlakeDetectionRetryCount,
349
- '30s': earlyFlakeDetectionRetryCount,
350
- '5m': earlyFlakeDetectionRetryCount,
351
- }
352
- : earlyFlakeDetectionSlowTestRetries,
353
- earlyFlakeDetectionFaultyThreshold,
354
- isFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled && testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_ENABLED,
355
- flakyTestRetriesCount: testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
356
- isDiEnabled: isDiEnabled && testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED,
357
- isKnownTestsEnabled,
358
- isTestManagementEnabled: isTestManagementEnabled && testOptimization.DD_TEST_MANAGEMENT_ENABLED,
359
- testManagementAttemptToFixRetries:
360
- testManagementAttemptToFixRetries ?? testOptimization.DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES,
361
360
  isImpactedTestsEnabled:
362
- isImpactedTestsEnabled && testOptimization.DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED,
363
- isCoverageReportUploadEnabled,
364
- }
361
+ remoteConfiguration.isImpactedTestsEnabled === true && isImpactedTestsAllowed === true,
362
+ isCoverageReportUploadEnabled: remoteConfiguration.isCoverageReportUploadEnabled === true,
363
+ })
365
364
  }
366
365
 
367
366
  sendGitMetadata (repositoryUrl) {
@@ -12,12 +12,145 @@ const {
12
12
  TELEMETRY_GIT_REQUESTS_SETTINGS_RESPONSE,
13
13
  } = require('../telemetry')
14
14
  const { writeSettingsToCache } = require('../test-optimization-cache')
15
- const { validateSettingsResponse } = require('../test-optimization-http-cache-schema')
15
+ const { MAX_RETRIES, validateSettingsResponse } = require('../test-optimization-http-cache-schema')
16
16
  const request = require('./request')
17
17
 
18
18
  const DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES = 2
19
- const DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES = { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
19
+ const DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES = Object.freeze({
20
+ '5s': 10,
21
+ '10s': 5,
22
+ '30s': 3,
23
+ '5m': 2,
24
+ })
20
25
  const DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD = 30
26
+ const EARLY_FLAKE_DETECTION_RETRY_BUCKETS = Object.keys(DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES)
27
+
28
+ /**
29
+ * @typedef {object} EarlyFlakeDetectionSettings
30
+ * @property {boolean} enabled
31
+ * @property {number} numRetries
32
+ * @property {Readonly<Record<string, number>>} slowTestRetries
33
+ * @property {number} faultyThreshold
34
+ */
35
+
36
+ /**
37
+ * @typedef {object} TestManagementSettings
38
+ * @property {boolean} enabled
39
+ * @property {number|undefined} attemptToFixRetries
40
+ */
41
+
42
+ /**
43
+ * @param {unknown} value
44
+ * @returns {value is Record<string, unknown>}
45
+ */
46
+ function isRecord (value) {
47
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
48
+ }
49
+
50
+ /**
51
+ * @param {unknown} value
52
+ * @returns {value is number}
53
+ */
54
+ function isNonNegativeInteger (value) {
55
+ return Number.isInteger(value) && value >= 0
56
+ }
57
+
58
+ /**
59
+ * @param {unknown} value
60
+ * @returns {value is number}
61
+ */
62
+ function isRetryCount (value) {
63
+ return isNonNegativeInteger(value) && value <= MAX_RETRIES
64
+ }
65
+
66
+ /**
67
+ * @param {unknown} value
68
+ * @returns {Readonly<Record<string, number>>|undefined}
69
+ */
70
+ function parseSlowTestRetries (value) {
71
+ if (!isRecord(value)) return
72
+
73
+ const slowTestRetries = {}
74
+ for (const bucket of EARLY_FLAKE_DETECTION_RETRY_BUCKETS) {
75
+ if (!Object.hasOwn(value, bucket)) continue
76
+
77
+ const retries = value[bucket]
78
+ if (!isRetryCount(retries)) return
79
+ slowTestRetries[bucket] = retries
80
+ }
81
+ return Object.freeze(slowTestRetries)
82
+ }
83
+
84
+ /**
85
+ * @param {unknown} value
86
+ * @param {boolean} isKnownTestsEnabled
87
+ * @returns {EarlyFlakeDetectionSettings}
88
+ */
89
+ function parseEarlyFlakeDetectionSettings (value, isKnownTestsEnabled) {
90
+ if (!isRecord(value) || value.enabled !== true) {
91
+ return {
92
+ enabled: false,
93
+ numRetries: DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
94
+ slowTestRetries: DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
95
+ faultyThreshold: DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
96
+ }
97
+ }
98
+
99
+ let isValid = true
100
+ let numRetries = DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES
101
+ let slowTestRetries = DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES
102
+ if (Object.hasOwn(value, 'slow_test_retries')) {
103
+ const parsedSlowTestRetries = parseSlowTestRetries(value.slow_test_retries)
104
+ if (parsedSlowTestRetries) {
105
+ slowTestRetries = parsedSlowTestRetries
106
+ numRetries = parsedSlowTestRetries['5s'] ?? DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES
107
+ } else {
108
+ isValid = false
109
+ }
110
+ }
111
+
112
+ let faultyThreshold = DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD
113
+ if (Object.hasOwn(value, 'faulty_session_threshold')) {
114
+ if (isNonNegativeInteger(value.faulty_session_threshold) && value.faulty_session_threshold <= 100) {
115
+ faultyThreshold = value.faulty_session_threshold
116
+ } else {
117
+ isValid = false
118
+ }
119
+ }
120
+
121
+ return {
122
+ enabled: isKnownTestsEnabled && isValid,
123
+ numRetries,
124
+ slowTestRetries,
125
+ faultyThreshold,
126
+ }
127
+ }
128
+
129
+ /**
130
+ * @param {unknown} value
131
+ * @returns {TestManagementSettings}
132
+ */
133
+ function parseTestManagementSettings (value) {
134
+ if (!isRecord(value) || value.enabled !== true) {
135
+ return {
136
+ enabled: false,
137
+ attemptToFixRetries: undefined,
138
+ }
139
+ }
140
+
141
+ const attemptToFixRetries = value.attempt_to_fix_retries
142
+ if (attemptToFixRetries !== undefined && !isRetryCount(attemptToFixRetries)) {
143
+ return {
144
+ enabled: false,
145
+ attemptToFixRetries: undefined,
146
+ }
147
+ }
148
+
149
+ return {
150
+ enabled: true,
151
+ attemptToFixRetries,
152
+ }
153
+ }
21
154
 
22
155
  function parseJsonResponse (rawJson) {
23
156
  return typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson
@@ -28,40 +161,35 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
28
161
  if (options.validateRequiredFields) {
29
162
  validateSettingsResponse(parsedResponse, options)
30
163
  }
31
- const {
32
- code_coverage: isCodeCoverageEnabled,
33
- tests_skipping: isSuitesSkippingEnabled,
34
- itr_enabled: isItrEnabled,
35
- require_git: requireGit,
36
- early_flake_detection: earlyFlakeDetectionConfig,
37
- flaky_test_retries_enabled: isFlakyTestRetriesEnabled,
38
- di_enabled: isDiEnabled,
39
- known_tests_enabled: isKnownTestsEnabled,
40
- test_management: testManagementConfig,
41
- impacted_tests_enabled: isImpactedTestsEnabled,
42
- coverage_report_upload_enabled: isCoverageReportUploadEnabled,
43
- } = parsedResponse?.data?.attributes ?? parsedResponse
164
+ const parsedAttributes = parsedResponse?.data?.attributes ?? parsedResponse
165
+ if (!isRecord(parsedAttributes)) {
166
+ throw new TypeError('Invalid settings response: attributes must be an object')
167
+ }
168
+ const attributes = parsedAttributes
169
+ const isKnownTestsEnabled = attributes.known_tests_enabled === true
170
+ const isFlakyTestRetriesEnabled = attributes.flaky_test_retries_enabled === true
171
+ const earlyFlakeDetection = parseEarlyFlakeDetectionSettings(
172
+ attributes.early_flake_detection,
173
+ isKnownTestsEnabled
174
+ )
175
+ const testManagement = parseTestManagementSettings(attributes.test_management)
44
176
 
45
177
  const settings = {
46
- isCodeCoverageEnabled,
47
- isSuitesSkippingEnabled,
48
- isItrEnabled,
49
- requireGit,
50
- isEarlyFlakeDetectionEnabled: isKnownTestsEnabled && (earlyFlakeDetectionConfig?.enabled ?? false),
51
- earlyFlakeDetectionNumRetries:
52
- earlyFlakeDetectionConfig?.slow_test_retries?.['5s'] ?? DEFAULT_EARLY_FLAKE_DETECTION_NUM_RETRIES,
53
- earlyFlakeDetectionSlowTestRetries:
54
- earlyFlakeDetectionConfig?.slow_test_retries ?? DEFAULT_EARLY_FLAKE_DETECTION_SLOW_TEST_RETRIES,
55
- earlyFlakeDetectionFaultyThreshold:
56
- earlyFlakeDetectionConfig?.faulty_session_threshold ?? DEFAULT_EARLY_FLAKE_DETECTION_ERROR_THRESHOLD,
178
+ isCodeCoverageEnabled: attributes.code_coverage === true,
179
+ isSuitesSkippingEnabled: attributes.tests_skipping === true,
180
+ isItrEnabled: attributes.itr_enabled === true,
181
+ requireGit: attributes.require_git === true,
182
+ isEarlyFlakeDetectionEnabled: earlyFlakeDetection.enabled,
183
+ earlyFlakeDetectionNumRetries: earlyFlakeDetection.numRetries,
184
+ earlyFlakeDetectionSlowTestRetries: earlyFlakeDetection.slowTestRetries,
185
+ earlyFlakeDetectionFaultyThreshold: earlyFlakeDetection.faultyThreshold,
57
186
  isFlakyTestRetriesEnabled,
58
- isDiEnabled: isDiEnabled && isFlakyTestRetriesEnabled,
187
+ isDiEnabled: attributes.di_enabled === true && isFlakyTestRetriesEnabled,
59
188
  isKnownTestsEnabled,
60
- isTestManagementEnabled: (testManagementConfig?.enabled ?? false),
61
- testManagementAttemptToFixRetries:
62
- testManagementConfig?.attempt_to_fix_retries,
63
- isImpactedTestsEnabled,
64
- isCoverageReportUploadEnabled: isCoverageReportUploadEnabled ?? false,
189
+ isTestManagementEnabled: testManagement.enabled,
190
+ testManagementAttemptToFixRetries: testManagement.attemptToFixRetries,
191
+ isImpactedTestsEnabled: attributes.impacted_tests_enabled === true,
192
+ isCoverageReportUploadEnabled: attributes.coverage_report_upload_enabled === true,
65
193
  }
66
194
 
67
195
  log.debug('Remote settings: %j', settings)
@@ -82,7 +210,7 @@ function parseLibraryConfigurationResponse (rawJson, config = getConfig(), optio
82
210
  log.debug('Code coverage report upload was disabled by the environment variable')
83
211
  }
84
212
 
85
- return settings
213
+ return Object.freeze(settings)
86
214
  }
87
215
 
88
216
  function getLibraryConfiguration ({
@@ -30,7 +30,7 @@ const RETRY_THRESHOLD_PATTERN = /^\d+(?:ms|s|m|h)$/
30
30
  const MAX_VALIDATION_MODULES = 1000
31
31
  const MAX_VALIDATION_SUITES = 10_000
32
32
  const MAX_VALIDATION_TESTS = 100_000
33
- const MAX_VALIDATION_RETRIES = 100
33
+ const MAX_RETRIES = 100
34
34
  const MAX_VALIDATION_STRING_BYTES = 4096
35
35
 
36
36
  function isObject (value) {
@@ -75,7 +75,8 @@ function validateSettingsResponse (response, options = {}) {
75
75
  assertBooleanFields(attributes.test_management, ['enabled'], 'settings test_management')
76
76
  assertOptionalBoundedNumber(
77
77
  attributes.early_flake_detection.faulty_session_threshold,
78
- 'settings early_flake_detection faulty_session_threshold'
78
+ 'settings early_flake_detection faulty_session_threshold',
79
+ { integer: true }
79
80
  )
80
81
  assertRetryMap(attributes.early_flake_detection.slow_test_retries)
81
82
  assertOptionalBoundedNumber(
@@ -216,8 +217,8 @@ function assertBooleanFields (object, keys, endpoint) {
216
217
  function assertOptionalBoundedNumber (value, endpoint, { integer = false } = {}) {
217
218
  if (value === undefined) return
218
219
  if (!Number.isFinite(value) || (integer && !Number.isInteger(value)) ||
219
- value < 0 || value > MAX_VALIDATION_RETRIES) {
220
- throw new TypeError(`Invalid ${endpoint}: value must be between 0 and ${MAX_VALIDATION_RETRIES}`)
220
+ value < 0 || value > MAX_RETRIES) {
221
+ throw new TypeError(`Invalid ${endpoint}: value must be between 0 and ${MAX_RETRIES}`)
221
222
  }
222
223
  }
223
224
 
@@ -249,6 +250,7 @@ function assertValidationString (value, field) {
249
250
  }
250
251
 
251
252
  module.exports = {
253
+ MAX_RETRIES,
252
254
  validateKnownTestsResponse,
253
255
  validateSettingsResponse,
254
256
  validateSkippableTestsResponse,
@@ -333,6 +333,7 @@ export interface GeneratedConfig {
333
333
  DD_TRACE_NODE_SERIALIZE_ENABLED: boolean;
334
334
  DD_TRACE_NYC_ENABLED: boolean;
335
335
  DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: string;
336
+ DD_TRACE_OPENAI_AGENTS_ENABLED: boolean;
336
337
  DD_TRACE_OPENAI_ENABLED: boolean;
337
338
  DD_TRACE_OPENSEARCH_ENABLED: boolean;
338
339
  DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
@@ -1028,6 +1029,7 @@ export interface GeneratedEnvVarConfig {
1028
1029
  DD_TRACE_NODE_SERIALIZE_ENABLED: boolean;
1029
1030
  DD_TRACE_NYC_ENABLED: boolean;
1030
1031
  DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: string;
1032
+ DD_TRACE_OPENAI_AGENTS_ENABLED: boolean;
1031
1033
  DD_TRACE_OPENAI_ENABLED: boolean;
1032
1034
  DD_TRACE_OPENSEARCH_ENABLED: boolean;
1033
1035
  DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
@@ -3452,6 +3452,13 @@
3452
3452
  "default": "true"
3453
3453
  }
3454
3454
  ],
3455
+ "DD_TRACE_OPENAI_AGENTS_ENABLED": [
3456
+ {
3457
+ "implementation": "A",
3458
+ "type": "boolean",
3459
+ "default": "true"
3460
+ }
3461
+ ],
3455
3462
  "DD_TRACE_OPENSEARCH_ENABLED": [
3456
3463
  {
3457
3464
  "implementation": "A",
@@ -31,7 +31,7 @@ class SpanStatsEncoder extends AgentEncoder {
31
31
  }
32
32
 
33
33
  _encodeStat (bytes, stat) {
34
- bytes.writeMapPrefix(15)
34
+ bytes.writeMapPrefix(17)
35
35
 
36
36
  this._encodeString(bytes, 'Service')
37
37
  const service = stat.Service || DEFAULT_SERVICE_NAME
@@ -79,6 +79,12 @@ class SpanStatsEncoder extends AgentEncoder {
79
79
 
80
80
  this._encodeString(bytes, 'srv_src')
81
81
  this._encodeString(bytes, stat.srv_src || '')
82
+
83
+ this._encodeString(bytes, 'SpanKind')
84
+ this._encodeString(bytes, stat.SpanKind || '')
85
+
86
+ this._encodeString(bytes, 'GRPCStatusCode')
87
+ this._encodeString(bytes, stat.GRPCStatusCode || '')
82
88
  }
83
89
 
84
90
  _encodeBucket (bytes, bucket) {
@@ -4,6 +4,7 @@ const fs = require('fs')
4
4
  const exporters = require('../../../ext/exporters')
5
5
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
6
6
  const constants = require('./constants')
7
+ const { isTrue } = require('./util')
7
8
 
8
9
  module.exports = function getExporter (name) {
9
10
  switch (name) {
@@ -20,20 +21,26 @@ module.exports = function getExporter (name) {
20
21
  case exporters.AGENT_PROXY:
21
22
  return require('./ci-visibility/exporters/agent-proxy')
22
23
  case exporters.CI_VALIDATION:
23
- return require('./ci-visibility/exporters/ci-validation')
24
+ if (hasCiValidationEnvironment()) return require('./ci-visibility/exporters/ci-validation')
25
+ break
24
26
  case exporters.JEST_WORKER:
25
27
  case exporters.CUCUMBER_WORKER:
26
28
  case exporters.MOCHA_WORKER:
27
29
  case exporters.PLAYWRIGHT_WORKER:
28
30
  case exporters.VITEST_WORKER:
29
31
  return require('./ci-visibility/exporters/test-worker')
30
- default: {
31
- const inAWSLambda = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined
32
- const usingAgent = inAWSLambda && (
33
- fs.existsSync(constants.DATADOG_LAMBDA_EXTENSION_PATH) ||
34
- fs.existsSync(constants.DATADOG_MINI_AGENT_PATH)
35
- )
36
- return inAWSLambda && !usingAgent ? require('./exporters/log') : require('./exporters/agent')
37
- }
38
32
  }
33
+
34
+ const inAWSLambda = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME') !== undefined
35
+ const usingAgent = inAWSLambda && (
36
+ fs.existsSync(constants.DATADOG_LAMBDA_EXTENSION_PATH) ||
37
+ fs.existsSync(constants.DATADOG_MINI_AGENT_PATH)
38
+ )
39
+ return inAWSLambda && !usingAgent ? require('./exporters/log') : require('./exporters/agent')
40
+ }
41
+
42
+ function hasCiValidationEnvironment () {
43
+ return isTrue(getEnvironmentVariable('_DD_TEST_OPTIMIZATION_VALIDATION_MODE')) &&
44
+ getEnvironmentVariable('_DD_TEST_OPTIMIZATION_VALIDATION_MANIFEST_FILE') &&
45
+ getEnvironmentVariable('_DD_TEST_OPTIMIZATION_VALIDATION_OUTPUT_DIR')
39
46
  }
@@ -16,6 +16,8 @@ const {
16
16
  SAMPLING_DECISION,
17
17
  PROPAGATED_SAMPLE_RATE_KEY,
18
18
  PROPAGATED_SAMPLING_DECISION_KEY,
19
+ TRACE_ID,
20
+ PROPAGATED_TRACE_ID_KEY,
19
21
  } = require('./constants/tags')
20
22
  const { storage } = require('./storage')
21
23
  const telemetry = require('./telemetry')
@@ -25,6 +27,7 @@ const LLMObsTagger = require('./tagger')
25
27
  const LLMObsSpanWriter = require('./writers/spans')
26
28
  const { setAgentStrategy } = require('./writers/util')
27
29
  const { INCOMPATIBLE_INITIALIZATION } = require('./constants/text')
30
+ const { llmObsTraceIdToWire } = require('./util')
28
31
 
29
32
  const spanFinishCh = channel('dd-trace:span:finish')
30
33
  const evalMetricAppendCh = channel('llmobs:eval-metric:append')
@@ -137,8 +140,12 @@ function handleLLMObsInjection ({ carrier }) {
137
140
  mlObsSpanTags?.[SESSION_ID] ??
138
141
  parentContext?._trace?.tags?.[SESSION_ID_TRACE_DEFAULT_KEY] ??
139
142
  parentContext?._trace?.tags?.[PROPAGATED_SESSION_ID_KEY]
143
+ const llmobsTraceId = mlObsSpanTags?.[TRACE_ID]
144
+ const propagatedTraceId = llmobsTraceId === undefined
145
+ ? parentContext?._trace?.tags?.[PROPAGATED_TRACE_ID_KEY]
146
+ : llmObsTraceIdToWire(llmobsTraceId)
140
147
 
141
- if (!parentId && !mlApp && samplingDecision == null && !sessionId) return
148
+ if (!parentId && !mlApp && samplingDecision == null && !sessionId && !propagatedTraceId) return
142
149
 
143
150
  // `_injectTags` only writes `x-datadog-tags` when the trace has `_dd.p.*`
144
151
  // tags, so it may be undefined here — coalesce before appending.
@@ -149,6 +156,7 @@ function handleLLMObsInjection ({ carrier }) {
149
156
  if (sessionId) tags += `${tags ? ',' : ''}${PROPAGATED_SESSION_ID_KEY}=${sessionId}`
150
157
  if (sampleRate != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
151
158
  if (samplingDecision != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
159
+ if (propagatedTraceId != null) tags += `${tags ? ',' : ''}${PROPAGATED_TRACE_ID_KEY}=${propagatedTraceId}`
152
160
  if (tags !== existing) carrier['x-datadog-tags'] = tags
153
161
  }
154
162
 
@@ -13,6 +13,8 @@ const MODEL_METADATA_KEYS = new Set([
13
13
  const VERCEL_AI_TELEMETRY_METADATA_PREFIX = 'ai.telemetry.metadata.'
14
14
  const VERCEL_AI_MODEL_METADATA_PREFIX = 'gen_ai.request.'
15
15
  const VERCEL_AI_GENERATION_METADATA_PREFIX = 'ai.settings.'
16
+ const UNPARSABLE_TOOL_RESULT = '[Unparsable Tool Result]'
17
+ const UNSUPPORTED_TOOL_RESULT = '[Unsupported Tool Result]'
16
18
 
17
19
  /**
18
20
  * @typedef {import('../../../opentracing/span')} Span
@@ -26,8 +28,31 @@ const VERCEL_AI_GENERATION_METADATA_PREFIX = 'ai.settings.'
26
28
 
27
29
  /**
28
30
  * @typedef {{
29
- * type: string,
30
- * value?: unknown
31
+ * type: 'text',
32
+ * text: string
33
+ * } | {
34
+ * type: 'media' | 'file',
35
+ * mediaType: string
36
+ * } | {
37
+ * type: 'file-data' | 'file-url' | 'file-id' | 'file-reference'
38
+ * } | {
39
+ * type: 'image-data' | 'image-url' | 'image-file-id' | 'image-file-reference'
40
+ * } | {
41
+ * type: 'custom'
42
+ * }} ToolCallContentPart
43
+ *
44
+ * @typedef {{
45
+ * type: 'text' | 'error-text',
46
+ * value: string
47
+ * } | {
48
+ * type: 'json' | 'error-json',
49
+ * value: unknown
50
+ * } | {
51
+ * type: 'content',
52
+ * value: ToolCallContentPart[]
53
+ * } | {
54
+ * type: 'execution-denied',
55
+ * reason?: string
31
56
  * }} ToolCallOutput
32
57
  *
33
58
  * @typedef {{ output?: ToolCallOutput, result?: unknown } & Record<string, unknown>} ToolCallResultContent
@@ -304,32 +329,89 @@ function getToolNameFromTags (tags) {
304
329
  }
305
330
 
306
331
  /**
307
- * Get the content of a tool call result.
308
- * Version 5 of the ai sdk sets this tag as `content.output`, with a `
309
- * @param {{ output?: { type: string, value: unknown }, result?: unknown }} content
332
+ * @param {unknown} value
333
+ * @returns {string}
334
+ */
335
+ function stringifyToolCallResult (value) {
336
+ return JSON.stringify(value) ?? UNPARSABLE_TOOL_RESULT
337
+ }
338
+
339
+ /**
340
+ * @param {ToolCallContentPart[]} value
341
+ * @returns {string}
342
+ */
343
+ function formatToolCallContent (value) {
344
+ if (!Array.isArray(value)) return UNPARSABLE_TOOL_RESULT
345
+
346
+ let result = ''
347
+ for (const part of value) {
348
+ if (typeof part !== 'object' || part === null) return UNPARSABLE_TOOL_RESULT
349
+
350
+ const { type } = part
351
+ if (type === 'text') {
352
+ if (typeof part.text !== 'string') return UNPARSABLE_TOOL_RESULT
353
+ result += part.text
354
+ } else if (type === 'media' || type === 'file') {
355
+ const { mediaType } = part
356
+ if (typeof mediaType !== 'string') return UNPARSABLE_TOOL_RESULT
357
+ result += mediaType === 'image' || mediaType.startsWith('image/') ? '[Image]' : '[File]'
358
+ } else if (
359
+ type === 'file-data' ||
360
+ type === 'file-url' ||
361
+ type === 'file-id' ||
362
+ type === 'file-reference'
363
+ ) {
364
+ result += '[File]'
365
+ } else if (
366
+ type === 'image-data' ||
367
+ type === 'image-url' ||
368
+ type === 'image-file-id' ||
369
+ type === 'image-file-reference'
370
+ ) {
371
+ result += '[Image]'
372
+ } else if (type === 'custom') {
373
+ result += '[Custom Content]'
374
+ } else {
375
+ return UNPARSABLE_TOOL_RESULT
376
+ }
377
+ }
378
+
379
+ return result
380
+ }
381
+
382
+ /**
383
+ * @param {ToolCallResultContent | null | undefined} content
310
384
  * @returns {string}
311
385
  */
312
386
  function getToolCallResultContent (content) {
313
- const { output, result } = content
314
- if (output) {
315
- if (output.type === 'text') {
316
- return output.value
317
- } else if (output.type === 'json') {
318
- return JSON.stringify(output.value)
387
+ try {
388
+ if (typeof content !== 'object' || content === null) return UNPARSABLE_TOOL_RESULT
389
+
390
+ const { output, result } = content
391
+ if (output !== undefined) {
392
+ if (typeof output !== 'object' || output === null) return UNPARSABLE_TOOL_RESULT
393
+
394
+ const { type, value } = output
395
+ if (type === 'text' || type === 'error-text') {
396
+ return typeof value === 'string' ? value : UNPARSABLE_TOOL_RESULT
397
+ } else if (type === 'json' || type === 'error-json') {
398
+ return stringifyToolCallResult(value)
399
+ } else if (type === 'content') {
400
+ return formatToolCallContent(value)
401
+ } else if (type === 'execution-denied') {
402
+ const { reason } = output
403
+ if (reason === undefined) return '[Tool Execution Denied]'
404
+ return typeof reason === 'string' ? reason : UNPARSABLE_TOOL_RESULT
405
+ }
406
+ return UNPARSABLE_TOOL_RESULT
319
407
  }
320
- return '[Unparsable Tool Result]'
321
- } else if (result) {
322
- if (typeof result === 'string') {
323
- return result
408
+ if (result !== undefined) {
409
+ return typeof result === 'string' ? result : stringifyToolCallResult(result)
324
410
  }
325
411
 
326
- try {
327
- return JSON.stringify(result)
328
- } catch {
329
- return '[Unparsable Tool Result]'
330
- }
331
- } else {
332
- return '[Unsupported Tool Result]'
412
+ return UNSUPPORTED_TOOL_RESULT
413
+ } catch {
414
+ return UNPARSABLE_TOOL_RESULT
333
415
  }
334
416
  }
335
417