Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

if (process.env.USE_ORCHESTRION) {
Sentry.experimentalUseDiagnosticsChannelInjection();
}

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

if (process.env.USE_ORCHESTRION) {
Sentry.experimentalUseDiagnosticsChannelInjection();
}

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
import { cleanupChildProcesses, createEsmAndCjsTests, createEsmTests } from '../../../../utils/runner';

describe.each([
['6', '^6.0.0'],
['7', '7.0.0-beta.179'],
])('Vercel AI integration (version %s)', (version, vercelAiVersion) => {
['6', {}, '^6.0.0'],
['6', { USE_ORCHESTRION: 'true' }, '^6.0.0'],
['7', {}, '7.0.0-beta.179'],
['7', { USE_ORCHESTRION: 'true' }, '7.0.0-beta.179'],
])('Vercel AI integration (version %s, env %o)', (version, env: Record<string, string>, vercelAiVersion) => {
afterAll(() => {
cleanupChildProcesses();
});
Expand All @@ -36,9 +38,12 @@
const nodeVersion = NODE_VERSION.major;
const failsOnCjs = version === '7' && nodeVersion === 18;

// v6 is instrumented via the OTel processor, v7 via the `ai:telemetry` tracing-channel subscriber,
// so the span origin differs by version.
const expectedOrigin = version === '7' ? 'auto.vercelai.channel' : 'auto.vercelai.otel';
const useOrchestrion = env.USE_ORCHESTRION === 'true';
const usesChannels = version === '7' || useOrchestrion;

// in v7 and orchestrion mode, we use the channel-based integration
// else, we use the OTel processor
const expectedOrigin = usesChannels ? 'auto.vercelai.channel' : 'auto.vercelai.otel';

// We only run this in ESM and CJS to verify full support
// Other suites we only run in ESM to simplify the test setup
Expand All @@ -49,6 +54,7 @@
(createRunner, test) => {
test('creates ai spans for dataCollection defaults', async () => {
await createRunner()
.withEnv(env)
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -172,8 +178,9 @@
'scenario.mjs',
'instrument.mjs',
(createRunner, test) => {
test('creates ai spans when dataCollection.genAi has inputs and outputs disabled', async () => {

Check failure on line 181 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates ai spans when dataCollection.genAi has inputs and outputs disabled

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:181:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:176:3

Check failure on line 181 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates ai spans when dataCollection.genAi has inputs and outputs disabled

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:181:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:176:3

Check failure on line 181 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates ai spans when dataCollection.genAi has inputs and outputs disabled

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:181:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:176:3
await createRunner()
.withEnv(env)
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -233,10 +240,10 @@
// On v6, vercel AI natively defaults to recording inputs and outputs by default when telemetry is enabled
// On v7, we do not have access to this, so this defaults to false in this case
expect(secondInvokeAgentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toEqual(
version === '6' ? '[{"role":"user","content":"Where is the second span?"}]' : undefined,
!usesChannels ? '[{"role":"user","content":"Where is the second span?"}]' : undefined,
);
expect(secondInvokeAgentSpan.attributes?.[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toEqual(
version === '6'
!usesChannels
? '[{"role":"assistant","parts":[{"type":"text","content":"Second span here!"}],"finish_reason":"stop"}]'
: undefined,
);
Expand Down Expand Up @@ -295,11 +302,12 @@
'scenario-error-in-tool.mjs',
'instrument.mjs',
(createRunner, test) => {
test('captures error in tool', async () => {

Check failure on line 305 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > captures error in tool

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:305:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:300:3

Check failure on line 305 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > captures error in tool

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:305:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:300:3

Check failure on line 305 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > captures error in tool

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:305:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:300:3
let transactionEvent: Event | undefined;
let errorEvent: Event | undefined;

await createRunner()
.withEnv(env)
.expect({
transaction: transaction => {
transactionEvent = transaction;
Expand Down Expand Up @@ -367,8 +375,9 @@
'scenario.mjs',
'instrument.mjs',
(createRunner, test) => {
test('creates ai related spans', async () => {

Check failure on line 378 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates ai related spans

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:378:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:373:3

Check failure on line 378 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates ai related spans

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:378:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:373:3

Check failure on line 378 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates ai related spans

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:378:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:373:3
await createRunner()
.withEnv(env)
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -409,8 +418,9 @@
'scenario-tool-loop-agent.mjs',
'instrument.mjs',
(createRunner, test) => {
test('creates spans for ToolLoopAgent with tool calls', async () => {

Check failure on line 421 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates spans for ToolLoopAgent with tool calls

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:421:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:416:3

Check failure on line 421 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates spans for ToolLoopAgent with tool calls

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:421:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:416:3

Check failure on line 421 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates spans for ToolLoopAgent with tool calls

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:421:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:416:3
await createRunner()
.withEnv(env)
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -472,8 +482,9 @@
'scenario-concurrent.mjs',
'instrument.mjs',
(createRunner, test) => {
test('parents concurrent calls that share one model instance correctly', async () => {

Check failure on line 485 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > parents concurrent calls that share one model instance correctly

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:485:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:480:3

Check failure on line 485 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > parents concurrent calls that share one model instance correctly

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:485:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:480:3

Check failure on line 485 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > parents concurrent calls that share one model instance correctly

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:485:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:480:3
await createRunner()
.withEnv(env)
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -525,10 +536,11 @@
// Node 20.16 / 22.3 and never backported to Node 18. On Node 18 that lookup returns undefined,
// so the `streamText` event is never published and no `invoke_agent` span is created. The
// non-streaming ops load the channel via dynamic `import()` and are unaffected.
test.skipIf(version === '7' && nodeVersion === 18)(

Check failure on line 539 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates streamText spans with the model call parented to invoke_agent

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:539:56 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:529:3

Check failure on line 539 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates streamText spans with the model call parented to invoke_agent

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:539:56 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:529:3

Check failure on line 539 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > creates streamText spans with the model call parented to invoke_agent

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:539:56 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:529:3
'creates streamText spans with the model call parented to invoke_agent',
async () => {
await createRunner()
.withEnv(env)
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -567,8 +579,9 @@
'scenario-rejected-model.mjs',
'instrument.mjs',
(createRunner, test) => {
test('finishes spans with an error status when the operation rejects', async () => {

Check failure on line 582 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > finishes spans with an error status when the operation rejects

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:582:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:577:3

Check failure on line 582 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > finishes spans with an error status when the operation rejects

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:582:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:577:3

Check failure on line 582 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > finishes spans with an error status when the operation rejects

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:582:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:577:3
await createRunner()
.withEnv(env)
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down Expand Up @@ -607,8 +620,9 @@
'scenario-provider-metadata.mjs',
'instrument.mjs',
(createRunner, test) => {
test('derives provider-metadata token breakdown, conversation id and system instructions', async () => {

Check failure on line 623 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > derives provider-metadata token breakdown, conversation id and system instructions

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:623:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:618:3

Check failure on line 623 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > derives provider-metadata token breakdown, conversation id and system instructions

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:623:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:618:3

Check failure on line 623 in dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts

View workflow job for this annotation

GitHub Actions / Node (24) (TS 3.8) Integration Tests

suites/tracing/vercelai/v6_v7/test.ts > Vercel AI integration (version 6, env { USE_ORCHESTRION: 'true' }) > derives provider-metadata token breakdown, conversation id and system instructions

Error: Test timed out in 15000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ additionalDependencies.ai suites/tracing/vercelai/v6_v7/test.ts:623:7 ❯ createEsmTests utils/runner/createEsmAndCjsTests.ts:89:3 ❯ suites/tracing/vercelai/v6_v7/test.ts:618:3
await createRunner()
.withEnv(env)
.expect({ transaction: { transaction: 'main' } })
.expect({
span: container => {
Expand Down
24 changes: 21 additions & 3 deletions packages/core/src/asyncContext/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,28 @@ export function getAsyncContextStrategy(carrier: Carrier): AsyncContextStrategy
}

/**
* Get the runtime binding needed to connect tracing channels to async context.
* Execute a callback whenever the tracing channel binding is available.
* If it is not available after retry, the callback is not executed.
*/
export function getTracingChannelBinding(): TracingChannelBinding | undefined {
return getAsyncContextStrategy(getMainCarrier()).getTracingChannelBinding?.();
export function waitForTracingChannelBinding(callback: () => void, retries = 1): void {
const binding = getAsyncContextStrategy(getMainCarrier()).getTracingChannelBinding?.();

if (binding) {
callback();
return;
}

if (!retries) {
return;
}

// It is possible that the binding is not available yet when this is initially called
// This happens when users use a custom OTEL setup
// In this case, we wait for a tick and try again afterwards
// If it still fails, we bail and do nothing
setTimeout(() => {
waitForTracingChannelBinding(callback, retries - 1);
}, 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timer missing safeUnref call

Low Severity

waitForTracingChannelBinding schedules a setTimeout retry in @sentry/core without calling safeUnref (or unref), which can keep short-lived Node processes alive until the timer fires.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit d05d40e. Configure here.

}

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/shared-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export {
export { getDefaultCurrentScope, getDefaultIsolationScope } from './defaultScopes';
export {
setAsyncContextStrategy,
getTracingChannelBinding as _INTERNAL_getTracingChannelBinding,
getAsyncContextStrategy,
waitForTracingChannelBinding,
_INTERNAL_createTracingChannelBinding,
} from './asyncContext';
export { getGlobalSingleton, getMainCarrier } from './carrier';
Expand Down
2 changes: 1 addition & 1 deletion packages/node-core/src/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ function _init(
initializeEsmLoader();
}

setOpenTelemetryContextAsyncContextStrategy();
setOpenTelemetryContextAsyncContextStrategy(options);

const scope = getCurrentScope();
scope.update(options.initialScope);
Expand Down
7 changes: 4 additions & 3 deletions packages/node/src/integrations/tracing/redis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
spanToJSON,
truncate,
waitForTracingChannelBinding,
} from '@sentry/core';
import * as dc from 'node:diagnostics_channel';
import { subscribeRedisDiagnosticChannels, type RedisTracingChannelFactory } from '@sentry/server-utils';
Expand Down Expand Up @@ -128,9 +129,9 @@ export const instrumentRedis = Object.assign(
// so defer to the next tick.
// Check this here to ensure this does not fail at runtime for Node <= 18.18.0
if (dc.tracingChannel) {
void Promise.resolve().then(() =>
subscribeRedisDiagnosticChannels(dc.tracingChannel as RedisTracingChannelFactory, cacheResponseHook),
);
waitForTracingChannelBinding(() => {
subscribeRedisDiagnosticChannels(dc.tracingChannel as RedisTracingChannelFactory, cacheResponseHook);
});
}

// todo: implement them gradually
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { mysqlChannelIntegration, detectOrchestrionSetup } from '@sentry/server-utils/orchestrion';
import {
mysqlChannelIntegration,
vercelAiChannelIntegration,
detectOrchestrionSetup,
} from '@sentry/server-utils/orchestrion';
import { registerDiagnosticsChannelInjection } from '@sentry/server-utils/orchestrion/register';
import type { DiagnosticsChannelInjection } from './diagnosticsChannelInjection';
import { setDiagnosticsChannelInjectionLoader } from './diagnosticsChannelInjection';
Expand Down Expand Up @@ -38,8 +42,8 @@ import { setDiagnosticsChannelInjectionLoader } from './diagnosticsChannelInject
export function experimentalUseDiagnosticsChannelInjection(): void {
setDiagnosticsChannelInjectionLoader(
(): DiagnosticsChannelInjection => ({
integrations: [mysqlChannelIntegration()],
replacedOtelIntegrationNames: ['Mysql'],
integrations: [mysqlChannelIntegration(), vercelAiChannelIntegration()],
replacedOtelIntegrationNames: ['Mysql', 'VercelAI'],
register: registerDiagnosticsChannelInjection,
detect: detectOrchestrionSetup,
}),
Expand Down
37 changes: 27 additions & 10 deletions packages/opentelemetry/src/asyncContextStrategy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as api from '@opentelemetry/api';
import type { Scope, Span, withActiveSpan as defaultWithActiveSpan } from '@sentry/core';
import type { Scope, withActiveSpan as defaultWithActiveSpan } from '@sentry/core';
import { getDefaultCurrentScope, getDefaultIsolationScope, setAsyncContextStrategy } from '@sentry/core';
import {
SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY,
Expand All @@ -12,20 +12,23 @@ import { getContextFromScope, getScopesFromContext } from './utils/contextData';
import { getActiveSpan } from './utils/getActiveSpan';
import { getTraceData } from './utils/getTraceData';
import { suppressTracing } from './utils/suppressTracing';
import { getAsyncLocalStorage } from './asyncLocalStorageContextManager';

interface ContextApi {
_getContextManager(): {
getAsyncLocalStorageLookup(): {
asyncLocalStorage: unknown;
};
};
_getContextManager():
| undefined
| {
getAsyncLocalStorageLookup(): {
asyncLocalStorage: unknown;
};
};
}

/**
* Sets the async context strategy to use follow the OTEL context under the hood.
* We handle forking a hub inside of our custom OTEL Context Manager (./otelContextManager.ts)
*/
export function setOpenTelemetryContextAsyncContextStrategy(): void {
export function setOpenTelemetryContextAsyncContextStrategy(options?: { skipOpenTelemetrySetup?: boolean }): void {
function getScopes(): CurrentScopes {
const ctx = api.context.active();
const scopes = getScopesFromContext(ctx);
Expand Down Expand Up @@ -117,13 +120,27 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void {
// than the OTEL one - but this is OK for here, as we now we'll only have OTEL spans passed around
withActiveSpan: withActiveSpan as typeof defaultWithActiveSpan,
getTracingChannelBinding: () => {
// Default case: by default we can just access the async local storage instance here
// this will work no matter if this called before or after the Otel ContextManager was setup
if (!options?.skipOpenTelemetrySetup) {
const asyncLocalStorage = getAsyncLocalStorage();

return {
asyncLocalStorage,
getStoreWithActiveSpan: span => api.trace.setSpan(api.context.active(), span),
};
}

// Else, if we have a custom context manager, we need to access it via the context manager
// this may not be available yet, if this is called before the Otel ContextManager was setup
// in this case, we need to return undefined and retry later, hoping that the setup works by then
try {
const contextManager = (api.context as unknown as ContextApi)._getContextManager();
const lookup = contextManager.getAsyncLocalStorageLookup();
const asyncLocalStorage = contextManager?.getAsyncLocalStorageLookup().asyncLocalStorage;

return {
asyncLocalStorage: lookup.asyncLocalStorage,
getStoreWithActiveSpan: (span: Span) => api.trace.setSpan(api.context.active(), span as api.Span),
asyncLocalStorage,
getStoreWithActiveSpan: span => api.trace.setSpan(api.context.active(), span as api.Span),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty binding skips retry

Medium Severity

With skipOpenTelemetrySetup, getTracingChannelBinding can return a binding whose asyncLocalStorage is still undefined when the global OpenTelemetry context manager is not registered yet. waitForTracingChannelBinding treats any truthy binding as ready, so it runs channel subscribers immediately during Sentry.init() instead of retrying after the user registers their context manager.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d05d40e. Configure here.

};
} catch {
return undefined;
Expand Down
13 changes: 12 additions & 1 deletion packages/opentelemetry/src/asyncLocalStorageContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,29 @@ type PatchMap = Record<string, WeakMap<ListenerFn, ListenerFn>>;

const ADD_LISTENER_METHODS = ['addListener', 'on', 'once', 'prependListener', 'prependOnceListener'] as const;

let _asyncLocalStorage: AsyncLocalStorage<Context> | undefined;

/** Get hold of the async local storage instance. */
export function getAsyncLocalStorage(): AsyncLocalStorage<Context> {
if (!_asyncLocalStorage) {
_asyncLocalStorage = new AsyncLocalStorage<Context>();
}
return _asyncLocalStorage;
}

/**
* OpenTelemetry-compatible context manager using Node.js `AsyncLocalStorage`.
* Semantics match `@opentelemetry/context-async-hooks` (function `bind` + `EventEmitter` patching).
*/
export class SentryAsyncLocalStorageContextManager implements ContextManager {
protected readonly _asyncLocalStorage = new AsyncLocalStorage<Context>();
protected readonly _asyncLocalStorage;

private readonly _kOtListeners = Symbol('OtListeners');
private _wrapped = false;

public constructor() {
setIsSetup('SentryContextManager');
this._asyncLocalStorage = getAsyncLocalStorage();
}

public active(): Context {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { IntegrationFn } from '@sentry/core';
import { defineIntegration, waitForTracingChannelBinding } from '@sentry/core';
import { vercelAiIntegration as baseVercelAiIntegration } from '../../vercel-ai';
import * as dc from 'node:diagnostics_channel';
import { subscribeVercelAiOrchestrionChannels } from '../../vercel-ai/vercel-ai-orchestrion-v6-subscriber';

type VercelAiOptions = Parameters<typeof baseVercelAiIntegration>[0];

// In channel-based (orchestrion) mode we emit our own `gen_ai.*` spans from the
// diagnostics channels. The `ai` SDK still emits its own native OpenTelemetry
// spans whenever the user enables `experimental_telemetry`, which would be
// duplicates. Every native `ai` span carries an `ai.operationId` attribute
// (e.g. `ai.generateText`, `ai.generateText.doGenerate`, `ai.toolCall`) at span
// start, whereas our channel spans use `vercel.ai.operationId` — so we drop the
// native ones up front via `ignoreSpans`, before any vercel-ai processing runs.
const NATIVE_VERCEL_AI_SPANS = { attributes: { 'ai.operationId': /^ai\./ } };

const _vercelAiChannelIntegration = ((options: VercelAiOptions = {}) => {
const parentIntegration = baseVercelAiIntegration(options);

return {
name: 'VercelAI' as const,
beforeSetup(client) {
// Ensure we drop spans emitted by ai v6 or below
// To avoid double-instrumentation - in this scenario, we only want to rely on our own spans
const options = client.getOptions();
options.ignoreSpans = [...(options.ignoreSpans || []), NATIVE_VERCEL_AI_SPANS];
},
setupOnce() {
parentIntegration?.setupOnce?.();

// Bail if this is not available
if (!dc.tracingChannel) {
return;
}

waitForTracingChannelBinding(() => {
subscribeVercelAiOrchestrionChannels(dc.tracingChannel, options);
});
},
};
}) satisfies IntegrationFn;

/**
* Auto-instrument the `ai` SDK. Supported are:
* - v7 via native `ai:telemetry` tracing channel
* - v6 via orchestrion `orchestrion:ai:*` channels
*/
export const vercelAiChannelIntegration = defineIntegration(_vercelAiChannelIntegration);
12 changes: 12 additions & 0 deletions packages/server-utils/src/orchestrion/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
*/
export const CHANNELS = {
MYSQL_QUERY: 'orchestrion:mysql:query',
// Vercel AI (`ai`) v6: orchestrion injects these so the same channel-based
// integration that consumes `ai`'s native `ai:telemetry` channel (v7) can
// also instrument v6. Each maps to a top-level function in `ai`'s bundle.
VERCEL_AI_GENERATE_TEXT: 'orchestrion:ai:generateText',
VERCEL_AI_STREAM_TEXT: 'orchestrion:ai:streamText',
VERCEL_AI_EMBED: 'orchestrion:ai:embed',
VERCEL_AI_EXECUTE_TOOL_CALL: 'orchestrion:ai:executeToolCall',
// `resolveLanguageModel` is the single chokepoint every model call flows
// through; we wrap it to monkey-patch `doGenerate`/`doStream` on the returned
// model (the model-call site itself is an inline call with no injectable
// definition).
VERCEL_AI_RESOLVE_LANGUAGE_MODEL: 'orchestrion:ai:resolveLanguageModel',
} as const;

export type ChannelName = (typeof CHANNELS)[keyof typeof CHANNELS];
Loading
Loading