From b86942ea794f27a6d755b6d918b354cc7bebf6e6 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Sun, 12 Apr 2026 02:29:28 +0900 Subject: [PATCH 1/3] feat(core): Automatically disable truncation when span streaming is enabled in Vercel AI integration When span streaming is enabled, the `enableTruncation` option now defaults to `false` unless the user has explicitly set it. Closes: #20226 --- .../instrument-streaming-with-truncation.mjs | 16 +++++++ .../tracing/vercelai/instrument-streaming.mjs | 11 +++++ .../tracing/vercelai/scenario-streaming.mjs | 30 ++++++++++++ .../suites/tracing/vercelai/test.ts | 46 +++++++++++++++++++ packages/core/src/tracing/vercel-ai/index.ts | 3 +- 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-streaming-with-truncation.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-streaming.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-streaming.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-streaming-with-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-streaming-with-truncation.mjs new file mode 100644 index 000000000000..39c60d69dacb --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-streaming-with-truncation.mjs @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + sendDefaultPii: true, + transport: loggingTransport, + traceLifecycle: 'stream', + integrations: [ + Sentry.vercelAIIntegration({ + enableTruncation: true, + }), + ], +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-streaming.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-streaming.mjs new file mode 100644 index 000000000000..48a860c510c5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-streaming.mjs @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + sendDefaultPii: true, + transport: loggingTransport, + traceLifecycle: 'stream', +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-streaming.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-streaming.mjs new file mode 100644 index 000000000000..7f824dee4a3e --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-streaming.mjs @@ -0,0 +1,30 @@ +import * as Sentry from '@sentry/node'; +import { generateText } from 'ai'; +import { MockLanguageModelV1 } from 'ai/test'; + +async function run() { + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + const longContent = 'A'.repeat(50_000); + await generateText({ + experimental_telemetry: { isEnabled: true }, + model: new MockLanguageModelV1({ + doGenerate: async () => ({ + rawCall: { rawPrompt: null, rawSettings: {} }, + finishReason: 'stop', + usage: { promptTokens: 10, completionTokens: 5 }, + text: 'Response', + }), + }), + messages: [ + { role: 'user', content: longContent }, + { role: 'assistant', content: 'Some reply' }, + { role: 'user', content: 'Follow-up question' }, + ], + }); + }); + + // Flush is required when span streaming is enabled to ensure streamed spans are sent before the process exits + await Sentry.flush(2000); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts index d75a1faf8ea0..df2632ad635b 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts @@ -983,4 +983,50 @@ describe('Vercel AI integration', () => { }); }, ); + + const streamingLongContent = 'A'.repeat(50_000); + + createEsmAndCjsTests(__dirname, 'scenario-streaming.mjs', 'instrument-streaming.mjs', (createRunner, test) => { + test('automatically disables truncation when span streaming is enabled', async () => { + await createRunner() + .expect({ + span: container => { + const spans = container.items; + + const chatSpan = spans.find(s => + s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value?.includes(streamingLongContent), + ); + expect(chatSpan).toBeDefined(); + }, + }) + .start() + .completed(); + }); + }); + + createEsmAndCjsTests( + __dirname, + 'scenario-streaming.mjs', + 'instrument-streaming-with-truncation.mjs', + (createRunner, test) => { + test('respects explicit enableTruncation: true even when span streaming is enabled', async () => { + await createRunner() + .expect({ + span: container => { + const spans = container.items; + + // With explicit enableTruncation: true, truncation keeps only the last message + // and drops the long content. The result should NOT contain the full 50k 'A' string. + const chatSpan = spans.find(s => + s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value?.includes('Follow-up question'), + ); + expect(chatSpan).toBeDefined(); + expect(chatSpan!.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE].value).not.toContain(streamingLongContent); + }, + }) + .start() + .completed(); + }); + }, + ); }); diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index 90fb1b16fc65..1c2fb20c0a0b 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -1,6 +1,7 @@ /* eslint-disable max-lines */ import type { Client } from '../../client'; import { getClient } from '../../currentScopes'; +import { hasSpanStreamingEnabled } from '../../tracing/spans/hasSpanStreamingEnabled'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; import type { Event } from '../../types-hoist/event'; import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON } from '../../types-hoist/span'; @@ -119,7 +120,7 @@ function onVercelAiSpanStart(span: Span): void { const integration = client?.getIntegrationByName('VercelAI') as | { options?: { enableTruncation?: boolean } } | undefined; - const enableTruncation = integration?.options?.enableTruncation ?? true; + const enableTruncation = integration?.options?.enableTruncation ?? !(client && hasSpanStreamingEnabled(client)); processGenerateSpan(span, name, attributes, enableTruncation); } From 457e80eacc8695209b2d0c98f96c9b2dd9596012 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Sun, 12 Apr 2026 01:56:19 +0900 Subject: [PATCH 2/3] Add shouldEnableTruncation helper --- packages/core/src/tracing/ai/utils.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/core/src/tracing/ai/utils.ts b/packages/core/src/tracing/ai/utils.ts index d3cce644dbc1..05502b249efb 100644 --- a/packages/core/src/tracing/ai/utils.ts +++ b/packages/core/src/tracing/ai/utils.ts @@ -3,6 +3,7 @@ */ import { captureException } from '../../exports'; import { getClient } from '../../currentScopes'; +import { hasSpanStreamingEnabled } from '../spans/hasSpanStreamingEnabled'; import type { Span } from '../../types-hoist/span'; import { isThenable } from '../../utils/is'; import { @@ -56,6 +57,16 @@ export function resolveAIRecordingOptions(options? } as T & Required; } +/** + * Resolves whether truncation should be enabled. + * If the user explicitly set `enableTruncation`, that value is used. + * Otherwise, truncation is disabled when span streaming is active. + */ +export function shouldEnableTruncation(enableTruncation: boolean | undefined): boolean { + const client = getClient(); + return enableTruncation ?? !(client && hasSpanStreamingEnabled(client)); +} + /** * Build method path from current traversal */ From 60b6a5102f9c396a035a1c114f3cb80cfc16bab6 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Sun, 12 Apr 2026 02:30:45 +0900 Subject: [PATCH 3/3] Use shouldEnableTruncation in Vercel AI integration --- packages/core/src/tracing/vercel-ai/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index 1c2fb20c0a0b..569233cf8321 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -1,8 +1,8 @@ /* eslint-disable max-lines */ import type { Client } from '../../client'; import { getClient } from '../../currentScopes'; -import { hasSpanStreamingEnabled } from '../../tracing/spans/hasSpanStreamingEnabled'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; +import { shouldEnableTruncation } from '../ai/utils'; import type { Event } from '../../types-hoist/event'; import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON } from '../../types-hoist/span'; import { spanToJSON } from '../../utils/spanUtils'; @@ -120,7 +120,7 @@ function onVercelAiSpanStart(span: Span): void { const integration = client?.getIntegrationByName('VercelAI') as | { options?: { enableTruncation?: boolean } } | undefined; - const enableTruncation = integration?.options?.enableTruncation ?? !(client && hasSpanStreamingEnabled(client)); + const enableTruncation = shouldEnableTruncation(integration?.options?.enableTruncation); processGenerateSpan(span, name, attributes, enableTruncation); }