dd-trace 6.16.0 → 6.18.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 +0 -23
- package/ci/test-optimization-validation/approval-artifacts.js +0 -3
- package/ci/test-optimization-validation/approval.js +0 -6
- package/ci/test-optimization-validation/artifact-id.js +0 -1
- package/ci/test-optimization-validation/bounded-json.js +0 -1
- package/ci/test-optimization-validation/cli.js +0 -12
- package/ci/test-optimization-validation/command-output-policy.js +0 -1
- package/ci/test-optimization-validation/command-runner.js +0 -3
- package/ci/test-optimization-validation/environment.js +0 -4
- package/ci/test-optimization-validation/executable-approval.js +0 -1
- package/ci/test-optimization-validation/executable.js +0 -5
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +0 -6
- package/ci/test-optimization-validation/framework-adapters/cypress.js +0 -3
- package/ci/test-optimization-validation/framework-adapters/playwright.js +0 -6
- package/ci/test-optimization-validation/framework-adapters/vitest.js +0 -4
- package/ci/test-optimization-validation/generated-test-contract.js +0 -5
- package/ci/test-optimization-validation/generated-verifier.js +0 -1
- package/ci/test-optimization-validation/literal-environment.js +0 -1
- package/ci/test-optimization-validation/manifest-scaffold.js +0 -17
- package/ci/test-optimization-validation/manifest-schema.js +0 -21
- package/ci/test-optimization-validation/offline-fixtures.js +0 -4
- package/ci/test-optimization-validation/plan-writer.js +0 -11
- package/ci/test-optimization-validation/preflight-runner.js +0 -1
- package/ci/test-optimization-validation/redaction.js +0 -4
- package/ci/test-optimization-validation/report-writer.js +0 -16
- package/ci/test-optimization-validation/runner-command.js +0 -3
- package/ci/test-optimization-validation/runner-contract.js +0 -4
- package/ci/test-optimization-validation/safe-files.js +0 -1
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +0 -1
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +0 -1
- package/ci/test-optimization-validation/source-text.js +0 -3
- package/ci/vitest-no-worker-init-setup.mjs +41 -16
- package/index.d.ts +208 -0
- package/package.json +17 -16
- package/packages/datadog-esbuild/src/utils.js +0 -2
- package/packages/datadog-instrumentations/src/aerospike.js +28 -6
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -2
- package/packages/datadog-instrumentations/src/aws-sdk.js +0 -2
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +0 -2
- package/packages/datadog-instrumentations/src/console.js +97 -0
- package/packages/datadog-instrumentations/src/cucumber.js +77 -12
- package/packages/datadog-instrumentations/src/cypress-config.js +27 -38
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/channel.js +0 -2
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +0 -5
- package/packages/datadog-instrumentations/src/helpers/hooks.js +8 -6
- package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +0 -3
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +0 -6
- package/packages/datadog-instrumentations/src/helpers/register.js +24 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +111 -26
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +41 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +3 -17
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +55 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +36 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/supabase.js +128 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +20 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +388 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +0 -5
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +0 -1
- package/packages/datadog-instrumentations/src/jest.js +165 -51
- package/packages/datadog-instrumentations/src/knex.js +0 -1
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +0 -24
- package/packages/datadog-instrumentations/src/mariadb.js +0 -2
- package/packages/datadog-instrumentations/src/mocha/common.js +0 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +25 -26
- package/packages/datadog-instrumentations/src/mocha/utils.js +119 -49
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +0 -6
- package/packages/datadog-instrumentations/src/mocha/worker.js +14 -17
- package/packages/datadog-instrumentations/src/mysql2.js +0 -2
- package/packages/datadog-instrumentations/src/openai-realtime/audio-accumulator.js +241 -0
- package/packages/datadog-instrumentations/src/openai-realtime/events.js +76 -0
- package/packages/datadog-instrumentations/src/openai-realtime/index.js +301 -0
- package/packages/datadog-instrumentations/src/openai-realtime/session.js +998 -0
- package/packages/datadog-instrumentations/src/openai-realtime/turns.js +211 -0
- package/packages/datadog-instrumentations/src/openai.js +51 -12
- package/packages/datadog-instrumentations/src/oracledb.js +9 -3
- package/packages/datadog-instrumentations/src/playwright-reporter.js +6 -20
- package/packages/datadog-instrumentations/src/playwright.js +280 -56
- package/packages/datadog-instrumentations/src/{azure-cosmos.js → postgres.js} +1 -1
- package/packages/datadog-instrumentations/src/router.js +0 -2
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +0 -2
- package/packages/datadog-instrumentations/src/sequelize.js +0 -1
- package/packages/datadog-instrumentations/src/supabase.js +15 -0
- package/packages/datadog-instrumentations/src/undici.js +7 -3
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +18 -18
- package/packages/datadog-instrumentations/src/vitest-main.js +121 -30
- package/packages/datadog-instrumentations/src/vitest-util.js +7 -1
- package/packages/datadog-instrumentations/src/vitest-worker.js +49 -13
- package/packages/datadog-instrumentations/src/webdriverio.js +44 -47
- package/packages/datadog-instrumentations/src/ws.js +41 -2
- package/packages/datadog-plugin-ai/src/utils.js +0 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +0 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +0 -2
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +0 -1
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +0 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +0 -1
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +0 -1
- package/packages/datadog-plugin-cucumber/src/index.js +9 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +211 -85
- package/packages/datadog-plugin-cypress/src/finalization.js +0 -3
- package/packages/datadog-plugin-cypress/src/index.js +3 -1
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +0 -4
- package/packages/datadog-plugin-cypress/src/support.js +52 -7
- package/packages/datadog-plugin-graphql/src/resolve-error.js +1 -2
- package/packages/datadog-plugin-graphql/src/utils.js +0 -5
- package/packages/datadog-plugin-jest/src/index.js +12 -0
- package/packages/datadog-plugin-kafkajs/src/producer.js +0 -1
- package/packages/datadog-plugin-mocha/src/index.js +38 -11
- package/packages/datadog-plugin-mongodb-core/src/query.js +0 -1
- package/packages/datadog-plugin-openai/src/index.js +16 -1
- package/packages/datadog-plugin-openai/src/realtime.js +144 -0
- package/packages/datadog-plugin-openai-agents/src/integration.js +0 -4
- package/packages/datadog-plugin-openai-agents/src/util.js +0 -1
- package/packages/datadog-plugin-playwright/src/index.js +81 -9
- package/packages/datadog-plugin-postgres/src/index.js +167 -0
- package/packages/datadog-plugin-prisma/src/datadog-tracing-helper.js +0 -1
- package/packages/datadog-plugin-redis/src/index.js +15 -1
- package/packages/datadog-plugin-supabase/src/error.js +18 -0
- package/packages/datadog-plugin-supabase/src/index.js +21 -0
- package/packages/datadog-plugin-supabase/src/supabase-auth-js-gotrueclient-getuser.js +69 -0
- package/packages/datadog-plugin-supabase/src/supabase-functions-js-functionsclient-invoke.js +70 -0
- package/packages/datadog-plugin-supabase/src/supabase-postgrest-js-postgrestbuilder-then.js +122 -0
- package/packages/datadog-plugin-supabase/src/supabase-realtime-js-realtimechannel-send.js +66 -0
- package/packages/datadog-plugin-supabase/src/supabase-storage-js-handle-request.js +93 -0
- package/packages/datadog-plugin-supabase/src/url.js +17 -0
- package/packages/datadog-plugin-vitest/src/index.js +9 -0
- package/packages/datadog-plugin-ws/src/producer.js +1 -1
- package/packages/datadog-plugin-ws/src/util.js +0 -4
- package/packages/datadog-shimmer/src/shimmer.js +14 -10
- package/packages/datadog-turbopack/index.js +4 -7
- package/packages/datadog-turbopack/src/loader.js +6 -18
- package/packages/datadog-webpack/src/loader.js +0 -1
- package/packages/dd-trace/src/agent/info.js +5 -4
- package/packages/dd-trace/src/aiguard/client.js +2 -3
- package/packages/dd-trace/src/aiguard/evaluation.js +6 -12
- package/packages/dd-trace/src/aiguard/index.js +2 -2
- package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
- package/packages/dd-trace/src/aiguard/integrations/openai.js +81 -7
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +0 -2
- package/packages/dd-trace/src/aiguard/messages/openai.js +64 -1
- package/packages/dd-trace/src/aiguard/messages/utils.js +0 -1
- package/packages/dd-trace/src/aiguard/redaction.js +0 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/activation.js +1 -1
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +0 -5
- package/packages/dd-trace/src/appsec/api_security/sampler.js +0 -4
- package/packages/dd-trace/src/appsec/blocking/index.js +3 -3
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -4
- package/packages/dd-trace/src/appsec/iast/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +5 -5
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +3 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +8 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +4 -1
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +7 -7
- package/packages/dd-trace/src/appsec/index.js +2 -2
- package/packages/dd-trace/src/appsec/lambda.js +71 -2
- package/packages/dd-trace/src/appsec/rasp/utils.js +5 -1
- package/packages/dd-trace/src/appsec/remote_config.js +8 -6
- package/packages/dd-trace/src/appsec/reporter.js +10 -6
- package/packages/dd-trace/src/appsec/rule_manager.js +3 -3
- package/packages/dd-trace/src/appsec/sdk/track_event.js +0 -2
- package/packages/dd-trace/src/appsec/waf/index.js +2 -2
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +8 -5
- package/packages/dd-trace/src/carrier.js +0 -5
- package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +104 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +0 -2
- package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +34 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +0 -4
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +0 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +37 -15
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +0 -3
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +0 -11
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +0 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +0 -1
- package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +0 -6
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +2 -2
- package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +0 -1
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +0 -5
- package/packages/dd-trace/src/ci-visibility/requests/video-request.js +0 -1
- package/packages/dd-trace/src/ci-visibility/telemetry.js +13 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +0 -1
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +0 -2
- package/packages/dd-trace/src/ci-visibility/test-video.js +0 -1
- package/packages/dd-trace/src/config/defaults.js +3 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +88 -82
- package/packages/dd-trace/src/config/helper.js +0 -1
- package/packages/dd-trace/src/config/index.js +31 -30
- package/packages/dd-trace/src/config/parsers.js +34 -6
- package/packages/dd-trace/src/config/remote_config.js +25 -109
- package/packages/dd-trace/src/config/supported-configurations.json +150 -10
- package/packages/dd-trace/src/datastreams/encoding.js +0 -1
- package/packages/dd-trace/src/datastreams/pathway.js +0 -1
- package/packages/dd-trace/src/debugger/constants.js +10 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +62 -10
- package/packages/dd-trace/src/debugger/devtools_client/config.js +4 -1
- package/packages/dd-trace/src/debugger/devtools_client/guardrail-metrics.js +15 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +103 -27
- package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +50 -7
- package/packages/dd-trace/src/debugger/devtools_client/log.js +3 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +21 -8
- package/packages/dd-trace/src/debugger/devtools_client/send.js +41 -15
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +53 -13
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +118 -71
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +0 -3
- package/packages/dd-trace/src/debugger/devtools_client/status.js +21 -7
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +167 -0
- package/packages/dd-trace/src/debugger/index.js +51 -3
- package/packages/dd-trace/src/debugger/inspect-segment.js +0 -3
- package/packages/dd-trace/src/debugger/probe_sampler.js +133 -9
- package/packages/dd-trace/src/debugger/probe_sampler_constants.js +31 -1
- package/packages/dd-trace/src/dogstatsd.js +0 -23
- package/packages/dd-trace/src/encode/0.4-cross-payload.js +0 -3
- package/packages/dd-trace/src/encode/0.4.js +0 -7
- package/packages/dd-trace/src/encode/0.5.js +0 -2
- package/packages/dd-trace/src/encode/tags-processors.js +0 -1
- package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
- package/packages/dd-trace/src/evp_proxy/discovery.js +14 -11
- package/packages/dd-trace/src/evp_proxy/path.js +21 -9
- package/packages/dd-trace/src/exporters/agent/writer.js +0 -1
- package/packages/dd-trace/src/exporters/agentless/index.js +0 -1
- package/packages/dd-trace/src/exporters/agentless/intake.js +0 -1
- package/packages/dd-trace/src/exporters/agentless/writer.js +0 -3
- package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +0 -6
- package/packages/dd-trace/src/exporters/common/request.js +1 -0
- package/packages/dd-trace/src/exporters/common/url.js +33 -4
- package/packages/dd-trace/src/exporters/common/writer.js +0 -2
- package/packages/dd-trace/src/id.js +0 -11
- package/packages/dd-trace/src/llmobs/audio-codec.js +192 -0
- package/packages/dd-trace/src/llmobs/audio-utils.js +121 -0
- package/packages/dd-trace/src/llmobs/constants/audio.js +35 -0
- package/packages/dd-trace/src/llmobs/constants/prompts.js +14 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +17 -0
- package/packages/dd-trace/src/llmobs/eval-metric.js +0 -6
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +0 -4
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +0 -3
- package/packages/dd-trace/src/llmobs/experiments/index.js +7 -6
- package/packages/dd-trace/src/llmobs/experiments/util.js +1 -11
- package/packages/dd-trace/src/llmobs/index.js +1 -3
- package/packages/dd-trace/src/llmobs/noop.js +10 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +0 -4
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +0 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +0 -4
- package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/utils.js +0 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +0 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +4 -6
- package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +285 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +16 -3
- package/packages/dd-trace/src/llmobs/prompts/cache.js +272 -0
- package/packages/dd-trace/src/llmobs/prompts/manager.js +647 -0
- package/packages/dd-trace/src/llmobs/prompts/noop.js +29 -0
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +158 -0
- package/packages/dd-trace/src/llmobs/sdk.js +21 -6
- package/packages/dd-trace/src/llmobs/span_processor.js +81 -2
- package/packages/dd-trace/src/llmobs/tagger.js +39 -11
- package/packages/dd-trace/src/llmobs/telemetry.js +36 -4
- package/packages/dd-trace/src/llmobs/util.js +0 -34
- package/packages/dd-trace/src/llmobs/writers/base.js +0 -1
- package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
- package/packages/dd-trace/src/log/channels.js +3 -0
- package/packages/dd-trace/src/log/index.js +23 -5
- package/packages/dd-trace/src/log-submission/log-submission-plugin.js +74 -6
- package/packages/dd-trace/src/msgpack/chunk.js +0 -1
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +0 -12
- package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
- package/packages/dd-trace/src/openfeature/encoding.js +0 -2
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +0 -1
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
- package/packages/dd-trace/src/openfeature/index.js +6 -6
- package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +0 -1
- package/packages/dd-trace/src/openfeature/span-enrichment.js +0 -4
- package/packages/dd-trace/src/openfeature/writers/base.js +90 -23
- package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -5
- package/packages/dd-trace/src/openfeature/writers/util.js +104 -37
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +0 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +0 -1
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +0 -1
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +0 -2
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +0 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +0 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +0 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -14
- package/packages/dd-trace/src/opentelemetry/metrics/time.js +0 -1
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +30 -13
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +0 -1
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +0 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +0 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +0 -3
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +0 -6
- package/packages/dd-trace/src/opentracing/span_context.js +0 -1
- package/packages/dd-trace/src/opentracing/tracer.js +3 -3
- package/packages/dd-trace/src/otel-thread-ctx.js +38 -11
- package/packages/dd-trace/src/payload-tagging/tagging.js +15 -3
- package/packages/dd-trace/src/plugin_manager.js +27 -8
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -2
- package/packages/dd-trace/src/plugins/database.js +2 -6
- package/packages/dd-trace/src/plugins/index.js +7 -0
- package/packages/dd-trace/src/plugins/plugin.js +0 -4
- package/packages/dd-trace/src/plugins/tracing.js +1 -1
- package/packages/dd-trace/src/plugins/util/git.js +1 -3
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +0 -1
- package/packages/dd-trace/src/plugins/util/llm.js +0 -2
- package/packages/dd-trace/src/plugins/util/status-validator.js +0 -1
- package/packages/dd-trace/src/plugins/util/test.js +26 -20
- package/packages/dd-trace/src/plugins/util/url.js +0 -5
- package/packages/dd-trace/src/priority_sampler.js +0 -6
- package/packages/dd-trace/src/process-tags/index.js +0 -1
- package/packages/dd-trace/src/profiler.js +132 -35
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +0 -1
- package/packages/dd-trace/src/profiling/profiler.js +101 -41
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +29 -15
- package/packages/dd-trace/src/propagation-hash/index.js +0 -1
- package/packages/dd-trace/src/proxy.js +23 -47
- package/packages/dd-trace/src/random_sampler.js +0 -4
- package/packages/dd-trace/src/rate_limiter.js +0 -4
- package/packages/dd-trace/src/remote_config/capabilities.js +1 -9
- package/packages/dd-trace/src/remote_config/index.js +0 -1
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +0 -3
- package/packages/dd-trace/src/sampler.js +0 -4
- package/packages/dd-trace/src/sampling_rule.js +0 -7
- package/packages/dd-trace/src/serverless/vercel.js +0 -1
- package/packages/dd-trace/src/serverless.js +0 -1
- package/packages/dd-trace/src/service-naming/index.js +0 -1
- package/packages/dd-trace/src/service-naming/schemas/util.js +0 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +7 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +5 -0
- package/packages/dd-trace/src/span_processor.js +2 -2
- package/packages/dd-trace/src/span_sampler.js +0 -1
- package/packages/dd-trace/src/standalone/product.js +2 -2
- package/packages/dd-trace/src/standalone/tracesource.js +0 -1
- package/packages/dd-trace/src/startup-log.js +1 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +0 -8
- package/packages/dd-trace/src/telemetry/logs/index.js +16 -5
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +16 -6
- package/packages/dd-trace/src/telemetry/metrics.js +0 -2
- package/packages/dd-trace/src/telemetry/send-data.js +2 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +4 -1
- package/packages/dd-trace/src/tracer.js +5 -4
- package/packages/dd-trace/src/util.js +13 -1
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/packages/datadog-instrumentations/src/bullmq.js +0 -11
- package/packages/datadog-instrumentations/src/langchain.js +0 -7
- package/packages/datadog-instrumentations/src/langgraph.js +0 -7
- package/packages/datadog-instrumentations/src/mercurius.js +0 -11
|
@@ -55,7 +55,6 @@ function groupPartsByRole (parts) {
|
|
|
55
55
|
/**
|
|
56
56
|
* Check if parts contain thought/reasoning content
|
|
57
57
|
* @param {Array<{thought?: boolean}>} parts
|
|
58
|
-
* @returns {boolean}
|
|
59
58
|
*/
|
|
60
59
|
function hasThoughtParts (parts) {
|
|
61
60
|
return parts.some(part => part.thought === true)
|
|
@@ -65,7 +64,6 @@ function hasThoughtParts (parts) {
|
|
|
65
64
|
* Determine the role from a candidate and its parts
|
|
66
65
|
* @param {object} candidate
|
|
67
66
|
* @param {Array<{thought?: boolean}>} parts
|
|
68
|
-
* @returns {string}
|
|
69
67
|
*/
|
|
70
68
|
function determineRole (candidate, parts = []) {
|
|
71
69
|
// Check parts for thought indicators
|
|
@@ -84,7 +82,6 @@ function determineRole (candidate, parts = []) {
|
|
|
84
82
|
/**
|
|
85
83
|
* Normalize role to standard values
|
|
86
84
|
* @param {string} role
|
|
87
|
-
* @returns {string}
|
|
88
85
|
*/
|
|
89
86
|
function normalizeRole (role) {
|
|
90
87
|
if (role === ROLES.MODEL) return ROLES.ASSISTANT
|
|
@@ -520,7 +517,6 @@ function formatOutputMessages (response, isStreaming = false) {
|
|
|
520
517
|
/**
|
|
521
518
|
* Format embedding output from response
|
|
522
519
|
* @param {object} response
|
|
523
|
-
* @returns {string}
|
|
524
520
|
*/
|
|
525
521
|
function formatEmbeddingOutput (response) {
|
|
526
522
|
if (!response?.embeddings?.length) {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Formats tool call input as a JSON string.
|
|
5
5
|
* @param {string} toolName - The name of the tool being called
|
|
6
6
|
* @param {object} toolArguments - The arguments passed to the tool
|
|
7
|
-
* @returns {string} Formatted input string
|
|
8
7
|
*/
|
|
9
8
|
function formatInput (toolName, toolArguments) {
|
|
10
9
|
if (!toolName && !toolArguments) return ''
|
|
@@ -25,7 +24,6 @@ function formatInput (toolName, toolArguments) {
|
|
|
25
24
|
* MCP tool results contain a `content` array with items like:
|
|
26
25
|
* `[{ type: 'text', text: '...' }, { type: 'image', data: '...', mimeType: '...' }]`
|
|
27
26
|
* @param {object} result - The MCP CallToolResult
|
|
28
|
-
* @returns {string} JSON string of `{ content: Array<{type, text, annotations, meta}>, isError: boolean }`
|
|
29
27
|
*/
|
|
30
28
|
function formatOutput (result) {
|
|
31
29
|
if (!result) return ''
|
|
@@ -6,7 +6,6 @@ const INPUT_TYPE_TEXT = 'input_text'
|
|
|
6
6
|
|
|
7
7
|
const IMAGE_FALLBACK = '[image]'
|
|
8
8
|
const FILE_FALLBACK = '[file]'
|
|
9
|
-
const AUDIO_FALLBACK = '[audio]'
|
|
10
9
|
|
|
11
10
|
// OpenAI audio `format` values that don't map cleanly to `audio/<format>`.
|
|
12
11
|
const AUDIO_MIME_TYPES = {
|
|
@@ -19,6 +18,5 @@ module.exports = {
|
|
|
19
18
|
INPUT_TYPE_TEXT,
|
|
20
19
|
IMAGE_FALLBACK,
|
|
21
20
|
FILE_FALLBACK,
|
|
22
|
-
AUDIO_FALLBACK,
|
|
23
21
|
AUDIO_MIME_TYPES,
|
|
24
22
|
}
|
|
@@ -6,7 +6,8 @@ const {
|
|
|
6
6
|
PROMPT_MULTIMODAL,
|
|
7
7
|
INSTRUMENTATION_METHOD_AUTO,
|
|
8
8
|
} = require('../../constants/tags')
|
|
9
|
-
const { audioMimeTypeFromFormat, formatAudioPart
|
|
9
|
+
const { audioMimeTypeFromFormat, formatAudioPart } = require('../../audio-utils')
|
|
10
|
+
const { safeJsonParse } = require('../../util')
|
|
10
11
|
const { AUDIO_MIME_TYPES } = require('./constants')
|
|
11
12
|
const {
|
|
12
13
|
extractChatTemplateFromInstructions,
|
|
@@ -14,7 +15,7 @@ const {
|
|
|
14
15
|
extractTextFromContentItem,
|
|
15
16
|
extractContentParts,
|
|
16
17
|
hasMultimodalInputs,
|
|
17
|
-
|
|
18
|
+
getModelProviderAndClient,
|
|
18
19
|
} = require('./utils')
|
|
19
20
|
|
|
20
21
|
const allowedParamKeys = new Set([
|
|
@@ -108,10 +109,7 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
_getModelProviderAndClient (baseUrl = '') {
|
|
111
|
-
|
|
112
|
-
if (modelProvider === 'azure_openai') return { modelProvider, client: 'AzureOpenAI' }
|
|
113
|
-
if (modelProvider === 'deepseek') return { modelProvider, client: 'DeepSeek' }
|
|
114
|
-
return { modelProvider, client: 'OpenAI' }
|
|
112
|
+
return getModelProviderAndClient(baseUrl)
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
_extractMetrics (response) {
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
g711ToPcm16,
|
|
5
|
+
g711Variant,
|
|
6
|
+
isPcm16AudioMime,
|
|
7
|
+
pcm16ToWav,
|
|
8
|
+
} = require('../../audio-codec')
|
|
9
|
+
const { fitsInlineAudioBudget, formatAudioPartWithGuard } = require('../../audio-utils')
|
|
10
|
+
const {
|
|
11
|
+
AUDIO_FALLBACK,
|
|
12
|
+
G711_SAMPLE_RATE,
|
|
13
|
+
LLMOBS_AUDIO_INLINE_MAX_BYTES,
|
|
14
|
+
PCM16_BYTES_PER_SAMPLE,
|
|
15
|
+
WAV_HEADER_BYTES,
|
|
16
|
+
} = require('../../constants/audio')
|
|
17
|
+
const { storage: llmobsStorage } = require('../../storage')
|
|
18
|
+
const { safeJsonParse } = require('../../util')
|
|
19
|
+
const LLMObsPlugin = require('../base')
|
|
20
|
+
const { getModelProviderAndClient } = require('./utils')
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {import('../../audio-utils').AudioPart} AudioPart
|
|
24
|
+
* @typedef {{
|
|
25
|
+
* role: string,
|
|
26
|
+
* content: string,
|
|
27
|
+
* audioParts?: AudioPart[],
|
|
28
|
+
* toolCalls?: object[],
|
|
29
|
+
* toolResults?: object[],
|
|
30
|
+
* }} Message
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Turn a turn side's raw audio into a playable audio part.
|
|
35
|
+
*
|
|
36
|
+
* Realtime audio is raw PCM16 (24 kHz mono) by default, which the UI can't render, so it is wrapped
|
|
37
|
+
* in a WAV container — lossless, just a header. G.711 telephony audio, used by phone-call
|
|
38
|
+
* integrations, is decoded to PCM16 and likewise WAV-wrapped at its fixed 8 kHz rate. Anything else
|
|
39
|
+
* is passed to the size guard as-is, which keeps it only if it is renderable and within budget.
|
|
40
|
+
*
|
|
41
|
+
* Both conversions expand what they are given — G.711 doubles it, and either way a WAV header is
|
|
42
|
+
* prepended — so the budget is checked against the size the conversion *would* produce before
|
|
43
|
+
* running it. Checking afterwards, as the guard alone does, means decoding and copying megabytes
|
|
44
|
+
* only to discard them: the retention cap admits roughly twice what a G.711 turn can ever inline.
|
|
45
|
+
*
|
|
46
|
+
* @param {Buffer} audio
|
|
47
|
+
* @param {string} mimeType
|
|
48
|
+
* @param {number} sampleRate
|
|
49
|
+
* @param {number} maxBytes - Encoded bytes still available to this turn. See `setLLMObsTags`.
|
|
50
|
+
* @returns {AudioPart | undefined}
|
|
51
|
+
*/
|
|
52
|
+
function buildAudioPart (audio, mimeType, sampleRate, maxBytes) {
|
|
53
|
+
if (!audio.length) return
|
|
54
|
+
|
|
55
|
+
if (isPcm16AudioMime(mimeType)) {
|
|
56
|
+
if (!fitsInlineAudioBudget(audio.length + WAV_HEADER_BYTES, maxBytes)) return
|
|
57
|
+
return formatAudioPartWithGuard(pcm16ToWav(audio, sampleRate), 'audio/wav', maxBytes)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const variant = g711Variant(mimeType)
|
|
61
|
+
if (variant !== undefined) {
|
|
62
|
+
if (!fitsInlineAudioBudget(audio.length * PCM16_BYTES_PER_SAMPLE + WAV_HEADER_BYTES, maxBytes)) return
|
|
63
|
+
return formatAudioPartWithGuard(
|
|
64
|
+
pcm16ToWav(g711ToPcm16(audio, variant), G711_SAMPLE_RATE), 'audio/wav', maxBytes
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return formatAudioPartWithGuard(audio, mimeType, maxBytes)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Encoded size an already-built message contributes to the span event, which for audio is the
|
|
73
|
+
* base64 content itself.
|
|
74
|
+
*
|
|
75
|
+
* @param {Message | undefined} message
|
|
76
|
+
*/
|
|
77
|
+
function audioBytesOf (message) {
|
|
78
|
+
return message?.audioParts?.[0]?.content?.length ?? 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {object[]} toolCalls
|
|
83
|
+
* @returns {object[]}
|
|
84
|
+
*/
|
|
85
|
+
function parseToolCallArguments (toolCalls) {
|
|
86
|
+
return toolCalls.map(toolCall => ({ ...toolCall, arguments: safeJsonParse(toolCall.arguments ?? '', {}) }))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build the message for one side of a turn.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} role
|
|
93
|
+
* @param {import('../../../../../datadog-instrumentations/src/openai-realtime/session').TurnSide} side
|
|
94
|
+
* @param {number} maxAudioBytes - Encoded audio bytes still available to this turn.
|
|
95
|
+
* @returns {Message | undefined}
|
|
96
|
+
*/
|
|
97
|
+
function buildMessage (role, side, maxAudioBytes) {
|
|
98
|
+
const audioPart = buildAudioPart(side.audio, side.mimeType, side.sampleRate, maxAudioBytes)
|
|
99
|
+
|
|
100
|
+
let content = side.transcript || side.text
|
|
101
|
+
if (!content && audioPart === undefined && side.audioPresent) {
|
|
102
|
+
// Audio was captured but couldn't be turned into a playable part — an unsupported format, or
|
|
103
|
+
// over the size budget — and there is no transcript, so surface a marker rather than let the
|
|
104
|
+
// turn look silently empty.
|
|
105
|
+
content = AUDIO_FALLBACK
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const toolCalls = side.toolCalls?.length ? parseToolCallArguments(side.toolCalls) : undefined
|
|
109
|
+
const toolResults = side.toolResults.length ? side.toolResults : undefined
|
|
110
|
+
|
|
111
|
+
if (!content && audioPart === undefined && toolCalls === undefined && toolResults === undefined) return
|
|
112
|
+
|
|
113
|
+
/** @type {Message} */
|
|
114
|
+
const message = { role, content: content || '' }
|
|
115
|
+
if (audioPart !== undefined) message.audioParts = [audioPart]
|
|
116
|
+
if (toolCalls !== undefined) message.toolCalls = toolCalls
|
|
117
|
+
if (toolResults !== undefined) message.toolResults = toolResults
|
|
118
|
+
|
|
119
|
+
return message
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {{ input_tokens?: number, output_tokens?: number, total_tokens?: number } | undefined} usage
|
|
124
|
+
* @returns {Record<string, number> | undefined}
|
|
125
|
+
*/
|
|
126
|
+
function usageMetrics (usage) {
|
|
127
|
+
if (!usage) return
|
|
128
|
+
|
|
129
|
+
const { input_tokens: inputTokens, output_tokens: outputTokens } = usage
|
|
130
|
+
let { total_tokens: totalTokens } = usage
|
|
131
|
+
|
|
132
|
+
/** @type {Record<string, number>} */
|
|
133
|
+
const metrics = {}
|
|
134
|
+
if (inputTokens != null) metrics.input_tokens = inputTokens
|
|
135
|
+
if (outputTokens != null) metrics.output_tokens = outputTokens
|
|
136
|
+
// Mirror the chat and responses fallback.
|
|
137
|
+
if (totalTokens == null && inputTokens != null && outputTokens != null) {
|
|
138
|
+
totalTokens = inputTokens + outputTokens
|
|
139
|
+
}
|
|
140
|
+
if (totalTokens != null) metrics.total_tokens = totalTokens
|
|
141
|
+
|
|
142
|
+
return metrics.total_tokens === undefined && metrics.input_tokens === undefined ? undefined : metrics
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* LLM Observability spans for one OpenAI Realtime turn.
|
|
147
|
+
*
|
|
148
|
+
* The turn is modelled as a small tree — a `workflow` root with a `user speech` window, the `llm`
|
|
149
|
+
* generation span, and an `agent speech` window — so span duration is meaningful (the llm span
|
|
150
|
+
* measures model work, not the human's speaking time) and time-to-first-agent-audio falls out of the
|
|
151
|
+
* span boundaries. Consumers identify the phase spans by span kind and name, so those strings are a
|
|
152
|
+
* contract: renaming one is a breaking change for the UI and the backend metric alike.
|
|
153
|
+
*/
|
|
154
|
+
class RealtimeLLMObsPlugin extends LLMObsPlugin {
|
|
155
|
+
static integration = 'openai'
|
|
156
|
+
static system = 'openai'
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The instrumentation replays the turn with `traceSync`, which never publishes `asyncEnd`, so tag
|
|
160
|
+
* on `end` instead — before the sibling tracing plugin's `end` finishes the span, since the LLM
|
|
161
|
+
* Observability plugins are registered first.
|
|
162
|
+
*
|
|
163
|
+
* @param {object} ctx
|
|
164
|
+
*/
|
|
165
|
+
end (ctx) {
|
|
166
|
+
super.end(ctx)
|
|
167
|
+
super.asyncEnd(ctx)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** The whole perceived turn. Carries the transcripts so the waterfall row reads as a conversation. */
|
|
172
|
+
class RealtimeTurnLLMObsPlugin extends RealtimeLLMObsPlugin {
|
|
173
|
+
static id = 'openai_realtime_turn_llmobs'
|
|
174
|
+
static prefix = 'tracing:apm:openai:realtime:turn'
|
|
175
|
+
|
|
176
|
+
constructor (...args) {
|
|
177
|
+
super(...args)
|
|
178
|
+
|
|
179
|
+
this.addSub('dd-trace:openai:realtime:capture-context', turn => {
|
|
180
|
+
if (!this._tracerConfig.llmobs.DD_LLMOBS_ENABLED) return
|
|
181
|
+
|
|
182
|
+
// Composed on top of the tracing plugin's, so the replayed tree restores both the APM and the
|
|
183
|
+
// LLM Observability context the caller had active when the turn began.
|
|
184
|
+
const store = llmobsStorage.getStore()
|
|
185
|
+
const previous = turn.runInContext ?? (fn => fn())
|
|
186
|
+
turn.runInContext = fn => previous(() => llmobsStorage.run(store, fn))
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
191
|
+
return {
|
|
192
|
+
kind: 'workflow',
|
|
193
|
+
// Provider-agnostic `audio turn` marker, so a consumer can gate on "is this a voice turn?".
|
|
194
|
+
name: 'realtime audio turn',
|
|
195
|
+
sessionId: ctx.turn.sessionId,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
setLLMObsTags (ctx) {
|
|
200
|
+
const span = ctx.currentStore?.span
|
|
201
|
+
if (!span) return
|
|
202
|
+
|
|
203
|
+
const { input, output } = ctx.turn
|
|
204
|
+
this._tagger.tagTextIO(span, input.transcript || input.text, output.transcript || output.text)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The model's generation work. The turn's audio, tool calls and token usage ride here. */
|
|
209
|
+
class RealtimeResponseLLMObsPlugin extends RealtimeLLMObsPlugin {
|
|
210
|
+
static id = 'openai_realtime_response_llmobs'
|
|
211
|
+
static prefix = 'tracing:apm:openai:realtime:response'
|
|
212
|
+
|
|
213
|
+
constructor (...args) {
|
|
214
|
+
super(...args)
|
|
215
|
+
|
|
216
|
+
// This plugin is the only consumer of a turn's audio bytes, and the instrumentation keeps them
|
|
217
|
+
// only while something is subscribed here — a disabled plugin unsubscribes, so an APM-only
|
|
218
|
+
// process never buffers audio it cannot use. Nothing is ever published; the subscription is the
|
|
219
|
+
// signal. Keep it if `buildMessage` stops reading `side.audio`, and the buffering stops with it.
|
|
220
|
+
this.addSub('dd-trace:openai:realtime:audio', () => {})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
224
|
+
const { turn } = ctx
|
|
225
|
+
const { modelProvider, client } = getModelProviderAndClient(turn.basePath)
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
kind: 'llm',
|
|
229
|
+
name: `${client}.createRealtimeResponse`,
|
|
230
|
+
modelName: turn.model || 'unknown_model',
|
|
231
|
+
modelProvider,
|
|
232
|
+
sessionId: turn.sessionId,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
setLLMObsTags (ctx) {
|
|
237
|
+
const span = ctx.currentStore?.span
|
|
238
|
+
if (!span) return
|
|
239
|
+
|
|
240
|
+
const { turn } = ctx
|
|
241
|
+
|
|
242
|
+
// One budget for the whole event, spent across both sides rather than offered to each.
|
|
243
|
+
// A turn normally carries audio on both sides, so a per-message budget lets two individually
|
|
244
|
+
// accepted clips add up to twice the limit — and `writers/spans.js` responds by truncating the
|
|
245
|
+
// event's *entire* input and output, losing the transcripts too. Input is served first and the
|
|
246
|
+
// output falls back to its transcript when nothing is left, which is the same graceful
|
|
247
|
+
// degradation an oversize single clip already gets.
|
|
248
|
+
const inputMessage = buildMessage('user', turn.input, LLMOBS_AUDIO_INLINE_MAX_BYTES)
|
|
249
|
+
const remainingAudioBytes = LLMOBS_AUDIO_INLINE_MAX_BYTES - audioBytesOf(inputMessage)
|
|
250
|
+
const outputMessage = buildMessage('assistant', turn.output, remainingAudioBytes)
|
|
251
|
+
|
|
252
|
+
this._tagger.tagLLMIO(span, inputMessage ? [inputMessage] : [], outputMessage ? [outputMessage] : [])
|
|
253
|
+
this._tagger.tagMetadata(span, turn.metadata)
|
|
254
|
+
|
|
255
|
+
const metrics = usageMetrics(turn.usage)
|
|
256
|
+
if (metrics !== undefined) this._tagger.tagMetrics(span, metrics)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** A speaking window — the human's or the agent's. These are timing regions; the audio rides on the llm span. */
|
|
261
|
+
class RealtimeSpeechLLMObsPlugin extends RealtimeLLMObsPlugin {
|
|
262
|
+
static id = 'openai_realtime_speech_llmobs'
|
|
263
|
+
static prefix = 'tracing:apm:openai:realtime:speech'
|
|
264
|
+
|
|
265
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
266
|
+
return {
|
|
267
|
+
kind: 'workflow',
|
|
268
|
+
name: ctx.phase.llmobsName,
|
|
269
|
+
sessionId: ctx.turn.sessionId,
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
setLLMObsTags (ctx) {
|
|
274
|
+
const span = ctx.currentStore?.span
|
|
275
|
+
if (!span) return
|
|
276
|
+
|
|
277
|
+
this._tagger.tagTextIO(span, undefined, ctx.transcript)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = [
|
|
282
|
+
RealtimeTurnLLMObsPlugin,
|
|
283
|
+
RealtimeResponseLLMObsPlugin,
|
|
284
|
+
RealtimeSpeechLLMObsPlugin,
|
|
285
|
+
]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { audioMimeTypeFromFormat, formatAudioPart } = require('../../audio-utils')
|
|
4
|
+
const { AUDIO_FALLBACK } = require('../../constants/audio')
|
|
3
5
|
const { UNKNOWN_MODEL_PROVIDER } = require('../../constants/tags')
|
|
4
|
-
const { audioMimeTypeFromFormat, formatAudioPart } = require('../../util')
|
|
5
6
|
const {
|
|
6
7
|
INPUT_TYPE_IMAGE,
|
|
7
8
|
INPUT_TYPE_FILE,
|
|
8
9
|
IMAGE_FALLBACK,
|
|
9
10
|
FILE_FALLBACK,
|
|
10
|
-
AUDIO_FALLBACK,
|
|
11
11
|
AUDIO_MIME_TYPES,
|
|
12
12
|
} = require('./constants')
|
|
13
13
|
|
|
@@ -177,7 +177,6 @@ function extractContentParts (parts) {
|
|
|
177
177
|
* client baseURL convention.
|
|
178
178
|
*
|
|
179
179
|
* @param {string} baseUrl
|
|
180
|
-
* @returns {string}
|
|
181
180
|
*/
|
|
182
181
|
function getOpenAIModelProvider (baseUrl = '') {
|
|
183
182
|
if (baseUrl.includes('azure')) return 'azure_openai'
|
|
@@ -186,11 +185,25 @@ function getOpenAIModelProvider (baseUrl = '') {
|
|
|
186
185
|
return UNKNOWN_MODEL_PROVIDER
|
|
187
186
|
}
|
|
188
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Pair an OpenAI-compatible base URL's model provider with the client label used in span names.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} [baseUrl]
|
|
192
|
+
* @returns {{ modelProvider: string, client: string }}
|
|
193
|
+
*/
|
|
194
|
+
function getModelProviderAndClient (baseUrl = '') {
|
|
195
|
+
const modelProvider = getOpenAIModelProvider(baseUrl)
|
|
196
|
+
if (modelProvider === 'azure_openai') return { modelProvider, client: 'AzureOpenAI' }
|
|
197
|
+
if (modelProvider === 'deepseek') return { modelProvider, client: 'DeepSeek' }
|
|
198
|
+
return { modelProvider, client: 'OpenAI' }
|
|
199
|
+
}
|
|
200
|
+
|
|
189
201
|
module.exports = {
|
|
190
202
|
extractChatTemplateFromInstructions,
|
|
191
203
|
normalizePromptVariables,
|
|
192
204
|
extractTextFromContentItem,
|
|
193
205
|
extractContentParts,
|
|
194
206
|
hasMultimodalInputs,
|
|
207
|
+
getModelProviderAndClient,
|
|
195
208
|
getOpenAIModelProvider,
|
|
196
209
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { createHash, randomUUID } = require('node:crypto')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const os = require('node:os')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
|
|
8
|
+
const { LRUCache } = require('../../../../../vendor/dist/lru-cache')
|
|
9
|
+
|
|
10
|
+
const log = require('../../log')
|
|
11
|
+
const ManagedPrompt = require('./prompt')
|
|
12
|
+
|
|
13
|
+
const CACHE_SUBPATH = path.join('datadog', 'llmobs', 'prompts')
|
|
14
|
+
const MAX_HOT_ENTRIES = 1024
|
|
15
|
+
const PROMPT_SOURCES = new Set(['registry', 'cache', 'fallback', 'ff', 'resolve'])
|
|
16
|
+
|
|
17
|
+
/** @param {string} key */
|
|
18
|
+
function promptIdFromKey (key) {
|
|
19
|
+
return key.slice(0, key.lastIndexOf(':'))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} promptId
|
|
24
|
+
* @param {Array<unknown>} selector
|
|
25
|
+
*/
|
|
26
|
+
function cacheKey (promptId, selector) {
|
|
27
|
+
const hash = createHash('sha1').update(JSON.stringify(selector)).digest('hex').slice(0, 16)
|
|
28
|
+
return `${promptId}:${hash}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function defaultCacheDir () {
|
|
32
|
+
try {
|
|
33
|
+
const home = os.homedir()
|
|
34
|
+
if (home) return path.join(home, '.cache', CACHE_SUBPATH)
|
|
35
|
+
} catch {}
|
|
36
|
+
return path.join(os.tmpdir(), CACHE_SUBPATH)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class HotCache {
|
|
40
|
+
/**
|
|
41
|
+
* Create the bounded in-memory prompt cache.
|
|
42
|
+
* @param {object} options
|
|
43
|
+
* @param {number} options.ttlMs
|
|
44
|
+
* @param {(key: string, stale: ManagedPrompt | undefined, options: {context: object}) =>
|
|
45
|
+
* Promise<ManagedPrompt | undefined>} [options.fetchMethod]
|
|
46
|
+
*/
|
|
47
|
+
constructor ({ ttlMs, fetchMethod }) {
|
|
48
|
+
this.enabled = ttlMs > 0
|
|
49
|
+
this.cache = new LRUCache({
|
|
50
|
+
max: MAX_HOT_ENTRIES,
|
|
51
|
+
ttl: this.enabled && Number.isFinite(ttlMs) ? ttlMs : undefined,
|
|
52
|
+
allowStale: true,
|
|
53
|
+
noDeleteOnStaleGet: true,
|
|
54
|
+
fetchMethod,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read a prompt and its freshness.
|
|
60
|
+
* @param {string} key
|
|
61
|
+
*/
|
|
62
|
+
get (key) {
|
|
63
|
+
if (!this.enabled) return
|
|
64
|
+
const status = {}
|
|
65
|
+
const prompt = this.cache.get(key, { status })
|
|
66
|
+
if (!prompt) return
|
|
67
|
+
return { prompt, stale: status.get === 'stale' }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Store a prompt.
|
|
72
|
+
* @param {string} key
|
|
73
|
+
* @param {ManagedPrompt} prompt
|
|
74
|
+
* @param {number} [ageMs]
|
|
75
|
+
*/
|
|
76
|
+
set (key, prompt, ageMs = 0) {
|
|
77
|
+
if (this.enabled) this.cache.set(key, prompt, { start: performance.now() - ageMs })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Refresh a stale prompt in the background.
|
|
82
|
+
* @param {string} key
|
|
83
|
+
* @param {object} context
|
|
84
|
+
*/
|
|
85
|
+
refresh (key, context) {
|
|
86
|
+
if (!this.enabled) return
|
|
87
|
+
void this.cache.fetch(key, {
|
|
88
|
+
allowStaleOnFetchRejection: true,
|
|
89
|
+
context,
|
|
90
|
+
forceRefresh: true,
|
|
91
|
+
noDeleteOnFetchRejection: true,
|
|
92
|
+
}).catch(() => {})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Delete one selector.
|
|
97
|
+
* @param {string} key
|
|
98
|
+
*/
|
|
99
|
+
delete (key) {
|
|
100
|
+
if (this.enabled) this.cache.delete(key)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Clear all hot prompt entries.
|
|
105
|
+
*/
|
|
106
|
+
clear () {
|
|
107
|
+
if (this.enabled) this.cache.clear()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Evict every selector for one exact prompt ID.
|
|
112
|
+
* @param {string} promptId
|
|
113
|
+
*/
|
|
114
|
+
evictPrompt (promptId) {
|
|
115
|
+
if (!this.enabled) return
|
|
116
|
+
for (const [key] of this.cache.dump()) {
|
|
117
|
+
if (promptIdFromKey(key) === promptId) this.cache.delete(key)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class WarmCache {
|
|
123
|
+
/**
|
|
124
|
+
* Create the optional filesystem prompt cache.
|
|
125
|
+
* @param {object} options
|
|
126
|
+
* @param {string | undefined} options.cacheDir
|
|
127
|
+
* @param {boolean} [options.enabled]
|
|
128
|
+
* @param {number} options.ttlMs
|
|
129
|
+
*/
|
|
130
|
+
constructor ({ cacheDir, enabled = true, ttlMs }) {
|
|
131
|
+
this.enabled = enabled && ttlMs > 0
|
|
132
|
+
this.ttlMs = ttlMs
|
|
133
|
+
this.cacheDir = cacheDir || defaultCacheDir()
|
|
134
|
+
if (this.enabled) this.#ensureDir(this.cacheDir)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create a secure cache directory.
|
|
139
|
+
* @param {string} directory
|
|
140
|
+
*/
|
|
141
|
+
#ensureDir (directory) {
|
|
142
|
+
try {
|
|
143
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 })
|
|
144
|
+
} catch (error) {
|
|
145
|
+
log.warn('Failed to create prompt cache directory: %s', error.message)
|
|
146
|
+
this.enabled = false
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Resolve the collision-safe directory for a prompt ID.
|
|
152
|
+
* @param {string} promptId
|
|
153
|
+
*/
|
|
154
|
+
#promptDir (promptId) {
|
|
155
|
+
return path.join(this.cacheDir, Buffer.from(promptId).toString('base64url') || '_')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolve the cache file for a selector key.
|
|
160
|
+
* @param {string} key
|
|
161
|
+
*/
|
|
162
|
+
#path (key) {
|
|
163
|
+
const index = key.lastIndexOf(':')
|
|
164
|
+
return path.join(this.#promptDir(key.slice(0, index)), `${key.slice(index + 1)}.json`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Restore and validate a prompt from the warm cache.
|
|
169
|
+
* @param {object} data
|
|
170
|
+
*/
|
|
171
|
+
#deserialize (data) {
|
|
172
|
+
const validTemplate = typeof data?.template === 'string' || (
|
|
173
|
+
Array.isArray(data?.template) && data.template.every(message => {
|
|
174
|
+
return message && typeof message.role === 'string' && typeof message.content === 'string'
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
if (
|
|
178
|
+
typeof data?.id !== 'string' ||
|
|
179
|
+
typeof data.version !== 'string' ||
|
|
180
|
+
!PROMPT_SOURCES.has(data.source) ||
|
|
181
|
+
!validTemplate ||
|
|
182
|
+
(data.promptUuid !== undefined && typeof data.promptUuid !== 'string') ||
|
|
183
|
+
(data.promptVersionUuid !== undefined && typeof data.promptVersionUuid !== 'string')
|
|
184
|
+
) {
|
|
185
|
+
throw new TypeError('Invalid managed prompt cache entry')
|
|
186
|
+
}
|
|
187
|
+
return new ManagedPrompt(data)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Read a prompt and its freshness.
|
|
192
|
+
* @param {string} key
|
|
193
|
+
*/
|
|
194
|
+
get (key) {
|
|
195
|
+
if (!this.enabled) return
|
|
196
|
+
try {
|
|
197
|
+
const data = JSON.parse(fs.readFileSync(this.#path(key), 'utf8'))
|
|
198
|
+
if (!Number.isFinite(data.timestamp)) throw new TypeError('Invalid prompt cache timestamp')
|
|
199
|
+
const prompt = this.#deserialize(data.prompt)
|
|
200
|
+
const ageMs = Math.max(0, Date.now() - data.timestamp)
|
|
201
|
+
return { prompt, stale: ageMs > this.ttlMs, ageMs }
|
|
202
|
+
} catch (error) {
|
|
203
|
+
log.debug('Failed to read prompt from cache: %s', error.message)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Store a prompt with restrictive permissions.
|
|
209
|
+
* @param {string} key
|
|
210
|
+
* @param {ManagedPrompt} prompt
|
|
211
|
+
*/
|
|
212
|
+
set (key, prompt) {
|
|
213
|
+
if (!this.enabled) return
|
|
214
|
+
const file = this.#path(key)
|
|
215
|
+
const temporary = `${file}.tmp.${randomUUID()}`
|
|
216
|
+
try {
|
|
217
|
+
this.#ensureDir(path.dirname(file))
|
|
218
|
+
if (!this.enabled) return
|
|
219
|
+
// Write separately, then rename so other processes see either the old file or the complete new one.
|
|
220
|
+
fs.writeFileSync(temporary, JSON.stringify({ prompt, timestamp: Date.now() }), {
|
|
221
|
+
encoding: 'utf8',
|
|
222
|
+
mode: 0o600,
|
|
223
|
+
})
|
|
224
|
+
fs.renameSync(temporary, file)
|
|
225
|
+
} catch (error) {
|
|
226
|
+
try { fs.rmSync(temporary, { force: true }) } catch {}
|
|
227
|
+
log.debug('Failed to write prompt to cache: %s', error.message)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Delete one selector.
|
|
233
|
+
* @param {string} key
|
|
234
|
+
*/
|
|
235
|
+
delete (key) {
|
|
236
|
+
if (!this.enabled) return
|
|
237
|
+
try {
|
|
238
|
+
fs.rmSync(this.#path(key), { force: true })
|
|
239
|
+
} catch (error) {
|
|
240
|
+
log.debug('Failed to delete prompt from cache: %s', error.message)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Evict every selector for one exact prompt ID.
|
|
246
|
+
* @param {string} promptId
|
|
247
|
+
*/
|
|
248
|
+
evictPrompt (promptId) {
|
|
249
|
+
if (!this.enabled) return
|
|
250
|
+
try {
|
|
251
|
+
fs.rmSync(this.#promptDir(promptId), { recursive: true, force: true })
|
|
252
|
+
} catch (error) {
|
|
253
|
+
log.debug('Failed to evict prompt from cache: %s', error.message)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Clear all warm prompt entries.
|
|
259
|
+
*/
|
|
260
|
+
clear () {
|
|
261
|
+
if (!this.enabled) return
|
|
262
|
+
try {
|
|
263
|
+
for (const entry of fs.readdirSync(this.cacheDir)) {
|
|
264
|
+
fs.rmSync(path.join(this.cacheDir, entry), { recursive: true, force: true })
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
log.debug('Failed to clear prompt cache: %s', error.message)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = { HotCache, WarmCache, cacheKey, promptIdFromKey }
|