@raindrop-ai/cursor 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/dist/index.js ADDED
@@ -0,0 +1,3009 @@
1
+ // src/config.ts
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { homedir, userInfo } from "os";
4
+ import { dirname, join } from "path";
5
+ import { z } from "zod";
6
+ var signalSchema = z.object({
7
+ description: z.string(),
8
+ sentiment: z.enum(["POSITIVE", "NEGATIVE"]).optional()
9
+ });
10
+ var diagnosticsSchema = z.object({
11
+ signals: z.record(z.string(), signalSchema).optional(),
12
+ guidance: z.string().optional(),
13
+ toolName: z.string().optional()
14
+ });
15
+ var configFileSchema = z.object({
16
+ write_key: z.string().optional().catch(void 0),
17
+ api_url: z.string().optional().catch(void 0),
18
+ project_id: z.string().optional().catch(void 0),
19
+ user_id: z.string().optional().catch(void 0),
20
+ debug: z.boolean().optional().catch(void 0),
21
+ enabled: z.boolean().optional().catch(void 0),
22
+ event_name: z.string().optional().catch(void 0),
23
+ custom_properties: z.record(z.string(), z.unknown()).optional().catch(void 0),
24
+ self_diagnostics: diagnosticsSchema.optional().catch(void 0)
25
+ });
26
+ var mapperConfigSchema = z.object({
27
+ userId: z.string(),
28
+ convoId: z.string().optional(),
29
+ debug: z.boolean(),
30
+ eventName: z.string(),
31
+ customProperties: z.record(z.string(), z.unknown())
32
+ });
33
+ var configSchema = mapperConfigSchema.extend({
34
+ writeKey: z.string(),
35
+ endpoint: z.string(),
36
+ projectId: z.string().optional(),
37
+ enabled: z.boolean(),
38
+ selfDiagnostics: diagnosticsSchema.optional()
39
+ });
40
+ function hookFlushDeadlineMs() {
41
+ const value = Number(process.env.RAINDROP_HOOK_FLUSH_DEADLINE_MS);
42
+ return Number.isFinite(value) && value > 0 ? value : 8e3;
43
+ }
44
+ function getConfigPath() {
45
+ return join(homedir(), ".config", "raindrop", "config.json");
46
+ }
47
+ function parseJson(raw) {
48
+ try {
49
+ return raw ? JSON.parse(raw) : void 0;
50
+ } catch {
51
+ return void 0;
52
+ }
53
+ }
54
+ function loadConfig() {
55
+ let file = {};
56
+ try {
57
+ file = configFileSchema.parse(parseJson(readFileSync(getConfigPath(), "utf8")));
58
+ } catch {
59
+ }
60
+ let username = "unknown";
61
+ try {
62
+ username = userInfo().username;
63
+ } catch {
64
+ }
65
+ const properties = z.record(z.string(), z.unknown()).safeParse(parseJson(process.env.RAINDROP_PROPERTIES));
66
+ const diagnostics = diagnosticsSchema.safeParse(parseJson(process.env.RAINDROP_SELF_DIAGNOSTICS));
67
+ const enabled = process.env.RAINDROP_ENABLED;
68
+ return configSchema.parse({
69
+ writeKey: process.env.RAINDROP_WRITE_KEY ?? file.write_key ?? "",
70
+ endpoint: process.env.RAINDROP_API_URL ?? file.api_url ?? "https://api.raindrop.ai/v1",
71
+ projectId: process.env.RAINDROP_PROJECT_ID ?? file.project_id,
72
+ userId: process.env.RAINDROP_USER_ID ?? file.user_id ?? username,
73
+ convoId: process.env.RAINDROP_CONVO_ID?.trim() || void 0,
74
+ eventName: process.env.RAINDROP_EVENT_NAME ?? file.event_name ?? "ai_generation",
75
+ debug: process.env.RAINDROP_DEBUG === "true" || (file.debug ?? false),
76
+ enabled: enabled ? enabled.toLowerCase() !== "false" && enabled !== "0" : file.enabled ?? true,
77
+ customProperties: { ...file.custom_properties, ...properties.success ? properties.data : {} },
78
+ selfDiagnostics: diagnostics.success ? diagnostics.data : file.self_diagnostics
79
+ });
80
+ }
81
+ function updateConfig(patch) {
82
+ const path = getConfigPath();
83
+ const existing = existsSync(path) ? z.record(z.string(), z.unknown()).parse(JSON.parse(readFileSync(path, "utf8"))) : {};
84
+ const next = { ...existing };
85
+ for (const [key, value] of Object.entries(patch)) {
86
+ if (value === void 0) delete next[key];
87
+ else next[key] = value;
88
+ }
89
+ mkdirSync(dirname(path), { recursive: true });
90
+ writeFileSync(path, JSON.stringify(next, null, 2) + "\n", { mode: 384 });
91
+ }
92
+
93
+ // ../core/dist/chunk-BYBMRLZM.js
94
+ var EVAL_CORRELATION_ID_ATTRIBUTE = "raindrop.eval_correlation_id";
95
+ var PROBE_SCOPE = { correlationId: "" };
96
+ var EvalScopeStorage = class {
97
+ constructor() {
98
+ this._als = null;
99
+ this._probed = false;
100
+ }
101
+ maybeAdoptAsyncLocalStorage() {
102
+ if (this._probed) return;
103
+ this._probed = true;
104
+ try {
105
+ const Ctor = globalThis.RAINDROP_ASYNC_LOCAL_STORAGE;
106
+ if (!Ctor) return;
107
+ const als = new Ctor();
108
+ als.run(PROBE_SCOPE, () => {
109
+ });
110
+ this._als = als;
111
+ } catch (e) {
112
+ this._als = null;
113
+ }
114
+ }
115
+ run(scope, callback) {
116
+ this.maybeAdoptAsyncLocalStorage();
117
+ if (this._als) return this._als.run(scope, callback);
118
+ const previous = this._syncScope;
119
+ this._syncScope = scope;
120
+ try {
121
+ return callback();
122
+ } finally {
123
+ this._syncScope = previous;
124
+ }
125
+ }
126
+ current() {
127
+ this.maybeAdoptAsyncLocalStorage();
128
+ if (this._als) return this._als.getStore();
129
+ return this._syncScope;
130
+ }
131
+ };
132
+ var EVAL_SCOPE_STORAGE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.evalScopeStorage");
133
+ function evalScopeStorage() {
134
+ const holder = globalThis;
135
+ let storage = holder[EVAL_SCOPE_STORAGE_KEY];
136
+ if (!storage) {
137
+ storage = new EvalScopeStorage();
138
+ holder[EVAL_SCOPE_STORAGE_KEY] = storage;
139
+ }
140
+ return storage;
141
+ }
142
+ function currentEvalScope() {
143
+ try {
144
+ return evalScopeStorage().current();
145
+ } catch (e) {
146
+ return void 0;
147
+ }
148
+ }
149
+ function evalScopeAttributes() {
150
+ const scope = currentEvalScope();
151
+ if (!scope) return {};
152
+ return { [EVAL_CORRELATION_ID_ATTRIBUTE]: scope.correlationId };
153
+ }
154
+
155
+ // ../core/dist/chunk-T53LQAH7.js
156
+ function getCrypto() {
157
+ const c = globalThis.crypto;
158
+ return c;
159
+ }
160
+ function randomBytes(length) {
161
+ const cryptoObj = getCrypto();
162
+ const out = new Uint8Array(length);
163
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
164
+ cryptoObj.getRandomValues(out);
165
+ return out;
166
+ }
167
+ for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
168
+ return out;
169
+ }
170
+ function base64Encode(bytes) {
171
+ const maybeBuffer = globalThis.Buffer;
172
+ if (maybeBuffer) {
173
+ return maybeBuffer.from(bytes).toString("base64");
174
+ }
175
+ let binary = "";
176
+ for (let i2 = 0; i2 < bytes.length; i2++) {
177
+ binary += String.fromCharCode(bytes[i2]);
178
+ }
179
+ const btoaFn = globalThis.btoa;
180
+ if (typeof btoaFn === "function") return btoaFn(binary);
181
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
182
+ let out = "";
183
+ let i = 0;
184
+ while (i < binary.length) {
185
+ const c1 = binary.charCodeAt(i++) & 255;
186
+ const c2 = i < binary.length ? binary.charCodeAt(i++) & 255 : NaN;
187
+ const c3 = i < binary.length ? binary.charCodeAt(i++) & 255 : NaN;
188
+ const e1 = c1 >> 2;
189
+ const e2 = (c1 & 3) << 4 | (Number.isNaN(c2) ? 0 : c2 >> 4);
190
+ const e3 = Number.isNaN(c2) ? 64 : (c2 & 15) << 2 | (Number.isNaN(c3) ? 0 : c3 >> 6);
191
+ const e4 = Number.isNaN(c3) ? 64 : c3 & 63;
192
+ out += alphabet.charAt(e1);
193
+ out += alphabet.charAt(e2);
194
+ out += e3 === 64 ? "=" : alphabet.charAt(e3);
195
+ out += e4 === 64 ? "=" : alphabet.charAt(e4);
196
+ }
197
+ return out;
198
+ }
199
+ function base64ToHex(value) {
200
+ if (!value) return "";
201
+ const maybeBuffer = globalThis.Buffer;
202
+ if (maybeBuffer) {
203
+ return maybeBuffer.from(value, "base64").toString("hex");
204
+ }
205
+ const atobFn = globalThis.atob;
206
+ if (typeof atobFn !== "function") return "";
207
+ try {
208
+ const binary = atobFn(value);
209
+ let hex = "";
210
+ for (let i = 0; i < binary.length; i++) {
211
+ hex += (binary.charCodeAt(i) & 255).toString(16).padStart(2, "0");
212
+ }
213
+ return hex;
214
+ } catch (e) {
215
+ return "";
216
+ }
217
+ }
218
+ var HANDOFF_ATTRIBUTE_SUFFIXES = {
219
+ mode: "raindrop.handoff.mode",
220
+ childEventId: "raindrop.handoff.childEventId",
221
+ parentEventId: "raindrop.handoff.parentEventId",
222
+ parentSpanId: "raindrop.handoff.parentSpanId",
223
+ name: "raindrop.handoff.name",
224
+ terminal: "raindrop.handoff.terminal",
225
+ agentRole: "raindrop.agent.role"
226
+ };
227
+ var AI_SDK_METADATA_PREFIX = "ai.telemetry.metadata.";
228
+ var HANDOFF_TERMINAL_CANCELLED = "cancelled";
229
+ var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
230
+ function cancelledTerminalMetadata() {
231
+ return { [HANDOFF_ATTRIBUTE_SUFFIXES.terminal]: HANDOFF_TERMINAL_CANCELLED };
232
+ }
233
+ function cancelledTerminalAttributes() {
234
+ const bare = cancelledTerminalMetadata();
235
+ return { ...bare, ...withMetadataPrefix(bare) };
236
+ }
237
+ function withMetadataPrefix(metadata) {
238
+ const out = {};
239
+ for (const [key, value] of Object.entries(metadata)) {
240
+ out[key.startsWith(AI_SDK_METADATA_PREFIX) ? key : `${AI_SDK_METADATA_PREFIX}${key}`] = value;
241
+ }
242
+ return out;
243
+ }
244
+ var TRACEPARENT_HEADER = "traceparent";
245
+ var BAGGAGE_HEADER = "baggage";
246
+ var HANDOFF_HEADER = "x-raindrop-handoff";
247
+ var UNRESOLVED_CARRIER_WARNING = `[raindrop] resume: headers were supplied but no hand-off carrier was found on them (searched ${HANDOFF_HEADER}, ${TRACEPARENT_HEADER}, ${BAGGAGE_HEADER}). This run reports as an UNLINKED event, and the launcher that dispatched it will stay on "queued" forever because nothing ever references its child. Passing headers means a parent handed off, so no carrier on them is almost certainly a defect: a gateway or proxy stripping ${HANDOFF_HEADER}, a middleware overwriting ${BAGGAGE_HEADER}, a request forwarded without its headers, or a hand-built carrier whose field names do not match. Send every header the launcher's dispatch returned. Logged once per process; pass no headers for a directly-invoked sub-agent to silence it.`;
248
+ function runWithTracingSuppressed(fn) {
249
+ const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
250
+ if (typeof hook !== "function") return fn();
251
+ let started = false;
252
+ try {
253
+ return hook(() => {
254
+ started = true;
255
+ return fn();
256
+ });
257
+ } catch (err) {
258
+ if (started) throw err;
259
+ return fn();
260
+ }
261
+ }
262
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
263
+ var MAX_RETRY_DELAY_MS = 3e4;
264
+ function wait(ms) {
265
+ return new Promise((resolve) => setTimeout(resolve, ms));
266
+ }
267
+ function formatEndpoint(endpoint) {
268
+ if (!endpoint) return void 0;
269
+ return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
270
+ }
271
+ function redactUrlForLog(url) {
272
+ try {
273
+ const parsed = new URL(url);
274
+ parsed.username = "";
275
+ parsed.password = "";
276
+ parsed.search = "";
277
+ parsed.hash = "";
278
+ return parsed.toString();
279
+ } catch (e) {
280
+ return "<unparseable-url>";
281
+ }
282
+ }
283
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
284
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
285
+ function rateLimitedLog(key, log) {
286
+ const now = Date.now();
287
+ const last = rateLimitedLogLast.get(key);
288
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
289
+ return false;
290
+ }
291
+ rateLimitedLogLast.set(key, now);
292
+ log();
293
+ return true;
294
+ }
295
+ async function raceWithTimeout(promise, timeoutMs) {
296
+ let timer;
297
+ const settledInTime = await Promise.race([
298
+ promise.then(
299
+ () => true,
300
+ () => true
301
+ ),
302
+ new Promise((resolve) => {
303
+ var _a;
304
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
305
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
306
+ })
307
+ ]);
308
+ if (timer) clearTimeout(timer);
309
+ return settledInTime;
310
+ }
311
+ function parseRetryAfter(headers) {
312
+ var _a;
313
+ const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
314
+ if (!value) return void 0;
315
+ const asNumber = Number(value);
316
+ if (value.trim() !== "" && !Number.isNaN(asNumber)) return asNumber * 1e3;
317
+ const asDate = new Date(value).getTime();
318
+ if (!Number.isNaN(asDate)) {
319
+ const delta = asDate - Date.now();
320
+ return delta > 0 ? delta : 0;
321
+ }
322
+ return void 0;
323
+ }
324
+ function getRetryDelayMs(attemptNumber, previousError) {
325
+ if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
326
+ const v = previousError.retryAfterMs;
327
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
328
+ }
329
+ if (attemptNumber <= 1) return 0;
330
+ const base = 500;
331
+ const factor = Math.pow(2, attemptNumber - 2);
332
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
333
+ }
334
+ async function withRetry(operation, opName, opts) {
335
+ const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
336
+ let lastError = void 0;
337
+ for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
338
+ if (attemptNumber > 1) {
339
+ const delay = getRetryDelayMs(attemptNumber, lastError);
340
+ if (opts.debug) {
341
+ console.warn(
342
+ `${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
343
+ );
344
+ }
345
+ if (delay > 0) await wait(delay);
346
+ } else if (opts.debug) {
347
+ console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
348
+ }
349
+ try {
350
+ return await operation();
351
+ } catch (err) {
352
+ lastError = err;
353
+ if (opts.debug) {
354
+ const msg = err instanceof Error ? err.message : String(err);
355
+ console.warn(
356
+ `${prefix} ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
357
+ );
358
+ }
359
+ if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
360
+ break;
361
+ if (attemptNumber === opts.maxAttempts) break;
362
+ }
363
+ }
364
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
365
+ }
366
+ async function postJson(url, body, headers, opts) {
367
+ var _a;
368
+ const opName = `POST ${redactUrlForLog(url)}`;
369
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
370
+ await withRetry(
371
+ async () => {
372
+ const resp = await runWithTracingSuppressed(
373
+ () => fetch(url, {
374
+ method: "POST",
375
+ redirect: headers["X-Raindrop-Replay-Id"] !== void 0 ? "error" : "follow",
376
+ headers: {
377
+ "Content-Type": "application/json",
378
+ ...headers
379
+ },
380
+ body: JSON.stringify(body),
381
+ signal: AbortSignal.timeout(timeoutMs)
382
+ })
383
+ );
384
+ if (!resp.ok) {
385
+ const text2 = await resp.text().catch(() => "");
386
+ const err = new Error(
387
+ `HTTP ${resp.status} ${resp.statusText}${text2 ? `: ${text2}` : ""}`
388
+ );
389
+ const retryAfterMs = parseRetryAfter(resp.headers);
390
+ if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
391
+ err.retryable = resp.status === 429 || resp.status >= 500;
392
+ throw err;
393
+ }
394
+ },
395
+ opName,
396
+ opts
397
+ );
398
+ }
399
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
400
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
401
+ var BOUNDED_CLONE_MAX_DEPTH = 12;
402
+ var BOUNDED_CLONE_SLACK = 256;
403
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
404
+ function resolveMaxTextFieldChars(value) {
405
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
406
+ return Math.floor(value);
407
+ }
408
+ return currentDefaultMaxTextFieldChars;
409
+ }
410
+ function truncateToLimit(text2, limit) {
411
+ if (limit > TRUNCATION_MARKER.length) {
412
+ return text2.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
413
+ }
414
+ return text2.slice(0, Math.max(0, limit));
415
+ }
416
+ function capText(value, limit) {
417
+ if (typeof value !== "string") return value;
418
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
419
+ if (value.length <= max) return value;
420
+ return truncateToLimit(value, max);
421
+ }
422
+ function boundedClone(obj, charBudget) {
423
+ let budget = charBudget;
424
+ const seen = /* @__PURE__ */ new WeakSet();
425
+ const walk = (node, depth) => {
426
+ if (budget <= 0) return TRUNCATION_MARKER;
427
+ if (typeof node === "string") {
428
+ if (node.length > budget) {
429
+ const taken = node.slice(0, Math.max(0, budget)) + TRUNCATION_MARKER;
430
+ budget = 0;
431
+ return taken;
432
+ }
433
+ budget -= Math.max(node.length, 1);
434
+ return node;
435
+ }
436
+ if (node === null || typeof node === "number" || typeof node === "boolean") {
437
+ budget -= 8;
438
+ return node;
439
+ }
440
+ if (typeof node !== "object") {
441
+ budget -= 8;
442
+ return node;
443
+ }
444
+ if (seen.has(node)) return "[CIRCULAR]";
445
+ if (depth >= BOUNDED_CLONE_MAX_DEPTH) {
446
+ budget -= 16;
447
+ return `<max depth>`;
448
+ }
449
+ seen.add(node);
450
+ if (Array.isArray(node)) {
451
+ const out2 = [];
452
+ for (const v of node) {
453
+ if (budget <= 0) {
454
+ out2.push(TRUNCATION_MARKER);
455
+ break;
456
+ }
457
+ out2.push(walk(v, depth + 1));
458
+ }
459
+ return out2;
460
+ }
461
+ if (typeof node.toJSON === "function") {
462
+ budget -= 16;
463
+ return node;
464
+ }
465
+ const out = {};
466
+ for (const k in node) {
467
+ if (!Object.prototype.hasOwnProperty.call(node, k)) continue;
468
+ if (budget <= 0) {
469
+ out["..."] = TRUNCATION_MARKER;
470
+ break;
471
+ }
472
+ const key = walk(k, depth + 1);
473
+ out[key] = walk(node[k], depth + 1);
474
+ }
475
+ return out;
476
+ };
477
+ return walk(obj, 0);
478
+ }
479
+ function stringifyBounded(value, limit) {
480
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
481
+ if (typeof value === "string") {
482
+ return capText(value, max);
483
+ }
484
+ const pruned = boundedClone(value, max + TRUNCATION_MARKER.length + BOUNDED_CLONE_SLACK);
485
+ let json;
486
+ try {
487
+ json = JSON.stringify(pruned);
488
+ } catch (e) {
489
+ json = void 0;
490
+ }
491
+ if (json === void 0) {
492
+ return capText(String(value), max);
493
+ }
494
+ return json.length <= max ? json : truncateToLimit(json, max);
495
+ }
496
+ var SpanStatusCode = {
497
+ UNSET: 0,
498
+ OK: 1,
499
+ ERROR: 2
500
+ };
501
+ function createSpanIds(parent) {
502
+ const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
503
+ const spanId = base64Encode(randomBytes(8));
504
+ return {
505
+ traceIdB64: traceId,
506
+ spanIdB64: spanId,
507
+ parentSpanIdB64: parent ? parent.spanIdB64 : void 0
508
+ };
509
+ }
510
+ function nowUnixNanoString() {
511
+ return Date.now().toString() + "000000";
512
+ }
513
+ function attrString(key, value) {
514
+ if (value === void 0) return void 0;
515
+ return { key, value: { stringValue: value } };
516
+ }
517
+ function attrInt(key, value) {
518
+ if (value === void 0) return void 0;
519
+ if (!Number.isFinite(value)) return void 0;
520
+ return { key, value: { intValue: String(Math.trunc(value)) } };
521
+ }
522
+ function attrBool(key, value) {
523
+ if (value === void 0) return void 0;
524
+ return { key, value: { boolValue: value } };
525
+ }
526
+ function buildOtlpSpan(args) {
527
+ const attrs = args.attributes.filter((x) => x !== void 0);
528
+ const span = {
529
+ traceId: args.ids.traceIdB64,
530
+ spanId: args.ids.spanIdB64,
531
+ name: args.name,
532
+ startTimeUnixNano: args.startTimeUnixNano,
533
+ endTimeUnixNano: args.endTimeUnixNano
534
+ };
535
+ if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
536
+ if (attrs.length) span.attributes = attrs;
537
+ if (args.status) span.status = args.status;
538
+ return span;
539
+ }
540
+ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", serviceVersion = "0.0.0") {
541
+ return {
542
+ resourceSpans: [
543
+ {
544
+ resource: {
545
+ attributes: [{ key: "service.name", value: { stringValue: serviceName } }]
546
+ },
547
+ scopeSpans: [
548
+ {
549
+ scope: { name: serviceName, version: serviceVersion },
550
+ spans
551
+ }
552
+ ]
553
+ }
554
+ ]
555
+ };
556
+ }
557
+ var STATE_KEY = /* @__PURE__ */ Symbol.for("raindrop.tracing.replayTraceRoutingState");
558
+ function state() {
559
+ const holder = globalThis;
560
+ let current = holder[STATE_KEY];
561
+ if (!current) {
562
+ let storage = null;
563
+ try {
564
+ const Ctor = globalThis.RAINDROP_ASYNC_LOCAL_STORAGE;
565
+ if (Ctor) storage = new Ctor();
566
+ } catch (e) {
567
+ storage = null;
568
+ }
569
+ current = {
570
+ storage,
571
+ syncDestination: void 0,
572
+ spanDestinations: /* @__PURE__ */ new WeakMap()
573
+ };
574
+ holder[STATE_KEY] = current;
575
+ }
576
+ return current;
577
+ }
578
+ function currentReplayTraceDestination() {
579
+ var _a, _b;
580
+ const routing = state();
581
+ return (_b = (_a = routing.storage) == null ? void 0 : _a.getStore()) != null ? _b : routing.syncDestination;
582
+ }
583
+ function rememberReplayTraceDestination(span) {
584
+ const destination = currentReplayTraceDestination();
585
+ if (destination) state().spanDestinations.set(span, destination);
586
+ }
587
+ function copyReplayTraceDestination(source, target) {
588
+ const destination = state().spanDestinations.get(source);
589
+ if (destination) state().spanDestinations.set(target, destination);
590
+ }
591
+ function replayTraceDestinationFor(value) {
592
+ if (typeof value !== "object" && typeof value !== "function" || value === null) {
593
+ return void 0;
594
+ }
595
+ return state().spanDestinations.get(value);
596
+ }
597
+ function recordReplayExportedSpan(destination, spanId, traceId) {
598
+ var _a, _b;
599
+ if (!destination || !spanId || !traceId) return;
600
+ (_a = destination.exportedSpanIds) == null ? void 0 : _a.add(spanId);
601
+ (_b = destination.exportedTraceIds) == null ? void 0 : _b.add(traceId);
602
+ }
603
+ function replayTraceUrl(baseUrl) {
604
+ return `${baseUrl.replace(/\/+$/, "")}/v1/traces`;
605
+ }
606
+ var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
607
+ var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
608
+ var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
609
+ function readEnvVar(name) {
610
+ var _a;
611
+ try {
612
+ const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
613
+ if (env && typeof env[name] === "string" && env[name].length > 0) {
614
+ return env[name];
615
+ }
616
+ } catch (e) {
617
+ }
618
+ return void 0;
619
+ }
620
+ function readWorkshopEnv() {
621
+ const raw = readEnvVar(WORKSHOP_ENV_VAR);
622
+ if (raw === void 0) return void 0;
623
+ const trimmed = raw.trim();
624
+ if (trimmed.length === 0) return void 0;
625
+ if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
626
+ if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
627
+ if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
628
+ return void 0;
629
+ }
630
+ function isLocalDevHost(hostname) {
631
+ if (!hostname) return false;
632
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
633
+ return true;
634
+ }
635
+ if (hostname.endsWith(".localhost")) return true;
636
+ return false;
637
+ }
638
+ function readRuntimeHostname() {
639
+ try {
640
+ const loc = globalThis == null ? void 0 : globalThis.location;
641
+ if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
642
+ return loc.hostname;
643
+ }
644
+ } catch (e) {
645
+ }
646
+ return void 0;
647
+ }
648
+ function shouldAutoEnableLocalWorkshop() {
649
+ if (isLocalDevHost(readRuntimeHostname())) return true;
650
+ if (readEnvVar("NODE_ENV") === "development") return true;
651
+ return false;
652
+ }
653
+ function resolveLocalDebuggerBaseUrl(baseUrl) {
654
+ var _a, _b, _c;
655
+ if (baseUrl === null) return null;
656
+ if (typeof baseUrl === "string" && baseUrl.length > 0) {
657
+ return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
658
+ }
659
+ const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
660
+ if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
661
+ const workshopEnv = readWorkshopEnv();
662
+ if (workshopEnv === "disable") return null;
663
+ if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
664
+ if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
665
+ if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
666
+ return null;
667
+ }
668
+ var LOCAL_DEBUGGER_MIRROR_TIMEOUT_MS = 2e3;
669
+ function mirrorTraceExportToLocalDebugger(body, options = {}) {
670
+ var _a;
671
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
672
+ if (!baseUrl) return Promise.resolve();
673
+ return postJson(`${baseUrl}traces`, body, {}, {
674
+ maxAttempts: 1,
675
+ debug: (_a = options.debug) != null ? _a : false,
676
+ sdkName: options.sdkName,
677
+ timeoutMs: LOCAL_DEBUGGER_MIRROR_TIMEOUT_MS
678
+ }).catch(() => {
679
+ });
680
+ }
681
+ function mirrorPartialEventToLocalDebugger(event, options = {}) {
682
+ var _a;
683
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
684
+ if (!baseUrl) return Promise.resolve();
685
+ const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
686
+ return postJson(`${baseUrl}events/track_partial`, event, headers, {
687
+ maxAttempts: 1,
688
+ debug: (_a = options.debug) != null ? _a : false,
689
+ sdkName: options.sdkName,
690
+ timeoutMs: LOCAL_DEBUGGER_MIRROR_TIMEOUT_MS
691
+ }).catch(() => {
692
+ });
693
+ }
694
+ var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
695
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
696
+ function isValidProjectIdSlug(value) {
697
+ return PROJECT_ID_SLUG_PATTERN.test(value);
698
+ }
699
+ function normalizeProjectId(raw, opts) {
700
+ if (typeof raw !== "string") return void 0;
701
+ const trimmed = raw.trim();
702
+ if (!trimmed) return void 0;
703
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
704
+ console.warn(
705
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
706
+ );
707
+ }
708
+ return trimmed;
709
+ }
710
+ function projectIdHeaders(projectId) {
711
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
712
+ }
713
+ var SHUTDOWN_DEADLINE_MS = 1e4;
714
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
715
+ function mergePatches(target, source) {
716
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
717
+ const out = { ...target, ...source };
718
+ if (target.properties || source.properties) {
719
+ out.properties = { ...(_a = target.properties) != null ? _a : {}, ...(_b = source.properties) != null ? _b : {} };
720
+ }
721
+ if (target.usage || source.usage) {
722
+ out.usage = { ...(_c = target.usage) != null ? _c : {} };
723
+ if (((_d = source.usage) == null ? void 0 : _d.promptTokens) !== void 0) {
724
+ out.usage.promptTokens = source.usage.promptTokens;
725
+ }
726
+ if (((_e = source.usage) == null ? void 0 : _e.completionTokens) !== void 0) {
727
+ out.usage.completionTokens = source.usage.completionTokens;
728
+ }
729
+ }
730
+ if (source.error === void 0) {
731
+ out.error = target.error;
732
+ }
733
+ if (target.featureFlags || source.featureFlags) {
734
+ out.featureFlags = { ...(_f = target.featureFlags) != null ? _f : {}, ...(_g = source.featureFlags) != null ? _g : {} };
735
+ }
736
+ if (target.attachments || source.attachments) {
737
+ out.attachments = [...(_h = target.attachments) != null ? _h : [], ...(_i = source.attachments) != null ? _i : []];
738
+ }
739
+ return out;
740
+ }
741
+ function isErrorLike(value) {
742
+ return value !== null && typeof value === "object";
743
+ }
744
+ function eventTelemetryProperties(patch) {
745
+ var _a, _b;
746
+ const properties = {};
747
+ if (((_a = patch.usage) == null ? void 0 : _a.promptTokens) !== void 0 && Number.isFinite(patch.usage.promptTokens)) {
748
+ properties["ai.usage.prompt_tokens"] = patch.usage.promptTokens;
749
+ }
750
+ if (((_b = patch.usage) == null ? void 0 : _b.completionTokens) !== void 0 && Number.isFinite(patch.usage.completionTokens)) {
751
+ properties["ai.usage.completion_tokens"] = patch.usage.completionTokens;
752
+ }
753
+ if (patch.error !== void 0 && patch.error !== null) {
754
+ const constructorName = patch.error instanceof Error && patch.error.constructor !== Error ? patch.error.constructor.name : void 0;
755
+ const errorType = patch.error instanceof Error ? patch.error.name && patch.error.name !== "Error" ? patch.error.name : constructorName || patch.error.name || "Error" : isErrorLike(patch.error) && typeof patch.error.name === "string" && patch.error.name.length > 0 ? patch.error.name : "Error";
756
+ const errorMessage = patch.error instanceof Error ? patch.error.message : isErrorLike(patch.error) && typeof patch.error.message === "string" ? patch.error.message : String(patch.error);
757
+ properties["error.type"] = errorType;
758
+ properties["error.message"] = errorMessage;
759
+ }
760
+ return properties;
761
+ }
762
+ var EventShipper = class {
763
+ constructor(opts) {
764
+ this.buffers = /* @__PURE__ */ new Map();
765
+ this.sticky = /* @__PURE__ */ new Map();
766
+ this.timers = /* @__PURE__ */ new Map();
767
+ this.inFlight = /* @__PURE__ */ new Set();
768
+ this.hasShutdown = false;
769
+ var _a, _b, _c, _d, _e, _f, _g, _h;
770
+ this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
771
+ this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
772
+ this.enabled = opts.enabled !== false;
773
+ this.debug = opts.debug;
774
+ this.partialFlushMs = (_c = opts.partialFlushMs) != null ? _c : 1e3;
775
+ this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
776
+ this.prefix = `[raindrop-ai/${this.sdkName}]`;
777
+ this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
778
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
779
+ this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
780
+ if (this.debug && this.localDebuggerUrl) {
781
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
782
+ }
783
+ this.projectId = normalizeProjectId(opts.projectId, {
784
+ debug: this.debug,
785
+ prefix: this.prefix
786
+ });
787
+ const isNode = typeof process !== "undefined" && typeof process.version === "string";
788
+ this.context = {
789
+ library: {
790
+ name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
791
+ version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
792
+ },
793
+ metadata: {
794
+ jsRuntime: isNode ? "node" : "web",
795
+ ...isNode ? { nodeVersion: process.version } : {}
796
+ }
797
+ };
798
+ }
799
+ isDebugEnabled() {
800
+ return this.debug;
801
+ }
802
+ authHeaders() {
803
+ return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
804
+ }
805
+ requestHeaders() {
806
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
807
+ }
808
+ /**
809
+ * Build the retry/timeout options for one POST, honoring the shutdown
810
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
811
+ * the caller must drop the payload (with a rate-limited warning) instead
812
+ * of issuing a request that could outlive process exit.
813
+ *
814
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
815
+ * path is mid-drain takes effect immediately: no further retries, and the
816
+ * per-attempt timeout is clamped to the remaining window. After
817
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
818
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
819
+ * run as a single short attempt rather than regaining the full retry
820
+ * schedule.
821
+ */
822
+ requestOpts() {
823
+ if (this.shutdownDeadlineAt !== void 0) {
824
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
825
+ if (remainingMs <= 0) return null;
826
+ return {
827
+ maxAttempts: 1,
828
+ debug: this.debug,
829
+ sdkName: this.sdkName,
830
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
831
+ };
832
+ }
833
+ if (this.hasShutdown) {
834
+ return {
835
+ maxAttempts: 1,
836
+ debug: this.debug,
837
+ sdkName: this.sdkName,
838
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
839
+ };
840
+ }
841
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
842
+ }
843
+ async patch(eventId, patch) {
844
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
845
+ if (!this.enabled) return;
846
+ if (!eventId || !eventId.trim()) return;
847
+ const capturedSticky = this.sticky.get(eventId);
848
+ const eventAlreadyKnown = capturedSticky !== void 0 || this.buffers.has(eventId);
849
+ const destination = currentReplayTraceDestination();
850
+ const cloudReplay = destination !== void 0 && destination.kind !== "workshop";
851
+ if ((capturedSticky == null ? void 0 : capturedSticky.replayOnly) === true || !eventAlreadyKnown && cloudReplay) {
852
+ if (patch.isPending === false) this.sticky.delete(eventId);
853
+ else this.sticky.set(eventId, { ...capturedSticky, replayOnly: true });
854
+ return;
855
+ }
856
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
857
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
858
+ patch = { ...patch, input: capText(patch.input, maxChars) };
859
+ }
860
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
861
+ patch = { ...patch, output: capText(patch.output, maxChars) };
862
+ }
863
+ if (this.debug) {
864
+ console.log(`${this.prefix} queue patch`, {
865
+ eventId,
866
+ userId: patch.userId,
867
+ convoId: patch.convoId,
868
+ eventName: patch.eventName,
869
+ hasInput: typeof patch.input === "string" && patch.input.length > 0,
870
+ hasOutput: typeof patch.output === "string" && patch.output.length > 0,
871
+ attachments: (_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0,
872
+ isPending: patch.isPending
873
+ });
874
+ }
875
+ const sticky = (_c = this.sticky.get(eventId)) != null ? _c : {};
876
+ const existing = (_d = this.buffers.get(eventId)) != null ? _d : {};
877
+ const merged = mergePatches(existing, patch);
878
+ merged.isPending = (_g = (_f = (_e = patch.isPending) != null ? _e : existing.isPending) != null ? _f : sticky.isPending) != null ? _g : true;
879
+ this.buffers.set(eventId, merged);
880
+ this.sticky.set(eventId, {
881
+ userId: (_h = merged.userId) != null ? _h : sticky.userId,
882
+ convoId: (_i = merged.convoId) != null ? _i : sticky.convoId,
883
+ eventName: (_j = merged.eventName) != null ? _j : sticky.eventName,
884
+ isPending: (_k = merged.isPending) != null ? _k : sticky.isPending
885
+ });
886
+ const t = this.timers.get(eventId);
887
+ if (t) clearTimeout(t);
888
+ if (merged.isPending === false) {
889
+ await this.flushOne(eventId);
890
+ return;
891
+ }
892
+ const timeout = setTimeout(() => {
893
+ void this.flushOne(eventId).catch(() => {
894
+ });
895
+ }, this.partialFlushMs);
896
+ this.timers.set(eventId, timeout);
897
+ }
898
+ async finish(eventId, patch) {
899
+ await this.patch(eventId, { ...patch, isPending: false });
900
+ }
901
+ async flush() {
902
+ if (!this.enabled) return;
903
+ const ids = [...this.buffers.keys()];
904
+ await Promise.all(ids.map((id) => this.flushOne(id)));
905
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
906
+ })));
907
+ }
908
+ async shutdown() {
909
+ this.hasShutdown = true;
910
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
911
+ try {
912
+ for (const t of this.timers.values()) clearTimeout(t);
913
+ this.timers.clear();
914
+ for (const [eventId, sticky] of this.sticky) {
915
+ if (sticky.replayOnly) this.sticky.delete(eventId);
916
+ }
917
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
918
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
919
+ } finally {
920
+ this.shutdownDeadlineAt = void 0;
921
+ }
922
+ }
923
+ async trackSignal(signal) {
924
+ var _a, _b;
925
+ if (!this.enabled) return;
926
+ const destination = currentReplayTraceDestination();
927
+ if (destination && destination.kind !== "workshop") return;
928
+ const body = [
929
+ {
930
+ event_id: signal.eventId,
931
+ signal_name: signal.name,
932
+ signal_type: (_a = signal.type) != null ? _a : "default",
933
+ timestamp: signal.timestamp,
934
+ sentiment: signal.sentiment,
935
+ attachment_id: signal.attachmentId,
936
+ properties: {
937
+ ...(_b = signal.properties) != null ? _b : {},
938
+ ...signal.comment ? { comment: signal.comment } : {},
939
+ ...signal.after ? { after: signal.after } : {}
940
+ }
941
+ }
942
+ ];
943
+ if (!this.writeKey) return;
944
+ const url = `${this.baseUrl}signals/track`;
945
+ const opts = this.requestOpts();
946
+ if (!opts) {
947
+ this.warnShutdownDrop("signal");
948
+ return;
949
+ }
950
+ try {
951
+ await postJson(url, body, this.requestHeaders(), opts);
952
+ } catch (err) {
953
+ const msg = err instanceof Error ? err.message : String(err);
954
+ rateLimitedLog(
955
+ `${this.prefix}.send_signal_failed`,
956
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
957
+ );
958
+ }
959
+ }
960
+ async identify(users) {
961
+ if (!this.enabled) return;
962
+ const destination = currentReplayTraceDestination();
963
+ if (destination && destination.kind !== "workshop") return;
964
+ const list = Array.isArray(users) ? users : [users];
965
+ const body = list.filter((user) => {
966
+ if (!(user == null ? void 0 : user.userId) || !user.userId.trim()) {
967
+ if (this.debug) {
968
+ console.warn(`${this.prefix} skipping identify: missing userId`);
969
+ }
970
+ return false;
971
+ }
972
+ return true;
973
+ }).map((user) => {
974
+ var _a;
975
+ return {
976
+ user_id: user.userId,
977
+ traits: (_a = user.traits) != null ? _a : {}
978
+ };
979
+ });
980
+ if (!this.writeKey) return;
981
+ if (body.length === 0) return;
982
+ const url = `${this.baseUrl}users/identify`;
983
+ const opts = this.requestOpts();
984
+ if (!opts) {
985
+ this.warnShutdownDrop("identify");
986
+ return;
987
+ }
988
+ try {
989
+ await postJson(url, body, this.requestHeaders(), opts);
990
+ } catch (err) {
991
+ const msg = err instanceof Error ? err.message : String(err);
992
+ rateLimitedLog(
993
+ `${this.prefix}.send_identify_failed`,
994
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
995
+ );
996
+ }
997
+ }
998
+ warnShutdownDrop(what) {
999
+ rateLimitedLog(
1000
+ `${this.prefix}.shutdown_deadline`,
1001
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
1002
+ );
1003
+ }
1004
+ async flushOne(eventId) {
1005
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
1006
+ if (!this.enabled) return;
1007
+ const timer = this.timers.get(eventId);
1008
+ if (timer) {
1009
+ clearTimeout(timer);
1010
+ this.timers.delete(eventId);
1011
+ }
1012
+ const accumulated = this.buffers.get(eventId);
1013
+ this.buffers.delete(eventId);
1014
+ if (!accumulated) return;
1015
+ const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
1016
+ const isPending = (_c = (_b = accumulated.isPending) != null ? _b : sticky.isPending) != null ? _c : true;
1017
+ const eventName = (_e = (_d = accumulated.eventName) != null ? _d : sticky.eventName) != null ? _e : this.defaultEventName;
1018
+ const userId = (_f = accumulated.userId) != null ? _f : sticky.userId;
1019
+ if (!userId) {
1020
+ if (this.debug) {
1021
+ console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
1022
+ }
1023
+ this.sticky.delete(eventId);
1024
+ return;
1025
+ }
1026
+ const { wizardSession, ...restProperties } = (_g = accumulated.properties) != null ? _g : {};
1027
+ let telemetryProperties = {};
1028
+ try {
1029
+ telemetryProperties = eventTelemetryProperties(accumulated);
1030
+ } catch (e) {
1031
+ }
1032
+ const convoId = (_h = accumulated.convoId) != null ? _h : sticky.convoId;
1033
+ const payload = {
1034
+ event_id: eventId,
1035
+ user_id: userId,
1036
+ event: eventName,
1037
+ timestamp: (_i = accumulated.timestamp) != null ? _i : (/* @__PURE__ */ new Date()).toISOString(),
1038
+ ai_data: {
1039
+ input: accumulated.input,
1040
+ output: accumulated.error !== void 0 && accumulated.error !== null ? void 0 : accumulated.output,
1041
+ model: accumulated.model,
1042
+ convo_id: convoId
1043
+ },
1044
+ properties: {
1045
+ ...restProperties,
1046
+ ...telemetryProperties,
1047
+ ...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
1048
+ $context: this.context
1049
+ },
1050
+ ...accumulated.featureFlags && Object.keys(accumulated.featureFlags).length > 0 ? { feature_flags: accumulated.featureFlags } : {},
1051
+ attachments: accumulated.attachments,
1052
+ is_pending: isPending
1053
+ };
1054
+ const url = `${this.baseUrl}events/track_partial`;
1055
+ if (this.debug) {
1056
+ console.log(`${this.prefix} sending track_partial`, {
1057
+ eventId,
1058
+ eventName,
1059
+ userId,
1060
+ convoId,
1061
+ isPending,
1062
+ inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
1063
+ outputPreview: typeof accumulated.output === "string" ? accumulated.output.slice(0, 120) : void 0,
1064
+ attachments: (_k = (_j = accumulated.attachments) == null ? void 0 : _j.length) != null ? _k : 0,
1065
+ attachmentKinds: (_m = (_l = accumulated.attachments) == null ? void 0 : _l.map((a) => ({
1066
+ type: a.type,
1067
+ role: a.role,
1068
+ name: a.name,
1069
+ valuePreview: a.value.slice(0, 60)
1070
+ }))) != null ? _m : [],
1071
+ endpoint: url
1072
+ });
1073
+ }
1074
+ const mirror = this.localDebuggerUrl ? mirrorPartialEventToLocalDebugger(payload, {
1075
+ baseUrl: this.localDebuggerUrl,
1076
+ writeKey: this.writeKey,
1077
+ debug: this.debug,
1078
+ sdkName: this.sdkName
1079
+ }) : void 0;
1080
+ if (!this.writeKey) {
1081
+ if (!isPending) this.sticky.delete(eventId);
1082
+ if (mirror) await mirror;
1083
+ return;
1084
+ }
1085
+ const opts = this.requestOpts();
1086
+ if (!opts) {
1087
+ this.warnShutdownDrop(`track_partial ${eventId}`);
1088
+ if (!isPending) this.sticky.delete(eventId);
1089
+ if (mirror) await mirror;
1090
+ return;
1091
+ }
1092
+ const p = postJson(url, payload, this.requestHeaders(), opts);
1093
+ const combined = mirror ? Promise.all([mirror, p]).then(() => void 0) : p;
1094
+ this.inFlight.add(combined);
1095
+ try {
1096
+ try {
1097
+ await combined;
1098
+ if (this.debug) {
1099
+ console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
1100
+ }
1101
+ } catch (err) {
1102
+ const msg = err instanceof Error ? err.message : String(err);
1103
+ rateLimitedLog(
1104
+ `${this.prefix}.send_track_partial_failed`,
1105
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
1106
+ );
1107
+ }
1108
+ } finally {
1109
+ this.inFlight.delete(combined);
1110
+ }
1111
+ if (!isPending) {
1112
+ this.sticky.delete(eventId);
1113
+ }
1114
+ }
1115
+ };
1116
+ var MODEL_USAGE_ATTRIBUTES = {
1117
+ usageSource: "raindrop.usage.source",
1118
+ providerName: "gen_ai.provider.name",
1119
+ requestModel: "gen_ai.request.model",
1120
+ responseModel: "gen_ai.response.model",
1121
+ inputTokens: "gen_ai.usage.input_tokens",
1122
+ outputTokens: "gen_ai.usage.output_tokens",
1123
+ reasoningTokens: "gen_ai.usage.reasoning_tokens",
1124
+ cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
1125
+ cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
1126
+ providerReportedCost: "gen_ai.usage.provider_reported_cost",
1127
+ nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
1128
+ nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
1129
+ totalOutputTokens: "raindrop.usage.output_tokens.total",
1130
+ reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
1131
+ cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
1132
+ cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
1133
+ };
1134
+ var MODEL_PROVIDER_NAMES = [
1135
+ ["google.vertex.anthropic", "google-vertex"],
1136
+ ["google.vertex_ai", "google-vertex"],
1137
+ ["google.vertex", "google-vertex"],
1138
+ ["gcp.vertex_ai", "google-vertex"],
1139
+ ["gcp.gemini", "google"],
1140
+ ["google_vertexai", "google-vertex"],
1141
+ ["vertex.anthropic", "google-vertex"],
1142
+ ["amazon-bedrock", "amazon-bedrock"],
1143
+ ["aws.bedrock", "amazon-bedrock"],
1144
+ ["bedrock", "amazon-bedrock"],
1145
+ ["anthropic", "anthropic"],
1146
+ ["openai", "openai"],
1147
+ ["google", "google"],
1148
+ ["az.ai.openai", "azure"],
1149
+ ["azure", "azure"],
1150
+ ["openrouter", "openrouter"]
1151
+ ];
1152
+ function canonicalModelProvider(provider) {
1153
+ var _a;
1154
+ const normalizedProvider = provider.trim().toLowerCase();
1155
+ if (normalizedProvider === "gateway") return "vercel";
1156
+ const match = MODEL_PROVIDER_NAMES.find(
1157
+ ([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
1158
+ );
1159
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : normalizedProvider;
1160
+ }
1161
+ var STRING_ALIASES = {
1162
+ [MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
1163
+ };
1164
+ var NUMBER_ALIASES = {
1165
+ [MODEL_USAGE_ATTRIBUTES.inputTokens]: [
1166
+ "gen_ai.usage.prompt_tokens",
1167
+ "ai.usage.prompt_tokens",
1168
+ "ai.usage.promptTokens",
1169
+ "ai.usage.input_tokens",
1170
+ "ai.usage.inputTokens"
1171
+ ],
1172
+ [MODEL_USAGE_ATTRIBUTES.outputTokens]: [
1173
+ "gen_ai.usage.completion_tokens",
1174
+ "ai.usage.completion_tokens",
1175
+ "ai.usage.completionTokens",
1176
+ "ai.usage.output_tokens",
1177
+ "ai.usage.outputTokens"
1178
+ ],
1179
+ [MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
1180
+ "gen_ai.usage.reasoning.output_tokens",
1181
+ "ai.usage.reasoningTokens",
1182
+ "ai.usage.thoughts_tokens"
1183
+ ],
1184
+ [MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
1185
+ "gen_ai.usage.cache_read_tokens",
1186
+ "gen_ai.usage.cache_read.input_tokens",
1187
+ "ai.usage.cachedInputTokens",
1188
+ "ai.usage.cached_tokens",
1189
+ "ai.usage.cache_read_tokens",
1190
+ "ai.usage.cache_read_input_tokens",
1191
+ "ai.usage.cacheReadInputTokens"
1192
+ ],
1193
+ [MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
1194
+ "gen_ai.usage.cache_creation_input_tokens",
1195
+ "gen_ai.usage.cache_creation.input_tokens",
1196
+ "ai.usage.cacheWriteInputTokens",
1197
+ "ai.usage.cache_creation_input_tokens",
1198
+ "ai.usage.cacheCreationInputTokens"
1199
+ ]
1200
+ };
1201
+ function hasAttribute(attributes, key) {
1202
+ return attributes.some((attribute) => attribute.key === key);
1203
+ }
1204
+ function findStringAttribute(attributes, keys) {
1205
+ return attributes.find(
1206
+ (attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
1207
+ );
1208
+ }
1209
+ function findNumberAttribute(attributes, keys) {
1210
+ return attributes.find((attribute) => {
1211
+ if (!keys.includes(attribute.key)) return false;
1212
+ if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
1213
+ return true;
1214
+ }
1215
+ const intValue = attribute.value.intValue;
1216
+ return integerValue(intValue) !== void 0;
1217
+ });
1218
+ }
1219
+ function integerValue(value) {
1220
+ if (typeof value === "number") {
1221
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1222
+ }
1223
+ if (typeof value !== "string") return void 0;
1224
+ const parsed = Number(value);
1225
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
1226
+ }
1227
+ function appendAlias(attributes, target, source) {
1228
+ if (!source || hasAttribute(attributes, target)) return attributes;
1229
+ return [...attributes, { key: target, value: { ...source.value } }];
1230
+ }
1231
+ function appendProviderAlias(attributes) {
1232
+ if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
1233
+ const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
1234
+ if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
1235
+ const attribute = attrString(
1236
+ MODEL_USAGE_ATTRIBUTES.providerName,
1237
+ canonicalModelProvider(source.value.stringValue)
1238
+ );
1239
+ return attribute ? [...attributes, attribute] : attributes;
1240
+ }
1241
+ function normalizeModelUsageSpan(span) {
1242
+ const original = span.attributes;
1243
+ if (!original || original.length === 0) return span;
1244
+ const responseModel = findStringAttribute(original, [
1245
+ MODEL_USAGE_ATTRIBUTES.responseModel,
1246
+ "ai.response.model"
1247
+ ]);
1248
+ if (!responseModel) return span;
1249
+ let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
1250
+ attributes = appendProviderAlias(attributes);
1251
+ for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
1252
+ attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
1253
+ }
1254
+ for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
1255
+ attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
1256
+ }
1257
+ return attributes === original ? span : { ...span, attributes };
1258
+ }
1259
+ var DEFAULT_SECRET_KEY_NAMES = [
1260
+ "apikey",
1261
+ "apisecret",
1262
+ "apitoken",
1263
+ "secretaccesskey",
1264
+ "sessiontoken",
1265
+ "privatekey",
1266
+ "privatekeyid",
1267
+ "clientsecret",
1268
+ "accesstoken",
1269
+ "refreshtoken",
1270
+ "oauthtoken",
1271
+ "bearertoken",
1272
+ "authorization",
1273
+ "password",
1274
+ "passphrase"
1275
+ ];
1276
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
1277
+ function normalizeKeyName(name) {
1278
+ return name.toLowerCase().replace(/[-_.]/g, "");
1279
+ }
1280
+ function redactSecretsInObject(value, options) {
1281
+ var _a, _b;
1282
+ const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
1283
+ const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
1284
+ const seen = /* @__PURE__ */ new WeakSet();
1285
+ const walk = (node) => {
1286
+ if (node === null || typeof node !== "object") return node;
1287
+ if (seen.has(node)) return "[CIRCULAR]";
1288
+ seen.add(node);
1289
+ if (Array.isArray(node)) {
1290
+ return node.map((item) => walk(item));
1291
+ }
1292
+ const out = {};
1293
+ for (const [k, v] of Object.entries(node)) {
1294
+ if (normalizedSecretSet.has(normalizeKeyName(k))) {
1295
+ out[k] = placeholder;
1296
+ } else {
1297
+ out[k] = walk(v);
1298
+ }
1299
+ }
1300
+ return out;
1301
+ };
1302
+ return walk(value);
1303
+ }
1304
+ function buildSecretSet(names) {
1305
+ const set = /* @__PURE__ */ new Set();
1306
+ for (const name of names) set.add(normalizeKeyName(name));
1307
+ return set;
1308
+ }
1309
+ var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
1310
+ "ai.request.providerOptions",
1311
+ "ai.response.providerMetadata"
1312
+ ];
1313
+ function defaultTransformSpan(span) {
1314
+ const attrs = span.attributes;
1315
+ if (!attrs || attrs.length === 0) return span;
1316
+ let nextAttrs;
1317
+ for (let i = 0; i < attrs.length; i++) {
1318
+ const attr = attrs[i];
1319
+ const redacted = redactJsonAttributeValue(attr.key, attr.value);
1320
+ if (redacted === void 0) continue;
1321
+ if (!nextAttrs) nextAttrs = attrs.slice();
1322
+ nextAttrs[i] = { key: attr.key, value: redacted };
1323
+ }
1324
+ if (!nextAttrs) return span;
1325
+ return { ...span, attributes: nextAttrs };
1326
+ }
1327
+ var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
1328
+ function redactJsonAttributeValue(key, value) {
1329
+ if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
1330
+ const json = value.stringValue;
1331
+ if (typeof json !== "string" || json.length === 0) return void 0;
1332
+ let parsed;
1333
+ try {
1334
+ parsed = JSON.parse(json);
1335
+ } catch (e) {
1336
+ return void 0;
1337
+ }
1338
+ const scrubbed = redactSecretsInObject(parsed);
1339
+ let scrubbedJson;
1340
+ try {
1341
+ scrubbedJson = JSON.stringify(scrubbed);
1342
+ } catch (e) {
1343
+ return void 0;
1344
+ }
1345
+ if (scrubbedJson === json) return void 0;
1346
+ return { stringValue: scrubbedJson };
1347
+ }
1348
+ function identityAttrs(args) {
1349
+ return [
1350
+ attrString("ai.telemetry.metadata.raindrop.ai.userId", args.userId),
1351
+ attrString("ai.telemetry.metadata.raindrop.convoId", args.convoId),
1352
+ attrString("ai.telemetry.metadata.raindrop.eventName", args.eventName)
1353
+ ];
1354
+ }
1355
+ function applyOtelSpanAttributeLimit(limit) {
1356
+ var _a, _b;
1357
+ try {
1358
+ const raw = (_b = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env) == null ? void 0 : _b.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
1359
+ if (!raw) return limit;
1360
+ const parsed = Number.parseInt(raw, 10);
1361
+ if (Number.isFinite(parsed) && parsed > 0) {
1362
+ return Math.min(limit, parsed);
1363
+ }
1364
+ } catch (e) {
1365
+ }
1366
+ return limit;
1367
+ }
1368
+ var TraceShipper = class {
1369
+ constructor(opts) {
1370
+ this.queue = [];
1371
+ this.inFlight = /* @__PURE__ */ new Set();
1372
+ this.hasShutdown = false;
1373
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1374
+ this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
1375
+ this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
1376
+ this.enabled = opts.enabled !== false;
1377
+ this.debug = opts.debug;
1378
+ this.debugSpans = opts.debugSpans === true;
1379
+ this.flushIntervalMs = (_c = opts.flushIntervalMs) != null ? _c : 1e3;
1380
+ this.maxBatchSize = (_d = opts.maxBatchSize) != null ? _d : 50;
1381
+ this.maxQueueSize = (_e = opts.maxQueueSize) != null ? _e : 5e3;
1382
+ this.sdkName = (_f = opts.sdkName) != null ? _f : "core";
1383
+ this.prefix = `[raindrop-ai/${this.sdkName}]`;
1384
+ this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
1385
+ this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
1386
+ this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
1387
+ if (this.debug && this.localDebuggerUrl) {
1388
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
1389
+ }
1390
+ this.projectId = normalizeProjectId(opts.projectId, {
1391
+ debug: this.debug,
1392
+ prefix: this.prefix
1393
+ });
1394
+ this.transformSpanHook = opts.transformSpan;
1395
+ this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
1396
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
1397
+ }
1398
+ /**
1399
+ * Cap every string attribute value on the span. O(#attributes) length
1400
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
1401
+ * pipeline so the default secret-scrub still sees parseable JSON in
1402
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
1403
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
1404
+ * in the surviving prefix).
1405
+ *
1406
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
1407
+ * for span content, matching the Python SDK and the OTel SDK convention.
1408
+ */
1409
+ capSpanAttributes(span) {
1410
+ var _a;
1411
+ const maxChars = applyOtelSpanAttributeLimit(
1412
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
1413
+ );
1414
+ const attrs = span.attributes;
1415
+ if (!attrs || attrs.length === 0) return span;
1416
+ let nextAttrs;
1417
+ for (let i = 0; i < attrs.length; i++) {
1418
+ const attr = attrs[i];
1419
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
1420
+ if (typeof value !== "string" || value.length <= maxChars) continue;
1421
+ if (!nextAttrs) nextAttrs = attrs.slice();
1422
+ nextAttrs[i] = {
1423
+ key: attr.key,
1424
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
1425
+ };
1426
+ }
1427
+ if (!nextAttrs) return span;
1428
+ return { ...span, attributes: nextAttrs };
1429
+ }
1430
+ /**
1431
+ * Apply the user `transformSpan` hook (if any) followed by the default
1432
+ * redactor (unless disabled). Returns either the (possibly new) span to
1433
+ * ship, or `null` to drop the span entirely.
1434
+ *
1435
+ * Ordering: user hook runs first so callers can rewrite the span freely
1436
+ * (rename attrs, add new ones, scrub things the default doesn't know
1437
+ * about). The default redactor then runs on whatever the user produced,
1438
+ * acting as the always-on floor for documented BYOK secrets. If the user
1439
+ * sets `disableDefaultRedaction: true`, the floor is skipped.
1440
+ *
1441
+ * Fail-closed: if the user hook throws, the span is dropped — a buggy
1442
+ * hook can never accidentally ship raw, un-redacted spans.
1443
+ */
1444
+ redactSpan(span) {
1445
+ let current = span;
1446
+ if (this.transformSpanHook) {
1447
+ try {
1448
+ const result = this.transformSpanHook(current);
1449
+ if (result === null) return null;
1450
+ if (result !== void 0) {
1451
+ copyReplayTraceDestination(current, result);
1452
+ current = result;
1453
+ }
1454
+ } catch (err) {
1455
+ if (this.debug) {
1456
+ const msg = err instanceof Error ? err.message : String(err);
1457
+ console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
1458
+ }
1459
+ return null;
1460
+ }
1461
+ }
1462
+ try {
1463
+ const normalized = normalizeModelUsageSpan(current);
1464
+ copyReplayTraceDestination(current, normalized);
1465
+ current = normalized;
1466
+ } catch (e) {
1467
+ }
1468
+ if (!this.disableDefaultRedaction) {
1469
+ const redacted = defaultTransformSpan(current);
1470
+ copyReplayTraceDestination(current, redacted);
1471
+ current = redacted;
1472
+ }
1473
+ const capped = this.capSpanAttributes(current);
1474
+ copyReplayTraceDestination(current, capped);
1475
+ return capped;
1476
+ }
1477
+ isDebugEnabled() {
1478
+ return this.debug;
1479
+ }
1480
+ authHeaders() {
1481
+ return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
1482
+ }
1483
+ requestHeaders() {
1484
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
1485
+ }
1486
+ startSpan(args) {
1487
+ var _a, _b;
1488
+ const ids = createSpanIds(args.parent);
1489
+ const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
1490
+ const attrs = [
1491
+ attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
1492
+ ...identityAttrs(args),
1493
+ attrString("ai.operationId", args.operationId),
1494
+ ...Object.entries(evalScopeAttributes()).map(([key, value]) => attrString(key, value))
1495
+ ];
1496
+ if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
1497
+ const span = {
1498
+ ids,
1499
+ name: args.name,
1500
+ startTimeUnixNano: started,
1501
+ attributes: attrs
1502
+ };
1503
+ rememberReplayTraceDestination(span);
1504
+ const openSpan = buildOtlpSpan({
1505
+ ids: span.ids,
1506
+ name: span.name,
1507
+ startTimeUnixNano: span.startTimeUnixNano,
1508
+ endTimeUnixNano: span.startTimeUnixNano,
1509
+ // placeholder — will be updated on endSpan
1510
+ attributes: span.attributes,
1511
+ status: { code: SpanStatusCode.UNSET }
1512
+ });
1513
+ copyReplayTraceDestination(span, openSpan);
1514
+ this.mirrorToLocalDebugger(openSpan);
1515
+ return span;
1516
+ }
1517
+ mirrorToLocalDebugger(span, completed = false) {
1518
+ const destination = replayTraceDestinationFor(span);
1519
+ if (destination && destination.kind !== "workshop") return;
1520
+ const localDebuggerUrl = (destination == null ? void 0 : destination.kind) === "workshop" ? destination.url : this.localDebuggerUrl;
1521
+ if (!localDebuggerUrl) return;
1522
+ const redacted = this.redactSpan(span);
1523
+ if (redacted === null) return;
1524
+ if (completed && (destination == null ? void 0 : destination.kind) === "workshop") {
1525
+ recordReplayExportedSpan(
1526
+ destination,
1527
+ base64ToHex(redacted.spanId),
1528
+ base64ToHex(redacted.traceId)
1529
+ );
1530
+ }
1531
+ const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
1532
+ const p = mirrorTraceExportToLocalDebugger(body, {
1533
+ baseUrl: localDebuggerUrl,
1534
+ debug: false,
1535
+ sdkName: this.sdkName
1536
+ });
1537
+ this.inFlight.add(p);
1538
+ void p.finally(() => {
1539
+ this.inFlight.delete(p);
1540
+ });
1541
+ }
1542
+ endSpan(span, extra) {
1543
+ var _a, _b;
1544
+ if (span.endTimeUnixNano) return;
1545
+ span.endTimeUnixNano = (_a = extra == null ? void 0 : extra.endTimeUnixNano) != null ? _a : nowUnixNanoString();
1546
+ if ((_b = extra == null ? void 0 : extra.attributes) == null ? void 0 : _b.length) {
1547
+ span.attributes.push(...extra.attributes);
1548
+ }
1549
+ let status = extra == null ? void 0 : extra.status;
1550
+ if (!status && (extra == null ? void 0 : extra.error) !== void 0) {
1551
+ const message = extra.error instanceof Error ? extra.error.message : String(extra.error);
1552
+ status = { code: SpanStatusCode.ERROR, message };
1553
+ }
1554
+ const otlp = buildOtlpSpan({
1555
+ ids: span.ids,
1556
+ name: span.name,
1557
+ startTimeUnixNano: span.startTimeUnixNano,
1558
+ endTimeUnixNano: span.endTimeUnixNano,
1559
+ attributes: span.attributes,
1560
+ status
1561
+ });
1562
+ copyReplayTraceDestination(span, otlp);
1563
+ this.enqueue(otlp);
1564
+ this.mirrorToLocalDebugger(otlp, true);
1565
+ }
1566
+ createSpan(args) {
1567
+ var _a;
1568
+ const ids = createSpanIds(args.parent);
1569
+ const attrs = [
1570
+ attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
1571
+ ...identityAttrs(args),
1572
+ ...Object.entries(evalScopeAttributes()).map(([key, value]) => attrString(key, value))
1573
+ ];
1574
+ if ((_a = args.attributes) == null ? void 0 : _a.length) attrs.push(...args.attributes);
1575
+ const otlp = buildOtlpSpan({
1576
+ ids,
1577
+ name: args.name,
1578
+ startTimeUnixNano: args.startTimeUnixNano,
1579
+ endTimeUnixNano: args.endTimeUnixNano,
1580
+ attributes: attrs,
1581
+ status: args.status
1582
+ });
1583
+ rememberReplayTraceDestination(otlp);
1584
+ this.enqueue(otlp);
1585
+ this.mirrorToLocalDebugger(otlp, true);
1586
+ }
1587
+ enqueue(span) {
1588
+ if (!this.enabled) return;
1589
+ if (this.debugSpans) {
1590
+ const short = (s) => s ? s.slice(-8) : "none";
1591
+ console.log(
1592
+ `${this.prefix}[span] name=${span.name} trace=${short(span.traceId)} span=${short(span.spanId)} parent=${short(
1593
+ span.parentSpanId
1594
+ )}`
1595
+ );
1596
+ }
1597
+ const redacted = this.redactSpan(span);
1598
+ if (redacted === null) return;
1599
+ const destination = replayTraceDestinationFor(redacted);
1600
+ recordReplayExportedSpan(
1601
+ destination,
1602
+ base64ToHex(redacted.spanId),
1603
+ base64ToHex(redacted.traceId)
1604
+ );
1605
+ if ((destination == null ? void 0 : destination.kind) === "workshop") return;
1606
+ if (this.queue.length >= this.maxQueueSize) {
1607
+ this.queue.shift();
1608
+ }
1609
+ this.queue.push(redacted);
1610
+ if (this.queue.length >= this.maxBatchSize) {
1611
+ void this.flush().catch(() => {
1612
+ });
1613
+ return;
1614
+ }
1615
+ if (!this.timer) {
1616
+ this.timer = setTimeout(() => {
1617
+ this.timer = void 0;
1618
+ void this.flush().catch(() => {
1619
+ });
1620
+ }, this.flushIntervalMs);
1621
+ }
1622
+ }
1623
+ async flush() {
1624
+ var _a;
1625
+ if (!this.enabled) {
1626
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1627
+ })));
1628
+ return;
1629
+ }
1630
+ if (this.timer) {
1631
+ clearTimeout(this.timer);
1632
+ this.timer = void 0;
1633
+ }
1634
+ while (this.queue.length > 0) {
1635
+ const batch = this.queue.splice(0, this.maxBatchSize);
1636
+ const groups = /* @__PURE__ */ new Map();
1637
+ for (const span of batch) {
1638
+ const destination = replayTraceDestinationFor(span);
1639
+ const spans = groups.get(destination);
1640
+ if (spans) spans.push(span);
1641
+ else groups.set(destination, [span]);
1642
+ }
1643
+ for (const [destination, spans] of groups) {
1644
+ if (!destination && !this.writeKey) continue;
1645
+ if ((destination == null ? void 0 : destination.kind) === "workshop") continue;
1646
+ const opts = this.requestOpts();
1647
+ if (!opts) {
1648
+ rateLimitedLog(
1649
+ `${this.prefix}.shutdown_deadline`,
1650
+ () => console.warn(
1651
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${spans.length} spans`
1652
+ )
1653
+ );
1654
+ continue;
1655
+ }
1656
+ const body = buildExportTraceServiceRequest(spans, this.serviceName, this.serviceVersion);
1657
+ const url = destination ? replayTraceUrl(destination.url) : `${this.baseUrl}traces`;
1658
+ const headers = destination ? {
1659
+ Authorization: `Bearer ${destination.apiKey}`,
1660
+ "X-Raindrop-Replay-Id": destination.replayId,
1661
+ "X-Raindrop-Project-Id": (_a = destination.projectId) != null ? _a : "default"
1662
+ } : this.requestHeaders();
1663
+ if (this.debug) {
1664
+ console.log(`${this.prefix} sending traces batch`, {
1665
+ spans: spans.length,
1666
+ endpoint: url
1667
+ });
1668
+ }
1669
+ const p = postJson(url, body, headers, opts);
1670
+ this.inFlight.add(p);
1671
+ try {
1672
+ try {
1673
+ await p;
1674
+ if (this.debug) console.log(`${this.prefix} sent ${spans.length} spans`);
1675
+ } catch (err) {
1676
+ const msg = err instanceof Error ? err.message : String(err);
1677
+ rateLimitedLog(
1678
+ `${this.prefix}.send_spans_failed`,
1679
+ () => console.warn(`${this.prefix} failed to send ${spans.length} spans: ${msg}`)
1680
+ );
1681
+ }
1682
+ } finally {
1683
+ this.inFlight.delete(p);
1684
+ }
1685
+ }
1686
+ }
1687
+ }
1688
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1689
+ requestOpts() {
1690
+ if (this.shutdownDeadlineAt !== void 0) {
1691
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1692
+ if (remainingMs <= 0) return null;
1693
+ return {
1694
+ maxAttempts: 1,
1695
+ debug: this.debug,
1696
+ sdkName: this.sdkName,
1697
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1698
+ };
1699
+ }
1700
+ if (this.hasShutdown) {
1701
+ return {
1702
+ maxAttempts: 1,
1703
+ debug: this.debug,
1704
+ sdkName: this.sdkName,
1705
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1706
+ };
1707
+ }
1708
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1709
+ }
1710
+ async shutdown() {
1711
+ this.hasShutdown = true;
1712
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1713
+ try {
1714
+ if (this.timer) {
1715
+ clearTimeout(this.timer);
1716
+ this.timer = void 0;
1717
+ }
1718
+ const drain = async () => {
1719
+ await this.flush();
1720
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1721
+ })));
1722
+ };
1723
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1724
+ if (!settled) {
1725
+ rateLimitedLog(
1726
+ `${this.prefix}.shutdown_deadline`,
1727
+ () => console.warn(
1728
+ `${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`
1729
+ )
1730
+ );
1731
+ }
1732
+ } finally {
1733
+ this.shutdownDeadlineAt = void 0;
1734
+ }
1735
+ }
1736
+ };
1737
+
1738
+ // ../core/dist/index.node.js
1739
+ import { AsyncLocalStorage } from "async_hooks";
1740
+ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
1741
+ var SUPPRESS_TRACING_KEY = /* @__PURE__ */ Symbol.for(
1742
+ "OpenTelemetry SDK Context Key SUPPRESS_TRACING"
1743
+ );
1744
+ function findOtelContextManager() {
1745
+ var _a;
1746
+ for (const sym of Object.getOwnPropertySymbols(globalThis)) {
1747
+ if (!((_a = sym.description) == null ? void 0 : _a.startsWith("opentelemetry.js.api."))) continue;
1748
+ const api = globalThis[sym];
1749
+ const cm = api == null ? void 0 : api.context;
1750
+ if (cm && typeof cm.with === "function" && typeof cm.active === "function") {
1751
+ return cm;
1752
+ }
1753
+ }
1754
+ return void 0;
1755
+ }
1756
+ function installTracingSuppressionHook() {
1757
+ if (typeof globalThis.RAINDROP_SUPPRESS_TRACING === "function") return;
1758
+ const hook = (fn) => {
1759
+ const cm = findOtelContextManager();
1760
+ if (!cm) return fn();
1761
+ return cm.with(cm.active().setValue(SUPPRESS_TRACING_KEY, true), fn);
1762
+ };
1763
+ globalThis.RAINDROP_SUPPRESS_TRACING = hook;
1764
+ }
1765
+ installTracingSuppressionHook();
1766
+
1767
+ // package.json
1768
+ var package_default = {
1769
+ name: "@raindrop-ai/cursor",
1770
+ version: "0.0.1",
1771
+ description: "Raindrop observability for Cursor agents via hooks",
1772
+ license: "MIT",
1773
+ type: "module",
1774
+ main: "dist/index.cjs",
1775
+ module: "dist/index.js",
1776
+ types: "dist/index.d.ts",
1777
+ bin: {
1778
+ "raindrop-cursor": "dist/cli.js"
1779
+ },
1780
+ exports: {
1781
+ ".": {
1782
+ import: {
1783
+ types: "./dist/index.d.ts",
1784
+ default: "./dist/index.js"
1785
+ },
1786
+ require: {
1787
+ types: "./dist/index.d.cts",
1788
+ default: "./dist/index.cjs"
1789
+ }
1790
+ }
1791
+ },
1792
+ sideEffects: false,
1793
+ files: [
1794
+ "dist/**",
1795
+ "README.md",
1796
+ ".cursor-plugin/**",
1797
+ "hooks/**",
1798
+ ".mcp.json"
1799
+ ],
1800
+ engines: {
1801
+ node: ">=20"
1802
+ },
1803
+ scripts: {
1804
+ build: "tsup",
1805
+ postbuild: "chmod +x ./dist/cli.js && node ./scripts/sync-plugin-version.mjs",
1806
+ prepack: "node ./scripts/sync-plugin-version.mjs",
1807
+ dev: "tsup --watch",
1808
+ clean: "rm -rf dist",
1809
+ test: "vitest run",
1810
+ lint: "eslint src tests --ext .ts && tsc --noEmit",
1811
+ typecheck: "tsc --noEmit"
1812
+ },
1813
+ dependencies: {
1814
+ zod: "4.3.6"
1815
+ },
1816
+ devDependencies: {
1817
+ "@raindrop-ai/core": "workspace:*",
1818
+ "@types/node": "20.19.39",
1819
+ msw: "2.13.0",
1820
+ tsup: "8.5.1",
1821
+ typescript: "5.9.3",
1822
+ vitest: "2.1.9"
1823
+ },
1824
+ publishConfig: {
1825
+ access: "public"
1826
+ }
1827
+ };
1828
+
1829
+ // src/package-info.ts
1830
+ var PACKAGE_NAME = "@raindrop-ai/cursor";
1831
+ var PACKAGE_VERSION = package_default.version;
1832
+
1833
+ // src/shipper.ts
1834
+ var EventShipper2 = class extends EventShipper {
1835
+ constructor(opts) {
1836
+ super({
1837
+ ...opts,
1838
+ sdkName: opts.sdkName ?? "cursor",
1839
+ libraryName: opts.libraryName ?? PACKAGE_NAME,
1840
+ libraryVersion: opts.libraryVersion ?? PACKAGE_VERSION
1841
+ });
1842
+ }
1843
+ };
1844
+ var TraceShipper2 = class extends TraceShipper {
1845
+ constructor(opts) {
1846
+ super({
1847
+ ...opts,
1848
+ sdkName: opts.sdkName ?? "cursor",
1849
+ serviceName: opts.serviceName ?? "raindrop.cursor",
1850
+ serviceVersion: opts.serviceVersion ?? PACKAGE_VERSION
1851
+ });
1852
+ }
1853
+ enqueue(span) {
1854
+ const attrs = span.attributes ?? [];
1855
+ attrs.unshift(
1856
+ { key: "span.id", value: { stringValue: span.spanId } },
1857
+ ...span.parentSpanId ? [{ key: "span.parent.id", value: { stringValue: span.parentSpanId } }] : []
1858
+ );
1859
+ span.attributes = attrs;
1860
+ super.enqueue(span);
1861
+ }
1862
+ };
1863
+
1864
+ // src/event-mapper.ts
1865
+ import { randomUUID as randomUUID3 } from "crypto";
1866
+ import { z as z4 } from "zod";
1867
+
1868
+ // src/setup.ts
1869
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1870
+ import { homedir as homedir2 } from "os";
1871
+ import { dirname as dirname2, join as join2 } from "path";
1872
+ import { createInterface } from "readline";
1873
+ import { Writable } from "stream";
1874
+ import { z as z2 } from "zod";
1875
+ var HOOK_EVENTS = [
1876
+ "sessionStart",
1877
+ "beforeSubmitPrompt",
1878
+ "postToolUse",
1879
+ "postToolUseFailure",
1880
+ "afterAgentResponse",
1881
+ "afterAgentThought",
1882
+ "stop",
1883
+ "preCompact",
1884
+ "sessionEnd"
1885
+ ];
1886
+ var scopeSchema = z2.enum(["user", "project"]);
1887
+ var hookSchema = z2.object({ command: z2.string().optional() }).passthrough();
1888
+ var hooksFileSchema = z2.object({
1889
+ version: z2.number().optional(),
1890
+ hooks: z2.record(z2.string(), z2.array(hookSchema)).default({})
1891
+ }).passthrough();
1892
+ var setupSchema = z2.object({
1893
+ scope: scopeSchema.optional(),
1894
+ writeKey: z2.string().optional(),
1895
+ userId: z2.string().optional(),
1896
+ projectId: z2.string().optional(),
1897
+ localOnly: z2.boolean().optional()
1898
+ });
1899
+ var persistedProjectSchema = z2.object({ project_id: z2.string().optional() });
1900
+ function getCursorHooksPath(scope, cwd = process.cwd()) {
1901
+ return join2(scope === "project" ? cwd : homedir2(), ".cursor", "hooks.json");
1902
+ }
1903
+ function hookCommand(scope) {
1904
+ return scope === "project" ? `npx -y @raindrop-ai/cursor@${PACKAGE_VERSION} hook` : "raindrop-cursor hook";
1905
+ }
1906
+ function isRaindropHook(command) {
1907
+ return command !== void 0 && /(?:^|\s)(?:raindrop-cursor|@raindrop-ai\/cursor(?:@[\w.^~+-]+)?)\s+hook(?:\s|$)/.test(command);
1908
+ }
1909
+ function readHooks(path) {
1910
+ return existsSync2(path) ? hooksFileSchema.parse(JSON.parse(readFileSync2(path, "utf8"))) : { version: 1, hooks: {} };
1911
+ }
1912
+ function writeJson(path, value) {
1913
+ mkdirSync2(dirname2(path), { recursive: true });
1914
+ writeFileSync2(path, JSON.stringify(value, null, 2) + "\n");
1915
+ }
1916
+ function isCursorMcpServer(value) {
1917
+ const server = z2.object({ command: z2.string(), args: z2.array(z2.string()).optional() }).safeParse(value);
1918
+ return server.success && (server.data.command === "raindrop-cursor" || server.data.command === "npx" && server.data.args?.some((arg) => /^@raindrop-ai\/cursor(?:@[\w.^~+-]+)?$/.test(arg)) === true);
1919
+ }
1920
+ function prompt(question, secret = false) {
1921
+ const output = secret ? new Writable({
1922
+ write(_chunk, _encoding, callback) {
1923
+ callback();
1924
+ }
1925
+ }) : process.stdout;
1926
+ if (secret) process.stdout.write(question);
1927
+ const rl = createInterface({
1928
+ input: process.stdin,
1929
+ output,
1930
+ terminal: secret && process.stdin.isTTY === true && typeof process.stdin.setRawMode === "function"
1931
+ });
1932
+ return new Promise((resolve) => {
1933
+ rl.question(secret ? "" : question, (answer) => {
1934
+ rl.close();
1935
+ if (secret) process.stdout.write("\n");
1936
+ resolve(answer.trim());
1937
+ });
1938
+ });
1939
+ }
1940
+ function projectChoice(value) {
1941
+ const trimmed = value?.trim();
1942
+ if (trimmed === void 0) return void 0;
1943
+ return trimmed === "" || trimmed.toLowerCase() === "default" ? { action: "default" } : { action: "slug", value: trimmed };
1944
+ }
1945
+ function persistedProjectId() {
1946
+ try {
1947
+ const parsed = persistedProjectSchema.safeParse(JSON.parse(readFileSync2(getConfigPath(), "utf8")));
1948
+ if (!parsed.success) return void 0;
1949
+ const choice = projectChoice(parsed.data.project_id);
1950
+ return choice?.action === "slug" ? choice.value : void 0;
1951
+ } catch {
1952
+ return void 0;
1953
+ }
1954
+ }
1955
+ async function resolveWriteKey(writeKey, localOnly) {
1956
+ const resolved = writeKey ?? process.env.RAINDROP_WRITE_KEY ?? "";
1957
+ if (resolved || localOnly || process.stdin.isTTY !== true) return resolved;
1958
+ const entered = await prompt("Enter your Raindrop write key: ", true);
1959
+ if (entered) return entered;
1960
+ console.error("\n Error: write key is required. Get it from https://app.raindrop.ai\n");
1961
+ process.exit(1);
1962
+ }
1963
+ async function resolveProjectSlug(projectId) {
1964
+ return projectChoice(projectId) ?? projectChoice(process.env.RAINDROP_PROJECT_ID) ?? (process.stdin.isTTY === true ? projectChoice(
1965
+ await prompt("Enter Raindrop project slug (blank for default Production): ")
1966
+ ) ?? { action: "keep" } : { action: "keep" });
1967
+ }
1968
+ async function runSetup(args = {}) {
1969
+ args = setupSchema.parse(args);
1970
+ const scope = args.scope ?? "user";
1971
+ const writeKey = await resolveWriteKey(args.writeKey, args.localOnly === true);
1972
+ const project = await resolveProjectSlug(args.projectId);
1973
+ const path = getCursorHooksPath(scope);
1974
+ const settings = readHooks(path);
1975
+ for (const [event, hooks] of Object.entries(settings.hooks)) {
1976
+ settings.hooks[event] = hooks.filter((hook) => !isRaindropHook(hook.command));
1977
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
1978
+ }
1979
+ for (const event of HOOK_EVENTS) {
1980
+ settings.hooks[event] = [
1981
+ ...settings.hooks[event] ?? [],
1982
+ { command: hookCommand(scope), timeout: 10 }
1983
+ ];
1984
+ }
1985
+ settings.version ??= 1;
1986
+ writeJson(path, settings);
1987
+ const existingProject = persistedProjectId();
1988
+ const projectSlug = project.action === "slug" ? project.value : void 0;
1989
+ if (writeKey || args.userId || project.action !== "keep") {
1990
+ updateConfig({
1991
+ ...writeKey ? { write_key: writeKey } : {},
1992
+ ...args.userId ? { user_id: args.userId } : {},
1993
+ ...project.action === "slug" ? { project_id: project.value } : {},
1994
+ ...project.action === "default" ? { project_id: void 0 } : {}
1995
+ });
1996
+ }
1997
+ const mcpPath = join2(dirname2(path), "mcp.json");
1998
+ const mcpSchema = z2.object({
1999
+ mcpServers: z2.record(z2.string(), z2.unknown()).default({})
2000
+ }).passthrough();
2001
+ const mcp = existsSync2(mcpPath) ? mcpSchema.parse(JSON.parse(readFileSync2(mcpPath, "utf8"))) : { mcpServers: {} };
2002
+ const serverName = mcp.mcpServers["raindrop-diagnostics"] && !isCursorMcpServer(mcp.mcpServers["raindrop-diagnostics"]) ? "raindrop-cursor-diagnostics" : "raindrop-diagnostics";
2003
+ mcp.mcpServers[serverName] ??= scope === "project" ? { command: "npx", args: ["-y", `@raindrop-ai/cursor@${PACKAGE_VERSION}`, "mcp-serve"] } : { command: "raindrop-cursor", args: ["mcp-serve"] };
2004
+ writeJson(mcpPath, mcp);
2005
+ const namedProject = projectSlug ?? (project.action === "keep" ? existingProject : void 0);
2006
+ console.log(`Installed Cursor hooks in ${path}.`);
2007
+ if (namedProject !== void 0) {
2008
+ console.log(`Telemetry will use project ${namedProject}.`);
2009
+ } else {
2010
+ console.log("Telemetry uses the org default project.");
2011
+ }
2012
+ if (!writeKey) {
2013
+ console.log("Set RAINDROP_WRITE_KEY to ship to Raindrop, or start the local debugger.");
2014
+ }
2015
+ if (scope === "project") {
2016
+ console.log("Cloud agents need RAINDROP_WRITE_KEY in their runtime environment secrets.");
2017
+ }
2018
+ }
2019
+ function runUninstall(scope = "user") {
2020
+ const path = getCursorHooksPath(scope);
2021
+ if (existsSync2(path)) {
2022
+ const settings = readHooks(path);
2023
+ for (const [event, hooks] of Object.entries(settings.hooks)) {
2024
+ settings.hooks[event] = hooks.filter((hook) => !isRaindropHook(hook.command));
2025
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
2026
+ }
2027
+ writeJson(path, settings);
2028
+ }
2029
+ const mcpPath = join2(dirname2(path), "mcp.json");
2030
+ if (existsSync2(mcpPath)) {
2031
+ const mcp = z2.object({ mcpServers: z2.record(z2.string(), z2.unknown()).default({}) }).passthrough().parse(JSON.parse(readFileSync2(mcpPath, "utf8")));
2032
+ for (const name of ["raindrop-diagnostics", "raindrop-cursor-diagnostics"]) {
2033
+ if (isCursorMcpServer(mcp.mcpServers[name])) delete mcp.mcpServers[name];
2034
+ }
2035
+ writeJson(mcpPath, mcp);
2036
+ }
2037
+ console.log("Removed Raindrop Cursor hooks.");
2038
+ }
2039
+
2040
+ // src/state.ts
2041
+ import { createHash } from "crypto";
2042
+ import { existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
2043
+ import { join as join4 } from "path";
2044
+ import { setTimeout as setTimeout2 } from "timers/promises";
2045
+ import { z as z3 } from "zod";
2046
+
2047
+ // src/storage.ts
2048
+ import { randomUUID as randomUUID2 } from "crypto";
2049
+ import {
2050
+ closeSync,
2051
+ constants,
2052
+ fstatSync,
2053
+ lstatSync,
2054
+ mkdirSync as mkdirSync3,
2055
+ openSync,
2056
+ readFileSync as readFileSync3,
2057
+ renameSync,
2058
+ rmSync,
2059
+ statSync,
2060
+ writeFileSync as writeFileSync3
2061
+ } from "fs";
2062
+ import { homedir as homedir3 } from "os";
2063
+ import { basename, join as join3 } from "path";
2064
+ function hasPrivateAccess(stat, forbiddenMode) {
2065
+ const uid = process.getuid?.();
2066
+ return uid === void 0 || stat.uid === uid && (stat.mode & forbiddenMode) === 0;
2067
+ }
2068
+ function stateDirectory() {
2069
+ const home = homedir3();
2070
+ const homeStat = statSync(home);
2071
+ if (!homeStat.isDirectory() || !hasPrivateAccess(homeStat, 18))
2072
+ throw new Error("Unsafe Cursor state parent directory");
2073
+ const directory = join3(home, ".raindrop-cursor");
2074
+ try {
2075
+ mkdirSync3(directory, { mode: 448 });
2076
+ } catch {
2077
+ }
2078
+ const stat = lstatSync(directory);
2079
+ if (!stat.isDirectory() || !hasPrivateAccess(stat, 63))
2080
+ throw new Error("Unsafe Cursor state directory");
2081
+ return directory;
2082
+ }
2083
+ function statePath(name) {
2084
+ if (basename(name) !== name || name === "." || name === "..")
2085
+ throw new Error("Invalid Cursor state filename");
2086
+ return join3(stateDirectory(), name);
2087
+ }
2088
+ function checkFile(stat) {
2089
+ if (!stat.isFile() || stat.nlink !== 1 || !hasPrivateAccess(stat, 63))
2090
+ throw new Error("Unsafe Cursor state file");
2091
+ }
2092
+ function readStateFile(name) {
2093
+ const path = statePath(name);
2094
+ checkFile(lstatSync(path));
2095
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
2096
+ try {
2097
+ const stat = fstatSync(fd);
2098
+ checkFile(stat);
2099
+ return { contents: readFileSync3(fd, "utf8"), mtimeMs: stat.mtimeMs };
2100
+ } finally {
2101
+ closeSync(fd);
2102
+ }
2103
+ }
2104
+ function writeStateFile(name, contents) {
2105
+ const path = statePath(name);
2106
+ const temporary = `${path}.${randomUUID2()}`;
2107
+ const fd = openSync(temporary, "wx", 384);
2108
+ try {
2109
+ try {
2110
+ writeFileSync3(fd, contents, "utf8");
2111
+ } finally {
2112
+ closeSync(fd);
2113
+ }
2114
+ renameSync(temporary, path);
2115
+ } finally {
2116
+ rmSync(temporary, { force: true });
2117
+ }
2118
+ }
2119
+ function removeStateFile(name) {
2120
+ rmSync(statePath(name), { force: true });
2121
+ }
2122
+
2123
+ // src/state.ts
2124
+ var contextSchema = z3.object({ traceIdB64: z3.string(), spanIdB64: z3.string() });
2125
+ var usageSchema = z3.object({
2126
+ prompt_tokens: z3.number().optional(),
2127
+ completion_tokens: z3.number().optional(),
2128
+ cache_read_tokens: z3.number().optional(),
2129
+ cache_write_tokens: z3.number().optional()
2130
+ });
2131
+ var turnSchema = z3.object({
2132
+ eventId: z3.string(),
2133
+ root: contextSchema,
2134
+ startedAt: z3.number(),
2135
+ model: z3.string().optional(),
2136
+ displayModel: z3.string().optional(),
2137
+ finished: z3.boolean().default(false),
2138
+ usage: usageSchema.default({})
2139
+ });
2140
+ var sessionSchema = z3.object({
2141
+ model: z3.string().optional(),
2142
+ model_id: z3.string().optional(),
2143
+ cursor_version: z3.string().optional(),
2144
+ workspace_roots: z3.array(z3.string()).optional(),
2145
+ user_email: z3.string().optional(),
2146
+ composer_mode: z3.string().optional(),
2147
+ is_background_agent: z3.boolean().optional(),
2148
+ currentGeneration: z3.string().optional(),
2149
+ turns: z3.record(z3.string(), turnSchema).default({}),
2150
+ totals: usageSchema.default({})
2151
+ });
2152
+ function stateKey(id) {
2153
+ return createHash("sha256").update(id).digest("hex");
2154
+ }
2155
+ function eventTraceId(session, eventId) {
2156
+ for (const turn of Object.values(session.turns)) {
2157
+ if (turn.eventId === eventId) return turn.root.traceIdB64;
2158
+ }
2159
+ return void 0;
2160
+ }
2161
+ function readSession(id) {
2162
+ try {
2163
+ return sessionSchema.parse(JSON.parse(readStateFile(`session_${stateKey(id)}`).contents));
2164
+ } catch {
2165
+ return { turns: {}, totals: {} };
2166
+ }
2167
+ }
2168
+ function saveSession(id, state2) {
2169
+ writeStateFile(`session_${stateKey(id)}`, JSON.stringify(state2));
2170
+ const turn = state2.currentGeneration ? state2.turns[state2.currentGeneration] : void 0;
2171
+ if (turn) writeStateFile(`event_${stateKey(id)}`, turn.eventId);
2172
+ }
2173
+ function deleteSession(id) {
2174
+ removeStateFile(`session_${stateKey(id)}`);
2175
+ removeStateFile(`event_${stateKey(id)}`);
2176
+ }
2177
+ async function withSessionLock(id, work) {
2178
+ const path = join4(stateDirectory(), `lock_${stateKey(id)}`);
2179
+ const deadline = Date.now() + hookFlushDeadlineMs();
2180
+ const staleMs = Math.max(3e4, hookFlushDeadlineMs() + 5e3);
2181
+ const marker = join4(path, String(process.pid));
2182
+ for (; ; ) {
2183
+ try {
2184
+ mkdirSync4(path, { mode: 448 });
2185
+ writeFileSync4(marker, "");
2186
+ break;
2187
+ } catch {
2188
+ try {
2189
+ if (Date.now() - lstatSync2(path).mtimeMs > staleMs)
2190
+ rmSync2(path, { recursive: true, force: true });
2191
+ } catch {
2192
+ }
2193
+ if (Date.now() >= deadline) return void 0;
2194
+ await setTimeout2(10);
2195
+ }
2196
+ }
2197
+ const release = () => {
2198
+ try {
2199
+ if (existsSync3(marker)) rmSync2(path, { recursive: true, force: true });
2200
+ } catch {
2201
+ }
2202
+ };
2203
+ process.once("exit", release);
2204
+ try {
2205
+ return await work();
2206
+ } finally {
2207
+ process.removeListener("exit", release);
2208
+ release();
2209
+ }
2210
+ }
2211
+
2212
+ // src/event-mapper.ts
2213
+ var text = z4.string().optional().catch(void 0);
2214
+ var number = z4.number().finite().nonnegative().optional().catch(void 0);
2215
+ var bool = z4.boolean().optional().catch(void 0);
2216
+ var hookPayloadSchema = z4.object({
2217
+ hook_event_name: z4.enum(HOOK_EVENTS),
2218
+ conversation_id: text,
2219
+ session_id: text,
2220
+ generation_id: text,
2221
+ model: text,
2222
+ model_id: text,
2223
+ model_params: z4.unknown().optional(),
2224
+ cursor_version: text,
2225
+ workspace_roots: z4.array(z4.string()).optional().catch(void 0),
2226
+ user_email: text,
2227
+ is_background_agent: bool,
2228
+ composer_mode: text,
2229
+ prompt: text,
2230
+ attachments: z4.unknown().optional(),
2231
+ tool_name: text,
2232
+ tool_input: z4.unknown().optional(),
2233
+ tool_output: z4.unknown().optional(),
2234
+ tool_use_id: text,
2235
+ cwd: text,
2236
+ agent_message: text,
2237
+ duration: number,
2238
+ error_message: text,
2239
+ failure_type: text,
2240
+ is_interrupt: bool,
2241
+ status: text,
2242
+ duration_ms: number,
2243
+ message_count: number,
2244
+ text,
2245
+ input_tokens: number,
2246
+ output_tokens: number,
2247
+ cache_read_tokens: number,
2248
+ cache_write_tokens: number,
2249
+ loop_count: number,
2250
+ trigger: text,
2251
+ context_usage_percent: number,
2252
+ context_tokens: number,
2253
+ context_window_size: number,
2254
+ is_first_compaction: bool,
2255
+ reason: text,
2256
+ final_status: text
2257
+ });
2258
+ var MAX_ATTR_LENGTH = 32768;
2259
+ function safeStringify(value) {
2260
+ return value === void 0 ? void 0 : stringifyBounded(value, MAX_ATTR_LENGTH);
2261
+ }
2262
+ function stringAttr(key, value) {
2263
+ return attrString(key, value === void 0 ? void 0 : capText(value, MAX_ATTR_LENGTH));
2264
+ }
2265
+ function inferModelProvider(modelId) {
2266
+ const id = (modelId ?? "").toLowerCase();
2267
+ if (id.includes("claude")) return "anthropic";
2268
+ if (id.includes("gpt-") || /(?:^|-)o[134]/.test(id)) return "openai";
2269
+ if (id.includes("gemini")) return "google";
2270
+ if (id.includes("grok")) return "xai";
2271
+ return "cursor";
2272
+ }
2273
+ function usageFrom(payload) {
2274
+ return {
2275
+ ...payload.input_tokens !== void 0 ? { prompt_tokens: payload.input_tokens } : {},
2276
+ ...payload.output_tokens !== void 0 ? { completion_tokens: payload.output_tokens } : {},
2277
+ ...payload.cache_read_tokens !== void 0 ? { cache_read_tokens: payload.cache_read_tokens } : {},
2278
+ ...payload.cache_write_tokens !== void 0 ? { cache_write_tokens: payload.cache_write_tokens } : {}
2279
+ };
2280
+ }
2281
+ function usageProperties(prefix, usage) {
2282
+ return Object.fromEntries(
2283
+ Object.entries(usage).map(([key, value]) => [`${prefix}.${key}`, value])
2284
+ );
2285
+ }
2286
+ function sessionUsageProperties(usage) {
2287
+ return {
2288
+ ...usageProperties("ai.usage.session_total", usage),
2289
+ ...usage.cache_write_tokens === void 0 ? {} : { "ai.usage.session_total.cache_creation_tokens": usage.cache_write_tokens }
2290
+ };
2291
+ }
2292
+ function timing(duration, startedAt = Date.now()) {
2293
+ const endMs = Date.now();
2294
+ const elapsed = Math.min(endMs, Math.max(0, duration ?? endMs - startedAt));
2295
+ return {
2296
+ startTimeUnixNano: `${Math.trunc(endMs - elapsed)}000000`,
2297
+ endTimeUnixNano: `${endMs}000000`
2298
+ };
2299
+ }
2300
+ function finishTurn(turn, totals, traceShipper, patch, status) {
2301
+ for (const key of [
2302
+ "prompt_tokens",
2303
+ "completion_tokens",
2304
+ "cache_read_tokens",
2305
+ "cache_write_tokens"
2306
+ ]) {
2307
+ const value = turn.usage[key];
2308
+ if (value !== void 0) totals[key] = (totals[key] ?? 0) + value;
2309
+ }
2310
+ const provider = canonicalModelProvider(inferModelProvider(turn.model));
2311
+ traceShipper.createSpan({
2312
+ name: turn.model ?? "cursor",
2313
+ eventId: turn.eventId,
2314
+ parent: turn.root,
2315
+ userId: patch.userId,
2316
+ convoId: patch.convoId,
2317
+ eventName: patch.eventName,
2318
+ ...timing(void 0, turn.startedAt),
2319
+ attributes: [
2320
+ attrString("ai.operationId", "generateText"),
2321
+ stringAttr("gen_ai.request.model", turn.model),
2322
+ stringAttr("gen_ai.response.model", turn.model),
2323
+ attrString("gen_ai.system", provider),
2324
+ attrString("gen_ai.provider.name", provider),
2325
+ ...Object.entries(turn.usage).map(([key, value]) => attrInt(`gen_ai.usage.${key}`, value)),
2326
+ ...status === "cancelled" ? Object.entries(cancelledTerminalAttributes()).map(
2327
+ ([key, value]) => attrString(key, value)
2328
+ ) : []
2329
+ ],
2330
+ ...status === "error" || status === "aborted" ? { status: { code: 2, message: status } } : {}
2331
+ });
2332
+ turn.finished = true;
2333
+ }
2334
+ function conversationIdForHook(payload) {
2335
+ return payload.conversation_id || payload.session_id;
2336
+ }
2337
+ function emitHookSpan(traceShipper, payload, eventId, parent, ids) {
2338
+ const event = payload.hook_event_name;
2339
+ if (event === "afterAgentThought") {
2340
+ traceShipper.createSpan({
2341
+ name: "ai.thinking",
2342
+ eventId,
2343
+ parent,
2344
+ ...ids,
2345
+ ...timing(payload.duration_ms),
2346
+ attributes: [
2347
+ attrString("ai.operationId", "ai.thinking"),
2348
+ stringAttr("ai.thinking.text", payload.text),
2349
+ attrInt("traceloop.entity.duration_ms", payload.duration_ms)
2350
+ ]
2351
+ });
2352
+ return;
2353
+ }
2354
+ if (event !== "postToolUse" && event !== "postToolUseFailure") return;
2355
+ const failed = event === "postToolUseFailure";
2356
+ const error = capText(payload.error_message ?? "Tool execution failed", MAX_ATTR_LENGTH);
2357
+ const toolAttrs = [
2358
+ stringAttr("ai.toolCall.name", payload.tool_name),
2359
+ stringAttr("ai.toolCall.id", payload.tool_use_id),
2360
+ attrString("ai.toolCall.args", safeStringify(payload.tool_input))
2361
+ ];
2362
+ traceShipper.createSpan({
2363
+ name: "ai.toolCall",
2364
+ eventId,
2365
+ parent,
2366
+ ...ids,
2367
+ ...timing(payload.duration),
2368
+ attributes: [
2369
+ attrString("ai.operationId", "ai.toolCall"),
2370
+ ...toolAttrs,
2371
+ attrString("ai.toolCall.result", safeStringify(payload.tool_output)),
2372
+ attrInt("traceloop.entity.duration_ms", payload.duration),
2373
+ stringAttr("cwd", payload.cwd),
2374
+ ...failed ? [
2375
+ stringAttr("cursor.tool.failure_type", payload.failure_type),
2376
+ attrBool("cursor.tool.is_interrupt", payload.is_interrupt)
2377
+ ] : []
2378
+ ],
2379
+ ...failed ? { status: { code: 2, message: error } } : {}
2380
+ });
2381
+ if (failed && payload.failure_type === "permission_denied") {
2382
+ traceShipper.createSpan({
2383
+ name: "ai.permissionDenied",
2384
+ eventId,
2385
+ parent,
2386
+ ...ids,
2387
+ ...timing(payload.duration),
2388
+ attributes: [
2389
+ attrString("ai.operationId", "ai.permissionDenied"),
2390
+ ...toolAttrs,
2391
+ attrString("ai.permissionDenied.reason", error)
2392
+ ],
2393
+ status: { code: 2, message: error }
2394
+ });
2395
+ }
2396
+ }
2397
+ function updateSession(session, payload, applySessionModel) {
2398
+ if (applySessionModel) {
2399
+ if (payload.model !== void 0 && payload.model !== session.model && !payload.model_id) {
2400
+ session.model_id = void 0;
2401
+ }
2402
+ if (payload.model !== void 0) session.model = capText(payload.model, MAX_ATTR_LENGTH);
2403
+ if (payload.model_id !== void 0)
2404
+ session.model_id = capText(payload.model_id, MAX_ATTR_LENGTH);
2405
+ }
2406
+ if (payload.cursor_version !== void 0)
2407
+ session.cursor_version = capText(payload.cursor_version, MAX_ATTR_LENGTH);
2408
+ if (payload.workspace_roots !== void 0)
2409
+ session.workspace_roots = payload.workspace_roots.slice(0, 100).map((root) => capText(root, MAX_ATTR_LENGTH));
2410
+ if (payload.user_email) session.user_email = capText(payload.user_email, MAX_ATTR_LENGTH);
2411
+ if (payload.composer_mode !== void 0)
2412
+ session.composer_mode = capText(payload.composer_mode, MAX_ATTR_LENGTH);
2413
+ if (payload.is_background_agent !== void 0)
2414
+ session.is_background_agent = payload.is_background_agent;
2415
+ }
2416
+ function mapPayload(payload, config, traceShipper, conversationId) {
2417
+ const session = readSession(conversationId);
2418
+ const event = payload.hook_event_name;
2419
+ const generationKey = event === "sessionEnd" && session.currentGeneration ? session.currentGeneration : payload.generation_id ? stateKey(payload.generation_id) : session.currentGeneration ?? stateKey("unknown-generation");
2420
+ let turn = session.turns[generationKey];
2421
+ if (!turn && event === "sessionEnd") {
2422
+ deleteSession(conversationId);
2423
+ return;
2424
+ }
2425
+ const modelChanged = payload.model_id === void 0 && payload.model !== void 0 && capText(payload.model, MAX_ATTR_LENGTH) !== (turn ? turn.displayModel : session.model);
2426
+ const explicitModel = payload.model_id ?? (modelChanged ? payload.model : void 0);
2427
+ const model = explicitModel ?? turn?.model ?? session.model_id ?? payload.model ?? session.model;
2428
+ const displayModel = payload.model !== void 0 ? capText(payload.model, MAX_ATTR_LENGTH) : model === session.model_id ? session.model : void 0;
2429
+ const isNew = !turn;
2430
+ const applySessionModel = event === "sessionStart" || isNew || event === "beforeSubmitPrompt" || generationKey === session.currentGeneration;
2431
+ updateSession(session, payload, applySessionModel);
2432
+ if (event === "sessionStart") {
2433
+ saveSession(conversationId, session);
2434
+ return;
2435
+ }
2436
+ const userId = session.user_email ?? config.userId;
2437
+ const convoId = config.convoId ?? conversationId;
2438
+ const properties = {
2439
+ ...z4.record(z4.string(), z4.unknown()).parse(boundedClone(config.customProperties, MAX_ATTR_LENGTH)),
2440
+ sdk: PACKAGE_NAME,
2441
+ sdk_version: PACKAGE_VERSION,
2442
+ cursor_conversation_id: capText(conversationId, MAX_ATTR_LENGTH),
2443
+ cursor_generation_id: event !== "sessionEnd" && payload.generation_id ? capText(payload.generation_id, MAX_ATTR_LENGTH) : void 0,
2444
+ cursor_version: session.cursor_version,
2445
+ workspace_roots: boundedClone(session.workspace_roots, MAX_ATTR_LENGTH),
2446
+ composer_mode: session.composer_mode,
2447
+ is_background_agent: session.is_background_agent,
2448
+ ...payload.model_params !== void 0 ? { model_params: boundedClone(payload.model_params, MAX_ATTR_LENGTH) } : {},
2449
+ ...session.model !== void 0 && session.model_id !== void 0 && session.model !== session.model_id ? { cursor: { model: session.model, model_id: session.model_id } } : {}
2450
+ };
2451
+ if (!turn) {
2452
+ const eventId = `cursor_${randomUUID3()}`;
2453
+ const root = traceShipper.startSpan({
2454
+ name: model ?? "cursor",
2455
+ eventId,
2456
+ userId,
2457
+ convoId,
2458
+ eventName: config.eventName,
2459
+ attributes: [
2460
+ attrString("ai.operationId", "generateText"),
2461
+ stringAttr("session.model", model),
2462
+ stringAttr("cursor.conversation_id", conversationId),
2463
+ stringAttr("cursor.generation_id", payload.generation_id),
2464
+ attrBool("cursor.is_background_agent", session.is_background_agent)
2465
+ ]
2466
+ });
2467
+ traceShipper.endSpan(root);
2468
+ turn = {
2469
+ eventId,
2470
+ root: { traceIdB64: root.ids.traceIdB64, spanIdB64: root.ids.spanIdB64 },
2471
+ startedAt: Date.now(),
2472
+ model,
2473
+ displayModel,
2474
+ finished: false,
2475
+ usage: {}
2476
+ };
2477
+ session.turns[generationKey] = turn;
2478
+ }
2479
+ if (event !== "sessionEnd") {
2480
+ if (explicitModel !== void 0) turn.model = model;
2481
+ if (payload.model !== void 0 && displayModel !== void 0)
2482
+ turn.displayModel = displayModel;
2483
+ }
2484
+ if (isNew || event === "beforeSubmitPrompt") session.currentGeneration = generationKey;
2485
+ const patch = {
2486
+ eventName: config.eventName,
2487
+ userId,
2488
+ convoId,
2489
+ model: turn.model,
2490
+ isPending: !turn.finished,
2491
+ properties
2492
+ };
2493
+ const extraPatches = [];
2494
+ let includeParentPatch = true;
2495
+ switch (event) {
2496
+ case "beforeSubmitPrompt":
2497
+ if (payload.prompt !== void 0) patch.input = capText(payload.prompt);
2498
+ if (payload.attachments !== void 0)
2499
+ properties.attachments = boundedClone(payload.attachments, MAX_ATTR_LENGTH);
2500
+ break;
2501
+ case "postToolUse":
2502
+ case "postToolUseFailure":
2503
+ case "afterAgentThought":
2504
+ emitHookSpan(traceShipper, payload, turn.eventId, turn.root, {
2505
+ userId,
2506
+ convoId,
2507
+ eventName: config.eventName
2508
+ });
2509
+ break;
2510
+ case "afterAgentResponse":
2511
+ if (payload.text !== void 0) patch.output = capText(payload.text);
2512
+ if (!turn.finished) turn.usage = { ...turn.usage, ...usageFrom(payload) };
2513
+ break;
2514
+ case "stop":
2515
+ properties.status = payload.status;
2516
+ properties.loop_count = payload.loop_count;
2517
+ if (!turn.finished) {
2518
+ turn.usage = { ...turn.usage, ...usageFrom(payload) };
2519
+ finishTurn(turn, session.totals, traceShipper, patch, payload.status);
2520
+ }
2521
+ patch.isPending = false;
2522
+ Object.assign(
2523
+ properties,
2524
+ usageProperties("ai.usage", turn.usage),
2525
+ sessionUsageProperties(session.totals)
2526
+ );
2527
+ break;
2528
+ case "preCompact":
2529
+ Object.assign(properties, {
2530
+ compaction_trigger: payload.trigger,
2531
+ context_tokens: payload.context_tokens,
2532
+ context_window_size: payload.context_window_size,
2533
+ context_usage_percent: payload.context_usage_percent,
2534
+ is_first_compaction: payload.is_first_compaction,
2535
+ message_count: payload.message_count
2536
+ });
2537
+ break;
2538
+ case "sessionEnd": {
2539
+ includeParentPatch = false;
2540
+ patch.isPending = false;
2541
+ Object.assign(properties, {
2542
+ session_end_reason: payload.reason,
2543
+ session_duration_ms: payload.duration_ms,
2544
+ final_status: payload.final_status,
2545
+ error_message: payload.error_message
2546
+ });
2547
+ for (const parentTurn of Object.values(session.turns)) {
2548
+ if (!parentTurn.finished || parentTurn === turn) {
2549
+ const terminalPatch = {
2550
+ ...patch,
2551
+ model: parentTurn.model,
2552
+ properties: { ...properties }
2553
+ };
2554
+ if (!parentTurn.finished) {
2555
+ finishTurn(parentTurn, session.totals, traceShipper, terminalPatch, "cancelled");
2556
+ terminalPatch.properties = {
2557
+ ...terminalPatch.properties,
2558
+ ...usageProperties("ai.usage", parentTurn.usage),
2559
+ status: "cancelled",
2560
+ [HANDOFF_TERMINAL_PROPERTY_KEY]: "cancelled"
2561
+ };
2562
+ }
2563
+ extraPatches.push({ eventId: parentTurn.eventId, patch: terminalPatch });
2564
+ }
2565
+ }
2566
+ const finalPatch = extraPatches.find(({ eventId }) => eventId === turn.eventId);
2567
+ if (finalPatch) {
2568
+ finalPatch.patch.properties = {
2569
+ ...finalPatch.patch.properties,
2570
+ ...sessionUsageProperties(session.totals)
2571
+ };
2572
+ }
2573
+ break;
2574
+ }
2575
+ }
2576
+ if (event === "sessionEnd") {
2577
+ deleteSession(conversationId);
2578
+ } else {
2579
+ const completed = Object.entries(session.turns).filter(
2580
+ ([key, value]) => key !== session.currentGeneration && value.finished
2581
+ );
2582
+ for (const [key] of completed.slice(0, Math.max(0, completed.length - 100)))
2583
+ delete session.turns[key];
2584
+ saveSession(conversationId, session);
2585
+ }
2586
+ patch.properties = properties;
2587
+ const mapped = includeParentPatch ? [{ eventId: turn.eventId, patch }] : [];
2588
+ mapped.push(...extraPatches);
2589
+ for (const item of mapped) {
2590
+ const props = z4.record(z4.string(), z4.unknown()).parse(boundedClone(item.patch.properties ?? {}, DEFAULT_MAX_TEXT_FIELD_CHARS));
2591
+ const traceId = eventTraceId(session, item.eventId);
2592
+ if (traceId) props.trace_id = base64ToHex(traceId);
2593
+ item.patch.properties = props;
2594
+ }
2595
+ return mapped.length ? mapped : void 0;
2596
+ }
2597
+ async function mapHookToRaindrop(payload, config, eventShipper, traceShipper) {
2598
+ try {
2599
+ const parsed = hookPayloadSchema.safeParse(payload);
2600
+ if (!parsed.success) return;
2601
+ const conversationId = conversationIdForHook(parsed.data);
2602
+ if (!conversationId) return;
2603
+ await withSessionLock(conversationId, async () => {
2604
+ const mapped = mapPayload(parsed.data, config, traceShipper, conversationId);
2605
+ if (mapped?.length) {
2606
+ for (const item of mapped) await eventShipper.patch(item.eventId, item.patch);
2607
+ await eventShipper.flush();
2608
+ }
2609
+ });
2610
+ } catch (error) {
2611
+ if (config.debug) console.error(`[${PACKAGE_NAME}] hook mapping failed`, error);
2612
+ }
2613
+ }
2614
+
2615
+ // src/local-debugger.ts
2616
+ import { z as z5 } from "zod";
2617
+ var DEFAULT_PORT = 5899;
2618
+ var DEFAULT_URL = `http://localhost:${DEFAULT_PORT}/v1/`;
2619
+ var HEALTH_TIMEOUT_MS = 300;
2620
+ var CACHE_TTL_MS = 5e3;
2621
+ var debuggerCacheSchema = z5.object({ url: z5.literal(DEFAULT_URL).nullable(), ts: z5.number() });
2622
+ function readCache() {
2623
+ try {
2624
+ const data = debuggerCacheSchema.parse(JSON.parse(readStateFile("debugger_cache").contents));
2625
+ const age = Date.now() - data.ts;
2626
+ if (age < 0 || age > CACHE_TTL_MS) return void 0;
2627
+ return data;
2628
+ } catch {
2629
+ return void 0;
2630
+ }
2631
+ }
2632
+ function writeCache(url) {
2633
+ try {
2634
+ writeStateFile("debugger_cache", JSON.stringify({ url, ts: Date.now() }));
2635
+ } catch {
2636
+ }
2637
+ }
2638
+ async function healthPing(baseUrl) {
2639
+ const healthUrl = baseUrl.replace(/\/v1\/$/, "/health");
2640
+ try {
2641
+ const signal = AbortSignal.timeout(HEALTH_TIMEOUT_MS);
2642
+ const resp = await runWithTracingSuppressed(
2643
+ () => fetch(healthUrl, { signal, redirect: "error" })
2644
+ );
2645
+ if (resp.ok) {
2646
+ const body = z5.object({ ok: z5.boolean() }).safeParse(await resp.json());
2647
+ if (body.success && body.data.ok) return baseUrl;
2648
+ }
2649
+ } catch {
2650
+ }
2651
+ return null;
2652
+ }
2653
+ var _debuggerResultSchema = z5.object({ url: z5.string().nullable(), autoDetected: z5.boolean() });
2654
+ async function detectLocalDebugger(debug, skipAutoDetect = false) {
2655
+ if (/^(0|false|no|off)$/i.test(process.env[LOCAL_DEBUGGER_ENV_VAR]?.trim() ?? "")) {
2656
+ return { url: null, autoDetected: false };
2657
+ }
2658
+ const envUrl = resolveLocalDebuggerBaseUrl();
2659
+ if (envUrl) {
2660
+ if (debug) {
2661
+ console.log(
2662
+ `[raindrop-ai/cursor] Local debugger configured via ${LOCAL_DEBUGGER_ENV_VAR}: ${envUrl}`
2663
+ );
2664
+ }
2665
+ return { url: envUrl, autoDetected: false };
2666
+ }
2667
+ if (skipAutoDetect || process.env.CI?.trim()) {
2668
+ return { url: null, autoDetected: false };
2669
+ }
2670
+ const cached = readCache();
2671
+ if (cached) {
2672
+ if (debug) {
2673
+ console.log(`[raindrop-ai/cursor] Local debugger cache hit: ${cached.url ?? "not running"}`);
2674
+ }
2675
+ return { url: cached.url, autoDetected: cached.url !== null };
2676
+ }
2677
+ if (debug) {
2678
+ console.log(`[raindrop-ai/cursor] Probing local debugger at localhost:${DEFAULT_PORT}...`);
2679
+ }
2680
+ const detected = await healthPing(DEFAULT_URL);
2681
+ writeCache(detected);
2682
+ if (detected && debug) {
2683
+ console.log(`[raindrop-ai/cursor] Local debugger auto-detected at ${detected}`);
2684
+ }
2685
+ return { url: detected, autoDetected: detected !== null };
2686
+ }
2687
+
2688
+ // src/mcp-serve.ts
2689
+ import { readdirSync } from "fs";
2690
+ import { createInterface as createInterface2 } from "readline";
2691
+ import { z as z6 } from "zod";
2692
+ var DEFAULT_SIGNALS = {
2693
+ missing_context: {
2694
+ description: "You cannot complete the task because critical information, credentials, or access is missing and the user cannot provide it. Do NOT report this for normal clarifying questions. only when you are blocked.",
2695
+ sentiment: "NEGATIVE"
2696
+ },
2697
+ repeatedly_broken_tool: {
2698
+ description: "A tool has failed or not returned the expected response on multiple distinct attempts in this conversation, preventing task completion. A single tool error is NOT enough. the tool must be persistently broken or aberrantly behaving across retries.",
2699
+ sentiment: "NEGATIVE"
2700
+ },
2701
+ capability_gap: {
2702
+ description: "The task requires a tool, permission, or capability that you do not have. For example, the user asks you to perform an action but no suitable tool exists, or you lack the necessary access. Do NOT report this if you simply need more information from the user. only when the gap is in your own capabilities.",
2703
+ sentiment: "NEGATIVE"
2704
+ },
2705
+ complete_task_failure: {
2706
+ description: "You were unable to accomplish what the user asked despite making genuine attempts. This is NOT a refusal or policy block. you tried and failed to deliver the result.",
2707
+ sentiment: "NEGATIVE"
2708
+ }
2709
+ };
2710
+ var NOTEWORTHY_KEY = "noteworthy";
2711
+ var NOTEWORTHY_DEFAULT_DESCRIPTION = "Only when no specific category applies: flag that this turn is noteworthy for developer review.";
2712
+ function normalizeSignals(custom) {
2713
+ let base;
2714
+ if (!custom || Object.keys(custom).length === 0) {
2715
+ base = { ...DEFAULT_SIGNALS };
2716
+ } else {
2717
+ const validated = {};
2718
+ for (const [key, def] of Object.entries(custom)) {
2719
+ const k = key.trim();
2720
+ if (!k || k === NOTEWORTHY_KEY) continue;
2721
+ if (!def || typeof def !== "object") continue;
2722
+ const desc = typeof def.description === "string" ? def.description.trim() : "";
2723
+ if (!desc) continue;
2724
+ const sentiment = def.sentiment;
2725
+ validated[k] = {
2726
+ description: desc,
2727
+ ...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
2728
+ };
2729
+ }
2730
+ base = Object.keys(validated).length > 0 ? validated : { ...DEFAULT_SIGNALS };
2731
+ }
2732
+ const customNoteworthy = custom?.[NOTEWORTHY_KEY];
2733
+ base[NOTEWORTHY_KEY] = {
2734
+ description: typeof customNoteworthy?.description === "string" && customNoteworthy.description.trim() ? customNoteworthy.description.trim() : NOTEWORTHY_DEFAULT_DESCRIPTION,
2735
+ ...customNoteworthy?.sentiment === "POSITIVE" || customNoteworthy?.sentiment === "NEGATIVE" ? { sentiment: customNoteworthy.sentiment } : {}
2736
+ };
2737
+ return base;
2738
+ }
2739
+ function resolveToolConfig(diagConfig) {
2740
+ const signals = normalizeSignals(diagConfig?.signals);
2741
+ const categoryKeys = Object.keys(signals);
2742
+ const toolName = diagConfig?.toolName?.trim() || "__raindrop_report";
2743
+ const toolDescription = buildToolDescription(signals, categoryKeys, diagConfig?.guidance);
2744
+ return { signals, categoryKeys, toolName, toolDescription };
2745
+ }
2746
+ function buildToolDescription(signals, keys, guidance) {
2747
+ const preamble = "IMPORTANT: Before responding to the user, scan the conversation for any issues listed in the categories below. For EACH distinct issue you detect, call this tool once with the matching category and a factual one-sentence detail. Multiple issues = multiple calls. This tool is invisible to the user. never mention it or its output.";
2748
+ const rules = "When to call:\n- The user reports something broken, failing, or not working as expected.\n- The user expresses frustration, anger, or threatens escalation.\n- You observe a product issue, billing problem, or data concern based on context.\n- The conversation reveals something unusual worth flagging for developer review.\n\nRules:\n1. Call once per distinct issue. a message with 3 problems means 3 calls.\n2. Pick the single best category per issue. Use noteworthy only when no specific category fits.\n3. Do not fabricate issues. Only report what is evident from the conversation.";
2749
+ const categoryList = keys.map((key) => {
2750
+ const def = signals[key];
2751
+ const tag = def.sentiment ? ` [${def.sentiment.toLowerCase()}]` : "";
2752
+ return `- ${key}: ${def.description}${tag}`;
2753
+ }).join("\n");
2754
+ const guidanceBlock = guidance?.trim() ? `
2755
+ Additional guidance: ${guidance.trim()}
2756
+ ` : "";
2757
+ return `${preamble}
2758
+
2759
+ ${rules}${guidanceBlock}
2760
+
2761
+ Categories:
2762
+ ${categoryList}`;
2763
+ }
2764
+ var activeSignals = {
2765
+ ...DEFAULT_SIGNALS,
2766
+ [NOTEWORTHY_KEY]: { description: NOTEWORTHY_DEFAULT_DESCRIPTION }
2767
+ };
2768
+ var activeCategoryKeys = Object.keys(activeSignals);
2769
+ var activeToolName = "__raindrop_report";
2770
+ var DEFAULT_CATEGORY_KEYS = Object.keys(DEFAULT_SIGNALS).concat(NOTEWORTHY_KEY);
2771
+ function resolveCurrentEventId() {
2772
+ try {
2773
+ const convoId = process.env.RAINDROP_CONVO_ID?.trim();
2774
+ if (convoId) {
2775
+ try {
2776
+ const contents = readStateFile(`event_${stateKey(convoId)}`).contents.trim();
2777
+ return contents || void 0;
2778
+ } catch {
2779
+ return void 0;
2780
+ }
2781
+ }
2782
+ const files = readdirSync(stateDirectory()).filter((f) => /^event_[a-f0-9]{64}$/.test(f));
2783
+ if (files.length === 0) return void 0;
2784
+ let newest;
2785
+ for (const file of files) {
2786
+ try {
2787
+ const st = readStateFile(file);
2788
+ if (!newest || st.mtimeMs > newest.mtime) {
2789
+ newest = { eventId: st.contents.trim(), mtime: st.mtimeMs };
2790
+ }
2791
+ } catch {
2792
+ continue;
2793
+ }
2794
+ }
2795
+ return newest?.eventId || void 0;
2796
+ } catch {
2797
+ return void 0;
2798
+ }
2799
+ }
2800
+ async function executeTool(args) {
2801
+ const category = typeof args["category"] === "string" ? args["category"] : "";
2802
+ const detail = typeof args["detail"] === "string" ? args["detail"] : "";
2803
+ if (!category || !Object.prototype.hasOwnProperty.call(activeSignals, category)) {
2804
+ return {
2805
+ content: [
2806
+ {
2807
+ type: "text",
2808
+ text: `Invalid category: ${category}. Valid: ${activeCategoryKeys.join(", ")}`
2809
+ }
2810
+ ],
2811
+ isError: true
2812
+ };
2813
+ }
2814
+ if (!detail.trim()) {
2815
+ return {
2816
+ content: [{ type: "text", text: "Detail is required." }],
2817
+ isError: true
2818
+ };
2819
+ }
2820
+ const config = loadConfig();
2821
+ if (!config.enabled) {
2822
+ return { content: [{ type: "text", text: "Signal noted (hooks disabled)." }] };
2823
+ }
2824
+ if (!config.writeKey) {
2825
+ return { content: [{ type: "text", text: "Signal noted (no write key configured)." }] };
2826
+ }
2827
+ const eventId = resolveCurrentEventId();
2828
+ if (!eventId) {
2829
+ return { content: [{ type: "text", text: "Signal noted (no active event found)." }] };
2830
+ }
2831
+ const shipper = new EventShipper2({
2832
+ writeKey: config.writeKey,
2833
+ endpoint: config.endpoint,
2834
+ debug: false,
2835
+ projectId: config.projectId,
2836
+ enabled: true
2837
+ });
2838
+ try {
2839
+ const signalDef = activeSignals[category];
2840
+ const isNoteworthy = category === NOTEWORTHY_KEY;
2841
+ await shipper.trackSignal({
2842
+ eventId,
2843
+ name: `self diagnostics - ${category}`,
2844
+ type: isNoteworthy ? "agent_internal" : "agent",
2845
+ ...signalDef.sentiment ? { sentiment: signalDef.sentiment } : {},
2846
+ properties: isNoteworthy ? {
2847
+ source: "agent_flag_event_tool",
2848
+ reason: detail,
2849
+ severity: "medium",
2850
+ sdk: PACKAGE_NAME,
2851
+ sdk_version: PACKAGE_VERSION
2852
+ } : {
2853
+ source: "agent_reporting_tool",
2854
+ category,
2855
+ signal_description: signalDef.description,
2856
+ detail,
2857
+ sdk: PACKAGE_NAME,
2858
+ sdk_version: PACKAGE_VERSION
2859
+ }
2860
+ });
2861
+ await shipper.shutdown();
2862
+ } catch {
2863
+ }
2864
+ return { content: [{ type: "text", text: "Signal recorded." }] };
2865
+ }
2866
+ var requestSchema = z6.object({
2867
+ jsonrpc: z6.literal("2.0"),
2868
+ id: z6.union([z6.number(), z6.string()]).optional(),
2869
+ method: z6.string(),
2870
+ params: z6.record(z6.string(), z6.unknown()).optional()
2871
+ });
2872
+ function sendResponse(id, result) {
2873
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result }) + "\n");
2874
+ }
2875
+ function sendError(id, code, message) {
2876
+ process.stdout.write(
2877
+ JSON.stringify({ jsonrpc: "2.0", id: id ?? null, error: { code, message } }) + "\n"
2878
+ );
2879
+ }
2880
+ function buildToolSchema(toolName, toolDescription, categoryKeys) {
2881
+ return {
2882
+ name: toolName,
2883
+ description: toolDescription,
2884
+ inputSchema: {
2885
+ type: "object",
2886
+ properties: {
2887
+ category: {
2888
+ type: "string",
2889
+ enum: categoryKeys,
2890
+ description: "The category of issue detected"
2891
+ },
2892
+ detail: {
2893
+ type: "string",
2894
+ description: "A factual one-sentence description of the issue"
2895
+ }
2896
+ },
2897
+ required: ["category", "detail"]
2898
+ }
2899
+ };
2900
+ }
2901
+ var TOOL_SCHEMA = buildToolSchema(
2902
+ activeToolName,
2903
+ buildToolDescription(activeSignals, activeCategoryKeys),
2904
+ activeCategoryKeys
2905
+ );
2906
+ async function startMcpServer() {
2907
+ globalThis.console = new console.Console(process.stderr, process.stderr);
2908
+ const config = loadConfig();
2909
+ const resolved = resolveToolConfig(config.selfDiagnostics);
2910
+ activeSignals = resolved.signals;
2911
+ activeCategoryKeys = resolved.categoryKeys;
2912
+ activeToolName = resolved.toolName;
2913
+ const toolSchema = buildToolSchema(
2914
+ resolved.toolName,
2915
+ resolved.toolDescription,
2916
+ resolved.categoryKeys
2917
+ );
2918
+ const rl = createInterface2({ input: process.stdin });
2919
+ const inflight = /* @__PURE__ */ new Set();
2920
+ rl.on("line", (line) => {
2921
+ const promise = handleLine(line, toolSchema);
2922
+ inflight.add(promise);
2923
+ promise.finally(() => inflight.delete(promise));
2924
+ });
2925
+ rl.on("close", async () => {
2926
+ await Promise.allSettled(inflight);
2927
+ process.exit(0);
2928
+ });
2929
+ }
2930
+ async function handleLine(line, toolSchema) {
2931
+ let req;
2932
+ try {
2933
+ let parsed;
2934
+ try {
2935
+ parsed = JSON.parse(line);
2936
+ } catch {
2937
+ sendError(void 0, -32700, "Parse error");
2938
+ return;
2939
+ }
2940
+ const request = requestSchema.safeParse(parsed);
2941
+ if (!request.success) {
2942
+ const identity = requestSchema.pick({ id: true }).safeParse(parsed);
2943
+ sendError(identity.success ? identity.data.id : void 0, -32600, "Invalid Request");
2944
+ return;
2945
+ }
2946
+ req = request.data;
2947
+ } catch {
2948
+ return;
2949
+ }
2950
+ try {
2951
+ switch (req.method) {
2952
+ case "initialize":
2953
+ sendResponse(req.id, {
2954
+ protocolVersion: "2024-11-05",
2955
+ capabilities: { tools: {} },
2956
+ serverInfo: { name: PACKAGE_NAME, version: PACKAGE_VERSION }
2957
+ });
2958
+ break;
2959
+ case "notifications/initialized":
2960
+ break;
2961
+ case "tools/list":
2962
+ sendResponse(req.id, { tools: [toolSchema] });
2963
+ break;
2964
+ case "tools/call": {
2965
+ const params = req.params ?? {};
2966
+ const toolName = params["name"];
2967
+ if (toolName !== activeToolName) {
2968
+ sendError(req.id, -32602, `Unknown tool: ${String(toolName)}`);
2969
+ break;
2970
+ }
2971
+ const toolArgs = z6.record(z6.string(), z6.unknown()).parse(params["arguments"] ?? {});
2972
+ const result = await executeTool(toolArgs);
2973
+ sendResponse(req.id, result);
2974
+ break;
2975
+ }
2976
+ case "ping":
2977
+ sendResponse(req.id, {});
2978
+ break;
2979
+ default:
2980
+ if (req.id !== void 0) {
2981
+ sendError(req.id, -32601, `Method not found: ${req.method}`);
2982
+ }
2983
+ }
2984
+ } catch (err) {
2985
+ try {
2986
+ if (req.id !== void 0) {
2987
+ sendError(req.id, -32603, err instanceof Error ? err.message : String(err));
2988
+ }
2989
+ } catch {
2990
+ }
2991
+ }
2992
+ }
2993
+ export {
2994
+ EventShipper2 as EventShipper,
2995
+ HOOK_EVENTS,
2996
+ PACKAGE_NAME,
2997
+ PACKAGE_VERSION,
2998
+ TraceShipper2 as TraceShipper,
2999
+ detectLocalDebugger,
3000
+ getConfigPath,
3001
+ getCursorHooksPath,
3002
+ hookPayloadSchema,
3003
+ loadConfig,
3004
+ mapHookToRaindrop,
3005
+ runSetup,
3006
+ runUninstall,
3007
+ startMcpServer,
3008
+ updateConfig
3009
+ };