dd-trace 5.117.0 → 5.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ci/diagnose.js +162 -21
- package/ci/init.js +0 -1
- package/ci/runbook.md +187 -180
- package/ci/test-optimization-validation/approval-artifacts.js +3 -1
- package/ci/test-optimization-validation/approval.js +52 -15
- package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
- package/ci/test-optimization-validation/ci-discovery.js +1 -1
- package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
- package/ci/test-optimization-validation/ci-remediation.js +112 -39
- package/ci/test-optimization-validation/cli.js +516 -681
- package/ci/test-optimization-validation/command-blocker.js +188 -33
- package/ci/test-optimization-validation/command-output-policy.js +3 -26
- package/ci/test-optimization-validation/command-runner.js +189 -245
- package/ci/test-optimization-validation/environment.js +90 -0
- package/ci/test-optimization-validation/executable.js +159 -388
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
- package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
- package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
- package/ci/test-optimization-validation/generated-files.js +75 -13
- package/ci/test-optimization-validation/generated-test-contract.js +283 -0
- package/ci/test-optimization-validation/generated-verifier.js +49 -16
- package/ci/test-optimization-validation/literal-environment.js +57 -0
- package/ci/test-optimization-validation/manifest-loader.js +2 -22
- package/ci/test-optimization-validation/manifest-scaffold.js +816 -515
- package/ci/test-optimization-validation/manifest-schema.js +494 -702
- package/ci/test-optimization-validation/offline-fixtures.js +4 -1
- package/ci/test-optimization-validation/plan-writer.js +243 -984
- package/ci/test-optimization-validation/preflight-runner.js +49 -55
- package/ci/test-optimization-validation/redaction.js +2 -1
- package/ci/test-optimization-validation/report-writer.js +391 -1357
- package/ci/test-optimization-validation/result-semantics.js +86 -0
- package/ci/test-optimization-validation/runner-command.js +249 -0
- package/ci/test-optimization-validation/runner-contract.js +664 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
- package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
- package/ci/test-optimization-validation/scenarios/test-management.js +4 -3
- package/ci/test-optimization-validation/source-text.js +87 -0
- package/ci/test-optimization-validation/test-output.js +12 -22
- package/ext/tags.d.ts +1 -0
- package/index.d.ts +7 -0
- package/package.json +2 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
- package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
- package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
- package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
- package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
- package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +48 -49
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +6 -4
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/supported-configurations.json +7 -0
- package/packages/dd-trace/src/encode/span-stats.js +7 -1
- package/packages/dd-trace/src/exporter.js +16 -9
- package/packages/dd-trace/src/llmobs/index.js +9 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
- package/packages/dd-trace/src/llmobs/sdk.js +2 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +7 -2
- package/packages/dd-trace/src/llmobs/tagger.js +23 -3
- package/packages/dd-trace/src/llmobs/util.js +56 -3
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
- package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -3
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -1
- package/packages/dd-trace/src/span_processor.js +1 -1
- package/packages/dd-trace/src/span_stats.js +22 -4
- package/ci/test-optimization-validation/command-suitability.js +0 -471
- package/ci/test-optimization-validation/init-probe-preload.js +0 -163
- package/ci/test-optimization-validation/init-probe.js +0 -246
- package/ci/test-optimization-validation/late-initialization.js +0 -63
- package/ci/test-optimization-validation/local-command.js +0 -163
- package/ci/test-optimization-validation/setup-runner.js +0 -97
- package/ci/test-optimization-validation-manifest.schema.json +0 -1
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
failWithDebugRerun,
|
|
8
8
|
pass,
|
|
9
9
|
prepareGeneratedScenario,
|
|
10
|
+
reportMissingGeneratedTest,
|
|
10
11
|
requireGeneratedScenario,
|
|
11
12
|
runInstrumentedCommand,
|
|
12
13
|
skip,
|
|
@@ -28,14 +29,14 @@ async function runEarlyFlakeDetection ({ framework, out, options }) {
|
|
|
28
29
|
|
|
29
30
|
const discovery = await discoverScenarioTests({ framework, out, scenarioName, scenario, options })
|
|
30
31
|
if (discovery.tests.length === 0) {
|
|
31
|
-
return
|
|
32
|
+
return reportMissingGeneratedTest({
|
|
32
33
|
command: scenario.runCommand,
|
|
33
34
|
diagnosis: 'The generated new-test candidate was not reported during baseline identity discovery.',
|
|
34
|
-
|
|
35
|
+
discovery,
|
|
35
36
|
framework,
|
|
36
37
|
options,
|
|
37
38
|
out,
|
|
38
|
-
|
|
39
|
+
scenario,
|
|
39
40
|
scenarioName,
|
|
40
41
|
})
|
|
41
42
|
}
|
|
@@ -4,7 +4,7 @@ const fs = require('node:fs')
|
|
|
4
4
|
const path = require('path')
|
|
5
5
|
|
|
6
6
|
const { getArtifactId } = require('../artifact-id')
|
|
7
|
-
const {
|
|
7
|
+
const { buildDatadogEnv, buildOfflineCaptureEnv, runCommand } = require('../command-runner')
|
|
8
8
|
const {
|
|
9
9
|
cleanupGeneratedRuntimeFiles,
|
|
10
10
|
findGeneratedScenario,
|
|
@@ -14,10 +14,10 @@ const {
|
|
|
14
14
|
eventsOfType,
|
|
15
15
|
findTestsByIdentity,
|
|
16
16
|
} = require('../payload-normalizer')
|
|
17
|
-
const { getLocalValidationCommand } = require('../local-command')
|
|
18
17
|
const { cleanupOfflineFixture, createOfflineFixture } = require('../offline-fixtures')
|
|
19
18
|
const { readOfflineOutput } = require('../offline-output')
|
|
20
19
|
const { sanitizeForReport, sanitizeString } = require('../redaction')
|
|
20
|
+
const { getGeneratedCommand } = require('../runner-command')
|
|
21
21
|
const { ensureSafeDirectory, writeFileSafely } = require('../safe-files')
|
|
22
22
|
|
|
23
23
|
const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}${String.raw`\[[0-?]*[ -/]*[@-~]`}`, 'g')
|
|
@@ -34,7 +34,7 @@ async function runInstrumentedCommand ({
|
|
|
34
34
|
options,
|
|
35
35
|
extraEnv,
|
|
36
36
|
fixtureConfig,
|
|
37
|
-
|
|
37
|
+
injectInitialization = true,
|
|
38
38
|
allowMissingInitialization = false,
|
|
39
39
|
}) {
|
|
40
40
|
const outDir = frameworkOutDir(out, framework, scenarioName)
|
|
@@ -52,9 +52,9 @@ async function runInstrumentedCommand ({
|
|
|
52
52
|
scenarioName,
|
|
53
53
|
...fixtureConfig,
|
|
54
54
|
})
|
|
55
|
-
const validationEnv =
|
|
56
|
-
?
|
|
57
|
-
:
|
|
55
|
+
const validationEnv = injectInitialization
|
|
56
|
+
? buildDatadogEnv({ fixture, outputRoot: rawOutputRoot, scenario: scenarioName, framework })
|
|
57
|
+
: buildOfflineCaptureEnv({ fixture, outputRoot: rawOutputRoot })
|
|
58
58
|
result = await runCommand(command, {
|
|
59
59
|
env: {
|
|
60
60
|
...validationEnv,
|
|
@@ -82,7 +82,7 @@ async function runInstrumentedCommand ({
|
|
|
82
82
|
`${JSON.stringify(sanitizeForReport(result), null, 2)}\n`,
|
|
83
83
|
'scenario result artifact'
|
|
84
84
|
)
|
|
85
|
-
if (!offline.initialized &&
|
|
85
|
+
if (!offline.initialized && injectInitialization && !allowMissingInitialization) {
|
|
86
86
|
const stderr = sanitizeString(result.stderr).trim().slice(-2000)
|
|
87
87
|
throw new Error(
|
|
88
88
|
'Offline Test Optimization exporter did not initialize or write completion evidence. ' +
|
|
@@ -180,11 +180,11 @@ async function prepareGeneratedScenario (framework, scenarioId) {
|
|
|
180
180
|
const scenario = findGeneratedScenario(framework, scenarioId)
|
|
181
181
|
if (!scenario) return { scenario: null, written: [] }
|
|
182
182
|
cleanupGeneratedRuntimeFiles(framework)
|
|
183
|
-
const written = await writeGeneratedFiles(framework)
|
|
183
|
+
const written = await writeGeneratedFiles(framework, scenario)
|
|
184
184
|
return {
|
|
185
185
|
scenario: {
|
|
186
186
|
...scenario,
|
|
187
|
-
runCommand:
|
|
187
|
+
runCommand: getGeneratedCommand(framework, scenario),
|
|
188
188
|
},
|
|
189
189
|
written,
|
|
190
190
|
}
|
|
@@ -314,6 +314,63 @@ async function discoverScenarioTests ({ framework, out, scenarioName, scenario,
|
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Reports a missing generated test without blaming dd-trace when clean execution was unproven.
|
|
319
|
+
*
|
|
320
|
+
* @param {object} input missing-test evidence
|
|
321
|
+
* @param {object} input.command generated scenario command
|
|
322
|
+
* @param {string} input.diagnosis diagnosis used when clean execution was proven
|
|
323
|
+
* @param {object} input.discovery instrumented baseline result
|
|
324
|
+
* @param {object} input.framework framework manifest entry
|
|
325
|
+
* @param {object} input.options execution options
|
|
326
|
+
* @param {string} input.out validation output root
|
|
327
|
+
* @param {object} input.scenario generated scenario
|
|
328
|
+
* @param {string} input.scenarioName advanced scenario name
|
|
329
|
+
* @returns {object|Promise<object>} incomplete or confirmed failure result
|
|
330
|
+
*/
|
|
331
|
+
function reportMissingGeneratedTest ({
|
|
332
|
+
command,
|
|
333
|
+
diagnosis,
|
|
334
|
+
discovery,
|
|
335
|
+
framework,
|
|
336
|
+
options,
|
|
337
|
+
out,
|
|
338
|
+
scenario,
|
|
339
|
+
scenarioName,
|
|
340
|
+
}) {
|
|
341
|
+
const verification = framework.generatedTestStrategy?.verification?.observedScenarios
|
|
342
|
+
?.find(observed => observed.id === scenario.id)
|
|
343
|
+
const evidence = {
|
|
344
|
+
...discoveryEvidence(discovery),
|
|
345
|
+
generatedVerificationObservedTestCount: verification?.observedTestCount,
|
|
346
|
+
}
|
|
347
|
+
if (verification?.observedTestCount === null) {
|
|
348
|
+
return inconclusive(
|
|
349
|
+
framework,
|
|
350
|
+
scenarioName,
|
|
351
|
+
'The clean temporary validation command exited as expected without a parseable test count, and the ' +
|
|
352
|
+
'instrumented baseline emitted no matching test event. The validator cannot prove that the generated test ' +
|
|
353
|
+
'executed, so no advanced-feature conclusion was reached.',
|
|
354
|
+
{
|
|
355
|
+
...evidence,
|
|
356
|
+
reasonCode: 'generated-test-execution-unproven',
|
|
357
|
+
},
|
|
358
|
+
discovery.outDir
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return failWithDebugRerun({
|
|
363
|
+
command,
|
|
364
|
+
diagnosis,
|
|
365
|
+
evidence,
|
|
366
|
+
framework,
|
|
367
|
+
options,
|
|
368
|
+
out,
|
|
369
|
+
outDir: discovery.outDir,
|
|
370
|
+
scenarioName,
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
|
|
317
374
|
function testsForDiscoveredScenario (events, scenario, discovery) {
|
|
318
375
|
if (discovery?.testIdentities?.length > 0) {
|
|
319
376
|
return findTestsByIdentity(events, discovery.testIdentities)
|
|
@@ -528,6 +585,7 @@ module.exports = {
|
|
|
528
585
|
inconclusive,
|
|
529
586
|
pass,
|
|
530
587
|
prepareGeneratedScenario,
|
|
588
|
+
reportMissingGeneratedTest,
|
|
531
589
|
requireGeneratedScenario,
|
|
532
590
|
runDebugInstrumentedCommand,
|
|
533
591
|
runInstrumentedCommand,
|
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
failWithDebugRerun,
|
|
10
10
|
pass,
|
|
11
11
|
prepareGeneratedScenario,
|
|
12
|
+
reportMissingGeneratedTest,
|
|
12
13
|
requireGeneratedScenario,
|
|
13
14
|
runInstrumentedCommand,
|
|
14
15
|
testEventSamples,
|
|
@@ -25,14 +26,14 @@ async function runTestManagement ({ framework, out, options }) {
|
|
|
25
26
|
const { scenario } = await prepareGeneratedScenario(framework, 'test-management-target')
|
|
26
27
|
const discovery = await discoverScenarioTests({ framework, out, scenarioName, scenario, options })
|
|
27
28
|
if (discovery.tests.length === 0) {
|
|
28
|
-
return
|
|
29
|
+
return reportMissingGeneratedTest({
|
|
29
30
|
command: scenario.runCommand,
|
|
30
31
|
diagnosis: 'The test-management target was not reported during baseline identity discovery.',
|
|
31
|
-
|
|
32
|
+
discovery,
|
|
32
33
|
framework,
|
|
33
34
|
options,
|
|
34
35
|
out,
|
|
35
|
-
|
|
36
|
+
scenario,
|
|
36
37
|
scenarioName,
|
|
37
38
|
})
|
|
38
39
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Replaces JavaScript comments with spaces while preserving line structure and string literals.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} source JavaScript or TypeScript source
|
|
7
|
+
* @returns {string} source with comments masked
|
|
8
|
+
*/
|
|
9
|
+
function maskJavaScriptComments (source) {
|
|
10
|
+
return maskJavaScriptSource(source, false)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replaces JavaScript comments and string literals with spaces while preserving line structure.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} source JavaScript or TypeScript source
|
|
17
|
+
* @returns {string} source with non-code text masked
|
|
18
|
+
*/
|
|
19
|
+
function maskJavaScriptNonCode (source) {
|
|
20
|
+
return maskJavaScriptSource(source, true)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Masks comments and optionally string literals in JavaScript-like source.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} source JavaScript or TypeScript source
|
|
27
|
+
* @param {boolean} maskStrings whether string and template literal contents should be masked
|
|
28
|
+
* @returns {string} masked source
|
|
29
|
+
*/
|
|
30
|
+
function maskJavaScriptSource (source, maskStrings) {
|
|
31
|
+
const characters = [...source]
|
|
32
|
+
let blockComment = false
|
|
33
|
+
let lineComment = false
|
|
34
|
+
let quote = ''
|
|
35
|
+
|
|
36
|
+
for (let index = 0; index < characters.length; index++) {
|
|
37
|
+
const character = characters[index]
|
|
38
|
+
const next = characters[index + 1]
|
|
39
|
+
if (lineComment) {
|
|
40
|
+
if (character === '\n') {
|
|
41
|
+
lineComment = false
|
|
42
|
+
} else {
|
|
43
|
+
characters[index] = ' '
|
|
44
|
+
}
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
if (blockComment) {
|
|
48
|
+
if (character === '*' && next === '/') {
|
|
49
|
+
characters[index] = ' '
|
|
50
|
+
characters[++index] = ' '
|
|
51
|
+
blockComment = false
|
|
52
|
+
} else if (character !== '\r' && character !== '\n') {
|
|
53
|
+
characters[index] = ' '
|
|
54
|
+
}
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
if (quote) {
|
|
58
|
+
if (maskStrings && character !== '\r' && character !== '\n') characters[index] = ' '
|
|
59
|
+
if (character.charCodeAt(0) === 92) {
|
|
60
|
+
if (maskStrings && index + 1 < characters.length &&
|
|
61
|
+
characters[index + 1] !== '\r' && characters[index + 1] !== '\n') {
|
|
62
|
+
characters[index + 1] = ' '
|
|
63
|
+
}
|
|
64
|
+
index++
|
|
65
|
+
} else if (character === quote) {
|
|
66
|
+
quote = ''
|
|
67
|
+
}
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
if (character === '"' || character === '\'' || character === '`') {
|
|
71
|
+
quote = character
|
|
72
|
+
if (maskStrings) characters[index] = ' '
|
|
73
|
+
} else if (character === '/' && next === '/') {
|
|
74
|
+
characters[index] = ' '
|
|
75
|
+
characters[++index] = ' '
|
|
76
|
+
lineComment = true
|
|
77
|
+
} else if (character === '/' && next === '*') {
|
|
78
|
+
characters[index] = ' '
|
|
79
|
+
characters[++index] = ' '
|
|
80
|
+
blockComment = true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return characters.join('')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { maskJavaScriptComments, maskJavaScriptNonCode }
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const cypressAdapter = require('./framework-adapters/cypress')
|
|
4
|
+
const cucumberAdapter = require('./framework-adapters/cucumber')
|
|
5
|
+
const playwrightAdapter = require('./framework-adapters/playwright')
|
|
6
|
+
|
|
3
7
|
const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}${String.raw`\[[0-?]*[ -/]*[@-~]`}`, 'g')
|
|
4
8
|
|
|
5
9
|
/**
|
|
@@ -11,16 +15,14 @@ const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}${String.raw`\[[0-?]*
|
|
|
11
15
|
* @returns {number|null} observed count when a supported summary was found
|
|
12
16
|
*/
|
|
13
17
|
function getObservedTestCount (framework, stdout = '', stderr = '') {
|
|
14
|
-
const output = `${stdout}\n${stderr}
|
|
18
|
+
const output = stripAnsi(`${stdout}\n${stderr}`)
|
|
19
|
+
if (framework === 'cucumber') return cucumberAdapter.getObservedTestCount(output)
|
|
20
|
+
if (framework === 'cypress') return cypressAdapter.getObservedTestCount(output)
|
|
21
|
+
if (framework === 'playwright') return playwrightAdapter.getObservedTestCount(output)
|
|
15
22
|
if (framework === 'jest') return getJestObservedTestCount(output)
|
|
16
23
|
if (framework === 'vitest') return getVitestObservedTestCount(output)
|
|
17
|
-
if (framework === 'playwright') return getPlaywrightObservedTestCount(output)
|
|
18
24
|
|
|
19
|
-
const totalPatterns = framework === 'node:test'
|
|
20
|
-
? [/^# tests\s+(\d+)\s*$/gim]
|
|
21
|
-
: framework === 'cucumber'
|
|
22
|
-
? [/\b(\d+)\s+scenarios?\b/gi]
|
|
23
|
-
: []
|
|
25
|
+
const totalPatterns = framework === 'node:test' ? [/^# tests\s+(\d+)\s*$/gim] : []
|
|
24
26
|
|
|
25
27
|
for (const pattern of totalPatterns) {
|
|
26
28
|
const count = getLastMatchCount(output, pattern)
|
|
@@ -38,20 +40,8 @@ function getObservedTestCount (framework, stdout = '', stderr = '') {
|
|
|
38
40
|
return getLastMatchCount(output, /\b(\d+)\s+tests?\s+(?:passed|failed)\b/gi)
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
* @param {string} output test output without ANSI codes
|
|
45
|
-
* @returns {number|null} executed test count
|
|
46
|
-
*/
|
|
47
|
-
function getPlaywrightObservedTestCount (output) {
|
|
48
|
-
const observed = sumLastMatchCounts(output, [
|
|
49
|
-
/^\s*(\d+)\s+passed\b/gim,
|
|
50
|
-
/^\s*(\d+)\s+failed\b/gim,
|
|
51
|
-
/^\s*(\d+)\s+flaky\b/gim,
|
|
52
|
-
])
|
|
53
|
-
if (observed !== null) return observed
|
|
54
|
-
return /^\s*\d+\s+skipped\b/im.test(output) ? 0 : null
|
|
43
|
+
function stripAnsi (value) {
|
|
44
|
+
return String(value || '').replaceAll(ANSI_PATTERN, '')
|
|
55
45
|
}
|
|
56
46
|
|
|
57
47
|
/**
|
|
@@ -126,4 +116,4 @@ function sumLastMatchCounts (output, patterns) {
|
|
|
126
116
|
return found ? count : null
|
|
127
117
|
}
|
|
128
118
|
|
|
129
|
-
module.exports = { getObservedTestCount }
|
|
119
|
+
module.exports = { getObservedTestCount, stripAnsi }
|
package/ext/tags.d.ts
CHANGED
package/index.d.ts
CHANGED
|
@@ -293,6 +293,7 @@ interface Plugins {
|
|
|
293
293
|
"next": tracer.plugins.next;
|
|
294
294
|
"nyc": tracer.plugins.nyc;
|
|
295
295
|
"openai": tracer.plugins.openai;
|
|
296
|
+
"openai-agents": tracer.plugins.openai_agents;
|
|
296
297
|
"opensearch": tracer.plugins.opensearch;
|
|
297
298
|
"oracledb": tracer.plugins.oracledb;
|
|
298
299
|
"playwright": tracer.plugins.playwright;
|
|
@@ -3138,6 +3139,12 @@ declare namespace tracer {
|
|
|
3138
3139
|
*/
|
|
3139
3140
|
interface openai extends Instrumentation {}
|
|
3140
3141
|
|
|
3142
|
+
/**
|
|
3143
|
+
* This plugin automatically instruments the
|
|
3144
|
+
* [@openai/agents](https://www.npmjs.com/package/@openai/agents) library.
|
|
3145
|
+
*/
|
|
3146
|
+
interface openai_agents extends Instrumentation {}
|
|
3147
|
+
|
|
3141
3148
|
/**
|
|
3142
3149
|
* This plugin automatically instruments the
|
|
3143
3150
|
* [opensearch](https://github.com/opensearch-project/opensearch-js) module.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.118.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
"@types/mocha": "^10.0.10",
|
|
199
199
|
"@types/node": "^18.19.106",
|
|
200
200
|
"@types/sinon": "^22.0.0",
|
|
201
|
-
"@vercel/nft": "^
|
|
201
|
+
"@vercel/nft": "^1.10.2",
|
|
202
202
|
"axios": "^1.18.1",
|
|
203
203
|
"benchmark": "^2.1.4",
|
|
204
204
|
"body-parser": "^2.3.0",
|
|
@@ -21,6 +21,8 @@ module.exports = {
|
|
|
21
21
|
'@apollo/gateway': () => require('../apollo'),
|
|
22
22
|
'@langchain/langgraph': { esmFirst: true, fn: () => require('../langgraph') },
|
|
23
23
|
'@modelcontextprotocol/sdk': { esmFirst: true, fn: () => require('../modelcontextprotocol-sdk') },
|
|
24
|
+
'@openai/agents': () => require('../openai-agents'),
|
|
25
|
+
'@openai/agents-openai': () => require('../openai-agents'),
|
|
24
26
|
'apollo-server-core': () => require('../apollo-server-core'),
|
|
25
27
|
'@aws-sdk/smithy-client': () => require('../aws-sdk'),
|
|
26
28
|
'@aws/durable-execution-sdk-js': () => require('../aws-durable-execution-sdk-js'),
|
package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const moduleName = '@openai/agents-openai'
|
|
4
|
+
const versionRange = '>=0.7.0'
|
|
5
|
+
const functionQuery = {
|
|
6
|
+
methodName: 'constructor',
|
|
7
|
+
className: 'OpenAIChatCompletionsModel',
|
|
8
|
+
kind: 'Sync',
|
|
9
|
+
}
|
|
10
|
+
const channelName = 'OpenAIChatCompletionsModel_constructor'
|
|
11
|
+
|
|
12
|
+
module.exports = [
|
|
13
|
+
{
|
|
14
|
+
module: {
|
|
15
|
+
name: moduleName,
|
|
16
|
+
versionRange,
|
|
17
|
+
filePath: 'dist/openaiChatCompletionsModel.js',
|
|
18
|
+
},
|
|
19
|
+
functionQuery,
|
|
20
|
+
channelName,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
module: {
|
|
24
|
+
name: moduleName,
|
|
25
|
+
versionRange,
|
|
26
|
+
filePath: 'dist/openaiChatCompletionsModel.mjs',
|
|
27
|
+
},
|
|
28
|
+
functionQuery,
|
|
29
|
+
channelName,
|
|
30
|
+
},
|
|
31
|
+
]
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { channel, tracingChannel } = require('dc-polyfill')
|
|
4
|
+
const shimmer = require('../../datadog-shimmer')
|
|
5
|
+
const { addHook, getHooks } = require('./helpers/instrument')
|
|
6
|
+
|
|
7
|
+
// `WeakSet` keyed by module exports — replaces the underscored
|
|
8
|
+
// `mod._datadogPatched` flag while keeping dedupe semantics. Mods are kept
|
|
9
|
+
// alive by `require.cache` anyway, so this doesn't add lifetime to anything.
|
|
10
|
+
const patchedMods = new WeakSet()
|
|
11
|
+
const modelBaseURLs = new WeakMap()
|
|
12
|
+
const chatCompletionsModelConstructorCh =
|
|
13
|
+
tracingChannel('orchestrion:@openai/agents-openai:OpenAIChatCompletionsModel_constructor')
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Capture the client base URL before the SDK stores the client in a #private field.
|
|
17
|
+
*
|
|
18
|
+
* @param {{ arguments?: [{ baseURL?: string }], self?: object }} ctx
|
|
19
|
+
*/
|
|
20
|
+
function captureChatCompletionsModelBaseURL (ctx) {
|
|
21
|
+
const baseURL = ctx.arguments?.[0]?.baseURL
|
|
22
|
+
if (ctx.self && typeof baseURL === 'string') modelBaseURLs.set(ctx.self, baseURL)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
chatCompletionsModelConstructorCh.end.subscribe(captureChatCompletionsModelBaseURL)
|
|
26
|
+
|
|
27
|
+
for (const hook of getHooks('@openai/agents-openai')) {
|
|
28
|
+
addHook(hook, moduleExports => moduleExports)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Plugin subscribes to this and registers its TracingProcessor when
|
|
32
|
+
// `@openai/agents` loads. Publishing from here keeps this file free of
|
|
33
|
+
// any cross-package import from the plugin.
|
|
34
|
+
const agentsCoreLoadedCh = channel('apm:openai-agents:agents-core:loaded')
|
|
35
|
+
|
|
36
|
+
// Plugin uses addBind on this channel so that legacyStorage.run(store, fn) wraps
|
|
37
|
+
// the model call — including async iterator advancement for streaming responses.
|
|
38
|
+
// This ensures the active dd-trace span is visible to the openai plugin when it
|
|
39
|
+
// creates its openai.request span, correctly parenting it under the agent span.
|
|
40
|
+
const modelStartCh = channel('apm:openai-agents:model:start')
|
|
41
|
+
|
|
42
|
+
// Tool.invoke runs inside agents-core's public function-span context. Bind the
|
|
43
|
+
// corresponding dd-trace tool span around that public invocation boundary so
|
|
44
|
+
// spans created by user tool code inherit it.
|
|
45
|
+
const toolStartCh = channel('apm:openai-agents:tool:start')
|
|
46
|
+
|
|
47
|
+
// Reference to the loaded @openai/agents module, captured in the first hook
|
|
48
|
+
// so that wrapResponseMethod can call getCurrentSpan() without an additional
|
|
49
|
+
// require (and without triggering n/no-missing-require on agents-core internals).
|
|
50
|
+
let agentsMod
|
|
51
|
+
|
|
52
|
+
// @openai/agents >=0.8.0 moved addTraceProcessor / getCurrentSpan out of the
|
|
53
|
+
// top-level re-exports. The new public surface uses getGlobalTraceProvider():
|
|
54
|
+
// provider.registerProcessor(processor) (replaces addTraceProcessor)
|
|
55
|
+
// provider.getCurrentSpan() (replaces getCurrentSpan)
|
|
56
|
+
// Both APIs are tried so this file works across the full supported version range.
|
|
57
|
+
// The plugin subscriber (index.js) handles processor registration via the channel.
|
|
58
|
+
function getCurrentSpan () {
|
|
59
|
+
if (typeof agentsMod?.getCurrentSpan === 'function') {
|
|
60
|
+
return agentsMod.getCurrentSpan()
|
|
61
|
+
}
|
|
62
|
+
if (typeof agentsMod?.getGlobalTraceProvider === 'function') {
|
|
63
|
+
return agentsMod.getGlobalTraceProvider().getCurrentSpan()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getCurrentSpanId () {
|
|
68
|
+
return getCurrentSpan()?.spanId
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
addHook({ name: '@openai/agents', versions: ['>=0.7.0'] }, (mod) => {
|
|
72
|
+
if (patchedMods.has(mod)) return mod
|
|
73
|
+
if (typeof mod?.addTraceProcessor !== 'function' && typeof mod?.getGlobalTraceProvider !== 'function') return mod
|
|
74
|
+
patchedMods.add(mod)
|
|
75
|
+
agentsMod = mod
|
|
76
|
+
if (typeof mod.tool === 'function') {
|
|
77
|
+
shimmer.wrap(mod, 'tool', wrapToolFactory, { replaceGetter: true })
|
|
78
|
+
}
|
|
79
|
+
agentsCoreLoadedCh.publish({ mod })
|
|
80
|
+
return mod
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
function wrapToolFactory (original) {
|
|
84
|
+
return function (...args) {
|
|
85
|
+
const tool = original.apply(this, args)
|
|
86
|
+
if (typeof tool?.invoke === 'function') {
|
|
87
|
+
shimmer.wrap(tool, 'invoke', wrapToolInvoke)
|
|
88
|
+
}
|
|
89
|
+
return tool
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function wrapToolInvoke (original) {
|
|
94
|
+
return function (...args) {
|
|
95
|
+
const agentsCoreSpan = getCurrentSpan()
|
|
96
|
+
return toolStartCh.runStores({ agentsCoreSpan }, () => original.apply(this, args))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function wrapResponseMethod (original) {
|
|
101
|
+
return function (...args) {
|
|
102
|
+
const agentsCoreSpanId = getCurrentSpanId()
|
|
103
|
+
const baseURL = getClientBaseURL(this)
|
|
104
|
+
return modelStartCh.runStores({ agentsCoreSpanId, baseURL }, () => original.apply(this, args))
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function wrapStreamedResponseMethod (original) {
|
|
109
|
+
return function (...args) {
|
|
110
|
+
const agentsCoreSpanId = getCurrentSpanId()
|
|
111
|
+
const baseURL = getClientBaseURL(this)
|
|
112
|
+
const context = { agentsCoreSpanId, baseURL }
|
|
113
|
+
const iterator = modelStartCh.runStores(context, () => original.apply(this, args))
|
|
114
|
+
return wrapAsyncIterator(iterator, context)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getClientBaseURL (model) {
|
|
119
|
+
return model?.client?.baseURL ?? model?._client?.baseURL ?? modelBaseURLs.get(model)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function wrapAsyncIterator (iterator, context) {
|
|
123
|
+
if (!iterator || typeof iterator !== 'object') return iterator
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
next () {
|
|
127
|
+
return modelStartCh.runStores(context, () => iterator.next.apply(iterator, arguments))
|
|
128
|
+
},
|
|
129
|
+
throw () {
|
|
130
|
+
if (typeof iterator.throw !== 'function') return Promise.reject(arguments[0])
|
|
131
|
+
return modelStartCh.runStores(context, () => iterator.throw.apply(iterator, arguments))
|
|
132
|
+
},
|
|
133
|
+
return () {
|
|
134
|
+
if (typeof iterator.return !== 'function') return Promise.resolve({ done: true, value: arguments[0] })
|
|
135
|
+
return modelStartCh.runStores(context, () => iterator.return.apply(iterator, arguments))
|
|
136
|
+
},
|
|
137
|
+
[Symbol.asyncIterator] () {
|
|
138
|
+
return this
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
addHook({ name: '@openai/agents-openai', versions: ['>=0.7.0'] }, (mod) => {
|
|
144
|
+
if (patchedMods.has(mod)) return mod
|
|
145
|
+
const responseProto = mod?.OpenAIResponsesModel?.prototype
|
|
146
|
+
const chatCompletionsProto = mod?.OpenAIChatCompletionsModel?.prototype
|
|
147
|
+
if (!responseProto && !chatCompletionsProto) return mod
|
|
148
|
+
|
|
149
|
+
patchedMods.add(mod)
|
|
150
|
+
for (const proto of [responseProto, chatCompletionsProto]) {
|
|
151
|
+
if (typeof proto?.getResponse === 'function') {
|
|
152
|
+
shimmer.wrap(proto, 'getResponse', wrapResponseMethod)
|
|
153
|
+
}
|
|
154
|
+
if (typeof proto?.getStreamedResponse === 'function') {
|
|
155
|
+
shimmer.wrap(proto, 'getStreamedResponse', wrapStreamedResponseMethod)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return mod
|
|
159
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../datadog-core')
|
|
4
|
+
const Plugin = require('../../dd-trace/src/plugins/plugin')
|
|
5
|
+
const { MODEL_BASE_URL_STORE_KEY, OpenAIAgentsIntegration } = require('./integration')
|
|
6
|
+
const { DDOpenAIAgentsProcessor } = require('./processor')
|
|
7
|
+
|
|
8
|
+
const legacyStorage = storage('legacy')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Drives the openai-agents integration through agents-core's
|
|
12
|
+
* `TracingProcessor` interface. The instrumentation hook publishes the
|
|
13
|
+
* loaded `@openai/agents` module on a channel; this plugin subscribes
|
|
14
|
+
* during its constructor (which runs synchronously between `loadChannel`'s
|
|
15
|
+
* publish and the addHook callback) and registers the processor.
|
|
16
|
+
*
|
|
17
|
+
* The integration's `enabled` flag follows this plugin's configure()
|
|
18
|
+
* lifecycle. Each loaded version of the agents package replaces all processors
|
|
19
|
+
* via setTraceProcessors() on module load, so the plugin re-registers a
|
|
20
|
+
* fresh DDOpenAIAgentsProcessor for each module version that fires the channel.
|
|
21
|
+
*/
|
|
22
|
+
class OpenaiAgentsPlugin extends Plugin {
|
|
23
|
+
static id = 'openai-agents'
|
|
24
|
+
|
|
25
|
+
#integration
|
|
26
|
+
|
|
27
|
+
constructor (tracer, tracerConfig) {
|
|
28
|
+
super(tracer, tracerConfig)
|
|
29
|
+
this.#integration = new OpenAIAgentsIntegration({
|
|
30
|
+
tracer: this.tracer,
|
|
31
|
+
config: tracerConfig,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Register a new processor each time @openai/agents fires the channel.
|
|
35
|
+
// Each module version calls setTraceProcessors() on load (which replaces
|
|
36
|
+
// all processors), so we must re-register after every new version loads.
|
|
37
|
+
// The instrumentation's patchedMods WeakSet ensures each module instance
|
|
38
|
+
// fires the channel exactly once, so no duplicates accumulate.
|
|
39
|
+
this.addSub('apm:openai-agents:agents-core:loaded', ({ mod }) => {
|
|
40
|
+
const processor = new DDOpenAIAgentsProcessor(() => this.#integration)
|
|
41
|
+
if (typeof mod?.addTraceProcessor === 'function') {
|
|
42
|
+
mod.addTraceProcessor(processor)
|
|
43
|
+
} else {
|
|
44
|
+
mod.getGlobalTraceProvider().registerProcessor(processor)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Activate the current agent's dd-trace span in legacyStorage for the
|
|
49
|
+
// duration of model response calls and stream iterator advancement. This
|
|
50
|
+
// makes the openai plugin's shimmer see the correct parent when it creates its
|
|
51
|
+
// openai.request span, so all spans land in the same trace.
|
|
52
|
+
this.addBind('apm:openai-agents:model:start', ({ agentsCoreSpanId, baseURL }) => {
|
|
53
|
+
const store = legacyStorage.getStore()
|
|
54
|
+
if (!this.#integration.enabled || !agentsCoreSpanId) return store
|
|
55
|
+
const ddSpan = this.#integration.getDDSpan(agentsCoreSpanId)
|
|
56
|
+
if (!ddSpan) return store
|
|
57
|
+
return { ...store, [MODEL_BASE_URL_STORE_KEY]: baseURL, span: ddSpan }
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
this.addBind('apm:openai-agents:tool:start', ({ agentsCoreSpan }) => {
|
|
61
|
+
const store = legacyStorage.getStore()
|
|
62
|
+
if (!this.#integration.enabled || !agentsCoreSpan) return store
|
|
63
|
+
const ddSpan = this.#integration.getOrStartToolSpan(agentsCoreSpan)
|
|
64
|
+
return ddSpan ? { ...store, span: ddSpan } : store
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
configure (config) {
|
|
69
|
+
super.configure(config)
|
|
70
|
+
this.#integration.configure(config)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = OpenaiAgentsPlugin
|