dd-trace 5.132.0 → 5.133.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 +9 -14
- package/ci/init.js +1 -2
- package/ci/test-optimization-validation/ci-discovery.js +9 -4
- package/ci/test-optimization-validation/executable.js +1 -5
- package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
- package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
- package/ci/test-optimization-validation/generated-files.js +6 -4
- package/ci/test-optimization-validation/generated-test-contract.js +7 -3
- package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
- package/ci/test-optimization-validation/manifest-schema.js +6 -7
- package/ci/test-optimization-validation/report-writer.js +6 -2
- package/ci/test-optimization-validation/runner-command.js +2 -4
- package/ci/test-optimization-validation/runner-contract.js +1 -2
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
- package/ci/vitest-no-worker-init-setup.mjs +10 -7
- package/index.d.ts +25 -14
- package/initialize.mjs +1 -3
- package/openfeature/rules-based.d.ts +11 -0
- package/openfeature/rules-based.js +7 -0
- package/package.json +12 -10
- package/packages/datadog-core/src/storage.js +16 -0
- package/packages/datadog-esbuild/index.js +18 -12
- package/packages/datadog-instrumentations/src/aerospike.js +3 -3
- package/packages/datadog-instrumentations/src/ai.js +3 -3
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
- package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
- package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
- package/packages/datadog-instrumentations/src/cucumber.js +48 -33
- package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
- package/packages/datadog-instrumentations/src/electron.js +6 -6
- package/packages/datadog-instrumentations/src/fastify.js +13 -4
- package/packages/datadog-instrumentations/src/fs.js +2 -2
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
- package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +7 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
- package/packages/datadog-instrumentations/src/ioredis.js +7 -5
- package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
- package/packages/datadog-instrumentations/src/jest.js +3 -10
- package/packages/datadog-instrumentations/src/knex.js +9 -5
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
- package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
- package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
- package/packages/datadog-instrumentations/src/mocha/utils.js +3 -2
- package/packages/datadog-instrumentations/src/mocha/worker.js +1 -2
- package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
- package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
- package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
- package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
- package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
- package/packages/datadog-instrumentations/src/openai.js +7 -0
- package/packages/datadog-instrumentations/src/pino.js +5 -6
- package/packages/datadog-instrumentations/src/redis.js +27 -13
- package/packages/datadog-instrumentations/src/router.js +30 -0
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
- package/packages/datadog-instrumentations/src/vitest-main.js +9 -15
- package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
- package/packages/datadog-instrumentations/src/vitest.js +2 -2
- package/packages/datadog-instrumentations/src/webdriverio.js +5 -8
- package/packages/datadog-instrumentations/src/ws.js +1 -4
- package/packages/datadog-plugin-amqplib/src/client.js +1 -2
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -5
- package/packages/datadog-plugin-cypress/src/index.js +6 -6
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
- package/packages/datadog-plugin-cypress/src/support.js +1 -4
- package/packages/datadog-plugin-express/src/code_origin.js +1 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
- package/packages/datadog-plugin-graphql/src/execute.js +5 -5
- package/packages/datadog-plugin-grpc/src/util.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
- package/packages/datadog-plugin-mocha/src/index.js +7 -10
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +8 -4
- package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
- package/packages/datadog-plugin-next/src/index.js +1 -2
- package/packages/datadog-plugin-openai/src/tracing.js +5 -2
- package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
- package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
- package/packages/datadog-plugin-postgres/src/index.js +24 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
- package/packages/datadog-plugin-vitest/src/index.js +5 -2
- package/packages/datadog-shimmer/src/shimmer.js +5 -4
- package/packages/datadog-webpack/index.js +55 -43
- package/packages/datadog-webpack/src/loader.js +56 -8
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
- package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
- package/packages/dd-trace/src/aiguard/redaction.js +8 -2
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
- package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
- package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
- package/packages/dd-trace/src/appsec/lambda.js +1 -3
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
- package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
- package/packages/dd-trace/src/appsec/reporter.js +21 -1
- package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
- package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
- package/packages/dd-trace/src/config/config-types.d.ts +2 -2
- package/packages/dd-trace/src/config/defaults.js +5 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
- package/packages/dd-trace/src/config/helper.js +34 -3
- package/packages/dd-trace/src/config/index.js +10 -9
- package/packages/dd-trace/src/config/normalize-service.js +1 -1
- package/packages/dd-trace/src/config/parsers.js +2 -2
- package/packages/dd-trace/src/config/supported-configurations.json +35 -0
- package/packages/dd-trace/src/datastreams/processor.js +3 -2
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
- package/packages/dd-trace/src/dogstatsd.js +1 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -5
- package/packages/dd-trace/src/id.js +14 -1
- package/packages/dd-trace/src/lambda/handler.js +179 -19
- package/packages/dd-trace/src/lambda/index.js +15 -1
- package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
- package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +12 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
- package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +1 -2
- package/packages/dd-trace/src/llmobs/tagger.js +2 -2
- package/packages/dd-trace/src/llmobs/util.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/base.js +4 -5
- package/packages/dd-trace/src/log/log.js +1 -5
- package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +48 -21
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
- package/packages/dd-trace/src/otel-sampling.js +4 -5
- package/packages/dd-trace/src/otel-thread-ctx.js +79 -13
- package/packages/dd-trace/src/plugins/database.js +7 -16
- package/packages/dd-trace/src/plugins/util/git.js +9 -25
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
- package/packages/dd-trace/src/plugins/util/test.js +48 -4
- package/packages/dd-trace/src/priority_sampler.js +1 -2
- package/packages/dd-trace/src/profiling/profiler.js +4 -6
- package/packages/dd-trace/src/proxy.js +1 -2
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +5 -0
- package/packages/dd-trace/src/span_processor.js +13 -17
- package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
- package/packages/dd-trace/src/util.js +29 -0
- package/packages/dd-trace/src/web-tags-cache.js +6 -2
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* atomics, and the main thread periodically drains the counters into instrumentation telemetry metrics.
|
|
10
10
|
*
|
|
11
11
|
* The metric names, tags and reason codes are defined by the "Debugger Observability for GA" specification and are
|
|
12
|
-
* shared with the other tracers. All metrics are counts in the `
|
|
12
|
+
* shared with the other tracers. All metrics are counts in the `debugger` telemetry namespace.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const TELEMETRY_NAMESPACE = '
|
|
15
|
+
const TELEMETRY_NAMESPACE = 'debugger'
|
|
16
16
|
|
|
17
17
|
const EVENT_TYPE = Object.freeze({
|
|
18
18
|
SNAPSHOT: 0,
|
|
@@ -533,7 +533,7 @@ class DogStatsDClient {
|
|
|
533
533
|
for (const [key, value] of Object.entries(config.tags)) {
|
|
534
534
|
// Skip runtime-id unless enabled as cardinality may be too high
|
|
535
535
|
if (typeof value === 'string' && (key !== 'runtime-id' || config.runtimeMetricsRuntimeId)) {
|
|
536
|
-
// https://docs.datadoghq.com/tagging/#
|
|
536
|
+
// https://docs.datadoghq.com/getting_started/tagging/#define-tags
|
|
537
537
|
const valueStripped = value.replaceAll(/[^a-z0-9_:./-]/ig, '_')
|
|
538
538
|
|
|
539
539
|
tags.push(`${key}:${valueStripped}`)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// From agent truncators: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/agent/truncator.go
|
|
4
4
|
|
|
5
|
-
// Values from: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/truncate.go
|
|
5
|
+
// Values from: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize/truncate.go
|
|
6
6
|
// MAX_RESOURCE_NAME_LENGTH the maximum length a span resource can have
|
|
7
7
|
const MAX_RESOURCE_NAME_LENGTH = 5000
|
|
8
8
|
// MAX_META_KEY_LENGTH the maximum length of metadata key
|
|
@@ -15,7 +15,7 @@ const MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION = 5000
|
|
|
15
15
|
const MAX_METRIC_KEY_LENGTH = MAX_META_KEY_LENGTH
|
|
16
16
|
|
|
17
17
|
// From agent normalizer:
|
|
18
|
-
// https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize.go
|
|
18
|
+
// https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize/normalize.go
|
|
19
19
|
// DEFAULT_SPAN_NAME is the default name we assign a span if it's missing and we have no reasonable fallback
|
|
20
20
|
const DEFAULT_SPAN_NAME = 'unnamed_operation'
|
|
21
21
|
// DEFAULT_SERVICE_NAME is the default name we assign a service if it's missing and we have no reasonable fallback
|
|
@@ -86,11 +86,7 @@ function request (data, options, callback) {
|
|
|
86
86
|
const timeout = options.timeout || 2000
|
|
87
87
|
const isSecure = options.protocol === 'https:'
|
|
88
88
|
const client = isSecure ? https : http
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (!Array.isArray(data)) {
|
|
92
|
-
dataArray = [data]
|
|
93
|
-
}
|
|
89
|
+
const dataArray = Array.isArray(data) ? data : [data]
|
|
94
90
|
const contentLength = byteLength(dataArray)
|
|
95
91
|
options.headers['Content-Length'] = contentLength
|
|
96
92
|
|
|
@@ -6,6 +6,8 @@ const UINT_MAX = 4_294_967_296
|
|
|
6
6
|
|
|
7
7
|
const data = new Uint8Array(8 * 8192)
|
|
8
8
|
const zeroId = new Uint8Array(8)
|
|
9
|
+
// Reused for hex encoding: toString('hex') copies out, so the bytes need not outlive the call.
|
|
10
|
+
const hexScratch = Buffer.alloc(16)
|
|
9
11
|
|
|
10
12
|
let batch = 0
|
|
11
13
|
|
|
@@ -35,7 +37,7 @@ class Identifier {
|
|
|
35
37
|
*/
|
|
36
38
|
toString (radix = 16) {
|
|
37
39
|
if (radix === 16) {
|
|
38
|
-
this.#stringHex ??=
|
|
40
|
+
this.#stringHex ??= toHexString(this.#buffer)
|
|
39
41
|
return this.#stringHex
|
|
40
42
|
}
|
|
41
43
|
if (radix === 10) {
|
|
@@ -190,6 +192,17 @@ function toNumberString (buffer, radix) {
|
|
|
190
192
|
return str
|
|
191
193
|
}
|
|
192
194
|
|
|
195
|
+
// Same string as Buffer.from(buffer).toString('hex'), without allocating a Buffer per call.
|
|
196
|
+
/**
|
|
197
|
+
* @param {number[] | Uint8Array} buffer
|
|
198
|
+
*/
|
|
199
|
+
function toHexString (buffer) {
|
|
200
|
+
const length = buffer.length
|
|
201
|
+
if (length > hexScratch.length) return Buffer.from(buffer).toString('hex')
|
|
202
|
+
for (let i = 0; i < length; i++) hexScratch[i] = buffer[i]
|
|
203
|
+
return hexScratch.toString('hex', 0, length)
|
|
204
|
+
}
|
|
205
|
+
|
|
193
206
|
// Simple pseudo-random 64-bit ID generator.
|
|
194
207
|
/**
|
|
195
208
|
* @returns {number[] | Uint8Array}
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const { types: { isProxy } } = require('node:util')
|
|
4
|
+
|
|
4
5
|
const { channel } = require('../../../datadog-instrumentations/src/helpers/instrument')
|
|
5
6
|
const { ERROR_MESSAGE, ERROR_TYPE } = require('../constants')
|
|
7
|
+
const log = require('../log')
|
|
6
8
|
const { ImpendingTimeout } = require('./runtime/errors')
|
|
7
9
|
const { extractContext } = require('./context')
|
|
8
10
|
|
|
11
|
+
const HANDLER_STREAMING = Symbol.for('aws.lambda.runtime.handler.streaming')
|
|
12
|
+
const HANDLER_HIGHWATERMARK = Symbol.for('aws.lambda.runtime.handler.streaming.highWaterMark')
|
|
13
|
+
const BEFORE_EXIT = Symbol.for('aws.lambda.beforeExit')
|
|
9
14
|
const timeoutChannel = channel('apm:aws:lambda:timeout')
|
|
15
|
+
const monitoredContexts = new WeakSet()
|
|
10
16
|
// Always crash the flushes when a message is received
|
|
11
17
|
// from this channel.
|
|
12
18
|
timeoutChannel.subscribe(_ => {
|
|
13
19
|
crashFlush()
|
|
14
20
|
})
|
|
15
21
|
|
|
16
|
-
let __lambdaTimeout
|
|
17
|
-
|
|
18
22
|
/**
|
|
19
23
|
* Publishes to the `apm:aws:lambda:timeout` channel when
|
|
20
24
|
* the AWS Lambda run time is about to end.
|
|
@@ -23,11 +27,15 @@ let __lambdaTimeout
|
|
|
23
27
|
*/
|
|
24
28
|
function checkTimeout (context) {
|
|
25
29
|
const remainingTimeInMillis = context.getRemainingTimeInMillis()
|
|
26
|
-
const apmFlushDeadline = global._ddtrace
|
|
30
|
+
const apmFlushDeadline = global._ddtrace?._tracer?._config?.DD_APM_FLUSH_DEADLINE_MILLISECONDS
|
|
31
|
+
if (!Number.isFinite(remainingTimeInMillis) || !Number.isFinite(apmFlushDeadline)) return
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
const timeout = setTimeout(() => {
|
|
29
34
|
timeoutChannel.publish()
|
|
30
|
-
}, remainingTimeInMillis - apmFlushDeadline)
|
|
35
|
+
}, Math.max(0, remainingTimeInMillis - apmFlushDeadline))
|
|
36
|
+
// eslint-disable-next-line eslint-rules/eslint-timer-unref -- AWS Lambda uses Node.js timers.
|
|
37
|
+
timeout.unref()
|
|
38
|
+
return timeout
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
/**
|
|
@@ -58,26 +66,178 @@ function crashFlush () {
|
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
/**
|
|
61
|
-
*
|
|
69
|
+
* Monitors an AWS Lambda handler for impending timeouts without creating a span.
|
|
62
70
|
*
|
|
63
71
|
* @param {Function} lambdaHandler a Lambda handler function.
|
|
72
|
+
* @param {boolean} normalized whether datadog-lambda-js owns completion selection.
|
|
64
73
|
*/
|
|
65
|
-
exports.datadog = function datadog (lambdaHandler) {
|
|
66
|
-
|
|
74
|
+
exports.datadog = function datadog (lambdaHandler, normalized = false) {
|
|
75
|
+
function monitored (...args) {
|
|
67
76
|
const context = extractContext(args)
|
|
77
|
+
if (!context || monitoredContexts.has(context)) return Reflect.apply(lambdaHandler, this, args)
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
const timeout = checkTimeout(context)
|
|
80
|
+
if (timeout === undefined) return Reflect.apply(lambdaHandler, this, args)
|
|
81
|
+
|
|
82
|
+
let completed = false
|
|
83
|
+
let callbackPending = false
|
|
84
|
+
/** @type {Array<[object, string | symbol, Function, Function]>} */
|
|
85
|
+
const completionMethods = []
|
|
86
|
+
const clear = () => {
|
|
87
|
+
if (completed) return
|
|
88
|
+
completed = true
|
|
89
|
+
clearTimeout(timeout)
|
|
90
|
+
for (const [target, name, original, wrapped] of completionMethods) {
|
|
91
|
+
try {
|
|
92
|
+
if (target[name] === wrapped) Reflect.set(target, name, original)
|
|
93
|
+
} catch (error) {
|
|
94
|
+
log.error('Error restoring Lambda completion %s: %s', name, error)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const clearOnHandlerCompletion = normalized
|
|
99
|
+
? clear
|
|
100
|
+
: () => {
|
|
101
|
+
// Once a native callback selects event-loop drain, later handler signals are ignored by RIC.
|
|
102
|
+
if (!callbackPending) clear()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const contextIndex = args.indexOf(context)
|
|
106
|
+
const callbackObservable = contextIndex !== 2 && typeof args[2] === 'function'
|
|
107
|
+
if (!normalized) {
|
|
108
|
+
for (const name of ['done', 'succeed', 'fail']) {
|
|
109
|
+
wrapCompletion(context, name, clearOnHandlerCompletion, completionMethods)
|
|
110
|
+
}
|
|
71
111
|
}
|
|
112
|
+
// Legacy Lambda runtimes complete idle invocations through this callback instead of the handler's callback.
|
|
113
|
+
wrapCompletion(globalThis, BEFORE_EXIT, clear, completionMethods)
|
|
72
114
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
115
|
+
if (callbackObservable) {
|
|
116
|
+
const callback = args[2]
|
|
117
|
+
const beforeExitObservable = completionMethods.at(-1)?.[1] === BEFORE_EXIT
|
|
118
|
+
let callbackInvoked = false
|
|
119
|
+
args[2] = function (...callbackArgs) {
|
|
120
|
+
if (completed || callbackInvoked) return Reflect.apply(callback, this, callbackArgs)
|
|
121
|
+
callbackInvoked = true
|
|
122
|
+
let beforeExit
|
|
123
|
+
try {
|
|
124
|
+
if (beforeExitObservable && callbackArgs[0] == null && context.callbackWaitsForEmptyEventLoop) {
|
|
125
|
+
beforeExit = getDataProperty(globalThis, BEFORE_EXIT)
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
log.error('Error reading Lambda callback completion: %s', error)
|
|
129
|
+
}
|
|
130
|
+
if (typeof beforeExit !== 'function') clear()
|
|
131
|
+
try {
|
|
132
|
+
const result = Reflect.apply(callback, this, callbackArgs)
|
|
133
|
+
if (!completed) {
|
|
134
|
+
// A successful legacy callback installs a new listener while waiting for event-loop drain.
|
|
135
|
+
const replacement = getDataProperty(globalThis, BEFORE_EXIT)
|
|
136
|
+
if (typeof replacement !== 'function' || replacement === beforeExit ||
|
|
137
|
+
!wrapCompletion(globalThis, BEFORE_EXIT, clear, completionMethods)) clear()
|
|
138
|
+
else callbackPending = true
|
|
139
|
+
}
|
|
140
|
+
return result
|
|
141
|
+
} catch (error) {
|
|
142
|
+
clear()
|
|
143
|
+
throw error
|
|
144
|
+
}
|
|
145
|
+
}
|
|
79
146
|
}
|
|
80
|
-
|
|
81
|
-
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// The outer completion adapter owns this context during synchronous dispatch to nested wrappers.
|
|
150
|
+
monitoredContexts.add(context)
|
|
151
|
+
const result = Reflect.apply(lambdaHandler, this, args)
|
|
152
|
+
if (result && typeof result.then === 'function') {
|
|
153
|
+
return result.then(value => {
|
|
154
|
+
clearOnHandlerCompletion()
|
|
155
|
+
return value
|
|
156
|
+
}, error => {
|
|
157
|
+
clearOnHandlerCompletion()
|
|
158
|
+
throw error
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
// Legacy RIC supplies a callback; newer runtimes complete from the return value.
|
|
162
|
+
if (!callbackObservable) clear()
|
|
163
|
+
return result
|
|
164
|
+
} catch (error) {
|
|
165
|
+
clearOnHandlerCompletion()
|
|
166
|
+
throw error
|
|
167
|
+
} finally {
|
|
168
|
+
monitoredContexts.delete(context)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (normalized) return monitored
|
|
173
|
+
|
|
174
|
+
// datadog-lambda-js uses arity and the runtime's streaming metadata to choose how to invoke the handler.
|
|
175
|
+
Object.defineProperty(monitored, 'length', { value: getDataProperty(lambdaHandler, 'length') ?? 0 })
|
|
176
|
+
for (const name of [HANDLER_STREAMING, HANDLER_HIGHWATERMARK, '_ddWrapped']) {
|
|
177
|
+
const value = getDataProperty(lambdaHandler, name)
|
|
178
|
+
if (value !== undefined) monitored[name] = value
|
|
179
|
+
}
|
|
180
|
+
if (!isProxy(lambdaHandler)) {
|
|
181
|
+
for (const name of Reflect.ownKeys(lambdaHandler)) {
|
|
182
|
+
if (name === 'arguments' || name === 'caller') continue
|
|
183
|
+
const descriptor = Object.getOwnPropertyDescriptor(lambdaHandler, name)
|
|
184
|
+
if (!Object.hasOwn(descriptor, 'value')) continue
|
|
185
|
+
const existing = Object.getOwnPropertyDescriptor(monitored, name)
|
|
186
|
+
if (existing?.configurable === false) {
|
|
187
|
+
// Intrinsic function slots such as prototype cannot change their descriptor shape.
|
|
188
|
+
if (existing.writable) {
|
|
189
|
+
Object.defineProperty(monitored, name, { value: descriptor.value, writable: descriptor.writable })
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
Object.defineProperty(monitored, name, descriptor)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return monitored
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @param {object} target
|
|
201
|
+
* @param {string | symbol} name
|
|
202
|
+
* @param {() => void} clear
|
|
203
|
+
* @param {Array<[object, string | symbol, Function, Function]>} completionMethods
|
|
204
|
+
*/
|
|
205
|
+
function wrapCompletion (target, name, clear, completionMethods) {
|
|
206
|
+
try {
|
|
207
|
+
const original = target[name]
|
|
208
|
+
if (typeof original !== 'function') return true
|
|
209
|
+
const wrapped = onCompletion(original, clear)
|
|
210
|
+
if (!Reflect.set(target, name, wrapped) || target[name] !== wrapped) return false
|
|
211
|
+
completionMethods.push([target, name, original, wrapped])
|
|
212
|
+
return true
|
|
213
|
+
} catch (error) {
|
|
214
|
+
log.error('Error wrapping Lambda completion %s: %s', name, error)
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @param {Function} callback
|
|
221
|
+
* @param {() => void} clear
|
|
222
|
+
*/
|
|
223
|
+
function onCompletion (callback, clear) {
|
|
224
|
+
return function (...args) {
|
|
225
|
+
clear()
|
|
226
|
+
return Reflect.apply(callback, this, args)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Reads metadata without executing application getters or Proxy traps.
|
|
232
|
+
*
|
|
233
|
+
* @param {unknown} target
|
|
234
|
+
* @param {string | symbol} name
|
|
235
|
+
*/
|
|
236
|
+
function getDataProperty (target, name) {
|
|
237
|
+
if (target === null || (typeof target !== 'object' && typeof target !== 'function')) return
|
|
238
|
+
while (target !== null && !isProxy(target)) {
|
|
239
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, name)
|
|
240
|
+
if (descriptor) return descriptor.value
|
|
241
|
+
target = Object.getPrototypeOf(target)
|
|
82
242
|
}
|
|
83
243
|
}
|
|
@@ -41,7 +41,6 @@ if (!getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')?.split(',').in
|
|
|
41
41
|
|
|
42
42
|
return moduleExports
|
|
43
43
|
})
|
|
44
|
-
return
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
const moduleToPatch = 'datadog-lambda-js'
|
|
@@ -62,4 +61,19 @@ if (!getValueFromEnvSources('DD_TRACE_DISABLED_INSTRUMENTATIONS')?.split(',').in
|
|
|
62
61
|
|
|
63
62
|
return moduleExports
|
|
64
63
|
})
|
|
64
|
+
|
|
65
|
+
// The published datadog-lambda-js ESM entrypoint imports this helper before initializing the tracer.
|
|
66
|
+
// Revisit cached copies through the registered hook without loading datadog-lambda-js early.
|
|
67
|
+
const helperSuffixes = [
|
|
68
|
+
path.join('datadog-lambda-js', 'dist', 'utils', 'handler.js'),
|
|
69
|
+
path.join('datadog-lambda-js', 'utils', 'handler.js'),
|
|
70
|
+
]
|
|
71
|
+
for (const filename of Object.keys(require.cache)) {
|
|
72
|
+
if (!helperSuffixes.some(suffix => filename.endsWith(path.sep + suffix))) continue
|
|
73
|
+
try {
|
|
74
|
+
require(filename)
|
|
75
|
+
} catch (error) {
|
|
76
|
+
log.error('Error executing cached lambda hook for datadog-lambda-js: %s', error)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
65
79
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const path = require('path')
|
|
4
|
+
const { types: { isModuleNamespaceObject } } = require('node:util')
|
|
4
5
|
|
|
5
6
|
const { datadog } = require('../handler')
|
|
6
7
|
const { addHook } = require('../../../../datadog-instrumentations/src/helpers/instrument')
|
|
@@ -23,22 +24,48 @@ function patchDatadogLambdaHandler (datadogHandler) {
|
|
|
23
24
|
return userHandler => datadogHandler(datadog(userHandler))
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Wraps datadog-lambda-js's promisifiedHandler factory, which wraps the customer's handler.
|
|
29
|
+
* The timeout monitor surrounds that adapter to observe the completion Promise selected by datadog-lambda-js.
|
|
30
|
+
* Legacy RIC completes returned Promises through context.succeed without waiting for event-loop drain.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} moduleExports
|
|
33
|
+
*/
|
|
34
|
+
function patchPromisifiedHandlerModule (moduleExports) {
|
|
35
|
+
shimmer.wrap(moduleExports, 'promisifiedHandler', promisify => {
|
|
36
|
+
return handler => datadog(promisify(handler), true)
|
|
37
|
+
})
|
|
38
|
+
return moduleExports
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
/** @param {string} handlerPath */
|
|
27
42
|
function patchLambdaModule (handlerPath) {
|
|
28
43
|
return lambdaModule => {
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
const descriptor = Object.getOwnPropertyDescriptor(lambdaModule, handlerPath)
|
|
45
|
+
if (typeof descriptor?.value === 'function') {
|
|
46
|
+
if (!descriptor.configurable && !descriptor.writable) return lambdaModule
|
|
47
|
+
// The monitor copies Lambda metadata without invoking application accessors or Proxy traps.
|
|
48
|
+
// Generic shimmer wrapping would copy the original properties back onto that safe wrapper.
|
|
49
|
+
descriptor.value = datadog(descriptor.value)
|
|
50
|
+
// Native ESM namespaces reject replacement even when their export reports writable: true.
|
|
51
|
+
const exports = isModuleNamespaceObject(lambdaModule)
|
|
52
|
+
? Object.create(lambdaModule, Object.getOwnPropertyDescriptors(lambdaModule))
|
|
53
|
+
: lambdaModule
|
|
54
|
+
Object.defineProperty(exports, handlerPath, descriptor)
|
|
55
|
+
return exports
|
|
56
|
+
}
|
|
57
|
+
return shimmer.wrap(lambdaModule, handlerPath, datadog)
|
|
31
58
|
}
|
|
32
59
|
}
|
|
33
60
|
|
|
34
|
-
/** @param {Function} lambdaHandler */
|
|
35
|
-
function patchLambdaHandler (lambdaHandler) {
|
|
36
|
-
return datadog(lambdaHandler)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
61
|
const lambdaTaskRoot = getEnvironmentVariable('LAMBDA_TASK_ROOT')
|
|
40
62
|
const originalLambdaHandler = getValueFromEnvSources('DD_LAMBDA_HANDLER')
|
|
41
63
|
|
|
64
|
+
// Observe the completion Promise selected by datadog-lambda-js inside the invocation span's async scope.
|
|
65
|
+
// The npm package keeps dist/; Lambda layer builds place its contents directly in the package root.
|
|
66
|
+
addHook({ name: 'datadog-lambda-js', file: 'dist/utils/handler.js' }, patchPromisifiedHandlerModule)
|
|
67
|
+
addHook({ name: 'datadog-lambda-js', file: 'utils/handler.js' }, patchPromisifiedHandlerModule)
|
|
68
|
+
|
|
42
69
|
if (originalLambdaHandler === undefined) {
|
|
43
70
|
addHook({ name: 'datadog-lambda-js' }, patchDatadogLambdaModule)
|
|
44
71
|
} else {
|
|
@@ -106,8 +106,9 @@ function fitsInlineAudioBudget (byteLength, maxBytes = LLMOBS_AUDIO_INLINE_MAX_B
|
|
|
106
106
|
* @returns {AudioPart | undefined}
|
|
107
107
|
*/
|
|
108
108
|
function formatAudioPartWithGuard (audioBytes, mimeType, maxBytes = LLMOBS_AUDIO_INLINE_MAX_BYTES) {
|
|
109
|
-
if (!audioBytes?.length || !isRenderableAudioMime(mimeType))
|
|
110
|
-
|
|
109
|
+
if (!audioBytes?.length || !isRenderableAudioMime(mimeType) || !fitsInlineAudioBudget(audioBytes.length, maxBytes)) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
111
112
|
|
|
112
113
|
return formatAudioPart(audioBytes, mimeType)
|
|
113
114
|
}
|
|
@@ -7,7 +7,11 @@ const CACHE_READ_INPUT_TOKENS_METRIC_KEY = 'cache_read_input_tokens'
|
|
|
7
7
|
const CACHE_WRITE_INPUT_TOKENS_METRIC_KEY = 'cache_write_input_tokens'
|
|
8
8
|
const CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY = 'ephemeral_5m_input_tokens'
|
|
9
9
|
const CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY = 'ephemeral_1h_input_tokens'
|
|
10
|
+
const CACHE_WRITE_30M_INPUT_TOKENS_METRIC_KEY = 'ephemeral_30m_input_tokens'
|
|
11
|
+
const CACHE_WRITE_12H_INPUT_TOKENS_METRIC_KEY = 'ephemeral_12h_input_tokens'
|
|
10
12
|
const REASONING_OUTPUT_TOKENS_METRIC_KEY = 'reasoning_output_tokens'
|
|
13
|
+
const WEB_SEARCH_COUNT_METRIC_KEY = 'web_search_count'
|
|
14
|
+
const STORAGE_SEARCH_COUNT_METRIC_KEY = 'storage_search_count'
|
|
11
15
|
|
|
12
16
|
module.exports = {
|
|
13
17
|
SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval', 'experiment'],
|
|
@@ -72,7 +76,11 @@ module.exports = {
|
|
|
72
76
|
CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
|
|
73
77
|
CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
|
|
74
78
|
CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
|
|
79
|
+
CACHE_WRITE_30M_INPUT_TOKENS_METRIC_KEY,
|
|
80
|
+
CACHE_WRITE_12H_INPUT_TOKENS_METRIC_KEY,
|
|
75
81
|
REASONING_OUTPUT_TOKENS_METRIC_KEY,
|
|
82
|
+
WEB_SEARCH_COUNT_METRIC_KEY,
|
|
83
|
+
STORAGE_SEARCH_COUNT_METRIC_KEY,
|
|
76
84
|
|
|
77
85
|
// integrations build metric objects with these camelCase spellings. Null prototype: a metric
|
|
78
86
|
// named after an `Object.prototype` member must not resolve to an inherited property.
|
|
@@ -84,7 +92,11 @@ module.exports = {
|
|
|
84
92
|
cacheWriteTokens: CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
|
|
85
93
|
cacheWrite5mTokens: CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
|
|
86
94
|
cacheWrite1hTokens: CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
|
|
95
|
+
cacheWrite30mTokens: CACHE_WRITE_30M_INPUT_TOKENS_METRIC_KEY,
|
|
96
|
+
cacheWrite12hTokens: CACHE_WRITE_12H_INPUT_TOKENS_METRIC_KEY,
|
|
87
97
|
reasoningOutputTokens: REASONING_OUTPUT_TOKENS_METRIC_KEY,
|
|
98
|
+
webSearchCount: WEB_SEARCH_COUNT_METRIC_KEY,
|
|
99
|
+
storageSearchCount: STORAGE_SEARCH_COUNT_METRIC_KEY,
|
|
88
100
|
}),
|
|
89
101
|
|
|
90
102
|
DROPPED_IO_COLLECTION_ERROR: 'dropped_io',
|
|
@@ -253,7 +253,7 @@ class Experiment {
|
|
|
253
253
|
this.#external = options.external === true
|
|
254
254
|
if (!this.#external) {
|
|
255
255
|
if (!options.dataset) throw new Error('Experiment dataset is required')
|
|
256
|
-
if (typeof options.task !== 'function') throw new
|
|
256
|
+
if (typeof options.task !== 'function') throw new TypeError('Experiment task is required')
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
this.#client = client
|
|
@@ -239,11 +239,19 @@ function mergeChunkUsage (usage, chunk) {
|
|
|
239
239
|
const mergedUsage = usage ?? { input_tokens: 0, output_tokens: 0 }
|
|
240
240
|
mergedUsage.output_tokens = chunk.usage.output_tokens
|
|
241
241
|
|
|
242
|
+
// `message_delta` counts are cumulative; input grows past the `message_start` count when server
|
|
243
|
+
// tools (e.g. web search) feed results back into the model
|
|
244
|
+
const inputTokens = chunk.usage.input_tokens
|
|
245
|
+
if (inputTokens != null) mergedUsage.input_tokens = inputTokens
|
|
246
|
+
|
|
242
247
|
const cacheCreationTokens = chunk.usage.cache_creation_input_tokens
|
|
243
248
|
const cacheReadTokens = chunk.usage.cache_read_input_tokens
|
|
244
249
|
if (cacheCreationTokens) mergedUsage.cache_creation_input_tokens = cacheCreationTokens
|
|
245
250
|
if (cacheReadTokens) mergedUsage.cache_read_input_tokens = cacheReadTokens
|
|
246
251
|
|
|
252
|
+
const serverToolUse = chunk.usage.server_tool_use
|
|
253
|
+
if (serverToolUse) mergedUsage.server_tool_use = serverToolUse
|
|
254
|
+
|
|
247
255
|
return mergedUsage
|
|
248
256
|
}
|
|
249
257
|
|
|
@@ -280,6 +288,13 @@ function extractUsage (result) {
|
|
|
280
288
|
metrics.cacheWrite1hTokens = 0
|
|
281
289
|
}
|
|
282
290
|
|
|
291
|
+
// Read the usage counter, not server_tool_use blocks: dynamic filtering nests searches under code
|
|
292
|
+
// execution, and `response_inclusion: "excluded"` drops the blocks while the searches are still billed.
|
|
293
|
+
// Failed searches aren't billed and are already excluded. web_fetch_requests is skipped because web
|
|
294
|
+
// fetch has no per-call price.
|
|
295
|
+
const webSearchRequests = usage.server_tool_use?.web_search_requests
|
|
296
|
+
if (webSearchRequests > 0) metrics.webSearchCount = webSearchRequests
|
|
297
|
+
|
|
283
298
|
return metrics
|
|
284
299
|
}
|
|
285
300
|
|
|
@@ -84,8 +84,7 @@ function determineRole (candidate, parts = []) {
|
|
|
84
84
|
* @param {string} role
|
|
85
85
|
*/
|
|
86
86
|
function normalizeRole (role) {
|
|
87
|
-
if (role === ROLES.MODEL) return ROLES.ASSISTANT
|
|
88
|
-
if (role === ROLES.ASSISTANT) return ROLES.ASSISTANT
|
|
87
|
+
if (role === ROLES.MODEL || role === ROLES.ASSISTANT) return ROLES.ASSISTANT
|
|
89
88
|
if (role === ROLES.USER) return ROLES.USER
|
|
90
89
|
if (role === ROLES.REASONING) return ROLES.REASONING
|
|
91
90
|
return ROLES.USER // default
|
|
@@ -62,11 +62,9 @@ class LangChainLLMObsChatModelHandler extends LangChainLLMObsHandler {
|
|
|
62
62
|
*/
|
|
63
63
|
getTokenUsage (results) {
|
|
64
64
|
const tokens = this.checkTokenUsageChatOrLLMResult(results)
|
|
65
|
-
if (tokens.totalTokens > 0) return tokens
|
|
66
|
-
|
|
67
65
|
// providers that report usage on each generated message instead of on `llmOutput`; counts are
|
|
68
66
|
// summed per run so a run split across generations is totalled once
|
|
69
|
-
if (!results.generations) return tokens
|
|
67
|
+
if (tokens.totalTokens > 0 || !results.generations) return tokens
|
|
70
68
|
|
|
71
69
|
const tokensPerRunId = {}
|
|
72
70
|
for (const messageSet of results.generations) {
|
|
@@ -76,7 +76,7 @@ class NextStreamLLMObsPlugin extends LLMObsPlugin {
|
|
|
76
76
|
const { streamInputs: inputs, chunks } = streamData
|
|
77
77
|
const input = inputs == null ? undefined : formatIO(inputs)
|
|
78
78
|
const lastChunk = chunks.length > 0 ? chunks.at(-1) : undefined
|
|
79
|
-
const output =
|
|
79
|
+
const output = hasError || lastChunk == null ? undefined : formatIO(lastChunk)
|
|
80
80
|
|
|
81
81
|
this._tagger.tagTextIO(span, input, output)
|
|
82
82
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { pcm16ToWav } = require('../audio-codec')
|
|
4
|
+
const { fitsInlineAudioBudget, formatAudioPartWithGuard } = require('../audio-utils')
|
|
5
|
+
const { AUDIO_FALLBACK, LLMOBS_AUDIO_INLINE_MAX_BYTES } = require('../constants/audio')
|
|
6
|
+
const { storage } = require('../storage')
|
|
7
|
+
const LLMObsPlugin = require('./base')
|
|
8
|
+
|
|
9
|
+
class SonicLLMObsPlugin extends LLMObsPlugin {
|
|
10
|
+
static id = 'bedrockruntime_sonic_llmobs'
|
|
11
|
+
static integration = 'bedrock'
|
|
12
|
+
static system = 'aws.bedrock'
|
|
13
|
+
static prefix = 'tracing:apm:aws:bedrockruntime:sonic:span'
|
|
14
|
+
static emitsGenAiApmTags = false
|
|
15
|
+
|
|
16
|
+
constructor (...args) {
|
|
17
|
+
super(...args)
|
|
18
|
+
this.addSub('dd-trace:aws:bedrockruntime:sonic:capture-context', context => {
|
|
19
|
+
if (!this._llmobsEnabled) return
|
|
20
|
+
context.enabled = true
|
|
21
|
+
// Capture once at invocation, even when absent or already finished. Scoped replay restores
|
|
22
|
+
// the consumer's context on every exit, including exceptions, without repairing IDs later.
|
|
23
|
+
const store = storage.getStore()
|
|
24
|
+
const previous = context.runInContext ?? (fn => fn())
|
|
25
|
+
context.runInContext = fn => previous(() => storage.run(store, fn))
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @param {object} ctx */
|
|
30
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
31
|
+
const options = {
|
|
32
|
+
kind: ctx.kind,
|
|
33
|
+
name: ctx.name,
|
|
34
|
+
sessionId: ctx.descriptor.sessionId,
|
|
35
|
+
}
|
|
36
|
+
if (ctx.kind === 'llm') {
|
|
37
|
+
options.modelName = ctx.model
|
|
38
|
+
options.modelProvider = 'amazon'
|
|
39
|
+
}
|
|
40
|
+
return options
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @param {object} ctx */
|
|
44
|
+
start (ctx) {
|
|
45
|
+
super.start(ctx)
|
|
46
|
+
// Replay already has the complete descriptor. Annotate before any end subscriber can finish
|
|
47
|
+
// the span, including when disabling/re-enabling LLMObs changes subscriber registration order.
|
|
48
|
+
if (this._llmobsEnabledFor(ctx)) this.setLLMObsTags(ctx)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @param {object} ctx */
|
|
52
|
+
setLLMObsTags (ctx) {
|
|
53
|
+
const span = ctx.currentStore?.span
|
|
54
|
+
if (!span) return
|
|
55
|
+
const { turn, history, metadata } = ctx.descriptor
|
|
56
|
+
const text = turn.finalText || turn.speculativeText
|
|
57
|
+
if (ctx.kind !== 'llm') {
|
|
58
|
+
if (ctx.name === 'nova sonic audio turn') this._tagger.tagTextIO(span, turn.userText, text)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const input = { role: 'user', content: turn.userText || (turn.inputAudioObserved ? AUDIO_FALLBACK : '') }
|
|
63
|
+
const output = { role: 'assistant', content: text || (turn.outputAudioObserved ? AUDIO_FALLBACK : '') }
|
|
64
|
+
if (turn.tools.length) output.toolCalls = turn.tools
|
|
65
|
+
const inputs = [...history, input, ...turn.toolResults]
|
|
66
|
+
const outputs = [output]
|
|
67
|
+
// Reserve the serialized text/tools and metadata before spending one budget across both WAVs.
|
|
68
|
+
let budget = Math.max(0, LLMOBS_AUDIO_INLINE_MAX_BYTES -
|
|
69
|
+
Buffer.byteLength(JSON.stringify([inputs, outputs, metadata])) - 256)
|
|
70
|
+
for (const [message, pcm, rate] of [
|
|
71
|
+
[input, turn.inputPcm, turn.inputRate],
|
|
72
|
+
[output, turn.output.pcm(), turn.output.rate],
|
|
73
|
+
]) {
|
|
74
|
+
if (!pcm?.length || !rate) continue
|
|
75
|
+
const part = fitsInlineAudioBudget(pcm.length + 44, budget)
|
|
76
|
+
? formatAudioPartWithGuard(pcm16ToWav(pcm, rate), 'audio/wav', budget)
|
|
77
|
+
: undefined
|
|
78
|
+
if (part) {
|
|
79
|
+
message.audioParts = [part]
|
|
80
|
+
budget -= part.content.length
|
|
81
|
+
} else if (message === output) {
|
|
82
|
+
metadata.output_audio_omitted_reason = 'payload_limit'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (turn.output.omittedReason) metadata.output_audio_omitted_reason = turn.output.omittedReason
|
|
86
|
+
this._tagger.tagLLMIO(span, inputs, outputs)
|
|
87
|
+
this._tagger.tagMetadata(span, metadata)
|
|
88
|
+
this._tagger.tagMetrics(span, turn.metrics)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = SonicLLMObsPlugin
|