From 5a85b6c5530047138c42da41f2a2030948e7070d Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 10 Apr 2026 12:51:54 +0200 Subject: [PATCH 1/3] enableTruncation for vercel --- .../vercelai/instrument-no-truncation.mjs | 17 ++++++++++ .../vercelai/scenario-no-truncation.mjs | 28 ++++++++++++++++ .../suites/tracing/vercelai/test.ts | 33 +++++++++++++++++++ packages/core/src/tracing/vercel-ai/index.ts | 13 ++++---- packages/core/src/tracing/vercel-ai/utils.ts | 20 ++++++----- .../integrations/tracing/vercelai/index.ts | 5 +-- .../integrations/tracing/vercelai/types.ts | 6 ++++ 7 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-no-truncation.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-no-truncation.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-no-truncation.mjs new file mode 100644 index 000000000000..0593d975c8d7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/instrument-no-truncation.mjs @@ -0,0 +1,17 @@ +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, + integrations: [ + Sentry.vercelAIIntegration({ + recordInputs: true, + recordOutputs: true, + enableTruncation: false, + }), + ], +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-no-truncation.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-no-truncation.mjs new file mode 100644 index 000000000000..415c13ef9acf --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/scenario-no-truncation.mjs @@ -0,0 +1,28 @@ +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 () => { + // Multiple messages with long content (would normally be truncated and popped to last message only) + 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' }, + ], + }); + }); +} + +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 673887737ee6..d75a1faf8ea0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts @@ -950,4 +950,37 @@ describe('Vercel AI integration', () => { .completed(); }); }); + + const longContent = 'A'.repeat(50_000); + + createEsmAndCjsTests( + __dirname, + 'scenario-no-truncation.mjs', + 'instrument-no-truncation.mjs', + (createRunner, test) => { + test('does not truncate input messages when enableTruncation is false', async () => { + await createRunner() + .expect({ + transaction: { + transaction: 'main', + spans: expect.arrayContaining([ + // Multiple messages should all be preserved (no popping to last message only) + expect.objectContaining({ + data: expect.objectContaining({ + [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: JSON.stringify([ + { role: 'user', content: longContent }, + { role: 'assistant', content: 'Some reply' }, + { role: 'user', content: 'Follow-up question' }, + ]), + [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: 3, + }), + }), + ]), + }, + }) + .start() + .completed(); + }); + }, + ); }); diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index 5458ace456c5..c7afa47c10e2 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -94,7 +94,7 @@ function mapVercelAiOperationName(operationName: string): string { * Post-process spans emitted by the Vercel AI SDK. * This is supposed to be used in `client.on('spanStart', ...) */ -function onVercelAiSpanStart(span: Span): void { +function onVercelAiSpanStart(span: Span, enableTruncation: boolean): void { const { data: attributes, description: name } = spanToJSON(span); if (!name) { @@ -114,7 +114,7 @@ function onVercelAiSpanStart(span: Span): void { return; } - processGenerateSpan(span, name, attributes); + processGenerateSpan(span, name, attributes, enableTruncation); } function vercelAiEventProcessor(event: Event): Event { @@ -396,7 +396,7 @@ function processToolCallSpan(span: Span, attributes: SpanAttributes): void { } } -function processGenerateSpan(span: Span, name: string, attributes: SpanAttributes): void { +function processGenerateSpan(span: Span, name: string, attributes: SpanAttributes, enableTruncation: boolean): void { span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.vercelai.otel'); const nameWthoutAi = name.replace('ai.', ''); @@ -408,7 +408,7 @@ function processGenerateSpan(span: Span, name: string, attributes: SpanAttribute span.setAttribute('gen_ai.function_id', functionId); } - requestMessagesFromPrompt(span, attributes); + requestMessagesFromPrompt(span, attributes, enableTruncation); if (attributes[AI_MODEL_ID_ATTRIBUTE] && !attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]) { span.setAttribute(GEN_AI_RESPONSE_MODEL_ATTRIBUTE, attributes[AI_MODEL_ID_ATTRIBUTE]); @@ -444,8 +444,9 @@ function processGenerateSpan(span: Span, name: string, attributes: SpanAttribute /** * Add event processors to the given client to process Vercel AI spans. */ -export function addVercelAiProcessors(client: Client): void { - client.on('spanStart', onVercelAiSpanStart); +export function addVercelAiProcessors(client: Client, options?: { enableTruncation?: boolean }): void { + const enableTruncation = options?.enableTruncation ?? true; + client.on('spanStart', span => onVercelAiSpanStart(span, enableTruncation)); // Note: We cannot do this on `spanEnd`, because the span cannot be mutated anymore at this point client.addEventProcessor(Object.assign(vercelAiEventProcessor, { id: 'VercelAiEventProcessor' })); } diff --git a/packages/core/src/tracing/vercel-ai/utils.ts b/packages/core/src/tracing/vercel-ai/utils.ts index 2a715deab764..600cb8d2528a 100644 --- a/packages/core/src/tracing/vercel-ai/utils.ts +++ b/packages/core/src/tracing/vercel-ai/utils.ts @@ -16,7 +16,7 @@ import { GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; -import { extractSystemInstructions, getTruncatedJsonString } from '../ai/utils'; +import { extractSystemInstructions, getJsonString, getTruncatedJsonString } from '../ai/utils'; import { toolCallSpanContextMap } from './constants'; import type { TokenSummary, ToolCallSpanContext } from './types'; import { AI_PROMPT_ATTRIBUTE, AI_PROMPT_MESSAGES_ATTRIBUTE } from './vercel-ai-attributes'; @@ -227,7 +227,7 @@ export function convertUserInputToMessagesFormat(userInput: string): { role: str * Generate a request.messages JSON array from the prompt field in the * invoke_agent op */ -export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes): void { +export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes, enableTruncation: boolean): void { if ( typeof attributes[AI_PROMPT_ATTRIBUTE] === 'string' && !attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] && @@ -247,11 +247,13 @@ export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes } const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0; - const truncatedMessages = getTruncatedJsonString(filteredMessages); + const messagesJson = enableTruncation + ? getTruncatedJsonString(filteredMessages) + : getJsonString(filteredMessages); span.setAttributes({ - [AI_PROMPT_ATTRIBUTE]: truncatedMessages, - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: truncatedMessages, + [AI_PROMPT_ATTRIBUTE]: messagesJson, + [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: messagesJson, [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength, }); } @@ -268,11 +270,13 @@ export function requestMessagesFromPrompt(span: Span, attributes: SpanAttributes } const filteredLength = Array.isArray(filteredMessages) ? filteredMessages.length : 0; - const truncatedMessages = getTruncatedJsonString(filteredMessages); + const messagesJson = enableTruncation + ? getTruncatedJsonString(filteredMessages) + : getJsonString(filteredMessages); span.setAttributes({ - [AI_PROMPT_MESSAGES_ATTRIBUTE]: truncatedMessages, - [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: truncatedMessages, + [AI_PROMPT_MESSAGES_ATTRIBUTE]: messagesJson, + [GEN_AI_INPUT_MESSAGES_ATTRIBUTE]: messagesJson, [GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE]: filteredLength, }); } diff --git a/packages/node/src/integrations/tracing/vercelai/index.ts b/packages/node/src/integrations/tracing/vercelai/index.ts index a0b3f3126d01..6ff5be83d670 100644 --- a/packages/node/src/integrations/tracing/vercelai/index.ts +++ b/packages/node/src/integrations/tracing/vercelai/index.ts @@ -30,10 +30,11 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { // Note that this can only be detected if the 'Modules' integration is available, and running in CJS mode const shouldForce = options.force ?? shouldForceIntegration(client); + const processorOptions = { enableTruncation: options.enableTruncation }; if (shouldForce) { - addVercelAiProcessors(client); + addVercelAiProcessors(client, processorOptions); } else { - instrumentation?.callWhenPatched(() => addVercelAiProcessors(client)); + instrumentation?.callWhenPatched(() => addVercelAiProcessors(client, processorOptions)); } }, }; diff --git a/packages/node/src/integrations/tracing/vercelai/types.ts b/packages/node/src/integrations/tracing/vercelai/types.ts index 35cfeb33a112..624212f9f7bd 100644 --- a/packages/node/src/integrations/tracing/vercelai/types.ts +++ b/packages/node/src/integrations/tracing/vercelai/types.ts @@ -62,6 +62,12 @@ export interface VercelAiOptions { * If you want to register the span processors even when the ai package usage cannot be detected, you can set `force` to `true`. */ force?: boolean; + + /** + * Enable or disable truncation of recorded input messages. + * Defaults to `true`. + */ + enableTruncation?: boolean; } export interface VercelAiIntegration extends Integration { From b9de89331ad16ad3210bea6116d2a96ab6db6377 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 10 Apr 2026 13:23:36 +0200 Subject: [PATCH 2/3] . --- packages/core/src/tracing/vercel-ai/index.ts | 10 ++++++---- .../node/src/integrations/tracing/vercelai/index.ts | 5 ++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index c7afa47c10e2..faf844f33eea 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -94,7 +94,7 @@ function mapVercelAiOperationName(operationName: string): string { * Post-process spans emitted by the Vercel AI SDK. * This is supposed to be used in `client.on('spanStart', ...) */ -function onVercelAiSpanStart(span: Span, enableTruncation: boolean): void { +function onVercelAiSpanStart(span: Span, client: Client): void { const { data: attributes, description: name } = spanToJSON(span); if (!name) { @@ -114,6 +114,9 @@ function onVercelAiSpanStart(span: Span, enableTruncation: boolean): void { return; } + const integration = client.getIntegrationByName('VercelAI') as { options?: { enableTruncation?: boolean } } | undefined; + const enableTruncation = integration?.options?.enableTruncation ?? true; + processGenerateSpan(span, name, attributes, enableTruncation); } @@ -444,9 +447,8 @@ function processGenerateSpan(span: Span, name: string, attributes: SpanAttribute /** * Add event processors to the given client to process Vercel AI spans. */ -export function addVercelAiProcessors(client: Client, options?: { enableTruncation?: boolean }): void { - const enableTruncation = options?.enableTruncation ?? true; - client.on('spanStart', span => onVercelAiSpanStart(span, enableTruncation)); +export function addVercelAiProcessors(client: Client): void { + client.on('spanStart', span => onVercelAiSpanStart(span, client)); // Note: We cannot do this on `spanEnd`, because the span cannot be mutated anymore at this point client.addEventProcessor(Object.assign(vercelAiEventProcessor, { id: 'VercelAiEventProcessor' })); } diff --git a/packages/node/src/integrations/tracing/vercelai/index.ts b/packages/node/src/integrations/tracing/vercelai/index.ts index 6ff5be83d670..a0b3f3126d01 100644 --- a/packages/node/src/integrations/tracing/vercelai/index.ts +++ b/packages/node/src/integrations/tracing/vercelai/index.ts @@ -30,11 +30,10 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { // Note that this can only be detected if the 'Modules' integration is available, and running in CJS mode const shouldForce = options.force ?? shouldForceIntegration(client); - const processorOptions = { enableTruncation: options.enableTruncation }; if (shouldForce) { - addVercelAiProcessors(client, processorOptions); + addVercelAiProcessors(client); } else { - instrumentation?.callWhenPatched(() => addVercelAiProcessors(client, processorOptions)); + instrumentation?.callWhenPatched(() => addVercelAiProcessors(client)); } }, }; From d1760534c33b69a4441b4755cd2ce006b9d8f4eb Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 10 Apr 2026 13:26:51 +0200 Subject: [PATCH 3/3] . --- packages/core/src/tracing/vercel-ai/index.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index faf844f33eea..90fb1b16fc65 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ import type { Client } from '../../client'; +import { getClient } from '../../currentScopes'; 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'; @@ -94,7 +95,7 @@ function mapVercelAiOperationName(operationName: string): string { * Post-process spans emitted by the Vercel AI SDK. * This is supposed to be used in `client.on('spanStart', ...) */ -function onVercelAiSpanStart(span: Span, client: Client): void { +function onVercelAiSpanStart(span: Span): void { const { data: attributes, description: name } = spanToJSON(span); if (!name) { @@ -114,7 +115,10 @@ function onVercelAiSpanStart(span: Span, client: Client): void { return; } - const integration = client.getIntegrationByName('VercelAI') as { options?: { enableTruncation?: boolean } } | undefined; + const client = getClient(); + const integration = client?.getIntegrationByName('VercelAI') as + | { options?: { enableTruncation?: boolean } } + | undefined; const enableTruncation = integration?.options?.enableTruncation ?? true; processGenerateSpan(span, name, attributes, enableTruncation); @@ -448,7 +452,7 @@ function processGenerateSpan(span: Span, name: string, attributes: SpanAttribute * Add event processors to the given client to process Vercel AI spans. */ export function addVercelAiProcessors(client: Client): void { - client.on('spanStart', span => onVercelAiSpanStart(span, client)); + client.on('spanStart', onVercelAiSpanStart); // Note: We cannot do this on `spanEnd`, because the span cannot be mutated anymore at this point client.addEventProcessor(Object.assign(vercelAiEventProcessor, { id: 'VercelAiEventProcessor' })); }