From 2e54d85fe0e172b146434cf835ef535d351dd9ba Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Wed, 10 Sep 2025 11:21:52 +0200 Subject: [PATCH 1/4] fix(node): Preserve synchronous return behavior for streamText and other methods for AI --- .../tracing/vercelai/instrumentation.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts index 0b66f7e80919..570ab001db21 100644 --- a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts +++ b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts @@ -71,7 +71,7 @@ function isToolError(obj: unknown): obj is ToolError { * Check for tool errors in the result and capture them * Tool errors are not rejected in Vercel V5, it is added as metadata to the result content */ -function checkResultForToolErrors(result: unknown | Promise): void { +function checkResultForToolErrors(result: unknown): void { if (typeof result !== 'object' || result === null || !('content' in result)) { return; } @@ -236,14 +236,22 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase { }; return handleCallbackErrors( - async () => { + () => { // @ts-expect-error we know that the method exists - const result = await originalMethod.apply(this, args); - - // Tool errors are not rejected in Vercel V5, it is added as metadata to the result content - checkResultForToolErrors(result); - - return result; + const result = originalMethod.apply(this, args); + + // Handle both sync and async results + if (result && typeof result === 'object' && typeof (result as { then?: unknown }).then === 'function') { + // Result is a promise, handle it asynchronously + return (result as Promise).then((resolvedResult: unknown) => { + checkResultForToolErrors(resolvedResult); + return resolvedResult; + }); + } else { + // Result is synchronous, handle it directly + checkResultForToolErrors(result); + return result; + } }, error => { // This error bubbles up to unhandledrejection handler (if not handled before), From d33688c442502e1760c1c59c09792f20c0d0a370 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Wed, 10 Sep 2025 11:34:56 +0200 Subject: [PATCH 2/4] preserve promise return --- .../tracing/vercelai/instrumentation.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts index 570ab001db21..243da2e21419 100644 --- a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts +++ b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts @@ -242,11 +242,21 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase { // Handle both sync and async results if (result && typeof result === 'object' && typeof (result as { then?: unknown }).then === 'function') { - // Result is a promise, handle it asynchronously - return (result as Promise).then((resolvedResult: unknown) => { - checkResultForToolErrors(resolvedResult); - return resolvedResult; - }); + // Result is a promise - we need to preserve the original promise reference + // but still handle errors and check for tool errors + const originalPromise = result as Promise; + + // Set up tool error checking when the promise resolves (but don't change the promise) + originalPromise + .then((resolvedResult: unknown) => { + checkResultForToolErrors(resolvedResult); + }) + .catch(() => { + // Ignore errors here - they'll be handled by the original promise + }); + + // Return the original promise unchanged + return originalPromise; } else { // Result is synchronous, handle it directly checkResultForToolErrors(result); From 7efb74324a5601a4062070f4a094a73883196fef Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Wed, 10 Sep 2025 12:05:39 +0200 Subject: [PATCH 3/4] quick refactor for a simpler logic --- .../tracing/vercelai/instrumentation.ts | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts index 243da2e21419..50d08e7f29f6 100644 --- a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts +++ b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts @@ -132,6 +132,15 @@ function checkResultForToolErrors(result: unknown): void { } } +/** + * Checks if the given value is a promise like object. + * @param value The value to check. + * @returns True if the value is a promise like object, false otherwise. + */ +function isPromiseLike(value: unknown): value is PromiseLike { + return !!value && typeof value === 'object' && typeof (value as { then?: unknown }).then === 'function'; +} + /** * Determines whether to record inputs and outputs for Vercel AI telemetry based on the configuration hierarchy. * @@ -240,28 +249,15 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase { // @ts-expect-error we know that the method exists const result = originalMethod.apply(this, args); - // Handle both sync and async results - if (result && typeof result === 'object' && typeof (result as { then?: unknown }).then === 'function') { - // Result is a promise - we need to preserve the original promise reference - // but still handle errors and check for tool errors - const originalPromise = result as Promise; - - // Set up tool error checking when the promise resolves (but don't change the promise) - originalPromise - .then((resolvedResult: unknown) => { - checkResultForToolErrors(resolvedResult); - }) - .catch(() => { - // Ignore errors here - they'll be handled by the original promise - }); - - // Return the original promise unchanged - return originalPromise; - } else { - // Result is synchronous, handle it directly - checkResultForToolErrors(result); + if (isPromiseLike(result)) { + // check for tool errors when the promise resolves, keep the original promise identity + result.then(checkResultForToolErrors, () => {}); return result; } + + // check for tool errors when the result is synchronous + checkResultForToolErrors(result); + return result; }, error => { // This error bubbles up to unhandledrejection handler (if not handled before), From c002f9683c9dfbe584c2556c8d44f40170a19b87 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 11 Sep 2025 15:15:50 +0200 Subject: [PATCH 4/4] update util --- .../integrations/tracing/vercelai/instrumentation.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts index 50d08e7f29f6..bf32d417c42e 100644 --- a/packages/node/src/integrations/tracing/vercelai/instrumentation.ts +++ b/packages/node/src/integrations/tracing/vercelai/instrumentation.ts @@ -9,6 +9,7 @@ import { getActiveSpan, getCurrentScope, handleCallbackErrors, + isThenable, SDK_VERSION, withScope, } from '@sentry/core'; @@ -132,15 +133,6 @@ function checkResultForToolErrors(result: unknown): void { } } -/** - * Checks if the given value is a promise like object. - * @param value The value to check. - * @returns True if the value is a promise like object, false otherwise. - */ -function isPromiseLike(value: unknown): value is PromiseLike { - return !!value && typeof value === 'object' && typeof (value as { then?: unknown }).then === 'function'; -} - /** * Determines whether to record inputs and outputs for Vercel AI telemetry based on the configuration hierarchy. * @@ -249,7 +241,7 @@ export class SentryVercelAiInstrumentation extends InstrumentationBase { // @ts-expect-error we know that the method exists const result = originalMethod.apply(this, args); - if (isPromiseLike(result)) { + if (isThenable(result)) { // check for tool errors when the promise resolves, keep the original promise identity result.then(checkResultForToolErrors, () => {}); return result;