dd-trace 5.110.0 → 5.112.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 (164) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/README.md +13 -8
  3. package/ci/init.js +21 -2
  4. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  5. package/ext/tags.js +2 -0
  6. package/index.d.ts +34 -1
  7. package/initialize.mjs +5 -6
  8. package/loader-hook.mjs +76 -55
  9. package/package.json +37 -34
  10. package/packages/datadog-instrumentations/src/ai.js +45 -0
  11. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  12. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  13. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  14. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  15. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  16. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  17. package/packages/datadog-instrumentations/src/fs.js +8 -6
  18. package/packages/datadog-instrumentations/src/graphql.js +26 -495
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  25. package/packages/datadog-instrumentations/src/jest.js +713 -63
  26. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  27. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  28. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  29. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  30. package/packages/datadog-instrumentations/src/mongoose.js +13 -2
  31. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  32. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
  33. package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
  34. package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
  35. package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
  36. package/packages/datadog-instrumentations/src/vitest.js +11 -1855
  37. package/packages/datadog-plugin-ai/src/index.js +1 -1
  38. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  39. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +19 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  44. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  45. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  46. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  47. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  48. package/packages/datadog-plugin-cucumber/src/index.js +15 -24
  49. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
  50. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  51. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  52. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  53. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  54. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  55. package/packages/datadog-plugin-graphql/src/execute.js +639 -12
  56. package/packages/datadog-plugin-graphql/src/index.js +37 -9
  57. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  58. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  59. package/packages/datadog-plugin-graphql/src/validate.js +17 -6
  60. package/packages/datadog-plugin-http/src/index.js +6 -8
  61. package/packages/datadog-plugin-jest/src/index.js +31 -15
  62. package/packages/datadog-plugin-mocha/src/index.js +40 -15
  63. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  64. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  65. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  66. package/packages/datadog-plugin-openai/src/services.js +2 -2
  67. package/packages/datadog-plugin-playwright/src/index.js +4 -3
  68. package/packages/datadog-plugin-vitest/src/index.js +120 -72
  69. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  70. package/packages/dd-trace/src/aiguard/index.js +9 -14
  71. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  72. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  73. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  74. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  75. package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
  76. package/packages/dd-trace/src/appsec/channels.js +3 -1
  77. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  78. package/packages/dd-trace/src/appsec/graphql.js +14 -11
  79. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
  80. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  81. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  82. package/packages/dd-trace/src/appsec/index.js +10 -1
  83. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  84. package/packages/dd-trace/src/appsec/reporter.js +49 -10
  85. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
  86. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  87. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  90. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  91. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
  92. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  93. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  94. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  96. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
  97. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  98. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  99. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  100. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  101. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  102. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  103. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  104. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  105. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  106. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  107. package/packages/dd-trace/src/config/defaults.js +3 -2
  108. package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
  109. package/packages/dd-trace/src/config/helper.js +1 -0
  110. package/packages/dd-trace/src/config/index.js +35 -22
  111. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  112. package/packages/dd-trace/src/config/remote_config.js +1 -1
  113. package/packages/dd-trace/src/config/supported-configurations.json +127 -56
  114. package/packages/dd-trace/src/constants.js +7 -0
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  116. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  117. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  118. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  119. package/packages/dd-trace/src/llmobs/index.js +1 -1
  120. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  121. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  122. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  123. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  124. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  125. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  126. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  127. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  128. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  129. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  130. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  131. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  132. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  133. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  135. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
  136. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  137. package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
  138. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  139. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  140. package/packages/dd-trace/src/plugin_manager.js +23 -7
  141. package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
  142. package/packages/dd-trace/src/plugins/index.js +2 -0
  143. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  144. package/packages/dd-trace/src/plugins/util/test.js +15 -9
  145. package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
  146. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  147. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
  148. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  149. package/packages/dd-trace/src/proxy.js +8 -8
  150. package/packages/dd-trace/src/span_processor.js +5 -6
  151. package/packages/dd-trace/src/span_stats.js +96 -78
  152. package/packages/dd-trace/src/startup-log.js +2 -1
  153. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  154. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  155. package/packages/dd-trace/src/telemetry/index.js +2 -2
  156. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  157. package/packages/dd-trace/src/telemetry/send-data.js +8 -8
  158. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  159. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  160. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  161. package/vendor/dist/protobufjs/index.js +1 -1
  162. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  163. package/vendor/dist/shell-quote/index.js +1 -1
  164. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
@@ -5,7 +5,7 @@ const VercelAILLMObsPlugin = require('../../dd-trace/src/llmobs/plugins/ai')
5
5
  const VercelAITracingPlugin = require('./tracing')
6
6
 
7
7
  class VercelAIPlugin extends CompositePlugin {
8
- static get id () { return 'ai' }
8
+ static id = 'ai'
9
9
  static get plugins () {
10
10
  return {
11
11
  llmobs: VercelAILLMObsPlugin,
@@ -1,10 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
3
4
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
- const { getModelProvider } = require('./utils')
5
+ const { getModelProvider, parseModelProvider } = require('./utils')
5
6
 
6
- class VercelAITracingPlugin extends TracingPlugin {
7
- static id = 'ai'
7
+ class DdTelemetryPlugin extends TracingPlugin {
8
+ static id = 'ai_tracing_dd_telemetry'
8
9
  static prefix = 'tracing:dd-trace:vercel-ai'
9
10
 
10
11
  bindStart (ctx) {
@@ -30,4 +31,66 @@ class VercelAITracingPlugin extends TracingPlugin {
30
31
  }
31
32
  }
32
33
 
34
+ class VercelAiTelemetryPlugin extends TracingPlugin {
35
+ static id = 'ai_tracing_vercel_telemetry'
36
+ static prefix = 'tracing:ai:telemetry'
37
+
38
+ #streamedCalls = new Set()
39
+
40
+ constructor () {
41
+ super(...arguments)
42
+
43
+ this.addSub('dd-trace:vercel-ai:chunk', ({ ctx, chunk, done }) => {
44
+ ctx.streamConsumed = done
45
+ })
46
+ }
47
+
48
+ bindStart (ctx) {
49
+ const { type: name, event } = ctx
50
+ const model = event.modelId
51
+ const modelProvider = parseModelProvider(event.provider, model)
52
+
53
+ let isStream = this.#streamedCalls.has(event.callId)
54
+ if (name.includes('stream')) {
55
+ this.#streamedCalls.add(event.callId)
56
+ isStream = true
57
+
58
+ ctx.streamConsumed = false
59
+ }
60
+
61
+ ctx.isStream = isStream
62
+
63
+ this.startSpan(name, {
64
+ meta: {
65
+ 'resource.name': event.functionId ?? name,
66
+ 'ai.request.model': model,
67
+ 'ai.request.model_provider': modelProvider,
68
+ },
69
+ }, ctx)
70
+
71
+ return ctx.currentStore
72
+ }
73
+
74
+ asyncEnd (ctx) {
75
+ // check if isStreamed and stream resolved
76
+ // this event will fire multiple times for the same channel
77
+ if (ctx.isStream && ctx.result?.stream && !ctx.streamConsumed) return
78
+
79
+ if (ctx.type?.includes('stream')) {
80
+ this.#streamedCalls.delete(ctx.event?.callId)
81
+ }
82
+
83
+ const span = ctx.currentStore?.span
84
+ span?.finish()
85
+ }
86
+ }
87
+
88
+ class VercelAITracingPlugin extends CompositePlugin {
89
+ static id = 'ai_tracing'
90
+ static plugins = {
91
+ dd: DdTelemetryPlugin,
92
+ ai: VercelAiTelemetryPlugin,
93
+ }
94
+ }
95
+
33
96
  module.exports = VercelAITracingPlugin
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { parseModelId } = require('../../datadog-plugin-aws-sdk/src/services/bedrockruntime/utils')
3
+ const { parseModelId: parseBedrockModelId } = require('../../datadog-plugin-aws-sdk/src/services/bedrockruntime/utils')
4
4
 
5
5
  /**
6
6
  * Get the model provider from the span tags or attributes.
@@ -11,12 +11,24 @@ const { parseModelId } = require('../../datadog-plugin-aws-sdk/src/services/bedr
11
11
  */
12
12
  function getModelProvider (tags) {
13
13
  const modelProviderTag = tags['ai.model.provider']
14
- const providerParts = modelProviderTag?.split('.')
14
+ const modelId = tags['ai.model.id']
15
+
16
+ return parseModelProvider(modelProviderTag, modelId)
17
+ }
18
+
19
+ /**
20
+ * Parse the model provider from the raw provider string.
21
+ *
22
+ * @param {string} rawProvider
23
+ * @param {string} modelId
24
+ * @returns {string}
25
+ */
26
+ function parseModelProvider (rawProvider, modelId) {
27
+ const providerParts = rawProvider?.split('.')
15
28
  const provider = providerParts?.[0]
16
29
 
17
30
  if (provider === 'amazon-bedrock') {
18
- const modelId = tags['ai.model.id']
19
- const model = modelId && parseModelId(modelId)
31
+ const model = modelId && parseBedrockModelId(modelId)
20
32
  return model?.modelProvider ?? provider
21
33
  }
22
34
 
@@ -25,4 +37,5 @@ function getModelProvider (tags) {
25
37
 
26
38
  module.exports = {
27
39
  getModelProvider,
40
+ parseModelProvider,
28
41
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const ClientPlugin = require('../../dd-trace/src/plugins/client')
4
- const { addOpMeta, unwrapDurableError } = require('./util')
4
+ const { addOpMeta, getStepDataForNext, unwrapDurableError } = require('./util')
5
5
 
6
6
  class AwsDurableExecutionSdkJsClientPlugin extends ClientPlugin {
7
7
  static id = 'aws-durable-execution-sdk-js'
@@ -29,7 +29,7 @@ class AwsDurableExecutionSdkJsClientPlugin extends ClientPlugin {
29
29
  if (operationName) {
30
30
  meta['aws.durable.operation_name'] = operationName
31
31
  }
32
- addOpMeta(meta, ctx.self)
32
+ addOpMeta(meta, getStepDataForNext(ctx.self))
33
33
 
34
34
  this.startSpan(this.operationName(), {
35
35
  resource: operationName,
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { storage } = require('../../datadog-core')
4
4
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
5
- const { addOpMeta, unwrapDurableError } = require('./util')
5
+ const { addOpMeta, getOperationAttempt, getStepDataForNext, unwrapDurableError } = require('./util')
6
6
 
7
7
  // Span names whose direct children must keep the default resource.
8
8
  // These can have very high cardinality which is undesireable in the resource.
@@ -41,12 +41,19 @@ class BaseContextPlugin extends TracingPlugin {
41
41
  if (operationName) {
42
42
  meta['aws.durable.operation_name'] = operationName
43
43
  }
44
- addOpMeta(meta, ctx.self)
44
+ const stepInfo = getStepDataForNext(ctx.self)
45
+ addOpMeta(meta, stepInfo)
46
+
47
+ const metrics = this.constructor.retryable
48
+ ? { 'aws.durable.operation_attempt': getOperationAttempt(stepInfo.stepData) }
49
+ : undefined
45
50
 
46
51
  this.startSpan(spanName, {
47
52
  resource,
53
+ type: this.constructor.type,
48
54
  kind: this.constructor.kind,
49
55
  meta,
56
+ metrics,
50
57
  }, ctx)
51
58
 
52
59
  return ctx.currentStore
@@ -71,11 +78,12 @@ class BaseContextPlugin extends TracingPlugin {
71
78
  }
72
79
  }
73
80
 
74
- function makeContextPlugin (method, spanName) {
81
+ function makeContextPlugin (method, spanName, { retryable }) {
75
82
  return class extends BaseContextPlugin {
76
83
  static prefix = `tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_${method}`
77
84
  static settleChannel = `apm:aws-durable-execution-sdk-js:${method}:settle`
78
85
  static spanName = spanName
86
+ static retryable = retryable
79
87
  }
80
88
  }
81
89
 
@@ -83,6 +91,7 @@ class RunInChildContextPlugin extends BaseContextPlugin {
83
91
  static prefix = 'tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_runInChildContext'
84
92
  static settleChannel = 'apm:aws-durable-execution-sdk-js:runInChildContext:settle'
85
93
  static spanName = 'aws.durable.child_context'
94
+ static retryable = false
86
95
 
87
96
  bindStart (ctx) {
88
97
  if (SUPPRESSED_CHILD_CONTEXT_SUBTYPES.has(getRunInChildContextSubType(ctx))) {
@@ -103,12 +112,12 @@ function getRunInChildContextSubType (ctx) {
103
112
  }
104
113
 
105
114
  module.exports = {
106
- step: makeContextPlugin('step', 'aws.durable.step'),
107
- wait: makeContextPlugin('wait', 'aws.durable.wait'),
108
- waitForCondition: makeContextPlugin('waitForCondition', 'aws.durable.wait_for_condition'),
109
- waitForCallback: makeContextPlugin('waitForCallback', 'aws.durable.wait_for_callback'),
110
- createCallback: makeContextPlugin('createCallback', 'aws.durable.create_callback'),
111
- map: makeContextPlugin('map', 'aws.durable.map'),
112
- parallel: makeContextPlugin('parallel', 'aws.durable.parallel'),
115
+ step: makeContextPlugin('step', 'aws.durable.step', { retryable: true }),
116
+ wait: makeContextPlugin('wait', 'aws.durable.wait', { retryable: false }),
117
+ waitForCondition: makeContextPlugin('waitForCondition', 'aws.durable.wait_for_condition', { retryable: true }),
118
+ waitForCallback: makeContextPlugin('waitForCallback', 'aws.durable.wait_for_callback', { retryable: false }),
119
+ createCallback: makeContextPlugin('createCallback', 'aws.durable.create_callback', { retryable: false }),
120
+ map: makeContextPlugin('map', 'aws.durable.map', { retryable: false }),
121
+ parallel: makeContextPlugin('parallel', 'aws.durable.parallel', { retryable: false }),
113
122
  runInChildContext: RunInChildContextPlugin,
114
123
  }
@@ -52,6 +52,7 @@ class AwsDurableExecutionSdkJsHandlerPlugin extends TracingPlugin {
52
52
 
53
53
  this.startSpan(this.operationName(), {
54
54
  resource: handler?.name,
55
+ type: this.constructor.type,
55
56
  kind: this.constructor.kind,
56
57
  meta,
57
58
  }, ctx)
@@ -2,27 +2,63 @@
2
2
 
3
3
  const { createHash } = require('node:crypto')
4
4
 
5
+ // A checkpoint in one of these terminal states means the operation is served from the checkpoint
6
+ // on a replay rather than executed: the SDK reloads a SUCCEEDED result or re-raises a FAILED error
7
+ // without running the user function. Both also carry the 1-indexed attempt that reached the terminal
8
+ // state, so both need the same normalization to agree with the 0-indexed live run.
9
+ const REPLAYED_STATUSES = new Set(['SUCCEEDED', 'FAILED'])
10
+
5
11
  /**
6
- * Populates the replay and operation_id tags for the op the DurableContextImpl is about to
7
- * run, deriving both from a single `getNextStepId()` call. `aws.durable.replayed` is always
8
- * set ('true' when the next stepId already has a SUCCEEDED checkpoint entry, i.e. the op will
9
- * be served from the SDK's checkpoint). `aws.durable.operation_id` — the 16-hex-char MD5 of
10
- * the stepId, mirroring the SDK's internal calculation — is only added when a stepId exists.
11
- * @param {Record<string, string>} meta - The span meta/tags object to populate.
12
+ * Resolves the SDK's next stepId and its checkpoint entry in a single pass, so one span start can
13
+ * feed both addOpMeta and getOperationAttempt without traversing the SDK internals twice. `stepData`
14
+ * is undefined when there is no next stepId, or no checkpoint entry exists for it yet.
12
15
  * @param {object} [ctxImpl] - The DurableContextImpl about to run the op.
13
- * @returns {void}
16
+ * @returns {{ stepId: string | undefined, stepData: object | undefined }}
14
17
  */
15
- function addOpMeta (meta, ctxImpl) {
18
+ function getStepDataForNext (ctxImpl) {
16
19
  const stepId = ctxImpl?.getNextStepId?.()
20
+ const stepData = stepId ? ctxImpl?._executionContext?.getStepData?.(stepId) : undefined
21
+ return { stepId, stepData }
22
+ }
23
+
24
+ /**
25
+ * Populates the replay and operation_id tags from a pre-resolved step lookup (see
26
+ * getStepDataForNext). `aws.durable.replayed` is always set ('true' when the next stepId already
27
+ * has a terminal checkpoint entry — SUCCEEDED or FAILED — i.e. the op will be served from the SDK's
28
+ * checkpoint rather than executed). `aws.durable.operation_id` — the 16-hex-char MD5 of the stepId,
29
+ * mirroring the SDK's internal calculation — is only added when a stepId exists.
30
+ * @param {Record<string, string>} meta - The span meta/tags object to populate.
31
+ * @param {{ stepId?: string, stepData?: object }} stepInfo - Resolved next stepId and checkpoint entry.
32
+ * @returns {void}
33
+ */
34
+ function addOpMeta (meta, { stepId, stepData }) {
17
35
  if (!stepId) {
18
36
  meta['aws.durable.replayed'] = 'false'
19
37
  return
20
38
  }
21
- const stepData = ctxImpl?._executionContext?.getStepData?.(stepId)
22
- meta['aws.durable.replayed'] = String(stepData?.Status === 'SUCCEEDED')
39
+ meta['aws.durable.replayed'] = String(REPLAYED_STATUSES.has(stepData?.Status))
23
40
  meta['aws.durable.operation_id'] = createHash('md5').update(stepId).digest('hex').slice(0, 16)
24
41
  }
25
42
 
43
+ /**
44
+ * Returns the 0-indexed attempt number for the op (0 original, 1 first retry, …) from a pre-resolved
45
+ * checkpoint entry (see getStepDataForNext), defaulting to 0 before any checkpoint exists.
46
+ *
47
+ * StepDetails.Attempt is indexed differently depending on checkpoint status: on a pending/retry
48
+ * checkpoint it's the count of prior failed attempts (already 0-indexed), but on a terminal
49
+ * checkpoint read on replay (SUCCEEDED or FAILED) it's the 1-indexed attempt that reached that
50
+ * state. We subtract 1 in the terminal case so a replay agrees with the original run, flooring at
51
+ * 0 since the 1-indexing is observed server behavior, not an SDK guarantee.
52
+ *
53
+ * @param {object} [stepData] - The checkpoint entry for the next stepId.
54
+ * @returns {number}
55
+ */
56
+ function getOperationAttempt (stepData) {
57
+ const attempt = stepData?.StepDetails?.Attempt
58
+ if (!Number.isFinite(attempt)) return 0
59
+ return REPLAYED_STATUSES.has(stepData.Status) ? Math.max(0, attempt - 1) : attempt
60
+ }
61
+
26
62
  /**
27
63
  * The SDK wraps user errors in typed classes (StepError, ChildContextError, etc.); we follow the
28
64
  * `.cause` chain to recover the user's original Error. SDK wrappers expose a string `errorType`
@@ -40,4 +76,4 @@ function unwrapDurableError (ctx) {
40
76
  return err
41
77
  }
42
78
 
43
- module.exports = { addOpMeta, unwrapDurableError }
79
+ module.exports = { addOpMeta, getOperationAttempt, getStepDataForNext, unwrapDurableError }
@@ -97,11 +97,22 @@ class ChildProcessPlugin extends TracingPlugin {
97
97
  }
98
98
 
99
99
  asyncEnd (ctx) {
100
- const { result } = ctx
100
+ const { result, error } = ctx
101
+ let exitCode
102
+
103
+ if (result !== null && typeof result === 'object') {
104
+ // util.promisify(execFile) resolves with a { stdout, stderr } object on
105
+ // success, where the exit code is 0.
106
+ exitCode = result.status ?? 0
107
+ } else if (result === undefined && error !== undefined) {
108
+ exitCode = error.status ?? error.code ?? 0
109
+ } else {
110
+ exitCode = result
111
+ }
101
112
 
102
113
  const span = ctx.currentStore?.span || this.activeSpan
103
114
 
104
- span?.setTag('cmd.exit_code', `${result}`)
115
+ span?.setTag('cmd.exit_code', `${exitCode}`)
105
116
  span?.finish()
106
117
 
107
118
  return ctx.parentStore
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ require('../../datadog-instrumentations/src/claude-agent-sdk')
4
+
5
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
6
+ const ClaudeAgentSdkLLMObsPlugins = require('../../dd-trace/src/llmobs/plugins/claude-agent-sdk')
7
+ const ClaudeAgentSdkTracingPlugins = require('./tracing')
8
+
9
+ class ClaudeAgentSdkPlugin extends CompositePlugin {
10
+ static id = 'claude-agent-sdk'
11
+
12
+ static get plugins () {
13
+ const plugins = {}
14
+
15
+ // LLM Obs plugins must be registered before tracing plugins so that
16
+ // annotations are added to the span before it finishes.
17
+ // The tracing plugin uses `bindStart` vs the LLM Obs plugin's `start`,
18
+ // so the span is created in the tracing plugin before the LLM Obs one runs.
19
+ for (const Plugin of ClaudeAgentSdkLLMObsPlugins) {
20
+ plugins[Plugin.id] = Plugin
21
+ }
22
+
23
+ for (const Plugin of ClaudeAgentSdkTracingPlugins) {
24
+ plugins[Plugin.id] = Plugin
25
+ }
26
+
27
+ return plugins
28
+ }
29
+ }
30
+
31
+ module.exports = ClaudeAgentSdkPlugin
@@ -0,0 +1,107 @@
1
+ 'use strict'
2
+
3
+ const { storage } = require('../../datadog-core')
4
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
5
+ const { splitModel } = require('./util')
6
+
7
+ class QueryTracingPlugin extends TracingPlugin {
8
+ static id = 'claude_agent_sdk_query'
9
+ static operation = 'turn'
10
+ static system = 'claude-agent-sdk'
11
+ static prefix = 'tracing:orchestrion:@anthropic-ai/claude-agent-sdk:query'
12
+
13
+ bindStart (ctx) {
14
+ this.startSpan('claude_agent_sdk.query', {
15
+ meta: { 'resource.name': 'claude_agent_sdk.query' },
16
+ startTime: ctx.startTime,
17
+ }, ctx)
18
+ ctx.runInContext = fn => storage('legacy').run(ctx.currentStore, fn)
19
+ return ctx.currentStore
20
+ }
21
+
22
+ asyncEnd (ctx) {
23
+ if (!ctx.streamResolved) return
24
+
25
+ ctx.currentStore?.span?.finish(ctx.finishTime)
26
+ }
27
+ }
28
+
29
+ class StepTracingPlugin extends TracingPlugin {
30
+ static id = 'claude_agent_sdk_step'
31
+ static operation = 'step'
32
+ static system = 'claude-agent-sdk'
33
+ static prefix = 'tracing:apm:claude-agent-sdk:step'
34
+
35
+ bindStart (ctx) {
36
+ this.startSpan(`step-${ctx.stepIndex}`, {
37
+ meta: { 'resource.name': 'claude_agent_sdk.step' },
38
+ startTime: ctx.startTime,
39
+ }, ctx)
40
+
41
+ return ctx.currentStore
42
+ }
43
+
44
+ end (ctx) {
45
+ ctx.currentStore?.span?.finish(ctx.finishTime)
46
+ }
47
+ }
48
+
49
+ class ToolTracingPlugin extends TracingPlugin {
50
+ static id = 'claude_agent_sdk_tool'
51
+ static operation = 'tool'
52
+ static system = 'claude-agent-sdk'
53
+ static prefix = 'tracing:apm:claude-agent-sdk:tool'
54
+
55
+ bindStart (ctx) {
56
+ const toolName = ctx.name || 'claude_agent_sdk.tool'
57
+
58
+ this.startSpan(toolName, {
59
+ meta: { 'resource.name': 'claude_agent_sdk.tool' },
60
+ startTime: ctx.startTime,
61
+ }, ctx)
62
+
63
+ return ctx.currentStore
64
+ }
65
+
66
+ end (ctx) {
67
+ const span = ctx.currentStore?.span
68
+ if (ctx.error) this.addError(ctx.error, span)
69
+ span?.finish(ctx.finishTime)
70
+ }
71
+ }
72
+
73
+ class LlmTracingPlugin extends TracingPlugin {
74
+ static id = 'claude_agent_sdk_llm'
75
+ static operation = 'llm'
76
+ static system = 'claude-agent-sdk'
77
+ static prefix = 'tracing:apm:claude-agent-sdk:llm'
78
+
79
+ bindStart (ctx) {
80
+ const { model } = ctx
81
+
82
+ const { modelName, modelProvider } = splitModel(model)
83
+ const name = modelName || 'claude_agent_sdk.llm'
84
+
85
+ this.startSpan(name, {
86
+ meta: {
87
+ 'resource.name': 'claude_agent_sdk.llm',
88
+ 'claude-agent-sdk.request.model_name': modelName,
89
+ 'claude-agent-sdk.request.model_provider': modelProvider,
90
+ },
91
+ startTime: ctx.startTime,
92
+ }, ctx)
93
+
94
+ return ctx.currentStore
95
+ }
96
+
97
+ end (ctx) {
98
+ ctx.currentStore?.span?.finish(ctx.finishTime)
99
+ }
100
+ }
101
+
102
+ module.exports = [
103
+ QueryTracingPlugin,
104
+ StepTracingPlugin,
105
+ ToolTracingPlugin,
106
+ LlmTracingPlugin,
107
+ ]
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Parse a model ID string into the model name and provider for claude agent sdk provided model IDs
5
+ * @param {string} model model id
6
+ * @returns {{ modelName: string, modelProvider: string }}
7
+ */
8
+ function splitModel (model) {
9
+ if (!model) return { modelName: undefined, modelProvider: 'anthropic' }
10
+ const idx = model.indexOf('/')
11
+ if (idx === -1) return { modelName: model, modelProvider: 'anthropic' }
12
+ return { modelName: model.slice(idx + 1), modelProvider: model.slice(0, idx) }
13
+ }
14
+
15
+ module.exports = { splitModel }
@@ -1,9 +1,5 @@
1
1
  'use strict'
2
2
 
3
- // Capture real timers at module load time, before any test can install fake timers.
4
- const realDateNow = Date.now.bind(Date)
5
- const realSetTimeout = setTimeout
6
-
7
3
  const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
8
4
  const { storage } = require('../../datadog-core')
9
5
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
@@ -54,9 +50,6 @@ const {
54
50
  TELEMETRY_TEST_SESSION,
55
51
  } = require('../../dd-trace/src/ci-visibility/telemetry')
56
52
 
57
- const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
58
- const BREAKPOINT_SET_GRACE_PERIOD_MS = 400
59
-
60
53
  const isCucumberWorker = !!getEnvironmentVariable('CUCUMBER_WORKER_ID')
61
54
 
62
55
  class CucumberPlugin extends CiPlugin {
@@ -130,7 +123,7 @@ class CucumberPlugin extends CiPlugin {
130
123
  finishAllTraceSpans(this.testSessionSpan)
131
124
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
132
125
  provider: this.ciProviderName,
133
- autoInjected: !!this._tracerConfig.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
126
+ autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
134
127
  })
135
128
 
136
129
  this.libraryConfig = null
@@ -248,17 +241,14 @@ class CucumberPlugin extends CiPlugin {
248
241
  ctx.currentStore = { ...store, span }
249
242
 
250
243
  this.activeTestSpan = span
251
- // Time we give the breakpoint to be hit
252
244
  if (promises && this.runningTestProbe) {
253
- promises.hitBreakpointPromise = new Promise((resolve) => {
254
- realSetTimeout(resolve, BREAKPOINT_HIT_GRACE_PERIOD_MS)
255
- })
245
+ promises.hitBreakpointPromise = this.waitForDiBreakpointHits()
256
246
  }
257
247
 
258
248
  return ctx.currentStore
259
249
  })
260
250
 
261
- this.addSub('ci:cucumber:test:retry', ({ span, isFirstAttempt, error, isAtrRetry }) => {
251
+ this.addSub('ci:cucumber:test:retry', ({ span, isFirstAttempt, error, isAtrRetry, promises, canWaitForDi }) => {
262
252
  if (!isFirstAttempt) {
263
253
  span.setTag(TEST_IS_RETRY, 'true')
264
254
  if (isAtrRetry) {
@@ -268,18 +258,18 @@ class CucumberPlugin extends CiPlugin {
268
258
  }
269
259
  }
270
260
  span.setTag('error', error)
271
- if (isFirstAttempt && this.di && error && this.libraryConfig?.isDiEnabled) {
272
- const probeInformation = this.addDiProbe(error)
273
- if (probeInformation) {
274
- const { file, line, stackIndex } = probeInformation
275
- this.runningTestProbe = { file, line }
276
- this.testErrorStackIndex = stackIndex
277
- const waitUntil = realDateNow() + BREAKPOINT_SET_GRACE_PERIOD_MS
278
- while (realDateNow() < waitUntil) {
279
- // TODO: To avoid a race condition, we should wait until `probeInformation.setProbePromise` has resolved.
280
- // However, Cucumber doesn't have a mechanism for waiting asyncrounously here, so for now, we'll have to
281
- // fall back to a fixed syncronous delay.
261
+ if (canWaitForDi !== false && promises && this.di && error && this.libraryConfig?.isDiEnabled) {
262
+ if (isFirstAttempt) {
263
+ const probeInformation = this.addDiProbe(error)
264
+ if (probeInformation) {
265
+ const { file, line, stackIndex, setProbePromise } = probeInformation
266
+ this.runningTestProbe = { file, line }
267
+ this.testErrorStackIndex = stackIndex
268
+ this.prepareDiBreakpointHitWait()
269
+ promises.setProbePromise = this.waitForDiOperation(setProbePromise)
282
270
  }
271
+ } else if (this.runningTestProbe) {
272
+ this.prepareDiBreakpointHitWait()
283
273
  }
284
274
  }
285
275
  span.setTag(TEST_STATUS, 'fail')
@@ -410,6 +400,7 @@ class CucumberPlugin extends CiPlugin {
410
400
  this.tracer._exporter.flush()
411
401
  }
412
402
  this.activeTestSpan = null
403
+ this.cancelDiBreakpointHitWait()
413
404
  if (this.runningTestProbe) {
414
405
  this.removeDiProbe(this.runningTestProbe)
415
406
  this.runningTestProbe = null