dd-trace 6.9.0 → 6.11.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/README.md +0 -17
- package/ci/vitest-no-worker-init-setup.mjs +112 -10
- package/index.d.ts +150 -3
- package/initialize.mjs +28 -17
- package/openfeature.js +2 -2
- package/package.json +14 -10
- package/packages/datadog-esbuild/index.js +0 -34
- package/packages/datadog-instrumentations/src/anthropic.js +210 -15
- package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
- package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
- package/packages/datadog-instrumentations/src/bunyan.js +3 -16
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
- package/packages/datadog-instrumentations/src/cucumber.js +26 -11
- package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
- package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
- package/packages/datadog-instrumentations/src/cypress.js +2 -0
- package/packages/datadog-instrumentations/src/fastify.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
- package/packages/datadog-instrumentations/src/jest.js +26 -18
- package/packages/datadog-instrumentations/src/mariadb.js +278 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
- package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
- package/packages/datadog-instrumentations/src/mysql.js +45 -15
- package/packages/datadog-instrumentations/src/mysql2.js +119 -18
- package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
- package/packages/datadog-instrumentations/src/pg.js +116 -92
- package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
- package/packages/datadog-instrumentations/src/playwright.js +149 -2
- package/packages/datadog-instrumentations/src/router.js +2 -0
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
- package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
- package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
- package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
- package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
- package/packages/datadog-plugin-bunyan/src/index.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +11 -0
- package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
- package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
- package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
- package/packages/datadog-plugin-cypress/src/index.js +12 -6
- package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
- package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
- package/packages/datadog-plugin-electron/src/index.js +2 -0
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
- package/packages/datadog-plugin-jest/src/index.js +11 -4
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
- package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
- package/packages/datadog-plugin-mocha/src/index.js +606 -40
- package/packages/datadog-plugin-mysql/src/index.js +37 -0
- package/packages/datadog-plugin-nats/src/index.js +1 -1
- package/packages/datadog-plugin-next/src/index.js +1 -19
- package/packages/datadog-plugin-openai/src/tracing.js +41 -30
- package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
- package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
- package/packages/datadog-plugin-pg/src/index.js +1 -0
- package/packages/datadog-plugin-playwright/src/index.js +42 -13
- package/packages/datadog-plugin-vitest/src/index.js +14 -20
- package/packages/datadog-plugin-ws/src/util.js +2 -1
- package/packages/datadog-webpack/index.js +0 -11
- package/packages/dd-trace/index.js +0 -1
- package/packages/dd-trace/src/agent/info.js +6 -5
- package/packages/dd-trace/src/aiguard/client.js +100 -1
- package/packages/dd-trace/src/aiguard/errors.js +41 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
- package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
- package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
- package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
- package/packages/dd-trace/src/aiguard/sdk.js +22 -278
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
- package/packages/dd-trace/src/carrier.js +356 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
- package/packages/dd-trace/src/config/index.js +13 -2
- package/packages/dd-trace/src/config/major-overrides.js +7 -0
- package/packages/dd-trace/src/config/supported-configurations.json +24 -0
- package/packages/dd-trace/src/datastreams/context.js +6 -1
- package/packages/dd-trace/src/datastreams/index.js +2 -0
- package/packages/dd-trace/src/datastreams/manager.js +14 -3
- package/packages/dd-trace/src/datastreams/pathway.js +23 -22
- package/packages/dd-trace/src/datastreams/processor.js +16 -13
- package/packages/dd-trace/src/datastreams/size.js +5 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
- package/packages/dd-trace/src/exporters/common/writer.js +11 -6
- package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
- package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
- package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
- package/packages/dd-trace/src/llmobs/index.js +34 -2
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
- package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
- package/packages/dd-trace/src/llmobs/tagger.js +96 -0
- package/packages/dd-trace/src/llmobs/util.js +78 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -4
- package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
- package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
- package/packages/dd-trace/src/opentracing/tracer.js +5 -2
- package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
- package/packages/dd-trace/src/plugin_manager.js +5 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
- package/packages/dd-trace/src/plugins/util/test.js +93 -14
- package/packages/dd-trace/src/plugins/util/web.js +56 -2
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
- package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
- package/packages/dd-trace/src/proxy.js +38 -29
- package/packages/dd-trace/src/ritm.js +9 -2
- package/packages/dd-trace/src/serverless.js +37 -0
- package/packages/dd-trace/src/span_format.js +1 -0
- package/packages/dd-trace/src/span_stats.js +30 -3
- package/packages/dd-trace/src/storage-channels.js +86 -0
- package/packages/dd-trace/src/tracer.js +9 -2
- package/packages/dd-trace/src/tracer_metadata.js +14 -1
- package/packages/dd-trace/src/web-tags-cache.js +132 -0
- package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
- package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
- package/packages/dd-trace/src/feature-registry.js +0 -29
- package/packages/dd-trace/src/openfeature/register.js +0 -35
- package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const log = require('../../log')
|
|
4
|
+
const { ExternalExperiment } = require('./experiment')
|
|
5
|
+
|
|
6
|
+
const NOOP_EXPERIMENT_ID = '00000000-0000-0000-0000-000000000000'
|
|
7
|
+
const NOOP_SPAN_ID = '0000000000000000'
|
|
8
|
+
const NOOP_TRACE_ID = '00000000000000000000000000000000'
|
|
4
9
|
|
|
5
10
|
class NoopDataset {
|
|
6
11
|
#name
|
|
12
|
+
#description
|
|
7
13
|
#records
|
|
8
14
|
|
|
9
15
|
constructor (name = '', options = {}) {
|
|
10
16
|
this.#name = name
|
|
17
|
+
this.#description = typeof options === 'string' ? options : (options.description ?? '')
|
|
11
18
|
this.#records = (typeof options === 'string' ? [] : (options.records ?? [])).map(record => ({
|
|
12
19
|
id: record.id ?? null,
|
|
13
20
|
input: record.inputData,
|
|
@@ -21,6 +28,20 @@ class NoopDataset {
|
|
|
21
28
|
return this
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
update (index, fields) {
|
|
32
|
+
const record = this.#records[index]
|
|
33
|
+
if (record == null) return this
|
|
34
|
+
if (Object.hasOwn(fields, 'input')) record.input = fields.input
|
|
35
|
+
if (Object.hasOwn(fields, 'expectedOutput')) record.expectedOutput = fields.expectedOutput ?? null
|
|
36
|
+
if (Object.hasOwn(fields, 'metadata')) record.metadata = fields.metadata ?? {}
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
delete (index) {
|
|
41
|
+
this.#records.splice(index, 1)
|
|
42
|
+
return this
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
push () {
|
|
25
46
|
return Promise.resolve({ pushedCount: 0, totalCount: 0 })
|
|
26
47
|
}
|
|
@@ -29,6 +50,10 @@ class NoopDataset {
|
|
|
29
50
|
return this.#name
|
|
30
51
|
}
|
|
31
52
|
|
|
53
|
+
description () {
|
|
54
|
+
return this.#description
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
id () {
|
|
33
58
|
return null
|
|
34
59
|
}
|
|
@@ -60,9 +85,11 @@ class NoopDataset {
|
|
|
60
85
|
|
|
61
86
|
class NoopExperiment {
|
|
62
87
|
#name
|
|
88
|
+
#external
|
|
63
89
|
|
|
64
|
-
constructor (name = '') {
|
|
90
|
+
constructor (name = '', external = false) {
|
|
65
91
|
this.#name = name
|
|
92
|
+
this.#external = external
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
name () {
|
|
@@ -70,7 +97,7 @@ class NoopExperiment {
|
|
|
70
97
|
}
|
|
71
98
|
|
|
72
99
|
experimentId () {
|
|
73
|
-
return null
|
|
100
|
+
return this.#external ? NOOP_EXPERIMENT_ID : null
|
|
74
101
|
}
|
|
75
102
|
|
|
76
103
|
url () {
|
|
@@ -80,6 +107,32 @@ class NoopExperiment {
|
|
|
80
107
|
run () {
|
|
81
108
|
return Promise.resolve({ experimentId: null, rows: [], url: null })
|
|
82
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @returns {Promise<{experimentId: string, spanId: string, traceId: string, url: null}>}
|
|
113
|
+
*/
|
|
114
|
+
submitSpan () {
|
|
115
|
+
return Promise.resolve({
|
|
116
|
+
experimentId: NOOP_EXPERIMENT_ID,
|
|
117
|
+
spanId: NOOP_SPAN_ID,
|
|
118
|
+
traceId: NOOP_TRACE_ID,
|
|
119
|
+
url: null,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
submitEvaluationMetrics () {
|
|
127
|
+
return Promise.resolve()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
close () {
|
|
134
|
+
return Promise.resolve()
|
|
135
|
+
}
|
|
83
136
|
}
|
|
84
137
|
|
|
85
138
|
// No-op Experiments used when LLM Observability is disabled or the API/APP keys
|
|
@@ -87,9 +140,11 @@ class NoopExperiment {
|
|
|
87
140
|
// throwing, so intentionally disabled experiments remain graceful.
|
|
88
141
|
class NoopExperiments {
|
|
89
142
|
#reason
|
|
143
|
+
#startExperiment
|
|
90
144
|
|
|
91
|
-
constructor (reason) {
|
|
145
|
+
constructor (reason, options = {}) {
|
|
92
146
|
this.#reason = reason || 'LLMObs experiments are not available'
|
|
147
|
+
this.#startExperiment = options.startExperiment
|
|
93
148
|
}
|
|
94
149
|
|
|
95
150
|
#warn () {
|
|
@@ -110,6 +165,19 @@ class NoopExperiments {
|
|
|
110
165
|
this.#warn()
|
|
111
166
|
return new NoopExperiment(options.name)
|
|
112
167
|
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @param {object} options
|
|
171
|
+
* @returns {Promise<ExternalExperiment>}
|
|
172
|
+
*/
|
|
173
|
+
startExperiment (options = {}) {
|
|
174
|
+
if (this.#startExperiment !== undefined && options.projectName) {
|
|
175
|
+
return this.#startExperiment(options)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.#warn()
|
|
179
|
+
return Promise.resolve(new ExternalExperiment(new NoopExperiment(options.name, true)))
|
|
180
|
+
}
|
|
113
181
|
}
|
|
114
182
|
|
|
115
183
|
module.exports = NoopExperiments
|
|
@@ -13,6 +13,7 @@ class Row {
|
|
|
13
13
|
this.expectedOutput = fields.expectedOutput
|
|
14
14
|
this.errorType = fields.errorType
|
|
15
15
|
this.errorMessage = fields.errorMessage
|
|
16
|
+
this.errorStack = fields.errorStack
|
|
16
17
|
this.evaluations = fields.evaluations
|
|
17
18
|
this.evaluationErrors = fields.evaluationErrors
|
|
18
19
|
}
|
|
@@ -178,12 +178,12 @@ function buildTags (userTags, autoTags) {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/**
|
|
181
|
-
* @param {Record<string, unknown> | undefined}
|
|
182
|
-
* @param {Record<string, unknown>}
|
|
181
|
+
* @param {Record<string, unknown> | undefined} baseTags
|
|
182
|
+
* @param {Record<string, unknown> | undefined} overrideTags
|
|
183
183
|
* @returns {Record<string, unknown>}
|
|
184
184
|
*/
|
|
185
|
-
function
|
|
186
|
-
return
|
|
185
|
+
function mergeTags (baseTags, overrideTags) {
|
|
186
|
+
return { ...baseTags, ...overrideTags }
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
/**
|
|
@@ -195,6 +195,40 @@ function sleep (ms) {
|
|
|
195
195
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* @param {unknown} value
|
|
200
|
+
* @param {number} fallback
|
|
201
|
+
* @returns {number}
|
|
202
|
+
*/
|
|
203
|
+
function timestampMs (value, fallback = Date.now()) {
|
|
204
|
+
if (value === null || value === undefined) return fallback
|
|
205
|
+
if (value instanceof Date) {
|
|
206
|
+
const timestamp = value.getTime()
|
|
207
|
+
return Number.isFinite(timestamp) ? timestamp : fallback
|
|
208
|
+
}
|
|
209
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
210
|
+
const parsed = Date.parse(String(value))
|
|
211
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param {{ durationMs?: unknown, completedAt?: unknown }} row
|
|
216
|
+
* @param {number} startMs
|
|
217
|
+
* @returns {number}
|
|
218
|
+
*/
|
|
219
|
+
function durationNs (row, startMs) {
|
|
220
|
+
if (typeof row.durationMs === 'number' && Number.isFinite(row.durationMs)) {
|
|
221
|
+
return Math.max(0, Math.round(row.durationMs * 1e6))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (row.completedAt !== undefined) {
|
|
225
|
+
const completedMs = timestampMs(row.completedAt, startMs)
|
|
226
|
+
return Math.max(0, Math.round((completedMs - startMs) * 1e6))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return 0
|
|
230
|
+
}
|
|
231
|
+
|
|
198
232
|
/**
|
|
199
233
|
* @param {Record<string, unknown> | undefined} recordMetadata
|
|
200
234
|
* @param {Record<string, unknown>} config
|
|
@@ -207,14 +241,16 @@ function buildSpanMetadata (recordMetadata, config) {
|
|
|
207
241
|
}
|
|
208
242
|
|
|
209
243
|
module.exports = {
|
|
210
|
-
buildExperimentTagObject,
|
|
211
244
|
buildSpanMetadata,
|
|
212
245
|
buildTags,
|
|
246
|
+
durationNs,
|
|
213
247
|
hasEntries,
|
|
214
248
|
inferMetricType,
|
|
249
|
+
mergeTags,
|
|
215
250
|
normalizeEvaluators,
|
|
216
251
|
normalizeJsonMetricValue,
|
|
217
252
|
sleep,
|
|
218
253
|
stringify,
|
|
254
|
+
timestampMs,
|
|
219
255
|
validateEvaluatorName,
|
|
220
256
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
|
+
const { readDatadogTags, writeDatadogTags } = require('../carrier')
|
|
5
6
|
const log = require('../log')
|
|
6
7
|
const { DD_MAJOR } = require('../../../../version')
|
|
7
8
|
const startupLogs = require('../startup-log')
|
|
@@ -12,6 +13,8 @@ const {
|
|
|
12
13
|
PROPAGATED_ML_APP_KEY,
|
|
13
14
|
PROPAGATED_PARENT_ID_KEY,
|
|
14
15
|
PROPAGATED_SESSION_ID_KEY,
|
|
16
|
+
PROPAGATED_PARENT_AGENT_ID_KEY,
|
|
17
|
+
PROPAGATED_PARENT_AGENT_NAME_KEY,
|
|
15
18
|
SAMPLE_RATE,
|
|
16
19
|
SAMPLING_DECISION,
|
|
17
20
|
PROPAGATED_SAMPLE_RATE_KEY,
|
|
@@ -20,6 +23,7 @@ const {
|
|
|
20
23
|
PROPAGATED_TRACE_ID_KEY,
|
|
21
24
|
} = require('./constants/tags')
|
|
22
25
|
const { storage } = require('./storage')
|
|
26
|
+
const { agentNameWireSafe, appendOptionalPropagatedTag, resolveAgentAttribution, stripTagsetEntry } = require('./util')
|
|
23
27
|
const telemetry = require('./telemetry')
|
|
24
28
|
const LLMObsSpanProcessor = require('./span_processor')
|
|
25
29
|
const LLMObsEvalMetricsWriter = require('./writers/evaluations')
|
|
@@ -147,9 +151,17 @@ function handleLLMObsInjection ({ carrier }) {
|
|
|
147
151
|
|
|
148
152
|
if (!parentId && !mlApp && samplingDecision == null && !sessionId && !propagatedTraceId) return
|
|
149
153
|
|
|
154
|
+
// Propagate the nearest agent so spans in the downstream process attribute correctly. When the
|
|
155
|
+
// active span sits under a distributed agent, `resolveAgentAttribution` inherits the propagated
|
|
156
|
+
// id/name already on the parent's registry entry, so the chain survives multiple hops. Resolved
|
|
157
|
+
// after the bail-out above so we don't allocate when there is nothing to inject.
|
|
158
|
+
const { name: parentAgentName, spanId: parentAgentSpanId } = resolveAgentAttribution(
|
|
159
|
+
mlObsSpanTags, parent
|
|
160
|
+
)
|
|
161
|
+
|
|
150
162
|
// `_injectTags` only writes `x-datadog-tags` when the trace has `_dd.p.*`
|
|
151
163
|
// tags, so it may be undefined here — coalesce before appending.
|
|
152
|
-
const existing = carrier
|
|
164
|
+
const existing = readDatadogTags(carrier)
|
|
153
165
|
let tags = existing || ''
|
|
154
166
|
if (parentId) tags += `${tags ? ',' : ''}${PROPAGATED_PARENT_ID_KEY}=${parentId}`
|
|
155
167
|
if (mlApp) tags += `${tags ? ',' : ''}${PROPAGATED_ML_APP_KEY}=${mlApp}`
|
|
@@ -157,7 +169,27 @@ function handleLLMObsInjection ({ carrier }) {
|
|
|
157
169
|
if (sampleRate != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
|
|
158
170
|
if (samplingDecision != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
|
|
159
171
|
if (propagatedTraceId != null) tags += `${tags ? ',' : ''}${PROPAGATED_TRACE_ID_KEY}=${propagatedTraceId}`
|
|
160
|
-
|
|
172
|
+
// When a local agent attribution is resolved, strip any stale upstream pagent entries that
|
|
173
|
+
// `_injectTags` may have already written into the carrier (it propagates all `_dd.p.*` from
|
|
174
|
+
// `_trace.tags`). This ensures the downstream sees a consistent id-only or id+name pair
|
|
175
|
+
// rather than a mix from different hops. The id is always digit-safe; an unsafe name is wiped.
|
|
176
|
+
if (parentAgentSpanId) {
|
|
177
|
+
tags = stripTagsetEntry(tags, PROPAGATED_PARENT_AGENT_ID_KEY)
|
|
178
|
+
tags = stripTagsetEntry(tags, PROPAGATED_PARENT_AGENT_NAME_KEY)
|
|
179
|
+
}
|
|
180
|
+
const maxLength = globalTracerConfig.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH
|
|
181
|
+
const tagsWithId = appendOptionalPropagatedTag(
|
|
182
|
+
tags, PROPAGATED_PARENT_AGENT_ID_KEY, parentAgentSpanId, null, maxLength
|
|
183
|
+
)
|
|
184
|
+
// Only append the name when the id fit: a name without an id is unresolvable by the backend.
|
|
185
|
+
if (tagsWithId === tags) {
|
|
186
|
+
tags = tagsWithId
|
|
187
|
+
} else {
|
|
188
|
+
tags = appendOptionalPropagatedTag(
|
|
189
|
+
tagsWithId, PROPAGATED_PARENT_AGENT_NAME_KEY, parentAgentName, agentNameWireSafe, maxLength
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
if (tags !== existing) writeDatadogTags(carrier, tags)
|
|
161
193
|
}
|
|
162
194
|
|
|
163
195
|
function handleFlush () {
|
|
@@ -228,6 +228,7 @@ function getJsonStringValue (str, defaultValue) {
|
|
|
228
228
|
function getModelMetadata (tags) {
|
|
229
229
|
/** @type {Record<string, unknown>} */
|
|
230
230
|
const modelMetadata = {}
|
|
231
|
+
let hasModelMetadata = false
|
|
231
232
|
for (const tag of Object.keys(tags)) {
|
|
232
233
|
const isModelMetadata = tag.startsWith(VERCEL_AI_MODEL_METADATA_PREFIX)
|
|
233
234
|
if (isModelMetadata) {
|
|
@@ -235,6 +236,7 @@ function getModelMetadata (tags) {
|
|
|
235
236
|
const metadataKey = lastCommaPosition === -1 ? tag : tag.slice(lastCommaPosition + 1)
|
|
236
237
|
if (metadataKey && MODEL_METADATA_KEYS.has(metadataKey)) {
|
|
237
238
|
modelMetadata[metadataKey] = tags[tag]
|
|
239
|
+
hasModelMetadata = true
|
|
238
240
|
}
|
|
239
241
|
} else {
|
|
240
242
|
const isTelemetryMetadata = tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)
|
|
@@ -242,12 +244,13 @@ function getModelMetadata (tags) {
|
|
|
242
244
|
const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
|
|
243
245
|
if (metadataKey) {
|
|
244
246
|
modelMetadata[metadataKey] = tags[tag]
|
|
247
|
+
hasModelMetadata = true
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
252
|
|
|
250
|
-
return
|
|
253
|
+
return hasModelMetadata ? modelMetadata : null
|
|
251
254
|
}
|
|
252
255
|
|
|
253
256
|
/**
|
|
@@ -259,6 +262,7 @@ function getModelMetadata (tags) {
|
|
|
259
262
|
function getGenerationMetadata (tags) {
|
|
260
263
|
/** @type {Record<string, unknown>} */
|
|
261
264
|
const metadata = {}
|
|
265
|
+
let hasMetadata = false
|
|
262
266
|
|
|
263
267
|
for (const tag of Object.keys(tags)) {
|
|
264
268
|
const isGenerationMetadata = tag.startsWith(VERCEL_AI_GENERATION_METADATA_PREFIX)
|
|
@@ -270,18 +274,20 @@ function getGenerationMetadata (tags) {
|
|
|
270
274
|
|
|
271
275
|
const settingValue = tags[tag]
|
|
272
276
|
metadata[settingKey] = settingValue
|
|
277
|
+
hasMetadata = true
|
|
273
278
|
} else {
|
|
274
279
|
const isTelemetryMetadata = tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)
|
|
275
280
|
if (isTelemetryMetadata) {
|
|
276
281
|
const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
|
|
277
282
|
if (metadataKey) {
|
|
278
283
|
metadata[metadataKey] = tags[tag]
|
|
284
|
+
hasMetadata = true
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
}
|
|
282
288
|
}
|
|
283
289
|
|
|
284
|
-
return
|
|
290
|
+
return hasMetadata ? metadata : null
|
|
285
291
|
}
|
|
286
292
|
|
|
287
293
|
/**
|
|
@@ -307,6 +313,7 @@ function getGenerationMetadataFromEvent (event) {
|
|
|
307
313
|
metadata[transformedKey] = value
|
|
308
314
|
}
|
|
309
315
|
|
|
316
|
+
// eslint-disable-next-line no-restricted-syntax -- manual tracking would duplicate Object.assign semantics
|
|
310
317
|
return Object.keys(metadata).length ? metadata : null
|
|
311
318
|
}
|
|
312
319
|
|
|
@@ -432,6 +439,7 @@ function getLlmObsSpanName (operation, functionId) {
|
|
|
432
439
|
*/
|
|
433
440
|
function getTelemetryMetadata (tags) {
|
|
434
441
|
const metadata = {}
|
|
442
|
+
let hasMetadata = false
|
|
435
443
|
|
|
436
444
|
for (const tag of Object.keys(tags)) {
|
|
437
445
|
if (!tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)) continue
|
|
@@ -439,10 +447,11 @@ function getTelemetryMetadata (tags) {
|
|
|
439
447
|
const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
|
|
440
448
|
if (metadataKey) {
|
|
441
449
|
metadata[metadataKey] = tags[tag]
|
|
450
|
+
hasMetadata = true
|
|
442
451
|
}
|
|
443
452
|
}
|
|
444
453
|
|
|
445
|
-
return
|
|
454
|
+
return hasMetadata ? metadata : null
|
|
446
455
|
}
|
|
447
456
|
|
|
448
457
|
module.exports = {
|
|
@@ -84,9 +84,11 @@ class LLMObsPlugin extends TracingPlugin {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
configure (config) {
|
|
87
|
-
// we do not want to enable any LLMObs plugins if it is disabled on the tracer
|
|
87
|
+
// we do not want to enable any LLMObs plugins if it is disabled on the tracer, or if the
|
|
88
|
+
// integration opted out via `tracer.use(<name>, { llmobs: false })`. Opting out only disables
|
|
89
|
+
// the LLMObs layer: the integration keeps emitting APM spans and propagating trace context.
|
|
88
90
|
const llmobsEnabled = this._tracerConfig.llmobs.DD_LLMOBS_ENABLED
|
|
89
|
-
if (llmobsEnabled === false) {
|
|
91
|
+
if (llmobsEnabled === false || config?.llmobs === false) {
|
|
90
92
|
config = typeof config === 'boolean' ? false : { ...config, enabled: false } // override to false
|
|
91
93
|
}
|
|
92
94
|
super.configure(config)
|
|
@@ -27,6 +27,8 @@ const {
|
|
|
27
27
|
ML_APP,
|
|
28
28
|
TAGS,
|
|
29
29
|
PARENT_ID_KEY,
|
|
30
|
+
PARENT_AGENT_NAME,
|
|
31
|
+
PARENT_AGENT_SPAN_ID,
|
|
30
32
|
SESSION_ID,
|
|
31
33
|
NAME,
|
|
32
34
|
INPUT_PROMPT,
|
|
@@ -157,6 +159,20 @@ class LLMObsSpanProcessor {
|
|
|
157
159
|
this.#addObject(mlObsTags[TOOL_DEFINITIONS], meta.tool_definitions)
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
// Surface the agent attribution resolved at registration, but only on spans that actually
|
|
163
|
+
// have an agent ancestor. The id is always present in that case; the name may be missing when
|
|
164
|
+
// it arrived id-only over distributed propagation (older/unsafe upstream). Emit the name as
|
|
165
|
+
// explicit `null` then, matching the cross-language wire shape (dd-trace-py sends null, not an
|
|
166
|
+
// absent key) so the shared backend and system-tests see the same payload.
|
|
167
|
+
const parentAgentName = mlObsTags[PARENT_AGENT_NAME]
|
|
168
|
+
const parentAgentSpanId = mlObsTags[PARENT_AGENT_SPAN_ID]
|
|
169
|
+
if (parentAgentName != null || parentAgentSpanId != null) {
|
|
170
|
+
meta.agent_attribution = {
|
|
171
|
+
pagent_name: parentAgentName ?? null,
|
|
172
|
+
pagent_span_id: parentAgentSpanId,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
160
176
|
const llmObsSpan = new LLMObservabilitySpan(spanKind)
|
|
161
177
|
|
|
162
178
|
if (spanKind === 'llm' && mlObsTags[INPUT_MESSAGES]) {
|
|
@@ -23,7 +23,11 @@ const {
|
|
|
23
23
|
OUTPUT_MESSAGES,
|
|
24
24
|
TAGS,
|
|
25
25
|
NAME,
|
|
26
|
+
PARENT_AGENT_NAME,
|
|
27
|
+
PARENT_AGENT_SPAN_ID,
|
|
26
28
|
PROPAGATED_PARENT_ID_KEY,
|
|
29
|
+
PROPAGATED_PARENT_AGENT_ID_KEY,
|
|
30
|
+
PROPAGATED_PARENT_AGENT_NAME_KEY,
|
|
27
31
|
ROOT_PARENT_ID,
|
|
28
32
|
CACHE_READ_INPUT_TOKENS_METRIC_KEY,
|
|
29
33
|
CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
|
|
@@ -57,6 +61,7 @@ const {
|
|
|
57
61
|
const { storage } = require('./storage')
|
|
58
62
|
const {
|
|
59
63
|
findGenAIAncestorSpanId,
|
|
64
|
+
resolveAgentAttribution,
|
|
60
65
|
validateCostTags,
|
|
61
66
|
writeBridgeTags,
|
|
62
67
|
validateToolDefinitions,
|
|
@@ -173,6 +178,7 @@ class LLMObsTagger {
|
|
|
173
178
|
this._setTag(span, PARENT_ID_KEY, parentId)
|
|
174
179
|
|
|
175
180
|
this.#tagSamplingDecision(span, parent)
|
|
181
|
+
this.#tagAgentAttribution(span, parent)
|
|
176
182
|
|
|
177
183
|
// apply annotation context
|
|
178
184
|
const annotationContext = storage.getStore()?.annotationContext
|
|
@@ -229,6 +235,34 @@ class LLMObsTagger {
|
|
|
229
235
|
if (samplingDecision != null) this._setTag(span, SAMPLING_DECISION, samplingDecision)
|
|
230
236
|
}
|
|
231
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Store the nearest agent ancestor on the span so it can be surfaced as
|
|
240
|
+
* `meta.agent_attribution` at finish. Resolved once here, at registration, so downstream
|
|
241
|
+
* children inherit it with a single lookup rather than walking the ancestor chain.
|
|
242
|
+
*
|
|
243
|
+
* @param {import('../opentracing/span')} span
|
|
244
|
+
* @param {import('../opentracing/span')} [parent] the LLMObs parent span, if any
|
|
245
|
+
*/
|
|
246
|
+
// TODO: spans whose kind changes after registration (e.g. claude-agent-sdk tools promoted to
|
|
247
|
+
// sub-agents) will not retroactively update already-finished children's attribution. Follow up.
|
|
248
|
+
#tagAgentAttribution (span, parent) {
|
|
249
|
+
let name, spanId
|
|
250
|
+
if (registry.has(parent)) {
|
|
251
|
+
// Local LLMObs parent: attribute to it if it is an agent, else inherit its resolution.
|
|
252
|
+
({ name, spanId } = resolveAgentAttribution(registry.get(parent), parent))
|
|
253
|
+
} else if (span.context()._trace.tags[PROPAGATED_PARENT_ID_KEY]) {
|
|
254
|
+
// Distributed LLMObs parent: inherit the nearest agent propagated from upstream. The
|
|
255
|
+
// name may be absent when the upstream hop ran an older SDK or its name was not
|
|
256
|
+
// wire-safe; the id-only case is expected and the backend resolves the name by span id.
|
|
257
|
+
const traceTags = span.context()._trace.tags
|
|
258
|
+
name = traceTags[PROPAGATED_PARENT_AGENT_NAME_KEY]
|
|
259
|
+
spanId = traceTags[PROPAGATED_PARENT_AGENT_ID_KEY]
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (name != null) this._setTag(span, PARENT_AGENT_NAME, name)
|
|
263
|
+
if (spanId != null) this._setTag(span, PARENT_AGENT_SPAN_ID, spanId)
|
|
264
|
+
}
|
|
265
|
+
|
|
232
266
|
// TODO: similarly for the following `tag` methods,
|
|
233
267
|
// how can we transition from a span weakmap to core API functionality
|
|
234
268
|
tagLLMIO (span, inputData, outputData) {
|
|
@@ -696,6 +730,59 @@ class LLMObsTagger {
|
|
|
696
730
|
return filteredAudioParts
|
|
697
731
|
}
|
|
698
732
|
|
|
733
|
+
// Image counterpart of #filterAudioParts, with the same wire shape and validation.
|
|
734
|
+
#filterImageParts (imageParts) {
|
|
735
|
+
if (!Array.isArray(imageParts)) {
|
|
736
|
+
imageParts = [imageParts]
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const filteredImageParts = []
|
|
740
|
+
for (const imagePart of imageParts) {
|
|
741
|
+
if (imagePart == null || typeof imagePart !== 'object') {
|
|
742
|
+
this.#handleFailure('Image part must be an object.', 'invalid_io_messages')
|
|
743
|
+
continue
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const { mimeType, content, attachmentKey } = imagePart
|
|
747
|
+
|
|
748
|
+
if (typeof mimeType !== 'string' || !mimeType) {
|
|
749
|
+
this.#handleFailure('Image part mimeType must be a non-empty string.', 'invalid_io_messages')
|
|
750
|
+
continue
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (content == null && attachmentKey == null) {
|
|
754
|
+
this.#handleFailure("Image part must have either 'content' or 'attachmentKey'.", 'invalid_io_messages')
|
|
755
|
+
continue
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (content != null && attachmentKey != null) {
|
|
759
|
+
this.#handleFailure(
|
|
760
|
+
"Image part must have only one of 'content' or 'attachmentKey', not both.", 'invalid_io_messages'
|
|
761
|
+
)
|
|
762
|
+
continue
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const imagePartObj = { mime_type: mimeType }
|
|
766
|
+
|
|
767
|
+
if (content == null) {
|
|
768
|
+
if (typeof attachmentKey !== 'string') {
|
|
769
|
+
this.#handleFailure('Image part attachmentKey must be a string.', 'invalid_io_messages')
|
|
770
|
+
continue
|
|
771
|
+
}
|
|
772
|
+
imagePartObj.attachment_key = attachmentKey
|
|
773
|
+
} else {
|
|
774
|
+
if (typeof content !== 'string') {
|
|
775
|
+
this.#handleFailure('Image part content must be a base64-encoded string.', 'invalid_io_messages')
|
|
776
|
+
continue
|
|
777
|
+
}
|
|
778
|
+
imagePartObj.content = content
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
filteredImageParts.push(imagePartObj)
|
|
782
|
+
}
|
|
783
|
+
return filteredImageParts
|
|
784
|
+
}
|
|
785
|
+
|
|
699
786
|
#tagMessages (span, data, key) {
|
|
700
787
|
if (!data) {
|
|
701
788
|
return
|
|
@@ -723,6 +810,7 @@ class LLMObsTagger {
|
|
|
723
810
|
toolResults,
|
|
724
811
|
toolId,
|
|
725
812
|
audioParts,
|
|
813
|
+
imageParts,
|
|
726
814
|
} = message
|
|
727
815
|
const messageObj = {}
|
|
728
816
|
|
|
@@ -764,6 +852,14 @@ class LLMObsTagger {
|
|
|
764
852
|
}
|
|
765
853
|
}
|
|
766
854
|
|
|
855
|
+
if (imageParts != null) {
|
|
856
|
+
const filteredImageParts = this.#filterImageParts(imageParts)
|
|
857
|
+
|
|
858
|
+
if (filteredImageParts.length) {
|
|
859
|
+
messageObj.image_parts = filteredImageParts
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
767
863
|
if (toolId) {
|
|
768
864
|
if (role === 'tool') {
|
|
769
865
|
condition = this.#tagConditionalString(toolId, 'Tool ID', messageObj, 'tool_id') && condition
|
|
@@ -6,6 +6,10 @@ const {
|
|
|
6
6
|
LLMOBS_PARENT_ID_BRIDGE_KEY,
|
|
7
7
|
LLMOBS_TRACE_ID_BRIDGE_KEY,
|
|
8
8
|
SPAN_KINDS,
|
|
9
|
+
SPAN_KIND,
|
|
10
|
+
NAME,
|
|
11
|
+
PARENT_AGENT_NAME,
|
|
12
|
+
PARENT_AGENT_SPAN_ID,
|
|
9
13
|
} = require('./constants/tags')
|
|
10
14
|
|
|
11
15
|
const DECIMAL_TRACE_ID_REGEX = /^\d+$/
|
|
@@ -294,6 +298,76 @@ function getFunctionArguments (fn, args = []) {
|
|
|
294
298
|
}
|
|
295
299
|
}
|
|
296
300
|
|
|
301
|
+
// The `x-datadog-tags` tagset encoder rejects commas (the entry delimiter) and any byte
|
|
302
|
+
// outside 0x20-0x7E, and a raise there drops the ENTIRE header (taking ml_app,
|
|
303
|
+
// llmobs_trace_id, parent_id, sampling with it). An agent name is arbitrary user text, so it
|
|
304
|
+
// must be skipped rather than sanitized when unsafe: only the digit-safe id then propagates and
|
|
305
|
+
// the backend resolves the real name by span id. `=` is legal in tagset values (illegal only in
|
|
306
|
+
// keys), so a name containing `=` is safe and must not be dropped.
|
|
307
|
+
/**
|
|
308
|
+
* @param {string} name
|
|
309
|
+
* @returns {boolean}
|
|
310
|
+
*/
|
|
311
|
+
function agentNameWireSafe (name) {
|
|
312
|
+
// Conservative slice of the 512B shared tagset budget, mirroring dd-trace-py.
|
|
313
|
+
if (Buffer.byteLength(name, 'utf8') > 256) return false
|
|
314
|
+
for (let index = 0; index < name.length; index++) {
|
|
315
|
+
const code = name.charCodeAt(index)
|
|
316
|
+
if (code < 0x20 || code > 0x7E || code === 0x2C /* comma */) return false
|
|
317
|
+
}
|
|
318
|
+
return true
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Resolve the nearest agent that a *child* of `span` should be attributed to.
|
|
323
|
+
*
|
|
324
|
+
* If `span` is itself an agent, the child attributes directly to `span`. Otherwise `span`
|
|
325
|
+
* already resolved its own nearest agent at registration, so the child inherits that with a
|
|
326
|
+
* single lookup rather than walking the ancestor chain. An agent never attributes itself.
|
|
327
|
+
*
|
|
328
|
+
* @param {Record<string, unknown> | undefined} tags - Registry entry for the parent span.
|
|
329
|
+
* @param {import('../opentracing/span')} [span] - The parent span itself (needed for span id / name).
|
|
330
|
+
* @returns {{ name: string | undefined, spanId: string | undefined }}
|
|
331
|
+
*/
|
|
332
|
+
function resolveAgentAttribution (tags, span) {
|
|
333
|
+
if (!tags) return { name: undefined, spanId: undefined }
|
|
334
|
+
if (tags[SPAN_KIND] === 'agent') {
|
|
335
|
+
return { name: tags[NAME] || span._name, spanId: span.context().toSpanId() }
|
|
336
|
+
}
|
|
337
|
+
return { name: tags[PARENT_AGENT_NAME], spanId: tags[PARENT_AGENT_SPAN_ID] }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Removes all `key=value` entries for the given key from a comma-separated tagset string.
|
|
342
|
+
*
|
|
343
|
+
* @param {string} tags - Existing tagset string (may be empty).
|
|
344
|
+
* @param {string} key
|
|
345
|
+
* @returns {string}
|
|
346
|
+
*/
|
|
347
|
+
function stripTagsetEntry (tags, key) {
|
|
348
|
+
if (!tags.includes(key)) return tags
|
|
349
|
+
return tags.split(',').filter(entry => !entry.startsWith(`${key}=`)).join(',')
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Appends `key=value` to the tagset string with a comma separator, but only when `value` is
|
|
354
|
+
* truthy, passes the optional `safeguard` predicate, and fits within `maxTagSetLength`. Returns
|
|
355
|
+
* the original `tags` unchanged when the value is absent, unsafe, or would overflow the budget.
|
|
356
|
+
*
|
|
357
|
+
* @param {string} tags - Existing tagset string (may be empty).
|
|
358
|
+
* @param {string} key
|
|
359
|
+
* @param {string | undefined} value
|
|
360
|
+
* @param {((v: string) => boolean) | null} [safeguard]
|
|
361
|
+
* @param {number} [maxTagSetLength]
|
|
362
|
+
* @returns {string}
|
|
363
|
+
*/
|
|
364
|
+
function appendOptionalPropagatedTag (tags, key, value, safeguard, maxTagSetLength) {
|
|
365
|
+
if (!value || (safeguard && !safeguard(value))) return tags
|
|
366
|
+
const entry = `${tags ? ',' : ''}${key}=${value}`
|
|
367
|
+
if (maxTagSetLength != null && tags.length + entry.length > maxTagSetLength) return tags
|
|
368
|
+
return `${tags}${entry}`
|
|
369
|
+
}
|
|
370
|
+
|
|
297
371
|
function spanHasError (span) {
|
|
298
372
|
const spanContext = span.context()
|
|
299
373
|
return !!(spanContext.getTag('error') || spanContext.getTag('error.type'))
|
|
@@ -443,6 +517,8 @@ function formatAudioPart (data, mimeType) {
|
|
|
443
517
|
}
|
|
444
518
|
|
|
445
519
|
module.exports = {
|
|
520
|
+
agentNameWireSafe,
|
|
521
|
+
appendOptionalPropagatedTag,
|
|
446
522
|
audioMimeTypeFromFormat,
|
|
447
523
|
encodeUnicode,
|
|
448
524
|
findGenAIAncestorSpanId,
|
|
@@ -450,6 +526,8 @@ module.exports = {
|
|
|
450
526
|
llmObsTraceIdToWire,
|
|
451
527
|
normalizeLlmObsTraceId,
|
|
452
528
|
formatAudioPart,
|
|
529
|
+
resolveAgentAttribution,
|
|
530
|
+
stripTagsetEntry,
|
|
453
531
|
validateCostTags,
|
|
454
532
|
validateKind,
|
|
455
533
|
getFunctionArguments,
|