feat(node): Add AI SDK v7 support via diagnostics_channel#20896
feat(node): Add AI SDK v7 support via diagnostics_channel#20896sergical wants to merge 1 commit into
Conversation
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>
size-limit report 📦
|
| 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]
| } from './dc-handlers'; | ||
|
|
||
| const DC_CHANNEL = 'aisdk:telemetry'; | ||
|
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ 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); | ||
| } |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit cb8ea5f. Configure here.
There was a problem hiding this comment.
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>(); |
There was a problem hiding this comment.
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.
Triggered by project rule: PR Review Guidelines for Cursor Bot
Reviewed by Cursor Bugbot for commit cb8ea5f. Configure here.
There was a problem hiding this comment.
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'; | |||
There was a problem hiding this comment.
m: The subscriber feels pretty thin, let's just merge both files here.
There was a problem hiding this comment.
Done — merged dc-subscriber.ts into dc-handlers.ts. The subscription logic and handler dispatch now live in a single file.
[AI-generated comment]


Summary
node:diagnostics_channelsubscription'aisdk:telemetry'regardless of which OTel integration the user registers — we subscribe and create spans directly withgen_ai.*attributes<7)New files
packages/node/src/integrations/tracing/vercelai/dc-handlers.ts— event handlers mapping DC events → Sentry spanspackages/node/src/integrations/tracing/vercelai/dc-subscriber.ts— subscribes to'aisdk:telemetry'and dispatches to handlersTest coverage (14 tests, all passing)
generateTextwith and withoutsendDefaultPiistreamTextembedToolLoopAgentwithfunctionIdtelemetry: { isEnabled: false }suppresses spansKnown limitations
@sentry/vercel-edge) does not supportnode:diagnostics_channel— v7 edge users are not covered by this PRLegacyOpenTelemetryon v7, both DC and OTel paths would fire (duplicate spans). This requires manual user action and is not the recommended v7 path.Test plan
ai@^7.0.0-canaryyarn format,yarn lint,yarn build:devpass@sentry/nodeand@sentry/corepass🤖 Generated with Claude Code