dd-trace 6.6.0 → 6.8.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 +8 -31
- 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 +84 -22
- package/ci/test-optimization-validation/generated-test-contract.js +281 -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 +815 -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/offline-output.js +6 -6
- 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/safe-files.js +4 -4
- 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 +6 -5
- package/ci/test-optimization-validation/source-text.js +87 -0
- package/ci/test-optimization-validation/test-output.js +12 -22
- package/ci/vitest-no-worker-init-setup.mjs +348 -51
- package/ext/tags.d.ts +1 -0
- package/index.d.ts +84 -13
- package/package.json +10 -4
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +9 -6
- package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest.js +8 -14
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
- package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
- package/packages/datadog-instrumentations/src/playwright.js +3 -2
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
- package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
- package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +2 -1
- package/packages/datadog-plugin-jest/src/index.js +2 -3
- package/packages/datadog-plugin-jest/src/util.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +25 -6
- 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/datadog-plugin-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +84 -11
- package/packages/datadog-webpack/index.js +2 -2
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +50 -51
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +7 -5
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
- package/packages/dd-trace/src/config/index.js +15 -0
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +12 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- package/packages/dd-trace/src/encode/span-stats.js +7 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporter.js +16 -9
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
- 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/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- 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 +6 -12
- package/packages/dd-trace/src/llmobs/span_processor.js +14 -7
- package/packages/dd-trace/src/llmobs/tagger.js +24 -5
- package/packages/dd-trace/src/llmobs/util.js +56 -3
- package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- 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/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +114 -23
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -1
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +7 -6
- package/packages/dd-trace/src/plugins/util/git.js +1 -1
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +38 -12
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/ritm.js +5 -20
- package/packages/dd-trace/src/span_processor.js +1 -1
- package/packages/dd-trace/src/span_stats.js +22 -4
- package/packages/dd-trace/src/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
- 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
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { extractContentParts, extractTextFromContentItem } = require('../openai/utils')
|
|
4
|
+
const { safeJsonParse } = require('../../util')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Flatten Responses and Chat Completions content parts without dropping
|
|
8
|
+
* provider-specific image or audio inputs.
|
|
9
|
+
*
|
|
10
|
+
* @param {Array<object>} parts
|
|
11
|
+
* @returns {{ content: string, audioParts: Array<{ mimeType: string, content: string }> }}
|
|
12
|
+
*/
|
|
13
|
+
function extractMessageContent (parts) {
|
|
14
|
+
const contentParts = []
|
|
15
|
+
const audioParts = []
|
|
16
|
+
|
|
17
|
+
for (const part of parts) {
|
|
18
|
+
if (!part) continue
|
|
19
|
+
const text = extractTextFromContentItem(part)
|
|
20
|
+
if (text) {
|
|
21
|
+
contentParts.push(text)
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const extracted = extractContentParts([part])
|
|
26
|
+
if (extracted.content) contentParts.push(extracted.content)
|
|
27
|
+
if (extracted.audioParts.length > 0) audioParts.push(...extracted.audioParts)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { content: contentParts.join(''), audioParts }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Normalize OpenAI Chat Completions tool calls to the LLMObs message schema.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} message
|
|
37
|
+
* @returns {Array<{ toolId?: string, name?: string, arguments: object, type?: string }>}
|
|
38
|
+
*/
|
|
39
|
+
function extractChatCompletionToolCalls (message) {
|
|
40
|
+
if (message.function_call) {
|
|
41
|
+
return [{
|
|
42
|
+
name: message.function_call.name,
|
|
43
|
+
arguments: safeJsonParse(message.function_call.arguments, {}),
|
|
44
|
+
type: 'function',
|
|
45
|
+
}]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const toolCalls = []
|
|
49
|
+
if (!Array.isArray(message.tool_calls)) return toolCalls
|
|
50
|
+
|
|
51
|
+
for (const toolCall of message.tool_calls) {
|
|
52
|
+
if (!toolCall) continue
|
|
53
|
+
toolCalls.push({
|
|
54
|
+
toolId: toolCall.id,
|
|
55
|
+
name: toolCall.function?.name,
|
|
56
|
+
arguments: safeJsonParse(toolCall.function?.arguments, {}),
|
|
57
|
+
type: toolCall.type,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
return toolCalls
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Normalize a Chat Completions message to the LLMObs message schema.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} message
|
|
67
|
+
* @returns {object}
|
|
68
|
+
*/
|
|
69
|
+
function normalizeChatCompletionMessage (message) {
|
|
70
|
+
const normalized = {
|
|
71
|
+
role: message.role || 'assistant',
|
|
72
|
+
content: message.content ?? '',
|
|
73
|
+
}
|
|
74
|
+
if (message.audioParts?.length > 0) normalized.audioParts = message.audioParts
|
|
75
|
+
const toolCalls = extractChatCompletionToolCalls(message)
|
|
76
|
+
if (toolCalls.length > 0) normalized.toolCalls = toolCalls
|
|
77
|
+
if (message.tool_call_id) normalized.toolId = message.tool_call_id
|
|
78
|
+
return normalized
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extracts input messages for an LLM span. agents-openai stores only
|
|
83
|
+
* `request.input` on `spanData._input` (string or message-array), and the
|
|
84
|
+
* system instructions are echoed back on the response as `instructions`.
|
|
85
|
+
*
|
|
86
|
+
* @param {string|Array<unknown>} input - The raw `request.input` (`spanData._input`).
|
|
87
|
+
* @param {string} [instructions] - System instructions echoed on `response.instructions`.
|
|
88
|
+
* @returns {Array<{ role: string, content: string }>}
|
|
89
|
+
*/
|
|
90
|
+
function extractInputMessages (input, instructions) {
|
|
91
|
+
const messages = []
|
|
92
|
+
|
|
93
|
+
if (instructions) {
|
|
94
|
+
messages.push({ role: 'system', content: instructions })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof input === 'string') {
|
|
98
|
+
messages.push({ role: 'user', content: input })
|
|
99
|
+
} else if (Array.isArray(input)) {
|
|
100
|
+
for (const item of input) {
|
|
101
|
+
if (!item) continue
|
|
102
|
+
if (item.type === 'message' || item.role) {
|
|
103
|
+
const role = item.role
|
|
104
|
+
if (!role) continue
|
|
105
|
+
|
|
106
|
+
let content = ''
|
|
107
|
+
let audioParts
|
|
108
|
+
if (Array.isArray(item.content)) {
|
|
109
|
+
const extracted = extractMessageContent(item.content)
|
|
110
|
+
content = extracted.content
|
|
111
|
+
audioParts = extracted.audioParts
|
|
112
|
+
} else if (typeof item.content === 'string') {
|
|
113
|
+
content = item.content
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const message = normalizeChatCompletionMessage({ ...item, content, role, audioParts })
|
|
117
|
+
if (content || message.audioParts || message.toolCalls || message.toolId) {
|
|
118
|
+
messages.push(message)
|
|
119
|
+
}
|
|
120
|
+
} else if (item.type === 'function_call') {
|
|
121
|
+
let args = item.arguments
|
|
122
|
+
if (typeof args === 'string') {
|
|
123
|
+
try {
|
|
124
|
+
args = JSON.parse(args)
|
|
125
|
+
} catch {
|
|
126
|
+
args = {}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
messages.push({
|
|
130
|
+
role: 'assistant',
|
|
131
|
+
toolCalls: [{
|
|
132
|
+
toolId: item.call_id,
|
|
133
|
+
name: item.name,
|
|
134
|
+
arguments: args,
|
|
135
|
+
type: item.type,
|
|
136
|
+
}],
|
|
137
|
+
})
|
|
138
|
+
} else if (item.type === 'function_call_output') {
|
|
139
|
+
messages.push({
|
|
140
|
+
role: 'user',
|
|
141
|
+
toolResults: [{
|
|
142
|
+
toolId: item.call_id,
|
|
143
|
+
result: item.output,
|
|
144
|
+
name: item.name || '',
|
|
145
|
+
type: item.type,
|
|
146
|
+
}],
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return messages.length > 0 ? messages : [{ role: 'user', content: '' }]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Extracts output messages from the model response.
|
|
157
|
+
*
|
|
158
|
+
* @param {{ output?: Array<unknown> }} result - The model response
|
|
159
|
+
* @returns {Array<{ role: string, content: string }>}
|
|
160
|
+
*/
|
|
161
|
+
function extractOutputMessages (result) {
|
|
162
|
+
const messages = []
|
|
163
|
+
|
|
164
|
+
if (result?.output) {
|
|
165
|
+
for (const item of result.output) {
|
|
166
|
+
if (!item) continue
|
|
167
|
+
if (item.type === 'message') {
|
|
168
|
+
let content = ''
|
|
169
|
+
if (Array.isArray(item.content)) {
|
|
170
|
+
const textParts = []
|
|
171
|
+
for (const contentItem of item.content) {
|
|
172
|
+
if (contentItem?.type === 'output_text' && contentItem.text) {
|
|
173
|
+
textParts.push(contentItem.text)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
content = textParts.join('')
|
|
177
|
+
} else if (typeof item.content === 'string') {
|
|
178
|
+
content = item.content
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
messages.push({ role: item.role || 'assistant', content })
|
|
182
|
+
} else if (item.type === 'function_call') {
|
|
183
|
+
let args = item.arguments
|
|
184
|
+
if (typeof args === 'string') {
|
|
185
|
+
try {
|
|
186
|
+
args = JSON.parse(args)
|
|
187
|
+
} catch {
|
|
188
|
+
args = {}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
messages.push({
|
|
192
|
+
role: 'assistant',
|
|
193
|
+
toolCalls: [{
|
|
194
|
+
toolId: item.call_id,
|
|
195
|
+
name: item.name,
|
|
196
|
+
arguments: args,
|
|
197
|
+
type: item.type,
|
|
198
|
+
}],
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return messages.length > 0 ? messages : [{ content: '', role: '' }]
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Extracts output messages from an OpenAI Chat Completions response.
|
|
209
|
+
*
|
|
210
|
+
* @param {Array<{ choices?: Array<{ message?: object }> }>} result - The model responses
|
|
211
|
+
* @returns {Array<object>}
|
|
212
|
+
*/
|
|
213
|
+
function extractGenerationOutputMessages (result) {
|
|
214
|
+
const messages = []
|
|
215
|
+
|
|
216
|
+
if (Array.isArray(result)) {
|
|
217
|
+
for (const response of result) {
|
|
218
|
+
if (!Array.isArray(response?.choices)) continue
|
|
219
|
+
for (const choice of response.choices) {
|
|
220
|
+
const message = choice?.message
|
|
221
|
+
if (!message) continue
|
|
222
|
+
messages.push(normalizeChatCompletionMessage(message))
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return messages.length > 0 ? messages : [{ content: '', role: '' }]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Extracts token usage metrics from the model response. Returns `undefined`
|
|
232
|
+
* when there's nothing to tag, so callers can skip the tagger call without
|
|
233
|
+
* allocating an Object.keys array.
|
|
234
|
+
*
|
|
235
|
+
* @param {{ usage?: { inputTokens?: number, outputTokens?: number, totalTokens?: number,
|
|
236
|
+
* outputTokensDetails?: { reasoningTokens?: number },
|
|
237
|
+
* completion_tokens_details?: { reasoning_tokens?: number } } }} result
|
|
238
|
+
* @returns {{ inputTokens?: number, outputTokens?: number, totalTokens?: number,
|
|
239
|
+
* reasoningOutputTokens?: number } | undefined}
|
|
240
|
+
*/
|
|
241
|
+
function extractMetrics (result) {
|
|
242
|
+
const usage = result?.usage
|
|
243
|
+
if (!usage) return
|
|
244
|
+
|
|
245
|
+
const inputTokens = usage.inputTokens ?? usage.input_tokens ?? usage.prompt_tokens
|
|
246
|
+
const outputTokens = usage.outputTokens ?? usage.output_tokens ?? usage.completion_tokens
|
|
247
|
+
const totalTokens = usage.totalTokens ?? usage.total_tokens
|
|
248
|
+
const reasoningTokens = usage.outputTokensDetails?.reasoningTokens ??
|
|
249
|
+
usage.output_tokens_details?.reasoning_tokens ??
|
|
250
|
+
usage.completion_tokens_details?.reasoning_tokens
|
|
251
|
+
|
|
252
|
+
if (inputTokens === undefined && outputTokens === undefined &&
|
|
253
|
+
totalTokens === undefined && !reasoningTokens) return
|
|
254
|
+
|
|
255
|
+
const metrics = {}
|
|
256
|
+
if (inputTokens !== undefined) metrics.inputTokens = inputTokens
|
|
257
|
+
if (outputTokens !== undefined) metrics.outputTokens = outputTokens
|
|
258
|
+
// Tagger maps `reasoningOutputTokens` → `reasoning_output_tokens` in the
|
|
259
|
+
// LLMObs span event. Skip when zero — emitting a zero just adds noise.
|
|
260
|
+
if (reasoningTokens) metrics.reasoningOutputTokens = reasoningTokens
|
|
261
|
+
|
|
262
|
+
if (totalTokens !== undefined) {
|
|
263
|
+
metrics.totalTokens = totalTokens
|
|
264
|
+
} else if (metrics.inputTokens !== undefined && metrics.outputTokens !== undefined) {
|
|
265
|
+
metrics.totalTokens = metrics.inputTokens + metrics.outputTokens
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return metrics
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Fields the OpenAI Responses API echoes back from the request configuration.
|
|
272
|
+
// agents-openai only stores `request.input` on the span — the user's
|
|
273
|
+
// `modelSettings` aren't directly observable, so we read the response-echoed
|
|
274
|
+
// values. Matches dd-trace-py's openai-agents integration (see
|
|
275
|
+
// `OaiSpanAdapter.llmobs_metadata`); both ship without filtering OpenAI's
|
|
276
|
+
// default values.
|
|
277
|
+
const RESPONSE_METADATA_FIELDS = [
|
|
278
|
+
'temperature',
|
|
279
|
+
'max_output_tokens',
|
|
280
|
+
'top_p',
|
|
281
|
+
'tools',
|
|
282
|
+
'tool_choice',
|
|
283
|
+
'truncation',
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Extracts metadata from the model response. Mirrors Python's
|
|
288
|
+
* `OaiSpanAdapter.llmobs_metadata` — emits all response-echoed configuration
|
|
289
|
+
* fields plus `text` when present. Returns `undefined` when nothing was
|
|
290
|
+
* captured, so callers can skip the tagger call without allocating.
|
|
291
|
+
*
|
|
292
|
+
* @param {object | undefined} response
|
|
293
|
+
* @returns {object | undefined}
|
|
294
|
+
*/
|
|
295
|
+
function extractMetadata (response) {
|
|
296
|
+
if (!response) return
|
|
297
|
+
|
|
298
|
+
let metadata
|
|
299
|
+
for (const field of RESPONSE_METADATA_FIELDS) {
|
|
300
|
+
const value = response[field]
|
|
301
|
+
if (value !== undefined && value !== null) {
|
|
302
|
+
metadata ??= {}
|
|
303
|
+
metadata[field] = value
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (response.text) {
|
|
308
|
+
metadata ??= {}
|
|
309
|
+
metadata.text = response.text
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return metadata
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
module.exports = {
|
|
316
|
+
extractInputMessages,
|
|
317
|
+
extractOutputMessages,
|
|
318
|
+
extractGenerationOutputMessages,
|
|
319
|
+
extractMetrics,
|
|
320
|
+
extractMetadata,
|
|
321
|
+
}
|
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
SPAN_KIND,
|
|
12
12
|
OUTPUT_VALUE,
|
|
13
13
|
INPUT_VALUE,
|
|
14
|
+
TRACE_ID,
|
|
14
15
|
} = require('./constants/tags')
|
|
15
16
|
const {
|
|
16
17
|
getFunctionArguments,
|
|
@@ -34,12 +35,6 @@ class LLMObs extends NoopLLMObs {
|
|
|
34
35
|
*/
|
|
35
36
|
#hasUserSpanProcessor = false
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
-
* Lazily-created experiments facade (see ./experiments).
|
|
39
|
-
* @type {import('./experiments').Experiments | undefined}
|
|
40
|
-
*/
|
|
41
|
-
#experiments
|
|
42
|
-
|
|
43
38
|
/**
|
|
44
39
|
* @param {import('../tracer')} tracer - Tracer instance
|
|
45
40
|
* @param {import('./index')} llmobsModule - LLMObs module instance
|
|
@@ -65,8 +60,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
65
60
|
* a clear message on use.
|
|
66
61
|
*/
|
|
67
62
|
get experiments () {
|
|
68
|
-
|
|
69
|
-
return this.#experiments
|
|
63
|
+
return createExperiments(this._config, this)
|
|
70
64
|
}
|
|
71
65
|
|
|
72
66
|
enable (options = {}) {
|
|
@@ -317,14 +311,14 @@ class LLMObs extends NoopLLMObs {
|
|
|
317
311
|
}
|
|
318
312
|
throw e
|
|
319
313
|
} finally {
|
|
320
|
-
if (autoinstrumented
|
|
314
|
+
if (!autoinstrumented) {
|
|
321
315
|
telemetry.recordLLMObsAnnotate(span, err)
|
|
322
316
|
}
|
|
323
317
|
}
|
|
324
318
|
}
|
|
325
319
|
|
|
326
320
|
exportSpan (span) {
|
|
327
|
-
span
|
|
321
|
+
span ||= this._active()
|
|
328
322
|
let err = ''
|
|
329
323
|
try {
|
|
330
324
|
if (!span) {
|
|
@@ -345,7 +339,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
345
339
|
}
|
|
346
340
|
try {
|
|
347
341
|
return {
|
|
348
|
-
traceId:
|
|
342
|
+
traceId: LLMObsTagger.tagMap.get(span)[TRACE_ID],
|
|
349
343
|
spanId: span.context().toSpanId(),
|
|
350
344
|
}
|
|
351
345
|
} catch {
|
|
@@ -425,7 +419,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
425
419
|
err = 'invalid_metric_value'
|
|
426
420
|
throw new Error('value must be a boolean for a boolean metric')
|
|
427
421
|
}
|
|
428
|
-
if (metricType === 'json' &&
|
|
422
|
+
if (metricType === 'json' && (typeof value !== 'object' || value == null || Array.isArray(value))) {
|
|
429
423
|
err = 'invalid_metric_value'
|
|
430
424
|
throw new Error('value must be a JSON object for a json metric')
|
|
431
425
|
}
|
|
@@ -35,6 +35,7 @@ const {
|
|
|
35
35
|
LLMOBS_SUBMITTED_TAG_KEY,
|
|
36
36
|
SAMPLE_RATE,
|
|
37
37
|
SAMPLING_DECISION,
|
|
38
|
+
TRACE_ID,
|
|
38
39
|
} = require('./constants/tags')
|
|
39
40
|
const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
|
|
40
41
|
const telemetry = require('./telemetry')
|
|
@@ -236,8 +237,19 @@ class LLMObsSpanProcessor {
|
|
|
236
237
|
meta.input.prompt = prompt
|
|
237
238
|
}
|
|
238
239
|
|
|
240
|
+
const apmTraceId = span.context().toTraceId(true)
|
|
241
|
+
const llmobsTraceId = mlObsTags[TRACE_ID] ?? apmTraceId
|
|
242
|
+
const dd = {
|
|
243
|
+
span_id: span.context().toSpanId(),
|
|
244
|
+
trace_id: apmTraceId,
|
|
245
|
+
sample_rate: mlObsTags[SAMPLE_RATE],
|
|
246
|
+
sampling_decision: mlObsTags[SAMPLING_DECISION],
|
|
247
|
+
apm_trace_id: apmTraceId,
|
|
248
|
+
}
|
|
249
|
+
if (tags.experiment_id) dd.scope = 'experiments'
|
|
250
|
+
|
|
239
251
|
const llmObsSpanEvent = {
|
|
240
|
-
trace_id:
|
|
252
|
+
trace_id: llmobsTraceId,
|
|
241
253
|
span_id: span.context().toSpanId(),
|
|
242
254
|
parent_id: parentId,
|
|
243
255
|
name,
|
|
@@ -247,12 +259,7 @@ class LLMObsSpanProcessor {
|
|
|
247
259
|
status: error ? 'error' : 'ok',
|
|
248
260
|
meta,
|
|
249
261
|
metrics,
|
|
250
|
-
_dd:
|
|
251
|
-
span_id: span.context().toSpanId(),
|
|
252
|
-
trace_id: span.context().toTraceId(true),
|
|
253
|
-
sample_rate: mlObsTags[SAMPLE_RATE],
|
|
254
|
-
sampling_decision: mlObsTags[SAMPLING_DECISION],
|
|
255
|
-
},
|
|
262
|
+
_dd: dd,
|
|
256
263
|
}
|
|
257
264
|
|
|
258
265
|
if (sessionId) llmObsSpanEvent.session_id = sessionId
|
|
@@ -51,9 +51,18 @@ const {
|
|
|
51
51
|
SAMPLING_DECISION_DROPPED,
|
|
52
52
|
PROPAGATED_SAMPLE_RATE_KEY,
|
|
53
53
|
PROPAGATED_SAMPLING_DECISION_KEY,
|
|
54
|
+
TRACE_ID,
|
|
55
|
+
PROPAGATED_TRACE_ID_KEY,
|
|
54
56
|
} = require('./constants/tags')
|
|
55
57
|
const { storage } = require('./storage')
|
|
56
|
-
const {
|
|
58
|
+
const {
|
|
59
|
+
findGenAIAncestorSpanId,
|
|
60
|
+
validateCostTags,
|
|
61
|
+
writeBridgeTags,
|
|
62
|
+
validateToolDefinitions,
|
|
63
|
+
generateLlmObsTraceId,
|
|
64
|
+
normalizeLlmObsTraceId,
|
|
65
|
+
} = require('./util')
|
|
57
66
|
|
|
58
67
|
// global registry of LLMObs spans
|
|
59
68
|
// maps LLMObs spans to their annotations
|
|
@@ -126,12 +135,20 @@ class LLMObsTagger {
|
|
|
126
135
|
|
|
127
136
|
this._register(span)
|
|
128
137
|
|
|
138
|
+
const traceTags = span.context()._trace.tags
|
|
139
|
+
|
|
140
|
+
const llmobsTraceId =
|
|
141
|
+
registry.get(parent)?.[TRACE_ID] ??
|
|
142
|
+
normalizeLlmObsTraceId(traceTags[PROPAGATED_TRACE_ID_KEY]) ??
|
|
143
|
+
generateLlmObsTraceId(span._startTime)
|
|
144
|
+
this._setTag(span, TRACE_ID, llmobsTraceId)
|
|
145
|
+
|
|
129
146
|
// When the registering span sits below an OTel `gen_ai.*` ancestor, use
|
|
130
147
|
// that ancestor as the parent_id fallback and suppress the bridge
|
|
131
148
|
// parent_id tag so the indexer doesn't invert the trace.
|
|
132
149
|
const genAIAncestorSpanId = findGenAIAncestorSpanId(span)
|
|
133
150
|
|
|
134
|
-
writeBridgeTags(span, { includeParentId: genAIAncestorSpanId === null })
|
|
151
|
+
writeBridgeTags(span, { includeParentId: genAIAncestorSpanId === null, llmobsTraceId })
|
|
135
152
|
|
|
136
153
|
this._setTag(span, ML_APP, spanMlApp)
|
|
137
154
|
|
|
@@ -141,9 +158,7 @@ class LLMObsTagger {
|
|
|
141
158
|
if (modelName) this.tagModelName(span, modelName)
|
|
142
159
|
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
|
|
143
160
|
|
|
144
|
-
|
|
145
|
-
sessionId = sessionId ||
|
|
146
|
-
registry.get(parent)?.[SESSION_ID] ||
|
|
161
|
+
sessionId ||= registry.get(parent)?.[SESSION_ID] ||
|
|
147
162
|
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] ||
|
|
148
163
|
traceTags[PROPAGATED_SESSION_ID_KEY]
|
|
149
164
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
@@ -500,6 +515,10 @@ class LLMObsTagger {
|
|
|
500
515
|
this._setTag(span, MODEL_NAME, modelName)
|
|
501
516
|
}
|
|
502
517
|
|
|
518
|
+
setName (span, name) {
|
|
519
|
+
this._setTag(span, NAME, name)
|
|
520
|
+
}
|
|
521
|
+
|
|
503
522
|
#tagText (span, data, key) {
|
|
504
523
|
if (data) {
|
|
505
524
|
if (typeof data === 'string') {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const id = require('../id')
|
|
3
4
|
const log = require('../log')
|
|
4
5
|
const {
|
|
5
6
|
LLMOBS_PARENT_ID_BRIDGE_KEY,
|
|
@@ -7,6 +8,10 @@ const {
|
|
|
7
8
|
SPAN_KINDS,
|
|
8
9
|
} = require('./constants/tags')
|
|
9
10
|
|
|
11
|
+
const DECIMAL_TRACE_ID_REGEX = /^\d+$/
|
|
12
|
+
const HEX_TRACE_ID_REGEX = /^[0-9a-f]{32}$/i
|
|
13
|
+
const MAX_UINT_64 = (1n << 64n) - 1n
|
|
14
|
+
|
|
10
15
|
// LLM I/O is overwhelmingly ASCII (English prompts and code). Walk once
|
|
11
16
|
// looking for the first non-ASCII char; if there is none, hand the input
|
|
12
17
|
// straight back. Otherwise pick up the slow path from the byte that needed
|
|
@@ -312,12 +317,12 @@ function safeJsonParse (value, fallback) {
|
|
|
312
317
|
// LLMObs root and hoists the gen_ai ancestors under it, inverting the trace.
|
|
313
318
|
/**
|
|
314
319
|
* @param {import('../opentracing/span')} span
|
|
315
|
-
* @param {{ includeParentId?: boolean }} [opts]
|
|
320
|
+
* @param {{ includeParentId?: boolean, llmobsTraceId?: string }} [opts]
|
|
316
321
|
*/
|
|
317
|
-
function writeBridgeTags (span, { includeParentId = true } = {}) {
|
|
322
|
+
function writeBridgeTags (span, { includeParentId = true, llmobsTraceId } = {}) {
|
|
318
323
|
const traceTags = span?.context?.()._trace?.tags
|
|
319
324
|
if (!traceTags || traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY]) return
|
|
320
|
-
traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = span.context().toTraceId(true)
|
|
325
|
+
traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = llmobsTraceId ?? span.context().toTraceId(true)
|
|
321
326
|
if (includeParentId) {
|
|
322
327
|
traceTags[LLMOBS_PARENT_ID_BRIDGE_KEY] = span.context().toSpanId()
|
|
323
328
|
}
|
|
@@ -362,6 +367,51 @@ function findGenAIAncestorSpanId (span) {
|
|
|
362
367
|
return null
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Generate a 128-bit LLMObs trace ID with the span start time encoded in its high bits.
|
|
372
|
+
* @param {number} startTime
|
|
373
|
+
* @returns {string}
|
|
374
|
+
*/
|
|
375
|
+
function generateLlmObsTraceId (startTime) {
|
|
376
|
+
const identifier = id()
|
|
377
|
+
const traceIdHigh = Math.floor(startTime / 1000)
|
|
378
|
+
.toString(16)
|
|
379
|
+
.padStart(8, '0')
|
|
380
|
+
.padEnd(16, '0')
|
|
381
|
+
|
|
382
|
+
return identifier.toTraceIdHex(traceIdHigh).padStart(32, '0')
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Convert an internally stored hexadecimal LLMObs trace ID to its distributed wire representation.
|
|
387
|
+
* @param {string | undefined} traceId
|
|
388
|
+
* @returns {string | undefined}
|
|
389
|
+
*/
|
|
390
|
+
function llmObsTraceIdToWire (traceId) {
|
|
391
|
+
if (!traceId) return
|
|
392
|
+
if (!HEX_TRACE_ID_REGEX.test(traceId)) return traceId
|
|
393
|
+
|
|
394
|
+
return BigInt(`0x${traceId}`).toString(10)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Normalize a distributed LLMObs trace ID to the representation expected by LLMObs span events.
|
|
399
|
+
* @param {string | undefined} traceId
|
|
400
|
+
* @returns {string | undefined}
|
|
401
|
+
*/
|
|
402
|
+
function normalizeLlmObsTraceId (traceId) {
|
|
403
|
+
if (!traceId) return
|
|
404
|
+
|
|
405
|
+
if (HEX_TRACE_ID_REGEX.test(traceId) && (traceId[0] === '0' || !DECIMAL_TRACE_ID_REGEX.test(traceId))) {
|
|
406
|
+
return traceId
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!DECIMAL_TRACE_ID_REGEX.test(traceId)) return traceId
|
|
410
|
+
|
|
411
|
+
const identifier = BigInt(traceId)
|
|
412
|
+
return identifier > MAX_UINT_64 ? identifier.toString(16).padStart(32, '0') : traceId
|
|
413
|
+
}
|
|
414
|
+
|
|
365
415
|
// Maps an audio `format` (e.g. "wav", "mp3") to a MIME type. Defaults to `audio/wav` when the
|
|
366
416
|
// format is missing. Provider-specific overrides (e.g. OpenAI's mp3 -> audio/mpeg) are passed in
|
|
367
417
|
// via `mimeTypeLookup` so this stays provider-agnostic. A non-string `format` is treated as missing
|
|
@@ -396,6 +446,9 @@ module.exports = {
|
|
|
396
446
|
audioMimeTypeFromFormat,
|
|
397
447
|
encodeUnicode,
|
|
398
448
|
findGenAIAncestorSpanId,
|
|
449
|
+
generateLlmObsTraceId,
|
|
450
|
+
llmObsTraceIdToWire,
|
|
451
|
+
normalizeLlmObsTraceId,
|
|
399
452
|
formatAudioPart,
|
|
400
453
|
validateCostTags,
|
|
401
454
|
validateKind,
|
|
@@ -49,12 +49,18 @@ class LLMObsSpanWriter extends BaseWriter {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
makePayload (events) {
|
|
52
|
-
return events.map(event =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
return events.map(event => {
|
|
53
|
+
const eventData = {
|
|
54
|
+
'_dd.stage': 'raw',
|
|
55
|
+
'_dd.tracer_version': tracerVersion,
|
|
56
|
+
event_type: this._eventType,
|
|
57
|
+
spans: [event],
|
|
58
|
+
}
|
|
59
|
+
if (event?._dd?.scope === 'experiments') {
|
|
60
|
+
eventData['_dd.scope'] = 'experiments'
|
|
61
|
+
}
|
|
62
|
+
return eventData
|
|
63
|
+
})
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
_truncateSpanEvent (event) {
|
|
@@ -55,7 +55,7 @@ class NoopProxy {
|
|
|
55
55
|
|
|
56
56
|
if (typeof fn !== 'function') return
|
|
57
57
|
|
|
58
|
-
options
|
|
58
|
+
options ||= {}
|
|
59
59
|
|
|
60
60
|
return this._tracer.trace(name, options, fn)
|
|
61
61
|
}
|
|
@@ -68,7 +68,7 @@ class NoopProxy {
|
|
|
68
68
|
|
|
69
69
|
if (typeof fn !== 'function') return fn
|
|
70
70
|
|
|
71
|
-
options
|
|
71
|
+
options ||= {}
|
|
72
72
|
|
|
73
73
|
return this._tracer.wrap(name, options, fn)
|
|
74
74
|
}
|
|
@@ -20,7 +20,7 @@ const RETRY_JITTER = 0.2
|
|
|
20
20
|
* @property {URL} endpoint
|
|
21
21
|
* @property {number} pollIntervalMs
|
|
22
22
|
* @property {number} requestTimeoutMs
|
|
23
|
-
* @property {string} apiKey
|
|
23
|
+
* @property {string | undefined} apiKey
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -138,7 +138,7 @@ class AgentlessConfigurationSource {
|
|
|
138
138
|
#request (signal) {
|
|
139
139
|
const headers = getClientLibraryHeaders()
|
|
140
140
|
headers['Accept-Encoding'] = 'gzip'
|
|
141
|
-
headers['DD-API-KEY'] = this.#config.apiKey
|
|
141
|
+
if (this.#config.apiKey) headers['DD-API-KEY'] = this.#config.apiKey
|
|
142
142
|
if (this.#etag) headers['If-None-Match'] = this.#etag
|
|
143
143
|
|
|
144
144
|
/**
|
|
@@ -228,10 +228,7 @@ class AgentlessConfigurationSource {
|
|
|
228
228
|
this.#failureWarnings.add(category)
|
|
229
229
|
|
|
230
230
|
if (statusCode === 401 || statusCode === 403) {
|
|
231
|
-
log.warn(
|
|
232
|
-
'Feature Flagging agentless endpoint returned HTTP %d; verify DD_API_KEY is configured and valid',
|
|
233
|
-
statusCode
|
|
234
|
-
)
|
|
231
|
+
log.warn('Feature Flagging agentless endpoint returned HTTP %d; verify endpoint authentication', statusCode)
|
|
235
232
|
} else if (statusCode) {
|
|
236
233
|
log.warn('Feature Flagging agentless endpoint returned HTTP %d after %d attempts', statusCode, attempts)
|
|
237
234
|
} else if (attempts > 1) {
|