@raindrop-ai/wizard 0.0.1
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/LICENSE +47 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +117 -0
- package/dist/bin.js.map +1 -0
- package/dist/src/docs/browser.md +105 -0
- package/dist/src/docs/python.md +618 -0
- package/dist/src/docs/typescript.md +584 -0
- package/dist/src/docs/vercel-ai-sdk.md +304 -0
- package/dist/src/lib/agent-interface.d.ts +46 -0
- package/dist/src/lib/agent-interface.js +292 -0
- package/dist/src/lib/agent-interface.js.map +1 -0
- package/dist/src/lib/agent-prompts.d.ts +10 -0
- package/dist/src/lib/agent-prompts.js +49 -0
- package/dist/src/lib/agent-prompts.js.map +1 -0
- package/dist/src/lib/config.d.ts +39 -0
- package/dist/src/lib/config.js +549 -0
- package/dist/src/lib/config.js.map +1 -0
- package/dist/src/lib/constants.d.ts +27 -0
- package/dist/src/lib/constants.js +165 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/handlers.d.ts +68 -0
- package/dist/src/lib/handlers.js +420 -0
- package/dist/src/lib/handlers.js.map +1 -0
- package/dist/src/lib/integration-testing.d.ts +44 -0
- package/dist/src/lib/integration-testing.js +123 -0
- package/dist/src/lib/integration-testing.js.map +1 -0
- package/dist/src/lib/mcp.d.ts +14 -0
- package/dist/src/lib/mcp.js +134 -0
- package/dist/src/lib/mcp.js.map +1 -0
- package/dist/src/lib/sdk-messages.d.ts +17 -0
- package/dist/src/lib/sdk-messages.js +278 -0
- package/dist/src/lib/sdk-messages.js.map +1 -0
- package/dist/src/lib/wizard.d.ts +6 -0
- package/dist/src/lib/wizard.js +131 -0
- package/dist/src/lib/wizard.js.map +1 -0
- package/dist/src/run.d.ts +8 -0
- package/dist/src/run.js +53 -0
- package/dist/src/run.js.map +1 -0
- package/dist/src/ui/App.d.ts +15 -0
- package/dist/src/ui/App.js +27 -0
- package/dist/src/ui/App.js.map +1 -0
- package/dist/src/ui/cancellation.d.ts +14 -0
- package/dist/src/ui/cancellation.js +17 -0
- package/dist/src/ui/cancellation.js.map +1 -0
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.d.ts +17 -0
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.js +359 -0
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.js.map +1 -0
- package/dist/src/ui/components/ContinuePrompt.d.ts +14 -0
- package/dist/src/ui/components/ContinuePrompt.js +23 -0
- package/dist/src/ui/components/ContinuePrompt.js.map +1 -0
- package/dist/src/ui/components/DiffDisplay.d.ts +18 -0
- package/dist/src/ui/components/DiffDisplay.js +110 -0
- package/dist/src/ui/components/DiffDisplay.js.map +1 -0
- package/dist/src/ui/components/FeedbackSelectPrompt.d.ts +20 -0
- package/dist/src/ui/components/FeedbackSelectPrompt.js +132 -0
- package/dist/src/ui/components/FeedbackSelectPrompt.js.map +1 -0
- package/dist/src/ui/components/HistoryItemDisplay.d.ts +14 -0
- package/dist/src/ui/components/HistoryItemDisplay.js +140 -0
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -0
- package/dist/src/ui/components/Logo.d.ts +10 -0
- package/dist/src/ui/components/Logo.js +47 -0
- package/dist/src/ui/components/Logo.js.map +1 -0
- package/dist/src/ui/components/OrgInfoBox.d.ts +11 -0
- package/dist/src/ui/components/OrgInfoBox.js +16 -0
- package/dist/src/ui/components/OrgInfoBox.js.map +1 -0
- package/dist/src/ui/components/PendingPrompt.d.ts +18 -0
- package/dist/src/ui/components/PendingPrompt.js +57 -0
- package/dist/src/ui/components/PendingPrompt.js.map +1 -0
- package/dist/src/ui/components/PersistentTextInput.d.ts +21 -0
- package/dist/src/ui/components/PersistentTextInput.js +117 -0
- package/dist/src/ui/components/PersistentTextInput.js.map +1 -0
- package/dist/src/ui/components/PlanApprovalPrompt.d.ts +19 -0
- package/dist/src/ui/components/PlanApprovalPrompt.js +62 -0
- package/dist/src/ui/components/PlanApprovalPrompt.js.map +1 -0
- package/dist/src/ui/components/PromptContainer.d.ts +14 -0
- package/dist/src/ui/components/PromptContainer.js +18 -0
- package/dist/src/ui/components/PromptContainer.js.map +1 -0
- package/dist/src/ui/components/SelectPrompt.d.ts +14 -0
- package/dist/src/ui/components/SelectPrompt.js +62 -0
- package/dist/src/ui/components/SelectPrompt.js.map +1 -0
- package/dist/src/ui/components/SpinnerDisplay.d.ts +13 -0
- package/dist/src/ui/components/SpinnerDisplay.js +11 -0
- package/dist/src/ui/components/SpinnerDisplay.js.map +1 -0
- package/dist/src/ui/components/ToolApprovalPrompt.d.ts +14 -0
- package/dist/src/ui/components/ToolApprovalPrompt.js +142 -0
- package/dist/src/ui/components/ToolApprovalPrompt.js.map +1 -0
- package/dist/src/ui/components/ToolCallDisplay.d.ts +14 -0
- package/dist/src/ui/components/ToolCallDisplay.js +83 -0
- package/dist/src/ui/components/ToolCallDisplay.js.map +1 -0
- package/dist/src/ui/components/WriteKeyDisplay.d.ts +15 -0
- package/dist/src/ui/components/WriteKeyDisplay.js +13 -0
- package/dist/src/ui/components/WriteKeyDisplay.js.map +1 -0
- package/dist/src/ui/contexts/WizardContext.d.ts +210 -0
- package/dist/src/ui/contexts/WizardContext.js +362 -0
- package/dist/src/ui/contexts/WizardContext.js.map +1 -0
- package/dist/src/ui/hooks/useCancellation.d.ts +15 -0
- package/dist/src/ui/hooks/useCancellation.js +25 -0
- package/dist/src/ui/hooks/useCancellation.js.map +1 -0
- package/dist/src/ui/render.d.ts +34 -0
- package/dist/src/ui/render.js +94 -0
- package/dist/src/ui/render.js.map +1 -0
- package/dist/src/ui/types.d.ts +184 -0
- package/dist/src/ui/types.js +6 -0
- package/dist/src/ui/types.js.map +1 -0
- package/dist/src/utils/clack-utils.d.ts +13 -0
- package/dist/src/utils/clack-utils.js +131 -0
- package/dist/src/utils/clack-utils.js.map +1 -0
- package/dist/src/utils/debug.d.ts +13 -0
- package/dist/src/utils/debug.js +47 -0
- package/dist/src/utils/debug.js.map +1 -0
- package/dist/src/utils/environment.d.ts +5 -0
- package/dist/src/utils/environment.js +131 -0
- package/dist/src/utils/environment.js.map +1 -0
- package/dist/src/utils/logging.d.ts +9 -0
- package/dist/src/utils/logging.js +38 -0
- package/dist/src/utils/logging.js.map +1 -0
- package/dist/src/utils/oauth.d.ts +12 -0
- package/dist/src/utils/oauth.js +497 -0
- package/dist/src/utils/oauth.js.map +1 -0
- package/dist/src/utils/package-json-types.d.ts +44 -0
- package/dist/src/utils/package-json-types.js +6 -0
- package/dist/src/utils/package-json-types.js.map +1 -0
- package/dist/src/utils/package-json.d.ts +19 -0
- package/dist/src/utils/package-json.js +22 -0
- package/dist/src/utils/package-json.js.map +1 -0
- package/dist/src/utils/session.d.ts +2 -0
- package/dist/src/utils/session.js +87 -0
- package/dist/src/utils/session.js.map +1 -0
- package/dist/src/utils/types.d.ts +61 -0
- package/dist/src/utils/types.js +2 -0
- package/dist/src/utils/types.js.map +1 -0
- package/dist/src/utils/ui.d.ts +120 -0
- package/dist/src/utils/ui.js +164 -0
- package/dist/src/utils/ui.js.map +1 -0
- package/package.json +140 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
## Vercel AI SDK Integration
|
|
2
|
+
|
|
3
|
+
Two-step setup:
|
|
4
|
+
|
|
5
|
+
1. Configure OpenTelemetry trace exporter (platform-specific)
|
|
6
|
+
2. Instrument AI SDK calls with Raindrop metadata
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Step 1: Configure OTEL Trace Exporter
|
|
11
|
+
|
|
12
|
+
> Choose the section matching the project setup:
|
|
13
|
+
>
|
|
14
|
+
> - **Next.js + Sentry** → [With Sentry (Next.js)](#with-sentry-nextjs)
|
|
15
|
+
> - **Next.js (no Sentry)** → [Next.js](#nextjs)
|
|
16
|
+
> - **Node.js** → [Node.js](#nodejs)
|
|
17
|
+
> - **Cloudflare Workers** → [Cloudflare Workers](#cloudflare-workers)
|
|
18
|
+
|
|
19
|
+
### With Sentry (Next.js)
|
|
20
|
+
|
|
21
|
+
If already using Sentry, add Raindrop's exporter to Sentry's OTEL config to
|
|
22
|
+
avoid duplicate registration issues.
|
|
23
|
+
|
|
24
|
+
**Install dependencies:**
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @opentelemetry/exporter-trace-otlp-proto @opentelemetry/sdk-trace-base
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Configure:**
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// sentry.server.config.ts
|
|
34
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
|
35
|
+
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
36
|
+
import * as Sentry from '@sentry/nextjs';
|
|
37
|
+
|
|
38
|
+
Sentry.init({
|
|
39
|
+
dsn: process.env.SENTRY_DSN,
|
|
40
|
+
tracesSampleRate: 1,
|
|
41
|
+
openTelemetrySpanProcessors: [
|
|
42
|
+
new BatchSpanProcessor(
|
|
43
|
+
new OTLPTraceExporter({
|
|
44
|
+
url: 'https://api.raindrop.ai/v1/traces',
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${process.env.RAINDROP_WRITE_KEY}`,
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Next.js
|
|
55
|
+
|
|
56
|
+
**Install dependencies:**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install raindrop-ai @opentelemetry/api @opentelemetry/sdk-trace-base @vercel/otel
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Configure:**
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// instrumentation.ts
|
|
66
|
+
import { registerOTel, OTLPHttpProtoTraceExporter } from '@vercel/otel';
|
|
67
|
+
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
68
|
+
|
|
69
|
+
export function register() {
|
|
70
|
+
registerOTel({
|
|
71
|
+
serviceName: projectName, // Replace with your app/service name
|
|
72
|
+
spanProcessors: [
|
|
73
|
+
new BatchSpanProcessor(
|
|
74
|
+
new OTLPHttpProtoTraceExporter({
|
|
75
|
+
url: 'https://api.raindrop.ai/v1/traces',
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${process.env.RAINDROP_WRITE_KEY}`,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> Error: "Cannot execute the operation on ended Span" → Use `runtime = 'nodejs'`
|
|
87
|
+
> instead of `'edge'`.
|
|
88
|
+
|
|
89
|
+
### Node.js
|
|
90
|
+
|
|
91
|
+
**Install dependencies:**
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm install raindrop-ai @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-proto @opentelemetry/sdk-trace-node
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Configure:**
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
101
|
+
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
|
|
102
|
+
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
|
103
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
|
104
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
105
|
+
|
|
106
|
+
const sdk = new NodeSDK({
|
|
107
|
+
resource: resourceFromAttributes({
|
|
108
|
+
[ATTR_SERVICE_NAME]: projectName, // Replace with your app/service name
|
|
109
|
+
}),
|
|
110
|
+
spanProcessors: [
|
|
111
|
+
new BatchSpanProcessor(
|
|
112
|
+
new OTLPTraceExporter({
|
|
113
|
+
url: 'https://api.raindrop.ai/v1/traces',
|
|
114
|
+
headers: {
|
|
115
|
+
Authorization: `Bearer ${process.env.RAINDROP_WRITE_KEY}`,
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
),
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
sdk.start();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Cloudflare Workers
|
|
126
|
+
|
|
127
|
+
Cloudflare's native tracing does not support custom spans. Use
|
|
128
|
+
`@microlabs/otel-cf-workers`.
|
|
129
|
+
|
|
130
|
+
**Install dependencies:**
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm install raindrop-ai @opentelemetry/api @microlabs/otel-cf-workers
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Wrangler config:**
|
|
137
|
+
|
|
138
|
+
```toml
|
|
139
|
+
compatibility_flags = ["nodejs_compat"]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**OTEL config:**
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// src/otel.ts
|
|
146
|
+
import { instrument, type ResolveConfigFn } from '@microlabs/otel-cf-workers';
|
|
147
|
+
|
|
148
|
+
export interface Env {
|
|
149
|
+
RAINDROP_WRITE_KEY: string;
|
|
150
|
+
[key: string]: unknown;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const otelConfig: ResolveConfigFn<Env> = (env, _trigger) => ({
|
|
154
|
+
exporter: {
|
|
155
|
+
url: 'https://api.raindrop.ai/v1/traces',
|
|
156
|
+
headers: {
|
|
157
|
+
Authorization: `Bearer ${env.RAINDROP_WRITE_KEY}`,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
service: {
|
|
161
|
+
name: workerName, // Replace with your worker/service name
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export { instrument };
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Instrument handler:**
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// src/index.ts — adapt AI provider imports to your project
|
|
172
|
+
import { streamText } from 'ai';
|
|
173
|
+
import { createOpenAI } from '@ai-sdk/openai'; // Adapt to your AI provider (e.g. @ai-sdk/anthropic)
|
|
174
|
+
import raindrop from 'raindrop-ai/otel';
|
|
175
|
+
import { instrument, otelConfig, Env } from './otel';
|
|
176
|
+
|
|
177
|
+
const handler = {
|
|
178
|
+
async fetch(
|
|
179
|
+
request: Request,
|
|
180
|
+
env: Env,
|
|
181
|
+
ctx: ExecutionContext,
|
|
182
|
+
): Promise<Response> {
|
|
183
|
+
const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY }); // Adapt to your AI provider
|
|
184
|
+
const { prompt } = await request.json();
|
|
185
|
+
|
|
186
|
+
const result = streamText({
|
|
187
|
+
model: model, // Your app's AI model instance (e.g. 'gpt-4o','claude-3-opus')
|
|
188
|
+
prompt,
|
|
189
|
+
experimental_telemetry: {
|
|
190
|
+
isEnabled: true,
|
|
191
|
+
functionId: 'chat',
|
|
192
|
+
metadata: {
|
|
193
|
+
...raindrop.metadata({ userId: userId }), // The authenticated user's ID from your app
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
ctx.waitUntil(result.text);
|
|
199
|
+
return result.toTextStreamResponse();
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export default instrument(handler, otelConfig);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Step 2: Instrumentation
|
|
209
|
+
|
|
210
|
+
### Requirements
|
|
211
|
+
|
|
212
|
+
1. Enable telemetry at **all** AI SDK call sites:
|
|
213
|
+
`experimental_telemetry: { isEnabled: true }`
|
|
214
|
+
2. Add Raindrop metadata at top-level call using `raindrop.metadata()`
|
|
215
|
+
|
|
216
|
+
### Usage
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Adapt imports to your project's AI provider (e.g. @ai-sdk/openai, @ai-sdk/anthropic, etc.)
|
|
220
|
+
import { generateText, tool } from 'ai';
|
|
221
|
+
import { z } from 'zod';
|
|
222
|
+
import raindrop from 'raindrop-ai/otel';
|
|
223
|
+
|
|
224
|
+
// Nested call - only needs isEnabled: true
|
|
225
|
+
const enhanceStory = tool({
|
|
226
|
+
description: 'Enhance story',
|
|
227
|
+
parameters: z.object({ story: z.string() }),
|
|
228
|
+
execute: async ({ story }) => {
|
|
229
|
+
const enhanced = await generateText({
|
|
230
|
+
model: model, // Your app's AI model instance (e.g. 'gpt-4o','claude-3-opus')
|
|
231
|
+
prompt: `Enhance: ${story}`,
|
|
232
|
+
experimental_telemetry: {
|
|
233
|
+
isEnabled: true, // Required at all call sites
|
|
234
|
+
functionId: 'enhance-story',
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
return { enhancedStory: enhanced.text };
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Top-level call - add raindrop.metadata()
|
|
242
|
+
const result = await generateText({
|
|
243
|
+
model: model, // Your app's AI model instance
|
|
244
|
+
prompt: userPrompt,
|
|
245
|
+
tools: { enhanceStory },
|
|
246
|
+
experimental_telemetry: {
|
|
247
|
+
isEnabled: true, // Required
|
|
248
|
+
functionId: 'generate-text',
|
|
249
|
+
metadata: {
|
|
250
|
+
...raindrop.metadata({
|
|
251
|
+
userId: userId, // Required — the authenticated user's ID from your app
|
|
252
|
+
eventName: eventName, // A descriptive name for this AI action (e.g. 'chat_message', 'code_generation')
|
|
253
|
+
convoId: conversationId, // Your app's conversation/thread ID (if applicable)
|
|
254
|
+
wizardSession: '__WIZARD_SESSION_UUID__', // Required
|
|
255
|
+
}),
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Key Points
|
|
262
|
+
|
|
263
|
+
- **All call sites** need `experimental_telemetry: { isEnabled: true }`
|
|
264
|
+
- **Top-level call** adds `raindrop.metadata()` to capture user/event info
|
|
265
|
+
- **Nested calls** only need `isEnabled: true`
|
|
266
|
+
|
|
267
|
+
## Troubleshooting
|
|
268
|
+
|
|
269
|
+
### Debug Logging
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
OTEL_LOG_LEVEL=debug npm run dev
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Missing Traces
|
|
276
|
+
|
|
277
|
+
Ensure `experimental_telemetry: { isEnabled: true }` at **every** AI SDK call:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// ❌ Not traced
|
|
281
|
+
const result = await generateText({ model: openai('gpt-4o'), prompt: 'Hello' });
|
|
282
|
+
|
|
283
|
+
// ✅ Traced
|
|
284
|
+
const result = await generateText({
|
|
285
|
+
model: openai('gpt-4o'),
|
|
286
|
+
prompt: 'Hello',
|
|
287
|
+
experimental_telemetry: { isEnabled: true },
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Cloudflare Workers: Incomplete Spans
|
|
292
|
+
|
|
293
|
+
Streaming responses need `waitUntil()` to prevent premature flush:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// ❌ Incomplete spans (1ms duration)
|
|
297
|
+
const result = streamText({ ... });
|
|
298
|
+
return result.toTextStreamResponse();
|
|
299
|
+
|
|
300
|
+
// ✅ Complete spans
|
|
301
|
+
const result = streamText({ ... });
|
|
302
|
+
ctx.waitUntil(result.text); // Delays flush until stream completes
|
|
303
|
+
return result.toTextStreamResponse();
|
|
304
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared agent interface for wizards
|
|
3
|
+
* Uses Claude Agent SDK directly with streaming input support
|
|
4
|
+
*/
|
|
5
|
+
import type { AgentQueryHandle } from '../ui/types.js';
|
|
6
|
+
import type { WizardOptions } from '../utils/types.js';
|
|
7
|
+
export type { AgentQueryHandle };
|
|
8
|
+
export type AgentConfig = {
|
|
9
|
+
workingDirectory: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Result from runAgentLoop including session ID and query handle
|
|
13
|
+
*/
|
|
14
|
+
export interface AgentRunResult {
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
handle: AgentQueryHandle;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Internal configuration object returned by initializeAgent
|
|
20
|
+
*/
|
|
21
|
+
export type AgentRunConfig = {
|
|
22
|
+
workingDirectory: string;
|
|
23
|
+
model: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Initialize agent configuration for the Claude agent
|
|
27
|
+
*/
|
|
28
|
+
export declare function initializeAgent(config: AgentConfig, options: WizardOptions): AgentRunConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Configuration for runAgentLoop
|
|
31
|
+
*/
|
|
32
|
+
export interface RunAgentConfig {
|
|
33
|
+
spinnerMessage?: string;
|
|
34
|
+
successMessage?: string;
|
|
35
|
+
resume?: string;
|
|
36
|
+
accessToken: string;
|
|
37
|
+
orgId: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Execute an agent with the provided prompt and options.
|
|
41
|
+
* Supports streaming input for user interruption and follow-up messages.
|
|
42
|
+
* Uses a while loop to handle user interactions instead of recursion.
|
|
43
|
+
*
|
|
44
|
+
* @returns Session ID and query handle for controlling the agent
|
|
45
|
+
*/
|
|
46
|
+
export declare function runAgentLoop(agentConfig: AgentRunConfig, prompt: string, options: WizardOptions, config?: RunAgentConfig): Promise<AgentRunResult>;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared agent interface for wizards
|
|
3
|
+
* Uses Claude Agent SDK directly with streaming input support
|
|
4
|
+
*/
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
import ui from '../utils/ui.js';
|
|
8
|
+
import { debug, logToFile, initLogFile, LOG_FILE_PATH, } from '../utils/debug.js';
|
|
9
|
+
import { createCanUseToolHandler, createAgentQueryHandle, } from './handlers.js';
|
|
10
|
+
import { processSDKMessage } from './sdk-messages.js';
|
|
11
|
+
import { createCompletionMcpServer } from './mcp.js';
|
|
12
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
13
|
+
// Create a require function for ESM compatibility
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
/**
|
|
16
|
+
* Get the path to the bundled Claude Code CLI from the SDK package.
|
|
17
|
+
* This ensures we use the SDK's bundled version rather than the user's installed Claude Code.
|
|
18
|
+
*/
|
|
19
|
+
function getClaudeCodeExecutablePath() {
|
|
20
|
+
// require.resolve finds the package's main entry, then we get cli.js from same dir
|
|
21
|
+
const sdkPackagePath = require.resolve('@anthropic-ai/claude-agent-sdk');
|
|
22
|
+
return path.join(path.dirname(sdkPackagePath), 'cli.js');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Initialize agent configuration for the Claude agent
|
|
26
|
+
*/
|
|
27
|
+
export function initializeAgent(config, options) {
|
|
28
|
+
// Initialize log file for this run
|
|
29
|
+
initLogFile();
|
|
30
|
+
logToFile('Agent initialization starting');
|
|
31
|
+
logToFile('Install directory:', options.installDir);
|
|
32
|
+
try {
|
|
33
|
+
process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
|
|
34
|
+
// Set default subagent model to Sonnet
|
|
35
|
+
process.env.CLAUDE_CODE_SUBAGENT_MODEL = 'claude-sonnet-4-5-20250929';
|
|
36
|
+
const agentRunConfig = {
|
|
37
|
+
workingDirectory: config.workingDirectory,
|
|
38
|
+
model: 'opus',
|
|
39
|
+
};
|
|
40
|
+
logToFile('Agent config:', {
|
|
41
|
+
workingDirectory: agentRunConfig.workingDirectory,
|
|
42
|
+
});
|
|
43
|
+
if (options.debug) {
|
|
44
|
+
debug('Agent config:', {
|
|
45
|
+
workingDirectory: agentRunConfig.workingDirectory,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
ui.addItem({
|
|
49
|
+
type: 'step',
|
|
50
|
+
text: `I'll keep verbose logs for this session at: ${LOG_FILE_PATH}`,
|
|
51
|
+
});
|
|
52
|
+
return agentRunConfig;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
ui.addItem({
|
|
56
|
+
type: 'error',
|
|
57
|
+
text: `Failed to initialize agent: ${error.message}`,
|
|
58
|
+
});
|
|
59
|
+
logToFile('Agent initialization error:', error);
|
|
60
|
+
debug('Agent initialization error:', error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Execute an agent with the provided prompt and options.
|
|
66
|
+
* Supports streaming input for user interruption and follow-up messages.
|
|
67
|
+
* Uses a while loop to handle user interactions instead of recursion.
|
|
68
|
+
*
|
|
69
|
+
* @returns Session ID and query handle for controlling the agent
|
|
70
|
+
*/
|
|
71
|
+
export async function runAgentLoop(agentConfig, prompt, options, config) {
|
|
72
|
+
const { spinnerMessage = 'Raindrop wizard is working...', successMessage = 'Raindrop integration complete', resume, accessToken, } = config ?? {};
|
|
73
|
+
// Add header to indicate start of interactive agent phase
|
|
74
|
+
ui.addItem({ type: 'phase', text: '─── Agent ───' });
|
|
75
|
+
const cliPath = getClaudeCodeExecutablePath();
|
|
76
|
+
logToFile('Starting agent run');
|
|
77
|
+
logToFile('Claude Code executable:', cliPath);
|
|
78
|
+
// Loop-persistent state
|
|
79
|
+
let currentPrompt = prompt;
|
|
80
|
+
let currentSessionId = resume;
|
|
81
|
+
let handle;
|
|
82
|
+
// Cache for approved files (persists across all iterations)
|
|
83
|
+
const approvedFilesCache = new Set();
|
|
84
|
+
// eslint-disable-next-line no-constant-condition
|
|
85
|
+
while (true) {
|
|
86
|
+
const spinner = ui.spinner();
|
|
87
|
+
logToFile('Prompt:', currentPrompt);
|
|
88
|
+
if (currentSessionId) {
|
|
89
|
+
logToFile('Resuming session:', currentSessionId);
|
|
90
|
+
}
|
|
91
|
+
// Timing and session state
|
|
92
|
+
const startTime = Date.now();
|
|
93
|
+
let sessionId = currentSessionId;
|
|
94
|
+
let queryObject = null;
|
|
95
|
+
// Message and tool call collectors
|
|
96
|
+
const collectedText = [];
|
|
97
|
+
const pendingToolCalls = new Map();
|
|
98
|
+
// Shared ref objects for cross-boundary state
|
|
99
|
+
const hasCompletedWorkRef = { value: false };
|
|
100
|
+
const isInterruptingRef = { value: false };
|
|
101
|
+
const waitingForUserInputRef = { value: false };
|
|
102
|
+
const pendingUserMessageRef = { value: null };
|
|
103
|
+
const exitHintShownRef = { value: false };
|
|
104
|
+
// Promise resolver for waiting on user input after interrupt
|
|
105
|
+
let resolveUserMessage = null;
|
|
106
|
+
// Query handle for external control (interrupt, etc.)
|
|
107
|
+
handle = createAgentQueryHandle({
|
|
108
|
+
isInterruptingRef,
|
|
109
|
+
waitingForUserInputRef,
|
|
110
|
+
pendingToolCalls,
|
|
111
|
+
getQueryObject: () => queryObject,
|
|
112
|
+
});
|
|
113
|
+
// Create MCP server with CompleteIntegration tool
|
|
114
|
+
const completionMcpServer = createCompletionMcpServer(hasCompletedWorkRef, {
|
|
115
|
+
sessionId: options.sessionId,
|
|
116
|
+
accessToken,
|
|
117
|
+
orgId: config?.orgId ?? '',
|
|
118
|
+
installDir: agentConfig.workingDirectory,
|
|
119
|
+
});
|
|
120
|
+
// Define callbacks for persistent input
|
|
121
|
+
const handlePersistentSubmit = (message) => {
|
|
122
|
+
// Reset exit hint flag when user submits a message
|
|
123
|
+
exitHintShownRef.value = false;
|
|
124
|
+
if (isInterruptingRef.value) {
|
|
125
|
+
// Already interrupted - resolve the waiting promise with this message
|
|
126
|
+
logToFile('User submitted message after interrupt:', message);
|
|
127
|
+
pendingUserMessageRef.value = message;
|
|
128
|
+
if (resolveUserMessage) {
|
|
129
|
+
resolveUserMessage(message);
|
|
130
|
+
resolveUserMessage = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Not yet interrupted - store message and trigger interrupt
|
|
135
|
+
pendingUserMessageRef.value = message;
|
|
136
|
+
logToFile('User submitted message while agent running - triggering interrupt');
|
|
137
|
+
void handle.interrupt();
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const handlePersistentInterrupt = () => {
|
|
141
|
+
logToFile('User requested interrupt (Esc)');
|
|
142
|
+
// Stop spinner immediately - persistent input stays visible
|
|
143
|
+
spinner.stop();
|
|
144
|
+
void handle.interrupt();
|
|
145
|
+
};
|
|
146
|
+
const handlePersistentCtrlC = () => {
|
|
147
|
+
logToFile('User pressed Ctrl+C');
|
|
148
|
+
// If already interrupted and exit hint was shown, exit immediately
|
|
149
|
+
if ((isInterruptingRef.value || waitingForUserInputRef.value) &&
|
|
150
|
+
exitHintShownRef.value) {
|
|
151
|
+
logToFile('Second Ctrl+C - exiting');
|
|
152
|
+
ui.stopPersistentInput();
|
|
153
|
+
ui.exit();
|
|
154
|
+
// Small delay to allow UI to clean up before exit
|
|
155
|
+
setTimeout(() => process.exit(130), 100);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// If already interrupted but hint not shown yet, show hint and clear input
|
|
159
|
+
if (isInterruptingRef.value || waitingForUserInputRef.value) {
|
|
160
|
+
logToFile('First Ctrl+C while interrupted - showing exit hint');
|
|
161
|
+
exitHintShownRef.value = true;
|
|
162
|
+
// Clear the input and update placeholder with hint
|
|
163
|
+
ui.stopPersistentInput();
|
|
164
|
+
ui.startPersistentInput({
|
|
165
|
+
onSubmit: handlePersistentSubmit,
|
|
166
|
+
onInterrupt: handlePersistentInterrupt,
|
|
167
|
+
onCtrlC: handlePersistentCtrlC,
|
|
168
|
+
placeholder: 'Press Ctrl+C again to exit',
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Not interrupted yet - treat as normal interrupt
|
|
173
|
+
logToFile('First Ctrl+C - triggering interrupt');
|
|
174
|
+
spinner.stop();
|
|
175
|
+
void handle.interrupt();
|
|
176
|
+
};
|
|
177
|
+
spinner.start(spinnerMessage);
|
|
178
|
+
ui.startPersistentInput({
|
|
179
|
+
onSubmit: handlePersistentSubmit,
|
|
180
|
+
onInterrupt: handlePersistentInterrupt,
|
|
181
|
+
onCtrlC: handlePersistentCtrlC,
|
|
182
|
+
});
|
|
183
|
+
// Session info for notifications
|
|
184
|
+
const sessionInfo = {
|
|
185
|
+
sessionId: options.sessionId,
|
|
186
|
+
accessToken,
|
|
187
|
+
orgId: config?.orgId ?? '',
|
|
188
|
+
};
|
|
189
|
+
queryObject = query({
|
|
190
|
+
prompt: currentPrompt,
|
|
191
|
+
options: {
|
|
192
|
+
model: agentConfig.model,
|
|
193
|
+
cwd: agentConfig.workingDirectory,
|
|
194
|
+
permissionMode: 'default',
|
|
195
|
+
mcpServers: {
|
|
196
|
+
'raindrop-wizard': completionMcpServer,
|
|
197
|
+
},
|
|
198
|
+
systemPrompt: '{WIZARD_SYSTEM_PROMPT}',
|
|
199
|
+
env: { ...process.env },
|
|
200
|
+
resume: currentSessionId,
|
|
201
|
+
canUseTool: createCanUseToolHandler(sessionInfo, approvedFilesCache),
|
|
202
|
+
stderr: (data) => {
|
|
203
|
+
logToFile('CLI stderr:', data);
|
|
204
|
+
if (options.debug) {
|
|
205
|
+
debug('CLI stderr:', data);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
// Update agent state
|
|
211
|
+
ui.setAgentState({
|
|
212
|
+
isRunning: true,
|
|
213
|
+
queryHandle: handle,
|
|
214
|
+
});
|
|
215
|
+
// Process the query stream
|
|
216
|
+
for await (const message of queryObject) {
|
|
217
|
+
// FIX: Check interrupt flag at start of each iteration
|
|
218
|
+
// This handles the case where interrupt() was called before SDK initialized
|
|
219
|
+
if (isInterruptingRef.value) {
|
|
220
|
+
logToFile('Breaking out of for-await loop - interrupt flag set before SDK init');
|
|
221
|
+
// Capture session_id from init message if available before breaking
|
|
222
|
+
if (message.session_id && !sessionId) {
|
|
223
|
+
sessionId = message.session_id;
|
|
224
|
+
ui.setAgentState({ sessionId });
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
// Capture session_id from any message
|
|
229
|
+
if (message.session_id && !sessionId) {
|
|
230
|
+
sessionId = message.session_id;
|
|
231
|
+
ui.setAgentState({ sessionId });
|
|
232
|
+
}
|
|
233
|
+
processSDKMessage(message, options, collectedText, pendingToolCalls, isInterruptingRef.value);
|
|
234
|
+
}
|
|
235
|
+
const durationMs = Date.now() - startTime;
|
|
236
|
+
logToFile(`Agent run completed in ${Math.round(durationMs / 1000)}s`);
|
|
237
|
+
logToFile('Session ID for resuming:', sessionId);
|
|
238
|
+
logToFile('Completion status:', hasCompletedWorkRef.value);
|
|
239
|
+
// Check if we need user input to continue (either stream ended without completion or interrupted)
|
|
240
|
+
const needsUserInput = !hasCompletedWorkRef.value && !waitingForUserInputRef.value && sessionId;
|
|
241
|
+
const wasInterrupted = waitingForUserInputRef.value && sessionId;
|
|
242
|
+
if (needsUserInput || wasInterrupted) {
|
|
243
|
+
if (needsUserInput) {
|
|
244
|
+
logToFile('Stream ended but agent has not called CompleteIntegration - waiting for user response');
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
logToFile('Stream ended after interrupt, waiting for user input');
|
|
248
|
+
}
|
|
249
|
+
spinner.stop();
|
|
250
|
+
ui.setAgentState({ isRunning: false });
|
|
251
|
+
// Get the resume message - either from pending submit or wait for user to submit
|
|
252
|
+
let userMessage;
|
|
253
|
+
if (pendingUserMessageRef.value) {
|
|
254
|
+
// Message was submitted during execution - use it directly
|
|
255
|
+
userMessage = pendingUserMessageRef.value;
|
|
256
|
+
pendingUserMessageRef.value = null; // Clear for next iteration
|
|
257
|
+
logToFile('Using pending user message:', userMessage);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// No pending message - wait for user to submit via persistent input
|
|
261
|
+
logToFile('Waiting for user to submit message via persistent input...');
|
|
262
|
+
// Persistent input is already visible, spinner already stopped - just wait for user
|
|
263
|
+
userMessage = await new Promise((resolve) => {
|
|
264
|
+
resolveUserMessage = resolve;
|
|
265
|
+
});
|
|
266
|
+
// Check if user cancelled (empty message from Esc)
|
|
267
|
+
if (!userMessage) {
|
|
268
|
+
logToFile('User cancelled input, ending session');
|
|
269
|
+
ui.stopPersistentInput();
|
|
270
|
+
return { sessionId, handle };
|
|
271
|
+
}
|
|
272
|
+
logToFile('Received user message from persistent input:', userMessage);
|
|
273
|
+
}
|
|
274
|
+
// Show user message in UI
|
|
275
|
+
ui.addItem({
|
|
276
|
+
type: 'user-message',
|
|
277
|
+
text: userMessage,
|
|
278
|
+
});
|
|
279
|
+
// Update state for next iteration
|
|
280
|
+
logToFile('Resuming agent with user message:', userMessage);
|
|
281
|
+
currentPrompt = userMessage;
|
|
282
|
+
currentSessionId = sessionId;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
// Normal completion - clean up and return
|
|
286
|
+
ui.stopPersistentInput();
|
|
287
|
+
ui.setAgentState({ isRunning: false });
|
|
288
|
+
spinner.stop(successMessage);
|
|
289
|
+
return { sessionId, handle };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=agent-interface.js.map
|