dd-trace 6.17.0 → 6.19.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 (110) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ci/vitest-no-worker-init-setup.mjs +22 -12
  3. package/index.d.ts +55 -11
  4. package/package.json +6 -6
  5. package/packages/datadog-instrumentations/src/anthropic.js +111 -9
  6. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
  7. package/packages/datadog-instrumentations/src/cucumber.js +50 -9
  8. package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
  9. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
  10. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
  12. package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +51 -0
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +6 -0
  19. package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
  20. package/packages/datadog-instrumentations/src/jest.js +30 -13
  21. package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
  23. package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
  24. package/packages/datadog-instrumentations/src/playwright.js +73 -6
  25. package/packages/datadog-instrumentations/src/undici.js +7 -3
  26. package/packages/datadog-instrumentations/src/vitest-main.js +95 -21
  27. package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
  28. package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
  29. package/packages/datadog-instrumentations/src/ws.js +41 -1
  30. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
  31. package/packages/datadog-plugin-cucumber/src/index.js +22 -8
  32. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -14
  33. package/packages/datadog-plugin-cypress/src/index.js +3 -1
  34. package/packages/datadog-plugin-fetch/src/index.js +3 -0
  35. package/packages/datadog-plugin-jest/src/index.js +3 -4
  36. package/packages/datadog-plugin-mocha/src/index.js +3 -4
  37. package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
  38. package/packages/datadog-plugin-playwright/src/index.js +11 -6
  39. package/packages/datadog-plugin-undici/src/index.js +5 -0
  40. package/packages/datadog-plugin-vitest/src/index.js +3 -4
  41. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  42. package/packages/datadog-shimmer/src/shimmer.js +14 -10
  43. package/packages/datadog-turbopack/index.js +4 -1
  44. package/packages/datadog-turbopack/src/loader.js +6 -2
  45. package/packages/dd-trace/src/agent/info.js +5 -4
  46. package/packages/dd-trace/src/aiguard/client.js +38 -19
  47. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
  48. package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
  49. package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
  50. package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
  51. package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
  52. package/packages/dd-trace/src/config/generated-config-types.d.ts +19 -19
  53. package/packages/dd-trace/src/config/index.js +7 -6
  54. package/packages/dd-trace/src/config/supported-configurations.json +16 -5
  55. package/packages/dd-trace/src/constants.js +2 -0
  56. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  57. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
  58. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
  59. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  60. package/packages/dd-trace/src/debugger/index.js +1 -1
  61. package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
  62. package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
  63. package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
  64. package/packages/dd-trace/src/evp_proxy/path.js +21 -7
  65. package/packages/dd-trace/src/exporters/common/url.js +33 -2
  66. package/packages/dd-trace/src/llmobs/constants/tags.js +31 -8
  67. package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
  68. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
  69. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
  70. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
  71. package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
  72. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
  73. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
  74. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
  75. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
  76. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
  77. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
  78. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
  79. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +13 -0
  80. package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
  81. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
  82. package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
  83. package/packages/dd-trace/src/llmobs/prompts/prompt.js +79 -12
  84. package/packages/dd-trace/src/llmobs/span_processor.js +10 -65
  85. package/packages/dd-trace/src/llmobs/tagger.js +11 -40
  86. package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
  87. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
  88. package/packages/dd-trace/src/openfeature/index.js +6 -2
  89. package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
  90. package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
  91. package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
  92. package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
  93. package/packages/dd-trace/src/opentracing/tracer.js +3 -3
  94. package/packages/dd-trace/src/plugin_manager.js +27 -8
  95. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
  96. package/packages/dd-trace/src/plugins/util/test.js +13 -4
  97. package/packages/dd-trace/src/profiler.js +51 -5
  98. package/packages/dd-trace/src/profiling/profiler.js +24 -20
  99. package/packages/dd-trace/src/proxy.js +2 -2
  100. package/packages/dd-trace/src/ritm.js +12 -9
  101. package/packages/dd-trace/src/span_processor.js +6 -1
  102. package/packages/dd-trace/src/telemetry/send-data.js +2 -1
  103. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  104. package/packages/dd-trace/src/tracer.js +5 -4
  105. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  106. package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
  107. package/packages/datadog-instrumentations/src/bullmq.js +0 -11
  108. package/packages/datadog-instrumentations/src/langchain.js +0 -7
  109. package/packages/datadog-instrumentations/src/langgraph.js +0 -7
  110. package/packages/datadog-instrumentations/src/mercurius.js +0 -11
@@ -431,10 +431,74 @@ function getMessagesOutputMessages (body) {
431
431
  return convertAnthropicMessage({ role, content: body.content })
432
432
  }
433
433
 
434
+ /**
435
+ * Combines Anthropic message stream events into regular output messages.
436
+ *
437
+ * Accumulates exactly the way the SDK does: every `content_block_start` appends a block, and
438
+ * deltas address blocks by position. Keying blocks by `event.index` instead would let a repeated
439
+ * or out-of-range index hide output that the caller still receives.
440
+ *
441
+ * @param {Array<object>} events
442
+ * @returns {Array<object>}
443
+ */
444
+ function getStreamedMessagesOutputMessages (events) {
445
+ let message
446
+ let contentBlocks
447
+ // Keyed by block, not by index: distinct indices can resolve to one block, and a second
448
+ // message must not inherit partial JSON accumulated for the first.
449
+ const inputJson = new Map()
450
+
451
+ for (const event of events) {
452
+ if (!event || typeof event !== 'object') continue
453
+
454
+ if (event.type === 'message_start' && event.message && typeof event.message === 'object') {
455
+ contentBlocks = Array.isArray(event.message.content)
456
+ ? event.message.content.map(block => ({ ...block }))
457
+ : []
458
+ message = { role: event.message.role || 'assistant' }
459
+ continue
460
+ }
461
+
462
+ if (event.type === 'content_block_start' && event.content_block && typeof event.content_block === 'object') {
463
+ message ??= { role: 'assistant' }
464
+ contentBlocks ??= []
465
+ contentBlocks.push({ ...event.content_block })
466
+ continue
467
+ }
468
+
469
+ if (event.type !== 'content_block_delta' || !event.delta || typeof event.delta !== 'object') continue
470
+
471
+ const block = contentBlocks?.at(event.index ?? 0)
472
+ if (!block) continue
473
+
474
+ if (event.delta.type === 'text_delta' && block.type === 'text' && typeof event.delta.text === 'string') {
475
+ block.text = (block.text || '') + event.delta.text
476
+ } else if (event.delta.type === 'input_json_delta' && typeof event.delta.partial_json === 'string') {
477
+ inputJson.set(block, (inputJson.get(block) || '') + event.delta.partial_json)
478
+ }
479
+ }
480
+
481
+ if (!message) return []
482
+
483
+ for (const [block, json] of inputJson) {
484
+ // An empty buffer is what a no-argument tool call accumulates; the SDK keeps `{}` there.
485
+ if (!json) continue
486
+ try {
487
+ block.input = JSON.parse(json)
488
+ } catch {
489
+ block.input = json
490
+ }
491
+ }
492
+
493
+ message.content = contentBlocks
494
+ return getMessagesOutputMessages(message)
495
+ }
496
+
434
497
  module.exports = {
435
498
  convertAnthropicSystem,
436
499
  convertAnthropicBlocksToContent,
437
500
  convertAnthropicMessage,
438
501
  getMessagesInputMessages,
439
502
  getMessagesOutputMessages,
503
+ getStreamedMessagesOutputMessages,
440
504
  }
@@ -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;
@@ -577,8 +574,8 @@ export interface GeneratedConfig {
577
574
  DD_CODE_COVERAGE_FLAGS: string | undefined;
578
575
  DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT: number | undefined;
579
576
  DD_TEST_FAILED_TEST_REPLAY_ENABLED: boolean;
580
- DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean | undefined;
581
- DD_TEST_FAILURE_VIDEOS_ENABLED: boolean | undefined;
577
+ DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean;
578
+ DD_TEST_FAILURE_VIDEOS_ENABLED: boolean;
582
579
  DD_TEST_FLEET_CONFIG_PATH: string | undefined;
583
580
  DD_TEST_LOCAL_CONFIG_PATH: string | undefined;
584
581
  DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: number;
@@ -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: {
@@ -835,8 +835,8 @@ export interface GeneratedEnvVarConfig {
835
835
  DD_TELEMETRY_METRICS_ENABLED: boolean;
836
836
  DD_TEST_EARLY_FLAKE_DETECTION_RETRY_COUNT: number | undefined;
837
837
  DD_TEST_FAILED_TEST_REPLAY_ENABLED: boolean;
838
- DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean | undefined;
839
- DD_TEST_FAILURE_VIDEOS_ENABLED: boolean | undefined;
838
+ DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean;
839
+ DD_TEST_FAILURE_VIDEOS_ENABLED: boolean;
840
840
  DD_TEST_FLEET_CONFIG_PATH: string | undefined;
841
841
  DD_TEST_LOCAL_CONFIG_PATH: string | undefined;
842
842
  DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: number;
@@ -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
@@ -59,7 +59,7 @@
59
59
  "type": "boolean",
60
60
  "namespace": "aiguard",
61
61
  "default": "false",
62
- "description": "Analyze streamed OpenAI and Vercel AI responses with AI Guard After Model evaluation."
62
+ "description": "Analyze streamed Anthropic, OpenAI, and Vercel AI responses with AI Guard After Model evaluation."
63
63
  }
64
64
  ],
65
65
  "DD_AI_GUARD_BLOCK": [
@@ -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
  ],
@@ -2046,17 +2055,17 @@
2046
2055
  ],
2047
2056
  "DD_TEST_FAILURE_SCREENSHOTS_ENABLED": [
2048
2057
  {
2049
- "implementation": "A",
2058
+ "implementation": "B",
2050
2059
  "type": "boolean",
2051
- "default": null,
2060
+ "default": "true",
2052
2061
  "namespace": "testOptimization"
2053
2062
  }
2054
2063
  ],
2055
2064
  "DD_TEST_FAILURE_VIDEOS_ENABLED": [
2056
2065
  {
2057
- "implementation": "A",
2066
+ "implementation": "B",
2058
2067
  "type": "boolean",
2059
- "default": null,
2068
+ "default": "true",
2060
2069
  "namespace": "testOptimization"
2061
2070
  }
2062
2071
  ],
@@ -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
  ],
@@ -22,6 +22,8 @@ module.exports = {
22
22
  SPAN_SAMPLING_RULE_RATE: '_dd.span_sampling.rule_rate',
23
23
  SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
24
24
  SVC_SRC_KEY: '_dd.svc_src',
25
+ SDK_OTLP_EXPORT_KEY: '_dd.sdk.otlp_export',
26
+ SDK_SEMANTICS_KEY: 'datadog.sdk.semantics',
25
27
  DATADOG_LAMBDA_EXTENSION_PATH: '/opt/extensions/datadog-agent',
26
28
  DATADOG_MINI_AGENT_PATH: '/tmp/datadog/mini_agent_ready',
27
29
  DECISION_MAKER_KEY: '_dd.p.dm',
@@ -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 }
@@ -1,5 +1,14 @@
1
1
  'use strict'
2
2
 
3
+ const INPUT_TOKENS_METRIC_KEY = 'input_tokens'
4
+ const OUTPUT_TOKENS_METRIC_KEY = 'output_tokens'
5
+ const TOTAL_TOKENS_METRIC_KEY = 'total_tokens'
6
+ const CACHE_READ_INPUT_TOKENS_METRIC_KEY = 'cache_read_input_tokens'
7
+ const CACHE_WRITE_INPUT_TOKENS_METRIC_KEY = 'cache_write_input_tokens'
8
+ const CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY = 'ephemeral_5m_input_tokens'
9
+ const CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY = 'ephemeral_1h_input_tokens'
10
+ const REASONING_OUTPUT_TOKENS_METRIC_KEY = 'reasoning_output_tokens'
11
+
3
12
  module.exports = {
4
13
  SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval', 'experiment'],
5
14
  SPAN_KIND: '_ml_obs.meta.span.kind',
@@ -42,6 +51,7 @@ module.exports = {
42
51
  MODEL_NAME: '_ml_obs.meta.model_name',
43
52
  MODEL_PROVIDER: '_ml_obs.meta.model_provider',
44
53
  UNKNOWN_MODEL_PROVIDER: 'unknown',
54
+ DEFAULT_MODEL: 'custom',
45
55
 
46
56
  INPUT_DOCUMENTS: '_ml_obs.meta.input.documents',
47
57
  INPUT_MESSAGES: '_ml_obs.meta.input.messages',
@@ -52,14 +62,27 @@ module.exports = {
52
62
  OUTPUT_MESSAGES: '_ml_obs.meta.output.messages',
53
63
  OUTPUT_VALUE: '_ml_obs.meta.output.value',
54
64
 
55
- INPUT_TOKENS_METRIC_KEY: 'input_tokens',
56
- OUTPUT_TOKENS_METRIC_KEY: 'output_tokens',
57
- TOTAL_TOKENS_METRIC_KEY: 'total_tokens',
58
- CACHE_READ_INPUT_TOKENS_METRIC_KEY: 'cache_read_input_tokens',
59
- CACHE_WRITE_INPUT_TOKENS_METRIC_KEY: 'cache_write_input_tokens',
60
- CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY: 'ephemeral_5m_input_tokens',
61
- CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY: 'ephemeral_1h_input_tokens',
62
- REASONING_OUTPUT_TOKENS_METRIC_KEY: 'reasoning_output_tokens',
65
+ INPUT_TOKENS_METRIC_KEY,
66
+ OUTPUT_TOKENS_METRIC_KEY,
67
+ TOTAL_TOKENS_METRIC_KEY,
68
+ CACHE_READ_INPUT_TOKENS_METRIC_KEY,
69
+ CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
70
+ CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
71
+ CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
72
+ REASONING_OUTPUT_TOKENS_METRIC_KEY,
73
+
74
+ // integrations build metric objects with these camelCase spellings. Null prototype: a metric
75
+ // named after an `Object.prototype` member must not resolve to an inherited property.
76
+ METRIC_KEY_ALIASES: Object.assign(Object.create(null), {
77
+ inputTokens: INPUT_TOKENS_METRIC_KEY,
78
+ outputTokens: OUTPUT_TOKENS_METRIC_KEY,
79
+ totalTokens: TOTAL_TOKENS_METRIC_KEY,
80
+ cacheReadTokens: CACHE_READ_INPUT_TOKENS_METRIC_KEY,
81
+ cacheWriteTokens: CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
82
+ cacheWrite5mTokens: CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
83
+ cacheWrite1hTokens: CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
84
+ reasoningOutputTokens: REASONING_OUTPUT_TOKENS_METRIC_KEY,
85
+ }),
63
86
 
64
87
  DROPPED_IO_COLLECTION_ERROR: 'dropped_io',
65
88