pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/getsentry/sentry-javascript/pull/20896

feat(node): Add AI SDK v7 support via diagnostics_channel by sergical · Pull Request #20896 · getsentry/sentry-javascript · GitHub
Skip to content

feat(node): Add AI SDK v7 support via diagnostics_channel#20896

Open
sergical wants to merge 1 commit into
developfrom
feat/vercel-ai-v7-dc-telemetry
Open

feat(node): Add AI SDK v7 support via diagnostics_channel#20896
sergical wants to merge 1 commit into
developfrom
feat/vercel-ai-v7-dc-telemetry

Conversation

@sergical
Copy link
Copy Markdown
Member

Summary

  • Adds AI SDK v7 telemetry support using node:diagnostics_channel subscription
  • v7 publishes all telemetry events to 'aisdk:telemetry' regardless of which OTel integration the user registers — we subscribe and create spans directly with gen_ai.* attributes
  • v3-v6 path is unchanged (OTel instrumentation, version-gated at <7)
  • On v3-v6, the DC subscriber is inert — the channel is never published to

New files

  • packages/node/src/integrations/tracing/vercelai/dc-handlers.ts — event handlers mapping DC events → Sentry spans
  • packages/node/src/integrations/tracing/vercelai/dc-subscriber.ts — subscribes to 'aisdk:telemetry' and dispatches to handlers

Test coverage (14 tests, all passing)

  • generateText with and without sendDefaultPii
  • streamText
  • embed
  • Tool calls and tool errors
  • ToolLoopAgent with functionId
  • telemetry: { isEnabled: false } suppresses spans
  • ESM and CJS for all scenarios

Known limitations

  • Edge runtime (@sentry/vercel-edge) does not support node:diagnostics_channel — v7 edge users are not covered by this PR
  • If a user explicitly registers LegacyOpenTelemetry on v7, both DC and OTel paths would fire (duplicate spans). This requires manual user action and is not the recommended v7 path.

Test plan

  • v7 integration tests pass (14/14) against ai@^7.0.0-canary
  • v6 integration tests still pass (10/10) — no regressions
  • yarn format, yarn lint, yarn build:dev pass
  • Unit tests for @sentry/node and @sentry/core pass
  • CI

🤖 Generated with Claude Code

AI SDK v7 publishes all telemetry events to node:diagnostics_channel
on 'aisdk:telemetry', regardless of which OpenTelemetry integration
the user registers. This subscribes to that channel and creates spans
directly with gen_ai.* attributes — no OTel span translation needed.

- v3-v6: existing OTel instrumentation path (unchanged)
- v7+: diagnostic channel subscriber creates spans from raw events
- On v3-v6, the DC subscriber is inert (channel never published to)

Handles: generateText, streamText, generateObject, streamObject,
embed, embedMany, rerank, tool execution, tool errors, ToolLoopAgent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 26.92 kB - -
@sentry/browser - with treeshaking flags 25.35 kB - -
@sentry/browser (incl. Tracing) 44.83 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 46.83 kB - -
@sentry/browser (incl. Tracing, Profiling) 49.82 kB - -
@sentry/browser (incl. Tracing, Replay) 84.45 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 73.92 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 89.15 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 101.8 kB - -
@sentry/browser (incl. Feedback) 44.11 kB - -
@sentry/browser (incl. sendFeedback) 31.73 kB - -
@sentry/browser (incl. FeedbackAsync) 36.84 kB - -
@sentry/browser (incl. Metrics) 28.01 kB - -
@sentry/browser (incl. Logs) 28.16 kB - -
@sentry/browser (incl. Metrics & Logs) 28.84 kB - -
@sentry/react 28.67 kB - -
@sentry/react (incl. Tracing) 47.1 kB - -
@sentry/vue 31.84 kB - -
@sentry/vue (incl. Tracing) 46.7 kB - -
@sentry/svelte 26.94 kB - -
CDN Bundle 29.31 kB - -
CDN Bundle (incl. Tracing) 47.23 kB - -
CDN Bundle (incl. Logs, Metrics) 30.68 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 48.36 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 70.05 kB - -
CDN Bundle (incl. Tracing, Replay) 84.65 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 85.72 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 90.46 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 91.56 kB - -
CDN Bundle - uncompressed 86.28 kB - -
CDN Bundle (incl. Tracing) - uncompressed 141.83 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 90.47 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 145.29 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 215.32 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 260.57 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 264.01 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 274.27 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 277.7 kB - -
@sentry/nextjs (client) 49.61 kB - -
@sentry/sveltekit (client) 45.31 kB - -
@sentry/node-core 61.96 kB - -
@sentry/node 168.23 kB +0.79% +1.3 kB 🔺
@sentry/node - without tracing 74.37 kB - -
@sentry/aws-serverless 109.18 kB -0.01% -1 B 🔽
@sentry/cloudflare (withSentry) - minified 170.88 kB - -
@sentry/cloudflare (withSentry) 431.1 kB - -

View base workflow run

@sergical sergical marked this pull request as ready for review May 14, 2026 22:46
@sergical sergical requested a review from a team as a code owner May 14, 2026 22:46
export function handleOnError(event: Record<string, unknown>): void {
const state = callStates.get(event.callId as string);
if (!state) return;
const error = (event.error ?? event) as Error;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: In handleOnError, if an event lacks an error property, the fallback (event.error ?? event) causes error.message to be undefined, creating spans with empty error messages.
Severity: LOW

Suggested Fix

Update the error handling in handleOnError to be more robust. Before accessing error.message, verify that event.error is a valid error object. If it is not, construct a new Error with a default message or serialize the event object into a string to ensure the span status always contains meaningful information.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/node/src/integrations/tracing/vercelai/dc-handlers.ts#L289

Potential issue: In the `handleOnError` function, the code uses a fallback `(event.error
?? event) as Error` to process diagnostic channel events. If the Vercel AI SDK publishes
an `onError` event that does not contain an `error` property, the entire event object is
treated as the error. Since this event object lacks a `message` property,
`error?.message` will be `undefined`. This results in the associated span's error status
being set with an empty message, which silently hides the actual error details instead
of providing useful observability.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — the error handling now uses instanceof Error to properly check the error object, with a fallback to 'unknown error' if event.error is not an Error instance.

[AI-generated comment]

Comment on lines +10 to +13
} from './dc-handlers';

const DC_CHANNEL = 'aisdk:telemetry';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: A module-level subscribed flag prevents re-subscribing to the Vercel AI diagnostic channel when Sentry.init() is called multiple times, causing subsequent clients to miss telemetry.
Severity: MEDIUM

Suggested Fix

The subscription logic should be tied to the lifecycle of an integration or client, not a global module-level flag. Remove the subscribed flag and ensure subscribeAiSdkDiagnosticChannel is idempotent or that a corresponding unsubscribe function is called when a client or integration is torn down.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/node/src/integrations/tracing/vercelai/dc-subscriber.ts#L10-L13

Potential issue: A module-level `subscribed` flag is used to ensure the Vercel AI
diagnostic channel is only subscribed to once. However, this flag is not reset if the
Sentry SDK is re-initialized within the same process, such as in serverless environments
with warm containers or during integration tests. Consequently, only the first Sentry
client instance will successfully subscribe and receive telemetry events. Subsequent
initializations will skip subscription, causing them to miss all Vercel AI diagnostic
data.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is by design — the module-level flag prevents duplicate event processing from multiple subscriptions. The diagnostic channel handlers use getClient() at call time, so they always operate on the current active client even after re-initialization. This matches the pattern used by other DC integrations in this codebase (e.g. redis-dc-subscriber).

[AI-generated comment]

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cb8ea5f. Configure here.

}
state.rootSpan.end();
callStates.delete(event.callId as string);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

handleOnEnd leaks child spans unlike handleOnError

Low Severity

handleOnEnd ends the rootSpan and deletes the callStates entry without first ending any still-active inferenceSpan or toolSpans. In contrast, handleOnError defensively iterates and ends all child spans before ending the root span. If handleOnEnd fires before handleOnLanguageModelCallEnd or handleOnToolExecutionEnd (e.g., unexpected event ordering from the AI SDK, or during streaming edge cases), those child spans become orphaned — never ended and unreachable since the callStates entry is deleted.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cb8ea5f. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — handleOnEnd now defensively ends any still-active child spans (inference + tool spans) before ending the root span, matching the cleanup behavior in handleOnError.

[AI-generated comment]

recordOutputs: boolean;
}

const callStates = new Map<string, CallState>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Module-level callStates Map never expires orphaned entries

Low Severity

The module-level callStates Map accumulates entries in handleOnStart and only removes them in handleOnEnd or handleOnError. If either end event is never received (e.g., aborted request, AI SDK bug, or process-level timeout), entries holding Span objects and their toolSpans Maps persist indefinitely. In long-running servers handling many AI requests, this can cause a gradual memory leak.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit cb8ea5f. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged — in practice the AI SDK should always fire either onEnd or onError, making orphaned entries unlikely. Adding TTL-based eviction would introduce meaningful complexity for a very marginal scenario. We'll monitor this and add eviction if it becomes an issue in production.

[AI-generated comment]

@@ -0,0 +1,65 @@
import { subscribe } from 'node:diagnostics_channel';
Copy link
Copy Markdown
Member

@logaretm logaretm May 14, 2026

Choose a reason for hiding this comment

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

m: The subscriber feels pretty thin, let's just merge both files here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done — merged dc-subscriber.ts into dc-handlers.ts. The subscription logic and handler dispatch now live in a single file.

[AI-generated comment]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy