dd-trace 6.0.0 → 6.1.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.
- package/LICENSE-3rdparty.csv +1 -2
- package/index.d.ts +21 -1
- package/package.json +32 -29
- package/packages/datadog-instrumentations/src/ai.js +45 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
- package/packages/datadog-instrumentations/src/child_process.js +3 -3
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
- package/packages/datadog-instrumentations/src/cucumber.js +102 -43
- package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
- package/packages/datadog-instrumentations/src/fastify.js +27 -8
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/jest.js +713 -63
- package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
- package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
- package/packages/datadog-instrumentations/src/mongodb.js +74 -0
- package/packages/datadog-instrumentations/src/mongoose.js +4 -5
- package/packages/datadog-instrumentations/src/playwright.js +13 -9
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +28 -1
- package/packages/datadog-instrumentations/src/vitest-util.js +5 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +104 -34
- package/packages/datadog-plugin-ai/src/index.js +1 -1
- package/packages/datadog-plugin-ai/src/tracing.js +66 -3
- package/packages/datadog-plugin-ai/src/utils.js +17 -4
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
- package/packages/datadog-plugin-child_process/src/index.js +13 -2
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
- package/packages/datadog-plugin-cucumber/src/index.js +14 -23
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
- package/packages/datadog-plugin-cypress/src/index.js +47 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
- package/packages/datadog-plugin-graphql/src/execute.js +21 -10
- package/packages/datadog-plugin-graphql/src/index.js +17 -1
- package/packages/datadog-plugin-graphql/src/utils.js +9 -2
- package/packages/datadog-plugin-graphql/src/validate.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +30 -14
- package/packages/datadog-plugin-mocha/src/index.js +39 -14
- package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
- package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
- package/packages/datadog-plugin-playwright/src/index.js +3 -2
- package/packages/datadog-plugin-vitest/src/index.js +51 -9
- package/packages/dd-trace/src/aiguard/index.js +9 -14
- package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
- package/packages/dd-trace/src/appsec/graphql.js +9 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +37 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugin_manager.js +16 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +4 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
|
@@ -1,8 +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 realSetTimeout = setTimeout
|
|
5
|
-
|
|
6
3
|
const { performance } = require('node:perf_hooks')
|
|
7
4
|
|
|
8
5
|
const shimmer = require('../../datadog-shimmer')
|
|
@@ -18,6 +15,7 @@ const {
|
|
|
18
15
|
testFinishTimeCh,
|
|
19
16
|
testPassCh,
|
|
20
17
|
testErrorCh,
|
|
18
|
+
testDiWaitCh,
|
|
21
19
|
testSkipCh,
|
|
22
20
|
testFnCh,
|
|
23
21
|
testSuiteStartCh,
|
|
@@ -32,39 +30,36 @@ const {
|
|
|
32
30
|
getVitestTestProperties,
|
|
33
31
|
} = require('./vitest-util')
|
|
34
32
|
|
|
35
|
-
const BREAKPOINT_HIT_GRACE_PERIOD_MS = 400
|
|
36
|
-
|
|
37
33
|
const taskToCtx = new WeakMap()
|
|
38
34
|
const taskToTestProperties = new WeakMap()
|
|
39
35
|
const taskToStatuses = new WeakMap()
|
|
40
36
|
const taskToReportedErrorCount = new WeakMap()
|
|
41
37
|
const attemptToFixTaskToStatuses = new WeakMap()
|
|
38
|
+
const fileToHasConcurrentTests = new WeakMap()
|
|
42
39
|
const originalHookFns = new WeakMap()
|
|
43
40
|
const newTasks = new WeakSet()
|
|
44
41
|
const dynamicNameTasks = new WeakSet()
|
|
45
42
|
const disabledTasks = new WeakSet()
|
|
46
43
|
const quarantinedTasks = new WeakSet()
|
|
47
44
|
const attemptToFixTasks = new WeakSet()
|
|
45
|
+
const attemptToFixRetryTasks = new WeakSet()
|
|
48
46
|
const modifiedTasks = new WeakSet()
|
|
47
|
+
const efdRetryTasks = new WeakSet()
|
|
49
48
|
const efdDeterminedRetries = new WeakMap()
|
|
50
49
|
const efdSlowAbortedTasks = new WeakSet()
|
|
51
50
|
const efdExecutionStartByTask = new WeakMap()
|
|
52
51
|
const efdSkippedRetryResults = new WeakMap()
|
|
53
52
|
const attemptToFixExecutions = new Map()
|
|
54
53
|
const loggedAttemptToFixTests = new Set()
|
|
55
|
-
let isRetryReasonEfd = false
|
|
56
|
-
let isRetryReasonAttemptToFix = false
|
|
57
54
|
const switchedStatuses = new WeakSet()
|
|
58
55
|
let vitestGetFn = null
|
|
59
56
|
let vitestSetFn = null
|
|
60
57
|
let vitestGetHooks = null
|
|
61
58
|
|
|
62
59
|
function waitForHitProbe () {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}, BREAKPOINT_HIT_GRACE_PERIOD_MS)
|
|
67
|
-
})
|
|
60
|
+
const promises = {}
|
|
61
|
+
testDiWaitCh.publish({ promises })
|
|
62
|
+
return promises.hitBreakpointPromise
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
function getVitestTestStatus (test, retryCount) {
|
|
@@ -138,6 +133,70 @@ function wrapBeforeEachCleanupResult (task, result) {
|
|
|
138
133
|
return result
|
|
139
134
|
}
|
|
140
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Returns whether a Vitest task tree includes any concurrent task.
|
|
138
|
+
*
|
|
139
|
+
* @param {Array<{ type?: string, concurrent?: boolean, tasks?: object[] }>|undefined} tasks
|
|
140
|
+
* @returns {boolean}
|
|
141
|
+
*/
|
|
142
|
+
function hasConcurrentTask (tasks) {
|
|
143
|
+
if (!tasks) return false
|
|
144
|
+
|
|
145
|
+
for (const task of tasks) {
|
|
146
|
+
if (task.concurrent === true) return true
|
|
147
|
+
if (hasConcurrentTask(task.tasks)) return true
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Returns whether a Vitest file includes any concurrent test.
|
|
155
|
+
*
|
|
156
|
+
* @param {{ tasks?: object[] }} file
|
|
157
|
+
* @returns {boolean}
|
|
158
|
+
*/
|
|
159
|
+
function hasConcurrentTests (file) {
|
|
160
|
+
const cached = fileToHasConcurrentTests.get(file)
|
|
161
|
+
if (cached !== undefined) return cached
|
|
162
|
+
|
|
163
|
+
const hasConcurrent = hasConcurrentTask(file.tasks)
|
|
164
|
+
fileToHasConcurrentTests.set(file, hasConcurrent)
|
|
165
|
+
return hasConcurrent
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Gets the task associated with a Vitest hook invocation.
|
|
170
|
+
*
|
|
171
|
+
* @param {unknown[]} args
|
|
172
|
+
* @param {object} fallbackTask
|
|
173
|
+
* @returns {object}
|
|
174
|
+
*/
|
|
175
|
+
function getTaskFromHookArgs (args, fallbackTask) {
|
|
176
|
+
return args[0]?.task || fallbackTask
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Wraps a Vitest hook so it runs inside the span context for the current test.
|
|
181
|
+
*
|
|
182
|
+
* @param {'beforeEach'|'afterEach'} hookType
|
|
183
|
+
* @param {Function} fn
|
|
184
|
+
* @param {object} fallbackTask
|
|
185
|
+
* @returns {Function}
|
|
186
|
+
*/
|
|
187
|
+
function wrapSuiteHookFn (hookType, fn, fallbackTask) {
|
|
188
|
+
return shimmer.wrapFunction(fn, fn => function (...args) {
|
|
189
|
+
const task = getTaskFromHookArgs(args, fallbackTask)
|
|
190
|
+
const result = testFnCh.runStores(taskToCtx.get(task), () => fn.apply(this, args))
|
|
191
|
+
|
|
192
|
+
if (hookType === 'beforeEach') {
|
|
193
|
+
return wrapBeforeEachCleanupResult(task, result)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return result
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
141
200
|
function wrapVitestTestRunner (VitestTestRunner) {
|
|
142
201
|
// `onBeforeRunTask` is run before any repetition or attempt is run
|
|
143
202
|
// `onBeforeRunTask` is an async function
|
|
@@ -163,7 +222,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
163
222
|
isQuarantined,
|
|
164
223
|
} = testProperties
|
|
165
224
|
if (isAttemptToFix) {
|
|
166
|
-
|
|
225
|
+
if (task.repeats !== testManagementAttemptToFixRetries) {
|
|
226
|
+
attemptToFixRetryTasks.add(task)
|
|
227
|
+
}
|
|
167
228
|
disableFrameworkRetries(task)
|
|
168
229
|
task.repeats = testManagementAttemptToFixRetries
|
|
169
230
|
attemptToFixTasks.add(task)
|
|
@@ -183,7 +244,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
183
244
|
|
|
184
245
|
if (isImpactedTestsEnabled && testProperties.isModified) {
|
|
185
246
|
if (isEarlyFlakeDetectionEnabled) {
|
|
186
|
-
|
|
247
|
+
efdRetryTasks.add(task)
|
|
187
248
|
disableFrameworkRetries(task)
|
|
188
249
|
task.repeats = numRepeats
|
|
189
250
|
}
|
|
@@ -193,7 +254,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
193
254
|
|
|
194
255
|
if (isKnownTestsEnabled && testProperties.isNew && !attemptToFixTasks.has(task)) {
|
|
195
256
|
if (isEarlyFlakeDetectionEnabled && !modifiedTasks.has(task)) {
|
|
196
|
-
|
|
257
|
+
efdRetryTasks.add(task)
|
|
197
258
|
disableFrameworkRetries(task)
|
|
198
259
|
task.repeats = numRepeats
|
|
199
260
|
}
|
|
@@ -267,6 +328,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
267
328
|
}
|
|
268
329
|
|
|
269
330
|
const { retry: numAttempt, repeats: numRepetition } = retryInfo
|
|
331
|
+
const isFailedTestReplayAllowed = !hasConcurrentTests(task.file)
|
|
270
332
|
const isEfdManagedTask = isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)
|
|
271
333
|
|
|
272
334
|
if (isEfdManagedTask && numRepetition > 0 && !efdDeterminedRetries.has(task)) {
|
|
@@ -303,19 +365,20 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
303
365
|
|
|
304
366
|
// We finish the previous test here because we know it has failed already
|
|
305
367
|
if (numAttempt > 0) {
|
|
306
|
-
const shouldWaitForHitProbe = isDiEnabled && numAttempt > 1
|
|
368
|
+
const shouldWaitForHitProbe = isDiEnabled && isFailedTestReplayAllowed && numAttempt > 1
|
|
307
369
|
if (shouldWaitForHitProbe) {
|
|
308
370
|
await waitForHitProbe()
|
|
309
371
|
}
|
|
310
372
|
|
|
311
373
|
const promises = {}
|
|
312
|
-
const shouldSetProbe = isDiEnabled && numAttempt === 1
|
|
374
|
+
const shouldSetProbe = isDiEnabled && isFailedTestReplayAllowed && numAttempt === 1
|
|
313
375
|
const ctx = taskToCtx.get(task)
|
|
314
376
|
const testError = getCurrentAttemptTestError(task, task.result?.errors)
|
|
315
377
|
if (ctx) {
|
|
316
378
|
testErrorCh.publish({
|
|
317
379
|
error: testError,
|
|
318
380
|
shouldSetProbe,
|
|
381
|
+
shouldWaitForHitProbe,
|
|
319
382
|
promises,
|
|
320
383
|
...ctx.currentStore,
|
|
321
384
|
})
|
|
@@ -377,18 +440,18 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
377
440
|
|
|
378
441
|
const isRetryReasonAtr = numAttempt > 0 &&
|
|
379
442
|
isFlakyTestRetriesEnabledForTask(providedContext, task) &&
|
|
380
|
-
!
|
|
381
|
-
!
|
|
443
|
+
!attemptToFixRetryTasks.has(task) &&
|
|
444
|
+
!efdRetryTasks.has(task)
|
|
382
445
|
|
|
383
446
|
const ctx = {
|
|
384
447
|
testName,
|
|
385
448
|
testSuiteAbsolutePath: task.file.filepath,
|
|
386
449
|
isRetry: numAttempt > 0 || numRepetition > 0,
|
|
387
|
-
isRetryReasonEfd,
|
|
388
|
-
isRetryReasonAttemptToFix:
|
|
450
|
+
isRetryReasonEfd: efdRetryTasks.has(task),
|
|
451
|
+
isRetryReasonAttemptToFix: attemptToFixRetryTasks.has(task) && numRepetition > 0,
|
|
389
452
|
isNew,
|
|
390
453
|
hasDynamicName: dynamicNameTasks.has(task),
|
|
391
|
-
mightHitProbe: isDiEnabled && numAttempt > 0,
|
|
454
|
+
mightHitProbe: isDiEnabled && isFailedTestReplayAllowed && numAttempt > 0,
|
|
392
455
|
isAttemptToFix: attemptToFixTasks.has(task),
|
|
393
456
|
isDisabled: disabledTasks.has(task),
|
|
394
457
|
isQuarantined: quarantinedTasks.has(task),
|
|
@@ -429,17 +492,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
429
492
|
if (!hookArray) continue
|
|
430
493
|
for (let i = 0; i < hookArray.length; i++) {
|
|
431
494
|
const currentFn = hookArray[i]
|
|
432
|
-
|
|
433
|
-
const wrappedFn =
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (hookType === 'beforeEach') {
|
|
437
|
-
return wrapBeforeEachCleanupResult(task, result)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return result
|
|
441
|
-
})
|
|
442
|
-
originalHookFns.set(wrappedFn, originalFn)
|
|
495
|
+
if (originalHookFns.has(currentFn)) continue
|
|
496
|
+
const wrappedFn = wrapSuiteHookFn(hookType, currentFn, task)
|
|
497
|
+
originalHookFns.set(wrappedFn, currentFn)
|
|
443
498
|
hookArray[i] = wrappedFn
|
|
444
499
|
}
|
|
445
500
|
}
|
|
@@ -468,6 +523,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
468
523
|
const ctx = taskToCtx.get(task)
|
|
469
524
|
|
|
470
525
|
const { isDiEnabled } = getProvidedContext()
|
|
526
|
+
const isFailedTestReplayAllowed = !hasConcurrentTests(task.file)
|
|
471
527
|
|
|
472
528
|
if (efdSkippedRetryResults.has(task)) {
|
|
473
529
|
task.result = efdSkippedRetryResults.get(task)
|
|
@@ -475,7 +531,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
475
531
|
return result
|
|
476
532
|
}
|
|
477
533
|
|
|
478
|
-
if (isDiEnabled && retryInfo.retry > 1) {
|
|
534
|
+
if (isDiEnabled && isFailedTestReplayAllowed && retryInfo.retry > 1) {
|
|
479
535
|
await waitForHitProbe()
|
|
480
536
|
}
|
|
481
537
|
|
|
@@ -601,6 +657,7 @@ addHook({
|
|
|
601
657
|
testCommand: providedContext.testCommand,
|
|
602
658
|
repositoryRoot: providedContext.repositoryRoot,
|
|
603
659
|
codeOwnersEntries: providedContext.codeOwnersEntries,
|
|
660
|
+
testEnvironmentMetadata: providedContext.testEnvironmentMetadata,
|
|
604
661
|
}
|
|
605
662
|
testSuiteStartCh.runStores(testSuiteCtx, () => {})
|
|
606
663
|
const startTestsResponse = await startTests.apply(this, arguments)
|
|
@@ -611,6 +668,7 @@ addHook({
|
|
|
611
668
|
})
|
|
612
669
|
|
|
613
670
|
const testTasks = getTypeTasks(startTestsResponse[0].tasks)
|
|
671
|
+
const testEventPromises = []
|
|
614
672
|
|
|
615
673
|
// Only one test task per test, even if there are retries
|
|
616
674
|
for (const task of testTasks) {
|
|
@@ -639,12 +697,17 @@ addHook({
|
|
|
639
697
|
if (testCtx) {
|
|
640
698
|
const isSkippedByTestManagement =
|
|
641
699
|
!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
|
|
700
|
+
const promises = {}
|
|
642
701
|
testPassCh.publish({
|
|
643
702
|
task,
|
|
644
703
|
finalStatus: isSkippedByTestManagement ? 'skip' : 'pass',
|
|
645
704
|
earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
|
|
705
|
+
promises,
|
|
646
706
|
...testCtx.currentStore,
|
|
647
707
|
})
|
|
708
|
+
if (promises.hitBreakpointPromise) {
|
|
709
|
+
testEventPromises.push(promises.hitBreakpointPromise)
|
|
710
|
+
}
|
|
648
711
|
}
|
|
649
712
|
} else if (state === 'fail' || isSwitchedStatus) {
|
|
650
713
|
let hasFailedAllRetries = false
|
|
@@ -684,6 +747,7 @@ addHook({
|
|
|
684
747
|
|
|
685
748
|
if (testCtx) {
|
|
686
749
|
const isRetry = task.result?.retryCount > 0
|
|
750
|
+
const promises = {}
|
|
687
751
|
// `duration` is the duration of all the retries, so it can't be used if there are retries
|
|
688
752
|
|
|
689
753
|
let finalStatus
|
|
@@ -708,8 +772,12 @@ addHook({
|
|
|
708
772
|
attemptToFixFailed,
|
|
709
773
|
finalStatus,
|
|
710
774
|
earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
|
|
775
|
+
promises,
|
|
711
776
|
...testCtx.currentStore,
|
|
712
777
|
})
|
|
778
|
+
if (promises.hitBreakpointPromise) {
|
|
779
|
+
testEventPromises.push(promises.hitBreakpointPromise)
|
|
780
|
+
}
|
|
713
781
|
}
|
|
714
782
|
if (errors?.length) {
|
|
715
783
|
testSuiteError = testError // we store the error to bubble it up to the suite
|
|
@@ -725,6 +793,8 @@ addHook({
|
|
|
725
793
|
}
|
|
726
794
|
}
|
|
727
795
|
|
|
796
|
+
await Promise.all(testEventPromises)
|
|
797
|
+
|
|
728
798
|
const testSuiteResult = startTestsResponse[0].result
|
|
729
799
|
|
|
730
800
|
if (testSuiteResult.errors?.length) { // Errors from root level hooks
|
|
@@ -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
|
|
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
|
|
7
|
-
static id = '
|
|
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
|
|
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
|
|
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
|
}
|
|
@@ -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', `${
|
|
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 }
|