npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@raindrop-ai/eve

v0.0.33

Published

Raindrop integration for Vercel's Eve agent framework (OTel traces to Raindrop + Workshop)

Readme

@raindrop-ai/eve

Raindrop integration for Vercel's Eve agent framework.

Drops into agent/instrumentation/raindrop.ts (Eve >= 0.62) or agent/instrumentation.ts (older Eve) and ships your Eve agent's OpenTelemetry traces to:

  • Raindrop (https://api.raindrop.ai/v1/traces by default), and
  • the local Workshop daemon (http://localhost:5899) — when one is reachable.

Both destinations receive the same OTLP/HTTP JSON payload, so an agent that works in production also works in raindrop workshop without changing any code.

Install

pnpm add @raindrop-ai/eve @vercel/otel @opentelemetry/api @opentelemetry/sdk-trace-base

eve is expected to already be in your agent project. @vercel/otel is a peer dependency on both layouts (install it to silence the peer warning); only the legacy layout calls its registerOTel. The integration is verified against Eve 0.84.x and keeps the legacy single-file contract (Eve 0.11–0.61) working — a single defineRaindropInstrumentation() call is all you need on either.

How it works:

  • Eve >= 0.62 discovers one instrumentation provider per file under agent/instrumentation/ and assembles the process-wide OpenTelemetry pipeline itself from the destinations those files export. When the installed Eve is >= 0.62 (read from its package.json), the object returned by defineRaindropInstrumentation() is an Eve otelIntegration() destination (Raindrop exporter + span processors + runtimeContext resolver) and a provider with setup. Do not pass registerOTel or call it anywhere — Eve refuses to start when another runtime owns the global tracer provider. Keep the export default un-annotated: the declared return type does not carry Eve's provider/integration symbol markers, so annotating it as Eve's InstrumentationProvider fails to type-check even though the object is one.
  • Eve <= 0.61 reads the legacy agent/instrumentation.ts default export (setup, events, recordInputs, recordOutputs, functionId). Pass registerOTel from @vercel/otel so Eve's own spans are exported.
  • Per-step runtime context is authored once via the events["step.started"] option (returns { runtimeContext }) and forwarded to Eve through otelIntegration({ runtimeContext }) on >= 0.62 or events["step.started"] on older Eve; see Identifying users.
  • Cross-sandbox sub-agent linkage is sourced from the session.parent lineage Eve exposes on that per-step input; sub-agents carry parent linkage with no extra wiring.

Usage

Eve >= 0.62:

// agent/instrumentation/raindrop.ts
import { otelIntegration } from "eve/instrumentation/otel";
import { defineRaindropInstrumentation } from "@raindrop-ai/eve";

export default defineRaindropInstrumentation({
  otelIntegration,
  writeKey: process.env.RAINDROP_WRITE_KEY,
});

Passing otelIntegration explicitly is required for bundled deploys — eve build output runs without node_modules, where the runtime fallback lookup of eve/instrumentation/otel cannot resolve eve.

Process-wide OpenTelemetry settings (functionId, tracePolicy, sampler, propagators) live in Eve's own otel() declaration — one per project, next to the Raindrop file:

// agent/instrumentation/otel.ts (optional)
import { otel } from "eve/instrumentation/otel";

export default otel({ functionId: "support-agent" });

Eve <= 0.61:

// agent/instrumentation.ts
import { registerOTel } from "@vercel/otel";
import { defineRaindropInstrumentation } from "@raindrop-ai/eve";

export default defineRaindropInstrumentation({
  registerOTel,
  writeKey: process.env.RAINDROP_WRITE_KEY,
});

That's it. Eve runs the file at server startup before any agent code. Upgrading an agent from <= 0.61 to >= 0.62: move the file to agent/instrumentation/raindrop.ts, drop registerOTel, and move functionId into otel({ functionId }) if you set it.

Configuration

defineRaindropInstrumentation({
  otelIntegration,                          // Eve >= 0.62, from eve/instrumentation/otel — required for `eve build` deploys
  registerOTel,                             // Eve <= 0.61 only, from @vercel/otel
  writeKey: process.env.RAINDROP_WRITE_KEY,
  endpoint: "https://api.raindrop.ai/v1/", // default
  projectId: "support-prod",                // route to a named project; omit for default Production
  localWorkshopUrl: undefined,              // env + auto-detect by default
  serviceName: "support-agent",             // defaults to the Eve agent name
  staticMetadata: { team: "support", tier: "prod" }, // static, every event
  events: {                                 // Eve per-step runtime context
    "step.started"(input) {
      return {
        runtimeContext: {
          "raindrop.userId": String(input.channel.metadata.triggeringUserId),
        },
      };
    },
  },
  recordInputs: false,                      // default: metadata-only spans. true → ship model inputs (Eve >= 0.62: destination exportPolicy / <= 0.61: forwarded to Eve)
  recordOutputs: false,                     // default: metadata-only spans. true → ship model outputs, likewise
  maxTextFieldChars: 1_000_000,             // per-attribute cap for exported span text
  debug: false,                             // or RAINDROP_AI_DEBUG=1
});

Projects

If your org has multiple projects, route telemetry to a specific one by passing its slug as projectId (or set RAINDROP_PROJECT_ID):

defineRaindropInstrumentation({
  writeKey: process.env.RAINDROP_WRITE_KEY,
  projectId: "support-prod",
});

This sets the X-Raindrop-Project-Id header on every trace export and AI SDK event. Omit it (or pass "default") to use your org's default Production project — the existing behavior. Single-project orgs need nothing new.

Payload size limits

Exported string span attributes (full prompts/completions recorded by the AI SDK under recordInputs / recordOutputs) are capped at 1,000,000 characters per value by default and truncated with a ...[truncated by raindrop] marker (the result, marker included, never exceeds the limit). The cap is enforced before the OTLP batch is serialized, so multi-MB attributes cost the cap — not the payload — and land truncated instead of being dropped at the ingest size limit. Tune it via maxTextFieldChars; a stricter OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT env var is also honored.

staticMetadata is the static authored lane — keys are merged onto every shipped event. events is Eve's InstrumentationEvents config: its step.started(input) callback runs once per model call, returns { runtimeContext }, and that runtime context is bridged onto the event too (see below). A flat metadata: Record<string,string> is still accepted and treated the same as staticMetadata for convenience.

Identifying users

Static identity. Pass raindrop.userId (and optionally raindrop.convoId) via staticMetadata and they are attached to every event from this Eve session:

defineRaindropInstrumentation({
  registerOTel,
  writeKey: process.env.RAINDROP_WRITE_KEY,
  staticMetadata: {
    "raindrop.userId": "user_123",
    "raindrop.convoId": "convo_456",
    team: "support",
  },
});

Dynamic per-turn identity. Use the events step.started callback. Eve invokes it once per model call with the live channel/session/turn input, so you can derive identity from the triggering request — e.g. map the Slack user id onto the Raindrop event userId. On Eve >= 0.84, input.turn is optional (a model call outside a turn has none), input.step is gone, and input.run carries { id, attempt }; read input.turn?.id. A call without a turn ships the session id but no turn id, and gets its own event instead of joining the previous turn's.

defineRaindropInstrumentation({
  registerOTel,
  writeKey: process.env.RAINDROP_WRITE_KEY,
  staticMetadata: { app: "demo-data-agent", team: "data" },
  events: {
    "step.started"(input) {
      const m = input.channel.metadata;
      return {
        runtimeContext: {
          // reserved keys -> Raindrop event identity
          "raindrop.userId": m.triggeringUserId, // becomes the event userId
          "raindrop.convoId": m.threadTs,        // becomes the event convoId
          // any other key -> Raindrop event properties
          "slack.user_id": m.triggeringUserId,
          "slack.channel_id": m.channelId,
          "slack.team_id": m.teamId,
          "slack.thread_ts": m.threadTs,
        },
      };
    },
  },
});

The callback's output is both attached to the AI SDK telemetry spans (as ai.settings.context.*) and routed onto the shipped Raindrop event:

  • reserved raindrop.* control keys set the event identity. A per-step raindrop.userId (e.g. the Slack user id) wins over the Eve session.id for the event's userId; raindrop.convoId sets the convoId.
  • every other key becomes an entry under event properties.

Precedence for any given key (highest first): per-call metadata on the AI SDK call itself > the step.started callback > staticMetadata. So a per-step key always overrides the static default of the same name. When the callback omits raindrop.userId, the integration falls back to the Eve session.id, then to opts.userId ?? agentName.

Using the Slack user's name / email / channel name

Whatever string you return as raindrop.userId becomes the event's userId — so to make Raindrop track e.g. the Slack email instead of the user id, just return that string. What's reachable from step.started:

  • User id (Uxxxx) — directly on input.channel.metadata.triggeringUserId.
  • User's name — already resolved, no API call: read input.session.auth.current?.attributes and use full_name / user_name (Eve's default Slack auth puts them there).
  • Email / channel name — not in any Slack mention payload, so resolve them once on the async inbound side (onAppMention) via the Slack Web API (users.info → user.profile.email, needs the users:read.email scope; conversations.info → channel.name), stash them on the session auth attributes, then read them back in step.started (which is synchronous and can't call Slack itself):
// agent/channels/slack.ts — runs on the inbound webhook (async ok)
export default slackChannel({
  async onAppMention(ctx, message) {
    const auth = defaultSlackAuth(message, ctx);
    const who = await ctx.slack.request("users.info", { user: message.author.userId });
    return auth && {
      auth: { ...auth, attributes: { ...auth.attributes, email: who?.user?.profile?.email ?? "" } },
    };
  },
});

// instrumentation.ts — synchronous, just reads what you stashed
"step.started"(input) {
  const a = input.session.auth.current?.attributes ?? {};
  return {
    runtimeContext: { "raindrop.userId": String(a.email || a.user_id || "") },
  }; // userId will be the email
},

Workshop / production mirroring

localWorkshopUrl controls Workshop mirroring:

| Value | Behavior | | -------------- | --------------------------------------------------------------------- | | string | Force-enable; mirror every export to that URL. | | false | Opt out entirely (skip env vars and auto-detect). | | undefined | Honor RAINDROP_WORKSHOP / RAINDROP_LOCAL_DEBUGGER, then auto-detect localhost during development. |

Run raindrop workshop locally to get a daemon at http://localhost:5899 — the integration will start mirroring as soon as the daemon is reachable.

Production-only mode

defineRaindropInstrumentation({
  registerOTel,
  writeKey: process.env.RAINDROP_WRITE_KEY,
  localWorkshopUrl: false,
});

Workshop-only mode (no Raindrop account)

Leave writeKey undefined. Spans still flow to Workshop:

defineRaindropInstrumentation({
  registerOTel,
  localWorkshopUrl: "http://localhost:5899/v1/",
});

Sub-agents

Eve runs each sub-agent in its own V8 sandbox. The integration reads the session.parent lineage Eve exposes on the per-step input (the otelIntegration({ runtimeContext }) input on Eve >= 0.62, the events["step.started"] input before that) to detect when the current sandbox was dispatched as a sub-agent, and lifts the parent's turn identity onto every sub-agent event:

| Attribute | Description | | ------------------------------- | ------------------------------------------------------------------------------------------ | | raindrop.agent.role | "subagent" when the current sandbox is a sub-agent dispatched by another agent; "root" otherwise. | | raindrop.subagent.name | The sub-agent's agentName (e.g. weatherResearcher). | | raindrop.parent.sessionId | The parent agent's (bare) Eve session id. | | raindrop.parent.eventId | The cross-sandbox link key. The dispatching parent turn's per-turn raindrop.eventId (the eve:<sessionId>:<turnId> composite); the dashboard stitches a sub-agent under its parent by matching this against that turn's own raindrop.eventId. | | raindrop.parent.turnId | The parent's turn id that dispatched this sub-agent. | | raindrop.parent.turnSequence | The parent's turn sequence number. |

These power the AGENT block in Workshop's Overview tab and let the Raindrop dashboard stitch sub-agent events under the parent turn that dispatched them (matching on raindrop.parent.eventId).

When Eve exposes no session.parent (a root session), events ship as root events in the feed with raindrop.agent.role = "root".

What gets traced

Eve automatically creates rich trace hierarchies per turn. Eve >= 0.62 owns the agent spans and emits OpenTelemetry GenAI semantic-convention attributes (gen_ai.*):

invoke_agent <agent>
  +-- agent.step
        +-- chat <model>                      // model call
        +-- execute_tool <tool>               // tool exec

Older Eve relies on the AI SDK's spans:

eve.turn                                      // session/turn metadata
  +-- ai.streamText                           // step
  |     +-- ai.streamText.doStream            // model call
  |     +-- ai.toolCall  { toolName: search } // tool exec
  +-- ai.streamText

Both flow through Raindrop's standard ingestion and show up in the Raindrop UI and Workshop with full nesting. On every Eve version the AI SDK telemetry integration this package registers also ships each model / tool call as a Raindrop event (track_partial).

Notes

  • This package depends on @raindrop-ai/core for OTLP serialization and the Workshop mirroring helper.
  • The exporter implements the SpanExporter interface structurally, so it works against both @opentelemetry/sdk-trace-base 1.x and 2.x.
  • On Eve >= 0.62 the session and turn identifiers Eve no longer forwards natively travel as raindrop.eve.sessionId / raindrop.eve.turnId runtime context. They are consumed by this package (per-turn raindrop.eventId, session-scoped raindrop.userId / raindrop.convoId) and never shipped as event metadata.
  • Span content defaults are unchanged by the upgrade: with recordInputs / recordOutputs omitted, spans shipped to Raindrop stay metadata-only on both layouts (Eve <= 0.61 read an omitted flag as false; on >= 0.62 this package declares a redacting exportPolicy for its destination). Set a flag to true to ship that direction; other destinations still follow Eve's own tracePolicy.
  • On Eve >= 0.62 the functionId option only affects this package's own sub-agent detection; Eve reads the span functionId from otel({ functionId }).
  • Eve requires Node >= 24 (Eve's own engines since well before 0.62); when otelIntegration is not passed, this package falls back to loading eve/package.json and eve/instrumentation/otel through require(), which Node 24 supports for ES modules. The fallback needs eve resolvable from node_modules, so bundled eve build deploys must pass the option.

Examples

  • examples/eve-basic — single-agent example with one tool (get_weather) and an end-to-end dashboard verifier.
  • examples/eve-subagents — parent agent dispatching weatherResearcher + attractionsFinder sub-agents, verifying cross-sandbox nesting against the production dashboard.

Application Git identity

Set appGit on defineRaindropInstrumentation (or RaindropEveSpanExporter) to override commitSha, commitDirty, and optional ordinary branch, or pass false to opt out. The revision is unknown by default because ambient deploy/CI and observer working-directory metadata are ignored. Local discovery requires an explicit sourceDirectory (or RAINDROP_GIT_SOURCE_DIRECTORY); explicit RAINDROP_COMMIT_SHA values are honored. Automatic branch discovery requires detectBranch: true. Eve's event feed and independent OTLP exporter share one application Git context.

For advanced direct exporter wiring, install the start processor to freeze automatic metadata when each span begins. On Eve >= 0.62 declare it as a destination in agent/instrumentation/raindrop.ts:

import { otelIntegration } from "eve/instrumentation/otel";
import { RaindropEveSpanExporter, createEveAppGitSpanProcessor } from "@raindrop-ai/eve";

const exporter = new RaindropEveSpanExporter({
  writeKey: process.env.RAINDROP_WRITE_KEY,
  appGit: { sourceDirectory: "/path/to/application" },
});
export default otelIntegration({
  traceExporter: exporter,
  spanProcessors: [createEveAppGitSpanProcessor(exporter)],
});

On older Eve, keep "auto" to preserve Vercel's default processors; traceExporter retains its normal batch exporter:

import { registerOTel } from "@vercel/otel";
import { RaindropEveSpanExporter, createEveAppGitSpanProcessor } from "@raindrop-ai/eve";

const exporter = new RaindropEveSpanExporter({
  writeKey: process.env.RAINDROP_WRITE_KEY,
  appGit: { sourceDirectory: "/path/to/application" },
});
registerOTel({
  traceExporter: exporter,
  spanProcessors: ["auto", createEveAppGitSpanProcessor(exporter)],
});

Without this processor, direct exporters attach only explicit client, environment, operation, or span values. Automatic discovery never fills in metadata for a span whose start was not observed. defineRaindropInstrumentation installs the processor automatically.