@raindrop-ai/eve 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -0
  3. package/package.json +67 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Raindrop AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # `@raindrop-ai/eve`
2
+
3
+ Raindrop integration for [Vercel's Eve agent framework](https://eve.labs.vercel.dev/).
4
+
5
+ Drops into `agent/instrumentation.ts` and ships your Eve agent's OpenTelemetry
6
+ traces to:
7
+
8
+ - **Raindrop** (`https://api.raindrop.ai/v1/traces` by default), and
9
+ - the local **Workshop** daemon (`http://localhost:5899`) — when one is reachable.
10
+
11
+ Both destinations receive the same OTLP/HTTP JSON payload, so an agent that
12
+ works in production also works in `raindrop workshop` without changing any code.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pnpm add @raindrop-ai/eve @vercel/otel @opentelemetry/api @opentelemetry/sdk-trace-base
18
+ ```
19
+
20
+ `eve` is expected to already be in your agent project. The integration tracks
21
+ Eve's fast-moving releases and is verified against the current `latest`
22
+ (**0.11.x**), hooking Eve's `defineInstrumentation` surface from
23
+ `eve/instrumentation` — so a single `defineRaindropInstrumentation()` call is all
24
+ you need.
25
+
26
+ How it works:
27
+
28
+ - Eve enables telemetry by the mere presence of an `agent/instrumentation.ts`
29
+ default export. The object returned by `defineRaindropInstrumentation()` is
30
+ that export: Eve reads its `setup`, `events`, `recordInputs`, `recordOutputs`,
31
+ and `functionId` fields and ignores the rest.
32
+ - **Per-step runtime context** is resolved through Eve's `events["step.started"]`
33
+ hook (returns `{ runtimeContext }`); see [Identifying users](#identifying-users).
34
+ - **Cross-sandbox sub-agent linkage** is sourced from the `step.started`
35
+ `session.parent` lineage Eve exposes; sub-agents carry parent linkage with no
36
+ extra wiring.
37
+
38
+ ## Usage
39
+
40
+ ```ts
41
+ // agent/instrumentation.ts
42
+ import { registerOTel } from "@vercel/otel";
43
+ import { defineRaindropInstrumentation } from "@raindrop-ai/eve";
44
+
45
+ export default defineRaindropInstrumentation({
46
+ registerOTel,
47
+ writeKey: process.env.RAINDROP_WRITE_KEY,
48
+ });
49
+ ```
50
+
51
+ That's it. The framework auto-discovers `agent/instrumentation.ts` and runs it
52
+ at server startup before any agent code. Pass `registerOTel` from `@vercel/otel`
53
+ so Eve's own OTel spans (workflow/step/fetch) are exported — without it the
54
+ integration still wires up Raindrop's AI SDK telemetry but skips the underlying
55
+ OTel registration.
56
+
57
+ ## Configuration
58
+
59
+ ```ts
60
+ defineRaindropInstrumentation({
61
+ registerOTel, // from @vercel/otel
62
+ writeKey: process.env.RAINDROP_WRITE_KEY,
63
+ endpoint: "https://api.raindrop.ai/v1/", // default
64
+ localWorkshopUrl: undefined, // env + auto-detect by default
65
+ serviceName: "support-agent", // defaults to the Eve agent name
66
+ staticMetadata: { team: "support", tier: "prod" }, // static, every event
67
+ events: { // Eve per-step runtime context
68
+ "step.started"(input) {
69
+ return {
70
+ runtimeContext: {
71
+ "raindrop.userId": String(input.channel.metadata.triggeringUserId),
72
+ },
73
+ };
74
+ },
75
+ },
76
+ recordInputs: true, // forwarded to Eve
77
+ recordOutputs: true, // forwarded to Eve
78
+ maxTextFieldChars: 1_000_000, // per-attribute cap for exported span text
79
+ debug: false, // or RAINDROP_AI_DEBUG=1
80
+ });
81
+ ```
82
+
83
+ ### Payload size limits
84
+
85
+ Exported string span attributes (full prompts/completions recorded by the AI
86
+ SDK under `recordInputs` / `recordOutputs`) are capped at **1,000,000
87
+ characters per value by default** and truncated with a
88
+ `...[truncated by raindrop]` marker (the result, marker included, never
89
+ exceeds the limit). The cap is enforced before the OTLP batch is serialized,
90
+ so multi-MB attributes cost the cap — not the payload — and land truncated
91
+ instead of being dropped at the ingest size limit. Tune it via
92
+ `maxTextFieldChars`; a stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env
93
+ var is also honored.
94
+
95
+ `staticMetadata` is the static authored lane — keys are merged onto every
96
+ shipped event. `events` is Eve's
97
+ [`InstrumentationEvents`](https://beta.eve.dev/docs) config: its
98
+ `step.started(input)` callback runs once per model call, returns
99
+ `{ runtimeContext }`, and that runtime context is bridged onto the event too
100
+ (see below). A flat `metadata: Record<string,string>` is still accepted and
101
+ treated the same as `staticMetadata` for convenience.
102
+
103
+ ### Identifying users
104
+
105
+ **Static identity.** Pass `raindrop.userId` (and optionally `raindrop.convoId`)
106
+ via `staticMetadata` and they are attached to every event from this Eve
107
+ session:
108
+
109
+ ```ts
110
+ defineRaindropInstrumentation({
111
+ registerOTel,
112
+ writeKey: process.env.RAINDROP_WRITE_KEY,
113
+ staticMetadata: {
114
+ "raindrop.userId": "user_123",
115
+ "raindrop.convoId": "convo_456",
116
+ team: "support",
117
+ },
118
+ });
119
+ ```
120
+
121
+ **Dynamic per-turn identity.** Use the `events`
122
+ [`step.started`](https://beta.eve.dev/docs)
123
+ callback. Eve invokes it once per model call with the live
124
+ channel/session/turn/step `input`, so you can derive identity from the
125
+ triggering request — e.g. map the Slack user id onto the Raindrop event
126
+ `userId`:
127
+
128
+ ```ts
129
+ defineRaindropInstrumentation({
130
+ registerOTel,
131
+ writeKey: process.env.RAINDROP_WRITE_KEY,
132
+ staticMetadata: { app: "demo-data-agent", team: "data" },
133
+ events: {
134
+ "step.started"(input) {
135
+ const m = input.channel.metadata;
136
+ return {
137
+ runtimeContext: {
138
+ // reserved keys -> Raindrop event identity
139
+ "raindrop.userId": m.triggeringUserId, // becomes the event userId
140
+ "raindrop.convoId": m.threadTs, // becomes the event convoId
141
+ // any other key -> Raindrop event properties
142
+ "slack.user_id": m.triggeringUserId,
143
+ "slack.channel_id": m.channelId,
144
+ "slack.team_id": m.teamId,
145
+ "slack.thread_ts": m.threadTs,
146
+ },
147
+ };
148
+ },
149
+ },
150
+ });
151
+ ```
152
+
153
+ The callback's output is both attached to the AI SDK telemetry spans (as
154
+ `ai.settings.context.*`) and routed onto the shipped Raindrop event:
155
+
156
+ - reserved `raindrop.*` control keys set the event identity. A per-step
157
+ `raindrop.userId` (e.g. the Slack user id) wins over the Eve `session.id`
158
+ for the event's `userId`; `raindrop.convoId` sets the `convoId`.
159
+ - every other key becomes an entry under event `properties`.
160
+
161
+ Precedence for any given key (highest first): per-call metadata on the AI SDK
162
+ call itself > the `step.started` callback > `staticMetadata`. So a per-step key
163
+ always overrides the static default of the same name. When the callback omits
164
+ `raindrop.userId`, the integration falls back to the Eve `session.id`, then to
165
+ `opts.userId ?? agentName`.
166
+
167
+ #### Using the Slack user's name / email / channel name
168
+
169
+ Whatever string you return as `raindrop.userId` becomes the event's `userId` —
170
+ so to make Raindrop track e.g. the Slack email instead of the user id, just
171
+ return that string. What's reachable from `step.started`:
172
+
173
+ - **User id** (`Uxxxx`) — directly on `input.channel.metadata.triggeringUserId`.
174
+ - **User's name** — already resolved, no API call: read
175
+ `input.session.auth.current?.attributes` and use `full_name` / `user_name`
176
+ (Eve's default Slack auth puts them there).
177
+ - **Email / channel name** — *not* in any Slack mention payload, so resolve them
178
+ once on the async inbound side (`onAppMention`) via the Slack Web API
179
+ (`users.info` → `user.profile.email`, needs the `users:read.email` scope;
180
+ `conversations.info` → `channel.name`), stash them on the session auth
181
+ `attributes`, then read them back in `step.started` (which is synchronous and
182
+ can't call Slack itself):
183
+
184
+ ```ts
185
+ // agent/channels/slack.ts — runs on the inbound webhook (async ok)
186
+ export default slackChannel({
187
+ async onAppMention(ctx, message) {
188
+ const auth = defaultSlackAuth(message, ctx);
189
+ const who = await ctx.slack.request("users.info", { user: message.author.userId });
190
+ return auth && {
191
+ auth: { ...auth, attributes: { ...auth.attributes, email: who?.user?.profile?.email ?? "" } },
192
+ };
193
+ },
194
+ });
195
+
196
+ // instrumentation.ts — synchronous, just reads what you stashed
197
+ "step.started"(input) {
198
+ const a = input.session.auth.current?.attributes ?? {};
199
+ return {
200
+ runtimeContext: { "raindrop.userId": String(a.email || a.user_id || "") },
201
+ }; // userId will be the email
202
+ },
203
+ ```
204
+
205
+ ### Workshop / production mirroring
206
+
207
+ `localWorkshopUrl` controls Workshop mirroring:
208
+
209
+ | Value | Behavior |
210
+ | -------------- | --------------------------------------------------------------------- |
211
+ | `string` | Force-enable; mirror every export to that URL. |
212
+ | `false` | Opt out entirely (skip env vars and auto-detect). |
213
+ | `undefined` | Honor `RAINDROP_WORKSHOP` / `RAINDROP_LOCAL_DEBUGGER`, then auto-detect localhost during development. |
214
+
215
+ Run `raindrop workshop` locally to get a daemon at `http://localhost:5899` —
216
+ the integration will start mirroring as soon as the daemon is reachable.
217
+
218
+ ### Production-only mode
219
+
220
+ ```ts
221
+ defineRaindropInstrumentation({
222
+ registerOTel,
223
+ writeKey: process.env.RAINDROP_WRITE_KEY,
224
+ localWorkshopUrl: false,
225
+ });
226
+ ```
227
+
228
+ ### Workshop-only mode (no Raindrop account)
229
+
230
+ Leave `writeKey` undefined. Spans still flow to Workshop:
231
+
232
+ ```ts
233
+ defineRaindropInstrumentation({
234
+ registerOTel,
235
+ localWorkshopUrl: "http://localhost:5899/v1/",
236
+ });
237
+ ```
238
+
239
+ ## Sub-agents
240
+
241
+ Eve runs each sub-agent in its own V8 sandbox. The integration reads the
242
+ `session.parent` lineage Eve exposes on the `events["step.started"]` input to
243
+ detect when the current sandbox was dispatched as a sub-agent, and lifts the
244
+ parent's turn identity onto every sub-agent event:
245
+
246
+ | Attribute | Description |
247
+ | ------------------------------- | ------------------------------------------------------------------------------------------ |
248
+ | `raindrop.agent.role` | `"subagent"` when the current sandbox is a sub-agent dispatched by another agent; `"root"` otherwise. |
249
+ | `raindrop.subagent.name` | The sub-agent's `agentName` (e.g. `weatherResearcher`). |
250
+ | `raindrop.parent.sessionId` | The parent agent's (bare) Eve session id. |
251
+ | `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`. |
252
+ | `raindrop.parent.turnId` | The parent's turn id that dispatched this sub-agent. |
253
+ | `raindrop.parent.turnSequence` | The parent's turn sequence number. |
254
+
255
+ These power the **AGENT block** in Workshop's Overview tab and let the Raindrop
256
+ dashboard stitch sub-agent events under the parent turn that dispatched them
257
+ (matching on `raindrop.parent.eventId`).
258
+
259
+ When Eve exposes no `session.parent` (a root session), events ship as root
260
+ events in the feed with `raindrop.agent.role` = `"root"`.
261
+
262
+ ## What gets traced
263
+
264
+ Eve automatically creates rich trace hierarchies per turn:
265
+
266
+ ```
267
+ eve.turn // session/turn metadata
268
+ +-- ai.streamText // step
269
+ | +-- ai.streamText.doStream // model call
270
+ | +-- ai.toolCall { toolName: search } // tool exec
271
+ +-- ai.streamText
272
+ ```
273
+
274
+ These flow through Raindrop's standard ingestion and show up in both the
275
+ Raindrop UI and Workshop with full nesting + AI SDK attributes.
276
+
277
+ ## Notes
278
+
279
+ - This package depends on `@raindrop-ai/core` for OTLP serialization and the
280
+ Workshop mirroring helper.
281
+ - The exporter implements the `SpanExporter` interface structurally, so it
282
+ works against both `@opentelemetry/sdk-trace-base` 1.x and 2.x.
283
+
284
+ ## Examples
285
+
286
+ - [`examples/eve-basic`](../../examples/eve-basic) — single-agent example with one tool (`get_weather`) and an end-to-end dashboard verifier.
287
+ - [`examples/eve-subagents`](../../examples/eve-subagents) — parent agent dispatching `weatherResearcher` + `attractionsFinder` sub-agents, verifying cross-sandbox nesting against the production dashboard.
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@raindrop-ai/eve",
3
+ "version": "0.0.10",
4
+ "description": "Raindrop integration for Vercel's Eve agent framework (OTel traces to Raindrop + Workshop)",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/raindrop-ai/raindrop-js.git",
12
+ "directory": "packages/eve"
13
+ },
14
+ "homepage": "https://github.com/raindrop-ai/raindrop-js/tree/main/packages/eve#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/raindrop-ai/raindrop-js/issues"
17
+ },
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js"
23
+ }
24
+ },
25
+ "sideEffects": false,
26
+ "files": [
27
+ "dist/**"
28
+ ],
29
+ "dependencies": {
30
+ "@raindrop-ai/ai-sdk": "0.0.36"
31
+ },
32
+ "peerDependencies": {
33
+ "@opentelemetry/api": ">=1.9.0",
34
+ "@opentelemetry/sdk-trace-base": ">=1.30.0 || >=2.0.0",
35
+ "@vercel/otel": ">=1.10.0",
36
+ "ai": ">=7.0.0-canary.0 <8",
37
+ "eve": ">=0.11.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "ai": {
41
+ "optional": true
42
+ },
43
+ "eve": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "devDependencies": {
48
+ "@opentelemetry/api": "^1.9.0",
49
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
50
+ "@types/node": "^20.11.17",
51
+ "@vercel/otel": "^2.1.2",
52
+ "tsup": "^8.4.0",
53
+ "typescript": "^5.3.3",
54
+ "vitest": "^2.1.9",
55
+ "@raindrop-ai/core": "0.0.4"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public",
59
+ "provenance": false
60
+ },
61
+ "scripts": {
62
+ "build": "tsup",
63
+ "dev": "tsup --watch",
64
+ "clean": "rm -rf .turbo && rm -rf dist",
65
+ "test": "vitest run"
66
+ }
67
+ }