Skip to main content
The @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_hooks shims
  • Works in Cloudflare Workers when nodejs_compat is enabled (see “Cloudflare Workers” below)
  • Automatic tool call tracing
  • Built-in attachment support
  • User trait identification
  • Instrumented signal tracking

Installation

Quick Start

That’s it. Every 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 — using nativeTelemetry 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:
Without 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.
You can also pass the integration per-call via 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 default wrap() 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:
The SDK works without 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(). Use the default import:
In Node, the default entrypoint wires up AsyncLocalStorage automatically.

Cloudflare Workers

Cloudflare Workers supports a subset of Node’s AsyncLocalStorage when you enable nodejs_compat (docs). If nodejs_compat is enabled, import the Workers entrypoint:
This enables real 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 as projectId:
This sets the 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-instance wrap() lanes. Each wrapped generateText/streamText ships under that client’s own writeKey and X-Raindrop-Project-Id, so the projects stay isolated:
The AI SDK v7 global telemetry registry (registerTelemetry(raindrop())) is not per-project. registerTelemetry fans every call out to every registered integration, so registering two clients globally duplicates all traffic to both projects — each copy shipped under its own writeKey/X-Raindrop-Project-Id (this is duplication, not cross-project leakage). For per-project isolation, use the per-instance client.wrap(ai) lanes shown above rather than registering more than one integration globally.
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

Set stable defaults in wrap() and override them per call with eventMetadata() when needed:
This keeps common values in one place while allowing each request to set or override userId, convoId, eventName, properties, or eventId. Merge behavior:
  • Call-time values override wrap-time defaults
  • properties are merged (call-time wins on conflicts)
  • featureFlags are merged the same way (call-time wins per flag)
  • eventId is auto-generated if neither wrap-time nor call-time sets it
If you need full manual control over attachments, set 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 as feature_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():
The example above ships {"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:
Feature flags work on both integration paths: the default Proxy 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:
  • sendAutomaticallyWhen
  • onToolCall
  • addToolOutput
  • user approval / confirmation flows
If you generate a new 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:
  • convoId is derived from the chat request id
  • eventId is derived from the latest user message in messages
This lets the wrapper keep intermediate 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

Use users.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 are missing_context, repeatedly_broken_tool, capability_gap, and complete_task_failure. You can replace these with your own.
You don’t need to mention self diagnostics in your system prompt — the SDK handles the tool description automatically. When the agent calls the tool, a signal is tracked on the same eventId:

Self diagnostics with native telemetry

Create the diagnostics tool and add it to your tools map:
Signals attach to the current Raindrop event automatically. To choose the event yourself, pass eventId or getEventId:
You can also pass 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:
Raindrop captures streaming-specific metrics:
  • 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.
ToolLoopAgent is wrapped automatically. Both 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 same eventId:

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)
Spans share the same trace tree as auto-instrumented spans when you pass matching eventId values.

One-shot spans (known timing)

Use createSpan when the operation is already complete and you know the duration:

Manual lifecycle spans

Use startSpan / endSpan when you want to time an operation in real time:

Nested spans (subagents)

Pass a span as parent to build a trace tree:

Mixing with auto-instrumented spans

Use the same eventId for both wrap() calls and manual spans. They appear in the same trace tree:

Custom Event Builder

Use buildEvent 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:
This logs:
  • 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

  1. Check your write key - Ensure RAINDROP_WRITE_KEY is set correctly
  2. Flush before exit - Call await raindrop.flush() before your process ends
  3. Enable debug logging - Set events: { debug: true } to see what’s being sent

Traces missing or incomplete

  1. Enable trace debugging - Set traces: { debug: true, debugSpans: true }
  2. Check for errors - Look for [raindrop-ai/ai-sdk] prefixed logs

Context not propagating to nested calls

  1. Check your runtime - Full async context propagation requires Node.js (or Cloudflare Workers with nodejs_compat)
  2. Workers import - On Cloudflare Workers with nodejs_compat, import @raindrop-ai/ai-sdk/workers
  3. Browser/edge import - In browser or edge runtimes, import @raindrop-ai/ai-sdk/browser
  4. Edge/browser fallback - In edge/browser runtimes, use an explicit eventId with eventMetadata() when you need correlation across async hops or multiple requests
Ensure nested AI calls use the same eventId and set send: { events: false } to avoid duplicate events:

One AI SDK UI turn appears as multiple events

If you use useChat with client-side tools and see one user turn split into multiple events:
  1. Use eventMetadataFromChatRequest() in the route handler so each tool handoff reuses the same logical eventId
  2. Do not generate a fresh eventId on every POST in AI SDK UI routes
  3. Preserve the default AI SDK UI request fields (id, messages, messageId) if you customize the transport body
With the helper in place, client-side tool round-trips reuse one stable 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!