@raindrop-ai/ai-sdk package provides automatic instrumentation for the Vercel AI SDK. It captures events and traces with zero configuration required at each call site.
Why use this over the OTEL-based integration?
- No OpenTelemetry setup required
- Works in Node.js out of the box
- Dedicated browser/edge entrypoint without
async_hooksshims - Works in Cloudflare Workers when
nodejs_compatis enabled (see “Cloudflare Workers” below) - Automatic tool call tracing
- Built-in attachment support
- User trait identification
- Instrumented signal tracking
Installation
Quick Start
generateText, streamText, generateObject, and streamObject call is now tracked.
Native Telemetry Integration (AI SDK v7+)
AI SDK v7 introduced a pluggable TelemetryIntegration interface. Raindrop provides a native implementation that replaces Proxy-based wrapping with first-class SDK integration. Requires AI SDK v7 or later — usingnativeTelemetry with v6 or earlier will throw an error.
Quick start: registerTelemetry(raindrop())
The simplest v7 setup is the raindrop() factory, which returns a
ready-to-register telemetry integration. The writeKey defaults to
process.env.RAINDROP_WRITE_KEY:
raindrop() is self-contained — no @ai-sdk/otel or OpenTelemetry setup is
required. Pass context and subagentWrapping to customise behaviour, e.g.
raindrop({ context: { userId: "user_123", eventName: "chat" } }). The returned
integration also exposes flush() / shutdown() so short-lived scripts and
serverless handlers can drain buffered telemetry before exiting.
Using wrap() with native telemetry
Enable the native path by passing nativeTelemetry: true:
nativeTelemetry, wrap() uses the existing Proxy-based wrapping, which works across all AI SDK versions (v4+).
Direct registration (without wrap())
You can also register the integration directly with the AI SDK’s global registry:
On AI SDK v7 betas before
beta.111 this function was named
registerTelemetryIntegration. It was renamed to registerTelemetry in the
v7 canary line — use registerTelemetry on current v7.experimental_telemetry.integrations if you need different configuration per request.
When to use native telemetry vs. default wrapping
The native integration uses the AI SDK’s own callback system instead of intercepting function calls. This is a cleaner integration, but some features from the defaultwrap() path are not yet available:
Use
nativeTelemetry: true if you want a cleaner integration without Proxy wrapping and don’t need buildEvent or attachment features. Use the default (no flag) if you rely on those features or want the most complete telemetry today.
Runtime support
Browsers and edge runtimes
Use the browser entrypoint:async_hooks shims. When AsyncLocalStorage is not available,
Raindrop falls back to synchronous context scoping: nested work in the same call stack
still inherits context, but automatic propagation across arbitrary async boundaries is
not guaranteed.
If you need stable correlation across async hops or multiple requests, pass an explicit
eventId with eventMetadata().
Node.js (recommended)
Use the default import:AsyncLocalStorage automatically.
Cloudflare Workers
Cloudflare Workers supports a subset of Node’sAsyncLocalStorage when you enable nodejs_compat (docs).
If nodejs_compat is enabled, import the Workers entrypoint:
AsyncLocalStorage propagation in Workers.
If nodejs_compat is not enabled, use the browser entrypoint instead. The SDK still works,
but it uses the same synchronous fallback described above rather than real
AsyncLocalStorage.
Configuration
Client Options
Wrap Options
Projects
Route events to a specific project by passing its slug asprojectId:
X-Raindrop-Project-Id header on every outbound event. Omit it (or pass "default") to use your org’s default Production project, which is the existing behavior. Single-project orgs need nothing new.
Multiple projects in one process
To report to different projects from one process, create one Raindrop client per project and use its per-instancewrap() lanes. Each wrapped generateText/streamText ships under that client’s own writeKey and X-Raindrop-Project-Id, so the projects stay isolated:
Mixed processes (a tracing-enabled
raindrop-ai client alongside this integration). If the same process also constructs a tracing-enabled raindrop-ai js-sdk client, its Traceloop pipeline auto-instruments the underlying LLM libraries process-wide — so calls made through this integration package additionally produce auto-instrumented spans that route to the js-sdk pipeline owner’s project. This is pre-existing behavior, unrelated to per-instance routing. Run mixed setups with a single project, or scope the integration’s calls with the js-sdk client’s asCurrent().Context & Metadata
Per-Call Context Override (Recommended)
Set stable defaults inwrap() and override them per call with eventMetadata() when needed:
userId, convoId, eventName, properties, or eventId.
Merge behavior:
- Call-time values override wrap-time defaults
propertiesare merged (call-time wins on conflicts)featureFlagsare merged the same way (call-time wins per flag)eventIdis auto-generated if neither wrap-time nor call-time sets it
autoAttachment: false in wrap().
context in wrap() is optional but useful for defaults. If both are present, call-time eventMetadata() wins.Feature Flags
Record which feature flags and experiment variants were active for each generation, so you can compare AI quality across variants in Raindrop. Flags are sent asfeature_flags on the event.
Two input forms are accepted: an array of flag names (each implicitly "true") or an object with explicit values (string, number, or boolean — all stringified before sending). ["prompt-v2"] ships as {"prompt-v2": "true"}; { "max-retries": 3, reasoning: false } ships as {"max-retries": "3", "reasoning": "false"}.
Set flags that apply to every call at wrap-time, and per-request variants with eventMetadata():
{"prompt-v2": "true", "retrieval-strategy": "hybrid"}.
To add flags to an event that has already been created — for example, after a fallback decision made once the generation finished — use events.setFeatureFlags() with the event’s ID:
wrap() (AI SDK v4+) and native telemetry on v7+ (nativeTelemetry: true, raindrop({ context: { featureFlags: [...] } }), or createTelemetryIntegration({ featureFlags: [...] })).
AI SDK UI / useChat with client-side tools
When you use AI SDK UI (useChat) with client-side tools, one user turn can produce multiple POST requests to your route handler.
This commonly happens when you use:
sendAutomaticallyWhenonToolCalladdToolOutput- user approval / confirmation flows
eventId on every request, Raindrop will treat each tool handoff as a separate event.
Use eventMetadataFromChatRequest() in your route handler to derive stable metadata from the default AI SDK UI request body:
convoIdis derived from the chat requestideventIdis derived from the latest user message inmessages
tool-calls responses pending and patch the same logical event until the model produces a final answer.
If your app sets
userId per request, pass it into eventMetadataFromChatRequest({ request: body, userId }).If you customize the AI SDK UI request body and remove the default id or messages fields, either preserve them or pass explicit eventId / convoId with eventMetadata().Identifying Users
Useusers.identify to associate traits with a user.
Tools
Tools are automatically wrapped and traced. No additional configuration needed:Self Diagnostics (Optional)
Self Diagnostics lets your agent proactively report its own issues — capability gaps, missing context, persistent tool failures — back to your team. It’s most valuable when an agent’s self-reflection provides signal you can’t easily get otherwise. When enabled, the SDK injects a hidden tool the agent calls silently when it hits an unrecoverable problem. Signals appear in Raindrop’s Self Diagnostics section and feed into issue discovery. The four default categories aremissing_context, repeatedly_broken_tool, capability_gap, and complete_task_failure. You can replace these with your own.
eventId:
Self diagnostics with native telemetry
Create the diagnostics tool and add it to yourtools map:
eventId or getEventId:
signals, guidance, toolName, and onMissingEventId ("warn", "ignore", or "throw").
Nested LLM Calls in Tools
If your tool makes additional LLM calls, wrap them too for full trace visibility:Streaming
Streaming methods (streamText, streamObject) work identically:
- Time to first chunk (
ai.stream.msToFirstChunk) - Total stream duration (
ai.stream.msToFinish) - Average output tokens per second (
ai.stream.avgOutputTokensPerSecond)
streamObject
streamObject works out of the box. You can await result.object directly without manually consuming the stream:
ToolLoopAgent
Requires AI SDK v6 or later.
generate() and stream() trace tool calls with their arguments and results.
Pass per-call context via the top-level metadata parameter.
Typescript
raindrop.wrap(ai) changes the ToolLoopAgent method signatures to accept top-level metadata, so typing against ai.ToolLoopAgent can cause TypeScript errors.
If you define your own agent type, wrap the AI SDK type with AgentWithMetadata so metadata is still accepted on generate() and stream():
generate
stream
Signals (Feedback & Instrumentation)
Track user feedback on AI responses using the sameeventId:
Signal Types
Manual Event Updates
Update events after they’re created:Manual Traces
Create trace spans manually alongside (or instead of) auto-instrumented ones. This is useful for:- Extracting DSL commands from agent output as tool-like spans
- Adding custom tracing for operations the AI SDK doesn’t auto-instrument
- Building full trace trees for custom agent loops (subagents, multi-step workflows)
eventId values.
One-shot spans (known timing)
UsecreateSpan when the operation is already complete and you know the duration:
Manual lifecycle spans
UsestartSpan / endSpan when you want to time an operation in real time:
Nested spans (subagents)
Pass a span asparent to build a trace tree:
Mixing with auto-instrumented spans
Use the sameeventId for both wrap() calls and manual spans. They appear in the same trace tree:
Custom Event Builder
UsebuildEvent to customize the event payload from the conversation messages:
Flush & Shutdown
Always flush before your process exits to ensure all data is sent:Debugging
Enable debug logging to troubleshoot issues:- Every event sent to Raindrop
- Every trace batch shipped
- Span parent/child relationships (with
debugSpans)
Advanced: Context Propagation APIs
For advanced use cases, access the underlying context propagation utilities:These APIs are strongest in Node.js (and Cloudflare Workers with
nodejs_compat), where
they use real AsyncLocalStorage. In edge/browser runtimes they still scope context
within the active synchronous call stack, but they do not guarantee propagation across
arbitrary async boundaries. In browser/edge runtimes, import them from
@raindrop-ai/ai-sdk/browser.Troubleshooting
Events not appearing in dashboard
- Check your write key - Ensure
RAINDROP_WRITE_KEYis set correctly - Flush before exit - Call
await raindrop.flush()before your process ends - Enable debug logging - Set
events: { debug: true }to see what’s being sent
Traces missing or incomplete
- Enable trace debugging - Set
traces: { debug: true, debugSpans: true } - Check for errors - Look for
[raindrop-ai/ai-sdk]prefixed logs
Context not propagating to nested calls
- Check your runtime - Full async context propagation requires Node.js (or Cloudflare Workers with
nodejs_compat) - Workers import - On Cloudflare Workers with
nodejs_compat, import@raindrop-ai/ai-sdk/workers - Browser/edge import - In browser or edge runtimes, import
@raindrop-ai/ai-sdk/browser - Edge/browser fallback - In edge/browser runtimes, use an explicit
eventIdwitheventMetadata()when you need correlation across async hops or multiple requests
eventId and set send: { events: false } to avoid duplicate events:
One AI SDK UI turn appears as multiple events
If you useuseChat with client-side tools and see one user turn split into multiple events:
- Use
eventMetadataFromChatRequest()in the route handler so each tool handoff reuses the same logicaleventId - Do not generate a fresh
eventIdon everyPOSTin AI SDK UI routes - Preserve the default AI SDK UI request fields (
id,messages,messageId) if you customize the transport body
eventId and one convoId, which lets the wrapper patch the same logical event instead of creating a new one per hop.
That’s it! You’re ready to explore your events and traces in the Raindrop dashboard. Ping us on Slack or email us if you get stuck!