@raindrop-ai/wizard 0.0.1 → 0.0.2
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.
- package/dist/src/docs/claude-agent-sdk.mdx +382 -0
- package/dist/src/docs/{typescript.md → typescript.mdx} +8 -4
- package/dist/src/docs/vercel-ai-sdk.mdx +769 -0
- package/dist/src/lib/agent-interface.d.ts +4 -3
- package/dist/src/lib/agent-interface.js +290 -197
- package/dist/src/lib/agent-interface.js.map +1 -1
- package/dist/src/lib/constants.d.ts +1 -0
- package/dist/src/lib/constants.js +1 -0
- package/dist/src/lib/constants.js.map +1 -1
- package/dist/src/lib/handlers.d.ts +16 -8
- package/dist/src/lib/handlers.js +232 -118
- package/dist/src/lib/handlers.js.map +1 -1
- package/dist/src/lib/integration-testing.d.ts +5 -5
- package/dist/src/lib/integration-testing.js +28 -12
- package/dist/src/lib/integration-testing.js.map +1 -1
- package/dist/src/lib/mcp.d.ts +1 -1
- package/dist/src/lib/mcp.js +88 -49
- package/dist/src/lib/mcp.js.map +1 -1
- package/dist/src/lib/sdk-messages.d.ts +8 -1
- package/dist/src/lib/sdk-messages.js +83 -27
- package/dist/src/lib/sdk-messages.js.map +1 -1
- package/dist/src/lib/wizard.js +16 -20
- package/dist/src/lib/wizard.js.map +1 -1
- package/dist/src/ui/App.d.ts +5 -4
- package/dist/src/ui/App.js +12 -12
- package/dist/src/ui/App.js.map +1 -1
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.js +4 -2
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.js.map +1 -1
- package/dist/src/ui/components/ContinuePrompt.d.ts +3 -2
- package/dist/src/ui/components/ContinuePrompt.js +4 -4
- package/dist/src/ui/components/ContinuePrompt.js.map +1 -1
- package/dist/src/ui/components/DiffDisplay.js +16 -6
- package/dist/src/ui/components/DiffDisplay.js.map +1 -1
- package/dist/src/ui/components/FeedbackSelectPrompt.js +6 -3
- package/dist/src/ui/components/FeedbackSelectPrompt.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.d.ts +5 -3
- package/dist/src/ui/components/HistoryItemDisplay.js +10 -9
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/Logo.js +19 -34
- package/dist/src/ui/components/Logo.js.map +1 -1
- package/dist/src/ui/components/OrgInfoBox.d.ts +2 -1
- package/dist/src/ui/components/OrgInfoBox.js +2 -4
- package/dist/src/ui/components/OrgInfoBox.js.map +1 -1
- package/dist/src/ui/components/PersistentTextInput.js +13 -11
- package/dist/src/ui/components/PersistentTextInput.js.map +1 -1
- package/dist/src/ui/components/PromptContainer.js +4 -8
- package/dist/src/ui/components/PromptContainer.js.map +1 -1
- package/dist/src/ui/components/ToolApprovalPrompt.js +4 -4
- package/dist/src/ui/components/ToolApprovalPrompt.js.map +1 -1
- package/dist/src/ui/components/WriteKeyDisplay.d.ts +1 -1
- package/dist/src/ui/components/WriteKeyDisplay.js +1 -1
- package/dist/src/ui/components/WriteKeyDisplay.js.map +1 -1
- package/dist/src/ui/contexts/WizardContext.d.ts +13 -5
- package/dist/src/ui/contexts/WizardContext.js +60 -20
- package/dist/src/ui/contexts/WizardContext.js.map +1 -1
- package/dist/src/ui/render.js +49 -5
- package/dist/src/ui/render.js.map +1 -1
- package/dist/src/ui/types.d.ts +4 -2
- package/dist/src/ui/types.js.map +1 -1
- package/dist/src/utils/oauth.js +0 -4
- package/dist/src/utils/oauth.js.map +1 -1
- package/dist/src/utils/session.d.ts +1 -0
- package/dist/src/utils/session.js +40 -1
- package/dist/src/utils/session.js.map +1 -1
- package/dist/src/utils/ui.d.ts +7 -2
- package/dist/src/utils/ui.js +9 -2
- package/dist/src/utils/ui.js.map +1 -1
- package/package.json +2 -1
- package/dist/src/docs/vercel-ai-sdk.md +0 -304
- /package/dist/src/docs/{browser.md → browser.mdx} +0 -0
- /package/dist/src/docs/{python.md → python.mdx} +0 -0
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Vercel AI SDK
|
|
3
|
+
description: >-
|
|
4
|
+
Reference for integrating Raindrop with the Vercel AI SDK.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The `@raindrop-ai/ai-sdk` package instruments the
|
|
8
|
+
[Vercel AI SDK](https://sdk.vercel.ai/) so wrapped `generateText`, `streamText`,
|
|
9
|
+
`generateObject`, and `streamObject` calls can be tracked in Raindrop.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
<CodeGroup>
|
|
14
|
+
```bash npm
|
|
15
|
+
npm install @raindrop-ai/ai-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```bash yarn
|
|
19
|
+
yarn add @raindrop-ai/ai-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```bash pnpm
|
|
23
|
+
pnpm add @raindrop-ai/ai-sdk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash bun
|
|
27
|
+
bun add @raindrop-ai/ai-sdk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</CodeGroup>
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import * as ai from 'ai';
|
|
36
|
+
import { openai } from '@ai-sdk/openai';
|
|
37
|
+
import { createRaindropAISDK, eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
38
|
+
|
|
39
|
+
// 1. Create the Raindrop client
|
|
40
|
+
const raindrop = createRaindropAISDK({
|
|
41
|
+
writeKey: process.env.RAINDROP_WRITE_KEY!,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 2. Wrap the AI SDK with defaults
|
|
45
|
+
const { generateText, streamText } = raindrop.wrap(ai, {
|
|
46
|
+
context: {
|
|
47
|
+
userId, // REQUIRED: authenticated user id from your app
|
|
48
|
+
eventName, // Optional: e.g. 'chat_message'
|
|
49
|
+
properties: {
|
|
50
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 3. Use the wrapped methods
|
|
56
|
+
const result = await generateText({
|
|
57
|
+
model: openai('gpt-4o'),
|
|
58
|
+
prompt: 'Hello, world!',
|
|
59
|
+
// Optional: override wrap defaults for this call
|
|
60
|
+
experimental_telemetry: {
|
|
61
|
+
isEnabled: true,
|
|
62
|
+
metadata: eventMetadata({
|
|
63
|
+
userId, // REQUIRED
|
|
64
|
+
convoId, // Optional: conversation/thread id from your app
|
|
65
|
+
eventName, // Optional override for this call
|
|
66
|
+
properties: { source: requestSource },
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Optional: identify user traits
|
|
72
|
+
await raindrop.users.identify({
|
|
73
|
+
userId,
|
|
74
|
+
traits: { plan: userPlan },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 4. Flush before shutdown (serverless/scripts)
|
|
78
|
+
await raindrop.flush();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Wrapped `generateText`, `streamText`, `generateObject`, and `streamObject` calls
|
|
82
|
+
are now tracked.
|
|
83
|
+
|
|
84
|
+
## Runtime support
|
|
85
|
+
|
|
86
|
+
### Node.js
|
|
87
|
+
|
|
88
|
+
Use the default import:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { createRaindropAISDK } from '@raindrop-ai/ai-sdk';
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Cloudflare Workers
|
|
95
|
+
|
|
96
|
+
Cloudflare Workers supports a subset of Node’s `AsyncLocalStorage` when you
|
|
97
|
+
enable `nodejs_compat`
|
|
98
|
+
([docs](https://developers.cloudflare.com/workers/runtime-apis/nodejs/asynclocalstorage/)).
|
|
99
|
+
|
|
100
|
+
Import the Workers entrypoint:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { createRaindropAISDK } from '@raindrop-ai/ai-sdk/workers';
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
If `nodejs_compat` is not enabled, `node:async_hooks` is not available and
|
|
107
|
+
AsyncLocalStorage-based context propagation cannot work.
|
|
108
|
+
|
|
109
|
+
### Edge / browser runtimes
|
|
110
|
+
|
|
111
|
+
Edge/browser runtimes don’t provide Node `AsyncLocalStorage`. Event/traces
|
|
112
|
+
shipping still works, but context propagation across async boundaries is
|
|
113
|
+
disabled. If you need correlation across nested calls, pass an explicit
|
|
114
|
+
`eventId` with `eventMetadata()`.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
### Client Options
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const raindrop = createRaindropAISDK({
|
|
124
|
+
writeKey: process.env.RAINDROP_WRITE_KEY!, // REQUIRED
|
|
125
|
+
endpoint: 'https://api.raindrop.ai/v1', // Optional, defaults to production
|
|
126
|
+
|
|
127
|
+
traces: {
|
|
128
|
+
enabled: true, // Default: true
|
|
129
|
+
flushIntervalMs: 1000, // Default: 1000
|
|
130
|
+
maxBatchSize: 50, // Default: 50
|
|
131
|
+
debug: false, // Logs trace shipping
|
|
132
|
+
debugSpans: false, // Logs span parent relationships
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
events: {
|
|
136
|
+
enabled: true, // Default: true
|
|
137
|
+
partialFlushMs: 1000, // Debounce for streaming updates
|
|
138
|
+
debug: false, // Logs event shipping
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Wrap Options
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const { generateText } = raindrop.wrap(ai, {
|
|
147
|
+
// Set defaults at wrap-time. Override per call with eventMetadata().
|
|
148
|
+
context: {
|
|
149
|
+
userId, // REQUIRED when setting user context here
|
|
150
|
+
eventId, // Optional - use your own ID for correlation
|
|
151
|
+
eventName, // Optional - categorize events
|
|
152
|
+
convoId, // Optional - group events into conversations
|
|
153
|
+
properties: {
|
|
154
|
+
...defaultProperties,
|
|
155
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
156
|
+
}, // Optional - custom metadata
|
|
157
|
+
attachments: [], // Optional - input/output attachments
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
autoAttachment: true, // Default: true. Set false to disable automatic parsing
|
|
161
|
+
|
|
162
|
+
// Optional: customize event payload from messages
|
|
163
|
+
buildEvent: (messages) => ({
|
|
164
|
+
input: messages
|
|
165
|
+
.filter((m) => m.role === 'user')
|
|
166
|
+
.map((m) => m.content)
|
|
167
|
+
.join('\n'),
|
|
168
|
+
output: messages.filter((m) => m.role === 'assistant').pop()?.content,
|
|
169
|
+
properties: { messageCount: messages.length },
|
|
170
|
+
}),
|
|
171
|
+
|
|
172
|
+
// Optional: control what gets sent
|
|
173
|
+
send: {
|
|
174
|
+
events: true, // Default: true
|
|
175
|
+
traces: true, // Default: true
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Context & Metadata
|
|
183
|
+
|
|
184
|
+
### Per-Call Context Override (Recommended)
|
|
185
|
+
|
|
186
|
+
Set stable defaults in `wrap()` and override them per call with
|
|
187
|
+
`eventMetadata()` when needed:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { createRaindropAISDK, eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
191
|
+
|
|
192
|
+
// Wrap once with defaults
|
|
193
|
+
const { generateText } = raindrop.wrap(ai, {
|
|
194
|
+
context: {
|
|
195
|
+
userId,
|
|
196
|
+
eventName,
|
|
197
|
+
properties: {
|
|
198
|
+
app: appName,
|
|
199
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Override defaults for this call
|
|
205
|
+
const result = await generateText({
|
|
206
|
+
model: openai('gpt-4o'),
|
|
207
|
+
prompt: 'Hello!',
|
|
208
|
+
experimental_telemetry: {
|
|
209
|
+
isEnabled: true,
|
|
210
|
+
metadata: eventMetadata({
|
|
211
|
+
userId, // REQUIRED per call when explicitly tracking
|
|
212
|
+
convoId, // Conversation routing
|
|
213
|
+
eventName, // Event categorization
|
|
214
|
+
properties: { source: requestSource }, // Additional metadata
|
|
215
|
+
// eventId, // Optional explicit eventId for correlation
|
|
216
|
+
}),
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This keeps common values in one place while allowing each request to set or
|
|
222
|
+
override `userId`, `convoId`, `eventName`, `properties`, or `eventId`.
|
|
223
|
+
|
|
224
|
+
**Merge behavior:**
|
|
225
|
+
|
|
226
|
+
- Call-time values override wrap-time defaults
|
|
227
|
+
- `properties` are merged (call-time wins on conflicts)
|
|
228
|
+
- `eventId` is auto-generated if neither wrap-time nor call-time sets it
|
|
229
|
+
|
|
230
|
+
If you need full manual control over attachments, set `autoAttachment: false` in
|
|
231
|
+
`wrap()`.
|
|
232
|
+
|
|
233
|
+
<Info>
|
|
234
|
+
`context` in `wrap()` is optional and useful for defaults. If both are
|
|
235
|
+
present, call-time `eventMetadata()` wins.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const { generateText } = raindrop.wrap(ai, {
|
|
239
|
+
context: {
|
|
240
|
+
userId, // Fallback defaults
|
|
241
|
+
properties: {
|
|
242
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await generateText({
|
|
248
|
+
model,
|
|
249
|
+
prompt,
|
|
250
|
+
experimental_telemetry: {
|
|
251
|
+
isEnabled: true,
|
|
252
|
+
metadata: eventMetadata({ userId: actualUserId }),
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
</Info>
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Identifying Users
|
|
261
|
+
|
|
262
|
+
Use `users.identify` to associate traits with a user.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
await raindrop.users.identify({
|
|
266
|
+
userId,
|
|
267
|
+
traits: {
|
|
268
|
+
email: userEmail,
|
|
269
|
+
plan: userPlan,
|
|
270
|
+
orgId,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Tools
|
|
278
|
+
|
|
279
|
+
Tools are automatically wrapped and traced. No additional configuration needed:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { tool } from 'ai';
|
|
283
|
+
import { z } from 'zod';
|
|
284
|
+
import { eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
285
|
+
|
|
286
|
+
const weatherTool = tool({
|
|
287
|
+
description: 'Get current weather',
|
|
288
|
+
parameters: z.object({ city: z.string() }),
|
|
289
|
+
execute: async ({ city }) => {
|
|
290
|
+
const data = await fetchWeather(city);
|
|
291
|
+
return { temperature: data.temp, conditions: data.conditions };
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const { generateText } = raindrop.wrap(ai, {
|
|
296
|
+
context: {
|
|
297
|
+
properties: {
|
|
298
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Tool calls are traced with args and results
|
|
304
|
+
const result = await generateText({
|
|
305
|
+
model: openai('gpt-4o'),
|
|
306
|
+
prompt: "What's the weather in Tokyo?",
|
|
307
|
+
tools: { weather: weatherTool },
|
|
308
|
+
maxSteps: 3,
|
|
309
|
+
experimental_telemetry: {
|
|
310
|
+
isEnabled: true,
|
|
311
|
+
metadata: eventMetadata({
|
|
312
|
+
userId,
|
|
313
|
+
eventName,
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Nested LLM Calls in Tools
|
|
320
|
+
|
|
321
|
+
If your tool makes additional LLM calls, wrap them too for full trace
|
|
322
|
+
visibility:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import { eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
326
|
+
|
|
327
|
+
const summarizeTool = tool({
|
|
328
|
+
description: 'Summarize text using AI',
|
|
329
|
+
parameters: z.object({ text: z.string() }),
|
|
330
|
+
execute: async ({ text }) => {
|
|
331
|
+
// Inner wrapper - traces only, no separate event
|
|
332
|
+
const { generateText: innerGenerate } = raindrop.wrap(ai, {
|
|
333
|
+
context: {
|
|
334
|
+
properties: {
|
|
335
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
send: { events: false, traces: true },
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const summary = await innerGenerate({
|
|
342
|
+
model: openai('gpt-4o-mini'),
|
|
343
|
+
prompt: `Summarize: ${text}`,
|
|
344
|
+
experimental_telemetry: {
|
|
345
|
+
isEnabled: true,
|
|
346
|
+
metadata: eventMetadata({ userId, eventId }), // Same eventId links traces
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
return summary.text;
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Streaming
|
|
358
|
+
|
|
359
|
+
Streaming methods (`streamText`, `streamObject`) work identically:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
363
|
+
|
|
364
|
+
const { streamText } = raindrop.wrap(ai, {
|
|
365
|
+
context: {
|
|
366
|
+
properties: {
|
|
367
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const result = await streamText({
|
|
373
|
+
model: openai('gpt-4o'),
|
|
374
|
+
prompt: 'Write a haiku about coding',
|
|
375
|
+
experimental_telemetry: {
|
|
376
|
+
isEnabled: true,
|
|
377
|
+
metadata: eventMetadata({
|
|
378
|
+
userId,
|
|
379
|
+
eventName,
|
|
380
|
+
}),
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Consume the stream
|
|
385
|
+
for await (const chunk of result.textStream) {
|
|
386
|
+
process.stdout.write(chunk);
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Raindrop captures streaming-specific metrics:
|
|
391
|
+
|
|
392
|
+
- Time to first chunk (`ai.stream.msToFirstChunk`)
|
|
393
|
+
- Total stream duration (`ai.stream.msToFinish`)
|
|
394
|
+
- Average output tokens per second (`ai.stream.avgOutputTokensPerSecond`)
|
|
395
|
+
|
|
396
|
+
### streamObject
|
|
397
|
+
|
|
398
|
+
`streamObject` works out of the box. You can await `result.object` directly
|
|
399
|
+
without manually consuming the stream:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
403
|
+
|
|
404
|
+
const { streamObject } = raindrop.wrap(ai, {
|
|
405
|
+
context: {
|
|
406
|
+
properties: {
|
|
407
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const result = await streamObject({
|
|
413
|
+
model: openai('gpt-4o'),
|
|
414
|
+
schema: z.object({ name: z.string(), age: z.number() }),
|
|
415
|
+
prompt: 'Generate a person named Alice who is 30',
|
|
416
|
+
experimental_telemetry: {
|
|
417
|
+
isEnabled: true,
|
|
418
|
+
metadata: eventMetadata({
|
|
419
|
+
userId,
|
|
420
|
+
eventName,
|
|
421
|
+
}),
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// This works without consuming partialObjectStream
|
|
426
|
+
const person = await result.object;
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## ToolLoopAgent
|
|
432
|
+
|
|
433
|
+
<Info>Requires AI SDK v6 or later.</Info>
|
|
434
|
+
|
|
435
|
+
ToolLoopAgent is wrapped automatically. Both `generate()` and `stream()` trace
|
|
436
|
+
tool calls with their arguments and results.
|
|
437
|
+
|
|
438
|
+
### generate
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { ToolLoopAgent, tool } from 'ai';
|
|
442
|
+
import { openai } from '@ai-sdk/openai';
|
|
443
|
+
import { z } from 'zod';
|
|
444
|
+
import { createRaindropAISDK, eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
445
|
+
|
|
446
|
+
const raindrop = createRaindropAISDK({
|
|
447
|
+
writeKey: process.env.RAINDROP_WRITE_KEY!,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const { ToolLoopAgent } = raindrop.wrap(ai, {
|
|
451
|
+
context: {
|
|
452
|
+
properties: {
|
|
453
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const weatherTool = tool({
|
|
459
|
+
description: 'Get current weather',
|
|
460
|
+
parameters: z.object({ city: z.string() }),
|
|
461
|
+
execute: async ({ city }) => {
|
|
462
|
+
return { temperature: 72, conditions: 'sunny' };
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
const agent = new ToolLoopAgent({
|
|
467
|
+
model: openai('gpt-4o'),
|
|
468
|
+
tools: { weather: weatherTool },
|
|
469
|
+
maxSteps: 5,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const result = await agent.generate({
|
|
473
|
+
prompt: "What's the weather in Tokyo?",
|
|
474
|
+
experimental_telemetry: {
|
|
475
|
+
isEnabled: true,
|
|
476
|
+
metadata: eventMetadata({
|
|
477
|
+
userId,
|
|
478
|
+
eventName,
|
|
479
|
+
}),
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### stream
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import { eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
488
|
+
|
|
489
|
+
const result = await agent.stream({
|
|
490
|
+
prompt: "What's the weather in Tokyo?",
|
|
491
|
+
experimental_telemetry: {
|
|
492
|
+
isEnabled: true,
|
|
493
|
+
metadata: eventMetadata({
|
|
494
|
+
userId,
|
|
495
|
+
eventName,
|
|
496
|
+
}),
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
for await (const chunk of result.textStream) {
|
|
501
|
+
process.stdout.write(chunk);
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Signals (Feedback)
|
|
508
|
+
|
|
509
|
+
Track user feedback on AI responses using the same `eventId`:
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
import { eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
513
|
+
|
|
514
|
+
const eventId = crypto.randomUUID();
|
|
515
|
+
|
|
516
|
+
const { generateText } = raindrop.wrap(ai, {
|
|
517
|
+
context: {
|
|
518
|
+
properties: {
|
|
519
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const result = await generateText({
|
|
525
|
+
model: openai('gpt-4o'),
|
|
526
|
+
prompt: userMessage,
|
|
527
|
+
experimental_telemetry: {
|
|
528
|
+
isEnabled: true,
|
|
529
|
+
metadata: eventMetadata({
|
|
530
|
+
userId,
|
|
531
|
+
eventId,
|
|
532
|
+
eventName,
|
|
533
|
+
}),
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Later, when user gives feedback
|
|
538
|
+
await raindrop.signals.track({
|
|
539
|
+
eventId,
|
|
540
|
+
name: 'thumbs_down',
|
|
541
|
+
comment: 'Answer was incorrect',
|
|
542
|
+
});
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Signal Types
|
|
546
|
+
|
|
547
|
+
| Type | Use Case |
|
|
548
|
+
| ------------ | -------------------------------- |
|
|
549
|
+
| `"default"` | Generic signals (thumbs up/down) |
|
|
550
|
+
| `"feedback"` | User comments about quality |
|
|
551
|
+
| `"edit"` | User corrected the output |
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
// Thumbs up
|
|
555
|
+
await raindrop.signals.track({
|
|
556
|
+
eventId,
|
|
557
|
+
name: 'thumbs_up',
|
|
558
|
+
sentiment: 'POSITIVE',
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// User edit
|
|
562
|
+
await raindrop.signals.track({
|
|
563
|
+
eventId,
|
|
564
|
+
name: 'user_edit',
|
|
565
|
+
type: 'edit',
|
|
566
|
+
after: 'The corrected response text',
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Manual Event Updates
|
|
573
|
+
|
|
574
|
+
Update events after they're created:
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
// Add properties
|
|
578
|
+
await raindrop.events.setProperties(eventId, {
|
|
579
|
+
latencyMs: 1234,
|
|
580
|
+
cached: true,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Add attachments
|
|
584
|
+
await raindrop.events.addAttachments(eventId, [
|
|
585
|
+
{
|
|
586
|
+
type: 'code',
|
|
587
|
+
name: 'generated.py',
|
|
588
|
+
value: "print('hello')",
|
|
589
|
+
role: 'output',
|
|
590
|
+
language: 'python',
|
|
591
|
+
},
|
|
592
|
+
]);
|
|
593
|
+
|
|
594
|
+
// Mark as complete with final output
|
|
595
|
+
await raindrop.events.finish(eventId, {
|
|
596
|
+
output: 'Final response',
|
|
597
|
+
model: 'gpt-4o',
|
|
598
|
+
});
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## Custom Event Builder
|
|
604
|
+
|
|
605
|
+
Use `buildEvent` to customize the event payload from the conversation messages:
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
import { eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
609
|
+
|
|
610
|
+
const { generateText } = raindrop.wrap(ai, {
|
|
611
|
+
context: {
|
|
612
|
+
properties: {
|
|
613
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
buildEvent: (messages) => {
|
|
617
|
+
const userMessages = messages.filter((m) => m.role === 'user');
|
|
618
|
+
const assistantMessages = messages.filter((m) => m.role === 'assistant');
|
|
619
|
+
|
|
620
|
+
return {
|
|
621
|
+
input: userMessages.map((m) => m.content).join('\n'),
|
|
622
|
+
output: assistantMessages.pop()?.content,
|
|
623
|
+
eventName: customEventName,
|
|
624
|
+
properties: {
|
|
625
|
+
turnCount: userMessages.length,
|
|
626
|
+
hasToolCalls: messages.some((m) => m.role === 'tool'),
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
await generateText({
|
|
633
|
+
model: openai('gpt-4o'),
|
|
634
|
+
prompt: 'Summarize this conversation',
|
|
635
|
+
experimental_telemetry: {
|
|
636
|
+
isEnabled: true,
|
|
637
|
+
metadata: eventMetadata({ userId, eventName }),
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## Flush & Shutdown
|
|
645
|
+
|
|
646
|
+
Always flush before your process exits to ensure all data is sent:
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
// Serverless: flush at end of request
|
|
650
|
+
export async function POST(request: Request) {
|
|
651
|
+
const result = await generateText({ ... });
|
|
652
|
+
await raindrop.flush();
|
|
653
|
+
return Response.json({ text: result.text });
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Long-running: shutdown gracefully
|
|
657
|
+
process.on("SIGTERM", async () => {
|
|
658
|
+
await raindrop.shutdown();
|
|
659
|
+
process.exit(0);
|
|
660
|
+
});
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## Debugging
|
|
666
|
+
|
|
667
|
+
Enable debug logging to troubleshoot issues:
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
const raindrop = createRaindropAISDK({
|
|
671
|
+
writeKey: process.env.RAINDROP_WRITE_KEY!,
|
|
672
|
+
events: { debug: true },
|
|
673
|
+
traces: { debug: true, debugSpans: true },
|
|
674
|
+
});
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
This logs:
|
|
678
|
+
|
|
679
|
+
- Every event sent to Raindrop
|
|
680
|
+
- Every trace batch shipped
|
|
681
|
+
- Span parent/child relationships (with `debugSpans`)
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## Advanced: Context Propagation APIs
|
|
686
|
+
|
|
687
|
+
For advanced use cases, access the underlying context propagation utilities:
|
|
688
|
+
|
|
689
|
+
<Info>
|
|
690
|
+
These APIs use AsyncLocalStorage and are only effective in Node.js (and
|
|
691
|
+
Cloudflare Workers with `nodejs_compat`). In Edge/browser runtimes they behave
|
|
692
|
+
like no-ops.
|
|
693
|
+
</Info>
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
import {
|
|
697
|
+
currentSpan,
|
|
698
|
+
withCurrent,
|
|
699
|
+
getContextManager,
|
|
700
|
+
} from '@raindrop-ai/ai-sdk';
|
|
701
|
+
|
|
702
|
+
// Get the current span (returns NOOP_SPAN if none)
|
|
703
|
+
const span = currentSpan();
|
|
704
|
+
console.log(span.eventId, span.traceIdB64);
|
|
705
|
+
|
|
706
|
+
// Run code within a span context
|
|
707
|
+
withCurrent(span, () => {
|
|
708
|
+
// Nested calls inherit this span as parent
|
|
709
|
+
const nested = currentSpan();
|
|
710
|
+
console.log(nested.spanIdB64 === span.spanIdB64); // true
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// Access the context manager directly
|
|
714
|
+
const cm = getContextManager();
|
|
715
|
+
const parentIds = cm.getParentSpanIds();
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## Troubleshooting
|
|
721
|
+
|
|
722
|
+
### Events not appearing in dashboard
|
|
723
|
+
|
|
724
|
+
1. **Check your write key** - Ensure `RAINDROP_WRITE_KEY` is set correctly
|
|
725
|
+
2. **Flush before exit** - Call `await raindrop.flush()` before your process
|
|
726
|
+
ends
|
|
727
|
+
3. **Enable debug logging** - Set `events: { debug: true }` to see what's being
|
|
728
|
+
sent
|
|
729
|
+
|
|
730
|
+
### Traces missing or incomplete
|
|
731
|
+
|
|
732
|
+
1. **Enable trace debugging** - Set `traces: { debug: true, debugSpans: true }`
|
|
733
|
+
2. **Check for errors** - Look for `[raindrop-ai/ai-sdk]` prefixed logs
|
|
734
|
+
|
|
735
|
+
### Context not propagating to nested calls
|
|
736
|
+
|
|
737
|
+
1. **Check your runtime** - AsyncLocalStorage-based propagation requires Node.js
|
|
738
|
+
(or Cloudflare Workers with `nodejs_compat`)
|
|
739
|
+
2. **Workers import** - On Cloudflare Workers, import
|
|
740
|
+
`@raindrop-ai/ai-sdk/workers`
|
|
741
|
+
|
|
742
|
+
Ensure nested AI calls use the same `eventId` and set `send: { events: false }`
|
|
743
|
+
to avoid duplicate events:
|
|
744
|
+
|
|
745
|
+
```typescript
|
|
746
|
+
import { eventMetadata } from '@raindrop-ai/ai-sdk';
|
|
747
|
+
|
|
748
|
+
const { generateText: innerGenerate } = raindrop.wrap(ai, {
|
|
749
|
+
context: {
|
|
750
|
+
properties: {
|
|
751
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // REQUIRED
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
send: { events: false, traces: true },
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
await innerGenerate({
|
|
758
|
+
model,
|
|
759
|
+
prompt,
|
|
760
|
+
experimental_telemetry: {
|
|
761
|
+
isEnabled: true,
|
|
762
|
+
metadata: eventMetadata({ userId, eventId }),
|
|
763
|
+
},
|
|
764
|
+
});
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
---
|
|
768
|
+
|
|
769
|
+
Use the Raindrop dashboard to verify events and traces after integration.
|