dd-trace 6.20.1 → 6.22.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 +116 -17
- package/initialize.mjs +1 -3
- package/openfeature/rules-based.d.ts +11 -0
- package/openfeature/rules-based.js +7 -0
- package/package.json +13 -11
- 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 +64 -11
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- 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 +122 -11
- 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 +127 -38
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
- 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/playwright.js +3 -0
- 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 -17
- 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 +118 -21
- 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 +18 -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 +223 -39
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
- 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/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
- package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
- package/packages/dd-trace/src/debugger/index.js +24 -2
- package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
- 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 +2 -5
- package/packages/dd-trace/src/exporters/common/url.js +1 -1
- package/packages/dd-trace/src/id.js +15 -5
- 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/agent-manifest.js +296 -0
- package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +13 -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/sdk.js +23 -6
- package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
- package/packages/dd-trace/src/llmobs/tagger.js +56 -3
- package/packages/dd-trace/src/llmobs/util.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
- package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
- package/packages/dd-trace/src/log/log.js +1 -5
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
- package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
- package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
- package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
- package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
- 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/resource-attributes.js +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 +81 -15
- 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/profiling/profilers/wall.js +4 -10
- package/packages/dd-trace/src/proxy.js +3 -2
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +10 -0
- package/packages/dd-trace/src/span_processor.js +13 -17
- package/packages/dd-trace/src/storage-channels.js +112 -46
- 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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { clearTimeout, setTimeout } = require('node:timers')
|
|
4
|
+
|
|
3
5
|
const {
|
|
4
6
|
createWebdriverioWorkerMessage,
|
|
5
7
|
WEBDRIVERIO_WORKER_ENV,
|
|
@@ -11,6 +13,9 @@ const { FINAL_FLUSH_TIMEOUT } = require('../../../dd-trace/src/ci-visibility/fin
|
|
|
11
13
|
const SCREENSHOT_UPLOAD = 'dd:test-optimization:webdriverio:screenshot:upload'
|
|
12
14
|
const SCREENSHOT_UPLOAD_RESPONSE = 'dd:test-optimization:webdriverio:screenshot:upload:response'
|
|
13
15
|
const SCREENSHOT_UPLOAD_TIMEOUT_MS = FINAL_FLUSH_TIMEOUT + 5000
|
|
16
|
+
const VIDEO_UPLOAD = 'dd:test-optimization:webdriverio:video:upload'
|
|
17
|
+
const VIDEO_UPLOAD_RESPONSE = 'dd:test-optimization:webdriverio:video:upload:response'
|
|
18
|
+
const VIDEO_UPLOAD_TIMEOUT_MS = 5 * FINAL_FLUSH_TIMEOUT + 5000
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Sends a message over WebdriverIO's worker IPC envelope.
|
|
@@ -34,95 +39,113 @@ function sendWebdriverioWorkerMessage (message, onError, onDone) {
|
|
|
34
39
|
})
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
let
|
|
38
|
-
const
|
|
42
|
+
let mediaUploadRequestId = 0
|
|
43
|
+
const mediaUploadRequests = new Map()
|
|
39
44
|
|
|
40
45
|
/**
|
|
41
|
-
* Removes shared
|
|
46
|
+
* Removes shared media response listeners when there are no pending requests.
|
|
42
47
|
*
|
|
43
48
|
*/
|
|
44
|
-
function
|
|
45
|
-
if (
|
|
49
|
+
function removeMediaUploadListeners () {
|
|
50
|
+
if (mediaUploadRequests.size !== 0) return
|
|
46
51
|
|
|
47
|
-
process.off('message',
|
|
48
|
-
process.off('disconnect',
|
|
52
|
+
process.off('message', onMediaUploadResponse)
|
|
53
|
+
process.off('disconnect', onMediaUploadDisconnect)
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
|
-
* Completes one pending
|
|
57
|
+
* Completes one pending media upload request.
|
|
53
58
|
*
|
|
54
59
|
* @param {string} requestId
|
|
55
60
|
* @param {Error} [error]
|
|
56
61
|
*/
|
|
57
|
-
function
|
|
58
|
-
const request =
|
|
62
|
+
function finishMediaUploadRequest (requestId, error) {
|
|
63
|
+
const request = mediaUploadRequests.get(requestId)
|
|
59
64
|
if (!request) return
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
mediaUploadRequests.delete(requestId)
|
|
62
67
|
clearTimeout(request.timeout)
|
|
63
|
-
|
|
68
|
+
removeMediaUploadListeners()
|
|
64
69
|
request.onDone(error)
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
/**
|
|
68
|
-
* Dispatches one coordinator
|
|
73
|
+
* Dispatches one coordinator media response to its pending request.
|
|
69
74
|
*
|
|
70
75
|
* @param {object} message
|
|
71
76
|
*/
|
|
72
|
-
function
|
|
73
|
-
if (message?.name !== SCREENSHOT_UPLOAD_RESPONSE) return
|
|
77
|
+
function onMediaUploadResponse (message) {
|
|
78
|
+
if (message?.name !== SCREENSHOT_UPLOAD_RESPONSE && message?.name !== VIDEO_UPLOAD_RESPONSE) return
|
|
74
79
|
|
|
75
80
|
const { error: errorMessage, requestId } = message.content || {}
|
|
76
81
|
if (!requestId) return
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
finishMediaUploadRequest(requestId, errorMessage ? new Error(errorMessage) : undefined)
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
/**
|
|
82
|
-
* Fails every pending
|
|
87
|
+
* Fails every pending media upload after coordinator disconnect.
|
|
83
88
|
*
|
|
84
89
|
*/
|
|
85
|
-
function
|
|
86
|
-
for (const requestId of
|
|
87
|
-
|
|
90
|
+
function onMediaUploadDisconnect () {
|
|
91
|
+
for (const requestId of mediaUploadRequests.keys()) {
|
|
92
|
+
finishMediaUploadRequest(
|
|
88
93
|
requestId,
|
|
89
|
-
new Error('WebdriverIO coordinator disconnected during
|
|
94
|
+
new Error('WebdriverIO coordinator disconnected during media upload')
|
|
90
95
|
)
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
/**
|
|
95
|
-
* Requests one
|
|
100
|
+
* Requests one media upload from the WebdriverIO coordinator.
|
|
96
101
|
*
|
|
97
|
-
* @param {object} content -
|
|
102
|
+
* @param {object} content - Upload metadata
|
|
103
|
+
* @param {'screenshot'|'video'} kind
|
|
98
104
|
* @param {(error?: Error) => void} onDone - Upload completion callback
|
|
99
105
|
*/
|
|
100
|
-
function
|
|
101
|
-
const requestId = `${process.pid}-${++
|
|
106
|
+
function requestWebdriverioMediaUpload (content, kind, onDone) {
|
|
107
|
+
const requestId = `${process.pid}-${++mediaUploadRequestId}`
|
|
102
108
|
const timeout = setTimeout(() => {
|
|
103
|
-
|
|
104
|
-
}, SCREENSHOT_UPLOAD_TIMEOUT_MS)
|
|
109
|
+
finishMediaUploadRequest(requestId, new Error(`WebdriverIO ${kind} upload timed out`))
|
|
110
|
+
}, kind === 'video' ? VIDEO_UPLOAD_TIMEOUT_MS : SCREENSHOT_UPLOAD_TIMEOUT_MS)
|
|
105
111
|
timeout.unref?.()
|
|
106
|
-
if (
|
|
107
|
-
process.on('message',
|
|
108
|
-
process.once('disconnect',
|
|
112
|
+
if (mediaUploadRequests.size === 0) {
|
|
113
|
+
process.on('message', onMediaUploadResponse)
|
|
114
|
+
process.once('disconnect', onMediaUploadDisconnect)
|
|
109
115
|
}
|
|
110
|
-
|
|
116
|
+
mediaUploadRequests.set(requestId, { onDone, timeout })
|
|
111
117
|
sendWebdriverioWorkerMessage({
|
|
112
118
|
origin: 'datadog',
|
|
113
|
-
name: SCREENSHOT_UPLOAD,
|
|
119
|
+
name: kind === 'video' ? VIDEO_UPLOAD : SCREENSHOT_UPLOAD,
|
|
114
120
|
content: { ...content, requestId },
|
|
115
|
-
}, error =>
|
|
121
|
+
}, error => finishMediaUploadRequest(
|
|
116
122
|
requestId,
|
|
117
|
-
error || new Error(
|
|
123
|
+
error || new Error(`WebdriverIO ${kind} upload IPC failed`)
|
|
118
124
|
))
|
|
119
125
|
}
|
|
120
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @param {object} content
|
|
129
|
+
* @param {(error?: Error) => void} onDone
|
|
130
|
+
*/
|
|
131
|
+
function requestWebdriverioScreenshotUpload (content, onDone) {
|
|
132
|
+
requestWebdriverioMediaUpload(content, 'screenshot', onDone)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {object} content
|
|
137
|
+
* @param {(error?: Error) => void} onDone
|
|
138
|
+
*/
|
|
139
|
+
function requestWebdriverioVideoUpload (content, onDone) {
|
|
140
|
+
requestWebdriverioMediaUpload(content, 'video', onDone)
|
|
141
|
+
}
|
|
142
|
+
|
|
121
143
|
module.exports = {
|
|
122
144
|
CONFIGURATION_REQUEST: 'dd:test-optimization:webdriverio:configuration:request',
|
|
123
145
|
CONFIGURATION_RESPONSE: 'dd:test-optimization:webdriverio:configuration:response',
|
|
124
146
|
createWebdriverioWorkerMessage,
|
|
125
147
|
requestWebdriverioScreenshotUpload,
|
|
148
|
+
requestWebdriverioVideoUpload,
|
|
126
149
|
SCREENSHOT_UPLOAD,
|
|
127
150
|
SCREENSHOT_UPLOAD_RESPONSE,
|
|
128
151
|
SCREENSHOT_UPLOAD_TIMEOUT_MS,
|
|
@@ -130,6 +153,10 @@ module.exports = {
|
|
|
130
153
|
SUITE_FINISH: 'dd:test-optimization:webdriverio:test-suite:finish',
|
|
131
154
|
WORKER_READY: 'dd:test-optimization:webdriverio:worker:ready',
|
|
132
155
|
WORKER_READY_RESPONSE: 'dd:test-optimization:webdriverio:worker:ready:response',
|
|
156
|
+
VIDEO_UPLOAD_FLUSH: 'dd:test-optimization:webdriverio:video:flush',
|
|
157
|
+
VIDEO_UPLOAD,
|
|
158
|
+
VIDEO_UPLOAD_RESPONSE,
|
|
159
|
+
VIDEO_UPLOAD_TIMEOUT_MS,
|
|
133
160
|
WEBDRIVERIO_WORKER_ENV,
|
|
134
161
|
WEBDRIVERIO_WORKER_EVENT,
|
|
135
162
|
WEBDRIVERIO_WORKER_ORIGIN,
|
|
@@ -416,7 +416,7 @@ function wrapMochaRun (Mocha, frameworkVersion) {
|
|
|
416
416
|
})
|
|
417
417
|
}
|
|
418
418
|
filterSkippedFiles(runner, skippedFiles)
|
|
419
|
-
if (isFailedTestReplayEnabled() || config.isFlakyTestRetriesEnabled) {
|
|
419
|
+
if (isFailedTestReplayEnabled() || config.isFlakyTestRetriesEnabled || config.isTestFailureVideosEnabled) {
|
|
420
420
|
patchFailedTestReplayHookUp(runner.constructor)
|
|
421
421
|
}
|
|
422
422
|
configurationReady = true
|
|
@@ -443,8 +443,7 @@ addHook({
|
|
|
443
443
|
filePattern: String.raw`lib/runner\.(?:c?js)$`,
|
|
444
444
|
}, function (runnerPackage) {
|
|
445
445
|
const Runner = runnerPackage.Runner ?? runnerPackage.default ?? runnerPackage
|
|
446
|
-
if (typeof Runner !== 'function') return
|
|
447
|
-
if (patchedRunners.has(Runner)) return
|
|
446
|
+
if (typeof Runner !== 'function' || patchedRunners.has(Runner)) return
|
|
448
447
|
|
|
449
448
|
patchedRunners.add(Runner)
|
|
450
449
|
shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
|
|
@@ -453,7 +452,7 @@ addHook({
|
|
|
453
452
|
if (!workerFinishCh.hasSubscribers) {
|
|
454
453
|
return run.apply(this, args)
|
|
455
454
|
}
|
|
456
|
-
if (isFailedTestReplayEnabled() || config.isFlakyTestRetriesEnabled) {
|
|
455
|
+
if (isFailedTestReplayEnabled() || config.isFlakyTestRetriesEnabled || config.isTestFailureVideosEnabled) {
|
|
457
456
|
patchFailedTestReplayHookUp(Runner)
|
|
458
457
|
}
|
|
459
458
|
if (isWebdriverioWorker) {
|
|
@@ -469,7 +468,10 @@ addHook({
|
|
|
469
468
|
const onRunDone = args[0]
|
|
470
469
|
if (isWebdriverioWorker && typeof onRunDone === 'function') {
|
|
471
470
|
args[0] = (...onRunDoneArgs) => {
|
|
472
|
-
|
|
471
|
+
const adjustedFailures = Number.isFinite(this.failures)
|
|
472
|
+
? Math.max(0, this.failures)
|
|
473
|
+
: onRunDoneArgs[0]
|
|
474
|
+
finishWebdriverioWorker(this, () => onRunDone(adjustedFailures, ...onRunDoneArgs.slice(1)))
|
|
473
475
|
}
|
|
474
476
|
} else {
|
|
475
477
|
// Flush after the worker finishes its Mocha run, including grouped spec files.
|
|
@@ -45,7 +45,7 @@ addHook({ name: 'mongodb', versions: ['>=4.6.0 <6.4.0'], file: 'lib/cmap/connect
|
|
|
45
45
|
|
|
46
46
|
addHook({ name: 'mongodb', versions: ['>=6.4.0'], file: 'lib/cmap/connection.js' }, Connection => {
|
|
47
47
|
const proto = Connection.Connection.prototype
|
|
48
|
-
shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command',
|
|
48
|
+
shimmer.wrap(proto, 'command', command => wrapConnectionCommand(command, 'command', instrumentPromise))
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
addHook({ name: 'mongodb', versions: ['>=3.3 <4'], file: 'lib/core/wireprotocol/index.js' }, wp => wrapWp(wp))
|
|
@@ -95,8 +95,8 @@ function wrapUnifiedCommand (command, operation, name) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function wrapConnectionCommand (command, operation,
|
|
99
|
-
const opts = { name }
|
|
98
|
+
function wrapConnectionCommand (command, operation, instrumentFn = instrument) {
|
|
99
|
+
const opts = { name: undefined }
|
|
100
100
|
return function (ns, ops) {
|
|
101
101
|
if (!startCh.hasSubscribers) {
|
|
102
102
|
return command.apply(this, arguments)
|
|
@@ -83,7 +83,7 @@ addHook({
|
|
|
83
83
|
|
|
84
84
|
let callbackWrapped = false
|
|
85
85
|
|
|
86
|
-
const wrapCallbackIfExist = (args
|
|
86
|
+
const wrapCallbackIfExist = (args) => {
|
|
87
87
|
const lastArgumentIndex = args.length - 1
|
|
88
88
|
|
|
89
89
|
if (typeof args[lastArgumentIndex] === 'function') {
|
|
@@ -106,7 +106,7 @@ addHook({
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
return startCh.runStores(ctx, () => {
|
|
109
|
-
wrapCallbackIfExist(args
|
|
109
|
+
wrapCallbackIfExist(args)
|
|
110
110
|
|
|
111
111
|
// A callback query executes synchronously inside `method.apply`, so the
|
|
112
112
|
// exec scope (and the deferred driver call it spawns) must be entered
|
|
@@ -121,7 +121,7 @@ addHook({
|
|
|
121
121
|
shimmer.wrap(res, 'exec', originalExec => {
|
|
122
122
|
return function wrappedExec (...args) {
|
|
123
123
|
if (!callbackWrapped) {
|
|
124
|
-
wrapCallbackIfExist(args
|
|
124
|
+
wrapCallbackIfExist(args)
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const execResult = execCh.runStores(ctx, () => originalExec.apply(this, args))
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { LLMOBS_AUDIO_ACCUMULATE_MAX_BYTES } = require('../../../dd-trace/src/llmobs/constants/audio')
|
|
4
|
+
|
|
5
|
+
// Leave room for the WAV header when the retained PCM is encoded.
|
|
6
|
+
const MAX_AUDIO = LLMOBS_AUDIO_ACCUMULATE_MAX_BYTES - 128
|
|
7
|
+
const RATES = new Set([8000, 16_000, 24_000, 48_000])
|
|
8
|
+
const INVALID_BASE64 = /[^A-Za-z0-9+/]/
|
|
9
|
+
|
|
10
|
+
/** @param {object} configuration */
|
|
11
|
+
function pcmRate (configuration = {}) {
|
|
12
|
+
return configuration.mediaType === 'audio/lpcm' && configuration.sampleSizeBits === 16 &&
|
|
13
|
+
configuration.channelCount === 1 && configuration.encoding === 'base64' &&
|
|
14
|
+
RATES.has(configuration.sampleRateHertz)
|
|
15
|
+
? configuration.sampleRateHertz
|
|
16
|
+
: 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** @param {string} encoded */
|
|
20
|
+
function decodedLength (encoded) {
|
|
21
|
+
if (typeof encoded !== 'string' || encoded.length % 4) return
|
|
22
|
+
const padding = encoded.endsWith('==') ? 2 : encoded.endsWith('=') ? 1 : 0
|
|
23
|
+
if (INVALID_BASE64.test(encoded.slice(0, encoded.length - padding))) return
|
|
24
|
+
return encoded.length / 4 * 3 - padding
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A bounded, connection-wide sample timeline. Content-name changes do not reset its origin. */
|
|
28
|
+
class InputAudio {
|
|
29
|
+
rate = 0
|
|
30
|
+
valid = true
|
|
31
|
+
total = 0
|
|
32
|
+
anchor
|
|
33
|
+
#chunks = []
|
|
34
|
+
#bytes = 0
|
|
35
|
+
|
|
36
|
+
/** @param {object} configuration */
|
|
37
|
+
configure (configuration) {
|
|
38
|
+
const rate = pcmRate(configuration)
|
|
39
|
+
if (!rate || (this.total && rate !== this.rate)) this.valid = false
|
|
40
|
+
this.rate = rate
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} encoded
|
|
45
|
+
* @param {number} now Milliseconds at SDK consumption, not an acknowledgement from the provider.
|
|
46
|
+
*/
|
|
47
|
+
append (encoded, now) {
|
|
48
|
+
const length = decodedLength(encoded)
|
|
49
|
+
if (length === undefined) {
|
|
50
|
+
this.valid = false
|
|
51
|
+
this.clear()
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
if (length % 2) this.valid = false
|
|
55
|
+
this.total += length
|
|
56
|
+
if (this.rate) this.anchor = now - this.total * 1000 / (2 * this.rate)
|
|
57
|
+
if (!this.valid || !length) return
|
|
58
|
+
// Decode from a base64 quartet boundary, retaining at most two alignment bytes
|
|
59
|
+
// beyond the cap instead of allocating the entire incoming frame.
|
|
60
|
+
const start = Math.max(0, length - MAX_AUDIO)
|
|
61
|
+
const retained = Buffer.from(encoded.slice(Math.floor(start / 3) * 4), 'base64').subarray(start % 3)
|
|
62
|
+
this.#chunks.push(retained)
|
|
63
|
+
this.#bytes += retained.length
|
|
64
|
+
while (this.#bytes > MAX_AUDIO) {
|
|
65
|
+
const first = this.#chunks[0]
|
|
66
|
+
const excess = this.#bytes - MAX_AUDIO
|
|
67
|
+
if (first.length <= excess) {
|
|
68
|
+
this.#chunks.shift()
|
|
69
|
+
this.#bytes -= first.length
|
|
70
|
+
} else {
|
|
71
|
+
this.#chunks[0] = Buffer.from(first.subarray(excess))
|
|
72
|
+
this.#bytes -= excess
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {number} startMs
|
|
79
|
+
* @param {number} endMs
|
|
80
|
+
*/
|
|
81
|
+
clip (startMs, endMs) {
|
|
82
|
+
if (!this.valid || !this.rate || !Number.isFinite(startMs) || !Number.isFinite(endMs) || startMs >= endMs) return
|
|
83
|
+
const start = Math.floor(startMs * this.rate / 1000) * 2
|
|
84
|
+
const end = Math.floor(endMs * this.rate / 1000) * 2
|
|
85
|
+
const base = this.total - this.#bytes
|
|
86
|
+
if (start < base || end > this.total) return
|
|
87
|
+
const clip = Buffer.allocUnsafe(end - start)
|
|
88
|
+
let offset = base
|
|
89
|
+
let written = 0
|
|
90
|
+
for (const chunk of this.#chunks) {
|
|
91
|
+
const chunkEnd = offset + chunk.length
|
|
92
|
+
if (chunkEnd > start) {
|
|
93
|
+
written += chunk.copy(clip, written, Math.max(start - offset, 0), Math.min(end - offset, chunk.length))
|
|
94
|
+
}
|
|
95
|
+
offset = chunkEnd
|
|
96
|
+
if (offset >= end) break
|
|
97
|
+
}
|
|
98
|
+
return clip
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
clear () {
|
|
102
|
+
this.#chunks = []
|
|
103
|
+
this.#bytes = 0
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Ordered PCM playback projection for one response, including genuine underrun silence. */
|
|
108
|
+
class OutputAudio {
|
|
109
|
+
rate = 0
|
|
110
|
+
start
|
|
111
|
+
end
|
|
112
|
+
interrupted
|
|
113
|
+
generatedBytes = 0
|
|
114
|
+
timingValid = true
|
|
115
|
+
omittedReason
|
|
116
|
+
#chunks = []
|
|
117
|
+
#bytes = 0
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} encoded
|
|
121
|
+
* @param {object} configuration
|
|
122
|
+
* @param {number} now
|
|
123
|
+
*/
|
|
124
|
+
append (encoded, configuration, now) {
|
|
125
|
+
const length = decodedLength(encoded)
|
|
126
|
+
if (length !== undefined) this.generatedBytes += length
|
|
127
|
+
// Late chunks cannot resurrect playback or invalidate the clip already cut at interruption.
|
|
128
|
+
if (this.interrupted !== undefined) return
|
|
129
|
+
const rate = pcmRate(configuration)
|
|
130
|
+
if (length === undefined || !rate || length % 2 || (this.rate && this.rate !== rate)) {
|
|
131
|
+
this.timingValid = false
|
|
132
|
+
this.omittedReason = 'invalid_audio'
|
|
133
|
+
this.#chunks = []
|
|
134
|
+
this.#bytes = 0
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
if (!length) return
|
|
138
|
+
this.rate ||= rate
|
|
139
|
+
this.start ??= now
|
|
140
|
+
const previousEnd = this.end ?? now
|
|
141
|
+
const gap = Math.floor(Math.max(0, now - previousEnd) * rate / 1000) * 2
|
|
142
|
+
this.end = previousEnd + (gap + length) * 1000 / (2 * rate)
|
|
143
|
+
if (!this.omittedReason && this.#bytes + gap + length <= MAX_AUDIO) {
|
|
144
|
+
if (gap) this.#chunks.push(Buffer.alloc(gap))
|
|
145
|
+
this.#chunks.push(Buffer.from(encoded, 'base64'))
|
|
146
|
+
this.#bytes += gap + length
|
|
147
|
+
} else {
|
|
148
|
+
this.omittedReason ||= 'retention_limit'
|
|
149
|
+
this.#chunks = []
|
|
150
|
+
this.#bytes = 0
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** @param {number} now */
|
|
155
|
+
interrupt (now) {
|
|
156
|
+
if (this.interrupted !== undefined) return
|
|
157
|
+
this.interrupted = now
|
|
158
|
+
if (this.start === undefined || this.end === undefined || !this.rate) return
|
|
159
|
+
const frames = Math.floor(Math.max(0, now - this.start) * this.rate / 1000)
|
|
160
|
+
this.end = Math.min(this.end, this.start + frames * 1000 / this.rate)
|
|
161
|
+
if (this.#bytes > frames * 2) {
|
|
162
|
+
const clip = Buffer.concat(this.#chunks, this.#bytes).subarray(0, frames * 2)
|
|
163
|
+
this.#chunks = [Buffer.from(clip)]
|
|
164
|
+
this.#bytes = clip.length
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pcm () {
|
|
169
|
+
if (!this.omittedReason && this.#bytes) return Buffer.concat(this.#chunks, this.#bytes)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { InputAudio, OutputAudio }
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const dc = require('dc-polyfill')
|
|
4
|
+
|
|
5
|
+
const log = require('../../../dd-trace/src/log')
|
|
6
|
+
const { storage } = require('../../../datadog-core')
|
|
7
|
+
const SonicSession = require('./session')
|
|
8
|
+
|
|
9
|
+
const captureChannel = dc.channel('dd-trace:aws:bedrockruntime:sonic:capture-context')
|
|
10
|
+
const spanChannel = dc.tracingChannel('apm:aws:bedrockruntime:sonic:span')
|
|
11
|
+
const MODEL = 'amazon.nova-2-sonic-v1:0'
|
|
12
|
+
const FOUNDATION_MODEL_ARN = /^arn:aws(?:-[a-z0-9]+)*:bedrock:[a-z0-9-]{1,20}::foundation-model\/([^/]+)$/
|
|
13
|
+
const sessionStorage = storage('nova-sonic')
|
|
14
|
+
const instrumentedClients = new WeakSet()
|
|
15
|
+
|
|
16
|
+
function noop () {}
|
|
17
|
+
|
|
18
|
+
/** @param {unknown} identifier */
|
|
19
|
+
function resolveModel (identifier) {
|
|
20
|
+
if (identifier === MODEL) return identifier
|
|
21
|
+
if (typeof identifier !== 'string' || identifier.length > 2048) return
|
|
22
|
+
// Only foundation-model ARNs directly identify a model. Profile IDs need a service lookup,
|
|
23
|
+
// and the bidirectional API currently rejects them. Never infer a model from an ARN suffix.
|
|
24
|
+
const model = FOUNDATION_MODEL_ARN.exec(identifier)?.[1]
|
|
25
|
+
if (model === MODEL) return model
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {object} descriptor
|
|
30
|
+
* @param {(fn: () => void) => void} runInContext
|
|
31
|
+
* @param {string} model
|
|
32
|
+
*/
|
|
33
|
+
function emitTurn (descriptor, runInContext, model) {
|
|
34
|
+
const { turn } = descriptor
|
|
35
|
+
const common = { descriptor, model }
|
|
36
|
+
runInContext(() => {
|
|
37
|
+
spanChannel.traceSync(() => {
|
|
38
|
+
if (turn.inputStart !== undefined && turn.inputEnd !== undefined && turn.inputEnd >= turn.inputStart) {
|
|
39
|
+
spanChannel.traceSync(noop, {
|
|
40
|
+
...common, name: 'user speech', kind: 'workflow', startTime: turn.inputStart, finishTime: turn.inputEnd,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
spanChannel.traceSync(noop, {
|
|
44
|
+
...common,
|
|
45
|
+
name: 'nova sonic response',
|
|
46
|
+
kind: 'llm',
|
|
47
|
+
startTime: descriptor.llmStart,
|
|
48
|
+
finishTime: descriptor.responseEnd,
|
|
49
|
+
})
|
|
50
|
+
const output = turn.output
|
|
51
|
+
if (output.timingValid && output.start !== undefined && output.end > output.start) {
|
|
52
|
+
spanChannel.traceSync(noop, {
|
|
53
|
+
...common, name: 'agent speech', kind: 'workflow', startTime: output.start, finishTime: output.end,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}, {
|
|
57
|
+
...common,
|
|
58
|
+
name: 'nova sonic audio turn',
|
|
59
|
+
kind: 'workflow',
|
|
60
|
+
startTime: descriptor.startTime,
|
|
61
|
+
finishTime: descriptor.finishTime,
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* These iterators are application/factory-created objects with no matchable source function.
|
|
68
|
+
* Wrap the concrete iterator, preserving next/return/throw and the SDK's own backpressure/cleanup.
|
|
69
|
+
* No producer is started here; each next delegates exactly once to the original iterator.
|
|
70
|
+
* @param {object} source SDK async iterable.
|
|
71
|
+
* @param {SonicSession} session
|
|
72
|
+
* @param {boolean} outbound
|
|
73
|
+
* @param {(error?: Error) => void} finish
|
|
74
|
+
*/
|
|
75
|
+
function observeIterable (source, session, outbound, finish) {
|
|
76
|
+
if (typeof source?.[Symbol.asyncIterator] !== 'function') return source
|
|
77
|
+
const rejected = error => {
|
|
78
|
+
finish(error)
|
|
79
|
+
throw error
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
[Symbol.asyncIterator] () {
|
|
83
|
+
let iterator
|
|
84
|
+
try {
|
|
85
|
+
iterator = source[Symbol.asyncIterator]()
|
|
86
|
+
} catch (error) {
|
|
87
|
+
finish(error)
|
|
88
|
+
throw error
|
|
89
|
+
}
|
|
90
|
+
const observed = { [Symbol.asyncIterator] () { return this } }
|
|
91
|
+
for (const method of ['next', 'return', 'throw']) {
|
|
92
|
+
if (typeof iterator[method] !== 'function') continue
|
|
93
|
+
const fulfilled = result => {
|
|
94
|
+
if (result.done || method === 'return') {
|
|
95
|
+
if (!outbound) finish()
|
|
96
|
+
} else {
|
|
97
|
+
session.observe(result.value, outbound)
|
|
98
|
+
}
|
|
99
|
+
return result
|
|
100
|
+
}
|
|
101
|
+
observed[method] = function (...args) {
|
|
102
|
+
try {
|
|
103
|
+
const result = iterator[method](...args)
|
|
104
|
+
return typeof result?.then === 'function' ? result.then(fulfilled, rejected) : fulfilled(result)
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return rejected(error)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return observed
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** @param {object} client */
|
|
116
|
+
function instrumentInput (client) {
|
|
117
|
+
if (instrumentedClients.has(client)) return true
|
|
118
|
+
try {
|
|
119
|
+
// Install before even an uncaptured send can cache the SDK handler. Cached handlers must
|
|
120
|
+
// look up the invocation's session at execution time, not retain the first session.
|
|
121
|
+
client.middlewareStack.add((next, context) => args => {
|
|
122
|
+
const capture = sessionStorage.getStore()
|
|
123
|
+
if (capture?.client !== client || context.commandName !== 'InvokeModelWithBidirectionalStreamCommand') {
|
|
124
|
+
return next(args)
|
|
125
|
+
}
|
|
126
|
+
const { session, finish } = capture
|
|
127
|
+
const input = { ...args.input, body: observeIterable(args.input.body, session, true, finish) }
|
|
128
|
+
// Smithy passes the shared Command as args. Copy it before asynchronous middleware can
|
|
129
|
+
// read input, so overlapping sends never modify the command or observe another session.
|
|
130
|
+
return next({ ...args, input })
|
|
131
|
+
}, { name: 'datadogSonicInput', step: 'initialize', priority: 'high' })
|
|
132
|
+
instrumentedClients.add(client)
|
|
133
|
+
return true
|
|
134
|
+
} catch {
|
|
135
|
+
log.debug('Cannot observe Nova Sonic input')
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Called from the existing Smithy send seam. The bidi operation bypasses the ordinary AWS response
|
|
142
|
+
* accumulator: retaining every chunk there would retain a whole conversation twice.
|
|
143
|
+
* @param {Function} send
|
|
144
|
+
* @param {object} client
|
|
145
|
+
* @param {object} command
|
|
146
|
+
* @param {Array<object | Function>} args
|
|
147
|
+
*/
|
|
148
|
+
function sendSonic (send, client, command, args) {
|
|
149
|
+
const instrumented = instrumentInput(client)
|
|
150
|
+
const passthrough = () => sessionStorage.run(undefined, () => send.call(client, command, ...args))
|
|
151
|
+
if (!instrumented || !captureChannel.hasSubscribers) return passthrough()
|
|
152
|
+
const model = resolveModel(command.input?.modelId)
|
|
153
|
+
if (!model) return passthrough()
|
|
154
|
+
const context = {}
|
|
155
|
+
captureChannel.publish(context)
|
|
156
|
+
// Only the LLMObs plugin enables capture; the tracing plugin alone does not buffer conversations.
|
|
157
|
+
if (!context.enabled || !context.runInContext) return passthrough()
|
|
158
|
+
const session = new SonicSession(descriptor => emitTurn(descriptor, context.runInContext, model))
|
|
159
|
+
const signal = args[0] !== null && typeof args[0] === 'object' ? args[0]?.abortSignal : undefined
|
|
160
|
+
// Smithy's legacy AbortSignal only exposes onabort; leave that slot to the SDK's transport.
|
|
161
|
+
const hasListeners = typeof signal?.addEventListener === 'function' &&
|
|
162
|
+
typeof signal?.removeEventListener === 'function'
|
|
163
|
+
const finish = error => {
|
|
164
|
+
if (hasListeners) signal.removeEventListener('abort', onAbort)
|
|
165
|
+
// Aborting an already delivered HTTP/2 response can surface as clean iterator EOF.
|
|
166
|
+
if (!error && signal?.aborted) {
|
|
167
|
+
error = new Error('Nova Sonic request aborted')
|
|
168
|
+
error.name = 'AbortError'
|
|
169
|
+
}
|
|
170
|
+
session.finish(error)
|
|
171
|
+
}
|
|
172
|
+
const onAbort = () => finish()
|
|
173
|
+
if (signal?.aborted) onAbort()
|
|
174
|
+
else if (hasListeners) signal.addEventListener('abort', onAbort, { once: true })
|
|
175
|
+
|
|
176
|
+
const completed = result => {
|
|
177
|
+
try {
|
|
178
|
+
if (typeof result?.body?.[Symbol.asyncIterator] === 'function') {
|
|
179
|
+
result.body = observeIterable(result.body, session, false, finish)
|
|
180
|
+
} else {
|
|
181
|
+
finish()
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
finish()
|
|
185
|
+
log.debug('Cannot observe Nova Sonic output')
|
|
186
|
+
}
|
|
187
|
+
return result
|
|
188
|
+
}
|
|
189
|
+
const failed = error => {
|
|
190
|
+
finish(error)
|
|
191
|
+
throw error
|
|
192
|
+
}
|
|
193
|
+
const callback = args.at(-1)
|
|
194
|
+
if (typeof callback === 'function') {
|
|
195
|
+
args[args.length - 1] = function (error, result) {
|
|
196
|
+
if (error) finish(error)
|
|
197
|
+
else completed(result)
|
|
198
|
+
return callback.apply(this, arguments)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const result = sessionStorage.run({ client, session, finish }, () => send.call(client, command, ...args))
|
|
203
|
+
return typeof callback === 'function' ? result : result.then(completed, failed)
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return failed(error)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = sendSonic
|