dd-trace 6.17.0 → 6.18.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 (70) hide show
  1. package/index.d.ts +52 -9
  2. package/package.json +1 -1
  3. package/packages/datadog-instrumentations/src/cucumber.js +10 -5
  4. package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
  5. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
  6. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
  8. package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +43 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +4 -0
  15. package/packages/datadog-instrumentations/src/jest.js +27 -3
  16. package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
  17. package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
  18. package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
  19. package/packages/datadog-instrumentations/src/playwright.js +47 -6
  20. package/packages/datadog-instrumentations/src/undici.js +7 -3
  21. package/packages/datadog-instrumentations/src/vitest-main.js +91 -20
  22. package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
  23. package/packages/datadog-instrumentations/src/ws.js +41 -1
  24. package/packages/datadog-plugin-cucumber/src/index.js +3 -4
  25. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +44 -14
  26. package/packages/datadog-plugin-cypress/src/index.js +3 -1
  27. package/packages/datadog-plugin-jest/src/index.js +3 -4
  28. package/packages/datadog-plugin-mocha/src/index.js +3 -4
  29. package/packages/datadog-plugin-playwright/src/index.js +11 -6
  30. package/packages/datadog-plugin-vitest/src/index.js +3 -4
  31. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  32. package/packages/datadog-shimmer/src/shimmer.js +14 -10
  33. package/packages/datadog-turbopack/index.js +4 -1
  34. package/packages/datadog-turbopack/src/loader.js +6 -2
  35. package/packages/dd-trace/src/agent/info.js +5 -4
  36. package/packages/dd-trace/src/config/generated-config-types.d.ts +15 -15
  37. package/packages/dd-trace/src/config/index.js +7 -6
  38. package/packages/dd-trace/src/config/supported-configurations.json +11 -0
  39. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  40. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
  41. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
  42. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  43. package/packages/dd-trace/src/debugger/index.js +1 -1
  44. package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
  45. package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
  46. package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
  47. package/packages/dd-trace/src/evp_proxy/path.js +21 -7
  48. package/packages/dd-trace/src/exporters/common/url.js +33 -2
  49. package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
  50. package/packages/dd-trace/src/llmobs/prompts/prompt.js +77 -11
  51. package/packages/dd-trace/src/llmobs/tagger.js +9 -4
  52. package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
  53. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
  54. package/packages/dd-trace/src/openfeature/index.js +6 -2
  55. package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
  56. package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
  57. package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
  58. package/packages/dd-trace/src/opentracing/tracer.js +3 -3
  59. package/packages/dd-trace/src/plugin_manager.js +27 -8
  60. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
  61. package/packages/dd-trace/src/plugins/util/test.js +9 -4
  62. package/packages/dd-trace/src/proxy.js +2 -2
  63. package/packages/dd-trace/src/telemetry/send-data.js +2 -1
  64. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  65. package/packages/dd-trace/src/tracer.js +5 -4
  66. package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
  67. package/packages/datadog-instrumentations/src/bullmq.js +0 -11
  68. package/packages/datadog-instrumentations/src/langchain.js +0 -7
  69. package/packages/datadog-instrumentations/src/langgraph.js +0 -7
  70. package/packages/datadog-instrumentations/src/mercurius.js +0 -11
@@ -414,27 +414,21 @@ export interface GeneratedConfig {
414
414
  };
415
415
  dsmEnabled: boolean;
416
416
  dynamicInstrumentation: {
417
- captureTimeoutMs: number;
418
- enabled: boolean;
419
- probeFile: string | undefined;
420
- redactedIdentifiers: string[];
421
- redactionExcludedIdentifiers: string[];
422
- uploadIntervalSeconds: number;
417
+ DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS: number;
418
+ DD_DYNAMIC_INSTRUMENTATION_ENABLED: boolean;
419
+ DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE: string | undefined;
420
+ DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS: string[];
421
+ DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS: string[];
422
+ DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS: number;
423
423
  };
424
424
  env: string | undefined;
425
425
  experimental: {
426
426
  b3: boolean;
427
- enableGetRumData: boolean;
428
- exporter: string;
429
- flaggingProvider: {
430
- enabled: boolean;
431
- initializationTimeoutMs: number;
432
- spanEnrichment: {
433
- enabled: boolean;
434
- };
435
- };
436
427
  };
437
428
  featureFlags: {
429
+ DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED: boolean;
430
+ DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS: number;
431
+ DD_EXPERIMENTAL_FLAGGING_PROVIDER_SPAN_ENRICHMENT_ENABLED: boolean;
438
432
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: string;
439
433
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: string | undefined;
440
434
  DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: number;
@@ -522,6 +516,9 @@ export interface GeneratedConfig {
522
516
  pollInterval: number;
523
517
  };
524
518
  reportHostname: boolean;
519
+ rum: {
520
+ DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED: boolean;
521
+ };
525
522
  runtimeMetrics: {
526
523
  enabled: boolean;
527
524
  eventLoop: boolean;
@@ -594,6 +591,9 @@ export interface GeneratedConfig {
594
591
  traceWebsocketMessagesEnabled: boolean;
595
592
  traceWebsocketMessagesInheritSampling: boolean;
596
593
  traceWebsocketMessagesSeparateTraces: boolean;
594
+ tracing: {
595
+ DD_TRACE_EXPERIMENTAL_EXPORTER: string;
596
+ };
597
597
  url: string | URL;
598
598
  version: string | undefined;
599
599
  vertexai: {
@@ -370,8 +370,8 @@ class Config extends ConfigBase {
370
370
 
371
371
  if (this.featureFlags.DD_FEATURE_FLAGS_ENABLED &&
372
372
  !trackedConfigOrigins.has('featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE') &&
373
- trackedConfigOrigins.has('experimental.flaggingProvider.enabled')) {
374
- if (this.experimental.flaggingProvider.enabled) {
373
+ trackedConfigOrigins.has('featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED')) {
374
+ if (this.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED) {
375
375
  setAndTrack(this, 'featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE', 'remote_config')
376
376
  } else {
377
377
  setAndTrack(this, 'featureFlags.DD_FEATURE_FLAGS_ENABLED', false)
@@ -505,7 +505,8 @@ class Config extends ConfigBase {
505
505
  // dd-trace's own sampling policy in that case.
506
506
  if (!trackedConfigOrigins.has('sampleRate') &&
507
507
  (trackedConfigOrigins.has('OTEL_TRACES_SAMPLER') ||
508
- (this.OTEL_TRACES_EXPORTER === 'otlp' && this.experimental.exporter !== 'electron'))) {
508
+ (this.OTEL_TRACES_EXPORTER === 'otlp' &&
509
+ this.tracing.DD_TRACE_EXPERIMENTAL_EXPORTER !== 'electron'))) {
509
510
  setAndTrack(this, 'sampleRate',
510
511
  getFromOtelSamplerMap(this.OTEL_TRACES_SAMPLER, this.OTEL_TRACES_SAMPLER_ARG))
511
512
  }
@@ -638,7 +639,7 @@ class Config extends ConfigBase {
638
639
  }
639
640
 
640
641
  const isTestOptimizationWorker = this.isCiVisibility &&
641
- TEST_OPTIMIZATION_WORKER_EXPORTERS.has(this.experimental.exporter)
642
+ TEST_OPTIMIZATION_WORKER_EXPORTERS.has(this.tracing.DD_TRACE_EXPERIMENTAL_EXPORTER)
642
643
  if (isTestOptimizationWorker) {
643
644
  setAndTrack(this, 'telemetry.DD_INSTRUMENTATION_TELEMETRY_ENABLED', false)
644
645
  }
@@ -651,7 +652,7 @@ class Config extends ConfigBase {
651
652
  setAndTrack(this, 'llmobs.DD_LLMOBS_AGENTLESS_ENABLED', true)
652
653
  setAndTrack(this, 'featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE', 'agentless')
653
654
  if (this.DD_API_KEY === undefined) {
654
- setAndTrack(this, 'dynamicInstrumentation.enabled', false)
655
+ setAndTrack(this, 'dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_ENABLED', false)
655
656
  }
656
657
  setAndTrack(this, 'runtimeMetrics.enabled', false)
657
658
  setAndTrack(this, 'dsmEnabled', false)
@@ -667,7 +668,7 @@ class Config extends ConfigBase {
667
668
  }
668
669
 
669
670
  if (agentlessTracingEnabled && !this.isCiVisibility) {
670
- setAndTrack(this, 'experimental.exporter', 'agentless')
671
+ setAndTrack(this, 'tracing.DD_TRACE_EXPERIMENTAL_EXPORTER', 'agentless')
671
672
  // Disable client-side stats computation
672
673
  setAndTrack(this, 'stats.DD_TRACE_STATS_COMPUTATION_ENABLED', false)
673
674
  // Enable hostname reporting
@@ -802,6 +802,7 @@
802
802
  {
803
803
  "implementation": "A",
804
804
  "type": "int",
805
+ "namespace": "dynamicInstrumentation",
805
806
  "configurationNames": [
806
807
  "dynamicInstrumentation.captureTimeoutMs"
807
808
  ],
@@ -812,6 +813,7 @@
812
813
  {
813
814
  "implementation": "A",
814
815
  "type": "boolean",
816
+ "namespace": "dynamicInstrumentation",
815
817
  "configurationNames": [
816
818
  "dynamicInstrumentation.enabled"
817
819
  ],
@@ -822,6 +824,7 @@
822
824
  {
823
825
  "implementation": "B",
824
826
  "type": "string",
827
+ "namespace": "dynamicInstrumentation",
825
828
  "configurationNames": [
826
829
  "dynamicInstrumentation.probeFile"
827
830
  ],
@@ -832,6 +835,7 @@
832
835
  {
833
836
  "implementation": "A",
834
837
  "type": "array",
838
+ "namespace": "dynamicInstrumentation",
835
839
  "configurationNames": [
836
840
  "dynamicInstrumentation.redactedIdentifiers"
837
841
  ],
@@ -842,6 +846,7 @@
842
846
  {
843
847
  "implementation": "C",
844
848
  "type": "array",
849
+ "namespace": "dynamicInstrumentation",
845
850
  "configurationNames": [
846
851
  "dynamicInstrumentation.redactionExcludedIdentifiers"
847
852
  ],
@@ -852,6 +857,7 @@
852
857
  {
853
858
  "implementation": "C",
854
859
  "type": "decimal",
860
+ "namespace": "dynamicInstrumentation",
855
861
  "configurationNames": [
856
862
  "dynamicInstrumentation.uploadIntervalSeconds"
857
863
  ],
@@ -890,6 +896,7 @@
890
896
  {
891
897
  "implementation": "A",
892
898
  "type": "int",
899
+ "namespace": "featureFlags",
893
900
  "configurationNames": [
894
901
  "experimental.flaggingProvider.initializationTimeoutMs"
895
902
  ],
@@ -900,6 +907,7 @@
900
907
  {
901
908
  "implementation": "A",
902
909
  "type": "boolean",
910
+ "namespace": "featureFlags",
903
911
  "configurationNames": [
904
912
  "experimental.flaggingProvider.enabled"
905
913
  ],
@@ -910,6 +918,7 @@
910
918
  {
911
919
  "implementation": "A",
912
920
  "type": "boolean",
921
+ "namespace": "featureFlags",
913
922
  "configurationNames": [
914
923
  "experimental.flaggingProvider.spanEnrichment.enabled"
915
924
  ],
@@ -2871,6 +2880,7 @@
2871
2880
  "configurationNames": [
2872
2881
  "experimental.exporter"
2873
2882
  ],
2883
+ "namespace": "tracing",
2874
2884
  "default": ""
2875
2885
  }
2876
2886
  ],
@@ -2881,6 +2891,7 @@
2881
2891
  "configurationNames": [
2882
2892
  "experimental.enableGetRumData"
2883
2893
  ],
2894
+ "namespace": "rum",
2884
2895
  "default": "false"
2885
2896
  }
2886
2897
  ],
@@ -25,5 +25,6 @@ function updateConfig (updates) {
25
25
  // The worker receives a serialized config (see ../config.js) where `url` is a string, so it is
26
26
  // reconstructed into a URL here rather than read directly off a Config instance.
27
27
  config.url = new URL(updates.url)
28
- config.dynamicInstrumentation.captureTimeoutNs = BigInt(updates.dynamicInstrumentation.captureTimeoutMs) * 1_000_000n
28
+ config.dynamicInstrumentation.captureTimeoutNs =
29
+ BigInt(updates.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS) * 1_000_000n
29
30
  }
@@ -32,7 +32,7 @@ setInputPath(config.inputPath)
32
32
  const jsonBuffer = new JSONBuffer({
33
33
  size: config.maxTotalPayloadSize,
34
34
  maxQueueBytes: config.queueMaxBytes,
35
- timeout: config.dynamicInstrumentation.uploadIntervalSeconds * 1000,
35
+ timeout: config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS * 1000,
36
36
  onFlush,
37
37
  })
38
38
 
@@ -2,8 +2,10 @@
2
2
 
3
3
  const config = require('../config')
4
4
 
5
- const excludedIdentifiers = new Set(config.dynamicInstrumentation.redactionExcludedIdentifiers
6
- .map((name) => normalizeName(name)))
5
+ const excludedIdentifiers = new Set(
6
+ config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS
7
+ .map((name) => normalizeName(name))
8
+ )
7
9
 
8
10
  const REDACTED_IDENTIFIERS = new Set(
9
11
  [
@@ -95,7 +97,7 @@ const REDACTED_IDENTIFIERS = new Set(
95
97
  'x_forwarded_for',
96
98
  'x_real_ip',
97
99
  'XSRF-TOKEN',
98
- ...config.dynamicInstrumentation.redactedIdentifiers,
100
+ ...config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS,
99
101
  ]
100
102
  .map(normalizeIdentifier)
101
103
  .filter(isIncludedIdentifier)
@@ -35,7 +35,7 @@ const cache = new TTLSet(60 * 60 * 1000) // 1 hour
35
35
  const jsonBuffer = new JSONBuffer({
36
36
  size: config.maxTotalPayloadSize,
37
37
  maxQueueBytes: DIAGNOSTICS_QUEUE_MAX_BYTES,
38
- timeout: config.dynamicInstrumentation.uploadIntervalSeconds * 1000,
38
+ timeout: config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS * 1000,
39
39
  onFlush,
40
40
  })
41
41
 
@@ -96,7 +96,7 @@ function start (config, rcInstance) {
96
96
 
97
97
  const probeSamplerBuffer = installProbeSampler(guardrailMetrics)
98
98
 
99
- readProbeFile(config.dynamicInstrumentation.probeFile, (probes) => {
99
+ readProbeFile(config.dynamicInstrumentation.DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE, (probes) => {
100
100
  const action = 'apply'
101
101
  for (const probe of probes) {
102
102
  probeChannel.port2.postMessage({ action, probe })
@@ -1,6 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
+ EVP_ORIGIN_HEADER_NAME: 'DD-EVP-ORIGIN',
5
+ EVP_ORIGIN_VERSION_HEADER_NAME: 'DD-EVP-ORIGIN-VERSION',
4
6
  EVP_PROXY_PATH_V2: '/evp_proxy/v2',
5
7
  EVP_PROXY_PATH_V4: '/evp_proxy/v4',
6
8
  EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
@@ -3,6 +3,8 @@
3
3
  const { createSiteUrl } = require('../exporters/common/url')
4
4
  const log = require('../log')
5
5
 
6
+ let invalidSiteWarningLogged = false
7
+
6
8
  /**
7
9
  * @typedef {object} DirectEVPRoute
8
10
  * @property {URL} url - Direct intake URL
@@ -21,21 +23,23 @@ const log = require('../log')
21
23
  */
22
24
  function createDirectEVPRoute (config, intake) {
23
25
  const apiKey = config.DD_API_KEY
24
- if (!apiKey || !config.site) return
25
-
26
- try {
27
- const url = createSiteUrl(config.site, intake)
28
- if (url === undefined) throw new Error('Invalid direct EVP intake URL')
26
+ if (!apiKey) return
29
27
 
30
- return {
31
- url,
32
- basePath: '',
33
- headers: {
34
- 'DD-API-KEY': apiKey,
35
- },
28
+ const url = createSiteUrl(config.site, intake)
29
+ if (url === undefined) {
30
+ if (!invalidSiteWarningLogged) {
31
+ invalidSiteWarningLogged = true
32
+ log.warn('Feature Flags direct event delivery is disabled because DD_SITE is invalid.')
36
33
  }
37
- } catch (error) {
38
- log.debug('Unable to configure direct EVP intake: %s', error.message)
34
+ return
35
+ }
36
+
37
+ return {
38
+ url,
39
+ basePath: '',
40
+ headers: {
41
+ 'DD-API-KEY': apiKey,
42
+ },
39
43
  }
40
44
  }
41
45
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { fetchAgentInfo } = require('../agent/info')
4
4
  const log = require('../log')
5
- const { stripTrailingSlashes } = require('./path')
5
+ const { joinAgentURLPath, stripTrailingSlashes } = require('./path')
6
6
 
7
7
  /**
8
8
  * Receiver discovery contract
@@ -25,13 +25,13 @@ const { stripTrailingSlashes } = require('./path')
25
25
  * serverless-init image or deployment type.
26
26
  *
27
27
  * This module only discovers a candidate route. A missing or unresponsive
28
- * `/info` endpoint returns an error through the shared request timeout and
29
- * retry policy. A valid response without a compatible path returns no route.
28
+ * `/info` endpoint returns an error after the request's bounded retry policy.
29
+ * Callers can disable retries to make discovery a single attempt. A valid
30
+ * response without a compatible path returns no route.
30
31
  * Discovery sends no events, so the caller can safely select direct intake
31
32
  * after either result. The caller also owns later delivery failures. Exposure
32
- * delivery uses retries and can therefore produce duplicates. After local
33
- * retries fail, the caller can retry through direct intake and use that route
34
- * for future batches.
33
+ * delivery decides whether a failed batch can be replayed and which route
34
+ * future batches use.
35
35
  *
36
36
  * Reference implementations:
37
37
  *
@@ -62,17 +62,17 @@ function selectEVPProxyPath (agentInfo, { supportedPaths, requiredHeaders = [] }
62
62
  }
63
63
 
64
64
  const allowedHeaders = agentInfo.evp_proxy_allowed_headers
65
- if (allowedHeaders !== undefined) {
65
+ if (requiredHeaders.length > 0 || allowedHeaders !== undefined) {
66
66
  if (!Array.isArray(allowedHeaders)) return
67
67
 
68
68
  const normalizedHeaders = new Set()
69
69
  for (const header of allowedHeaders) {
70
70
  if (typeof header === 'string') {
71
- normalizedHeaders.add(header.toLowerCase())
71
+ normalizedHeaders.add(header.trim().toLowerCase())
72
72
  }
73
73
  }
74
74
 
75
- if (requiredHeaders.some(header => !normalizedHeaders.has(header.toLowerCase()))) {
75
+ if (requiredHeaders.some(header => !normalizedHeaders.has(header.trim().toLowerCase()))) {
76
76
  return
77
77
  }
78
78
  }
@@ -105,6 +105,7 @@ function selectEVPProxyPath (agentInfo, { supportedPaths, requiredHeaders = [] }
105
105
  * @param {string[]} options.supportedPaths - Supported paths in preference order
106
106
  * @param {string[]} [options.requiredHeaders] - Headers that the proxy must forward unchanged to intake. Each
107
107
  * header must appear in `evp_proxy_allowed_headers`. Do not include routing headers that the Agent consumes.
108
+ * @param {boolean} [options.retry] - Set false to disable the request's default bounded retries
108
109
  * @param {(error: Error|null, route?: {url: URL, basePath: string}) => void} callback - Result callback
109
110
  */
110
111
  function discoverEVPProxy (url, options, callback) {
@@ -121,7 +122,10 @@ function discoverEVPProxy (url, options, callback) {
121
122
  }
122
123
 
123
124
  log.debug('EVP proxy route %s discovered through the configured local receiver', basePath)
124
- callback(null, { url, basePath })
125
+ callback(null, { url, basePath: joinAgentURLPath(url, basePath) })
126
+ }, {
127
+ path: joinAgentURLPath(url, '/info'),
128
+ retry: options.retry,
125
129
  })
126
130
  }
127
131
 
@@ -16,14 +16,28 @@ function stripTrailingSlashes (value) {
16
16
  *
17
17
  * This utility does not perform EVP proxy discovery.
18
18
  *
19
- * @param {string} basePath - EVP proxy base path
20
- * @param {string} endpoint - Product intake endpoint
19
+ * @param {...string} paths - URL path components
21
20
  */
22
- function joinEVPProxyPath (basePath, endpoint) {
23
- const normalizedBasePath = stripTrailingSlashes(basePath)
24
- const normalizedEndpoint = endpoint.replace(LEADING_SLASHES, '')
21
+ function joinEVPProxyPath (...paths) {
22
+ let joined = ''
23
+ for (const path of paths) {
24
+ const normalized = stripTrailingSlashes(path).replace(LEADING_SLASHES, '')
25
+ if (normalized) joined += `/${normalized}`
26
+ }
25
27
 
26
- return `${normalizedBasePath}/${normalizedEndpoint}`
28
+ return joined || '/'
27
29
  }
28
30
 
29
- module.exports = { joinEVPProxyPath, stripTrailingSlashes }
31
+ /**
32
+ * Joins an HTTP Agent URL path prefix with an EVP proxy path.
33
+ * Unix URL pathnames identify the socket itself and are not HTTP path prefixes.
34
+ *
35
+ * @param {URL} url - Configured Agent URL
36
+ * @param {...string} paths - EVP path components
37
+ */
38
+ function joinAgentURLPath (url, ...paths) {
39
+ const prefix = url.protocol === 'http:' || url.protocol === 'https:' ? url.pathname : ''
40
+ return joinEVPProxyPath(prefix, ...paths)
41
+ }
42
+
43
+ module.exports = { joinAgentURLPath, joinEVPProxyPath, stripTrailingSlashes }
@@ -5,6 +5,9 @@ const { format } = require('node:url')
5
5
 
6
6
  const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
7
7
 
8
+ const DEFAULT_SITE = 'datadoghq.com'
9
+ const DNS_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/
10
+
8
11
  /**
9
12
  * @param {string} hostname
10
13
  */
@@ -31,7 +34,11 @@ function canSendApiKey (protocol, hostname) {
31
34
  * @returns {URL | undefined}
32
35
  */
33
36
  function createSiteUrl (site, intake) {
34
- const hostname = `${intake === undefined ? '' : `${intake}.`}${site}`.toLowerCase()
37
+ const normalizedSite = normalizeSite(site)
38
+ if (normalizedSite === undefined) return
39
+
40
+ const hostname = `${intake === undefined ? '' : `${intake}.`}${normalizedSite}`
41
+ if (hostname.length > 253) return
35
42
 
36
43
  try {
37
44
  const url = new URL(format({
@@ -53,6 +60,30 @@ function createSiteUrl (site, intake) {
53
60
  } catch {}
54
61
  }
55
62
 
63
+ /**
64
+ * Normalizes a Datadog site as a DNS suffix.
65
+ *
66
+ * @param {string | undefined} site
67
+ * @returns {string | undefined}
68
+ */
69
+ function normalizeSite (site) {
70
+ if (site === undefined) return DEFAULT_SITE
71
+ if (typeof site !== 'string') return
72
+ for (let index = 0; index < site.length; index++) {
73
+ if (site.charCodeAt(index) > 127) return
74
+ }
75
+
76
+ const normalized = site.trim().toLowerCase() || DEFAULT_SITE
77
+ if (normalized.length > 253) return
78
+
79
+ const labels = normalized.split('.')
80
+ for (const label of labels) {
81
+ if (!DNS_LABEL.test(label)) return
82
+ }
83
+
84
+ return normalized
85
+ }
86
+
56
87
  /**
57
88
  * Convert an agent/intake URL into Node http(s) request options.
58
89
  *
@@ -80,4 +111,4 @@ function parseUrl (urlObjOrString) {
80
111
  return url
81
112
  }
82
113
 
83
- module.exports = { canSendApiKey, createSiteUrl, isLoopbackHost, parseUrl }
114
+ module.exports = { canSendApiKey, createSiteUrl, isLoopbackHost, normalizeSite, parseUrl }
@@ -77,6 +77,7 @@ function promptFromData (data, source) {
77
77
  version: String(version),
78
78
  source,
79
79
  template: template || data.chat_template || [],
80
+ config: data.config,
80
81
  promptUuid: data.prompt_uuid,
81
82
  promptVersionUuid: data.prompt_version_uuid || data.id || data.ID,
82
83
  })
@@ -488,6 +489,9 @@ class PromptManager {
488
489
  if (requireAppKey && !this.config.DD_APP_KEY) {
489
490
  throw new PromptAPIError(0, 'DD_APP_KEY is required for prompt write operations', ERROR_NAMES.AUTH)
490
491
  }
492
+ if (body?.config !== undefined && !isPlainObject(body.config)) {
493
+ throw new PromptAPIError(0, 'config must be a JSON object', ERROR_NAMES.VALIDATION)
494
+ }
491
495
 
492
496
  const headers = {
493
497
  'Content-Type': 'application/json',
@@ -523,7 +527,8 @@ class PromptManager {
523
527
  * Create a text or chat prompt and its first version.
524
528
  * @param {string} promptId
525
529
  * @param {string | Array<{role: string, content: string}>} template
526
- * @param {{title?: string, description?: string, userVersion?: string, envIds?: string[]}} [options]
530
+ * @param {{title?: string, description?: string, userVersion?: string, envIds?: string[],
531
+ * config?: Record<string, unknown>}} [options]
527
532
  * @returns {Promise<object | object[]>}
528
533
  */
529
534
  async createPrompt (promptId, template, options = {}) {
@@ -532,6 +537,7 @@ class PromptManager {
532
537
  if (options.description) body.description = options.description
533
538
  if (options.userVersion) body.user_version = options.userVersion
534
539
  if (options.envIds !== undefined) body.env_ids = options.envIds
540
+ if (options.config !== undefined) body.config = options.config
535
541
  const response = await this.#request('POST', PROMPTS_PATH, body, true)
536
542
  this.#evictPrompt(promptId)
537
543
  return response
@@ -541,7 +547,8 @@ class PromptManager {
541
547
  * Add a text or chat version to an existing prompt.
542
548
  * @param {string} promptId
543
549
  * @param {string | Array<{role: string, content: string}>} template
544
- * @param {{description?: string, userVersion?: string, envIds?: string[]}} [options]
550
+ * @param {{description?: string, userVersion?: string, envIds?: string[],
551
+ * config?: Record<string, unknown>}} [options]
545
552
  * @returns {Promise<object | object[]>}
546
553
  */
547
554
  async createPromptVersion (promptId, template, options = {}) {
@@ -549,6 +556,7 @@ class PromptManager {
549
556
  if (options.description) body.description = options.description
550
557
  if (options.userVersion) body.user_version = options.userVersion
551
558
  if (options.envIds !== undefined) body.env_ids = options.envIds
559
+ if (options.config !== undefined) body.config = options.config
552
560
  const path = `${PROMPTS_PATH}/${encodeURIComponent(promptId)}/versions`
553
561
  const response = await this.#request('POST', path, body, true)
554
562
  this.#evictPrompt(promptId)