diff --git a/.github/actions/datadog-ci/action.yml b/.github/actions/datadog-ci/action.yml
index 13924ed071..f5a5b01c11 100644
--- a/.github/actions/datadog-ci/action.yml
+++ b/.github/actions/datadog-ci/action.yml
@@ -4,7 +4,7 @@ description: Install @datadog/datadog-ci from npm and add it to PATH.
runs:
using: composite
steps:
- - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '20'
diff --git a/.github/actions/node/action.yml b/.github/actions/node/action.yml
index c1513f2c5f..355690ba13 100644
--- a/.github/actions/node/action.yml
+++ b/.github/actions/node/action.yml
@@ -5,6 +5,10 @@ inputs:
description: "Version identifier of the version to use."
required: false
default: 'latest'
+ registry-url:
+ description: "npm registry URL to configure for publishing. Leave unset for jobs that don't publish."
+ required: false
+ default: ''
runs:
using: composite
steps:
@@ -15,6 +19,7 @@ runs:
continue-on-error: true
with:
version: ${{ inputs.version }}
+ registry-url: ${{ inputs.registry-url }}
- if: steps.attempt.outcome == 'failure'
shell: bash
run: sleep 60
@@ -23,3 +28,4 @@ runs:
uses: ./.github/actions/node/setup
with:
version: ${{ inputs.version }}
+ registry-url: ${{ inputs.registry-url }}
diff --git a/.github/actions/node/setup/action.yml b/.github/actions/node/setup/action.yml
index ef54965603..490dd98173 100644
--- a/.github/actions/node/setup/action.yml
+++ b/.github/actions/node/setup/action.yml
@@ -5,6 +5,10 @@ inputs:
description: "Version identifier of the version to use."
required: false
default: 'latest'
+ registry-url:
+ description: "npm registry URL to configure for publishing. Leave unset for jobs that don't publish."
+ required: false
+ default: ''
runs:
using: composite
steps:
@@ -41,10 +45,14 @@ runs:
esac
echo "version=$version" >> "$GITHUB_OUTPUT"
- - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
+ # registry-url is left empty for jobs that don't publish: setup-node only writes an
+ # .npmrc auth placeholder when it's set, and since v7.0.0 no longer backstops that
+ # placeholder with a dummy NODE_AUTH_TOKEN, so an always-on registry-url broke Yarn
+ # Classic (it hard-errors on an unresolved ${NODE_AUTH_TOKEN} env reference in .npmrc).
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ steps.node-version.outputs.version }}
- registry-url: ${{ inputs.registry-url || 'https://registry.npmjs.org' }}
+ registry-url: ${{ inputs.registry-url }}
# The shipped package.json pins engines.node to the supported runtime range, but CI keeps
# running the full suite on Node 18/20. Widen the field to >=18 for this checkout so the
diff --git a/.github/actions/testagent/logs/action.yml b/.github/actions/testagent/logs/action.yml
index 91a24f4aae..6434afb021 100644
--- a/.github/actions/testagent/logs/action.yml
+++ b/.github/actions/testagent/logs/action.yml
@@ -10,7 +10,7 @@ inputs:
runs:
using: composite
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Preserve untracked artifacts (coverage/, .nyc_output/, node_modules/) produced by earlier
# test steps. Without this, the default `git clean -ffdx` wipes them before subsequent
diff --git a/.github/actions/testagent/start/action.yml b/.github/actions/testagent/start/action.yml
index 3d42d42d76..b53bb2de5b 100644
--- a/.github/actions/testagent/start/action.yml
+++ b/.github/actions/testagent/start/action.yml
@@ -3,7 +3,7 @@ description: "Starts the APM Test Agent image with environment."
runs:
using: composite
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: docker compose up -d testagent || docker compose up -d testagent
shell: bash
- name: Wait for test agent to be ready
diff --git a/.github/workflows/aiguard.yml b/.github/workflows/aiguard.yml
index 8e7eadfa86..3232e1331f 100644
--- a/.github/workflows/aiguard.yml
+++ b/.github/workflows/aiguard.yml
@@ -18,7 +18,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:aiguard:ci
@@ -36,7 +36,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:aiguard:ci
@@ -60,7 +60,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: 24.14.1 # TODO: remove pin when https://github.com/nodejs/node/issues/62991 is fixed
@@ -86,7 +86,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
diff --git a/.github/workflows/all-green.yml b/.github/workflows/all-green.yml
index 303e6bd174..83b8e3a575 100644
--- a/.github/workflows/all-green.yml
+++ b/.github/workflows/all-green.yml
@@ -22,7 +22,7 @@ jobs:
with:
scope: DataDog/dd-trace-js
policy: all-green
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout-cone-mode: false
sparse-checkout: |
diff --git a/.github/workflows/apm-capabilities.yml b/.github/workflows/apm-capabilities.yml
index b1f3016d62..d30a334d27 100644
--- a/.github/workflows/apm-capabilities.yml
+++ b/.github/workflows/apm-capabilities.yml
@@ -26,7 +26,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:trace:core:ci
@@ -47,7 +47,7 @@ jobs:
matrix:
node-version: [oldest, maintenance, active, latest]
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
@@ -66,7 +66,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: 24.14.1 # TODO: remove pin when https://github.com/nodejs/node/issues/62991 is fixed
diff --git a/.github/workflows/apm-integrations.yml b/.github/workflows/apm-integrations.yml
index d27e767385..4ad0daa44d 100644
--- a/.github/workflows/apm-integrations.yml
+++ b/.github/workflows/apm-integrations.yml
@@ -72,7 +72,7 @@ jobs:
SERVICES: aerospike
PACKAGE_VERSION_RANGE: ${{ matrix.range }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
@@ -106,7 +106,7 @@ jobs:
SERVICES: qpid
DD_DATA_STREAMS_ENABLED: true
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
amqplib:
@@ -122,7 +122,7 @@ jobs:
PLUGINS: amqplib
SERVICES: rabbitmq
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
apollo:
@@ -132,7 +132,7 @@ jobs:
env:
PLUGINS: apollo
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
avsc:
@@ -143,7 +143,7 @@ jobs:
PLUGINS: avsc
DD_DATA_STREAMS_ENABLED: true
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
axios:
@@ -153,7 +153,7 @@ jobs:
env:
PLUGINS: axios
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/upstream
# `plugins/upstream` only runs `test:plugins:upstream`, which is a non-glob suite runner;
# the in-tree `test/integration-test/*.spec.js` files would otherwise have no CI invocation
@@ -167,7 +167,7 @@ jobs:
env:
PLUGINS: body-parser
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/upstream
- uses: ./.github/actions/plugins/integration-test
@@ -184,7 +184,7 @@ jobs:
PLUGINS: bullmq
SERVICES: redis
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
bunyan:
@@ -194,7 +194,7 @@ jobs:
env:
PLUGINS: bunyan
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
cassandra:
@@ -210,7 +210,7 @@ jobs:
PLUGINS: cassandra-driver
SERVICES: cassandra
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
child_process:
@@ -220,7 +220,7 @@ jobs:
env:
PLUGINS: child_process
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -245,7 +245,7 @@ jobs:
env:
PLUGINS: crypto
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
confluentinc-kafka-javascript:
@@ -290,7 +290,7 @@ jobs:
SERVICES: kafka
PACKAGE_VERSION_RANGE: ${{ matrix.range }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
@@ -311,7 +311,7 @@ jobs:
env:
PLUGINS: cookie-parser
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
cookie:
@@ -321,7 +321,7 @@ jobs:
env:
PLUGINS: cookie
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
couchbase:
@@ -350,7 +350,7 @@ jobs:
PACKAGE_VERSION_RANGE: ${{ matrix.range }}
DD_INJECT_FORCE: "true"
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
@@ -372,7 +372,7 @@ jobs:
env:
PLUGINS: connect
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
dns:
@@ -382,7 +382,7 @@ jobs:
env:
PLUGINS: dns
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -415,7 +415,7 @@ jobs:
PLUGINS: elasticsearch
SERVICES: elasticsearch
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -434,7 +434,7 @@ jobs:
env:
PLUGINS: electron
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node
with:
@@ -479,7 +479,7 @@ jobs:
env:
PLUGINS: express
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -494,7 +494,7 @@ jobs:
env:
PLUGINS: express-mongo-sanitize
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
express-session:
@@ -504,7 +504,7 @@ jobs:
env:
PLUGINS: express-session
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
fastify:
@@ -514,7 +514,7 @@ jobs:
env:
PLUGINS: fastify
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
fetch:
@@ -524,7 +524,7 @@ jobs:
env:
PLUGINS: fetch
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
fs:
@@ -534,7 +534,7 @@ jobs:
env:
PLUGINS: fs
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
generic-pool:
@@ -544,7 +544,7 @@ jobs:
env:
PLUGINS: generic-pool
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
graphql:
@@ -554,7 +554,7 @@ jobs:
env:
PLUGINS: graphql
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
grpc:
@@ -564,7 +564,7 @@ jobs:
env:
PLUGINS: grpc
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
handlebars:
@@ -574,7 +574,7 @@ jobs:
env:
PLUGINS: handlebars
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
hapi:
@@ -584,7 +584,7 @@ jobs:
env:
PLUGINS: hapi
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
hono:
@@ -594,7 +594,7 @@ jobs:
env:
PLUGINS: hono
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
http:
@@ -608,7 +608,7 @@ jobs:
env:
PLUGINS: http
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
@@ -629,7 +629,7 @@ jobs:
env:
PLUGINS: http2
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -682,7 +682,7 @@ jobs:
PLUGINS: kafkajs
SERVICES: kafka
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
@@ -703,7 +703,7 @@ jobs:
env:
PLUGINS: knex
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
koa:
@@ -713,7 +713,7 @@ jobs:
env:
PLUGINS: koa
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
ldapjs:
@@ -723,7 +723,7 @@ jobs:
env:
PLUGINS: ldapjs
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
light-my-request:
@@ -733,7 +733,7 @@ jobs:
env:
PLUGINS: "light-my-request"
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
limitd-client:
@@ -753,7 +753,7 @@ jobs:
PLUGINS: limitd-client
SERVICES: limitd
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
lodash:
@@ -763,7 +763,7 @@ jobs:
env:
PLUGINS: lodash
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
mariadb:
@@ -782,7 +782,7 @@ jobs:
PLUGINS: mariadb
SERVICES: mariadb
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
memcached:
@@ -798,7 +798,7 @@ jobs:
PLUGINS: memcached
SERVICES: memcached
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
mercurius:
@@ -808,7 +808,7 @@ jobs:
env:
PLUGINS: mercurius
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
microgateway-core:
@@ -818,7 +818,7 @@ jobs:
env:
PLUGINS: microgateway-core
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
moleculer:
@@ -828,7 +828,7 @@ jobs:
env:
PLUGINS: moleculer
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
mongodb:
@@ -845,7 +845,7 @@ jobs:
PACKAGE_NAMES: mongodb
SERVICES: mongo
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
with:
node-floor: newest-maintenance-lts
@@ -864,7 +864,7 @@ jobs:
PACKAGE_NAMES: mongodb-core,express-mongo-sanitize
SERVICES: mongo
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
with:
node-floor: newest-maintenance-lts
@@ -882,7 +882,7 @@ jobs:
PLUGINS: mongoose
SERVICES: mongo
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
with:
node-floor: newest-maintenance-lts
@@ -894,7 +894,7 @@ jobs:
env:
PLUGINS: multer
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
mysql:
@@ -913,7 +913,7 @@ jobs:
PLUGINS: mysql
SERVICES: mysql
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
mysql2:
@@ -932,7 +932,7 @@ jobs:
PLUGINS: mysql2
SERVICES: mysql2
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
nats:
@@ -948,7 +948,7 @@ jobs:
PLUGINS: nats
SERVICES: nats
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
net:
@@ -958,7 +958,7 @@ jobs:
env:
PLUGINS: net
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -1008,7 +1008,7 @@ jobs:
PLUGINS: next
PACKAGE_VERSION_RANGE: ${{ matrix.range }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -1027,7 +1027,7 @@ jobs:
env:
PLUGINS: node-serialize
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
opensearch:
@@ -1046,7 +1046,7 @@ jobs:
PLUGINS: opensearch
SERVICES: opensearch
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
oracledb:
@@ -1066,7 +1066,7 @@ jobs:
SERVICES: oracledb
DD_INJECT_FORCE: "true"
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node
with:
@@ -1104,7 +1104,7 @@ jobs:
env:
PLUGINS: pino
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
with:
node-floor: newest-maintenance-lts
@@ -1116,7 +1116,7 @@ jobs:
env:
PLUGINS: passport-http
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
postgres:
@@ -1135,7 +1135,7 @@ jobs:
PLUGINS: pg
SERVICES: postgres
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
prisma:
@@ -1184,7 +1184,7 @@ jobs:
SERVICES: prisma
PACKAGE_VERSION_RANGE: ${{ matrix.range }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
@@ -1206,7 +1206,7 @@ jobs:
env:
PLUGINS: process
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
protobufjs:
@@ -1217,7 +1217,7 @@ jobs:
PLUGINS: protobufjs
DD_DATA_STREAMS_ENABLED: true
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
pug:
@@ -1227,7 +1227,7 @@ jobs:
env:
PLUGINS: pug
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
redis:
@@ -1243,7 +1243,7 @@ jobs:
PLUGINS: redis
SERVICES: redis
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
ioredis:
@@ -1271,7 +1271,7 @@ jobs:
PLUGINS: ioredis
SERVICES: redis-cluster
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
valkey:
@@ -1287,7 +1287,7 @@ jobs:
PLUGINS: iovalkey
SERVICES: valkey
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
# Restify isn't compatible with Node.js v24 so we don't run against latest Node.js
@@ -1299,7 +1299,7 @@ jobs:
env:
PLUGINS: restify
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
@@ -1329,7 +1329,7 @@ jobs:
SERVICES: qpid
DD_DATA_STREAMS_ENABLED: true
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test-and-upstream
router:
@@ -1339,7 +1339,7 @@ jobs:
env:
PLUGINS: router
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
sequelize:
@@ -1349,7 +1349,7 @@ jobs:
env:
PLUGINS: sequelize
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
sharedb:
@@ -1359,7 +1359,7 @@ jobs:
env:
PLUGINS: sharedb
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -1388,7 +1388,7 @@ jobs:
PLUGINS: tedious
SERVICES: mssql
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:plugins:ci
@@ -1408,7 +1408,7 @@ jobs:
env:
PLUGINS: undici
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
url:
@@ -1418,7 +1418,7 @@ jobs:
env:
PLUGINS: url
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
vm:
@@ -1428,7 +1428,7 @@ jobs:
env:
PLUGINS: vm
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: ./.github/actions/plugins/test
winston:
@@ -1438,7 +1438,7 @@ jobs:
env:
PLUGINS: winston
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
ws:
@@ -1448,5 +1448,5 @@ jobs:
env:
PLUGINS: ws
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
diff --git a/.github/workflows/appsec.yml b/.github/workflows/appsec.yml
index 3908a64aed..a887588ae2 100644
--- a/.github/workflows/appsec.yml
+++ b/.github/workflows/appsec.yml
@@ -27,7 +27,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:appsec:ci
@@ -45,7 +45,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:ci
@@ -69,7 +69,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: 24.14.1 # TODO: remove pin when https://github.com/nodejs/node/issues/62991 is fixed
@@ -104,7 +104,7 @@ jobs:
LDAP_USERS: "user01,user02"
LDAP_PASSWORDS: "password1,password2"
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -135,7 +135,7 @@ jobs:
PLUGINS: pg|knex
SERVICES: postgres
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -166,7 +166,7 @@ jobs:
PLUGINS: mysql|mysql2|sequelize
SERVICES: mysql
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -188,7 +188,7 @@ jobs:
env:
PLUGINS: express|body-parser|cookie-parser|multer
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -210,7 +210,7 @@ jobs:
env:
PLUGINS: fastify
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -232,7 +232,7 @@ jobs:
env:
PLUGINS: apollo-server|apollo-server-express|apollo-server-fastify|apollo-server-core
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -260,7 +260,7 @@ jobs:
PLUGINS: express-mongo-sanitize|mquery
SERVICES: mongo
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -288,7 +288,7 @@ jobs:
PLUGINS: mongoose
SERVICES: mongo
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/newest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -309,7 +309,7 @@ jobs:
env:
PLUGINS: cookie
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -357,7 +357,7 @@ jobs:
PLUGINS: next
PACKAGE_VERSION_RANGE: ${{ matrix.range }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
@@ -379,7 +379,7 @@ jobs:
env:
PLUGINS: lodash
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -403,7 +403,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
@@ -424,7 +424,7 @@ jobs:
env:
PLUGINS: passport-local|passport-http
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -445,7 +445,7 @@ jobs:
env:
PLUGINS: handlebars|pug
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -467,7 +467,7 @@ jobs:
env:
PLUGINS: node-serialize
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -507,7 +507,7 @@ jobs:
PLUGINS: kafkajs
SERVICES: kafka
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
@@ -528,7 +528,7 @@ jobs:
env:
PLUGINS: stripe
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:appsec:plugins:ci
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 191fc4190a..f6bc78b82b 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -13,7 +13,7 @@ jobs:
dependencies:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- run: yarn audit
- run: yarn audit
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 163bd884af..3c71b45f8d 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -39,13 +39,13 @@ jobs:
policy: codeql-analysis
- name: Checkout repository
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
token: ${{ steps.octo-sts.outputs.token }}
- name: Initialize CodeQL
id: init-codeql
- uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
+ uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
languages: ${{ matrix.language }}
config-file: .github/codeql_config.yml
@@ -57,7 +57,7 @@ jobs:
- name: Perform CodeQL Analysis
id: analyze
- uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
+ uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
token: ${{ github.token }}
wait-for-processing: false
diff --git a/.github/workflows/debugger.yml b/.github/workflows/debugger.yml
index 2d051b3574..b4e1f2dc7f 100644
--- a/.github/workflows/debugger.yml
+++ b/.github/workflows/debugger.yml
@@ -22,7 +22,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
diff --git a/.github/workflows/electron.yml b/.github/workflows/electron.yml
index ae2af432e7..f6fe3da0fe 100644
--- a/.github/workflows/electron.yml
+++ b/.github/workflows/electron.yml
@@ -18,7 +18,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: '24.15'
@@ -34,7 +34,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: '24.15'
diff --git a/.github/workflows/eslint-rules.yml b/.github/workflows/eslint-rules.yml
index 8296c1f6c1..a6a03beabb 100644
--- a/.github/workflows/eslint-rules.yml
+++ b/.github/workflows/eslint-rules.yml
@@ -17,7 +17,7 @@ jobs:
eslint-rules:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:eslint-rules
diff --git a/.github/workflows/flakiness.yml b/.github/workflows/flakiness.yml
index 95738bf77a..20b691d794 100644
--- a/.github/workflows/flakiness.yml
+++ b/.github/workflows/flakiness.yml
@@ -34,7 +34,7 @@ jobs:
with:
scope: DataDog/dd-trace-js
policy: self.flakiness
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout-cone-mode: false
sparse-checkout: |
@@ -52,7 +52,7 @@ jobs:
- run: cat flakiness.md >> $GITHUB_STEP_SUMMARY
- id: slack
run: echo "report=$(cat flakiness.txt)" >> $GITHUB_OUTPUT
- - uses: slackapi/slack-github-action@fc46ded2fc4d7f11bfa62864526f920cf1a1167d # v2.1.0
+ - uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v2.1.0
if: github.event_name == 'schedule'
with:
method: chat.postMessage
diff --git a/.github/workflows/instrumentation.yml b/.github/workflows/instrumentation.yml
index f2cc8d4d73..0769277743 100644
--- a/.github/workflows/instrumentation.yml
+++ b/.github/workflows/instrumentation.yml
@@ -26,7 +26,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:esbuild:ci
@@ -41,7 +41,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:webpack:ci
@@ -58,7 +58,7 @@ jobs:
env:
PLUGINS: ai
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-aws-sdk:
@@ -68,7 +68,7 @@ jobs:
env:
PLUGINS: aws-sdk
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-bluebird:
@@ -78,7 +78,7 @@ jobs:
env:
PLUGINS: bluebird
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-body-parser:
@@ -88,7 +88,7 @@ jobs:
env:
PLUGINS: body-parser
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-child_process:
@@ -98,7 +98,7 @@ jobs:
env:
PLUGINS: child_process
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-connect:
@@ -108,7 +108,7 @@ jobs:
env:
PLUGINS: connect
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-cookie-parser:
@@ -118,7 +118,7 @@ jobs:
env:
PLUGINS: cookie-parser
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
# The couchbase native binding ships libcouchbase: the 3.x line has no
@@ -142,7 +142,7 @@ jobs:
PLUGINS: couchbase
PACKAGE_VERSION_RANGE: ${{ matrix.range }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.node-version }}
@@ -164,7 +164,7 @@ jobs:
env:
PLUGINS: crypto
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-express-mongo-sanitize:
@@ -180,7 +180,7 @@ jobs:
PLUGINS: express-mongo-sanitize
SERVICES: mongo
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-express-session:
@@ -190,7 +190,7 @@ jobs:
env:
PLUGINS: express-session
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-express:
@@ -200,7 +200,7 @@ jobs:
env:
PLUGINS: express
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-express-multi-version:
@@ -210,7 +210,7 @@ jobs:
env:
PLUGINS: express-multi-version
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-fastify:
@@ -220,7 +220,7 @@ jobs:
env:
PLUGINS: fastify
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-fetch:
@@ -230,7 +230,7 @@ jobs:
env:
PLUGINS: fetch
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-fs:
@@ -240,7 +240,7 @@ jobs:
env:
PLUGINS: fs
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-generic-pool:
@@ -250,7 +250,7 @@ jobs:
env:
PLUGINS: generic-pool
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-hono:
@@ -260,7 +260,7 @@ jobs:
env:
PLUGINS: hono
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
# TODO: Retries below work around a flaky bug in Node.js http code. Revert to using
@@ -272,7 +272,7 @@ jobs:
env:
PLUGINS: http
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- name: Run instrumentation tests (oldest-maintenance, with retries)
@@ -305,7 +305,7 @@ jobs:
env:
PLUGINS: http-client-options
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-kafkajs:
@@ -315,7 +315,7 @@ jobs:
env:
PLUGINS: kafkajs
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-knex:
@@ -325,7 +325,7 @@ jobs:
env:
PLUGINS: knex
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-koa:
@@ -335,7 +335,7 @@ jobs:
env:
PLUGINS: koa
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-light-my-request:
@@ -345,7 +345,7 @@ jobs:
env:
PLUGINS: light-my-request
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-mongoose:
@@ -361,7 +361,7 @@ jobs:
PLUGINS: mongoose
SERVICES: mongo
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
with:
node-floor: newest-maintenance-lts
@@ -373,7 +373,7 @@ jobs:
env:
PLUGINS: multer
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-mysql2:
@@ -392,7 +392,7 @@ jobs:
PLUGINS: mysql2
SERVICES: mysql2
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-nyc:
@@ -402,7 +402,7 @@ jobs:
env:
PLUGINS: nyc
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-openai-lifecycle:
@@ -412,7 +412,7 @@ jobs:
env:
PLUGINS: openai-lifecycle
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-otel-sdk-trace:
@@ -422,7 +422,7 @@ jobs:
env:
PLUGINS: otel-sdk-trace
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-passport:
@@ -432,7 +432,7 @@ jobs:
env:
PLUGINS: passport
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-passport-http:
@@ -442,7 +442,7 @@ jobs:
env:
PLUGINS: passport-http
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-passport-local:
@@ -452,7 +452,7 @@ jobs:
env:
PLUGINS: passport-local
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-pg:
@@ -471,7 +471,7 @@ jobs:
PLUGINS: pg
SERVICES: postgres
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-promise-js:
@@ -481,7 +481,7 @@ jobs:
env:
PLUGINS: promise-js
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-promise:
@@ -491,7 +491,7 @@ jobs:
env:
PLUGINS: promise
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-q:
@@ -501,7 +501,7 @@ jobs:
env:
PLUGINS: q
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-stripe:
@@ -511,7 +511,7 @@ jobs:
env:
PLUGINS: stripe
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-restify:
@@ -521,7 +521,7 @@ jobs:
env:
PLUGINS: restify
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-router:
@@ -531,7 +531,7 @@ jobs:
env:
PLUGINS: router
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-url:
@@ -541,7 +541,7 @@ jobs:
env:
PLUGINS: url
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-when:
@@ -551,7 +551,7 @@ jobs:
env:
PLUGINS: when
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentation-zlib:
@@ -561,7 +561,7 @@ jobs:
env:
PLUGINS: zlib
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/instrumentations/test
instrumentations-misc:
@@ -569,7 +569,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:instrumentations:misc:ci
@@ -602,7 +602,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
@@ -630,7 +630,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
diff --git a/.github/workflows/llmobs.yml b/.github/workflows/llmobs.yml
index 11c9f032ca..370a87db33 100644
--- a/.github/workflows/llmobs.yml
+++ b/.github/workflows/llmobs.yml
@@ -30,7 +30,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node
with:
@@ -60,7 +60,7 @@ jobs:
env:
PLUGINS: openai
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node
with:
@@ -88,7 +88,7 @@ jobs:
env:
PLUGINS: langchain
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node/newest-maintenance-lts
- uses: ./.github/actions/install
@@ -118,7 +118,7 @@ jobs:
env:
PLUGINS: aws-sdk
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
@@ -146,7 +146,7 @@ jobs:
env:
PLUGINS: google-cloud-vertexai
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
@@ -176,7 +176,7 @@ jobs:
env:
PLUGINS: ai
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
@@ -202,7 +202,7 @@ jobs:
env:
PLUGINS: anthropic
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
@@ -232,7 +232,7 @@ jobs:
env:
PLUGINS: claude-agent-sdk
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
# The SDK is ESM-only. Tests use dynamic import() to load it.
- uses: ./.github/actions/node/active-lts
@@ -259,7 +259,7 @@ jobs:
env:
PLUGINS: google-genai
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
@@ -289,7 +289,7 @@ jobs:
env:
PLUGINS: langgraph
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
with:
node-floor: newest-maintenance-lts
@@ -301,5 +301,35 @@ jobs:
env:
PLUGINS: modelcontextprotocol-sdk
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
+
+ openai-agents:
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ env:
+ PLUGINS: openai-agents
+ steps:
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: ./.github/actions/testagent/start
+ - uses: ./.github/actions/node/oldest-maintenance-lts
+ - uses: ./.github/actions/install
+ - run: npm run test:plugins:ci
+ - run: npm run test:llmobs:plugins:ci
+ shell: bash
+ - uses: ./.github/actions/node/latest
+ - run: npm run test:plugins:ci
+ - run: npm run test:llmobs:plugins:ci
+ shell: bash
+ - uses: ./.github/actions/coverage
+ with:
+ flags: llmobs-${{ github.job }}
+ - if: always()
+ uses: ./.github/actions/testagent/logs
+ with:
+ suffix: llmobs-${{ github.job }}
+ - uses: ./.github/actions/upload-junit-artifacts
+ if: "!cancelled()"
+ with:
+ id: ${{ github.job }}
diff --git a/.github/workflows/openfeature.yml b/.github/workflows/openfeature.yml
index 2241f5b16f..22ec5dd03b 100644
--- a/.github/workflows/openfeature.yml
+++ b/.github/workflows/openfeature.yml
@@ -22,7 +22,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
@@ -42,7 +42,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:integration:openfeature:coverage
@@ -60,7 +60,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:integration:openfeature:coverage
@@ -84,7 +84,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: 24.14.1 # TODO: remove pin when https://github.com/nodejs/node/issues/62991 is fixed
diff --git a/.github/workflows/platform.yml b/.github/workflows/platform.yml
index fe30ae4c60..c0e97a897f 100644
--- a/.github/workflows/platform.yml
+++ b/.github/workflows/platform.yml
@@ -53,7 +53,7 @@ jobs:
install: bun add --linker=hoisted
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/active-lts
- uses: ./.github/actions/install
- run: FILENAME=$(npm pack --silent --pack-destination /tmp) && mv /tmp/$FILENAME /tmp/dd-trace.tgz
@@ -67,7 +67,7 @@ jobs:
bun-pack:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/active-lts
- uses: ./.github/actions/install
- run: mkdir npm bun
@@ -80,7 +80,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/active-lts
- uses: ./.github/actions/install
- run: npm run test:integration:bun
@@ -94,7 +94,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:core:ci
@@ -115,7 +115,7 @@ jobs:
env:
PLUGINS: dd-trace-api
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
# TODO: Split this up as it runs tests for multiple different teams.
@@ -131,7 +131,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
@@ -157,7 +157,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
@@ -175,7 +175,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:trace:guardrails:ci
@@ -197,7 +197,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/active-lts
- uses: ./.github/actions/install
- run: bun add --linker=hoisted --ignore-scripts mocha@10 # Use older mocha to support old Node.js versions
@@ -221,7 +221,7 @@ jobs:
env:
DD_TRACE_DEBUG: "true" # This exercises more of the guardrails code
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
@@ -235,7 +235,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:shimmer:ci
diff --git a/.github/workflows/profiling.yml b/.github/workflows/profiling.yml
index 5d66cfed39..6be7c32baa 100644
--- a/.github/workflows/profiling.yml
+++ b/.github/workflows/profiling.yml
@@ -27,7 +27,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:profiler:ci
@@ -46,7 +46,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:profiler:ci
@@ -74,7 +74,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: 24.14.1 # TODO: remove pin when https://github.com/nodejs/node/issues/62991 is fixed
diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml
index 8833b3db53..ed0b2e630d 100644
--- a/.github/workflows/project.yml
+++ b/.github/workflows/project.yml
@@ -13,7 +13,7 @@ jobs:
actionlint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: .github
- uses: ./.github/actions/node/latest
@@ -39,7 +39,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run lint
@@ -47,13 +47,13 @@ jobs:
lint-editorconfig:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: docker build -q -t ec .github/editorconfig-checker && docker run --rm --volume=$PWD:/check ec
release-scripts:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run test:release
@@ -63,7 +63,7 @@ jobs:
runs-on: ubuntu-latest
name: Generated config types
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- run: npm run verify:config:types
@@ -71,7 +71,7 @@ jobs:
runs-on: ubuntu-latest
name: Workflow job names (unique)
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
.github
@@ -94,7 +94,7 @@ jobs:
with:
scope: DataDog/dd-trace-js
policy: package-size-report
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- run: FILENAME=$(npm pack --silent --pack-destination /tmp) && mv /tmp/$FILENAME /tmp/dd-trace.tgz
- run: rm -rf *
@@ -114,7 +114,7 @@ jobs:
if: github.actor != 'dependabot[bot]' && github.event_name != 'pull_request'
steps:
- name: Checkout
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/dd-sts-api-key
id: dd-sts-api-key
- uses: ./.github/actions/dd-sts-app-key
@@ -131,7 +131,7 @@ jobs:
typescript:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/latest
- uses: ./.github/actions/install
- run: npm run type:doc:test
@@ -141,7 +141,7 @@ jobs:
# verify-yaml:
# runs-on: ubuntu-latest
# steps:
- # - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ # - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# - uses: ./.github/actions/node/latest
# - uses: ./.github/actions/install
# - run: node scripts/verify-ci-config.js
@@ -154,7 +154,7 @@ jobs:
has_changes: ${{ steps.diff.outputs.has_changes }}
steps:
- name: Checkout code
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
diff --git a/.github/workflows/release-proposal.yml b/.github/workflows/release-proposal.yml
index 02618239cb..615e706fd3 100644
--- a/.github/workflows/release-proposal.yml
+++ b/.github/workflows/release-proposal.yml
@@ -52,7 +52,7 @@ jobs:
with:
scope: DataDog/dd-trace-js
policy: release-proposal
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
token: ${{ steps.octo-sts.outputs.token }}
@@ -66,7 +66,7 @@ jobs:
run: node scripts/release/proposal ${{ matrix.release-line }} -y ${{ github.event_name == 'workflow_dispatch' && '-f' || '' }}
env:
GITHUB_TOKEN: ${{ steps.octo-sts.outputs.token }}
- - uses: slackapi/slack-github-action@fc46ded2fc4d7f11bfa62864526f920cf1a1167d # v2.1.0
+ - uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v2.1.0
if: >
github.event_name == 'schedule' &&
steps.proposal.outputs.commit_count != '' &&
@@ -89,7 +89,7 @@ jobs:
GitHub's rebase limit is 100 — once reached, commits that don't fit
will be deferred to a subsequent release.
Consider cutting this release soon.
- - uses: slackapi/slack-github-action@fc46ded2fc4d7f11bfa62864526f920cf1a1167d # v2.1.0
+ - uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v2.1.0
if: >
github.event_name == 'schedule' &&
steps.proposal.outputs.commit_count != '' &&
diff --git a/.github/workflows/release-validate.yml b/.github/workflows/release-validate.yml
index c08a5f886a..75a7f34355 100644
--- a/.github/workflows/release-validate.yml
+++ b/.github/workflows/release-validate.yml
@@ -17,7 +17,7 @@ jobs:
pull-requests: read
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Get GitHub Token via dd-octo-sts
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d978cb3045..bcbd2a70b7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -79,10 +79,12 @@ jobs:
with:
scope: DataDog/dd-trace-js
policy: self.github.release.push-tags
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/node
+ with:
+ registry-url: https://registry.npmjs.org
- id: pkg
run: |
content=`cat ./package.json | tr '\n' ' '`
@@ -137,10 +139,12 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/node
+ with:
+ registry-url: https://registry.npmjs.org
- run: node scripts/release/publish-electron.js --tag ${{ needs.setup.outputs.npm_tag }}
docs:
@@ -151,7 +155,7 @@ jobs:
id-token: write
contents: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
- id: pkg
run: |
@@ -164,7 +168,7 @@ jobs:
yarn
yarn build
mv out /tmp/out
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: gh-pages
- name: Deploy
@@ -176,7 +180,7 @@ jobs:
git add -A
git commit -m ${{ fromJson(steps.pkg.outputs.json).version }}
git push
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
publish-dev:
if: github.event_name == 'workflow_dispatch'
@@ -192,10 +196,12 @@ jobs:
with:
scope: DataDog/dd-trace-js
policy: self.github.release.push-tags
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/node
+ with:
+ registry-url: https://registry.npmjs.org
- uses: ./.github/actions/install
- id: pkg
run: |
@@ -218,10 +224,12 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/node
+ with:
+ registry-url: https://registry.npmjs.org
- uses: ./.github/actions/install
- id: pkg
run: |
diff --git a/.github/workflows/serverless.yml b/.github/workflows/serverless.yml
index 1a861649aa..63831a2223 100644
--- a/.github/workflows/serverless.yml
+++ b/.github/workflows/serverless.yml
@@ -26,7 +26,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node/oldest-maintenance-lts
- uses: ./.github/actions/install
- run: npm run test:lambda:ci
@@ -103,7 +103,7 @@ jobs:
SERVICES: localstack localstack-legacy
DD_DATA_STREAMS_ENABLED: true
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/testagent/start
- uses: ./.github/actions/node
with:
@@ -129,7 +129,7 @@ jobs:
env:
PLUGINS: aws-durable-execution-sdk-js
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
azure-event-hubs:
@@ -156,7 +156,7 @@ jobs:
PLUGINS: azure-event-hubs
SERVICES: azurite,azureeventhubsemulator
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/integration-test
azure-functions:
@@ -218,7 +218,7 @@ jobs:
SERVICES: azuresqledge,azureservicebusemulator,azurite,azureeventhubsemulator,azurecosmosemulator
SPEC: ${{ matrix.spec }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Wait for Cosmos emulator to be ready
run: timeout 120 bash -c 'until curl -sf -o /dev/null --max-time 5 http://127.0.0.1:8080/ready; do sleep 3; done'
- name: Copy emulator config files
@@ -270,7 +270,7 @@ jobs:
PLUGINS: azure-service-bus
SERVICES: azureservicebusemulator,azuresqledge
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Wait for Service Bus emulator to be ready
run: timeout 120 bash -c 'until nc -z localhost 5672; do sleep 3; done'
- uses: ./.github/actions/plugins/integration-test
@@ -291,7 +291,7 @@ jobs:
SERVICES: azurite
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/integration-test-newest-lts
with:
flags: serverless-azure-durable-functions
@@ -323,7 +323,7 @@ jobs:
NODE_OPTIONS: '--experimental-global-webcrypto'
NODE_TLS_REJECT_UNAUTHORIZED: 0
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Wait for Cosmos emulator to be ready
run: timeout 120 bash -c 'until curl -sf -o /dev/null --max-time 5 http://127.0.0.1:8080/ready; do sleep 3; done'
- uses: ./.github/actions/plugins/test
@@ -342,5 +342,5 @@ jobs:
PLUGINS: google-cloud-pubsub
SERVICES: gpubsub
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml
index a28470ebfa..710db17450 100644
--- a/.github/workflows/system-tests.yml
+++ b/.github/workflows/system-tests.yml
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout dd-trace-js
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: dd-trace-js
- name: Pack dd-trace-js
diff --git a/.github/workflows/test-optimization.yml b/.github/workflows/test-optimization.yml
index 6a182b5694..3ce3678890 100644
--- a/.github/workflows/test-optimization.yml
+++ b/.github/workflows/test-optimization.yml
@@ -27,7 +27,7 @@ jobs:
with:
scope: DataDog/test-environment
policy: dd-trace-js
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
token: ${{ steps.octo-sts.outputs.token }}
- uses: ./.github/actions/node/oldest-maintenance-lts
@@ -48,7 +48,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/node
with:
version: ${{ matrix.version }}
@@ -84,7 +84,7 @@ jobs:
# behaviour of matrix outputs is safe here.
images: ${{ steps.versions.outputs.images }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Determine versions
id: versions
run: |
@@ -150,7 +150,7 @@ jobs:
OPTIONS_OVERRIDE: 1
PLAYWRIGHT_VERSION: ${{ matrix.playwright-version }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/dd-sts-api-key
id: dd-sts
continue-on-error: true
@@ -185,7 +185,7 @@ jobs:
DD_SERVICE: dd-trace-js-integration-tests
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/dd-sts-api-key
id: dd-sts
continue-on-error: true
@@ -221,7 +221,7 @@ jobs:
DD_SERVICE: dd-trace-js-integration-tests
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/dd-sts-api-key
id: dd-sts
continue-on-error: true
@@ -249,7 +249,7 @@ jobs:
env:
PLUGINS: jest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/plugins/test
integration-cucumber:
@@ -265,7 +265,7 @@ jobs:
DD_SERVICE: dd-trace-js-integration-tests
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/dd-sts-api-key
id: dd-sts
continue-on-error: true
@@ -291,7 +291,7 @@ jobs:
outputs:
image: ${{ steps.image.outputs.image }}
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Determine image tag
id: image
run: |
@@ -334,7 +334,7 @@ jobs:
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
OPTIONS_OVERRIDE: 1
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/dd-sts-api-key
id: dd-sts
continue-on-error: true
@@ -401,7 +401,7 @@ jobs:
DD_SERVICE: dd-trace-js-integration-tests
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/dd-sts-api-key
id: dd-sts
continue-on-error: true
@@ -456,7 +456,7 @@ jobs:
DD_CIVISIBILITY_AGENTLESS_ENABLED: 1
OPTIONS_OVERRIDE: 1
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/dd-sts-api-key
id: dd-sts
continue-on-error: true
diff --git a/.github/workflows/update-3rdparty-licenses.yml b/.github/workflows/update-3rdparty-licenses.yml
index 42dc05e40d..5991d859ba 100644
--- a/.github/workflows/update-3rdparty-licenses.yml
+++ b/.github/workflows/update-3rdparty-licenses.yml
@@ -28,7 +28,7 @@ jobs:
policy: self.check-licenses
- name: Check out PR branch
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
@@ -36,7 +36,7 @@ jobs:
python-version: "3.14"
- name: Check out dd-license-attribution
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: watson/dd-license-attribution
ref: 8ea483b9f735bf8da632c89796789cc2a050a9a6
diff --git a/docs/API.md b/docs/API.md
index ab18e688d1..7ccfb8f7ba 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -86,6 +86,7 @@ tracer.use('pg', {
+
@@ -170,6 +171,7 @@ tracer.use('pg', {
* [next](./interfaces/export_.plugins.next.html)
* [nyc](./interfaces/export_.plugins.nyc.html)
* [openai](./interfaces/export_.plugins.openai.html)
+* [openai-agents](./interfaces/export_.plugins.openai_agents.html)
* [opensearch](./interfaces/export_.plugins.opensearch.html)
* [oracledb](./interfaces/export_.plugins.oracledb.html)
* [pg](./interfaces/export_.plugins.pg.html)
diff --git a/docs/test.ts b/docs/test.ts
index c8fc30d164..6ab75f373c 100644
--- a/docs/test.ts
+++ b/docs/test.ts
@@ -391,6 +391,7 @@ tracer.use('nats');
tracer.use('net');
tracer.use('next');
tracer.use('next', nextOptions);
+tracer.use('openai-agents');
tracer.use('opensearch');
tracer.use('opensearch', openSearchOptions);
tracer.use('oracledb');
diff --git a/index.d.ts b/index.d.ts
index 5590386b3f..6f703250bd 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -291,6 +291,7 @@ interface Plugins {
"next": tracer.plugins.next;
"nyc": tracer.plugins.nyc;
"openai": tracer.plugins.openai;
+ "openai-agents": tracer.plugins.openai_agents;
"opensearch": tracer.plugins.opensearch;
"oracledb": tracer.plugins.oracledb;
"playwright": tracer.plugins.playwright;
@@ -2968,6 +2969,12 @@ declare namespace tracer {
*/
interface openai extends Instrumentation {}
+ /**
+ * This plugin automatically instruments the
+ * [@openai/agents](https://www.npmjs.com/package/@openai/agents) library.
+ */
+ interface openai_agents extends Instrumentation {}
+
/**
* This plugin automatically instruments the
* [opensearch](https://github.com/opensearch-project/opensearch-js) module.
diff --git a/index.d.v5.ts b/index.d.v5.ts
index 0aa1604770..14a7680153 100644
--- a/index.d.v5.ts
+++ b/index.d.v5.ts
@@ -293,6 +293,7 @@ interface Plugins {
"next": tracer.plugins.next;
"nyc": tracer.plugins.nyc;
"openai": tracer.plugins.openai;
+ "openai-agents": tracer.plugins.openai_agents;
"opensearch": tracer.plugins.opensearch;
"oracledb": tracer.plugins.oracledb;
"playwright": tracer.plugins.playwright;
@@ -3138,6 +3139,12 @@ declare namespace tracer {
*/
interface openai extends Instrumentation {}
+ /**
+ * This plugin automatically instruments the
+ * [@openai/agents](https://www.npmjs.com/package/@openai/agents) library.
+ */
+ interface openai_agents extends Instrumentation {}
+
/**
* This plugin automatically instruments the
* [opensearch](https://github.com/opensearch-project/opensearch-js) module.
diff --git a/integration-tests/appsec/standalone-asm.spec.js b/integration-tests/appsec/standalone-asm.spec.js
index cb5f57ffb7..e1fdcf5ac1 100644
--- a/integration-tests/appsec/standalone-asm.spec.js
+++ b/integration-tests/appsec/standalone-asm.spec.js
@@ -121,11 +121,16 @@ describe('Standalone ASM', () => {
assert.ok(groups.indexOf(rootGroup) < groups.indexOf(outboundGroup))
assert.strictEqual(String(outboundSpan.parent_id), String(rootSpan.span_id))
- // Load-bearing: the delayed child chunk must carry the billing marker,
- // even though its parent is a local (non-remote) span.
- assert.strictEqual(rootSpan.metrics['_dd.apm.enabled'], 0)
- assert.strictEqual(outboundGroup[0], outboundSpan)
- assert.strictEqual(outboundSpan.metrics['_dd.apm.enabled'], 0)
+ // Load-bearing: every span in every chunk must carry the billing marker,
+ // including the delayed child whose parent is a local (non-remote) span.
+ for (const group of groups) {
+ for (const span of group) {
+ assert.strictEqual(
+ span.metrics['_dd.apm.enabled'], 0,
+ `span ${span.name}/${span.resource} missing _dd.apm.enabled:0`
+ )
+ }
+ }
})
it('should keep fifth req because RateLimiter allows 1 req/min', async () => {
diff --git a/integration-tests/openfeature/openfeature-configuration-sources.spec.js b/integration-tests/openfeature/openfeature-configuration-sources.spec.js
index 566c36639b..9923933241 100644
--- a/integration-tests/openfeature/openfeature-configuration-sources.spec.js
+++ b/integration-tests/openfeature/openfeature-configuration-sources.spec.js
@@ -341,7 +341,7 @@ function assertDeliveryTraffic (testCase) {
for (const request of cdnRequests) {
assert.strictEqual(request.url, `${AGENTLESS_PATH}?case=${testCase.identifier}`, testCase.label)
assert.strictEqual(request.headers['accept-encoding'], 'gzip', testCase.label)
- assert.strictEqual(request.headers['dd-api-key'], 'integration-api-key', testCase.label)
+ assert.strictEqual(request.headers['dd-api-key'], undefined, testCase.label)
assert.strictEqual(request.headers['dd-client-library-language'], 'nodejs', testCase.label)
assert.strictEqual(request.headers['dd-client-library-version'], VERSION, testCase.label)
}
diff --git a/integration-tests/webpack/build-and-test-git-tags.js b/integration-tests/webpack/build-and-test-git-tags.js
index 08949ada3b..88fefe143b 100644
--- a/integration-tests/webpack/build-and-test-git-tags.js
+++ b/integration-tests/webpack/build-and-test-git-tags.js
@@ -9,6 +9,7 @@ const { spawnSync } = require('child_process')
const assert = require('assert')
const webpack = require('webpack')
const DatadogWebpackPlugin = require('../../webpack') // dd-trace/webpack
+const experiments = require('./webpack-experiments')
const OUTFILE = path.join(__dirname, 'git-tags-out.js')
@@ -17,6 +18,7 @@ const compiler = webpack({
entry: path.join(__dirname, 'basic-test.js'),
target: 'node',
externalsType: 'commonjs',
+ ...(experiments && { experiments }),
output: {
filename: 'git-tags-out.js',
path: __dirname,
diff --git a/integration-tests/webpack/build-and-test-minify.js b/integration-tests/webpack/build-and-test-minify.js
index 6815d781b7..e664f518e3 100644
--- a/integration-tests/webpack/build-and-test-minify.js
+++ b/integration-tests/webpack/build-and-test-minify.js
@@ -8,6 +8,7 @@ const path = require('path')
const assert = require('assert')
const webpack = require('webpack')
const DatadogWebpackPlugin = require('../../webpack') // dd-trace/webpack
+const experiments = require('./webpack-experiments')
const OUTFILE = path.join(__dirname, 'minify-out.js')
@@ -17,6 +18,7 @@ try {
// optimization.minimize is enabled by default in production mode
entry: path.join(__dirname, 'basic-test.js'),
target: 'node',
+ ...(experiments && { experiments }),
output: {
filename: 'minify-out.js',
path: __dirname,
diff --git a/integration-tests/webpack/build-and-test-openfeature.js b/integration-tests/webpack/build-and-test-openfeature.js
index f1a74ced22..28842ca630 100644
--- a/integration-tests/webpack/build-and-test-openfeature.js
+++ b/integration-tests/webpack/build-and-test-openfeature.js
@@ -23,6 +23,7 @@ const assert = require('assert')
const { execFileSync } = require('child_process')
const webpack = require('webpack')
const DatadogWebpackPlugin = require('../../webpack') // dd-trace/webpack
+const experiments = require('./webpack-experiments')
const ENTRY = path.join(__dirname, 'openfeature-app.js')
const FLAGGING_PROVIDER = path.join('openfeature', 'flagging_provider')
@@ -49,6 +50,7 @@ function build (outfile, plugins) {
entry: ENTRY,
target: 'node',
externalsType: 'commonjs',
+ ...(experiments && { experiments }),
output: { filename: path.basename(outfile), path: path.dirname(outfile), hashFunction: 'sha256' },
externals: EXTERNALS,
plugins,
diff --git a/integration-tests/webpack/build-and-test-skip-external.js b/integration-tests/webpack/build-and-test-skip-external.js
index ff8e8307d7..be23bed01a 100644
--- a/integration-tests/webpack/build-and-test-skip-external.js
+++ b/integration-tests/webpack/build-and-test-skip-external.js
@@ -8,6 +8,7 @@ const path = require('path')
const assert = require('assert')
const webpack = require('webpack')
const DatadogWebpackPlugin = require('../../webpack') // dd-trace/webpack
+const experiments = require('./webpack-experiments')
const OUTFILE = path.join(__dirname, 'skip-external-out.js')
@@ -16,6 +17,7 @@ const compiler = webpack({
entry: path.join(__dirname, 'skip-external.js'),
target: 'node',
externalsType: 'commonjs',
+ ...(experiments && { experiments }),
output: {
filename: 'skip-external-out.js',
path: __dirname,
diff --git a/integration-tests/webpack/build.js b/integration-tests/webpack/build.js
index 7e8d4277cd..4408aeb2b7 100644
--- a/integration-tests/webpack/build.js
+++ b/integration-tests/webpack/build.js
@@ -6,12 +6,14 @@
const path = require('path')
const webpack = require('webpack')
const DatadogWebpackPlugin = require('../../webpack') // dd-trace/webpack
+const experiments = require('./webpack-experiments')
const compiler = webpack({
mode: 'development',
entry: path.join(__dirname, 'basic-test.js'),
target: 'node',
externalsType: 'commonjs',
+ ...(experiments && { experiments }),
output: {
filename: 'out.js',
path: __dirname,
diff --git a/integration-tests/webpack/webpack-experiments.js b/integration-tests/webpack/webpack-experiments.js
new file mode 100644
index 0000000000..6483eeb2ba
--- /dev/null
+++ b/integration-tests/webpack/webpack-experiments.js
@@ -0,0 +1,16 @@
+'use strict'
+
+const webpackPkg = require('webpack/package.json')
+
+// webpack 5.107.0 introduced `experiments.typescript` (opt-in), and 5.109.0 defaults it to
+// "auto". On Node.js >= 22.6 (where `module.stripTypeScriptTypes` exists), "auto" turns the
+// experiment on and sets `resolve.tsconfig = true`, making enhanced-resolve walk up to each
+// resolved package's own tsconfig.json. Some published packages (e.g. `side-channel`) ship a
+// tsconfig.json that `extends` a devDependency-only config package (`@ljharb/tsconfig`) that
+// isn't installed, so resolution fails with a "Module not found" error unrelated to
+// TypeScript. Explicitly disable the experiment where the option exists; older webpack
+// versions don't recognize the key at all, so it must not be set for those.
+const [major, minor] = webpackPkg.version.split('.').map(Number)
+const supportsTypescriptExperiment = major > 5 || (major === 5 && minor >= 107)
+
+module.exports = supportsTypescriptExperiment ? { typescript: false } : undefined
diff --git a/package.json b/package.json
index 307fe575aa..80df4d6843 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "dd-trace",
- "version": "5.117.0",
+ "version": "5.118.0",
"description": "Datadog APM tracing client for JavaScript",
"main": "index.js",
"typings": "index.d.ts",
@@ -198,7 +198,7 @@
"@types/mocha": "^10.0.10",
"@types/node": "^18.19.106",
"@types/sinon": "^22.0.0",
- "@vercel/nft": "^0.29.4",
+ "@vercel/nft": "^1.10.2",
"axios": "^1.18.1",
"benchmark": "^2.1.4",
"body-parser": "^2.3.0",
diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js
index b82309f408..f22a3b629d 100644
--- a/packages/datadog-instrumentations/src/helpers/hooks.js
+++ b/packages/datadog-instrumentations/src/helpers/hooks.js
@@ -21,6 +21,8 @@ module.exports = {
'@apollo/gateway': () => require('../apollo'),
'@langchain/langgraph': { esmFirst: true, fn: () => require('../langgraph') },
'@modelcontextprotocol/sdk': { esmFirst: true, fn: () => require('../modelcontextprotocol-sdk') },
+ '@openai/agents': () => require('../openai-agents'),
+ '@openai/agents-openai': () => require('../openai-agents'),
'apollo-server-core': () => require('../apollo-server-core'),
'@aws-sdk/smithy-client': () => require('../aws-sdk'),
'@aws/durable-execution-sdk-js': () => require('../aws-durable-execution-sdk-js'),
diff --git a/packages/datadog-instrumentations/src/openai-agents.js b/packages/datadog-instrumentations/src/openai-agents.js
new file mode 100644
index 0000000000..1337e9cd29
--- /dev/null
+++ b/packages/datadog-instrumentations/src/openai-agents.js
@@ -0,0 +1,109 @@
+'use strict'
+
+const { channel } = require('dc-polyfill')
+const shimmer = require('../../datadog-shimmer')
+const { addHook } = require('./helpers/instrument')
+
+// `WeakSet` keyed by module exports — replaces the underscored
+// `mod._datadogPatched` flag while keeping dedupe semantics. Mods are kept
+// alive by `require.cache` anyway, so this doesn't add lifetime to anything.
+const patchedMods = new WeakSet()
+
+// Plugin subscribes to this and registers its TracingProcessor when
+// `@openai/agents` loads. Publishing from here keeps this file free of
+// any cross-package import from the plugin.
+const agentsCoreLoadedCh = channel('apm:openai-agents:agents-core:loaded')
+
+// Plugin subscribes here to keep track of the OpenAI-compatible client's
+// baseURL — used to resolve `model_provider` (openai / azure_openai /
+// deepseek / unknown).
+const responseClientCh = channel('apm:openai-agents:response:client')
+
+// Plugin uses addBind on this channel so that legacyStorage.run(store, fn) wraps
+// the model call — including async iterator advancement for streaming responses.
+// This ensures the active dd-trace span is visible to the openai plugin when it
+// creates its openai.request span, correctly parenting it under the agent span.
+const modelStartCh = channel('apm:openai-agents:model:start')
+
+// Reference to the loaded @openai/agents module, captured in the first hook
+// so that wrapResponseMethod can call getCurrentSpan() without an additional
+// require (and without triggering n/no-missing-require on agents-core internals).
+let agentsMod
+
+// @openai/agents >=0.8.0 moved addTraceProcessor / getCurrentSpan out of the
+// top-level re-exports. The new public surface uses getGlobalTraceProvider():
+// provider.registerProcessor(processor) (replaces addTraceProcessor)
+// provider.getCurrentSpan() (replaces getCurrentSpan)
+// Both APIs are tried so this file works across the full supported version range.
+// The plugin subscriber (index.js) handles processor registration via the channel.
+function getCurrentSpanId (mod) {
+ if (typeof mod?.getCurrentSpan === 'function') {
+ return mod.getCurrentSpan()?.spanId
+ }
+ if (typeof mod?.getGlobalTraceProvider === 'function') {
+ return mod.getGlobalTraceProvider().getCurrentSpan()?.spanId
+ }
+}
+
+addHook({ name: '@openai/agents', versions: ['>=0.7.0'] }, (mod) => {
+ if (patchedMods.has(mod)) return mod
+ if (typeof mod?.addTraceProcessor !== 'function' && typeof mod?.getGlobalTraceProvider !== 'function') return mod
+ patchedMods.add(mod)
+ agentsMod = mod
+ agentsCoreLoadedCh.publish({ mod })
+ return mod
+})
+
+function wrapResponseMethod (original) {
+ return function (...args) {
+ const agentsCoreSpanId = getCurrentSpanId(agentsMod)
+ publishClientBaseURL(this)
+ return modelStartCh.runStores({ agentsCoreSpanId }, () => original.apply(this, args))
+ }
+}
+
+function wrapStreamedResponseMethod (original) {
+ return function (...args) {
+ const agentsCoreSpanId = getCurrentSpanId(agentsMod)
+ publishClientBaseURL(this)
+ const iterator = modelStartCh.runStores({ agentsCoreSpanId }, () => original.apply(this, args))
+ return wrapAsyncIterator(iterator, agentsCoreSpanId)
+ }
+}
+
+function publishClientBaseURL (model) {
+ const baseURL = model?.client?.baseURL ?? model?._client?.baseURL
+ if (baseURL) responseClientCh.publish({ baseURL })
+}
+
+function wrapAsyncIterator (iterator, agentsCoreSpanId) {
+ if (!iterator || typeof iterator !== 'object') return iterator
+
+ return {
+ next () {
+ return modelStartCh.runStores({ agentsCoreSpanId }, () => iterator.next.apply(iterator, arguments))
+ },
+ throw () {
+ if (typeof iterator.throw !== 'function') return Promise.reject(arguments[0])
+ return modelStartCh.runStores({ agentsCoreSpanId }, () => iterator.throw.apply(iterator, arguments))
+ },
+ return () {
+ if (typeof iterator.return !== 'function') return Promise.resolve({ done: true, value: arguments[0] })
+ return modelStartCh.runStores({ agentsCoreSpanId }, () => iterator.return.apply(iterator, arguments))
+ },
+ [Symbol.asyncIterator] () {
+ return this
+ },
+ }
+}
+
+addHook({ name: '@openai/agents-openai', versions: ['>=0.7.0'] }, (mod) => {
+ if (patchedMods.has(mod)) return mod
+ const proto = mod?.OpenAIResponsesModel?.prototype
+ if (!proto) return mod
+
+ patchedMods.add(mod)
+ shimmer.wrap(proto, 'getResponse', wrapResponseMethod)
+ shimmer.wrap(proto, 'getStreamedResponse', wrapStreamedResponseMethod)
+ return mod
+})
diff --git a/packages/datadog-plugin-mongodb-core/test/mongodb.spec.js b/packages/datadog-plugin-mongodb-core/test/mongodb.spec.js
index bbaf4eed2e..e570dbe90b 100644
--- a/packages/datadog-plugin-mongodb-core/test/mongodb.spec.js
+++ b/packages/datadog-plugin-mongodb-core/test/mongodb.spec.js
@@ -109,7 +109,8 @@ describe('Plugin', () => {
'mongodb-core',
(done) => collection.insertOne({ a: 1 }, {}, done),
'test',
- 'peer.service'
+ 'peer.service',
+ { component: 'mongodb' }
)
// The bulkWrite span is opened with `db.name` set, so it flows through the same
@@ -120,7 +121,11 @@ describe('Plugin', () => {
() => collection.bulkWrite([{ insertOne: { document: { a: 1 } } }]),
'test',
'peer.service',
- { desc: 'with bulkWrite' }
+ {
+ component: 'mongodb',
+ desc: 'with bulkWrite',
+ resource: () => `bulkWrite test.${collectionName}`,
+ }
)
it('should do automatic instrumentation', done => {
diff --git a/packages/datadog-plugin-mongoose/test/index.spec.js b/packages/datadog-plugin-mongoose/test/index.spec.js
index 2d63853655..0753039d68 100644
--- a/packages/datadog-plugin-mongoose/test/index.spec.js
+++ b/packages/datadog-plugin-mongoose/test/index.spec.js
@@ -73,7 +73,8 @@ describe('Plugin', () => {
return new PeerCat({ name: 'PeerCat' }).save()
},
() => dbName,
- 'peer.service')
+ 'peer.service',
+ { component: 'mongodb' })
it('should propagate context with write operations', () => {
const Cat = mongoose.model('Cat1', { name: String })
diff --git a/packages/datadog-plugin-openai-agents/src/index.js b/packages/datadog-plugin-openai-agents/src/index.js
new file mode 100644
index 0000000000..60b7ddc561
--- /dev/null
+++ b/packages/datadog-plugin-openai-agents/src/index.js
@@ -0,0 +1,74 @@
+'use strict'
+
+const { storage } = require('../../datadog-core')
+const Plugin = require('../../dd-trace/src/plugins/plugin')
+const { OpenAIAgentsIntegration } = require('./integration')
+const { DDOpenAIAgentsProcessor } = require('./processor')
+
+const legacyStorage = storage('legacy')
+
+/**
+ * Drives the openai-agents integration through agents-core's
+ * `TracingProcessor` interface. The instrumentation hook publishes the
+ * loaded `@openai/agents` module on a channel; this plugin subscribes
+ * during its constructor (which runs synchronously between `loadChannel`'s
+ * publish and the addHook callback) and registers the processor.
+ *
+ * The instrumentation also publishes the OpenAI-compatible client's baseURL on
+ * each model response call, so the integration can resolve `model_provider`.
+ *
+ * The integration's `enabled` flag follows this plugin's configure()
+ * lifecycle. Each loaded version of the agents package replaces all processors
+ * via setTraceProcessors() on module load, so the plugin re-registers a
+ * fresh DDOpenAIAgentsProcessor for each module version that fires the channel.
+ */
+class OpenaiAgentsPlugin extends Plugin {
+ static id = 'openai-agents'
+
+ #integration
+
+ constructor (tracer, tracerConfig) {
+ super(tracer, tracerConfig)
+ this.#integration = new OpenAIAgentsIntegration({
+ tracer: this.tracer,
+ config: tracerConfig,
+ })
+
+ // Register a new processor each time @openai/agents fires the channel.
+ // Each module version calls setTraceProcessors() on load (which replaces
+ // all processors), so we must re-register after every new version loads.
+ // The instrumentation's patchedMods WeakSet ensures each module instance
+ // fires the channel exactly once, so no duplicates accumulate.
+ this.addSub('apm:openai-agents:agents-core:loaded', ({ mod }) => {
+ const processor = new DDOpenAIAgentsProcessor(() => this.#integration)
+ if (typeof mod?.addTraceProcessor === 'function') {
+ mod.addTraceProcessor(processor)
+ } else {
+ mod.getGlobalTraceProvider().registerProcessor(processor)
+ }
+ })
+
+ this.addSub('apm:openai-agents:response:client', ({ baseURL }) => {
+ if (!this.#integration.enabled) return
+ this.#integration.setClientBaseURL(baseURL)
+ })
+
+ // Activate the current agent's dd-trace span in legacyStorage for the
+ // duration of model response calls and stream iterator advancement. This
+ // makes the openai plugin's shimmer see the correct parent when it creates its
+ // openai.request span, so all spans land in the same trace.
+ this.addBind('apm:openai-agents:model:start', ({ agentsCoreSpanId }) => {
+ if (!this.#integration.enabled || !agentsCoreSpanId) return legacyStorage.getStore()
+ const ddSpan = this.#integration.getDDSpan(agentsCoreSpanId)
+ if (!ddSpan) return legacyStorage.getStore()
+ return { ...legacyStorage.getStore(), span: ddSpan }
+ })
+ }
+
+ configure (config) {
+ super.configure(config)
+ this.#integration.setEnabled(!!config?.enabled)
+ }
+}
+
+module.exports = OpenaiAgentsPlugin
diff --git a/packages/datadog-plugin-openai-agents/src/integration.js b/packages/datadog-plugin-openai-agents/src/integration.js
new file mode 100644
index 0000000000..2996be5c42
--- /dev/null
+++ b/packages/datadog-plugin-openai-agents/src/integration.js
@@ -0,0 +1,405 @@
+'use strict'
+
+const LLMObsTagger = require('../../dd-trace/src/llmobs/tagger')
+const { getOpenAIModelProvider } = require('../../dd-trace/src/llmobs/plugins/openai/utils')
+const {
+ extractInputMessages,
+ extractOutputMessages,
+ extractMetrics,
+ extractMetadata,
+} = require('../../dd-trace/src/llmobs/plugins/openai-agents/utils')
+const { AGENTS_ERROR_TYPE, applyError, deriveSpanName } = require('./util')
+
+const COMPONENT = 'openai-agents'
+const DEFAULT_MODEL_PROVIDER = 'openai'
+
+const KIND_TO_SPAN_KIND = {
+ agent: 'internal',
+ tool: 'internal',
+ task: 'internal',
+ llm: 'client',
+}
+
+/**
+ * @typedef {{
+ * spanId: string,
+ * traceId: string,
+ * currentTopLevelAgentSpanId?: string,
+ * currentTopLevelAgentName?: string,
+ * inputOaiSpan?: object,
+ * inputMessages?: Array<{ role: string, content: string }>,
+ * outputOaiSpan?: object,
+ * metadata?: Record,
+ * groupId?: string,
+ * }} LLMObsTraceInfo
+ */
+
+/**
+ * Owns tracer/tagger refs, maps agents-core span ids → dd-trace spans, and
+ * reconstructs workflow-level input/output from the first and last response
+ * spans of the top-level agent.
+ */
+class OpenAIAgentsIntegration {
+ #tracer
+ #enabled = false
+ #modelProvider = DEFAULT_MODEL_PROVIDER
+ /**
+ * LLMObs is gated independently of APM tracing: when DD_LLMOBS_ENABLED is
+ * false we keep emitting APM spans for the agent workflow but skip all
+ * LLMObs tagging and the work that feeds it. The tagger is the single
+ * source of truth for "is LLMObs on for this integration?".
+ * @type {LLMObsTagger | undefined}
+ */
+ #tagger
+ /** @type {Map} */
+ #oaiToDdSpan = new Map()
+ /** @type {Map} */
+ #traceInfo = new Map()
+
+ constructor ({ tracer, config } = {}) {
+ this.#tracer = tracer
+ this.#tagger = config?.llmobs?.DD_LLMOBS_ENABLED ? new LLMObsTagger(config, true) : undefined
+ }
+
+ get enabled () {
+ return this.#enabled
+ }
+
+ setEnabled (enabled) {
+ this.#enabled = enabled
+ }
+
+ /**
+ * Update the model_provider tag based on the OpenAI-compatible client's
+ * baseURL captured by the agents-openai instrumentation hook. Single-
+ * provider-per-process is the assumed deployment shape; concurrent runs
+ * against different providers will see last-write-wins on this field.
+ *
+ * @param {string} baseURL
+ */
+ setClientBaseURL (baseURL) {
+ if (typeof baseURL !== 'string' || baseURL.length === 0) return
+ this.#modelProvider = getOpenAIModelProvider(baseURL)
+ }
+
+ /**
+ * @param {string} spanId agents-core spanId
+ * @returns {import('../../dd-trace/src/opentracing/span') | undefined}
+ */
+ getDDSpan (spanId) {
+ return this.#oaiToDdSpan.get(spanId)
+ }
+
+ clearState () {
+ // Finish any dd-trace spans still in-flight so we don't leak open traces
+ // when agents-core's TracingProcessor.shutdown() runs (e.g., process
+ // exiting mid-run).
+ for (const ddSpan of this.#oaiToDdSpan.values()) {
+ ddSpan.finish()
+ }
+ this.#oaiToDdSpan.clear()
+ this.#traceInfo.clear()
+ }
+
+ // ── Trace lifecycle ─────────────────────────────────────────────────────────
+
+ startTrace (oaiTrace) {
+ const traceId = oaiTrace.traceId
+ if (!traceId) return
+
+ const name = oaiTrace.name || 'Agent workflow'
+ const ddSpan = this.#tracer.startSpan(name, {
+ tags: {
+ component: COMPONENT,
+ 'span.kind': 'internal',
+ },
+ })
+
+ this.#oaiToDdSpan.set(traceId, ddSpan)
+ this.#traceInfo.set(traceId, {
+ spanId: ddSpan.context().toSpanId(),
+ traceId,
+ groupId: oaiTrace.groupId || undefined,
+ metadata: oaiTrace.metadata,
+ })
+
+ this.#tagger?.registerLLMObsSpan(ddSpan, {
+ kind: 'workflow',
+ name,
+ integration: COMPONENT,
+ sessionId: oaiTrace.groupId || undefined,
+ })
+ }
+
+ endTrace (oaiTrace) {
+ this.#completeWorkflowSpan(oaiTrace.traceId)
+ }
+
+ /**
+ * Finish the workflow dd-trace span and clear its bookkeeping. Used by both
+ * agents-core's normal `Trace.end()` path and the orphan-recovery path
+ * (when `withTrace` skips its end callback because the body threw). When
+ * `rootAgentSpan` is provided, its `error` field is reflected onto the
+ * workflow span before finishing.
+ *
+ * @param {string | undefined} traceId
+ * @param {object} [rootAgentSpan] - parentless oai-span that ended in error.
+ */
+ #completeWorkflowSpan (traceId, rootAgentSpan) {
+ if (!traceId) return
+ const ddSpan = this.#oaiToDdSpan.get(traceId)
+ if (!ddSpan) return
+
+ if (rootAgentSpan?.error) {
+ ddSpan.setTag('error', true)
+ ddSpan.setTag('error.type', AGENTS_ERROR_TYPE)
+ if (rootAgentSpan.error.message) {
+ ddSpan.setTag('error.message', rootAgentSpan.error.message)
+ }
+ }
+
+ if (this.#tagger) this.#setTraceAttributes(ddSpan, traceId)
+ ddSpan.finish()
+ this.#oaiToDdSpan.delete(traceId)
+ this.#traceInfo.delete(traceId)
+ }
+
+ // ── Span lifecycle ──────────────────────────────────────────────────────────
+
+ startSpan (oaiSpan, llmobsKind) {
+ const spanId = oaiSpan.spanId
+ if (!spanId) return
+
+ const parentSpan = this.#resolveParent(oaiSpan)
+ const spanName = deriveSpanName(oaiSpan)
+
+ const ddSpan = this.#tracer.startSpan(spanName, {
+ childOf: parentSpan,
+ tags: {
+ component: COMPONENT,
+ 'span.kind': KIND_TO_SPAN_KIND[llmobsKind] ?? 'internal',
+ },
+ })
+
+ this.#oaiToDdSpan.set(spanId, ddSpan)
+
+ if (this.#tagger) {
+ const llmobsOptions = {
+ kind: llmobsKind,
+ name: spanName,
+ integration: COMPONENT,
+ parent: parentSpan,
+ }
+
+ if (oaiSpan.spanData?.type === 'response') {
+ // Model name only arrives with the response; tagged in
+ // `#setResponseAttributes` once known. Model provider is resolved from
+ // the agents-openai client's baseURL captured at getResponse time.
+ llmobsOptions.modelProvider = this.#modelProvider
+ }
+
+ this.#tagger.registerLLMObsSpan(ddSpan, llmobsOptions)
+ this.#updateTraceInfoInput(oaiSpan, spanName)
+ }
+ }
+
+ endSpan (oaiSpan) {
+ const spanId = oaiSpan.spanId
+ const ddSpan = this.#oaiToDdSpan.get(spanId)
+ if (!ddSpan) return
+
+ applyError(ddSpan, oaiSpan)
+
+ if (oaiSpan.spanData?.type === 'handoff') {
+ const spanName = deriveSpanName(oaiSpan)
+ ddSpan.setOperationName(spanName)
+ this.#tagger?.setName(ddSpan, spanName)
+ }
+
+ if (this.#tagger) {
+ const spanData = oaiSpan.spanData
+ switch (spanData?.type) {
+ case 'response':
+ this.#setResponseAttributes(ddSpan, oaiSpan)
+ this.#updateTraceInfoOutput(oaiSpan)
+ break
+ case 'function':
+ this.#tagger.tagTextIO(ddSpan, spanData.input ?? '', spanData.output ?? '')
+ break
+ case 'handoff':
+ this.#tagger.tagTextIO(ddSpan, spanData.from_agent ?? '', spanData.to_agent ?? '')
+ break
+ case 'agent':
+ this.#setAgentAttributes(ddSpan, oaiSpan)
+ break
+ case 'custom':
+ if (spanData.data && typeof spanData.data === 'object') {
+ this.#tagger.tagMetadata(ddSpan, spanData.data)
+ }
+ break
+ }
+ }
+
+ ddSpan.finish()
+
+ // agents-core's withTrace skips Trace.end() when its callback throws, so an
+ // errored parentless span is our last chance to finalize the workflow.
+ if (oaiSpan.parentId == null && oaiSpan.error) {
+ this.#completeWorkflowSpan(oaiSpan.traceId, oaiSpan)
+ }
+ this.#oaiToDdSpan.delete(spanId)
+ }
+
+ // ── Per-type attribute setters ──────────────────────────────────────────────
+
+ #setResponseAttributes (ddSpan, oaiSpan) {
+ const response = oaiSpan.spanData?._response
+ const input = oaiSpan.spanData?._input
+ if (response?.model) {
+ this.#tagger.tagModelName(ddSpan, response.model)
+ }
+
+ // Override the LLMObs span name to `{parent_agent_name} (LLM)` only when
+ // the response is a direct child of the top-level agent (Python parity:
+ // see `_llmobs_set_response_attributes` in dd-trace-py). For bare
+ // `withResponseSpan` calls outside a `Runner.run()` flow the default
+ // name (`openai_agents.response`) stays.
+ const parentAgentName = this.#llmSpanParentAgentName(oaiSpan)
+ if (parentAgentName) {
+ this.#tagger.setName(ddSpan, `${parentAgentName} (LLM)`)
+ }
+
+ // Always tag LLM I/O so the LLMObs event shape is consistent across
+ // happy/error paths. The extract* helpers emit placeholder messages
+ // when their source is absent.
+ const inputMessages = extractInputMessages(input, response?.instructions)
+ this.#tagger.tagLLMIO(ddSpan, inputMessages, extractOutputMessages(response))
+
+ // Cache messages for the workflow span's trace-level input (Python
+ // parity: last message of the first response under the top-level agent).
+ // Avoids re-running extractInputMessages in #setTraceAttributes.
+ const info = this.#traceInfo.get(oaiSpan.traceId)
+ if (info && info.inputOaiSpan === oaiSpan) {
+ info.inputMessages = inputMessages
+ }
+
+ if (response) {
+ const metrics = extractMetrics(response)
+ if (metrics) this.#tagger.tagMetrics(ddSpan, metrics)
+
+ const metadata = extractMetadata(response)
+ if (metadata) this.#tagger.tagMetadata(ddSpan, metadata)
+ }
+ }
+
+ /**
+ * If this response span's parent is the top-level agent span of the trace,
+ * return that agent's dd-trace span name. Used to set the LLMObs span name
+ * to `${agentName} (LLM)` (Python parity).
+ *
+ * @param {object} oaiSpan
+ * @returns {string | undefined}
+ */
+ #llmSpanParentAgentName (oaiSpan) {
+ const traceInfo = this.#traceInfo.get(oaiSpan.traceId)
+ if (!traceInfo?.currentTopLevelAgentSpanId) return
+ if (oaiSpan.parentId !== traceInfo.currentTopLevelAgentSpanId) return
+ return traceInfo.currentTopLevelAgentName
+ }
+
+ #setAgentAttributes (ddSpan, oaiSpan) {
+ const spanData = oaiSpan.spanData
+ let metadata
+ if (Array.isArray(spanData?.handoffs) && spanData.handoffs.length > 0) {
+ metadata = { handoffs: spanData.handoffs }
+ }
+ if (Array.isArray(spanData?.tools) && spanData.tools.length > 0) {
+ metadata ??= {}
+ metadata.tools = spanData.tools
+ }
+ if (spanData?.output_type) {
+ metadata ??= {}
+ metadata.output_type = spanData.output_type
+ }
+ if (metadata) this.#tagger.tagMetadata(ddSpan, metadata)
+ }
+
+ #setTraceAttributes (ddSpan, traceId) {
+ const info = this.#traceInfo.get(traceId)
+ if (!info) return
+
+ // Workflow-level input is the last input message of the first response
+ // span under the top-level agent; output is `response.output_text` of
+ // the last response span. Matches dd-trace-py's
+ // `OaiSpanAdapter.llmobs_trace_input` / `response_output_text`. The
+ // input messages were cached during #setResponseAttributes.
+ const lastInputMessage = info.inputMessages?.at(-1)
+ const inputValue = typeof lastInputMessage?.content === 'string' ? lastInputMessage.content : ''
+ const outputValue = info.outputOaiSpan?.spanData?._response?.output_text ?? ''
+
+ this.#tagger.tagTextIO(ddSpan, inputValue, outputValue)
+
+ if (info.metadata && Object.keys(info.metadata).length > 0) {
+ this.#tagger.tagMetadata(ddSpan, info.metadata)
+ }
+ }
+
+ // ── Trace-info reconstruction (Python parity) ───────────────────────────────
+
+ #updateTraceInfoInput (oaiSpan, spanName) {
+ const info = this.#traceInfo.get(oaiSpan.traceId)
+ if (!info) return
+
+ const parentId = oaiSpan.parentId
+ const type = oaiSpan.spanData?.type
+
+ // Identify the first top-level agent span under the root trace and
+ // stash its display name so `${agentName} (LLM)` doesn't have to read
+ // the dd-trace span context's private fields later.
+ if (type === 'agent' && parentId == null) {
+ info.currentTopLevelAgentSpanId = oaiSpan.spanId
+ info.currentTopLevelAgentName = spanName
+ }
+
+ // Capture the first response span whose parent is the top-level agent
+ // as the workflow-level input source.
+ if (
+ type === 'response' &&
+ parentId &&
+ !info.inputOaiSpan &&
+ parentId === info.currentTopLevelAgentSpanId
+ ) {
+ info.inputOaiSpan = oaiSpan
+ }
+ }
+
+ #updateTraceInfoOutput (oaiSpan) {
+ const info = this.#traceInfo.get(oaiSpan.traceId)
+ if (!info) return
+
+ if (
+ oaiSpan.parentId &&
+ info.currentTopLevelAgentSpanId &&
+ oaiSpan.parentId === info.currentTopLevelAgentSpanId
+ ) {
+ info.outputOaiSpan = oaiSpan
+ }
+ }
+
+ // ── Helpers ─────────────────────────────────────────────────────────────────
+
+ #resolveParent (oaiSpan) {
+ const parentId = oaiSpan.parentId
+ const traceId = oaiSpan.traceId
+ if (parentId) {
+ const parent = this.#oaiToDdSpan.get(parentId)
+ if (parent) return parent
+ }
+ if (traceId) {
+ const root = this.#oaiToDdSpan.get(traceId)
+ if (root) return root
+ }
+ }
+}
+
+module.exports = { OpenAIAgentsIntegration }
diff --git a/packages/datadog-plugin-openai-agents/src/processor.js b/packages/datadog-plugin-openai-agents/src/processor.js
new file mode 100644
index 0000000000..41be76eea0
--- /dev/null
+++ b/packages/datadog-plugin-openai-agents/src/processor.js
@@ -0,0 +1,107 @@
+'use strict'
+
+const log = require('../../dd-trace/src/log')
+
+const SPAN_KIND_BY_TYPE = {
+ agent: 'agent',
+ function: 'tool',
+ handoff: 'tool',
+ guardrail: 'task',
+ custom: 'task',
+ response: 'llm',
+}
+
+// Lifecycle methods are awaited by agents-core. Share one resolved Promise
+// across every callback so we don't allocate per span event.
+const RESOLVED = Promise.resolve()
+
+/**
+ * dd-trace-js implementation of the agents-core `TracingProcessor` interface.
+ * Registered via `addTraceProcessor(new DDOpenAIAgentsProcessor(integration))` inside
+ * the `@openai/agents` module load hook. Mirrors Python's LLMObsTraceProcessor.
+ *
+ * Each agents-core Span / Trace lifecycle event turns into a dd-trace span
+ * (APM + LLMObs-annotated) keyed off the agents-core spanId / traceId. Parent
+ * hierarchy is resolved through the agents-core parentId chain, which gives us
+ * correct multi-agent handoff nesting that ctx-argument capture cannot provide.
+ *
+ * agents-core awaits the lifecycle methods, so each one returns a settled
+ * Promise even though the work is synchronous.
+ */
+class DDOpenAIAgentsProcessor {
+ /**
+ * @param {() => (import('./integration').OpenAIAgentsIntegration | undefined)} getIntegration - Lazy accessor for the
+ * current OpenAIAgentsIntegration singleton. Read on each lifecycle event
+ * so re-instantiating the plugin doesn't strand the processor against an
+ * old integration reference inside agents-core.
+ */
+ constructor (getIntegration) {
+ this._getIntegration = getIntegration
+ }
+
+ onTraceStart (oaiTrace) {
+ const integration = this._getIntegration()
+ if (!integration?.enabled) return RESOLVED
+ try {
+ integration.startTrace(oaiTrace)
+ } catch (err) {
+ log.warn('[openai-agents] onTraceStart failed: %s', err)
+ }
+ return RESOLVED
+ }
+
+ onTraceEnd (oaiTrace) {
+ const integration = this._getIntegration()
+ if (!integration?.enabled) return RESOLVED
+ try {
+ integration.endTrace(oaiTrace)
+ } catch (err) {
+ log.warn('[openai-agents] onTraceEnd failed: %s', err)
+ }
+ return RESOLVED
+ }
+
+ onSpanStart (oaiSpan) {
+ const integration = this._getIntegration()
+ if (!integration?.enabled) return RESOLVED
+ if (!oaiSpan?.spanData) return RESOLVED // guard NoopSpan
+ const kind = SPAN_KIND_BY_TYPE[oaiSpan.spanData.type]
+ if (!kind) return RESOLVED // span types without an LLMObs kind are not traced
+ try {
+ integration.startSpan(oaiSpan, kind)
+ } catch (err) {
+ log.warn('[openai-agents] onSpanStart failed: %s', err)
+ }
+ return RESOLVED
+ }
+
+ onSpanEnd (oaiSpan) {
+ const integration = this._getIntegration()
+ if (!integration?.enabled) return RESOLVED
+ if (!oaiSpan?.spanData) return RESOLVED
+ try {
+ integration.endSpan(oaiSpan)
+ } catch (err) {
+ log.warn('[openai-agents] onSpanEnd failed: %s', err)
+ }
+ return RESOLVED
+ }
+
+ forceFlush () {
+ // dd-trace exports on its own schedule; nothing to force here.
+ return RESOLVED
+ }
+
+ shutdown () {
+ const integration = this._getIntegration()
+ if (!integration) return RESOLVED
+ try {
+ integration.clearState()
+ } catch (err) {
+ log.warn('[openai-agents] shutdown cleanup failed: %s', err)
+ }
+ return RESOLVED
+ }
+}
+
+module.exports = { DDOpenAIAgentsProcessor }
diff --git a/packages/datadog-plugin-openai-agents/src/util.js b/packages/datadog-plugin-openai-agents/src/util.js
new file mode 100644
index 0000000000..062163b808
--- /dev/null
+++ b/packages/datadog-plugin-openai-agents/src/util.js
@@ -0,0 +1,60 @@
+'use strict'
+
+// agents-core's `error` is a plain `{ message, data }` object, not a JS Error
+// — there's no constructor to name and no stack. We tag a stable type constant
+// and stringify `data` into the message so the LLMObs error shape stays
+// consistent with other integrations.
+const AGENTS_ERROR_TYPE = 'AgentsCoreError'
+
+/**
+ * Build the dd-trace span name from an agents-core oai-span. Handoffs collapse
+ * the target agent name into snake_case under a `transfer_to_` prefix; other
+ * span types use the SDK-provided name, falling back to
+ * `openai_agents.` (or `openai_agents.request` when even the type is
+ * missing).
+ *
+ * @param {object} oaiSpan
+ * @returns {string}
+ */
+function deriveSpanName (oaiSpan) {
+ const spanData = oaiSpan.spanData
+ if (spanData?.type === 'handoff') {
+ const toAgent = spanData.to_agent || ''
+ if (toAgent) return `transfer_to_${toAgent.replaceAll(' ', '_').toLowerCase()}`
+ }
+ if (spanData?.name) return spanData.name
+ return spanData?.type ? `openai_agents.${spanData.type}` : 'openai_agents.request'
+}
+
+/**
+ * Apply agents-core's error shape onto a dd-trace span. No-op when the
+ * oai-span has no error attached.
+ *
+ * @param {object} ddSpan
+ * @param {object} oaiSpan
+ */
+function applyError (ddSpan, oaiSpan) {
+ const err = oaiSpan.error
+ if (!err) return
+
+ ddSpan.setTag('error', true)
+
+ let errorMessage = err.message || 'Error'
+ if (err.data) {
+ try {
+ errorMessage = JSON.stringify(err.data)
+ } catch {
+ // circular / non-serializable — fall back to the raw message
+ }
+ }
+
+ ddSpan.setTag('error.type', AGENTS_ERROR_TYPE)
+ ddSpan.setTag('error.message', errorMessage)
+ ddSpan.setTag('error.stack', err.stack || '')
+}
+
+module.exports = {
+ AGENTS_ERROR_TYPE,
+ deriveSpanName,
+ applyError,
+}
diff --git a/packages/datadog-plugin-openai-agents/test/index.spec.js b/packages/datadog-plugin-openai-agents/test/index.spec.js
new file mode 100644
index 0000000000..d9329e5498
--- /dev/null
+++ b/packages/datadog-plugin-openai-agents/test/index.spec.js
@@ -0,0 +1,157 @@
+'use strict'
+
+const assert = require('node:assert/strict')
+
+const { createIntegrationTestSuite } = require('../../dd-trace/test/setup/helpers/plugin-test-helpers')
+const { ANY_STRING, assertObjectContains } = require('../../../integration-tests/helpers')
+const TestSetup = require('./test-setup')
+
+const testSetup = new TestSetup()
+
+function findSpan (traces, predicate) {
+ return traces.flat().find(predicate)
+}
+
+/**
+ * Integration test suite for the processor-driven openai-agents integration.
+ *
+ * agents-core only creates Span objects inside real agent-execution flows
+ * (run(), withTrace(), withAgentSpan(), withResponseSpan(), etc.) — direct
+ * calls to helpers like invokeFunctionTool / onInvokeHandoff / runToolGuardrails
+ * do NOT emit Spans because agents-core's span creation happens in the runner,
+ * not in those helpers. The tests below exercise real flows.
+ */
+createIntegrationTestSuite('openai-agents', '@openai/agents', {
+ category: 'generative-ai',
+ additionalPlugins: ['openai'],
+}, (meta) => {
+ const { agent } = meta
+
+ before(async () => {
+ await testSetup.setup(meta.mod, meta.version)
+ })
+
+ after(async () => {
+ await testSetup.teardown()
+ })
+
+ describe('run() — single-agent workflow', () => {
+ it('emits a workflow span with correct component/kind tags', async () => {
+ const traceAssertion = agent.assertSomeTraces((traces) => {
+ const workflowSpan = findSpan(traces, s => s.name === 'Agent workflow')
+ assertObjectContains(workflowSpan, {
+ name: 'Agent workflow',
+ service: ANY_STRING,
+ meta: {
+ component: 'openai-agents',
+ 'span.kind': 'internal',
+ },
+ })
+ })
+
+ const result = await testSetup.run()
+ assert.ok(result, 'run() should return a result object')
+ assert.notStrictEqual(result.finalOutput, undefined, 'run() should have finalOutput')
+
+ return traceAssertion
+ })
+
+ it('emits an agent span named after the running agent', async () => {
+ const traceAssertion = agent.assertSomeTraces((traces) => {
+ const agentSpan = findSpan(traces, s => s.name === 'test_agent')
+ assertObjectContains(agentSpan, {
+ name: 'test_agent',
+ meta: { component: 'openai-agents' },
+ })
+ })
+
+ await testSetup.run()
+ return traceAssertion
+ })
+
+ it('parents the openai.request span under the agent span', async () => {
+ const traceAssertion = agent.assertSomeTraces((traces) => {
+ const flat = traces.flat()
+ const agentSpan = flat.find(s => s.name === 'test_agent')
+ const openaiSpan = flat.find(s => s.name === 'openai.request')
+
+ assert.ok(agentSpan, 'expected an agent span')
+ assert.ok(openaiSpan, 'expected an openai.request span')
+ assert.equal(
+ openaiSpan.parent_id.toString(),
+ agentSpan.span_id.toString(),
+ 'openai.request should be a child of the agent span'
+ )
+ })
+
+ await testSetup.run()
+ return traceAssertion
+ })
+
+ it('parents the streamed openai.request span under the agent span', async () => {
+ const traceAssertion = agent.assertSomeTraces((traces) => {
+ const flat = traces.flat()
+ const agentSpan = flat.find(s => s.name === 'test_agent')
+ const openaiSpan = flat.find(s => s.name === 'openai.request')
+
+ assert.ok(agentSpan, 'expected an agent span')
+ assert.ok(openaiSpan, 'expected an openai.request span')
+ assert.equal(
+ openaiSpan.parent_id.toString(),
+ agentSpan.span_id.toString(),
+ 'streamed openai.request should be a child of the agent span'
+ )
+ })
+
+ await testSetup.runStreamed()
+ return traceAssertion
+ })
+
+ it('finishes the workflow span even when the underlying agent throws', async () => {
+ const traceAssertion = agent.assertSomeTraces((traces) => {
+ const workflowSpan = findSpan(traces, s => s.name === 'Agent workflow')
+ assertObjectContains(workflowSpan, {
+ name: 'Agent workflow',
+ meta: { component: 'openai-agents' },
+ })
+ })
+
+ try {
+ await testSetup.runError()
+ } catch (err) {
+ // errorAgent triggers an intentional error from the mocked model
+ }
+
+ return traceAssertion
+ })
+ })
+
+ describe('multi-agent handoff hierarchy', () => {
+ it('keeps both agents in the workflow across a real Runner.run() handoff', async () => {
+ const traceAssertion = agent.assertSomeTraces((traces) => {
+ const flat = traces.flat()
+
+ const workflow = flat.find(s => s.name === 'Agent workflow')
+ const agentA = flat.find(s => s.name === 'agent_a')
+ const handoff = flat.find(s => s.name === 'transfer_to_agent_b')
+ const agentB = flat.find(s => s.name === 'agent_b')
+
+ assert.ok(workflow, 'expected a workflow span')
+ assert.ok(agentA, 'expected an agent span named agent_a')
+ assert.ok(handoff, 'expected a handoff span named transfer_to_agent_b')
+ assert.ok(agentB, 'expected an agent span named agent_b')
+
+ assert.equal(agentA.parent_id.toString(), workflow.span_id.toString(),
+ 'agent_a should be a child of the workflow span')
+ assert.equal(handoff.parent_id.toString(), agentA.span_id.toString(),
+ 'handoff should be a child of agent_a')
+ assert.equal(agentB.parent_id.toString(), workflow.span_id.toString(),
+ 'agent_b should be a child of the workflow span')
+ })
+
+ const result = await testSetup.multiAgentHandoff()
+ assert.strictEqual(result.finalOutput, 'done')
+ return traceAssertion
+ })
+ })
+})
diff --git a/packages/datadog-plugin-openai-agents/test/integration.spec.js b/packages/datadog-plugin-openai-agents/test/integration.spec.js
new file mode 100644
index 0000000000..c3ccd9a4cb
--- /dev/null
+++ b/packages/datadog-plugin-openai-agents/test/integration.spec.js
@@ -0,0 +1,323 @@
+'use strict'
+
+const assert = require('node:assert/strict')
+const sinon = require('sinon')
+
+const LLMObsTagger = require('../../dd-trace/src/llmobs/tagger')
+const { OpenAIAgentsIntegration } = require('../src/integration')
+
+function makeFakeSpan (spanId = 'dd-span-id') {
+ const context = {
+ _trace: { tags: {} },
+ toSpanId: () => spanId,
+ toTraceId: () => '00000000000000001111111111111111',
+ }
+
+ return {
+ setTag: sinon.stub(),
+ setOperationName: sinon.stub(),
+ finish: sinon.stub(),
+ context: () => context,
+ }
+}
+
+function makeFakeTracer (preseededSpans = []) {
+ const startSpan = sinon.stub()
+ preseededSpans.forEach((span, i) => startSpan.onCall(i).returns(span))
+ startSpan.callsFake(() => makeFakeSpan())
+ return { startSpan }
+}
+
+function build ({
+ tracerSpans = [],
+ config = { llmobs: { DD_LLMOBS_ENABLED: false } },
+} = {}) {
+ const tracer = makeFakeTracer(tracerSpans)
+ const integration = new OpenAIAgentsIntegration({
+ tracer,
+ config,
+ })
+ return { integration, tracer }
+}
+
+afterEach(() => sinon.restore())
+
+describe('OpenAIAgentsIntegration', () => {
+ describe('enabled flag', () => {
+ it('starts disabled and flips via setEnabled', () => {
+ const { integration } = build()
+ assert.strictEqual(integration.enabled, false)
+ integration.setEnabled(true)
+ assert.strictEqual(integration.enabled, true)
+ integration.setEnabled(false)
+ assert.strictEqual(integration.enabled, false)
+ })
+ })
+
+ describe('setClientBaseURL', () => {
+ it('ignores non-string baseURL values', () => {
+ const { integration, tracer } = build()
+ integration.setClientBaseURL(undefined)
+ integration.setClientBaseURL(null)
+ integration.setClientBaseURL(123)
+ sinon.assert.notCalled(tracer.startSpan)
+ })
+
+ it('ignores the empty string', () => {
+ const { integration } = build()
+ integration.setClientBaseURL('')
+ })
+
+ it('accepts a recognised URL', () => {
+ const { integration } = build()
+ integration.setClientBaseURL('https://my-resource.openai.azure.com/openai')
+ })
+ })
+
+ describe('startTrace', () => {
+ it('does nothing when traceId is missing', () => {
+ const { integration, tracer } = build()
+ integration.startTrace({})
+ sinon.assert.notCalled(tracer.startSpan)
+ })
+
+ it('falls back to the default workflow name when oaiTrace.name is empty', () => {
+ const { integration, tracer } = build()
+ integration.startTrace({ traceId: 't1' })
+ sinon.assert.calledOnce(tracer.startSpan)
+ assert.strictEqual(tracer.startSpan.firstCall.args[0], 'Agent workflow')
+ })
+
+ it('uses oaiTrace.name when provided', () => {
+ const { integration, tracer } = build()
+ integration.startTrace({ traceId: 't1', name: 'My workflow', groupId: 'g1' })
+ assert.strictEqual(tracer.startSpan.firstCall.args[0], 'My workflow')
+ })
+
+ it('registers LLMObs spans when DD_LLMOBS_ENABLED is true', () => {
+ const workflowSpan = makeFakeSpan()
+ const { integration } = build({
+ tracerSpans: [workflowSpan],
+ config: {
+ llmobs: {
+ DD_LLMOBS_ENABLED: true,
+ mlApp: 'test',
+ sampleRate: 1,
+ },
+ },
+ })
+
+ integration.startTrace({ traceId: 't1' })
+
+ assert.strictEqual(LLMObsTagger.tagMap.get(workflowSpan)['_ml_obs.meta.span.kind'], 'workflow')
+ })
+ })
+
+ describe('endTrace / #completeWorkflowSpan', () => {
+ it('does nothing when traceId is missing', () => {
+ const { integration } = build()
+ integration.endTrace({})
+ })
+
+ it('does nothing when no span is mapped to the traceId', () => {
+ const { integration } = build()
+ integration.endTrace({ traceId: 'unknown' })
+ })
+
+ it('applies a rootAgentSpan error with a message onto the workflow span', () => {
+ const workflowSpan = makeFakeSpan()
+ const { integration } = build({ tracerSpans: [workflowSpan, makeFakeSpan()] })
+ integration.startTrace({ traceId: 't1' })
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', parentId: null, spanData: { type: 'agent' } },
+ 'agent'
+ )
+ integration.endSpan({
+ spanId: 's1',
+ traceId: 't1',
+ parentId: null,
+ spanData: { type: 'agent' },
+ error: { message: 'oh no' },
+ })
+ sinon.assert.calledWith(workflowSpan.setTag, 'error', true)
+ sinon.assert.calledWith(workflowSpan.setTag, 'error.type', sinon.match.string)
+ sinon.assert.calledWith(workflowSpan.setTag, 'error.message', 'oh no')
+ sinon.assert.called(workflowSpan.finish)
+ })
+
+ it('still flags an error when rootAgentSpan.error has no message', () => {
+ const workflowSpan = makeFakeSpan()
+ const { integration } = build({ tracerSpans: [workflowSpan, makeFakeSpan()] })
+ integration.startTrace({ traceId: 't1' })
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', parentId: null, spanData: { type: 'agent' } },
+ 'agent'
+ )
+ integration.endSpan({
+ spanId: 's1',
+ traceId: 't1',
+ parentId: null,
+ spanData: { type: 'agent' },
+ error: {},
+ })
+ sinon.assert.calledWith(workflowSpan.setTag, 'error', true)
+ const messageCalls = workflowSpan.setTag.getCalls().filter(c => c.args[0] === 'error.message')
+ assert.strictEqual(messageCalls.length, 0)
+ })
+
+ it('keeps the workflow open when a successful top-level agent ends', () => {
+ const workflowSpan = makeFakeSpan()
+ const agentSpan = makeFakeSpan()
+ const { integration } = build({ tracerSpans: [workflowSpan, agentSpan] })
+ integration.startTrace({ traceId: 't1' })
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', parentId: null, spanData: { type: 'agent' } },
+ 'agent'
+ )
+
+ integration.endSpan({
+ spanId: 's1',
+ traceId: 't1',
+ parentId: null,
+ spanData: { type: 'agent' },
+ })
+
+ sinon.assert.notCalled(workflowSpan.finish)
+ integration.endTrace({ traceId: 't1' })
+ sinon.assert.calledOnce(workflowSpan.finish)
+
+ integration.endTrace({ traceId: 't1' })
+ sinon.assert.calledOnce(workflowSpan.finish)
+ })
+ })
+
+ describe('startSpan', () => {
+ it('does nothing when spanId is missing', () => {
+ const { integration, tracer } = build()
+ integration.startSpan({}, 'agent')
+ sinon.assert.notCalled(tracer.startSpan)
+ })
+
+ it('defaults span.kind to internal for unknown LLMObs kinds', () => {
+ const { integration, tracer } = build()
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', spanData: { type: 'custom' } },
+ 'unknown-kind'
+ )
+ const tags = tracer.startSpan.firstCall.args[1].tags
+ assert.strictEqual(tags['span.kind'], 'internal')
+ })
+
+ it('maps llm kind to span.kind=client', () => {
+ const { integration, tracer } = build()
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', spanData: { type: 'response' } },
+ 'llm'
+ )
+ const tags = tracer.startSpan.firstCall.args[1].tags
+ assert.strictEqual(tags['span.kind'], 'client')
+ })
+
+ it('maps agent kind to span.kind=internal', () => {
+ const { integration, tracer } = build()
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', spanData: { type: 'agent' } },
+ 'agent'
+ )
+ const tags = tracer.startSpan.firstCall.args[1].tags
+ assert.strictEqual(tags['span.kind'], 'internal')
+ })
+ })
+
+ describe('endSpan', () => {
+ it('does nothing when spanId is unknown', () => {
+ const { integration } = build()
+ integration.endSpan({ spanId: 'missing', spanData: { type: 'function' } })
+ })
+
+ it('finishes the dd-trace span on end', () => {
+ const ddSpan = makeFakeSpan()
+ const { integration } = build({ tracerSpans: [ddSpan] })
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', spanData: { type: 'function' } },
+ 'tool'
+ )
+ integration.endSpan({ spanId: 's1', spanData: { type: 'function' } })
+ sinon.assert.called(ddSpan.finish)
+ })
+
+ it('renames handoff spans after the target agent is available', () => {
+ const ddSpan = makeFakeSpan()
+ const { integration } = build({ tracerSpans: [ddSpan] })
+ const oaiSpan = {
+ spanId: 's1',
+ traceId: 't1',
+ spanData: { type: 'handoff', from_agent: 'agent_a' },
+ }
+ integration.startSpan(oaiSpan, 'tool')
+ oaiSpan.spanData.to_agent = 'Agent B'
+
+ integration.endSpan(oaiSpan)
+
+ sinon.assert.calledWith(ddSpan.setOperationName, 'transfer_to_agent_b')
+ })
+ })
+
+ describe('#resolveParent', () => {
+ it('uses the parent dd-trace span when parentId is mapped', () => {
+ const parentSpan = makeFakeSpan()
+ const childSpan = makeFakeSpan()
+ const { integration, tracer } = build({ tracerSpans: [parentSpan, childSpan] })
+ integration.startSpan(
+ { spanId: 'p1', traceId: 't1', spanData: { type: 'agent' } },
+ 'agent'
+ )
+ integration.startSpan(
+ { spanId: 'c1', traceId: 't1', parentId: 'p1', spanData: { type: 'function' } },
+ 'tool'
+ )
+ assert.strictEqual(tracer.startSpan.secondCall.args[1].childOf, parentSpan)
+ })
+
+ it('falls back to the trace root span when parentId has no mapping', () => {
+ const root = makeFakeSpan()
+ const child = makeFakeSpan()
+ const { integration, tracer } = build({ tracerSpans: [root, child] })
+ integration.startTrace({ traceId: 't1' })
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', parentId: 'unknown-parent', spanData: { type: 'agent' } },
+ 'agent'
+ )
+ assert.strictEqual(tracer.startSpan.secondCall.args[1].childOf, root)
+ })
+
+ it('returns undefined when neither parent nor trace root is mapped', () => {
+ const orphan = makeFakeSpan()
+ const { integration, tracer } = build({ tracerSpans: [orphan] })
+ integration.startSpan(
+ { spanId: 's1', traceId: 'no-trace', parentId: 'no-parent', spanData: { type: 'agent' } },
+ 'agent'
+ )
+ assert.strictEqual(tracer.startSpan.firstCall.args[1].childOf, undefined)
+ })
+ })
+
+ describe('clearState', () => {
+ it('finishes every in-flight dd-trace span and clears bookkeeping', () => {
+ const workflow = makeFakeSpan()
+ const agentSpan = makeFakeSpan()
+ const { integration } = build({ tracerSpans: [workflow, agentSpan] })
+ integration.startTrace({ traceId: 't1' })
+ integration.startSpan(
+ { spanId: 's1', traceId: 't1', spanData: { type: 'agent' } },
+ 'agent'
+ )
+ integration.clearState()
+ sinon.assert.called(workflow.finish)
+ sinon.assert.called(agentSpan.finish)
+ // After clearState, a second endTrace should be a no-op because the
+ // span map was cleared.
+ integration.endTrace({ traceId: 't1' })
+ })
+ })
+})
diff --git a/packages/datadog-plugin-openai-agents/test/processor.spec.js b/packages/datadog-plugin-openai-agents/test/processor.spec.js
new file mode 100644
index 0000000000..456c4c2107
--- /dev/null
+++ b/packages/datadog-plugin-openai-agents/test/processor.spec.js
@@ -0,0 +1,166 @@
+'use strict'
+
+const assert = require('node:assert/strict')
+const sinon = require('sinon')
+
+const { DDOpenAIAgentsProcessor } = require('../src/processor')
+const log = require('../../dd-trace/src/log')
+
+describe('DDOpenAIAgentsProcessor', () => {
+ let warnStub
+
+ beforeEach(() => {
+ warnStub = sinon.stub(log, 'warn')
+ })
+
+ afterEach(() => {
+ sinon.restore()
+ })
+
+ function makeIntegration (overrides = {}) {
+ return {
+ enabled: true,
+ startTrace: sinon.stub(),
+ endTrace: sinon.stub(),
+ startSpan: sinon.stub(),
+ endSpan: sinon.stub(),
+ clearState: sinon.stub(),
+ ...overrides,
+ }
+ }
+
+ describe('when no integration is registered', () => {
+ const processor = new DDOpenAIAgentsProcessor(() => undefined)
+
+ it('returns a resolved promise from every lifecycle method without throwing', async () => {
+ await processor.onTraceStart({})
+ await processor.onTraceEnd({})
+ await processor.onSpanStart({ spanData: { type: 'agent' } })
+ await processor.onSpanEnd({ spanData: { type: 'agent' } })
+ await processor.forceFlush()
+ await processor.shutdown()
+ })
+
+ it('shutdown skips clearState when the integration is missing', async () => {
+ await processor.shutdown()
+ // No throw, nothing to assert beyond the absence of work.
+ })
+ })
+
+ describe('when the integration is disabled', () => {
+ const integration = makeIntegration({ enabled: false })
+ const processor = new DDOpenAIAgentsProcessor(() => integration)
+
+ it('skips startTrace/endTrace/startSpan/endSpan', async () => {
+ await processor.onTraceStart({})
+ await processor.onTraceEnd({})
+ await processor.onSpanStart({ spanData: { type: 'agent' } })
+ await processor.onSpanEnd({ spanData: { type: 'agent' } })
+ sinon.assert.notCalled(integration.startTrace)
+ sinon.assert.notCalled(integration.endTrace)
+ sinon.assert.notCalled(integration.startSpan)
+ sinon.assert.notCalled(integration.endSpan)
+ })
+ })
+
+ describe('onSpanStart guards', () => {
+ const integration = makeIntegration()
+ const processor = new DDOpenAIAgentsProcessor(() => integration)
+
+ it('returns without calling startSpan when spanData is absent (NoopSpan guard)', async () => {
+ await processor.onSpanStart({})
+ sinon.assert.notCalled(integration.startSpan)
+ })
+
+ it('returns without calling startSpan for span types that have no LLMObs kind', async () => {
+ for (const type of ['generation']) {
+ integration.startSpan.resetHistory()
+ await processor.onSpanStart({ spanData: { type } })
+ sinon.assert.notCalled(integration.startSpan)
+ }
+ })
+
+ it('maps recognised span types to the expected LLMObs kind', async () => {
+ const cases = [
+ ['agent', 'agent'],
+ ['function', 'tool'],
+ ['handoff', 'tool'],
+ ['guardrail', 'task'],
+ ['custom', 'task'],
+ ['response', 'llm'],
+ ]
+ for (const [type, expectedKind] of cases) {
+ integration.startSpan.resetHistory()
+ await processor.onSpanStart({ spanData: { type } })
+ sinon.assert.calledOnce(integration.startSpan)
+ assert.strictEqual(integration.startSpan.firstCall.args[1], expectedKind)
+ }
+ })
+ })
+
+ describe('onSpanEnd guards', () => {
+ const integration = makeIntegration()
+ const processor = new DDOpenAIAgentsProcessor(() => integration)
+
+ it('returns without calling endSpan when spanData is absent', async () => {
+ await processor.onSpanEnd({})
+ sinon.assert.notCalled(integration.endSpan)
+ })
+ })
+
+ describe('error handling', () => {
+ const err = new Error('boom')
+
+ function processorWithThrowing (method) {
+ const integration = makeIntegration()
+ integration[method] = sinon.stub().throws(err)
+ return [new DDOpenAIAgentsProcessor(() => integration), integration]
+ }
+
+ it('logs and swallows startTrace failures', async () => {
+ const [p] = processorWithThrowing('startTrace')
+ await p.onTraceStart({})
+ sinon.assert.calledOnce(warnStub)
+ })
+
+ it('logs and swallows endTrace failures', async () => {
+ const [p] = processorWithThrowing('endTrace')
+ await p.onTraceEnd({})
+ sinon.assert.calledOnce(warnStub)
+ })
+
+ it('logs and swallows startSpan failures', async () => {
+ const [p] = processorWithThrowing('startSpan')
+ await p.onSpanStart({ spanData: { type: 'agent' } })
+ sinon.assert.calledOnce(warnStub)
+ })
+
+ it('logs and swallows endSpan failures', async () => {
+ const [p] = processorWithThrowing('endSpan')
+ await p.onSpanEnd({ spanData: { type: 'agent' } })
+ sinon.assert.calledOnce(warnStub)
+ })
+
+ it('logs and swallows shutdown clearState failures', async () => {
+ const [p] = processorWithThrowing('clearState')
+ await p.shutdown()
+ sinon.assert.calledOnce(warnStub)
+ })
+ })
+
+ it('forceFlush resolves without doing work', async () => {
+ const processor = new DDOpenAIAgentsProcessor(() => makeIntegration())
+ await processor.forceFlush()
+ })
+
+ it('reads the integration lazily on each lifecycle event', async () => {
+ let calls = 0
+ const integrations = [makeIntegration(), makeIntegration()]
+ const processor = new DDOpenAIAgentsProcessor(() => integrations[calls++ % 2])
+
+ await processor.onTraceStart({})
+ await processor.onTraceStart({})
+ sinon.assert.calledOnce(integrations[0].startTrace)
+ sinon.assert.calledOnce(integrations[1].startTrace)
+ })
+})
diff --git a/packages/datadog-plugin-openai-agents/test/test-setup.js b/packages/datadog-plugin-openai-agents/test/test-setup.js
new file mode 100644
index 0000000000..f160c8622e
--- /dev/null
+++ b/packages/datadog-plugin-openai-agents/test/test-setup.js
@@ -0,0 +1,231 @@
+'use strict'
+
+const path = require('node:path')
+
+function createStreamResponse (status) {
+ return {
+ id: 'resp_test',
+ object: 'response',
+ created_at: 0,
+ status,
+ output: status === 'completed'
+ ? [{
+ id: 'msg_test',
+ type: 'message',
+ status: 'completed',
+ role: 'assistant',
+ content: [{ type: 'output_text', text: 'hello', annotations: [] }],
+ }]
+ : [],
+ usage: status === 'completed'
+ ? {
+ input_tokens: 1,
+ output_tokens: 1,
+ total_tokens: 2,
+ input_tokens_details: { cached_tokens: 0 },
+ output_tokens_details: { reasoning_tokens: 0 },
+ }
+ : null,
+ model: 'gpt-4-0613',
+ parallel_tool_calls: true,
+ temperature: 1,
+ text: { format: { type: 'text' } },
+ tool_choice: 'auto',
+ tools: [],
+ top_p: 1,
+ truncation: 'disabled',
+ metadata: {},
+ }
+}
+
+function createStreamEvent (event, data) {
+ return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`
+}
+
+function createStreamingFetch () {
+ return async () => {
+ const responseStarted = createStreamResponse('in_progress')
+ const responseCompleted = createStreamResponse('completed')
+ const body = [
+ createStreamEvent('response.created', {
+ type: 'response.created',
+ response: responseStarted,
+ sequence_number: 0,
+ }),
+ createStreamEvent('response.output_text.delta', {
+ type: 'response.output_text.delta',
+ content_index: 0,
+ delta: 'hello',
+ item_id: 'msg_test',
+ output_index: 0,
+ sequence_number: 1,
+ }),
+ createStreamEvent('response.completed', {
+ type: 'response.completed',
+ response: responseCompleted,
+ sequence_number: 2,
+ }),
+ 'data: [DONE]\n\n',
+ ].join('')
+
+ return new Response(body, {
+ status: 200,
+ headers: {
+ 'content-type': 'text/event-stream',
+ 'x-request-id': 'req_test',
+ },
+ })
+ }
+}
+
+function createResponseFetch () {
+ return async () => {
+ return new Response(JSON.stringify(createStreamResponse('completed')), {
+ status: 200,
+ headers: {
+ 'content-type': 'application/json',
+ 'x-request-id': 'req_test',
+ },
+ })
+ }
+}
+
+function createHandoffFetch () {
+ let request = 0
+
+ return async () => {
+ const response = createStreamResponse('completed')
+ response.output = request++ === 0
+ ? [{
+ id: 'fc_handoff',
+ type: 'function_call',
+ call_id: 'call_handoff',
+ name: 'transfer_to_agent_b',
+ arguments: '{}',
+ status: 'completed',
+ }]
+ : [{
+ id: 'msg_final',
+ type: 'message',
+ status: 'completed',
+ role: 'assistant',
+ content: [{ type: 'output_text', text: 'done', annotations: [] }],
+ }]
+
+ return new Response(JSON.stringify(response), {
+ status: 200,
+ headers: {
+ 'content-type': 'application/json',
+ 'x-request-id': `req_handoff_${request}`,
+ },
+ })
+ }
+}
+
+class OpenaiAgentsTestSetup {
+ async setup (clientModule, version) {
+ this.module = clientModule
+
+ const agentsOpenaiDir = path.join(__dirname, '..', '..', '..', 'versions', '@openai', `agents-openai@${version}`)
+ const { OpenAIResponsesModel } = require(agentsOpenaiDir).get()
+ const openaiPath = require.resolve('openai', {
+ paths: [path.join(__dirname, '..', '..', '..', 'versions', 'node_modules', '@openai', 'agents-openai')],
+ })
+ const { OpenAI } = require(openaiPath)
+
+ const mockClient = new OpenAI({
+ apiKey: 'test',
+ baseURL: 'https://api.openai.com/v1',
+ fetch: createResponseFetch(),
+ })
+
+ clientModule.setDefaultModelProvider({
+ createModel: (modelName) => new OpenAIResponsesModel(mockClient, modelName),
+ })
+
+ const mockErrorClient = {
+ baseURL: 'https://api.openai.com/v1',
+ responses: {
+ create: async () => {
+ throw new Error('Intentional error for testing')
+ },
+ },
+ }
+
+ const fakeModel = new OpenAIResponsesModel(mockClient, 'gpt-4')
+ const streamModel = new OpenAIResponsesModel(new OpenAI({
+ apiKey: 'test',
+ baseURL: 'https://api.openai.com/v1',
+ fetch: createStreamingFetch(),
+ }), 'gpt-4')
+ const errorModel = new OpenAIResponsesModel(mockErrorClient, 'gpt-4')
+ const handoffModel = new OpenAIResponsesModel(new OpenAI({
+ apiKey: 'test',
+ baseURL: 'https://api.openai.com/v1',
+ fetch: createHandoffFetch(),
+ }), 'gpt-4')
+
+ this.agent = new clientModule.Agent({
+ name: 'test_agent',
+ instructions: 'You are a test agent',
+ model: fakeModel,
+ })
+
+ this.streamAgent = new clientModule.Agent({
+ name: 'test_agent',
+ instructions: 'You are a test agent',
+ model: streamModel,
+ })
+
+ this.errorAgent = new clientModule.Agent({
+ name: 'error_agent',
+ instructions: 'You are an error test agent',
+ model: errorModel,
+ })
+
+ this.handoffAgentB = new clientModule.Agent({
+ name: 'agent_b',
+ instructions: 'Finish the request',
+ model: handoffModel,
+ })
+ this.handoffAgentA = new clientModule.Agent({
+ name: 'agent_a',
+ instructions: 'Hand the request to agent_b',
+ model: handoffModel,
+ handoffs: [this.handoffAgentB],
+ })
+ }
+
+ async teardown () {
+ this.module = undefined
+ this.agent = undefined
+ this.streamAgent = undefined
+ this.errorAgent = undefined
+ this.handoffAgentA = undefined
+ this.handoffAgentB = undefined
+ }
+
+ async run () {
+ return this.module.run(this.agent, 'hello', { maxTurns: 2 })
+ }
+
+ async runStreamed () {
+ const result = await this.module.run(this.streamAgent, 'hello', { maxTurns: 2, stream: true })
+ for await (const event of result) {
+ // Drain the stream so the SDK finishes the underlying response span.
+ if (event === undefined) continue
+ }
+ await result.completed
+ return result
+ }
+
+ async runError () {
+ return this.module.run(this.errorAgent, 'hello', { maxTurns: 1 })
+ }
+
+ async multiAgentHandoff () {
+ return this.module.run(this.handoffAgentA, 'start', { maxTurns: 2 })
+ }
+}
+
+module.exports = OpenaiAgentsTestSetup
diff --git a/packages/dd-trace/src/config/generated-config-types.d.ts b/packages/dd-trace/src/config/generated-config-types.d.ts
index ef991ae73e..8a386bd45d 100644
--- a/packages/dd-trace/src/config/generated-config-types.d.ts
+++ b/packages/dd-trace/src/config/generated-config-types.d.ts
@@ -333,6 +333,7 @@ export interface GeneratedConfig {
DD_TRACE_NODE_SERIALIZE_ENABLED: boolean;
DD_TRACE_NYC_ENABLED: boolean;
DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: string;
+ DD_TRACE_OPENAI_AGENTS_ENABLED: boolean;
DD_TRACE_OPENAI_ENABLED: boolean;
DD_TRACE_OPENSEARCH_ENABLED: boolean;
DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
@@ -1028,6 +1029,7 @@ export interface GeneratedEnvVarConfig {
DD_TRACE_NODE_SERIALIZE_ENABLED: boolean;
DD_TRACE_NYC_ENABLED: boolean;
DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: string;
+ DD_TRACE_OPENAI_AGENTS_ENABLED: boolean;
DD_TRACE_OPENAI_ENABLED: boolean;
DD_TRACE_OPENSEARCH_ENABLED: boolean;
DD_TRACE_OPENSEARCH_PROJECT_OPENSEARCH_ENABLED: boolean;
diff --git a/packages/dd-trace/src/config/supported-configurations.json b/packages/dd-trace/src/config/supported-configurations.json
index 30d6866b1d..ad34c141bf 100644
--- a/packages/dd-trace/src/config/supported-configurations.json
+++ b/packages/dd-trace/src/config/supported-configurations.json
@@ -3452,6 +3452,13 @@
"default": "true"
}
],
+ "DD_TRACE_OPENAI_AGENTS_ENABLED": [
+ {
+ "implementation": "A",
+ "type": "boolean",
+ "default": "true"
+ }
+ ],
"DD_TRACE_OPENSEARCH_ENABLED": [
{
"implementation": "A",
diff --git a/packages/dd-trace/src/llmobs/index.js b/packages/dd-trace/src/llmobs/index.js
index 88439e175e..dfd3c41759 100644
--- a/packages/dd-trace/src/llmobs/index.js
+++ b/packages/dd-trace/src/llmobs/index.js
@@ -16,6 +16,8 @@ const {
SAMPLING_DECISION,
PROPAGATED_SAMPLE_RATE_KEY,
PROPAGATED_SAMPLING_DECISION_KEY,
+ TRACE_ID,
+ PROPAGATED_TRACE_ID_KEY,
} = require('./constants/tags')
const { storage } = require('./storage')
const telemetry = require('./telemetry')
@@ -25,6 +27,7 @@ const LLMObsTagger = require('./tagger')
const LLMObsSpanWriter = require('./writers/spans')
const { setAgentStrategy } = require('./writers/util')
const { INCOMPATIBLE_INITIALIZATION } = require('./constants/text')
+const { llmObsTraceIdToWire } = require('./util')
const spanFinishCh = channel('dd-trace:span:finish')
const evalMetricAppendCh = channel('llmobs:eval-metric:append')
@@ -137,8 +140,12 @@ function handleLLMObsInjection ({ carrier }) {
mlObsSpanTags?.[SESSION_ID] ??
parentContext?._trace?.tags?.[SESSION_ID_TRACE_DEFAULT_KEY] ??
parentContext?._trace?.tags?.[PROPAGATED_SESSION_ID_KEY]
+ const llmobsTraceId = mlObsSpanTags?.[TRACE_ID]
+ const propagatedTraceId = llmobsTraceId === undefined
+ ? parentContext?._trace?.tags?.[PROPAGATED_TRACE_ID_KEY]
+ : llmObsTraceIdToWire(llmobsTraceId)
- if (!parentId && !mlApp && samplingDecision == null && !sessionId) return
+ if (!parentId && !mlApp && samplingDecision == null && !sessionId && !propagatedTraceId) return
// `_injectTags` only writes `x-datadog-tags` when the trace has `_dd.p.*`
// tags, so it may be undefined here — coalesce before appending.
@@ -149,6 +156,7 @@ function handleLLMObsInjection ({ carrier }) {
if (sessionId) tags += `${tags ? ',' : ''}${PROPAGATED_SESSION_ID_KEY}=${sessionId}`
if (sampleRate != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
if (samplingDecision != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
+ if (propagatedTraceId != null) tags += `${tags ? ',' : ''}${PROPAGATED_TRACE_ID_KEY}=${propagatedTraceId}`
if (tags !== existing) carrier['x-datadog-tags'] = tags
}
diff --git a/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js b/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js
new file mode 100644
index 0000000000..e55d75d657
--- /dev/null
+++ b/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js
@@ -0,0 +1,211 @@
+'use strict'
+
+/**
+ * Extracts input messages for an LLM span. agents-openai stores only
+ * `request.input` on `spanData._input` (string or message-array), and the
+ * system instructions are echoed back on the response as `instructions`.
+ *
+ * @param {string|Array} input - The raw `request.input` (`spanData._input`).
+ * @param {string} [instructions] - System instructions echoed on `response.instructions`.
+ * @returns {Array<{ role: string, content: string }>}
+ */
+function extractInputMessages (input, instructions) {
+ const messages = []
+
+ if (instructions) {
+ messages.push({ role: 'system', content: instructions })
+ }
+
+ if (typeof input === 'string') {
+ messages.push({ role: 'user', content: input })
+ } else if (Array.isArray(input)) {
+ for (const item of input) {
+ if (item.type === 'message') {
+ const role = item.role
+ if (!role) continue
+
+ let content = ''
+ if (Array.isArray(item.content)) {
+ const textParts = item.content
+ .filter(c => c.type === 'input_text' || c.type === 'text')
+ .map(c => c.text)
+ content = textParts.join('')
+ } else if (typeof item.content === 'string') {
+ content = item.content
+ }
+
+ if (content) {
+ messages.push({ role, content })
+ }
+ } else if (item.type === 'function_call') {
+ let args = item.arguments
+ if (typeof args === 'string') {
+ try {
+ args = JSON.parse(args)
+ } catch {
+ args = {}
+ }
+ }
+ messages.push({
+ role: 'assistant',
+ toolCalls: [{
+ toolId: item.call_id,
+ name: item.name,
+ arguments: args,
+ type: item.type,
+ }],
+ })
+ } else if (item.type === 'function_call_output') {
+ messages.push({
+ role: 'user',
+ toolResults: [{
+ toolId: item.call_id,
+ result: item.output,
+ name: item.name || '',
+ type: item.type,
+ }],
+ })
+ }
+ }
+ }
+
+ return messages.length > 0 ? messages : [{ role: 'user', content: '' }]
+}
+
+/**
+ * Extracts output messages from the model response.
+ *
+ * @param {{ output?: Array }} result - The model response
+ * @returns {Array<{ role: string, content: string }>}
+ */
+function extractOutputMessages (result) {
+ const messages = []
+
+ if (result?.output) {
+ for (const item of result.output) {
+ if (item.type === 'message') {
+ let content = ''
+ if (Array.isArray(item.content)) {
+ const textParts = item.content
+ .filter(c => c.type === 'output_text')
+ .map(c => c.text)
+ content = textParts.join('')
+ } else if (typeof item.content === 'string') {
+ content = item.content
+ }
+
+ messages.push({ role: item.role || 'assistant', content })
+ } else if (item.type === 'function_call') {
+ let args = item.arguments
+ if (typeof args === 'string') {
+ try {
+ args = JSON.parse(args)
+ } catch {
+ args = {}
+ }
+ }
+ messages.push({
+ role: 'assistant',
+ toolCalls: [{
+ toolId: item.call_id,
+ name: item.name,
+ arguments: args,
+ type: item.type,
+ }],
+ })
+ }
+ }
+ }
+
+ return messages.length > 0 ? messages : [{ content: '', role: '' }]
+}
+
+/**
+ * Extracts token usage metrics from the model response. Returns `undefined`
+ * when there's nothing to tag, so callers can skip the tagger call without
+ * allocating an Object.keys array.
+ *
+ * @param {{ usage?: { inputTokens?: number, outputTokens?: number, totalTokens?: number,
+ * outputTokensDetails?: { reasoningTokens?: number } } }} result
+ * @returns {{ inputTokens?: number, outputTokens?: number, totalTokens?: number,
+ * reasoningOutputTokens?: number } | undefined}
+ */
+function extractMetrics (result) {
+ const usage = result?.usage
+ if (!usage) return
+
+ const inputTokens = usage.inputTokens ?? usage.input_tokens
+ const outputTokens = usage.outputTokens ?? usage.output_tokens
+ const totalTokens = usage.totalTokens ?? usage.total_tokens
+ const reasoningTokens = usage.outputTokensDetails?.reasoningTokens ??
+ usage.output_tokens_details?.reasoning_tokens
+
+ if (inputTokens === undefined && outputTokens === undefined &&
+ totalTokens === undefined && !reasoningTokens) return
+
+ const metrics = {}
+ if (inputTokens !== undefined) metrics.inputTokens = inputTokens
+ if (outputTokens !== undefined) metrics.outputTokens = outputTokens
+ // Tagger maps `reasoningOutputTokens` → `reasoning_output_tokens` in the
+ // LLMObs span event. Skip when zero — emitting a zero just adds noise.
+ if (reasoningTokens) metrics.reasoningOutputTokens = reasoningTokens
+
+ if (totalTokens !== undefined) {
+ metrics.totalTokens = totalTokens
+ } else if (metrics.inputTokens !== undefined && metrics.outputTokens !== undefined) {
+ metrics.totalTokens = metrics.inputTokens + metrics.outputTokens
+ }
+
+ return metrics
+}
+
+// Fields the OpenAI Responses API echoes back from the request configuration.
+// agents-openai only stores `request.input` on the span — the user's
+// `modelSettings` aren't directly observable, so we read the response-echoed
+// values. Matches dd-trace-py's openai-agents integration (see
+// `OaiSpanAdapter.llmobs_metadata`); both ship without filtering OpenAI's
+// default values.
+const RESPONSE_METADATA_FIELDS = [
+ 'temperature',
+ 'max_output_tokens',
+ 'top_p',
+ 'tools',
+ 'tool_choice',
+ 'truncation',
+]
+
+/**
+ * Extracts metadata from the model response. Mirrors Python's
+ * `OaiSpanAdapter.llmobs_metadata` — emits all response-echoed configuration
+ * fields plus `text` when present. Returns `undefined` when nothing was
+ * captured, so callers can skip the tagger call without allocating.
+ *
+ * @param {object | undefined} response
+ * @returns {object | undefined}
+ */
+function extractMetadata (response) {
+ if (!response) return
+
+ let metadata
+ for (const field of RESPONSE_METADATA_FIELDS) {
+ const value = response[field]
+ if (value !== undefined && value !== null) {
+ metadata ??= {}
+ metadata[field] = value
+ }
+ }
+
+ if (response.text) {
+ metadata ??= {}
+ metadata.text = response.text
+ }
+
+ return metadata
+}
+
+module.exports = {
+ extractInputMessages,
+ extractOutputMessages,
+ extractMetrics,
+ extractMetadata,
+}
diff --git a/packages/dd-trace/src/llmobs/plugins/openai/index.js b/packages/dd-trace/src/llmobs/plugins/openai/index.js
index 9dc881d022..090a36ae0b 100644
--- a/packages/dd-trace/src/llmobs/plugins/openai/index.js
+++ b/packages/dd-trace/src/llmobs/plugins/openai/index.js
@@ -5,7 +5,6 @@ const {
PROMPT_TRACKING_INSTRUMENTATION_METHOD,
PROMPT_MULTIMODAL,
INSTRUMENTATION_METHOD_AUTO,
- UNKNOWN_MODEL_PROVIDER,
} = require('../../constants/tags')
const { audioMimeTypeFromFormat, formatAudioPart, safeJsonParse } = require('../../util')
const { AUDIO_MIME_TYPES } = require('./constants')
@@ -15,6 +14,7 @@ const {
extractTextFromContentItem,
extractContentParts,
hasMultimodalInputs,
+ getOpenAIModelProvider,
} = require('./utils')
const allowedParamKeys = new Set([
@@ -108,14 +108,10 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
}
_getModelProviderAndClient (baseUrl = '') {
- if (baseUrl.includes('azure')) {
- return { modelProvider: 'azure_openai', client: 'AzureOpenAI' }
- } else if (baseUrl.includes('openai')) {
- return { modelProvider: 'openai', client: 'OpenAI' }
- } else if (baseUrl.includes('deepseek')) {
- return { modelProvider: 'deepseek', client: 'DeepSeek' }
- }
- return { modelProvider: UNKNOWN_MODEL_PROVIDER, client: 'OpenAI' }
+ const modelProvider = getOpenAIModelProvider(baseUrl)
+ if (modelProvider === 'azure_openai') return { modelProvider, client: 'AzureOpenAI' }
+ if (modelProvider === 'deepseek') return { modelProvider, client: 'DeepSeek' }
+ return { modelProvider, client: 'OpenAI' }
}
_extractMetrics (response) {
diff --git a/packages/dd-trace/src/llmobs/plugins/openai/utils.js b/packages/dd-trace/src/llmobs/plugins/openai/utils.js
index bb0014ba0c..17a10a12cc 100644
--- a/packages/dd-trace/src/llmobs/plugins/openai/utils.js
+++ b/packages/dd-trace/src/llmobs/plugins/openai/utils.js
@@ -1,5 +1,6 @@
'use strict'
+const { UNKNOWN_MODEL_PROVIDER } = require('../../constants/tags')
const { audioMimeTypeFromFormat, formatAudioPart } = require('../../util')
const {
INPUT_TYPE_IMAGE,
@@ -160,10 +161,29 @@ function extractContentParts (parts) {
return { content: extracted.join('\n'), audioParts }
}
+/**
+ * Maps an OpenAI-compatible base URL to a model provider string. Covers
+ * OpenAI, Azure OpenAI, and DeepSeek; falls back to UNKNOWN_MODEL_PROVIDER
+ * for unrecognised hosts (e.g. local proxies or custom deployments).
+ *
+ * Shared with the openai-agents integration since both consume the same
+ * client baseURL convention.
+ *
+ * @param {string} baseUrl
+ * @returns {string}
+ */
+function getOpenAIModelProvider (baseUrl = '') {
+ if (baseUrl.includes('azure')) return 'azure_openai'
+ if (baseUrl.includes('deepseek')) return 'deepseek'
+ if (baseUrl.includes('openai')) return 'openai'
+ return UNKNOWN_MODEL_PROVIDER
+}
+
module.exports = {
extractChatTemplateFromInstructions,
normalizePromptVariables,
extractTextFromContentItem,
extractContentParts,
hasMultimodalInputs,
+ getOpenAIModelProvider,
}
diff --git a/packages/dd-trace/src/llmobs/sdk.js b/packages/dd-trace/src/llmobs/sdk.js
index f7869545f5..a93bcaf05a 100644
--- a/packages/dd-trace/src/llmobs/sdk.js
+++ b/packages/dd-trace/src/llmobs/sdk.js
@@ -11,6 +11,7 @@ const {
SPAN_KIND,
OUTPUT_VALUE,
INPUT_VALUE,
+ TRACE_ID,
} = require('./constants/tags')
const {
getFunctionArguments,
@@ -345,7 +346,7 @@ class LLMObs extends NoopLLMObs {
}
try {
return {
- traceId: span.context().toTraceId(true),
+ traceId: LLMObsTagger.tagMap.get(span)[TRACE_ID],
spanId: span.context().toSpanId(),
}
} catch {
diff --git a/packages/dd-trace/src/llmobs/span_processor.js b/packages/dd-trace/src/llmobs/span_processor.js
index a41a629928..d22eef4a86 100644
--- a/packages/dd-trace/src/llmobs/span_processor.js
+++ b/packages/dd-trace/src/llmobs/span_processor.js
@@ -35,6 +35,7 @@ const {
LLMOBS_SUBMITTED_TAG_KEY,
SAMPLE_RATE,
SAMPLING_DECISION,
+ TRACE_ID,
} = require('./constants/tags')
const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
const telemetry = require('./telemetry')
@@ -236,8 +237,11 @@ class LLMObsSpanProcessor {
meta.input.prompt = prompt
}
+ const apmTraceId = span.context().toTraceId(true)
+ const llmobsTraceId = mlObsTags[TRACE_ID] ?? apmTraceId
+
const llmObsSpanEvent = {
- trace_id: span.context().toTraceId(true),
+ trace_id: llmobsTraceId,
span_id: span.context().toSpanId(),
parent_id: parentId,
name,
@@ -249,9 +253,10 @@ class LLMObsSpanProcessor {
metrics,
_dd: {
span_id: span.context().toSpanId(),
- trace_id: span.context().toTraceId(true),
+ trace_id: apmTraceId,
sample_rate: mlObsTags[SAMPLE_RATE],
sampling_decision: mlObsTags[SAMPLING_DECISION],
+ apm_trace_id: apmTraceId,
},
}
diff --git a/packages/dd-trace/src/llmobs/tagger.js b/packages/dd-trace/src/llmobs/tagger.js
index 6bdc55c9cd..a1ec7a1bb7 100644
--- a/packages/dd-trace/src/llmobs/tagger.js
+++ b/packages/dd-trace/src/llmobs/tagger.js
@@ -51,9 +51,18 @@ const {
SAMPLING_DECISION_DROPPED,
PROPAGATED_SAMPLE_RATE_KEY,
PROPAGATED_SAMPLING_DECISION_KEY,
+ TRACE_ID,
+ PROPAGATED_TRACE_ID_KEY,
} = require('./constants/tags')
const { storage } = require('./storage')
-const { findGenAIAncestorSpanId, validateCostTags, writeBridgeTags, validateToolDefinitions } = require('./util')
+const {
+ findGenAIAncestorSpanId,
+ validateCostTags,
+ writeBridgeTags,
+ validateToolDefinitions,
+ generateLlmObsTraceId,
+ normalizeLlmObsTraceId,
+} = require('./util')
// global registry of LLMObs spans
// maps LLMObs spans to their annotations
@@ -126,12 +135,20 @@ class LLMObsTagger {
this._register(span)
+ const traceTags = span.context()._trace.tags
+
+ const llmobsTraceId =
+ registry.get(parent)?.[TRACE_ID] ??
+ normalizeLlmObsTraceId(traceTags[PROPAGATED_TRACE_ID_KEY]) ??
+ generateLlmObsTraceId(span._startTime)
+ this._setTag(span, TRACE_ID, llmobsTraceId)
+
// When the registering span sits below an OTel `gen_ai.*` ancestor, use
// that ancestor as the parent_id fallback and suppress the bridge
// parent_id tag so the indexer doesn't invert the trace.
const genAIAncestorSpanId = findGenAIAncestorSpanId(span)
- writeBridgeTags(span, { includeParentId: genAIAncestorSpanId === null })
+ writeBridgeTags(span, { includeParentId: genAIAncestorSpanId === null, llmobsTraceId })
this._setTag(span, ML_APP, spanMlApp)
@@ -141,7 +158,6 @@ class LLMObsTagger {
if (modelName) this.tagModelName(span, modelName)
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
- const traceTags = span.context()._trace.tags
sessionId = sessionId ||
registry.get(parent)?.[SESSION_ID] ||
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] ||
@@ -500,6 +516,10 @@ class LLMObsTagger {
this._setTag(span, MODEL_NAME, modelName)
}
+ setName (span, name) {
+ this._setTag(span, NAME, name)
+ }
+
#tagText (span, data, key) {
if (data) {
if (typeof data === 'string') {
diff --git a/packages/dd-trace/src/llmobs/util.js b/packages/dd-trace/src/llmobs/util.js
index 2cdf80d26f..a1e399575e 100644
--- a/packages/dd-trace/src/llmobs/util.js
+++ b/packages/dd-trace/src/llmobs/util.js
@@ -1,5 +1,6 @@
'use strict'
+const id = require('../id')
const log = require('../log')
const {
LLMOBS_PARENT_ID_BRIDGE_KEY,
@@ -7,6 +8,10 @@ const {
SPAN_KINDS,
} = require('./constants/tags')
+const DECIMAL_TRACE_ID_REGEX = /^\d+$/
+const HEX_TRACE_ID_REGEX = /^[0-9a-f]{32}$/i
+const MAX_UINT_64 = (1n << 64n) - 1n
+
// LLM I/O is overwhelmingly ASCII (English prompts and code). Walk once
// looking for the first non-ASCII char; if there is none, hand the input
// straight back. Otherwise pick up the slow path from the byte that needed
@@ -312,12 +317,12 @@ function safeJsonParse (value, fallback) {
// LLMObs root and hoists the gen_ai ancestors under it, inverting the trace.
/**
* @param {import('../opentracing/span')} span
- * @param {{ includeParentId?: boolean }} [opts]
+ * @param {{ includeParentId?: boolean, llmobsTraceId?: string }} [opts]
*/
-function writeBridgeTags (span, { includeParentId = true } = {}) {
+function writeBridgeTags (span, { includeParentId = true, llmobsTraceId } = {}) {
const traceTags = span?.context?.()._trace?.tags
if (!traceTags || traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY]) return
- traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = span.context().toTraceId(true)
+ traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = llmobsTraceId ?? span.context().toTraceId(true)
if (includeParentId) {
traceTags[LLMOBS_PARENT_ID_BRIDGE_KEY] = span.context().toSpanId()
}
@@ -362,6 +367,51 @@ function findGenAIAncestorSpanId (span) {
return null
}
+/**
+ * Generate a 128-bit LLMObs trace ID with the span start time encoded in its high bits.
+ * @param {number} startTime
+ * @returns {string}
+ */
+function generateLlmObsTraceId (startTime) {
+ const identifier = id()
+ const traceIdHigh = Math.floor(startTime / 1000)
+ .toString(16)
+ .padStart(8, '0')
+ .padEnd(16, '0')
+
+ return identifier.toTraceIdHex(traceIdHigh).padStart(32, '0')
+}
+
+/**
+ * Convert an internally stored hexadecimal LLMObs trace ID to its distributed wire representation.
+ * @param {string | undefined} traceId
+ * @returns {string | undefined}
+ */
+function llmObsTraceIdToWire (traceId) {
+ if (!traceId) return
+ if (!HEX_TRACE_ID_REGEX.test(traceId)) return traceId
+
+ return BigInt(`0x${traceId}`).toString(10)
+}
+
+/**
+ * Normalize a distributed LLMObs trace ID to the representation expected by LLMObs span events.
+ * @param {string | undefined} traceId
+ * @returns {string | undefined}
+ */
+function normalizeLlmObsTraceId (traceId) {
+ if (!traceId) return
+
+ if (HEX_TRACE_ID_REGEX.test(traceId) && (traceId[0] === '0' || !DECIMAL_TRACE_ID_REGEX.test(traceId))) {
+ return traceId
+ }
+
+ if (!DECIMAL_TRACE_ID_REGEX.test(traceId)) return traceId
+
+ const identifier = BigInt(traceId)
+ return identifier > MAX_UINT_64 ? identifier.toString(16).padStart(32, '0') : traceId
+}
+
// Maps an audio `format` (e.g. "wav", "mp3") to a MIME type. Defaults to `audio/wav` when the
// format is missing. Provider-specific overrides (e.g. OpenAI's mp3 -> audio/mpeg) are passed in
// via `mimeTypeLookup` so this stays provider-agnostic. A non-string `format` is treated as missing
@@ -396,6 +446,9 @@ module.exports = {
audioMimeTypeFromFormat,
encodeUnicode,
findGenAIAncestorSpanId,
+ generateLlmObsTraceId,
+ llmObsTraceIdToWire,
+ normalizeLlmObsTraceId,
formatAudioPart,
validateCostTags,
validateKind,
diff --git a/packages/dd-trace/src/openfeature/agentless_configuration_source.js b/packages/dd-trace/src/openfeature/agentless_configuration_source.js
index fc50bf1711..fd86a16acc 100644
--- a/packages/dd-trace/src/openfeature/agentless_configuration_source.js
+++ b/packages/dd-trace/src/openfeature/agentless_configuration_source.js
@@ -20,7 +20,7 @@ const RETRY_JITTER = 0.2
* @property {URL} endpoint
* @property {number} pollIntervalMs
* @property {number} requestTimeoutMs
- * @property {string} apiKey
+ * @property {string | undefined} apiKey
*/
/**
@@ -138,7 +138,7 @@ class AgentlessConfigurationSource {
#request (signal) {
const headers = getClientLibraryHeaders()
headers['Accept-Encoding'] = 'gzip'
- headers['DD-API-KEY'] = this.#config.apiKey
+ if (this.#config.apiKey) headers['DD-API-KEY'] = this.#config.apiKey
if (this.#etag) headers['If-None-Match'] = this.#etag
/**
@@ -228,10 +228,7 @@ class AgentlessConfigurationSource {
this.#failureWarnings.add(category)
if (statusCode === 401 || statusCode === 403) {
- log.warn(
- 'Feature Flagging agentless endpoint returned HTTP %d; verify DD_API_KEY is configured and valid',
- statusCode
- )
+ log.warn('Feature Flagging agentless endpoint returned HTTP %d; verify endpoint authentication', statusCode)
} else if (statusCode) {
log.warn('Feature Flagging agentless endpoint returned HTTP %d after %d attempts', statusCode, attempts)
} else if (attempts > 1) {
diff --git a/packages/dd-trace/src/openfeature/configuration_source.js b/packages/dd-trace/src/openfeature/configuration_source.js
index 0bd7327953..4510cfdf79 100644
--- a/packages/dd-trace/src/openfeature/configuration_source.js
+++ b/packages/dd-trace/src/openfeature/configuration_source.js
@@ -1,6 +1,5 @@
'use strict'
-const { isLoopbackHost } = require('../exporters/common/url')
const log = require('../log')
const DEFAULT_AGENTLESS_PATH = '/api/v2/feature-flagging/config/rules-based/server'
@@ -27,9 +26,11 @@ function create (config, applyConfiguration) {
return
}
+ const hasCustomEndpoint = Boolean(baseUrl?.trim())
+
try {
- if (!config.DD_API_KEY) {
- throw new Error('DD_API_KEY is required for Feature Flagging agentless delivery')
+ if (!hasCustomEndpoint && !config.DD_API_KEY) {
+ throw new Error('DD_API_KEY is required for the default Datadog Feature Flagging endpoint')
}
const AgentlessConfigurationSource = require('./agentless_configuration_source')
@@ -37,7 +38,7 @@ function create (config, applyConfiguration) {
endpoint: endpoint(config, baseUrl),
pollIntervalMs: Math.min(pollIntervalSeconds, MAX_POLL_INTERVAL_SECONDS) * 1000,
requestTimeoutMs: requestTimeoutSeconds * 1000,
- apiKey: config.DD_API_KEY,
+ apiKey: hasCustomEndpoint ? undefined : config.DD_API_KEY,
}, applyConfiguration)
} catch (error) {
log.error('Unable to configure Feature Flagging configuration source', error)
@@ -74,10 +75,6 @@ function endpoint (config, configuredBaseUrl) {
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
throw new Error('Feature Flagging agentless URL must use HTTP or HTTPS')
}
- if (url.protocol === 'http:' && !isLoopbackHost(url.hostname)) {
- throw new Error('Feature Flagging agentless URL must use HTTPS unless it targets loopback')
- }
-
if (url.pathname === '' || url.pathname === '/') {
url.pathname = DEFAULT_AGENTLESS_PATH
}
diff --git a/packages/dd-trace/src/plugins/index.js b/packages/dd-trace/src/plugins/index.js
index b91919fcc4..591df8171d 100644
--- a/packages/dd-trace/src/plugins/index.js
+++ b/packages/dd-trace/src/plugins/index.js
@@ -37,6 +37,7 @@ const plugins = {
get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
get '@vitest/runner' () { return require('../../../datadog-plugin-vitest/src') },
get '@langchain/langgraph' () { return require('../../../datadog-plugin-langgraph/src') },
+ get '@openai/agents' () { return require('../../../datadog-plugin-openai-agents/src') },
get aerospike () { return require('../../../datadog-plugin-aerospike/src') },
get ai () { return require('../../../datadog-plugin-ai/src') },
get amqp10 () { return require('../../../datadog-plugin-amqp10/src') },
diff --git a/packages/dd-trace/src/plugins/outbound.js b/packages/dd-trace/src/plugins/outbound.js
index 05ae4bcae3..635a07cd51 100644
--- a/packages/dd-trace/src/plugins/outbound.js
+++ b/packages/dd-trace/src/plugins/outbound.js
@@ -125,7 +125,10 @@ class OutboundPlugin extends TracingPlugin {
*/
tagPeerService (span) {
if (this._tracerConfig.spanComputePeerService) {
- const peerData = this.getPeerService(span.context().getTags())
+ const tags = span.context().getTags()
+ if (tags[PEER_SERVICE_SOURCE_KEY] !== undefined) return
+
+ const peerData = this.getPeerService(tags)
if (peerData !== undefined) {
span.addTags(this.getPeerServiceRemap(peerData))
}
diff --git a/packages/dd-trace/src/span_processor.js b/packages/dd-trace/src/span_processor.js
index b924220702..727631a108 100644
--- a/packages/dd-trace/src/span_processor.js
+++ b/packages/dd-trace/src/span_processor.js
@@ -62,7 +62,7 @@ class SpanProcessor {
active.push(span)
} else {
const formattedSpan = spanFormat(span, isFirstSpanInChunk, this._processTags)
- if (isFirstSpanInChunk && stampApmDisabled) {
+ if (stampApmDisabled) {
formattedSpan.metrics[APM_TRACING_ENABLED_KEY] = 0
}
isFirstSpanInChunk = false
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_1e503f45.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_1e503f45.yaml
new file mode 100644
index 0000000000..6a76342ba2
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_1e503f45.yaml
@@ -0,0 +1,141 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4o-mini","instructions":"You are a calculator. Use the `add`
+ tool to answer math questions.","input":[{"role":"user","content":"What is the
+ sum of 1 and 2?"},{"id":"fc_0e5478180e8a08c50169f3fd16bb14819289e3160e1b1402a3","type":"function_call","name":"add","call_id":"call_CEwtyjHD7Mwsy0csHUiMkMQv","arguments":"{\"a\":1,\"b\":2}","status":"completed"},{"type":"function_call_output","call_id":"call_CEwtyjHD7Mwsy0csHUiMkMQv","output":"An
+ error occurred while running the tool. Please try again. Error: Error: Intentional
+ error for testing","status":"completed"}],"include":[],"tools":[{"type":"function","name":"add","description":"Adds
+ two numbers and returns the result.","parameters":{"type":"object","properties":{"a":{"type":"number","description":"First
+ number"},"b":{"type":"number","description":"Second number"}},"required":["a","b"],"additionalProperties":false},"strict":true}],"stream":false}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '918'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.35.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: "{\n \"id\": \"resp_0e5478180e8a08c50169f3fd17ad78819292298277c9d1a36c\",\n
+ \ \"object\": \"response\",\n \"created_at\": 1777597719,\n \"status\":
+ \"completed\",\n \"background\": false,\n \"billing\": {\n \"payer\":
+ \"developer\"\n },\n \"completed_at\": 1777597720,\n \"error\": null,\n
+ \ \"frequency_penalty\": 0.0,\n \"incomplete_details\": null,\n \"instructions\":
+ \"You are a calculator. Use the `add` tool to answer math questions.\",\n
+ \ \"max_output_tokens\": null,\n \"max_tool_calls\": null,\n \"model\":
+ \"gpt-4o-mini-2024-07-18\",\n \"moderation\": null,\n \"output\": [\n {\n
+ \ \"id\": \"fc_0e5478180e8a08c50169f3fd18cadc8192894e035a52eb5dc1\",\n
+ \ \"type\": \"function_call\",\n \"status\": \"completed\",\n \"arguments\":
+ \"{\\\"a\\\":1,\\\"b\\\":2}\",\n \"call_id\": \"call_Afc0VH4AMnChHMC1JJyv65rH\",\n
+ \ \"name\": \"add\"\n }\n ],\n \"parallel_tool_calls\": true,\n \"presence_penalty\":
+ 0.0,\n \"previous_response_id\": null,\n \"prompt_cache_key\": null,\n \"prompt_cache_retention\":
+ \"in_memory\",\n \"reasoning\": {\n \"effort\": null,\n \"summary\":
+ null\n },\n \"safety_identifier\": null,\n \"service_tier\": \"default\",\n
+ \ \"store\": false,\n \"temperature\": 1.0,\n \"text\": {\n \"format\":
+ {\n \"type\": \"text\"\n },\n \"verbosity\": \"medium\"\n },\n
+ \ \"tool_choice\": \"auto\",\n \"tools\": [\n {\n \"type\": \"function\",\n
+ \ \"description\": \"Adds two numbers and returns the result.\",\n \"name\":
+ \"add\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\":
+ {\n \"a\": {\n \"type\": \"number\",\n \"description\":
+ \"First number\"\n },\n \"b\": {\n \"type\":
+ \"number\",\n \"description\": \"Second number\"\n }\n
+ \ },\n \"required\": [\n \"a\",\n \"b\"\n ],\n
+ \ \"additionalProperties\": false\n },\n \"strict\": true\n
+ \ }\n ],\n \"top_logprobs\": 0,\n \"top_p\": 1.0,\n \"truncation\":
+ \"disabled\",\n \"usage\": {\n \"input_tokens\": 123,\n \"input_tokens_details\":
+ {\n \"cached_tokens\": 0\n },\n \"output_tokens\": 18,\n \"output_tokens_details\":
+ {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 141\n },\n
+ \ \"user\": null,\n \"metadata\": {}\n}"
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-Ray:
+ - 9f4ae5703ed68153-EWR
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 01 May 2026 01:08:41 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '1313'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=3qywEquAgFctMZKPbXBF3t159B0cHO0NF4f44SCggG8-1777597719.0729408-1.0.1.1-nUgXWX7rRQySHN8fZLgAS5ijYxXyFCCWS6AyyTcqYuwdqGBgTkHEDoIiwSXmqv4TMJaSSBHln_DSEYfFiDvcq08aN3O3i2n3VojZj7brVdhZD81olz6i94BhZBW8UbP_;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 01 May 2026
+ 01:38:41 GMT
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999660'
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_bc2f4349a43846e29510af2993044c5c
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_25ba1894.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_25ba1894.yaml
new file mode 100644
index 0000000000..e6c67fee82
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_25ba1894.yaml
@@ -0,0 +1,186 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4","instructions":"test","input":[{"role":"user","content":"hello"}],"include":[],"tools":[],"stream":true,"text":{}}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '131'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.32.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: 'event: response.created
+
+ data: {"type":"response.created","response":{"id":"resp_04364b9e39a803600169c40e9dea74819cab03f85e6a862127","object":"response","created_at":1774456477,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":"test","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4-0613","output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0}
+
+
+ event: response.in_progress
+
+ data: {"type":"response.in_progress","response":{"id":"resp_04364b9e39a803600169c40e9dea74819cab03f85e6a862127","object":"response","created_at":1774456477,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":"test","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4-0613","output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1}
+
+
+ event: response.output_item.added
+
+ data: {"type":"response.output_item.added","item":{"id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2}
+
+
+ event: response.content_part.added
+
+ data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":"Hi","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"921dZ2mEE6LZDA","output_index":0,"sequence_number":4}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" there","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"7QMX9nwF2j","output_index":0,"sequence_number":5}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":"!","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"IxnFnRnjHJpz7Qs","output_index":0,"sequence_number":6}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" How","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"HvCNSRjhvKOU","output_index":0,"sequence_number":7}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" can","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"jNvIDOTIqokT","output_index":0,"sequence_number":8}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" I","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"zb8n4Pp9a8iWtd","output_index":0,"sequence_number":9}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" assist","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"HRfpn6X9W","output_index":0,"sequence_number":10}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" you","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"ldsN4a6x1CGU","output_index":0,"sequence_number":11}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" today","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"TGXL09FzwC","output_index":0,"sequence_number":12}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":"?","item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"obfuscation":"81zFFzsVHJw0aj6","output_index":0,"sequence_number":13}
+
+
+ event: response.output_text.done
+
+ data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","logprobs":[],"output_index":0,"sequence_number":14,"text":"Hi
+ there! How can I assist you today?"}
+
+
+ event: response.content_part.done
+
+ data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"Hi
+ there! How can I assist you today?"},"sequence_number":15}
+
+
+ event: response.output_item.done
+
+ data: {"type":"response.output_item.done","item":{"id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"Hi
+ there! How can I assist you today?"}],"role":"assistant"},"output_index":0,"sequence_number":16}
+
+
+ event: response.completed
+
+ data: {"type":"response.completed","response":{"id":"resp_04364b9e39a803600169c40e9dea74819cab03f85e6a862127","object":"response","created_at":1774456477,"status":"completed","background":false,"completed_at":1774456479,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":"test","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4-0613","output":[{"id":"msg_04364b9e39a803600169c40e9ec1f8819cb92f585e552e937b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"Hi
+ there! How can I assist you today?"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":12,"input_tokens_details":{"cached_tokens":0},"output_tokens":12,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":24},"user":null,"metadata":{}},"sequence_number":17}
+
+
+ '
+ headers:
+ CF-RAY:
+ - 9e1f52f8eba982e6-IAD
+ Connection:
+ - keep-alive
+ Content-Type:
+ - text/event-stream; charset=utf-8
+ Date:
+ - Wed, 25 Mar 2026 16:34:38 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ cf-cache-status:
+ - DYNAMIC
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '131'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=QE4xJnx_UuWjrZsfnzrnDkVoPNG8JSQeq11BEXIH6ko-1774456477.5905411-1.0.1.1-FDD0G9GLnPVbb0pZaO9tsmFSRhHRnIFbpbxA9MU7frPUuWGVu9.9JWBT1flHGaPFOfLASf3Ew0hB9lJvBgVjx5SeSWjD3VEOIMefLLBM7O3CYK58zRk._gWPP67EtY_7;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 25 Mar 2026
+ 17:04:38 GMT
+ x-request-id:
+ - req_133cb0502a414fe4b340eab3d2380cf0
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_5004ba66.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_5004ba66.yaml
new file mode 100644
index 0000000000..fd03029832
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_5004ba66.yaml
@@ -0,0 +1,139 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4o-mini","instructions":"You are a calculator. Use the `add`
+ tool to answer math questions.","input":[{"role":"user","content":"What is the
+ sum of 1 and 2?"}],"include":[],"tools":[{"type":"function","name":"add","description":"Adds
+ two numbers and returns the result.","parameters":{"type":"object","properties":{"a":{"type":"number","description":"First
+ number"},"b":{"type":"number","description":"Second number"}},"required":["a","b"],"additionalProperties":false},"strict":true}],"stream":false}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '514'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.35.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: "{\n \"id\": \"resp_0e5478180e8a08c50169f3fd12eab08192b02045d3c423e5c5\",\n
+ \ \"object\": \"response\",\n \"created_at\": 1777597714,\n \"status\":
+ \"completed\",\n \"background\": false,\n \"billing\": {\n \"payer\":
+ \"developer\"\n },\n \"completed_at\": 1777597718,\n \"error\": null,\n
+ \ \"frequency_penalty\": 0.0,\n \"incomplete_details\": null,\n \"instructions\":
+ \"You are a calculator. Use the `add` tool to answer math questions.\",\n
+ \ \"max_output_tokens\": null,\n \"max_tool_calls\": null,\n \"model\":
+ \"gpt-4o-mini-2024-07-18\",\n \"moderation\": null,\n \"output\": [\n {\n
+ \ \"id\": \"fc_0e5478180e8a08c50169f3fd16bb14819289e3160e1b1402a3\",\n
+ \ \"type\": \"function_call\",\n \"status\": \"completed\",\n \"arguments\":
+ \"{\\\"a\\\":1,\\\"b\\\":2}\",\n \"call_id\": \"call_CEwtyjHD7Mwsy0csHUiMkMQv\",\n
+ \ \"name\": \"add\"\n }\n ],\n \"parallel_tool_calls\": true,\n \"presence_penalty\":
+ 0.0,\n \"previous_response_id\": null,\n \"prompt_cache_key\": null,\n \"prompt_cache_retention\":
+ \"in_memory\",\n \"reasoning\": {\n \"effort\": null,\n \"summary\":
+ null\n },\n \"safety_identifier\": null,\n \"service_tier\": \"default\",\n
+ \ \"store\": false,\n \"temperature\": 1.0,\n \"text\": {\n \"format\":
+ {\n \"type\": \"text\"\n },\n \"verbosity\": \"medium\"\n },\n
+ \ \"tool_choice\": \"auto\",\n \"tools\": [\n {\n \"type\": \"function\",\n
+ \ \"description\": \"Adds two numbers and returns the result.\",\n \"name\":
+ \"add\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\":
+ {\n \"a\": {\n \"type\": \"number\",\n \"description\":
+ \"First number\"\n },\n \"b\": {\n \"type\":
+ \"number\",\n \"description\": \"Second number\"\n }\n
+ \ },\n \"required\": [\n \"a\",\n \"b\"\n ],\n
+ \ \"additionalProperties\": false\n },\n \"strict\": true\n
+ \ }\n ],\n \"top_logprobs\": 0,\n \"top_p\": 1.0,\n \"truncation\":
+ \"disabled\",\n \"usage\": {\n \"input_tokens\": 80,\n \"input_tokens_details\":
+ {\n \"cached_tokens\": 0\n },\n \"output_tokens\": 18,\n \"output_tokens_details\":
+ {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 98\n },\n
+ \ \"user\": null,\n \"metadata\": {}\n}"
+ headers:
+ CF-Cache-Status:
+ - DYNAMIC
+ CF-Ray:
+ - 9f4ae5527a1d25d8-EWR
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Fri, 01 May 2026 01:08:39 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '3988'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=eKYElRGqGQuaIGgnStQcJEw31vLMZn4PZad3KU7oBFQ-1777597714.318872-1.0.1.1-OC7_nxkB71fXXwdAgSJ7du1KAnZgOkZlt_dr6gr2KKHsJuQ6COdufSB6Lqo7EdJcliLD7EcQ1MdovcZA2bCvGouJVIQmwCwURA_OJD5an3vkDwLJ8t5HCsUgi3becFOz;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Fri, 01 May 2026
+ 01:38:39 GMT
+ x-ratelimit-limit-requests:
+ - '30000'
+ x-ratelimit-limit-tokens:
+ - '150000000'
+ x-ratelimit-remaining-requests:
+ - '29999'
+ x-ratelimit-remaining-tokens:
+ - '149999702'
+ x-ratelimit-reset-requests:
+ - 2ms
+ x-ratelimit-reset-tokens:
+ - 0s
+ x-request-id:
+ - req_8924f1d298ae4a628f5879914814ef61
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_56b13001.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_56b13001.yaml
new file mode 100644
index 0000000000..0839016819
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_56b13001.yaml
@@ -0,0 +1,130 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4","instructions":"You are helpful","input":[{"role":"user","content":[{"type":"input_text","text":"Tell
+ me a joke"}]}],"include":[],"tools":[],"temperature":0.5,"max_output_tokens":100,"stream":false,"text":{}}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '225'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.32.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: "{\n \"id\": \"resp_0cc2e93f0acc417d0169c44b4b1c9c819f8b0f9f6f38cf423b\",\n
+ \ \"object\": \"response\",\n \"created_at\": 1774472011,\n \"status\":
+ \"completed\",\n \"background\": false,\n \"billing\": {\n \"payer\":
+ \"developer\"\n },\n \"completed_at\": 1774472012,\n \"error\": null,\n
+ \ \"frequency_penalty\": 0.0,\n \"incomplete_details\": null,\n \"instructions\":
+ \"You are helpful\",\n \"max_output_tokens\": 100,\n \"max_tool_calls\":
+ null,\n \"model\": \"gpt-4-0613\",\n \"output\": [\n {\n \"id\":
+ \"msg_0cc2e93f0acc417d0169c44b4be34c819fb36771e8ff347e66\",\n \"type\":
+ \"message\",\n \"status\": \"completed\",\n \"content\": [\n {\n
+ \ \"type\": \"output_text\",\n \"annotations\": [],\n \"logprobs\":
+ [],\n \"text\": \"Sure, here's a classic for you:\\n\\nWhy don't
+ scientists trust atoms?\\n\\nBecause they make up everything!\"\n }\n
+ \ ],\n \"role\": \"assistant\"\n }\n ],\n \"parallel_tool_calls\":
+ true,\n \"presence_penalty\": 0.0,\n \"previous_response_id\": null,\n \"prompt_cache_key\":
+ null,\n \"prompt_cache_retention\": null,\n \"reasoning\": {\n \"effort\":
+ null,\n \"summary\": null\n },\n \"safety_identifier\": null,\n \"service_tier\":
+ \"default\",\n \"store\": false,\n \"temperature\": 0.5,\n \"text\": {\n
+ \ \"format\": {\n \"type\": \"text\"\n },\n \"verbosity\": \"medium\"\n
+ \ },\n \"tool_choice\": \"auto\",\n \"tools\": [],\n \"top_logprobs\":
+ 0,\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n \"usage\": {\n \"input_tokens\":
+ 17,\n \"input_tokens_details\": {\n \"cached_tokens\": 0\n },\n
+ \ \"output_tokens\": 24,\n \"output_tokens_details\": {\n \"reasoning_tokens\":
+ 0\n },\n \"total_tokens\": 41\n },\n \"user\": null,\n \"metadata\":
+ {}\n}"
+ headers:
+ CF-RAY:
+ - 9e20ce339b321c3f-EWR
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 25 Mar 2026 20:53:32 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ cf-cache-status:
+ - DYNAMIC
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '1362'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=l3.ko7zdIzqrA9_kI24BWLarfeizSkQfqjwWzwkqjTY-1774472010.8202507-1.0.1.1-aeaQcc60yFR4V6alqgThW7sJzgIvRr2GSei3Q4bMFRIiHi4A8KCWsUX9HlbkWWnJmcrJbS7hgGBnFCnfxYgpYp3mMwZe1ErzJvvM9sFOLgvVsa7aI_ftpJPt79WCSIdy;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 25 Mar 2026
+ 21:23:32 GMT
+ x-ratelimit-limit-requests:
+ - '1000'
+ x-ratelimit-limit-tokens:
+ - '600000'
+ x-ratelimit-remaining-requests:
+ - '999'
+ x-ratelimit-remaining-tokens:
+ - '599978'
+ x-ratelimit-reset-requests:
+ - 60ms
+ x-ratelimit-reset-tokens:
+ - 2ms
+ x-request-id:
+ - req_53f8b65db0cb4c68ae6ea6b53de4cc95
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_7bac988c.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_7bac988c.yaml
new file mode 100644
index 0000000000..5410e8d0f0
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_7bac988c.yaml
@@ -0,0 +1,127 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4","input":[{"role":"user","content":"What is 2 + 2?"}],"include":[],"tools":[],"stream":false,"text":{}}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '119'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.32.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: "{\n \"id\": \"resp_0227bf7009346efa0169c44b49bfe881978675058684f5120e\",\n
+ \ \"object\": \"response\",\n \"created_at\": 1774472009,\n \"status\":
+ \"completed\",\n \"background\": false,\n \"billing\": {\n \"payer\":
+ \"developer\"\n },\n \"completed_at\": 1774472010,\n \"error\": null,\n
+ \ \"frequency_penalty\": 0.0,\n \"incomplete_details\": null,\n \"instructions\":
+ null,\n \"max_output_tokens\": null,\n \"max_tool_calls\": null,\n \"model\":
+ \"gpt-4-0613\",\n \"output\": [\n {\n \"id\": \"msg_0227bf7009346efa0169c44b4a7a7c8197accfbc837de21952\",\n
+ \ \"type\": \"message\",\n \"status\": \"completed\",\n \"content\":
+ [\n {\n \"type\": \"output_text\",\n \"annotations\":
+ [],\n \"logprobs\": [],\n \"text\": \"2 + 2 equals 4.\"\n
+ \ }\n ],\n \"role\": \"assistant\"\n }\n ],\n \"parallel_tool_calls\":
+ true,\n \"presence_penalty\": 0.0,\n \"previous_response_id\": null,\n \"prompt_cache_key\":
+ null,\n \"prompt_cache_retention\": null,\n \"reasoning\": {\n \"effort\":
+ null,\n \"summary\": null\n },\n \"safety_identifier\": null,\n \"service_tier\":
+ \"default\",\n \"store\": false,\n \"temperature\": 1.0,\n \"text\": {\n
+ \ \"format\": {\n \"type\": \"text\"\n },\n \"verbosity\": \"medium\"\n
+ \ },\n \"tool_choice\": \"auto\",\n \"tools\": [],\n \"top_logprobs\":
+ 0,\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n \"usage\": {\n \"input_tokens\":
+ 14,\n \"input_tokens_details\": {\n \"cached_tokens\": 0\n },\n
+ \ \"output_tokens\": 10,\n \"output_tokens_details\": {\n \"reasoning_tokens\":
+ 0\n },\n \"total_tokens\": 24\n },\n \"user\": null,\n \"metadata\":
+ {}\n}"
+ headers:
+ CF-RAY:
+ - 9e20ce2adcc15e4b-EWR
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 25 Mar 2026 20:53:30 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ cf-cache-status:
+ - DYNAMIC
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '901'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=GJqc1QysrbFDjJV7M1HquOcXVnmZz5QLIfqMqcT.2Tw-1774472009.42027-1.0.1.1-bmP6nuJyYMTc20ws9loL9I0e8.Kt2E8RUQ_S9FB3YS1lm.0qNP53HR0clNRWJeW.oeyvfrdLm4EpRrSzo6kpht9pg3c0u0Ezpt7v4yePWzbWyH6YUsZw6fMC.hzLsWNn;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 25 Mar 2026
+ 21:23:30 GMT
+ x-ratelimit-limit-requests:
+ - '1000'
+ x-ratelimit-limit-tokens:
+ - '600000'
+ x-ratelimit-remaining-requests:
+ - '999'
+ x-ratelimit-remaining-tokens:
+ - '599981'
+ x-ratelimit-reset-requests:
+ - 60ms
+ x-ratelimit-reset-tokens:
+ - 1ms
+ x-request-id:
+ - req_cad034c549554445be66626c5d40cc18
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_9ff3470b.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_9ff3470b.yaml
new file mode 100644
index 0000000000..f29c968e62
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_9ff3470b.yaml
@@ -0,0 +1,284 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4","instructions":"You are helpful","input":[{"role":"user","content":"Stream
+ this"}],"include":[],"tools":[],"temperature":0.3,"stream":true,"text":{}}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '166'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.32.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: 'event: response.created
+
+ data: {"type":"response.created","response":{"id":"resp_011293af9477a3470169c44b4cbe6081959108ab2a2613e20e","object":"response","created_at":1774472012,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":"You
+ are helpful","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4-0613","output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":0.3,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0}
+
+
+ event: response.in_progress
+
+ data: {"type":"response.in_progress","response":{"id":"resp_011293af9477a3470169c44b4cbe6081959108ab2a2613e20e","object":"response","created_at":1774472012,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":"You
+ are helpful","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4-0613","output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":0.3,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1}
+
+
+ event: response.output_item.added
+
+ data: {"type":"response.output_item.added","item":{"id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2}
+
+
+ event: response.content_part.added
+
+ data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":"As","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"hlXBzvxREVSvYv","output_index":0,"sequence_number":4}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" an","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"MQuL1ybPMlt7c","output_index":0,"sequence_number":5}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" AI","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"4VRvQEFyKcsme","output_index":0,"sequence_number":6}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"YeEBrzcwweQKUyj","output_index":0,"sequence_number":7}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" I","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"sXevK5nZhj7eFR","output_index":0,"sequence_number":8}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":"''m","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"n8bbBBE6vB2u8C","output_index":0,"sequence_number":9}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" unable","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"3aF2HZOkD","output_index":0,"sequence_number":10}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"JI7ZGROCIWtWO","output_index":0,"sequence_number":11}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" stream","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"S7GsT4L9Z","output_index":0,"sequence_number":12}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" content","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"lWJlUROL","output_index":0,"sequence_number":13}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"VgksSOpuuRh7vNK","output_index":0,"sequence_number":14}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" However","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"qmlMvQLv","output_index":0,"sequence_number":15}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"IvzpAvtp5Ts2dDK","output_index":0,"sequence_number":16}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" I","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"WzawlAD7ddnbJk","output_index":0,"sequence_number":17}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":"''m","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"BPJuf3Tv1AVwhW","output_index":0,"sequence_number":18}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" here","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"Za5jJP8omja","output_index":0,"sequence_number":19}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"rMRgBWrNoMAGz","output_index":0,"sequence_number":20}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" assist","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"qMJsbItOz","output_index":0,"sequence_number":21}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" you","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"oA7fuSJzKkhy","output_index":0,"sequence_number":22}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" with","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"SN7uVIz92EF","output_index":0,"sequence_number":23}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" any","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"ldl0fIWk0vqj","output_index":0,"sequence_number":24}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" questions","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"KGU4U1","output_index":0,"sequence_number":25}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" or","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"ZeKvwlHfbFCx8","output_index":0,"sequence_number":26}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" tasks","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"aGXYPegVfm","output_index":0,"sequence_number":27}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" you","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"AlqUQSG7Aemd","output_index":0,"sequence_number":28}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" may","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"QzJRcpNsPshM","output_index":0,"sequence_number":29}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":" have","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"P9xqSYG214k","output_index":0,"sequence_number":30}
+
+
+ event: response.output_text.delta
+
+ data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"obfuscation":"O5zuNAAnNaSdIOd","output_index":0,"sequence_number":31}
+
+
+ event: response.output_text.done
+
+ data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","logprobs":[],"output_index":0,"sequence_number":32,"text":"As
+ an AI, I''m unable to stream content. However, I''m here to assist you with
+ any questions or tasks you may have."}
+
+
+ event: response.content_part.done
+
+ data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"As
+ an AI, I''m unable to stream content. However, I''m here to assist you with
+ any questions or tasks you may have."},"sequence_number":33}
+
+
+ event: response.output_item.done
+
+ data: {"type":"response.output_item.done","item":{"id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"As
+ an AI, I''m unable to stream content. However, I''m here to assist you with
+ any questions or tasks you may have."}],"role":"assistant"},"output_index":0,"sequence_number":34}
+
+
+ event: response.completed
+
+ data: {"type":"response.completed","response":{"id":"resp_011293af9477a3470169c44b4cbe6081959108ab2a2613e20e","object":"response","created_at":1774472012,"status":"completed","background":false,"completed_at":1774472013,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":"You
+ are helpful","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4-0613","output":[{"id":"msg_011293af9477a3470169c44b4d45dc8195b67972956b2b3583","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"As
+ an AI, I''m unable to stream content. However, I''m here to assist you with
+ any questions or tasks you may have."}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":null,"reasoning":{"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":0.3,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":15,"input_tokens_details":{"cached_tokens":0},"output_tokens":30,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":45},"user":null,"metadata":{}},"sequence_number":35}
+
+
+ '
+ headers:
+ CF-RAY:
+ - 9e20ce3f5acb0c9c-EWR
+ Connection:
+ - keep-alive
+ Content-Type:
+ - text/event-stream; charset=utf-8
+ Date:
+ - Wed, 25 Mar 2026 20:53:32 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ cf-cache-status:
+ - DYNAMIC
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '68'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=AZvfdsnqzjzcad66svqkkltb4dd4q8Te2sRxTYPJ5rk-1774472012.6990771-1.0.1.1-A8TJ9VYbkJiD1X9ao7noPk.31qLwZdH9G4Flne8nbftzMfw38eHaQD8lEl0jddteORsLvGLkti.5I.YoCJdSBHjBSzA3ZffMmFeOaryI5FcYjX_AFSNgjkEE2_EipcEw;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 25 Mar 2026
+ 21:23:32 GMT
+ x-request-id:
+ - req_eabb26f8d15646ddb17c085b172a3109
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_af61e856.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_af61e856.yaml
new file mode 100644
index 0000000000..150b05796d
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_af61e856.yaml
@@ -0,0 +1,127 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4","instructions":"test","input":[{"role":"user","content":"hello"}],"include":[],"tools":[],"stream":false,"text":{}}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '132'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.32.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: "{\n \"id\": \"resp_0a2893d9090f470e0169c40e98f744819e9e362f0925c20494\",\n
+ \ \"object\": \"response\",\n \"created_at\": 1774456472,\n \"status\":
+ \"completed\",\n \"background\": false,\n \"billing\": {\n \"payer\":
+ \"developer\"\n },\n \"completed_at\": 1774456474,\n \"error\": null,\n
+ \ \"frequency_penalty\": 0.0,\n \"incomplete_details\": null,\n \"instructions\":
+ \"test\",\n \"max_output_tokens\": null,\n \"max_tool_calls\": null,\n \"model\":
+ \"gpt-4-0613\",\n \"output\": [\n {\n \"id\": \"msg_0a2893d9090f470e0169c40e99df8c819e853abe8dd2018e97\",\n
+ \ \"type\": \"message\",\n \"status\": \"completed\",\n \"content\":
+ [\n {\n \"type\": \"output_text\",\n \"annotations\":
+ [],\n \"logprobs\": [],\n \"text\": \"Hello! How can I assist
+ you today?\"\n }\n ],\n \"role\": \"assistant\"\n }\n
+ \ ],\n \"parallel_tool_calls\": true,\n \"presence_penalty\": 0.0,\n \"previous_response_id\":
+ null,\n \"prompt_cache_key\": null,\n \"prompt_cache_retention\": null,\n
+ \ \"reasoning\": {\n \"effort\": null,\n \"summary\": null\n },\n \"safety_identifier\":
+ null,\n \"service_tier\": \"default\",\n \"store\": false,\n \"temperature\":
+ 1.0,\n \"text\": {\n \"format\": {\n \"type\": \"text\"\n },\n
+ \ \"verbosity\": \"medium\"\n },\n \"tool_choice\": \"auto\",\n \"tools\":
+ [],\n \"top_logprobs\": 0,\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n
+ \ \"usage\": {\n \"input_tokens\": 12,\n \"input_tokens_details\": {\n
+ \ \"cached_tokens\": 0\n },\n \"output_tokens\": 11,\n \"output_tokens_details\":
+ {\n \"reasoning_tokens\": 0\n },\n \"total_tokens\": 23\n },\n
+ \ \"user\": null,\n \"metadata\": {}\n}"
+ headers:
+ CF-RAY:
+ - 9e1f52d9fa372015-IAD
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 25 Mar 2026 16:34:34 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ cf-cache-status:
+ - DYNAMIC
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '1461'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=jbKJfvehx8H0GEEr.pXbENzMx_qVnc1.265VVOjJSQY-1774456472.6321006-1.0.1.1-gqyisrxOzd5I7ZonPoFnQ1E9Zl5f96sfrC017yMx_OWB9CuuYPMRjapXbvN7t8yz5E9Wn73QhQLQ9Q5sR1oLoi0LPZ7K1ERONUBYBpfOQmG2VWNVfZP.7TgsTejaqYVG;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 25 Mar 2026
+ 17:04:34 GMT
+ x-ratelimit-limit-requests:
+ - '1000'
+ x-ratelimit-limit-tokens:
+ - '600000'
+ x-ratelimit-remaining-requests:
+ - '999'
+ x-ratelimit-remaining-tokens:
+ - '599983'
+ x-ratelimit-reset-requests:
+ - 60ms
+ x-ratelimit-reset-tokens:
+ - 1ms
+ x-request-id:
+ - req_89fc716437ad4150a07d68e16851cfb5
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_c7d7528a.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_c7d7528a.yaml
new file mode 100644
index 0000000000..1188b57f43
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_c7d7528a.yaml
@@ -0,0 +1,128 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4","instructions":"You are a helpful assistant","input":[{"role":"user","content":"Hello"}],"include":[],"tools":[],"temperature":0.7,"stream":false,"text":{}}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '173'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.32.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: "{\n \"id\": \"resp_052e1e648197b69a0169c44b4864f481968cbb4090817deebe\",\n
+ \ \"object\": \"response\",\n \"created_at\": 1774472008,\n \"status\":
+ \"completed\",\n \"background\": false,\n \"billing\": {\n \"payer\":
+ \"developer\"\n },\n \"completed_at\": 1774472009,\n \"error\": null,\n
+ \ \"frequency_penalty\": 0.0,\n \"incomplete_details\": null,\n \"instructions\":
+ \"You are a helpful assistant\",\n \"max_output_tokens\": null,\n \"max_tool_calls\":
+ null,\n \"model\": \"gpt-4-0613\",\n \"output\": [\n {\n \"id\":
+ \"msg_052e1e648197b69a0169c44b490e58819683edf0147b11f20a\",\n \"type\":
+ \"message\",\n \"status\": \"completed\",\n \"content\": [\n {\n
+ \ \"type\": \"output_text\",\n \"annotations\": [],\n \"logprobs\":
+ [],\n \"text\": \"Hello! How can I assist you today?\"\n }\n
+ \ ],\n \"role\": \"assistant\"\n }\n ],\n \"parallel_tool_calls\":
+ true,\n \"presence_penalty\": 0.0,\n \"previous_response_id\": null,\n \"prompt_cache_key\":
+ null,\n \"prompt_cache_retention\": null,\n \"reasoning\": {\n \"effort\":
+ null,\n \"summary\": null\n },\n \"safety_identifier\": null,\n \"service_tier\":
+ \"default\",\n \"store\": false,\n \"temperature\": 0.7,\n \"text\": {\n
+ \ \"format\": {\n \"type\": \"text\"\n },\n \"verbosity\": \"medium\"\n
+ \ },\n \"tool_choice\": \"auto\",\n \"tools\": [],\n \"top_logprobs\":
+ 0,\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n \"usage\": {\n \"input_tokens\":
+ 16,\n \"input_tokens_details\": {\n \"cached_tokens\": 0\n },\n
+ \ \"output_tokens\": 11,\n \"output_tokens_details\": {\n \"reasoning_tokens\":
+ 0\n },\n \"total_tokens\": 27\n },\n \"user\": null,\n \"metadata\":
+ {}\n}"
+ headers:
+ CF-RAY:
+ - 9e20ce225fb6f21e-EWR
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 25 Mar 2026 20:53:29 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ cf-cache-status:
+ - DYNAMIC
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '849'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=MaqyGhqWWz99H7isjTwek6F.xhWEvm30A22qLseJTVM-1774472008.054801-1.0.1.1-j8Z8EWycMIEwe9X37KiR.LsfxYAvpTAysXsVfPrKMFPhPY81RDcOu6tTby_EwLVXeFNoqacQMFBAe1f40Je27.VzE0FjotAxAK8AM1DkEVOkv7.q1HXbD5DY6j5Mlu12;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 25 Mar 2026
+ 21:23:29 GMT
+ x-ratelimit-limit-requests:
+ - '1000'
+ x-ratelimit-limit-tokens:
+ - '600000'
+ x-ratelimit-remaining-requests:
+ - '999'
+ x-ratelimit-remaining-tokens:
+ - '599979'
+ x-ratelimit-reset-requests:
+ - 60ms
+ x-ratelimit-reset-tokens:
+ - 2ms
+ x-request-id:
+ - req_0f581b4e674c4742a6bc9cca09916ca9
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_cecd80bb.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_cecd80bb.yaml
new file mode 100644
index 0000000000..47d141a330
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_responses_post_cecd80bb.yaml
@@ -0,0 +1,128 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4","instructions":"You are a test agent","input":[{"role":"user","content":"hello"}],"include":[],"tools":[],"stream":false}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Encoding
+ : - gzip, deflate
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept-Language
+ : - '*'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Connection
+ : - keep-alive
+ Content-Length:
+ - '138'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - Agents/JavaScript 0.7.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Arch
+ : - arm64
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Lang
+ : - js
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-OS
+ : - MacOS
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Package-Version
+ : - 6.32.0
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Retry-Count
+ : - '0'
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime
+ : - node
+ ? !!python/object/apply:multidict._multidict.istr
+ - X-Stainless-Runtime-Version
+ : - v25.2.1
+ ? !!python/object/apply:multidict._multidict.istr
+ - sec-fetch-mode
+ : - cors
+ method: POST
+ uri: https://api.openai.com/v1/responses
+ response:
+ body:
+ string: "{\n \"id\": \"resp_0bc715f27ef7a52b0169c41cdb729481a1a267646efc815202\",\n
+ \ \"object\": \"response\",\n \"created_at\": 1774460123,\n \"status\":
+ \"completed\",\n \"background\": false,\n \"billing\": {\n \"payer\":
+ \"developer\"\n },\n \"completed_at\": 1774460124,\n \"error\": null,\n
+ \ \"frequency_penalty\": 0.0,\n \"incomplete_details\": null,\n \"instructions\":
+ \"You are a test agent\",\n \"max_output_tokens\": null,\n \"max_tool_calls\":
+ null,\n \"model\": \"gpt-4-0613\",\n \"output\": [\n {\n \"id\":
+ \"msg_0bc715f27ef7a52b0169c41cdc7a0881a18e350c2c5951716f\",\n \"type\":
+ \"message\",\n \"status\": \"completed\",\n \"content\": [\n {\n
+ \ \"type\": \"output_text\",\n \"annotations\": [],\n \"logprobs\":
+ [],\n \"text\": \"Hello! How can I assist you today?\"\n }\n
+ \ ],\n \"role\": \"assistant\"\n }\n ],\n \"parallel_tool_calls\":
+ true,\n \"presence_penalty\": 0.0,\n \"previous_response_id\": null,\n \"prompt_cache_key\":
+ null,\n \"prompt_cache_retention\": null,\n \"reasoning\": {\n \"effort\":
+ null,\n \"summary\": null\n },\n \"safety_identifier\": null,\n \"service_tier\":
+ \"default\",\n \"store\": false,\n \"temperature\": 1.0,\n \"text\": {\n
+ \ \"format\": {\n \"type\": \"text\"\n },\n \"verbosity\": \"medium\"\n
+ \ },\n \"tool_choice\": \"auto\",\n \"tools\": [],\n \"top_logprobs\":
+ 0,\n \"top_p\": 1.0,\n \"truncation\": \"disabled\",\n \"usage\": {\n \"input_tokens\":
+ 16,\n \"input_tokens_details\": {\n \"cached_tokens\": 0\n },\n
+ \ \"output_tokens\": 11,\n \"output_tokens_details\": {\n \"reasoning_tokens\":
+ 0\n },\n \"total_tokens\": 27\n },\n \"user\": null,\n \"metadata\":
+ {}\n}"
+ headers:
+ CF-RAY:
+ - 9e1fabf99f43d648-IAD
+ Connection:
+ - keep-alive
+ Content-Encoding:
+ - gzip
+ Content-Type:
+ - application/json
+ Date:
+ - Wed, 25 Mar 2026 17:35:24 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ cf-cache-status:
+ - DYNAMIC
+ openai-organization:
+ - datadog-4
+ openai-processing-ms:
+ - '1260'
+ openai-project:
+ - proj_6cMiry5CHgK3zKotG0LtMb9H
+ openai-version:
+ - '2020-10-01'
+ set-cookie:
+ - __cf_bm=MqXpjdBpiNZGV_0goWMs2folVRrXakVjrvRbNz22DPE-1774460123.1357949-1.0.1.1-kaJVP3kL8q5lHEvOra6c3IE4kzLbeiJotp2HkLYSLleSUVmr1lhDLn3fsoZd9Gt5xhvNJ8cN51Ie_pHSENQARBh.XM5GzxonJQt48ATUpDimpPfu9k8Kyy_chWKwESMI;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 25 Mar 2026
+ 18:05:24 GMT
+ x-ratelimit-limit-requests:
+ - '1000'
+ x-ratelimit-limit-tokens:
+ - '600000'
+ x-ratelimit-remaining-requests:
+ - '999'
+ x-ratelimit-remaining-tokens:
+ - '599979'
+ x-ratelimit-reset-requests:
+ - 60ms
+ x-ratelimit-reset-tokens:
+ - 2ms
+ x-request-id:
+ - req_936f8cbf19144abb8262322211040648
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/packages/dd-trace/test/llmobs/cassettes/openai/openai_v1_responses_post_f671d1c2.yaml b/packages/dd-trace/test/llmobs/cassettes/openai/openai_v1_responses_post_f671d1c2.yaml
new file mode 100644
index 0000000000..a6edd58a45
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/cassettes/openai/openai_v1_responses_post_f671d1c2.yaml
@@ -0,0 +1,53 @@
+interactions:
+- request:
+ body: '{"model":"gpt-4","input":"hello"}'
+ headers:
+ ? !!python/object/apply:multidict._multidict.istr
+ - Accept
+ : - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '33'
+ ? !!python/object/apply:multidict._multidict.istr
+ - Content-Type
+ : - application/json
+ ? !!python/object/apply:multidict._multidict.istr
+ - User-Agent
+ : - curl/8.7.1
+ method: POST
+ uri: https://api.openai.com/v1/v1/responses
+ response:
+ body:
+ string: ''
+ headers:
+ CF-RAY:
+ - 9e1f69338c2de615-IAD
+ Connection:
+ - keep-alive
+ Date:
+ - Wed, 25 Mar 2026 16:49:48 GMT
+ Server:
+ - cloudflare
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains; preload
+ Transfer-Encoding:
+ - chunked
+ X-Content-Type-Options:
+ - nosniff
+ alt-svc:
+ - h3=":443"; ma=86400
+ cf-cache-status:
+ - DYNAMIC
+ set-cookie:
+ - __cf_bm=nhkfItMxViQKMxp30DdsWzyiLSYG_V24gTrFp6sNzXE-1774457388.0853722-1.0.1.1-eBBM5j_quoBmfxp_1hGhHqj7_aecnvXnvg1evq8dIQefomFRoyuiMgKtVWdgUCOJU01jnnFLymfL0NAw2h5FhVE_5CoBDUXWRxSJag3Is7OnOlysXYcwgUD4.oIp866Q;
+ HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 25 Mar 2026
+ 17:19:48 GMT
+ - _cfuvid=71ErowylG_JQ6Ez90TmQY7G70J3cdFTedJc1IQQNb8U-1774457388.0853722-1.0.1.1-FFHwkg8O2W_RxZWUyB4Fci.gtpBvCzK0OwVG13WoVIs;
+ HttpOnly; SameSite=None; Secure; Path=/; Domain=api.openai.com
+ status:
+ code: 404
+ message: Not Found
+version: 1
diff --git a/packages/dd-trace/test/llmobs/index.spec.js b/packages/dd-trace/test/llmobs/index.spec.js
index 573e8c89d4..da3020bfa6 100644
--- a/packages/dd-trace/test/llmobs/index.spec.js
+++ b/packages/dd-trace/test/llmobs/index.spec.js
@@ -10,7 +10,13 @@ const sinon = require('sinon')
const { DD_MAJOR } = require('../../../../version')
const { INCOMPATIBLE_INITIALIZATION } = require('../../src/llmobs/constants/text')
const LLMObsTagger = require('../../src/llmobs/tagger')
-const { SAMPLE_RATE, SAMPLING_DECISION, SESSION_ID } = require('../../src/llmobs/constants/tags')
+const {
+ PROPAGATED_TRACE_ID_KEY,
+ SAMPLE_RATE,
+ SAMPLING_DECISION,
+ SESSION_ID,
+ TRACE_ID,
+} = require('../../src/llmobs/constants/tags')
const { getConfigFresh } = require('../helpers/config')
const { removeDestroyHandler } = require('./util')
@@ -189,6 +195,51 @@ describe('module', () => {
)
})
+ it('converts the local LLMObs trace id to decimal for propagation', () => {
+ llmobsModule.enable({ llmobs: { mlApp: 'test', agentlessEnabled: false } })
+ store.span = {
+ context () {
+ return {
+ _trace: { tags: {} },
+ toSpanId () { return 'parent-id' },
+ }
+ },
+ }
+ LLMObsTagger.tagMap.set(store.span, {
+ [TRACE_ID]: '6a5f76e7000000001973227978d8110b',
+ })
+
+ const carrier = { 'x-datadog-tags': '' }
+ injectCh.publish({ carrier })
+
+ assert.strictEqual(
+ carrier['x-datadog-tags'],
+ // eslint-disable-next-line @stylistic/max-len
+ '_dd.p.llmobs_parent_id=parent-id,_dd.p.llmobs_ml_app=test,_dd.p.llmobs_trace_id=141393847380800662846519802803680448779'
+ )
+ })
+
+ it('forwards an extracted LLMObs trace id without reinterpreting it', () => {
+ llmobsModule.enable({ llmobs: { mlApp: 'test', agentlessEnabled: false } })
+ const wireTraceId = '12345678901234567890123456789012'
+ store.span = {
+ context () {
+ return {
+ _trace: { tags: { [PROPAGATED_TRACE_ID_KEY]: wireTraceId } },
+ toSpanId () { return 'parent-id' },
+ }
+ },
+ }
+
+ const carrier = { 'x-datadog-tags': '' }
+ injectCh.publish({ carrier })
+
+ assert.strictEqual(
+ carrier['x-datadog-tags'],
+ `_dd.p.llmobs_parent_id=parent-id,_dd.p.llmobs_ml_app=test,_dd.p.llmobs_trace_id=${wireTraceId}`
+ )
+ })
+
it('does not inject LLMObs parent ID info when there is no parent LLMObs span', () => {
llmobsModule.enable({ llmobs: { mlApp: 'test', agentlessEnabled: false } })
diff --git a/packages/dd-trace/test/llmobs/openai-agents-utils.spec.js b/packages/dd-trace/test/llmobs/openai-agents-utils.spec.js
new file mode 100644
index 0000000000..e922b31a03
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/openai-agents-utils.spec.js
@@ -0,0 +1,321 @@
+'use strict'
+
+const assert = require('node:assert/strict')
+const {
+ extractInputMessages,
+ extractOutputMessages,
+ extractMetrics,
+ extractMetadata,
+} = require('../../src/llmobs/plugins/openai-agents/utils')
+
+describe('openai-agents utils', () => {
+ describe('extractInputMessages', () => {
+ it('prepends a system message when instructions are provided', () => {
+ assert.deepStrictEqual(
+ extractInputMessages('hello', 'You are helpful'),
+ [
+ { role: 'system', content: 'You are helpful' },
+ { role: 'user', content: 'hello' },
+ ]
+ )
+ })
+
+ it('treats a bare string input as a user message', () => {
+ assert.deepStrictEqual(
+ extractInputMessages('hi'),
+ [{ role: 'user', content: 'hi' }]
+ )
+ })
+
+ it('joins input_text and text parts on array message content', () => {
+ const input = [{
+ type: 'message',
+ role: 'user',
+ content: [
+ { type: 'input_text', text: 'foo ' },
+ { type: 'text', text: 'bar' },
+ { type: 'image_url', url: 'data:...' },
+ ],
+ }]
+ assert.deepStrictEqual(
+ extractInputMessages(input),
+ [{ role: 'user', content: 'foo bar' }]
+ )
+ })
+
+ it('skips array message items missing a role', () => {
+ const input = [{ type: 'message', content: 'orphan' }]
+ assert.deepStrictEqual(
+ extractInputMessages(input),
+ [{ role: 'user', content: '' }]
+ )
+ })
+
+ it('drops array message items whose content is empty after extraction', () => {
+ const input = [{ type: 'message', role: 'user', content: [] }]
+ assert.deepStrictEqual(
+ extractInputMessages(input),
+ [{ role: 'user', content: '' }]
+ )
+ })
+
+ it('accepts string content on array message items', () => {
+ const input = [{ type: 'message', role: 'assistant', content: 'reply' }]
+ assert.deepStrictEqual(
+ extractInputMessages(input),
+ [{ role: 'assistant', content: 'reply' }]
+ )
+ })
+
+ it('parses function_call arguments as JSON when possible', () => {
+ const input = [{
+ type: 'function_call',
+ call_id: 'c1',
+ name: 'lookup',
+ arguments: '{"q":"sf"}',
+ }]
+ const out = extractInputMessages(input)
+ assert.strictEqual(out.length, 1)
+ assert.strictEqual(out[0].role, 'assistant')
+ assert.deepStrictEqual(out[0].toolCalls[0], {
+ toolId: 'c1',
+ name: 'lookup',
+ arguments: { q: 'sf' },
+ type: 'function_call',
+ })
+ })
+
+ it('falls back to empty object args when function_call JSON is invalid', () => {
+ const input = [{
+ type: 'function_call',
+ call_id: 'c2',
+ name: 'lookup',
+ arguments: '{not json}',
+ }]
+ const out = extractInputMessages(input)
+ assert.deepStrictEqual(out[0].toolCalls[0].arguments, {})
+ })
+
+ it('preserves non-string function_call arguments verbatim', () => {
+ const input = [{
+ type: 'function_call',
+ call_id: 'c3',
+ name: 'lookup',
+ arguments: { q: 'sf' },
+ }]
+ const out = extractInputMessages(input)
+ assert.deepStrictEqual(out[0].toolCalls[0].arguments, { q: 'sf' })
+ })
+
+ it('extracts function_call_output items into a tool-result user message', () => {
+ const input = [{
+ type: 'function_call_output',
+ call_id: 'c1',
+ name: 'lookup',
+ output: '72F',
+ }]
+ assert.deepStrictEqual(
+ extractInputMessages(input),
+ [{
+ role: 'user',
+ toolResults: [{
+ toolId: 'c1',
+ result: '72F',
+ name: 'lookup',
+ type: 'function_call_output',
+ }],
+ }]
+ )
+ })
+
+ it('defaults function_call_output name to empty string when missing', () => {
+ const input = [{ type: 'function_call_output', call_id: 'c1', output: '72F' }]
+ const out = extractInputMessages(input)
+ assert.strictEqual(out[0].toolResults[0].name, '')
+ })
+
+ it('returns a placeholder user message when input yields no messages', () => {
+ assert.deepStrictEqual(
+ extractInputMessages([]),
+ [{ role: 'user', content: '' }]
+ )
+ })
+ })
+
+ describe('extractOutputMessages', () => {
+ it('returns a placeholder when result is missing', () => {
+ assert.deepStrictEqual(
+ extractOutputMessages(undefined),
+ [{ content: '', role: '' }]
+ )
+ })
+
+ it('joins output_text parts and defaults role to assistant', () => {
+ const result = {
+ output: [{
+ type: 'message',
+ content: [
+ { type: 'output_text', text: 'hello ' },
+ { type: 'output_text', text: 'world' },
+ { type: 'reasoning', text: 'ignored' },
+ ],
+ }],
+ }
+ assert.deepStrictEqual(
+ extractOutputMessages(result),
+ [{ role: 'assistant', content: 'hello world' }]
+ )
+ })
+
+ it('accepts string content on message items', () => {
+ const result = { output: [{ type: 'message', role: 'user', content: 'echo' }] }
+ assert.deepStrictEqual(
+ extractOutputMessages(result),
+ [{ role: 'user', content: 'echo' }]
+ )
+ })
+
+ it('extracts function_call output items into assistant toolCalls', () => {
+ const result = {
+ output: [{
+ type: 'function_call',
+ call_id: 'c1',
+ name: 'lookup',
+ arguments: '{"q":"sf"}',
+ }],
+ }
+ const out = extractOutputMessages(result)
+ assert.strictEqual(out[0].role, 'assistant')
+ assert.deepStrictEqual(out[0].toolCalls[0], {
+ toolId: 'c1',
+ name: 'lookup',
+ arguments: { q: 'sf' },
+ type: 'function_call',
+ })
+ })
+
+ it('swallows invalid function_call JSON arguments', () => {
+ const result = {
+ output: [{ type: 'function_call', call_id: 'c1', name: 'lookup', arguments: '{bad' }],
+ }
+ const out = extractOutputMessages(result)
+ assert.deepStrictEqual(out[0].toolCalls[0].arguments, {})
+ })
+
+ it('preserves non-string function_call arguments verbatim', () => {
+ const result = {
+ output: [{ type: 'function_call', call_id: 'c1', name: 'lookup', arguments: { q: 'sf' } }],
+ }
+ const out = extractOutputMessages(result)
+ assert.deepStrictEqual(out[0].toolCalls[0].arguments, { q: 'sf' })
+ })
+ })
+
+ describe('extractMetrics', () => {
+ it('returns undefined when usage is absent', () => {
+ assert.strictEqual(extractMetrics({}), undefined)
+ assert.strictEqual(extractMetrics(undefined), undefined)
+ })
+
+ it('returns undefined when usage carries no recognised counters', () => {
+ assert.strictEqual(extractMetrics({ usage: {} }), undefined)
+ })
+
+ it('reads camelCase counters from usage', () => {
+ const metrics = extractMetrics({
+ usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 },
+ })
+ assert.deepStrictEqual(metrics, {
+ inputTokens: 10,
+ outputTokens: 20,
+ totalTokens: 30,
+ })
+ })
+
+ it('reads snake_case counters from usage', () => {
+ const metrics = extractMetrics({
+ usage: { input_tokens: 5, output_tokens: 7, total_tokens: 12 },
+ })
+ assert.deepStrictEqual(metrics, {
+ inputTokens: 5,
+ outputTokens: 7,
+ totalTokens: 12,
+ })
+ })
+
+ it('includes reasoning tokens from camelCase nested details', () => {
+ const metrics = extractMetrics({
+ usage: {
+ inputTokens: 1,
+ outputTokens: 2,
+ outputTokensDetails: { reasoningTokens: 3 },
+ },
+ })
+ assert.strictEqual(metrics.reasoningOutputTokens, 3)
+ })
+
+ it('includes reasoning tokens from snake_case nested details', () => {
+ const metrics = extractMetrics({
+ usage: { output_tokens_details: { reasoning_tokens: 4 } },
+ })
+ assert.strictEqual(metrics.reasoningOutputTokens, 4)
+ })
+
+ it('omits a zero reasoning token count', () => {
+ const metrics = extractMetrics({
+ usage: { inputTokens: 1, outputTokens: 1, outputTokensDetails: { reasoningTokens: 0 } },
+ })
+ assert.ok(!('reasoningOutputTokens' in metrics))
+ })
+
+ it('derives totalTokens from input+output when total is missing', () => {
+ const metrics = extractMetrics({ usage: { inputTokens: 4, outputTokens: 6 } })
+ assert.strictEqual(metrics.totalTokens, 10)
+ })
+
+ it('omits totalTokens when input or output is missing', () => {
+ const metrics = extractMetrics({ usage: { inputTokens: 4 } })
+ assert.ok(!('totalTokens' in metrics))
+ })
+ })
+
+ describe('extractMetadata', () => {
+ it('returns undefined when response is missing', () => {
+ assert.strictEqual(extractMetadata(undefined), undefined)
+ assert.strictEqual(extractMetadata(null), undefined)
+ })
+
+ it('returns undefined when no recognised fields are set', () => {
+ assert.strictEqual(extractMetadata({ unrelated: 1 }), undefined)
+ })
+
+ it('extracts recognised response config fields', () => {
+ const md = extractMetadata({
+ temperature: 0.7,
+ max_output_tokens: 256,
+ top_p: 1,
+ tools: [{ name: 'lookup' }],
+ tool_choice: 'auto',
+ truncation: 'disabled',
+ })
+ assert.deepStrictEqual(md, {
+ temperature: 0.7,
+ max_output_tokens: 256,
+ top_p: 1,
+ tools: [{ name: 'lookup' }],
+ tool_choice: 'auto',
+ truncation: 'disabled',
+ })
+ })
+
+ it('ignores undefined and null values', () => {
+ const md = extractMetadata({ temperature: 0.5, top_p: null, tools: undefined })
+ assert.deepStrictEqual(md, { temperature: 0.5 })
+ })
+
+ it('includes response.text when present', () => {
+ const md = extractMetadata({ text: { format: { type: 'text' } } })
+ assert.deepStrictEqual(md, { text: { format: { type: 'text' } } })
+ })
+ })
+})
diff --git a/packages/dd-trace/test/llmobs/openai-utils.spec.js b/packages/dd-trace/test/llmobs/openai-utils.spec.js
new file mode 100644
index 0000000000..0df67b7ed5
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/openai-utils.spec.js
@@ -0,0 +1,70 @@
+'use strict'
+
+const assert = require('node:assert/strict')
+const { getOpenAIModelProvider } = require('../../src/llmobs/plugins/openai/utils')
+const OpenAiLLMObsPlugin = require('../../src/llmobs/plugins/openai')
+const { UNKNOWN_MODEL_PROVIDER } = require('../../src/llmobs/constants/tags')
+
+describe('getOpenAIModelProvider', () => {
+ it('returns openai for openai.com URLs', () => {
+ assert.strictEqual(getOpenAIModelProvider('https://api.openai.com/v1'), 'openai')
+ })
+
+ it('returns azure_openai for Azure URLs', () => {
+ assert.strictEqual(
+ getOpenAIModelProvider('https://my-resource.openai.azure.com/openai'),
+ 'azure_openai'
+ )
+ })
+
+ it('returns deepseek for DeepSeek URLs', () => {
+ assert.strictEqual(getOpenAIModelProvider('https://api.deepseek.com/v1'), 'deepseek')
+ })
+
+ it('returns unknown provider for unrecognised URLs', () => {
+ assert.strictEqual(getOpenAIModelProvider('http://127.0.0.1:9126/vcr/proxy'), UNKNOWN_MODEL_PROVIDER)
+ })
+
+ it('defaults to unknown provider for an empty string', () => {
+ assert.strictEqual(getOpenAIModelProvider(''), UNKNOWN_MODEL_PROVIDER)
+ })
+})
+
+describe('OpenAiLLMObsPlugin#_getModelProviderAndClient', () => {
+ const call = (baseUrl) => OpenAiLLMObsPlugin.prototype._getModelProviderAndClient(baseUrl)
+
+ it('maps Azure URLs to AzureOpenAI', () => {
+ assert.deepStrictEqual(
+ call('https://my-resource.openai.azure.com/openai'),
+ { modelProvider: 'azure_openai', client: 'AzureOpenAI' }
+ )
+ })
+
+ it('maps DeepSeek URLs to DeepSeek', () => {
+ assert.deepStrictEqual(
+ call('https://api.deepseek.com/v1'),
+ { modelProvider: 'deepseek', client: 'DeepSeek' }
+ )
+ })
+
+ it('maps openai.com URLs to OpenAI', () => {
+ assert.deepStrictEqual(
+ call('https://api.openai.com/v1'),
+ { modelProvider: 'openai', client: 'OpenAI' }
+ )
+ })
+
+ it('falls back to OpenAI client for unknown providers', () => {
+ assert.deepStrictEqual(
+ call('http://127.0.0.1:9126/vcr/proxy'),
+ { modelProvider: UNKNOWN_MODEL_PROVIDER, client: 'OpenAI' }
+ )
+ })
+
+ it('defaults baseUrl to empty string', () => {
+ assert.deepStrictEqual(
+ OpenAiLLMObsPlugin.prototype._getModelProviderAndClient(),
+ { modelProvider: UNKNOWN_MODEL_PROVIDER, client: 'OpenAI' }
+ )
+ })
+})
diff --git a/packages/dd-trace/test/llmobs/plugins/openai-agents/index.spec.js b/packages/dd-trace/test/llmobs/plugins/openai-agents/index.spec.js
new file mode 100644
index 0000000000..efd1a02e6b
--- /dev/null
+++ b/packages/dd-trace/test/llmobs/plugins/openai-agents/index.spec.js
@@ -0,0 +1,313 @@
+'use strict'
+
+const assert = require('node:assert/strict')
+const path = require('node:path')
+
+const { withVersions } = require('../../../setup/mocha')
+
+const {
+ assertLlmObsSpanEvent,
+ MOCK_NOT_NULLISH,
+ MOCK_STRING,
+ useLlmObs,
+} = require('../../util')
+
+const AGENT_INSTRUCTIONS = 'You are a test agent'
+
+function createResponse (output, model = 'gpt-4-0613') {
+ return {
+ id: 'resp_test',
+ object: 'response',
+ created_at: 0,
+ status: 'completed',
+ instructions: AGENT_INSTRUCTIONS,
+ output,
+ usage: {
+ input_tokens: 1,
+ output_tokens: 1,
+ total_tokens: 2,
+ input_tokens_details: { cached_tokens: 0 },
+ output_tokens_details: { reasoning_tokens: 0 },
+ },
+ model,
+ parallel_tool_calls: true,
+ temperature: 1,
+ text: { format: { type: 'text' } },
+ tool_choice: 'auto',
+ tools: [],
+ top_p: 1,
+ truncation: 'disabled',
+ metadata: {},
+ }
+}
+
+function createMessageOutput (text) {
+ return {
+ id: 'msg_test',
+ type: 'message',
+ status: 'completed',
+ role: 'assistant',
+ content: [{ type: 'output_text', text, annotations: [] }],
+ }
+}
+
+function createFetch (responses) {
+ let index = 0
+
+ return async () => {
+ const response = responses[Math.min(index++, responses.length - 1)]
+ return new Response(JSON.stringify(response), {
+ status: 200,
+ headers: {
+ 'content-type': 'application/json',
+ 'x-request-id': 'req_test',
+ },
+ })
+ }
+}
+
+describe('integrations', () => {
+ describe('openai-agents LLMObs', () => {
+ const { getEvents } = useLlmObs({ plugin: 'openai-agents' })
+
+ let agentsCore
+ let agent
+ let handoffAgent
+ let toolErrorAgent
+
+ withVersions('openai-agents', '@openai/agents', (version) => {
+ before(() => {
+ agentsCore = require(`../../../../../../versions/@openai/agents@${version}`).get()
+
+ const { OpenAIResponsesModel } =
+ require(`../../../../../../versions/@openai/agents-openai@${version}`).get()
+
+ const agentsOpenaiDir = path.join(
+ __dirname, '..', '..', '..', '..', '..', '..', 'versions', 'node_modules', '@openai', 'agents-openai'
+ )
+ const openaiPath = require.resolve('openai', { paths: [agentsOpenaiDir] })
+ const { OpenAI } = require(openaiPath)
+
+ const mockClient = new OpenAI({
+ apiKey: 'test',
+ baseURL: 'https://api.openai.com/v1',
+ fetch: createFetch([createResponse([createMessageOutput('hello')])]),
+ })
+ const toolErrorClient = new OpenAI({
+ apiKey: 'test',
+ baseURL: 'https://api.openai.com/v1',
+ fetch: createFetch([
+ createResponse([{
+ id: 'fc_test',
+ type: 'function_call',
+ call_id: 'call_test',
+ name: 'add',
+ arguments: '{"a":1,"b":2}',
+ status: 'completed',
+ }], 'gpt-4o-mini'),
+ createResponse([createMessageOutput('done')], 'gpt-4o-mini'),
+ ]),
+ })
+ const handoffClient = new OpenAI({
+ apiKey: 'test',
+ baseURL: 'https://api.openai.com/v1',
+ fetch: createFetch([
+ createResponse([{
+ id: 'fc_handoff',
+ type: 'function_call',
+ call_id: 'call_handoff',
+ name: 'transfer_to_agent_b',
+ arguments: '{}',
+ status: 'completed',
+ }], 'gpt-4o-mini'),
+ createResponse([createMessageOutput('done')], 'gpt-4o-mini'),
+ ]),
+ })
+
+ agentsCore.setDefaultModelProvider({
+ createModel: (modelName) => new OpenAIResponsesModel(mockClient, modelName),
+ })
+
+ agent = new agentsCore.Agent({
+ name: 'test_agent',
+ instructions: AGENT_INSTRUCTIONS,
+ model: new OpenAIResponsesModel(mockClient, 'gpt-4'),
+ })
+
+ const handoffModel = new OpenAIResponsesModel(handoffClient, 'gpt-4o-mini')
+ const handoffAgentB = new agentsCore.Agent({
+ name: 'agent_b',
+ instructions: 'Finish the request',
+ model: handoffModel,
+ })
+ handoffAgent = new agentsCore.Agent({
+ name: 'agent_a',
+ instructions: 'Hand the request to agent_b',
+ model: handoffModel,
+ handoffs: [handoffAgentB],
+ })
+
+ // Tool with a real parameter schema so the model has something to
+ // pass — the underlying `execute` always throws, exercising the
+ // tool-error path. Mirrors dd-trace-py's
+ // `addition_agent_with_tool_errors` setup.
+ const additionErrorTool = agentsCore.tool({
+ name: 'add',
+ description: 'Adds two numbers and returns the result.',
+ parameters: {
+ type: 'object',
+ properties: {
+ a: { type: 'number', description: 'First number' },
+ b: { type: 'number', description: 'Second number' },
+ },
+ required: ['a', 'b'],
+ additionalProperties: false,
+ },
+ execute: async () => {
+ throw new Error('Intentional error for testing')
+ },
+ })
+
+ toolErrorAgent = new agentsCore.Agent({
+ name: 'addition_agent_with_tool_errors',
+ instructions: 'You are a calculator. Use the `add` tool to answer math questions.',
+ model: new OpenAIResponsesModel(toolErrorClient, 'gpt-4o-mini'),
+ tools: [additionErrorTool],
+ })
+ })
+
+ // Response metadata mirrors Python's openai-agents integration:
+ // response-echoed configuration fields with no filtering of OpenAI
+ // defaults — see `OaiSpanAdapter.llmobs_metadata` in dd-trace-py.
+ const COMMON_RESPONSE_METADATA = {
+ temperature: MOCK_NOT_NULLISH,
+ top_p: MOCK_NOT_NULLISH,
+ tool_choice: MOCK_NOT_NULLISH,
+ tools: MOCK_NOT_NULLISH,
+ truncation: MOCK_NOT_NULLISH,
+ text: MOCK_NOT_NULLISH,
+ }
+
+ describe('run', () => {
+ it('submits a workflow span for a basic run call', async () => {
+ // run() produces three LLMObs spans (workflow, agent, llm) — see
+ // dd-trace-py's `test_llmobs_single_agent`. We only assert the
+ // workflow span here; the LLM span shape is covered separately.
+ await agentsCore.run(agent, 'hello', { maxTurns: 1 })
+
+ const { apmSpans, llmobsSpans } = await getEvents(3)
+ const workflowEvent = llmobsSpans.find(s => s.meta?.['span.kind'] === 'workflow')
+ const workflowApmSpan = apmSpans.find(s => s.name === 'Agent workflow')
+
+ assertLlmObsSpanEvent(workflowEvent, {
+ span: workflowApmSpan,
+ spanKind: 'workflow',
+ name: 'Agent workflow',
+ inputValue: 'hello',
+ outputValue: MOCK_STRING,
+ tags: { ml_app: 'test', integration: 'openai-agents' },
+ })
+ })
+
+ it('submits an llm span under the agent span with the response shape', async () => {
+ // Under `Runner.run`, the response oai-span is a direct child of
+ // the top-level agent span, so the LLMObs span name becomes
+ // `${agent_name} (LLM)` (Python parity).
+ await agentsCore.run(agent, 'hello', { maxTurns: 1 })
+
+ const { apmSpans, llmobsSpans } = await getEvents(3)
+ const llmEvent = llmobsSpans.find(s => s.meta?.['span.kind'] === 'llm')
+ const llmApmSpan = apmSpans.find(s => s.name === 'openai_agents.response')
+ const agentApmSpan = apmSpans.find(s => s.name === 'test_agent')
+
+ assertLlmObsSpanEvent(llmEvent, {
+ span: llmApmSpan,
+ parentId: agentApmSpan?.span_id,
+ spanKind: 'llm',
+ name: 'test_agent (LLM)',
+ modelName: 'gpt-4-0613',
+ modelProvider: 'openai',
+ inputMessages: [
+ { role: 'system', content: AGENT_INSTRUCTIONS },
+ { role: 'user', content: 'hello' },
+ ],
+ outputMessages: [
+ { role: 'assistant', content: MOCK_STRING },
+ ],
+ metrics: {
+ input_tokens: MOCK_NOT_NULLISH,
+ output_tokens: MOCK_NOT_NULLISH,
+ total_tokens: MOCK_NOT_NULLISH,
+ },
+ metadata: COMMON_RESPONSE_METADATA,
+ tags: { ml_app: 'test', integration: 'openai-agents' },
+ })
+ })
+
+ it('keeps the workflow open through a real multi-agent handoff', async () => {
+ const result = await agentsCore.run(handoffAgent, 'start', { maxTurns: 2 })
+ assert.strictEqual(result.finalOutput, 'done')
+
+ const { apmSpans, llmobsSpans } = await getEvents(6)
+ const workflowApmSpan = apmSpans.find(s => s.name === 'Agent workflow')
+ const agentAApmSpan = apmSpans.find(s => s.name === 'agent_a')
+ const agentBApmSpan = apmSpans.find(s => s.name === 'agent_b')
+ const handoffApmSpan = apmSpans.find(s => s.name === 'transfer_to_agent_b')
+ const workflowEvent = llmobsSpans.find(s => s.meta?.['span.kind'] === 'workflow')
+ const handoffEvent = llmobsSpans.find(s => s.name === 'transfer_to_agent_b')
+
+ assert.ok(workflowApmSpan, 'expected a workflow APM span')
+ assert.ok(agentAApmSpan, 'expected agent_a APM span')
+ assert.ok(agentBApmSpan, 'expected agent_b APM span')
+ assert.ok(handoffApmSpan, 'expected a handoff APM span')
+ assert.strictEqual(agentAApmSpan.parent_id.toString(), workflowApmSpan.span_id.toString())
+ assert.strictEqual(agentBApmSpan.parent_id.toString(), workflowApmSpan.span_id.toString())
+ assert.strictEqual(handoffApmSpan.parent_id.toString(), agentAApmSpan.span_id.toString())
+
+ assertLlmObsSpanEvent(workflowEvent, {
+ span: workflowApmSpan,
+ spanKind: 'workflow',
+ name: 'Agent workflow',
+ inputValue: 'start',
+ outputValue: 'done',
+ tags: { ml_app: 'test', integration: 'openai-agents' },
+ })
+ assertLlmObsSpanEvent(handoffEvent, {
+ span: handoffApmSpan,
+ parentId: agentAApmSpan.span_id,
+ spanKind: 'tool',
+ name: 'transfer_to_agent_b',
+ inputValue: 'agent_a',
+ outputValue: 'agent_b',
+ tags: { ml_app: 'test', integration: 'openai-agents' },
+ })
+ })
+
+ it('emits a tool span flagged as errored when the tool throws', async () => {
+ // Mirrors dd-trace-py's `test_llmobs_single_agent_with_tool_errors`:
+ // a `Runner.run()` flow where the model decides to call a tool that
+ // throws. The SDK catches the error, surfaces it on the function
+ // span, then calls the model again with the error context.
+ //
+ // This is the canonical error-path coverage for the
+ // trace-processor architecture — direct `getResponse` /
+ // `invokeFunctionTool` errors don't produce spans without going
+ // through the runner.
+ try {
+ await agentsCore.run(toolErrorAgent, 'What is the sum of 1 and 2?', { maxTurns: 2 })
+ } catch (err) {
+ // Expected: model loops on the failing tool call until maxTurns.
+ }
+
+ const { llmobsSpans } = await getEvents(5)
+ const toolEvent = llmobsSpans.find(s => s.meta?.['span.kind'] === 'tool')
+
+ assert(toolEvent, 'expected a tool span event')
+ assert.strictEqual(toolEvent.meta['span.kind'], 'tool')
+ assert.strictEqual(toolEvent.name, 'add')
+ assert.strictEqual(toolEvent.status, 'error')
+ })
+ })
+ }) // withVersions
+ })
+})
diff --git a/packages/dd-trace/test/llmobs/sdk/index.spec.js b/packages/dd-trace/test/llmobs/sdk/index.spec.js
index 62c4d0394f..55720f9448 100644
--- a/packages/dd-trace/test/llmobs/sdk/index.spec.js
+++ b/packages/dd-trace/test/llmobs/sdk/index.spec.js
@@ -15,6 +15,7 @@ const agent = require('../../plugins/agent')
const { getConfigFresh } = require('../../helpers/config')
const tracerVersion = require('../../../../../package.json').version
const { removeDestroyHandler } = require('../util')
+const { assertObjectContains } = require('../../../../../integration-tests/helpers')
const injectCh = channel('dd-trace:span:inject')
@@ -262,8 +263,7 @@ describe('sdk', () => {
})
describe('parentage', () => {
- // TODO: need to implement custom trace IDs
- it.skip('starts a span with a distinct trace id', () => {
+ it('starts a span with a distinct trace id', () => {
llmobs.trace({ kind: 'workflow', name: 'test' }, span => {
const traceId = LLMObsTagger.tagMap.get(span)['_ml_obs.trace_id']
assert.ok(traceId)
@@ -278,9 +278,11 @@ describe('sdk', () => {
LLMObsTagger.tagMap.get(innerLLMSpan)['_ml_obs.llmobs_parent_id'],
outerLLMSpan.context().toSpanId()
)
- // TODO: need to implement custom trace IDs
- // expect(innerLLMSpan.context()._tags['_ml_obs.trace_id'])
- // .to.equal(outerLLMSpan.context()._tags['_ml_obs.trace_id'])
+
+ assert.equal(
+ LLMObsTagger.tagMap.get(innerLLMSpan)['_ml_obs.trace_id'],
+ LLMObsTagger.tagMap.get(outerLLMSpan)['_ml_obs.trace_id']
+ )
})
})
})
@@ -307,17 +309,16 @@ describe('sdk', () => {
})
})
- // TODO: need to implement custom trace IDs
- it.skip('starts different traces for llmobs spans as child spans of an apm root span', () => {
+ it('starts different traces for llmobs spans as child spans of an apm root span', () => {
let apmTraceId, traceId1, traceId2
tracer.trace('apmRootSpan', apmRootSpan => {
apmTraceId = apmRootSpan.context().toTraceId(true)
- llmobs.trace('workflow', llmobsSpan1 => {
- traceId1 = llmobsSpan1.context().getTag('_ml_obs.trace_id')
+ llmobs.trace({ kind: 'workflow' }, llmobsSpan1 => {
+ traceId1 = LLMObsTagger.tagMap.get(llmobsSpan1)['_ml_obs.trace_id']
})
- llmobs.trace('workflow', llmobsSpan2 => {
- traceId2 = llmobsSpan2.context().getTag('_ml_obs.trace_id')
+ llmobs.trace({ kind: 'workflow' }, llmobsSpan2 => {
+ traceId2 = LLMObsTagger.tagMap.get(llmobsSpan2)['_ml_obs.trace_id']
})
})
@@ -357,7 +358,7 @@ describe('sdk', () => {
span = _span
})
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'workflow',
@@ -373,8 +374,9 @@ describe('sdk', () => {
it('writes llmobs_trace_id and llmobs_parent_id to _trace.tags on first sdk activate', () => {
llmobs.trace({ kind: 'workflow', name: 'wf' }, span => {
const traceTags = span.context()._trace.tags
- assert.strictEqual(traceTags.llmobs_trace_id, span.context().toTraceId(true))
- assert.strictEqual(traceTags.llmobs_parent_id, span.context().toSpanId())
+ const llmobsTraceId = LLMObsTagger.tagMap.get(span)['_ml_obs.trace_id']
+
+ assert.strictEqual(traceTags.llmobs_trace_id, llmobsTraceId)
})
})
@@ -488,7 +490,7 @@ describe('sdk', () => {
wrappedMyLLM('input')
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -508,7 +510,7 @@ describe('sdk', () => {
wrappedMyEmbedding('input')
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'embedding',
@@ -529,7 +531,7 @@ describe('sdk', () => {
wrappedMyRetrieval('input')
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'retrieval',
@@ -556,7 +558,7 @@ describe('sdk', () => {
const wrappedMyWorkflow = llmobs.wrap({ kind: 'workflow' }, myWorkflow)
wrappedMyWorkflow(circular)
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'workflow',
@@ -578,7 +580,7 @@ describe('sdk', () => {
assert.throws(() => wrappedMyTask('foo', 'bar'))
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'task',
@@ -599,7 +601,7 @@ describe('sdk', () => {
return wrappedMyTask('foo', 'bar')
.catch(() => {
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'task',
@@ -627,7 +629,7 @@ describe('sdk', () => {
clock.tick(1000)
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'workflow',
@@ -655,7 +657,7 @@ describe('sdk', () => {
clock.tick(1000)
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'workflow',
@@ -683,7 +685,7 @@ describe('sdk', () => {
clock.tick(1000)
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'workflow',
@@ -739,17 +741,17 @@ describe('sdk', () => {
})
describe('parentage', () => {
- // TODO: need to implement custom trace IDs
- it.skip('starts a span with a distinct trace id', () => {
- const fn = llmobs.wrap('workflow', { name: 'test' }, () => {
- const span = llmobs._active()
-
- const traceId = span.context().getTag('_ml_obs.trace_id')
- assert.ok(traceId)
- assert.notStrictEqual(traceId, span.context().toTraceId(true))
+ it('starts a span with a distinct trace id', () => {
+ let span
+ const fn = llmobs.wrap({ kind: 'workflow', name: 'test' }, () => {
+ span = llmobs._active()
})
fn()
+
+ const llmobsTraceId = LLMObsTagger.tagMap.get(span)['_ml_obs.trace_id']
+ assert.ok(llmobsTraceId)
+ assert.notStrictEqual(llmobsTraceId, span.context().toTraceId(true))
})
it('sets span parentage correctly', () => {
@@ -766,9 +768,11 @@ describe('sdk', () => {
LLMObsTagger.tagMap.get(innerLLMSpan)['_ml_obs.llmobs_parent_id'],
outerLLMSpan.context().toSpanId()
)
- // TODO: need to implement custom trace IDs
- // expect(innerLLMSpan.context()._tags['_ml_obs.trace_id'])
- // .to.equal(outerLLMSpan.context()._tags['_ml_obs.trace_id'])
+
+ assert.equal(
+ LLMObsTagger.tagMap.get(innerLLMSpan)['_ml_obs.trace_id'],
+ LLMObsTagger.tagMap.get(outerLLMSpan)['_ml_obs.trace_id']
+ )
}
const outerWrapped = llmobs.wrap({ kind: 'workflow' }, outer)
@@ -797,9 +801,11 @@ describe('sdk', () => {
LLMObsTagger.tagMap.get(innerLLMObsSpan)['_ml_obs.llmobs_parent_id'],
outerLLMObsSpan.context().toSpanId()
)
- // TODO: need to implement custom trace IDs
- // expect(innerLLMObsSpan.context()._tags['_ml_obs.trace_id'])
- // .to.equal(outerLLMObsSpan.context()._tags['_ml_obs.trace_id'])
+
+ assert.equal(
+ LLMObsTagger.tagMap.get(innerLLMObsSpan)['_ml_obs.trace_id'],
+ LLMObsTagger.tagMap.get(outerLLMObsSpan)['_ml_obs.trace_id']
+ )
}
const outerWrapped = llmobs.wrap({ kind: 'workflow' }, outerLLMObs)
@@ -809,8 +815,7 @@ describe('sdk', () => {
outerWrapped()
})
- // TODO: need to implement custom trace IDs
- it.skip('starts different traces for llmobs spans as child spans of an apm root span', () => {
+ it('starts different traces for llmobs spans as child spans of an apm root span', () => {
let traceId1, traceId2, apmTraceId
function apm () {
apmTraceId = tracer.scope().active().context().toTraceId(true)
@@ -887,7 +892,7 @@ describe('sdk', () => {
fn()
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'workflow',
@@ -922,7 +927,7 @@ describe('sdk', () => {
assert.throws(() => llmobs.annotate(span))
// span should still exist in the registry, just with no annotations
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1002,7 +1007,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ inputData, outputData })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1025,7 +1030,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ inputData, outputData })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1056,7 +1061,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'embedding', name: 'test' }, span => {
llmobs.annotate({ inputData, outputData })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'embedding',
@@ -1075,7 +1080,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'retrieval', name: 'test' }, span => {
llmobs.annotate({ inputData, outputData })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'retrieval',
@@ -1093,7 +1098,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ metadata })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1110,7 +1115,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ metrics })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1127,7 +1132,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ tags })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1144,7 +1149,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ tags, costTags: ['team', 'feature'] })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1167,7 +1172,7 @@ describe('sdk', () => {
costTags: ['feature', 'project'],
})
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1183,7 +1188,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ tags: { team: 'ml' }, costTags: ['team', 'missing', 123] })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1199,7 +1204,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ tags: { team: 'ml' }, costTags: 'team' })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1214,7 +1219,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ tags: { team: 'ml' }, costTags: [] })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1229,7 +1234,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ tags: { team: 'ml' }, costTags: null })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1249,7 +1254,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ toolDefinitions })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1268,7 +1273,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ toolDefinitions })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1288,7 +1293,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ toolDefinitions })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1322,7 +1327,7 @@ describe('sdk', () => {
it('applies costTags to spans created in the context', () => {
llmobs.annotationContext({ tags: { team: 'ml', feature: 'chatbot' }, costTags: ['team', 'feature'] }, () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1340,7 +1345,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ tags: { team: 'ml' } })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
+ assertObjectContains(LLMObsTagger.tagMap.get(span), {
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
'_ml_obs.meta.span.kind': 'llm',
@@ -1361,14 +1366,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'llm', name: 'test' }, span => {
llmobs.annotate({ toolDefinitions })
- assert.deepStrictEqual(LLMObsTagger.tagMap.get(span), {
- '_ml_obs.sample_rate': '1',
- '_ml_obs.sampling_decision': '1',
- '_ml_obs.meta.span.kind': 'llm',
- '_ml_obs.meta.ml_app': 'mlApp',
- '_ml_obs.llmobs_parent_id': 'undefined',
- '_ml_obs.meta.tool_definitions': toolDefinitions,
- })
+ assert.deepStrictEqual(LLMObsTagger.tagMap.get(span)['_ml_obs.meta.tool_definitions'], toolDefinitions)
})
})
@@ -1399,7 +1397,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'workflow', name: 'test' }, span => {
const spanCtx = llmobs.exportSpan(span)
- const traceId = span.context().toTraceId(true)
+ const traceId = LLMObsTagger.tagMap.get(span)['_ml_obs.trace_id']
const spanId = span.context().toSpanId()
assert.deepStrictEqual(spanCtx, { traceId, spanId })
@@ -1410,7 +1408,7 @@ describe('sdk', () => {
llmobs.trace({ kind: 'workflow', name: 'test' }, span => {
const spanCtx = llmobs.exportSpan()
- const traceId = span.context().toTraceId(true)
+ const traceId = LLMObsTagger.tagMap.get(span)['_ml_obs.trace_id']
const spanId = span.context().toSpanId()
assert.deepStrictEqual(spanCtx, { traceId, spanId })
@@ -1422,23 +1420,13 @@ describe('sdk', () => {
tracer.trace('apmSpan', () => {
const spanCtx = llmobs.exportSpan()
- const traceId = llmobsSpan.context().toTraceId(true)
+ const traceId = LLMObsTagger.tagMap.get(llmobsSpan)['_ml_obs.trace_id']
const spanId = llmobsSpan.context().toSpanId()
assert.deepStrictEqual(spanCtx, { traceId, spanId })
})
})
})
-
- it('returns undefined if the provided span is not a span', () => {
- llmobs.trace({ kind: 'workflow', name: 'test' }, fakeSpan => {
- fakeSpan.context().toTraceId = undefined // something that would throw
- LLMObsTagger.tagMap.set(fakeSpan, {})
- const spanCtx = llmobs.exportSpan(fakeSpan)
-
- assert.strictEqual(spanCtx, undefined)
- })
- })
})
describe('submitEvaluation', () => {
@@ -1803,19 +1791,23 @@ describe('sdk', () => {
describe('distributed', () => {
it('adds the current llmobs span id and sampling decision to the injection context', () => {
const carrier = { 'x-datadog-tags': '' }
- let parentId, span
+ let parentId, span, traceId
llmobs.trace({ kind: 'workflow', name: 'myWorkflow' }, _span => {
span = _span
parentId = span.context().toSpanId()
+ traceId = LLMObsTagger.tagMap.get(span)['_ml_obs.trace_id']
// simulate injection from http integration or from tracer
// something that triggers the text_map injection
injectCh.publish({ carrier })
})
+ const wireTraceId = BigInt(`0x${traceId}`).toString(10)
+
assert.strictEqual(
carrier['x-datadog-tags'],
- `_dd.p.llmobs_parent_id=${parentId},_dd.p.llmobs_ml_app=mlApp,_dd.p.llmobs_sr=1,_dd.p.llmobs_sd=1`
+ // eslint-disable-next-line @stylistic/max-len
+ `_dd.p.llmobs_parent_id=${parentId},_dd.p.llmobs_ml_app=mlApp,_dd.p.llmobs_sr=1,_dd.p.llmobs_sd=1,_dd.p.llmobs_trace_id=${wireTraceId}`
)
})
})
diff --git a/packages/dd-trace/test/llmobs/sdk/integration.spec.js b/packages/dd-trace/test/llmobs/sdk/integration.spec.js
index 59436b98c5..be0affa2a6 100644
--- a/packages/dd-trace/test/llmobs/sdk/integration.spec.js
+++ b/packages/dd-trace/test/llmobs/sdk/integration.spec.js
@@ -166,19 +166,17 @@ describe('end to end sdk integration tests', () => {
describe('otel correlation bridge tags', () => {
it('writes llmobs_trace_id, llmobs_parent_id, and _dd.llmobs.submitted to apm span meta', async () => {
- let workflowSpanCtx
llmobs.trace({ kind: 'workflow', name: 'wf' }, span => {
- workflowSpanCtx = { traceId: span.context().toTraceId(true), spanId: span.context().toSpanId() }
llmobs.trace({ kind: 'task', name: 'inner' }, () => {})
})
- const { apmSpans } = await getEvents(2)
+ const { apmSpans, llmobsSpans } = await getEvents(2)
assert.equal(apmSpans.length, 2)
// The first span in the chunk carries _trace.tags, including the bridge tags.
const firstSpan = apmSpans[0]
- assert.equal(firstSpan.meta.llmobs_trace_id, workflowSpanCtx.traceId)
- assert.equal(firstSpan.meta.llmobs_parent_id, workflowSpanCtx.spanId)
+ assert.equal(firstSpan.meta.llmobs_trace_id, llmobsSpans[0].trace_id)
+ assert.equal(firstSpan.meta.llmobs_parent_id, llmobsSpans[0].span_id)
// Every SDK-tagged apm span carries the submitted marker.
for (const apmSpan of apmSpans) {
@@ -224,6 +222,8 @@ describe('end to end sdk integration tests', () => {
assert.equal(getTag(llmobsSpans[0], 'ml_app'), 'test')
assert.equal(getTag(llmobsSpans[1], 'ml_app'), 'test')
+ assert.equal(llmobsSpans[0].trace_id, llmobsSpans[1].trace_id)
+ assert.match(llmobsSpans[0].trace_id, /^[0-9a-f]{32}$/)
})
it('injects the local mlApp', async () => {
diff --git a/packages/dd-trace/test/llmobs/span_processor.spec.js b/packages/dd-trace/test/llmobs/span_processor.spec.js
index b6892f5f20..0311a671b5 100644
--- a/packages/dd-trace/test/llmobs/span_processor.spec.js
+++ b/packages/dd-trace/test/llmobs/span_processor.spec.js
@@ -75,13 +75,14 @@ describe('span processor', () => {
'_ml_obs.llmobs_parent_id': '1234',
'_ml_obs.sample_rate': '1',
'_ml_obs.sampling_decision': '1',
+ '_ml_obs.trace_id': 'mlob123',
})
processor.process(span)
const payload = writer.append.getCall(0).firstArg
assert.deepStrictEqual(payload, {
- trace_id: '123',
+ trace_id: 'mlob123',
span_id: '456',
parent_id: '1234',
name: 'test',
@@ -116,6 +117,7 @@ describe('span processor', () => {
span_id: '456',
sample_rate: '1',
sampling_decision: '1',
+ apm_trace_id: '123',
},
})
diff --git a/packages/dd-trace/test/llmobs/tagger.spec.js b/packages/dd-trace/test/llmobs/tagger.spec.js
index 0fc69388fc..fe5aef758d 100644
--- a/packages/dd-trace/test/llmobs/tagger.spec.js
+++ b/packages/dd-trace/test/llmobs/tagger.spec.js
@@ -6,7 +6,8 @@ const { beforeEach, describe, it } = require('mocha')
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const { INPUT_PROMPT } = require('../../src/llmobs/constants/tags')
-const { writeBridgeTags, findGenAIAncestorSpanId } = require('../../src/llmobs/util')
+const { writeBridgeTags, findGenAIAncestorSpanId, normalizeLlmObsTraceId } = require('../../src/llmobs/util')
+const { assertObjectContains } = require('../../../../integration-tests/helpers')
function unserializableObject () {
const obj = {}
@@ -43,9 +44,10 @@ describe('tagger', () => {
// existing tests get the "no gen_ai ancestor" branch; individual tests
// can call `.returns(id)` on the stub to exercise suppression.
util = {
- generateTraceId: sinon.stub().returns('0123'),
+ generateLlmObsTraceId: sinon.stub().returns('0123456789abcdef0123456789abcdef'),
writeBridgeTags,
findGenAIAncestorSpanId: sinon.stub().returns(null),
+ normalizeLlmObsTraceId,
}
logger = {
@@ -74,7 +76,7 @@ describe('tagger', () => {
it('tags an llm obs span with basic and default properties', () => {
tagger.registerLLMObsSpan(span, { kind: 'workflow' })
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'workflow',
'_ml_obs.meta.ml_app': 'my-default-ml-app',
'_ml_obs.llmobs_parent_id': 'undefined', // no parent id provided
@@ -92,7 +94,7 @@ describe('tagger', () => {
mlApp: 'my-app',
})
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'llm',
'_ml_obs.meta.model_name': 'my-model',
'_ml_obs.meta.model_provider': 'my-provider',
@@ -149,7 +151,7 @@ describe('tagger', () => {
it('uses the name if provided', () => {
tagger.registerLLMObsSpan(span, { kind: 'llm', name: 'my-span-name' })
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'llm',
'_ml_obs.meta.ml_app': 'my-default-ml-app',
'_ml_obs.llmobs_parent_id': 'undefined',
@@ -162,7 +164,7 @@ describe('tagger', () => {
it('defaults parent id to undefined', () => {
tagger.registerLLMObsSpan(span, { kind: 'llm' })
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'llm',
'_ml_obs.meta.ml_app': 'my-default-ml-app',
'_ml_obs.llmobs_parent_id': 'undefined',
@@ -189,7 +191,7 @@ describe('tagger', () => {
// The parent carries no sampling decision, so the child inherits none
// (it does not start a fresh decision mid-trace).
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'llm',
'_ml_obs.meta.ml_app': 'my-ml-app',
'_ml_obs.session_id': 'my-session',
@@ -198,15 +200,14 @@ describe('tagger', () => {
})
it('uses the propagated trace id if provided', () => {
+ spanContext._trace.tags['_dd.p.llmobs_trace_id'] = '141393847380800662846519802803680448779'
+
tagger.registerLLMObsSpan(span, { kind: 'llm' })
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
- '_ml_obs.meta.span.kind': 'llm',
- '_ml_obs.meta.ml_app': 'my-default-ml-app',
- '_ml_obs.llmobs_parent_id': 'undefined',
- '_ml_obs.sample_rate': '1',
- '_ml_obs.sampling_decision': '1',
- })
+ assert.strictEqual(
+ Tagger.tagMap.get(span)['_ml_obs.trace_id'],
+ '6a5f76e7000000001973227978d8110b'
+ )
})
it('uses the propagated parent id if provided', () => {
@@ -215,7 +216,7 @@ describe('tagger', () => {
tagger.registerLLMObsSpan(span, { kind: 'llm' })
// Propagated parent with no propagated sampling info: inherit none.
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'llm',
'_ml_obs.meta.ml_app': 'my-default-ml-app',
'_ml_obs.llmobs_parent_id': '-567',
@@ -228,6 +229,13 @@ describe('tagger', () => {
assert.strictEqual(Tagger.tagMap.get(span), undefined)
})
+ it('creates a custom trace id', () => {
+ tagger.registerLLMObsSpan(span, { kind: 'workflow' })
+ const llmobsTraceId = Tagger.tagMap.get(span)['_ml_obs.trace_id']
+ assert.strictEqual(llmobsTraceId, '0123456789abcdef0123456789abcdef')
+ assert.notEqual(llmobsTraceId, span.context().toTraceId(true))
+ })
+
describe('sampling', () => {
it('records a SAMPLED decision and the rate on a root span when sampleRate is 1', () => {
tagger.registerLLMObsSpan(span, { kind: 'llm' })
@@ -364,7 +372,7 @@ describe('tagger', () => {
it('writes llmobs_trace_id and llmobs_parent_id to _trace.tags after a successful register', () => {
tagger.registerLLMObsSpan(span, { kind: 'workflow' })
- assert.strictEqual(spanContext._trace.tags.llmobs_trace_id, '00000000000000001111111111111111')
+ assert.strictEqual(spanContext._trace.tags.llmobs_trace_id, Tagger.tagMap.get(span)['_ml_obs.trace_id'])
assert.strictEqual(spanContext._trace.tags.llmobs_parent_id, '2222222222222222')
})
@@ -381,7 +389,7 @@ describe('tagger', () => {
tagger.registerLLMObsSpan(secondSpan, { kind: 'task' })
- assert.strictEqual(spanContext._trace.tags.llmobs_trace_id, '00000000000000001111111111111111')
+ assert.strictEqual(spanContext._trace.tags.llmobs_trace_id, Tagger.tagMap.get(span)['_ml_obs.trace_id'])
assert.strictEqual(spanContext._trace.tags.llmobs_parent_id, '2222222222222222')
})
@@ -417,7 +425,7 @@ describe('tagger', () => {
it('writes llmobs_trace_id but omits llmobs_parent_id', () => {
tagger.registerLLMObsSpan(span, { kind: 'llm' })
- assert.strictEqual(spanContext._trace.tags.llmobs_trace_id, '00000000000000001111111111111111')
+ assert.strictEqual(spanContext._trace.tags.llmobs_trace_id, Tagger.tagMap.get(span)['_ml_obs.trace_id'])
assert.strictEqual(spanContext._trace.tags.llmobs_parent_id, undefined)
})
@@ -450,9 +458,10 @@ describe('tagger', () => {
RealTagger = proxyquire('../../src/llmobs/tagger', {
'../log': { warn () {} },
'./util': {
- generateTraceId: sinon.stub().returns('0123'),
+ generateLlmObsTraceId: sinon.stub().returns('0123456789abcdef0123456789abcdef'),
writeBridgeTags,
findGenAIAncestorSpanId,
+ normalizeLlmObsTraceId,
},
})
realTagger = new RealTagger({ llmobs: { DD_LLMOBS_ENABLED: true, mlApp: 'test-app' } })
@@ -490,7 +499,7 @@ describe('tagger', () => {
realTagger.registerLLMObsSpan(leafSpan, { kind: 'llm' })
- assert.strictEqual(traceTags.llmobs_trace_id, '00000000000000009999999999999999')
+ assert.strictEqual(traceTags.llmobs_trace_id, RealTagger.tagMap.get(leafSpan)['_ml_obs.trace_id'])
assert.strictEqual(traceTags.llmobs_parent_id, undefined)
assert.strictEqual(RealTagger.tagMap.get(leafSpan)['_ml_obs.llmobs_parent_id'], genAISpanId)
})
@@ -502,7 +511,7 @@ describe('tagger', () => {
it('tags a span with metadata', () => {
tagger._register(span)
tagger.tagMetadata(span, { a: 'foo', b: 'bar' })
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.metadata': { a: 'foo', b: 'bar' },
})
})
@@ -510,7 +519,7 @@ describe('tagger', () => {
it('updates instead of overriding', () => {
Tagger.tagMap.set(span, { '_ml_obs.meta.metadata': { a: 'foo' } })
tagger.tagMetadata(span, { b: 'bar' })
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.metadata': { a: 'foo', b: 'bar' },
})
})
@@ -520,7 +529,7 @@ describe('tagger', () => {
it('tags a span with metrics', () => {
tagger._register(span)
tagger.tagMetrics(span, { a: 1, b: 2 })
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.metrics': { a: 1, b: 2 },
})
})
@@ -533,7 +542,7 @@ describe('tagger', () => {
totalTokens: 3,
foo: 10,
})
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.metrics': { input_tokens: 1, output_tokens: 2, total_tokens: 3, foo: 10 },
})
})
@@ -552,7 +561,7 @@ describe('tagger', () => {
it('updates instead of overriding', () => {
Tagger.tagMap.set(span, { '_ml_obs.metrics': { a: 1 } })
tagger.tagMetrics(span, { b: 2 })
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.metrics': { a: 1, b: 2 },
})
})
@@ -570,7 +579,7 @@ describe('tagger', () => {
]
tagger._register(span)
tagger.tagToolDefinitions(span, toolDefinitions)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.tool_definitions': toolDefinitions,
})
})
@@ -578,7 +587,7 @@ describe('tagger', () => {
it('tags a span with only a name', () => {
tagger._register(span)
tagger.tagToolDefinitions(span, [{ name: 'get_time' }])
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.tool_definitions': [{ name: 'get_time' }],
})
})
@@ -588,7 +597,7 @@ describe('tagger', () => {
tagger.tagToolDefinitions(span, [
{ name: 'get_weather', description: 123, schema: 'not-an-object', version: 456 },
])
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.tool_definitions': [{ name: 'get_weather' }],
})
})
@@ -599,7 +608,7 @@ describe('tagger', () => {
{ description: 'no name' },
{ name: 'valid_tool' },
])
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.tool_definitions': [{ name: 'valid_tool' }],
})
})
@@ -625,7 +634,7 @@ describe('tagger', () => {
const tags = { foo: 'bar' }
tagger._register(span)
tagger.tagSpanTags(span, tags)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.tags': { foo: 'bar' },
})
})
@@ -634,12 +643,20 @@ describe('tagger', () => {
Tagger.tagMap.set(span, { '_ml_obs.tags': { a: 1 } })
const tags = { a: 2, b: 1 }
tagger.tagSpanTags(span, tags)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.tags': { a: 2, b: 1 },
})
})
})
+ describe('setName', () => {
+ it('sets the _ml_obs.name tag on the span', () => {
+ tagger._register(span)
+ tagger.setName(span, 'my-span-name')
+ assert.strictEqual(Tagger.tagMap.get(span)['_ml_obs.name'], 'my-span-name')
+ })
+ })
+
describe('tagCostTags', () => {
it('validates and sets cost tags', () => {
tagger._register(span)
@@ -709,7 +726,7 @@ describe('tagger', () => {
tagger._register(span)
tagger.tagLLMIO(span, inputData, outputData)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.input.messages': [
{ content: 'you are an amazing assistant', role: '' },
{ content: 'hello! my name is foobar', role: '' },
@@ -757,7 +774,7 @@ describe('tagger', () => {
tagger._register(span)
tagger.tagLLMIO(span, inputData, outputData)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.input.messages': [
{
content: 'hello',
@@ -829,7 +846,7 @@ describe('tagger', () => {
tagger._register(span)
tagger.tagLLMIO(span, inputData)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.input.messages': [
{
content: 'hello',
@@ -901,7 +918,7 @@ describe('tagger', () => {
tagger._register(span)
tagger.tagLLMIO(span, messages, undefined)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.input.messages': [
{ role: 'tool', content: 'The weather in San Francisco is sunny', tool_id: '123' },
],
@@ -1056,7 +1073,7 @@ describe('tagger', () => {
const outputData = 'embedded documents'
tagger._register(span)
tagger.tagEmbeddingIO(span, inputData, outputData)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.input.documents': [
{ text: 'my string document' },
{ text: 'my object document' },
@@ -1123,7 +1140,7 @@ describe('tagger', () => {
tagger._register(span)
tagger.tagRetrievalIO(span, inputData, outputData)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.input.value': 'some query',
'_ml_obs.meta.output.documents': [
{ text: 'result 1' },
@@ -1157,7 +1174,7 @@ describe('tagger', () => {
const outputData = 'some text'
tagger._register(span)
tagger.tagTextIO(span, inputData, outputData)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.input.value': '{"some":"object"}',
'_ml_obs.meta.output.value': 'some text',
})
@@ -1173,20 +1190,20 @@ describe('tagger', () => {
it('changes the span kind', () => {
tagger._register(span)
tagger._setTag(span, '_ml_obs.meta.span.kind', 'old-kind')
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'old-kind',
})
tagger.changeKind(span, 'new-kind')
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'new-kind',
})
})
it('sets the kind if it is not already set', () => {
tagger._register(span)
- assert.deepStrictEqual(Tagger.tagMap.get(span), {})
+ assertObjectContains(Tagger.tagMap.get(span), {})
tagger.changeKind(span, 'new-kind')
- assert.deepStrictEqual(Tagger.tagMap.get(span), {
+ assertObjectContains(Tagger.tagMap.get(span), {
'_ml_obs.meta.span.kind': 'new-kind',
})
})
diff --git a/packages/dd-trace/test/llmobs/util.spec.js b/packages/dd-trace/test/llmobs/util.spec.js
index 2fd3d290dd..cdb7c7a247 100644
--- a/packages/dd-trace/test/llmobs/util.spec.js
+++ b/packages/dd-trace/test/llmobs/util.spec.js
@@ -9,8 +9,11 @@ const {
audioMimeTypeFromFormat,
encodeUnicode,
findGenAIAncestorSpanId,
+ generateLlmObsTraceId,
+ llmObsTraceIdToWire,
formatAudioPart,
getFunctionArguments,
+ normalizeLlmObsTraceId,
validateCostTags,
safeJsonParse,
validateKind,
@@ -19,6 +22,48 @@ const {
} = require('../../src/llmobs/util')
describe('util', () => {
+ describe('LLMObs trace id propagation', () => {
+ const traceId = '6a5f76e7000000001973227978d8110b'
+ const wireTraceId = '141393847380800662846519802803680448779'
+
+ it('converts a canonical hexadecimal trace id to decimal for propagation', () => {
+ assert.strictEqual(llmObsTraceIdToWire(traceId), wireTraceId)
+ })
+
+ it('normalizes a propagated decimal trace id to canonical hexadecimal', () => {
+ assert.strictEqual(normalizeLlmObsTraceId(wireTraceId), traceId)
+ })
+
+ it('preserves 64-bit decimal trace ids and converts the first 128-bit value', () => {
+ assert.strictEqual(normalizeLlmObsTraceId('18446744073709551615'), '18446744073709551615')
+ assert.strictEqual(
+ normalizeLlmObsTraceId('18446744073709551616'),
+ '00000000000000010000000000000000'
+ )
+ })
+
+ it('preserves hexadecimal and custom trace ids while normalizing', () => {
+ assert.strictEqual(normalizeLlmObsTraceId(traceId), traceId)
+ assert.strictEqual(normalizeLlmObsTraceId('custom-trace-id'), 'custom-trace-id')
+ })
+
+ it('preserves custom trace ids for propagation', () => {
+ assert.strictEqual(llmObsTraceIdToWire('custom-trace-id'), 'custom-trace-id')
+ })
+
+ it('returns undefined for empty trace ids', () => {
+ assert.strictEqual(llmObsTraceIdToWire(''), undefined)
+ assert.strictEqual(normalizeLlmObsTraceId(''), undefined)
+ })
+
+ it('generates a 128-bit trace id containing the start time', () => {
+ const generatedTraceId = generateLlmObsTraceId(1_700_000_000_000)
+
+ assert.match(generatedTraceId, /^[0-9a-f]{32}$/)
+ assert.strictEqual(generatedTraceId.slice(0, 16), '6553f10000000000')
+ })
+ })
+
describe('encodeUnicode', () => {
it('should encode unicode characters', () => {
assert.strictEqual(encodeUnicode('😀'), '\\ud83d\\ude00')
diff --git a/packages/dd-trace/test/openfeature/agentless_configuration_source.spec.js b/packages/dd-trace/test/openfeature/agentless_configuration_source.spec.js
index d8816abb8f..11d8032d82 100644
--- a/packages/dd-trace/test/openfeature/agentless_configuration_source.spec.js
+++ b/packages/dd-trace/test/openfeature/agentless_configuration_source.spec.js
@@ -181,13 +181,15 @@ describe('AgentlessConfigurationSource', () => {
clock.restore()
clock = undefined
const body = zlib.gzipSync(responseBody())
- nock('http://127.0.0.1:8080', {
+ config.endpoint = new URL('http://flags.dev.internal:8080/custom/ufc')
+ delete config.apiKey
+ nock('http://flags.dev.internal:8080', {
+ badheaders: ['dd-api-key'],
reqheaders: {
'accept-encoding': 'gzip',
- 'dd-api-key': 'test-api-key',
},
})
- .get('/api/v2/feature-flagging/config/rules-based/server')
+ .get('/custom/ufc')
.reply(200, body, {
'content-encoding': 'gzip',
etag: '"real-path"',
@@ -414,7 +416,7 @@ describe('AgentlessConfigurationSource', () => {
'third',
])
assert.deepStrictEqual(log.warn.thirdCall.args, [
- 'Feature Flagging agentless endpoint returned HTTP %d; verify DD_API_KEY is configured and valid',
+ 'Feature Flagging agentless endpoint returned HTTP %d; verify endpoint authentication',
401,
])
})
@@ -480,7 +482,7 @@ describe('AgentlessConfigurationSource', () => {
assert.strictEqual(requests.length, 1)
sinon.assert.calledOnceWithExactly(
log.warn,
- 'Feature Flagging agentless endpoint returned HTTP %d; verify DD_API_KEY is configured and valid',
+ 'Feature Flagging agentless endpoint returned HTTP %d; verify endpoint authentication',
401
)
@@ -491,6 +493,22 @@ describe('AgentlessConfigurationSource', () => {
sinon.assert.notCalled(applyConfiguration)
})
+ it('omits a missing API key and reports the endpoint authentication failure', async () => {
+ delete config.apiKey
+ responses.push({ statusCode: 401 })
+
+ source().start()
+ await flush()
+
+ assert.strictEqual(Object.hasOwn(requests[0].options.headers, 'DD-API-KEY'), false)
+ sinon.assert.calledOnceWithExactly(
+ log.warn,
+ 'Feature Flagging agentless endpoint returned HTTP %d; verify endpoint authentication',
+ 401
+ )
+ sinon.assert.notCalled(applyConfiguration)
+ })
+
it('uses fixed-delay polling after a request completes', async () => {
responses.push(
{ pending: true },
diff --git a/packages/dd-trace/test/openfeature/configuration_source.spec.js b/packages/dd-trace/test/openfeature/configuration_source.spec.js
index 8ac81dba43..17aa2ae9b3 100644
--- a/packages/dd-trace/test/openfeature/configuration_source.spec.js
+++ b/packages/dd-trace/test/openfeature/configuration_source.spec.js
@@ -86,21 +86,37 @@ describe('OpenFeature configuration source', () => {
it(`allows the loopback endpoint ${baseUrl}`, () => {
config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL = baseUrl
+ const resolved = createSourceConfig()
assert.strictEqual(
- createSourceConfig().endpoint.toString(),
+ resolved.endpoint.toString(),
`${baseUrl}/api/v2/feature-flagging/config/rules-based/server`
)
+ assert.strictEqual(resolved.apiKey, undefined)
})
}
+ it('allows a cleartext custom endpoint for local development and proxies', () => {
+ config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL =
+ 'http://flags.dev.internal:8080'
+
+ const resolved = createSourceConfig()
+ assert.strictEqual(
+ resolved.endpoint.toString(),
+ 'http://flags.dev.internal:8080/api/v2/feature-flagging/config/rules-based/server'
+ )
+ assert.strictEqual(resolved.apiKey, undefined)
+ })
+
it('preserves an exact configured path and query', () => {
config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL =
'https://example.com/custom/ufc?tenant=one'
+ const resolved = createSourceConfig()
assert.strictEqual(
- createSourceConfig().endpoint.toString(),
+ resolved.endpoint.toString(),
'https://example.com/custom/ufc?tenant=one'
)
+ assert.strictEqual(resolved.apiKey, undefined)
})
it('derives and creates the managed GovCloud endpoint without hard-coding availability', () => {
@@ -156,20 +172,6 @@ describe('OpenFeature configuration source', () => {
sinon.assert.notCalled(AgentlessConfigurationSource)
})
- it('rejects cleartext non-loopback endpoints', () => {
- config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL = 'http://flags.example.test'
-
- const source = configurationSource.create(config, sinon.spy())
-
- assert.strictEqual(source, undefined)
- sinon.assert.calledOnceWithMatch(
- log.error,
- 'Unable to configure Feature Flagging configuration source',
- sinon.match.instanceOf(Error)
- )
- sinon.assert.notCalled(AgentlessConfigurationSource)
- })
-
it('rejects malformed endpoints without logging their sensitive value', () => {
const sentinel = 'sensitive-value'
config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL = `https://${sentinel} value`
@@ -187,7 +189,19 @@ describe('OpenFeature configuration source', () => {
assert.strictEqual(log.error.firstCall.args[1].cause, undefined)
})
- it('requires an API key without enabling a source', () => {
+ it('creates a source for a custom API without a Datadog API key', () => {
+ delete config.DD_API_KEY
+ config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL =
+ 'https://flags.example.test/custom/ufc'
+
+ const source = configurationSource.create(config, sinon.spy())
+
+ assert.ok(source instanceof AgentlessConfigurationSource)
+ assert.strictEqual(AgentlessConfigurationSource.firstCall.args[0].apiKey, undefined)
+ sinon.assert.notCalled(log.error)
+ })
+
+ it('requires a Datadog API key for the default Datadog Feature Flagging endpoint', () => {
delete config.DD_API_KEY
const source = configurationSource.create(config, sinon.spy())
@@ -195,7 +209,7 @@ describe('OpenFeature configuration source', () => {
sinon.assert.calledOnceWithMatch(
log.error,
'Unable to configure Feature Flagging configuration source',
- sinon.match.instanceOf(Error)
+ sinon.match.has('message', 'DD_API_KEY is required for the default Datadog Feature Flagging endpoint')
)
assert.strictEqual(source, undefined)
sinon.assert.notCalled(AgentlessConfigurationSource)
diff --git a/packages/dd-trace/test/openfeature/file-tracing.spec.js b/packages/dd-trace/test/openfeature/file-tracing.spec.js
index f5d1c179b1..1dce1f9817 100644
--- a/packages/dd-trace/test/openfeature/file-tracing.spec.js
+++ b/packages/dd-trace/test/openfeature/file-tracing.spec.js
@@ -6,9 +6,10 @@ const { mkdirSync, mkdtempSync, rmSync, symlinkSync } = require('node:fs')
const { tmpdir } = require('node:os')
const path = require('node:path')
-const { nodeFileTrace } = require('@vercel/nft')
const { describe, it } = require('mocha')
+const { NODE_MAJOR } = require('../../../../version')
+
const repoRoot = path.resolve(__dirname, '../../../..')
const expectedPackageFiles = [
'node_modules/@datadog/openfeature-node-server/package.json',
@@ -16,6 +17,14 @@ const expectedPackageFiles = [
'node_modules/spark-md5/package.json',
]
+if (NODE_MAJOR < 20) {
+ describe.skip('OpenFeature file tracing (requires @vercel/nft, which needs Node.js >= 20)')
+ return
+}
+
+// eslint-disable-next-line import/order
+const { nodeFileTrace } = require('@vercel/nft')
+
/**
* @param {string} entrypoint
*/
diff --git a/packages/dd-trace/test/plugins/externals.js b/packages/dd-trace/test/plugins/externals.js
index 8bb46fb873..535ee2dc38 100644
--- a/packages/dd-trace/test/plugins/externals.js
+++ b/packages/dd-trace/test/plugins/externals.js
@@ -542,6 +542,20 @@ module.exports = {
dep: true,
},
],
+ 'openai-agents': [
+ {
+ name: '@openai/agents',
+ versions: ['>=0.7.0'],
+ },
+ {
+ name: '@openai/agents-core',
+ versions: ['>=0.7.0'],
+ },
+ {
+ name: '@openai/agents-openai',
+ versions: ['>=0.7.0'],
+ },
+ ],
passport: [
{
name: 'express',
diff --git a/packages/dd-trace/test/plugins/outbound.spec.js b/packages/dd-trace/test/plugins/outbound.spec.js
index 2edd27f354..1ef24c107b 100644
--- a/packages/dd-trace/test/plugins/outbound.spec.js
+++ b/packages/dd-trace/test/plugins/outbound.spec.js
@@ -50,6 +50,44 @@ describe('OuboundPlugin', () => {
sinon.assert.notCalled(getRemapStub)
})
+ it('should not recompute peer service after its source was set', () => {
+ computePeerServiceStub.value({ spanComputePeerService: true })
+ const tags = {
+ 'peer.service': 'mypeerservice',
+ '_dd.peer.service.source': 'out.host',
+ }
+ instance.tagPeerService({ context: () => ({ getTags: () => tags }) })
+
+ sinon.assert.notCalled(getPeerServiceStub)
+ sinon.assert.notCalled(getRemapStub)
+ })
+
+ it('should not recompute peer service when only its source was set', () => {
+ computePeerServiceStub.value({ spanComputePeerService: true })
+ const tags = {
+ '_dd.peer.service.source': 'out.host',
+ }
+ instance.tagPeerService({ context: () => ({ getTags: () => tags }) })
+
+ sinon.assert.notCalled(getPeerServiceStub)
+ sinon.assert.notCalled(getRemapStub)
+ })
+
+ it('should compute peer service when only its value was set', () => {
+ computePeerServiceStub.value({ spanComputePeerService: true })
+ getPeerServiceStub.returns({
+ 'peer.service': 'mypeerservice',
+ '_dd.peer.service.source': 'peer.service',
+ })
+ const tags = {
+ 'peer.service': 'mypeerservice',
+ }
+ instance.tagPeerService({ context: () => ({ getTags: () => tags }), addTags: () => {} })
+
+ sinon.assert.called(getPeerServiceStub)
+ sinon.assert.called(getRemapStub)
+ })
+
it('should do nothing when disabled', () => {
computePeerServiceStub.value({ spanComputePeerService: false })
instance.tagPeerService({ context: () => ({ _tags: {}, getTags () { return this._tags } }), addTags: () => {} })
diff --git a/packages/dd-trace/test/plugins/versions/package.json b/packages/dd-trace/test/plugins/versions/package.json
index f4f5d498fc..bae164ff84 100644
--- a/packages/dd-trace/test/plugins/versions/package.json
+++ b/packages/dd-trace/test/plugins/versions/package.json
@@ -68,6 +68,7 @@
"@node-redis/client": "1.0.6",
"@openai/agents": "0.13.5",
"@openai/agents-core": "0.13.5",
+ "@openai/agents-openai": "0.13.5",
"@openfeature/core": "1.11.0",
"@openfeature/server-sdk": "1.22.0",
"@opensearch-project/opensearch": "3.6.0",
diff --git a/packages/dd-trace/test/setup/helpers/plugin-test-helpers/index.js b/packages/dd-trace/test/setup/helpers/plugin-test-helpers/index.js
index 199ce0cdc8..f365520efa 100644
--- a/packages/dd-trace/test/setup/helpers/plugin-test-helpers/index.js
+++ b/packages/dd-trace/test/setup/helpers/plugin-test-helpers/index.js
@@ -8,7 +8,7 @@ function createIntegrationTestSuite (pluginName, packageName, options, testCallb
describe('Plugin', () => {
describe(pluginName, () => {
withVersions(pluginName, packageName, version => {
- const meta = { agent, tracer: null, mod: null }
+ const meta = { agent, tracer: null, mod: null, version }
describe('without configuration', () => {
before(async () => {
diff --git a/packages/dd-trace/test/setup/mocha.js b/packages/dd-trace/test/setup/mocha.js
index 2b7af5263f..f7888d5f95 100644
--- a/packages/dd-trace/test/setup/mocha.js
+++ b/packages/dd-trace/test/setup/mocha.js
@@ -182,6 +182,14 @@ function withNamingSchema (
})
}
+/**
+ * @param {() => import('../../src/proxy')} tracer
+ * @param {string} pluginName
+ * @param {((callback: (error?: Error) => void) => unknown) | (() => Promise)} spanGenerationFn
+ * @param {string | (() => string)} service
+ * @param {string} serviceSource
+ * @param {{ component?: string, desc?: string, resource?: string | (() => string) }} [opts]
+ */
function withPeerService (tracer, pluginName, spanGenerationFn, service, serviceSource, opts = {}) {
describe('peer service computation' + (opts.desc ? ` ${opts.desc}` : ''), function () {
this.timeout(10000)
@@ -199,32 +207,72 @@ function withPeerService (tracer, pluginName, spanGenerationFn, service, service
})
it('should compute peer service', async () => {
- const useCallback = spanGenerationFn.length === 1
- const spanGenerationPromise = useCallback
- ? new Promise(/** @type {() => void} */ (resolve, reject) => {
- const result = spanGenerationFn((err) => err ? reject(err) : resolve())
- // Some callback based methods are a mixture of callback and promise,
- // depending on the module version. Await the promises as well.
- if (util.types.isPromise(result)) {
- result.then?.(resolve, reject)
+ const currentTracer = global._ddtrace
+ const parentSpan = currentTracer.startSpan('peer-service.test')
+ const traceId = BigInt(parentSpan.context().toTraceId())
+ const component = opts.component ?? pluginName
+ let traceAssertion
+
+ /**
+ * @param {Array }>>} traces
+ */
+ function assertPeerServiceSpan (traces) {
+ const expectedService = typeof service === 'function' ? service() : service
+ const expectedResource = typeof opts.resource === 'function' ? opts.resource() : opts.resource
+
+ for (const trace of traces) {
+ for (const span of trace) {
+ if (
+ span.trace_id === traceId &&
+ span.meta.component === component &&
+ (expectedResource === undefined || span.resource === expectedResource) &&
+ span.meta['peer.service'] === expectedService &&
+ span.meta['_dd.peer.service.source'] === serviceSource
+ ) {
+ return
+ }
}
- })
- : spanGenerationFn()
+ }
- assert.strictEqual(
- typeof spanGenerationPromise?.then, 'function',
- 'spanGenerationFn should return a promise in case no callback is defined. Received: ' +
- util.inspect(spanGenerationPromise, { depth: 1 }),
- )
+ assert.fail(
+ `No ${component} span in trace ${traceId} had peer.service=${expectedService} and ` +
+ `_dd.peer.service.source=${serviceSource}.\n\nCandidate Traces:\n${util.inspect(traces, { depth: null })}`
+ )
+ }
- await Promise.all([
- getAgent().assertSomeTraces(traces => {
- const span = traces[0][0]
- assert.strictEqual(span.meta['peer.service'], typeof service === 'function' ? service() : service)
- assert.strictEqual(span.meta['_dd.peer.service.source'], serviceSource)
- }),
- spanGenerationPromise,
- ])
+ try {
+ traceAssertion = getAgent().assertSomeTraces(assertPeerServiceSpan, { timeoutMs: 9000 })
+ const useCallback = spanGenerationFn.length === 1
+ const spanGenerationPromise = currentTracer.scope().activate(parentSpan, () => {
+ return useCallback
+ ? new Promise(/** @type {() => void} */ (resolve, reject) => {
+ const result = spanGenerationFn((error) => error ? reject(error) : resolve())
+ // Some callback based methods are a mixture of callback and promise,
+ // depending on the module version. Await the promises as well.
+ if (util.types.isPromise(result)) {
+ result.then?.(resolve, reject)
+ }
+ })
+ : spanGenerationFn()
+ })
+
+ assert.strictEqual(
+ typeof spanGenerationPromise?.then, 'function',
+ 'spanGenerationFn should return a promise in case no callback is defined. Received: ' +
+ util.inspect(spanGenerationPromise, { depth: 1 }),
+ )
+
+ await Promise.all([
+ traceAssertion,
+ (async () => {
+ await spanGenerationPromise
+ parentSpan.finish()
+ })(),
+ ])
+ } finally {
+ parentSpan.finish()
+ traceAssertion?.cancel()
+ }
})
})
}
diff --git a/packages/dd-trace/test/span_processor.spec.js b/packages/dd-trace/test/span_processor.spec.js
index 6e15476db1..06433980cd 100644
--- a/packages/dd-trace/test/span_processor.spec.js
+++ b/packages/dd-trace/test/span_processor.spec.js
@@ -235,7 +235,7 @@ describe('SpanProcessor', () => {
sinon.assert.calledWith(spanFormat.getCall(3), finishedSpan, false, processor._processTags)
})
- it('should add APM disabled marker to first span in a chunk when APM tracing is disabled', () => {
+ it('should add APM disabled marker to every span in a chunk when APM tracing is disabled', () => {
config.apmTracingEnabled = false
config.flushMinSpans = 2
const processor = new SpanProcessor(exporter, prioritySampler, config)
@@ -249,7 +249,7 @@ describe('SpanProcessor', () => {
processor.process(finishedSpan)
assert.strictEqual(firstFormatted.metrics[APM_TRACING_ENABLED_KEY], 0)
- assert.ok(!Object.hasOwn(secondFormatted.metrics, APM_TRACING_ENABLED_KEY))
+ assert.strictEqual(secondFormatted.metrics[APM_TRACING_ENABLED_KEY], 0)
sinon.assert.calledWith(exporter.export, [firstFormatted, secondFormatted])
})
diff --git a/supported_versions_output.json b/supported_versions_output.json
index 1c2bee0a62..40565c0db7 100644
--- a/supported_versions_output.json
+++ b/supported_versions_output.json
@@ -3,21 +3,21 @@
"dependency": "@anthropic-ai/claude-agent-sdk",
"integration": "claude-agent-sdk",
"minimum_tracer_supported": "0.2.113",
- "max_tracer_supported": "0.3.202",
+ "max_tracer_supported": "0.3.215",
"auto-instrumented": "True"
},
{
"dependency": "@anthropic-ai/sdk",
"integration": "anthropic",
"minimum_tracer_supported": "0.14.0",
- "max_tracer_supported": "0.105.0",
+ "max_tracer_supported": "0.112.3",
"auto-instrumented": "True"
},
{
"dependency": "@apollo/gateway",
"integration": "apollo",
"minimum_tracer_supported": "2.3.0",
- "max_tracer_supported": "2.14.0",
+ "max_tracer_supported": "2.14.2",
"auto-instrumented": "True"
},
{
@@ -31,7 +31,7 @@
"dependency": "@aws/durable-execution-sdk-js",
"integration": "aws-durable-execution-sdk-js",
"minimum_tracer_supported": "1.1.0",
- "max_tracer_supported": "2.0.0",
+ "max_tracer_supported": "2.2.0",
"auto-instrumented": "True"
},
{
@@ -52,7 +52,7 @@
"dependency": "@azure/functions",
"integration": "azure-functions",
"minimum_tracer_supported": "4.0.0",
- "max_tracer_supported": "4.16.1",
+ "max_tracer_supported": "4.16.2",
"auto-instrumented": "True"
},
{
@@ -66,28 +66,28 @@
"dependency": "@confluentinc/kafka-javascript",
"integration": "confluentinc-kafka-javascript",
"minimum_tracer_supported": "1.0.0",
- "max_tracer_supported": "1.9.1",
+ "max_tracer_supported": "1.10.0",
"auto-instrumented": "True"
},
{
"dependency": "@cucumber/cucumber",
"integration": "cucumber",
"minimum_tracer_supported": "7.0.0",
- "max_tracer_supported": "13.0.0",
+ "max_tracer_supported": "13.2.0",
"auto-instrumented": "True"
},
{
"dependency": "@elastic/elasticsearch",
"integration": "elasticsearch",
"minimum_tracer_supported": "5.6.16",
- "max_tracer_supported": "9.4.0",
+ "max_tracer_supported": "9.4.2",
"auto-instrumented": "True"
},
{
"dependency": "@elastic/transport",
"integration": "elasticsearch",
"minimum_tracer_supported": "8.0.0",
- "max_tracer_supported": "9.3.5",
+ "max_tracer_supported": "9.3.7",
"auto-instrumented": "True"
},
{
@@ -108,14 +108,14 @@
"dependency": "@google/genai",
"integration": "google-genai",
"minimum_tracer_supported": "1.19.0",
- "max_tracer_supported": "2.9.0",
+ "max_tracer_supported": "2.12.0",
"auto-instrumented": "True"
},
{
"dependency": "@grpc/grpc-js",
"integration": "grpc",
"minimum_tracer_supported": "1.0.3",
- "max_tracer_supported": "1.14.3",
+ "max_tracer_supported": "1.14.4",
"auto-instrumented": "True"
},
{
@@ -157,21 +157,21 @@
"dependency": "@koa/router",
"integration": "koa",
"minimum_tracer_supported": "8.0.0",
- "max_tracer_supported": "15.5.0",
+ "max_tracer_supported": "15.7.0",
"auto-instrumented": "True"
},
{
"dependency": "@langchain/core",
"integration": "langchain",
"minimum_tracer_supported": "0.1.0",
- "max_tracer_supported": "1.2.0",
+ "max_tracer_supported": "1.2.3",
"auto-instrumented": "True"
},
{
"dependency": "@langchain/langgraph",
"integration": "langgraph",
"minimum_tracer_supported": "1.1.2",
- "max_tracer_supported": "1.4.4",
+ "max_tracer_supported": "1.4.8",
"auto-instrumented": "True"
},
{
@@ -202,6 +202,13 @@
"max_tracer_supported": "1.0.6",
"auto-instrumented": "True"
},
+ {
+ "dependency": "@openai/agents",
+ "integration": "openai-agents",
+ "minimum_tracer_supported": "0.7.0",
+ "max_tracer_supported": "0.13.5",
+ "auto-instrumented": "True"
+ },
{
"dependency": "@opensearch-project/opensearch",
"integration": "opensearch",
@@ -220,42 +227,35 @@
"dependency": "@redis/client",
"integration": "redis",
"minimum_tracer_supported": "1.1.0",
- "max_tracer_supported": "5.12.1",
+ "max_tracer_supported": "6.1.0",
"auto-instrumented": "True"
},
{
"dependency": "@smithy/core",
"integration": "aws-sdk",
"minimum_tracer_supported": "3.24.0",
- "max_tracer_supported": "3.25.1",
+ "max_tracer_supported": "3.29.5",
"auto-instrumented": "True"
},
{
"dependency": "@smithy/smithy-client",
"integration": "aws-sdk",
"minimum_tracer_supported": "1.0.3",
- "max_tracer_supported": "4.14.1",
- "auto-instrumented": "True"
- },
- {
- "dependency": "@vitest/runner",
- "integration": "vitest",
- "minimum_tracer_supported": "1.6.0",
- "max_tracer_supported": "4.1.9",
+ "max_tracer_supported": "4.14.10",
"auto-instrumented": "True"
},
{
"dependency": "aerospike",
"integration": "aerospike",
"minimum_tracer_supported": "4.0.0",
- "max_tracer_supported": "6.7.0",
+ "max_tracer_supported": "6.7.1",
"auto-instrumented": "True"
},
{
"dependency": "ai",
"integration": "ai",
"minimum_tracer_supported": "4.0.0",
- "max_tracer_supported": "6.0.208",
+ "max_tracer_supported": "7.0.31",
"auto-instrumented": "True"
},
{
@@ -290,7 +290,7 @@
"dependency": "bullmq",
"integration": "bullmq",
"minimum_tracer_supported": "5.66.0",
- "max_tracer_supported": "5.79.1",
+ "max_tracer_supported": "5.80.9",
"auto-instrumented": "True"
},
{
@@ -310,7 +310,7 @@
{
"dependency": "child_process",
"integration": "child_process",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
@@ -325,7 +325,7 @@
"dependency": "couchbase",
"integration": "couchbase",
"minimum_tracer_supported": "3.0.7",
- "max_tracer_supported": "4.7.0",
+ "max_tracer_supported": "4.7.1",
"auto-instrumented": "True"
},
{
@@ -338,7 +338,7 @@
{
"dependency": "dns",
"integration": "dns",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
@@ -346,7 +346,7 @@
"dependency": "durable-functions",
"integration": "azure-durable-functions",
"minimum_tracer_supported": "3.0.0",
- "max_tracer_supported": "3.3.1",
+ "max_tracer_supported": "3.5.0",
"auto-instrumented": "True"
},
{
@@ -374,7 +374,7 @@
"dependency": "fastify",
"integration": "fastify",
"minimum_tracer_supported": "1.0.0",
- "max_tracer_supported": "5.8.5",
+ "max_tracer_supported": "5.10.0",
"auto-instrumented": "True"
},
{
@@ -387,7 +387,7 @@
{
"dependency": "fs",
"integration": "fs",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
@@ -395,7 +395,7 @@
"dependency": "graphql",
"integration": "graphql",
"minimum_tracer_supported": "0.10.0",
- "max_tracer_supported": "16.14.0",
+ "max_tracer_supported": "16.14.2",
"auto-instrumented": "True"
},
{
@@ -409,27 +409,27 @@
"dependency": "hono",
"integration": "hono",
"minimum_tracer_supported": "4.0.0",
- "max_tracer_supported": "4.12.19",
+ "max_tracer_supported": "4.12.30",
"auto-instrumented": "True"
},
{
"dependency": "http",
"integration": "http",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
{
"dependency": "http2",
"integration": "http2",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
{
"dependency": "https",
"integration": "http",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
@@ -437,7 +437,7 @@
"dependency": "ioredis",
"integration": "ioredis",
"minimum_tracer_supported": "2.0.0",
- "max_tracer_supported": "5.10.1",
+ "max_tracer_supported": "5.11.1",
"auto-instrumented": "True"
},
{
@@ -500,7 +500,7 @@
"dependency": "koa",
"integration": "koa",
"minimum_tracer_supported": "2.0.0",
- "max_tracer_supported": "3.2.0",
+ "max_tracer_supported": "3.2.1",
"auto-instrumented": "True"
},
{
@@ -524,6 +524,13 @@
"max_tracer_supported": "2.2.2",
"auto-instrumented": "True"
},
+ {
+ "dependency": "mercurius",
+ "integration": "graphql",
+ "minimum_tracer_supported": "13.0.0",
+ "max_tracer_supported": "16.10.0",
+ "auto-instrumented": "True"
+ },
{
"dependency": "microgateway-core",
"integration": "microgateway-core",
@@ -556,7 +563,7 @@
"dependency": "mongodb",
"integration": "mongodb-core",
"minimum_tracer_supported": "3.3.0",
- "max_tracer_supported": "7.2.0",
+ "max_tracer_supported": "7.5.0",
"auto-instrumented": "True"
},
{
@@ -570,7 +577,7 @@
"dependency": "mongoose",
"integration": "mongoose",
"minimum_tracer_supported": "4.6.4",
- "max_tracer_supported": "9.6.2",
+ "max_tracer_supported": "9.7.4",
"auto-instrumented": "True"
},
{
@@ -584,13 +591,13 @@
"dependency": "mysql2",
"integration": "mysql2",
"minimum_tracer_supported": "1.0.0",
- "max_tracer_supported": "3.22.3",
+ "max_tracer_supported": "3.23.0",
"auto-instrumented": "True"
},
{
"dependency": "net",
"integration": "net",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
@@ -604,42 +611,42 @@
{
"dependency": "node:dns",
"integration": "dns",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
{
"dependency": "node:fs",
"integration": "fs",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
{
"dependency": "node:http",
"integration": "http",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
{
"dependency": "node:http2",
"integration": "http2",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
{
"dependency": "node:https",
"integration": "http",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
{
"dependency": "node:net",
"integration": "net",
- "minimum_tracer_supported": "18.0.0",
+ "minimum_tracer_supported": "22.0.0",
"max_tracer_supported": "26.4.0",
"auto-instrumented": "True"
},
@@ -654,21 +661,21 @@
"dependency": "openai",
"integration": "openai",
"minimum_tracer_supported": "3.0.0",
- "max_tracer_supported": "6.44.0",
+ "max_tracer_supported": "6.48.0",
"auto-instrumented": "True"
},
{
"dependency": "oracledb",
"integration": "oracledb",
"minimum_tracer_supported": "5.0.0",
- "max_tracer_supported": "6.10.0",
+ "max_tracer_supported": "7.0.1",
"auto-instrumented": "True"
},
{
"dependency": "pg",
"integration": "pg",
"minimum_tracer_supported": "8.0.3",
- "max_tracer_supported": "8.21.0",
+ "max_tracer_supported": "8.22.0",
"auto-instrumented": "True"
},
{
@@ -696,14 +703,14 @@
"dependency": "protobufjs",
"integration": "protobufjs",
"minimum_tracer_supported": "6.8.0",
- "max_tracer_supported": "8.6.4",
+ "max_tracer_supported": "8.7.1",
"auto-instrumented": "True"
},
{
"dependency": "redis",
"integration": "redis",
"minimum_tracer_supported": "0.12.0",
- "max_tracer_supported": "5.12.1",
+ "max_tracer_supported": "6.1.0",
"auto-instrumented": "True"
},
{
@@ -738,14 +745,14 @@
"dependency": "sharedb",
"integration": "sharedb",
"minimum_tracer_supported": "1.0.0",
- "max_tracer_supported": "5.2.2",
+ "max_tracer_supported": "6.0.1",
"auto-instrumented": "True"
},
{
"dependency": "tedious",
"integration": "tedious",
"minimum_tracer_supported": "1.0.0",
- "max_tracer_supported": "19.2.1",
+ "max_tracer_supported": "20.0.0",
"auto-instrumented": "True"
},
{
@@ -759,7 +766,7 @@
"dependency": "undici",
"integration": "undici",
"minimum_tracer_supported": "4.4.1",
- "max_tracer_supported": "8.3.0",
+ "max_tracer_supported": "8.7.0",
"auto-instrumented": "True"
},
{
@@ -787,7 +794,7 @@
"dependency": "ws",
"integration": "ws",
"minimum_tracer_supported": "8.0.0",
- "max_tracer_supported": "8.20.1",
+ "max_tracer_supported": "8.21.1",
"auto-instrumented": "True"
}
]
diff --git a/supported_versions_table.csv b/supported_versions_table.csv
index d23d6c9904..5cd8e5ca78 100644
--- a/supported_versions_table.csv
+++ b/supported_versions_table.csv
@@ -1,67 +1,67 @@
dependency,integration,minimum_tracer_supported,max_tracer_supported,auto-instrumented
-@anthropic-ai/claude-agent-sdk,claude-agent-sdk,0.2.113,0.3.202,True
-@anthropic-ai/sdk,anthropic,0.14.0,0.105.0,True
-@apollo/gateway,apollo,2.3.0,2.14.0,True
+@anthropic-ai/claude-agent-sdk,claude-agent-sdk,0.2.113,0.3.215,True
+@anthropic-ai/sdk,anthropic,0.14.0,0.112.3,True
+@apollo/gateway,apollo,2.3.0,2.14.2,True
@aws-sdk/smithy-client,aws-sdk,3.0.0,3.374.0,True
-@aws/durable-execution-sdk-js,aws-durable-execution-sdk-js,1.1.0,2.0.0,True
+@aws/durable-execution-sdk-js,aws-durable-execution-sdk-js,1.1.0,2.2.0,True
@azure/cosmos,azure-cosmos,4.4.1,4.9.3,True
@azure/event-hubs,azure-event-hubs,6.0.0,6.0.4,True
-@azure/functions,azure-functions,4.0.0,4.16.1,True
+@azure/functions,azure-functions,4.0.0,4.16.2,True
@azure/service-bus,azure-service-bus,7.9.2,7.9.5,True
-@confluentinc/kafka-javascript,confluentinc-kafka-javascript,1.0.0,1.9.1,True
-@cucumber/cucumber,cucumber,7.0.0,13.0.0,True
-@elastic/elasticsearch,elasticsearch,5.6.16,9.4.0,True
-@elastic/transport,elasticsearch,8.0.0,9.3.5,True
+@confluentinc/kafka-javascript,confluentinc-kafka-javascript,1.0.0,1.10.0,True
+@cucumber/cucumber,cucumber,7.0.0,13.2.0,True
+@elastic/elasticsearch,elasticsearch,5.6.16,9.4.2,True
+@elastic/transport,elasticsearch,8.0.0,9.3.7,True
@google-cloud/pubsub,google-cloud-pubsub,1.2.0,5.3.1,True
@google-cloud/vertexai,google-cloud-vertexai,1.0.0,1.12.0,True
-@google/genai,google-genai,1.19.0,2.9.0,True
-@grpc/grpc-js,grpc,1.0.3,1.14.3,True
+@google/genai,google-genai,1.19.0,2.12.0,True
+@grpc/grpc-js,grpc,1.0.3,1.14.4,True
@hapi/hapi,hapi,17.9.0,21.4.9,True
@happy-dom/jest-environment,jest,10.0.0,20.10.6,True
@jest/core,jest,28.0.0,30.4.2,True
@jest/test-sequencer,jest,28.0.0,30.4.1,True
@jest/transform,jest,28.0.0,30.4.1,True
-@koa/router,koa,8.0.0,15.5.0,True
-@langchain/core,langchain,0.1.0,1.2.0,True
-@langchain/langgraph,langgraph,1.1.2,1.4.4,True
+@koa/router,koa,8.0.0,15.7.0,True
+@langchain/core,langchain,0.1.0,1.2.3,True
+@langchain/langgraph,langgraph,1.1.2,1.4.8,True
@modelcontextprotocol/sdk,modelcontextprotocol-sdk,1.27.1,1.29.0,True
@nats-io/nats-core,nats,3.0.0,3.4.0,True
@nats-io/transport-node,nats,3.0.0,3.4.0,True
@node-redis/client,redis,1.0.0,1.0.6,True
+@openai/agents,openai-agents,0.7.0,0.13.5,True
@opensearch-project/opensearch,opensearch,1.0.0,3.6.0,True
@prisma/client,prisma,6.1.0,7.8.0,True
-@redis/client,redis,1.1.0,5.12.1,True
-@smithy/core,aws-sdk,3.24.0,3.25.1,True
-@smithy/smithy-client,aws-sdk,1.0.3,4.14.1,True
-@vitest/runner,vitest,1.6.0,4.1.9,True
-aerospike,aerospike,4.0.0,6.7.0,True
-ai,ai,4.0.0,6.0.208,True
+@redis/client,redis,1.1.0,6.1.0,True
+@smithy/core,aws-sdk,3.24.0,3.29.5,True
+@smithy/smithy-client,aws-sdk,1.0.3,4.14.10,True
+aerospike,aerospike,4.0.0,6.7.1,True
+ai,ai,4.0.0,7.0.31,True
amqp10,amqp10,3.0.0,3.6.0,True
amqplib,amqplib,0.5.0,2.0.1,True
avsc,avsc,5.0.0,5.7.9,True
aws-sdk,aws-sdk,2.1.35,2.1693.0,True
-bullmq,bullmq,5.66.0,5.79.1,True
+bullmq,bullmq,5.66.0,5.80.9,True
bunyan,bunyan,1.0.0,2.0.5,True
cassandra-driver,cassandra-driver,3.0.0,4.9.0,True
-child_process,child_process,18.0.0,26.4.0,True
+child_process,child_process,22.0.0,26.4.0,True
connect,connect,2.2.2,3.7.0,True
-couchbase,couchbase,3.0.7,4.7.0,True
+couchbase,couchbase,3.0.7,4.7.1,True
cypress,cypress,12.0.0,15.16.0,True
-dns,dns,18.0.0,26.4.0,True
-durable-functions,azure-durable-functions,3.0.0,3.3.1,True
+dns,dns,22.0.0,26.4.0,True
+durable-functions,azure-durable-functions,3.0.0,3.5.0,True
elasticsearch,elasticsearch,10.0.0,16.7.3,True
electron,electron,37.0.0,42.1.0,True
express,express,4.0.0,5.2.1,True
-fastify,fastify,1.0.0,5.8.5,True
+fastify,fastify,1.0.0,5.10.0,True
find-my-way,find-my-way,1.0.0,9.6.0,True
-fs,fs,18.0.0,26.4.0,True
-graphql,graphql,0.10.0,16.14.0,True
+fs,fs,22.0.0,26.4.0,True
+graphql,graphql,0.10.0,16.14.2,True
hapi,hapi,16.0.0,18.1.0,True
-hono,hono,4.0.0,4.12.19,True
-http,http,18.0.0,26.4.0,True
-http2,http2,18.0.0,26.4.0,True
-https,http,18.0.0,26.4.0,True
-ioredis,ioredis,2.0.0,5.10.1,True
+hono,hono,4.0.0,4.12.30,True
+http,http,22.0.0,26.4.0,True
+http2,http2,22.0.0,26.4.0,True
+https,http,22.0.0,26.4.0,True
+ioredis,ioredis,2.0.0,5.11.1,True
iovalkey,iovalkey,0.0.1,0.3.3,True
jest-circus,jest,28.0.0,30.4.2,True
jest-config,jest,28.0.0,30.4.2,True
@@ -70,45 +70,46 @@ jest-environment-node,jest,28.0.0,30.4.1,True
jest-runtime,jest,28.0.0,30.4.2,True
jest-worker,jest,28.0.0,30.4.1,True
kafkajs,kafkajs,1.4.0,2.2.4,True
-koa,koa,2.0.0,3.2.0,True
+koa,koa,2.0.0,3.2.1,True
koa-router,koa,7.0.0,14.0.0,True
mariadb,mariadb,2.0.4,3.4.5,True
memcached,memcached,2.2.0,2.2.2,True
+mercurius,graphql,13.0.0,16.10.0,True
microgateway-core,microgateway-core,2.1.0,3.3.7,True
mocha,mocha,8.0.0,11.7.6,True
mocha-each,mocha,2.0.1,2.0.1,True
moleculer,moleculer,0.14.0,0.15.0,True
-mongodb,mongodb-core,3.3.0,7.2.0,True
+mongodb,mongodb-core,3.3.0,7.5.0,True
mongodb-core,mongodb-core,2.0.0,3.2.7,True
-mongoose,mongoose,4.6.4,9.6.2,True
+mongoose,mongoose,4.6.4,9.7.4,True
mysql,mysql,2.0.0,2.18.1,True
-mysql2,mysql2,1.0.0,3.22.3,True
-net,net,18.0.0,26.4.0,True
+mysql2,mysql2,1.0.0,3.23.0,True
+net,net,22.0.0,26.4.0,True
next,next,10.2.0,16.2.6,True
-node:dns,dns,18.0.0,26.4.0,True
-node:fs,fs,18.0.0,26.4.0,True
-node:http,http,18.0.0,26.4.0,True
-node:http2,http2,18.0.0,26.4.0,True
-node:https,http,18.0.0,26.4.0,True
-node:net,net,18.0.0,26.4.0,True
+node:dns,dns,22.0.0,26.4.0,True
+node:fs,fs,22.0.0,26.4.0,True
+node:http,http,22.0.0,26.4.0,True
+node:http2,http2,22.0.0,26.4.0,True
+node:https,http,22.0.0,26.4.0,True
+node:net,net,22.0.0,26.4.0,True
nyc,nyc,17.0.0,18.0.0,True
-openai,openai,3.0.0,6.44.0,True
-oracledb,oracledb,5.0.0,6.10.0,True
-pg,pg,8.0.3,8.21.0,True
+openai,openai,3.0.0,6.48.0,True
+oracledb,oracledb,5.0.0,7.0.1,True
+pg,pg,8.0.3,8.22.0,True
pino,pino,2.0.0,10.3.1,True
pino-pretty,pino,1.0.0,13.1.3,True
playwright,playwright,1.38.0,1.61.0,True
-protobufjs,protobufjs,6.8.0,8.6.4,True
-redis,redis,0.12.0,5.12.1,True
+protobufjs,protobufjs,6.8.0,8.7.1,True
+redis,redis,0.12.0,6.1.0,True
restify,restify,3.0.0,11.1.0,True
rhea,rhea,1.0.0,3.0.5,True
router,router,1.0.0,2.2.0,True
selenium-webdriver,selenium,4.11.0,4.44.0,True
-sharedb,sharedb,1.0.0,5.2.2,True
-tedious,tedious,1.0.0,19.2.1,True
+sharedb,sharedb,1.0.0,6.0.1,True
+tedious,tedious,1.0.0,20.0.0,True
tinypool,vitest,0.8.0,2.1.0,True
-undici,undici,4.4.1,8.3.0,True
+undici,undici,4.4.1,8.7.0,True
vitest,vitest,1.6.0,4.1.9,True
winston,winston,1.0.0,3.19.0,True
workerpool,mocha,6.0.0,10.0.2,True
-ws,ws,8.0.0,8.20.1,True
+ws,ws,8.0.0,8.21.1,True
diff --git a/yarn.lock b/yarn.lock
index 9bf9e895cd..e2cddd5d37 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1214,10 +1214,10 @@
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz#72da0da48d72b1e87831b9c0308931d3f4669027"
integrity sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==
-"@vercel/nft@^0.29.4":
- version "0.29.4"
- resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.29.4.tgz#e56b07d193776bcf67b31ac4da065ceb8e8d362e"
- integrity sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==
+"@vercel/nft@^1.10.2":
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-1.10.2.tgz#01aefaddc079a19bfe7d499872b2d6dd3cd729e1"
+ integrity sha512-w+WyX5Ulmj4dtTZrxaulqrjaLZHSbnPzx75SJsTNYmotKsqn1JlLnDJa+lz5hn90HJofhl/2MAtw0mCrgM3qYw==
dependencies:
"@mapbox/node-pre-gyp" "^2.0.0"
"@rollup/pluginutils" "^5.1.3"
@@ -1226,7 +1226,7 @@
async-sema "^3.1.1"
bindings "^1.4.0"
estree-walker "2.0.2"
- glob "^10.4.5"
+ glob "^13.0.0"
graceful-fs "^4.2.9"
node-gyp-build "^4.2.2"
picomatch "^4.0.2"
@@ -1381,7 +1381,7 @@ balanced-match@^4.0.2:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a"
integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==
-baseline-browser-mapping@^2.10.44, baseline-browser-mapping@^2.9.0:
+baseline-browser-mapping@^2.10.44:
version "2.11.1"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.1.tgz#390b4714558634093df77add4acca0c5c0c1605e"
integrity sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==
@@ -1571,7 +1571,7 @@ camelcase@^6.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
-caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001806:
+caniuse-lite@^1.0.30001806:
version "1.0.30001806"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz#1bc8e502b723fa393455dfbedd5ccec0c29bb74e"
integrity sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==
@@ -1851,7 +1851,7 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
-electron-to-chromium@^1.5.263, electron-to-chromium@^1.5.393:
+electron-to-chromium@^1.5.393:
version "1.5.395"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.395.tgz#b82e88344bdc9e5c00d280140e7ca278c7325661"
integrity sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA==
@@ -2451,7 +2451,7 @@ glob@^10.4.5:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
-glob@^13.0.3, glob@^13.0.6:
+glob@^13.0.0, glob@^13.0.3, glob@^13.0.6:
version "13.0.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d"
integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==
@@ -3189,7 +3189,7 @@ node-preload@^0.2.1:
dependencies:
process-on-spawn "^1.0.0"
-node-releases@^2.0.27, node-releases@^2.0.51:
+node-releases@^2.0.51:
version "2.0.51"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.51.tgz#cdc08433577f5b32ad01694481726e22eeb54aef"
integrity sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==
@@ -4204,7 +4204,7 @@ unrs-resolver@^1.9.2:
"@unrs/resolver-binding-win32-ia32-msvc" "1.12.2"
"@unrs/resolver-binding-win32-x64-msvc" "1.12.2"
-update-browserslist-db@^1.2.0, update-browserslist-db@^1.2.3:
+update-browserslist-db@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==